Java 08
Scanner 입력을 위한 세 가지 질문: import, 객체 생성, System.in
입력을 받으려는 것뿐인데 import, new Scanner(System.in), 객체 생성까지 필요해서 단계가 과해 보인다.
근거 · 교안 p60-p64
1장: 왜 Scanner를 ‘가져와야(import)’ 하나요?
솔라의 손가락이 키보드 위에서 멈칫했다. 화면에는 이제 막 자바 공부를 시작한 사람이라면 누구나 한 번쯤은 써보았을, 짧은 코드 몇 줄이 전부였다. 사용자의 이름을 입력받아 인사말을 출력하는 간단한 프로그램. 하지만 솔라의 눈길은 코드의 논리가 아닌, 맨 윗줄에 붉은 밑줄이 그어진 Scanner라는 단어에 박혀 있었다.
“그냥… 입력 좀 받겠다는데.”
작게 터져 나온 혼잣말에는 짜증이 섞여 있었다. 분명 책에서는 Scanner가 사용자 입력을 받을 때 쓰는 도구라고 했다. 하지만 막상 쓰려고 하니, 코드 편집기는 Scanner가 대체 무엇인지 모르겠다며 성을 냈다. 해결책으로 제시된 건 코드 첫 줄에 import java.util.Scanner;를 추가하라는 것. 마치 알 수 없는 주문 같았다.
그때, 방으로 들어온 언니 루나가 솔라의 굳은 표정을 보고는 조용히 옆에 섰다.
“빨간 줄이 또 심술이네.”
“언니, 이건 좀 이상해. System.out.println으로 화면에 글자를 찍을 땐 이런 거 안 물어봤잖아. 그냥 쓰면 됐지. 그런데 왜 키보드 입력을 받으려고만 하면 Scanner가 뭐냐고 묻고, import라는 걸 꼭 해야만 해? 그냥 입력받는 게 그렇게 특별한 일이야?”
솔라는 답답한 마음을 쏟아냈다. 입력을 받는 건 출력을 하는 것만큼이나 기본적인 일처럼 느껴졌다. 그런데 왜 한쪽은 간단하고, 다른 한쪽은 무언가를 ‘가져오는’ 복잡한 단계를 거쳐야 하는지 이해할 수 없었다. 마치 당연히 집에 있어야 할 숟가락을 쓸 때마다 옆집에 가서 빌려와야 하는 기분이었다.
루나는 솔라의 화면을 잠시 들여다보더니, 고개를 끄덕였다.
“그럼, 그 주문처럼 보이는 줄을 한번 지워볼까? 그리고 그 아래에, Scanner sc;라고 쓴 줄은 그대로 둬 봐.”
솔라는 루나의 말대로 import 문장을 지웠다. 그러자 Scanner라는 단어 아래의 붉은 밑줄이 더 선명해지며, 편집기 한쪽에 ‘기호를 찾을 수 없습니다(cannot find symbol)‘라는 경고가 떴다.
“오류가 더 확실해졌네.”
“응. 그럼 이제 그 바로 아랫줄에 다른 걸 한번 써보자. String name; 이라고.”
솔라는 키보드를 타닥거렸다. 이상했다. String name; 이라는 코드에는 아무런 밑줄도, 경고도 나타나지 않았다. String 역시 글자들을 다루는, 어엿한 이름이 있는 도구인데.
솔라는 화면의 두 줄을 번갈아 쳐다보았다.
Scanner sc; // cannot find symbol
String name; // OK
“잠깐만… 왜 Scanner는 모르겠다고 하고, String은 아무 말 안 하는 거지? 둘 다 내가 지금 이 파일 안에서 만든 게 아니잖아. 외부에서 만들어진 걸 쓰는 건 똑같은데.”
바로 그 지점이었다. 솔라 스스로가 이상한 점을 정확히 짚어낸 순간이었다. 루나는 그제야 입을 열었다.
“솔라 네가 지금 있는 이 작업 공간을 하나의 작은 공방이라고 생각해봐. 그리고 자바라는 세상에는 아주 기본적인 연장들이 있어. 망치나 펜, 자 같은 것들 말이야. 너무 기본적이라서 어떤 공방을 차리든 항상 책상 위에 기본으로 놓여있는 도구들이지. String이 바로 그런 거야.”
“기본 도구…?”
“응. 하지만 만약 네가 아주 정밀한 시계를 만들고 싶어서 특수한 확대경이 필요해지면 어떨까? 그건 모든 공방에 있는 물건이 아니야. 아마 공방 단지 한쪽에 있는 ‘정밀 도구 보관소’ 같은 곳에 가서, ‘A-3번 선반의 확대경을 지금부터 제 공방에서 쓰겠습니다’라고 사용 목록에 기록하고 가져와야 할 거야.”
루나의 비유에 솔라의 눈이 동그래졌다. import 라는 주문이 다른 의미로 보이기 시작했다.
“그럼… import java.util.Scanner; 라는 건… ‘정밀 도구 보관소(java)’ 안에 있는 ‘다용도 도구함(util)‘에서 ‘문자열 분석기(Scanner)‘라는 도구를 내 작업장으로 가져오겠다고 신고하는 행위 같은 거야?”
“바로 그거야. import는 마법 주문이 아니었어. 내 코드에 아직 알려지지 않은 외부의 전문 도구를 ‘어디에서’ 가져와 쓸 것인지 명확히 알려주는 이정표였던 거지. 자바는 우리가 Scanner라고만 말하면, 그게 수많은 도구 보관소 중 어디에 들어있는 도구인지 알 수가 없으니까.”
솔라는 고개를 끄덕이며 import java.util.Scanner; 문장을 다시 코드 맨 위에 타이핑했다. 거짓말처럼 Scanner sc; 아래의 붉은 밑줄이 사라졌다. 이제 자바는 Scanner가 어느 보관소에서 온 도구인지 알게 된 것이다.
“아하! 그러니까 import는 내가 만들지 않은 전문 도구를 빌려오는 거구나. 기본 도구가 아니니까 어디서 가져오는지 출처를 밝히는 거였어.”
명령어의 장막이 걷히고 그 역할이 뚜렷해지자 후련함이 밀려왔다. 하지만 그 후련함은 잠시였다. 솔라는 코드를 완성하기 위해 다음 줄을 써 내려갔다.
Scanner sc = new Scanner(System.in);
그러자 새로운 의문이 고개를 들었다.
“좋아. import로 ‘스캐너’라는 전문 도구를 공방 책상 위로 가져왔다는 건 알겠어. 그런데… 왜 또 new를 써서 ‘새로운 스캐너’를 만들어야 하는 거야? 그냥 가져온 그 도구를 바로 쓰면 안 되는 건가?“
2장: ‘새로운 Scanner(System.in) 객체 생성’은 왜 필요한가요?
솔라의 책상 위, 모니터 화면의 코드는 아까보다 한 줄이 줄어 있었다. import로 Scanner의 정체를 알려주는 데 성공하자마자, 솔라는 자신의 다음 의문을 증명해 보기로 마음먹었다. 그녀는 방금 전까지 멀쩡하게 작동하던 Scanner sc = new Scanner(System.in); 줄에서 new Scanner(System.in) 부분을 지워버렸다. 마치 불필요한 장식을 떼어내는 듯한 단호한 손길이었다. 이제 코드에는 Scanner sc; 라는 선언만 덩그러니 남았다.
‘전문 도구를 빌려왔으면, 바로 그걸 쓰면 되잖아. 왜 굳이 ‘새로운’ 도구를 또 만들어?’ 솔라는 자신의 생각이 맞다는 걸 보여주고 싶었다. 그녀는 다음 줄에 키보드 입력을 받아 name이라는 변수에 저장하는 코드를 자신만의 방식으로 고쳐 적었다. import로 가져온 Scanner라는 도구 이름을 그대로 사용해서.
import java.util.Scanner;
public class Hello {
public static void main(String[] args) {
String name = Scanner.nextLine(); // <-- 이 줄에서 오류가 발생합니다.
System.out.println("Hello, " + name);
}
}
역시나, Scanner.nextLine() 부분에 붉은 밑줄이 그어졌다. 이번에는 ‘기호를 찾을 수 없다’는 메시지가 아니었다. 조금 더 복잡한 내용의 오류였다. 솔라는 미간을 짚었다. 그녀의 가설은 보기 좋게 실패했다. import로 공방에 들여온 ‘스캐너’라는 도구는, 그 자체로는 아무것도 할 수 없는 듯했다. 화면의 붉은 밑줄이 그 증거였다. 답답한 마음에 솔라가 나지막이 중얼거렸다.
“도구를 가져왔으면 바로 쓸 수 있어야지… 왜 또 뭔가를 ‘새로’ 만들어야 하는 건데?”
그 혼잣말을 들은 루나는, 이 모든 과정을 지켜보다가 솔라의 책상에 놓인 스케치북과 펜을 조용히 가리켰다.
“솔라, 네가 만약 특별한 모양의 조각을 만들려고 해. 그래서 인터넷에서 최신 3D 프린터의 ‘설계도’ 파일을 내려받았어. 그게 바로 import야. 네 컴퓨터에 ‘이런이런 부품과 구조로 작동하는 멋진 기계’의 정보가 생긴 거지.”
루나는 말을 이었다.
“자, 그럼 이제 그 설계도 파일을 가지고 나무를 자를 수 있을까? 설계도 그림을 나무에 대고 문지르면 나무가 잘려?”
“말도 안 돼.”
솔라가 피식 웃으며 대답했다. 너무나 당연한 이야기였다.
“설계도는 그냥 종이나 파일일 뿐이잖아. 그걸로 뭔가를 하려면, 설계도에 따라 실제 3D 프린터 ‘기계’를 조립해서 만들어야지. 전원도 연결하고, 필라멘트도 끼우고. 쓸 수 있는 상태의 실물로.”
바로 그 순간, 솔라의 눈이 번쩍 뜨였다. 그녀는 자신의 말과 화면의 코드를 번갈아 보았다. 설계도와 실제 기계. Scanner와 new Scanner(...).
“잠깐만… 설마… import java.util.Scanner;는 ‘스캐너’라는 기계의 설계도를 가져오는 거고…”
솔라의 시선이 new Scanner(System.in)에 꽂혔다.
”…new는 그 설계도를 바탕으로 실제 작동하는 스캐너 ‘장치’ 한 대를 만들어내는 명령어였던 거야?”
루나는 고개를 끄덕였다. 솔라가 스스로 답을 찾아낸 것이다.
Scanner는 ‘입력을 이렇게 저렇게 처리할 수 있다’고 정의해놓은 일종의 설계도, 즉 **클래스(class)**였다. 하지만 설계도 자체는 아무런 일을 할 수 없다. 그 설계도를 보고 실제로 만들어낸 기계, 즉 **객체(object)**만이 일을 할 수 있다. new 키워드는 바로 그 ‘설계도로부터 실제 장치를 만들어내라’는 조립 명령어였던 셈이다.
솔라는 자신이 지웠던 코드를 다시 타이핑하기 시작했다. 이제 Scanner sc = new Scanner(System.in); 이라는 한 줄의 코드가 전혀 다른 의미로 읽혔다.
Scanner sc
‘스캐너 설계도’ 타입의 장치를 만들어서, 그 장치에 sc라는 이름을 붙여주자.
= new Scanner(System.in);
그리고 그 장치는 ‘new’ 명령으로 실제로 만들어진 ‘새로운 스캐너’ 실체여야 해.
“아하! import는 설계도를 빌려오는 거, new는 그 설계도로 진짜 기계를 조립하는 거였구나! 설계도 자체에 대고 ‘입력해!’라고 외칠 수는 없으니까, 실제로 작동할 기계가 필요했던 거네.”
답답했던 과정이 명쾌한 역할 분담으로 보이기 시작했다. 더 이상 new는 불필요하고 과한 단계가 아니었다. 설계도에서 실제품을 만드는, 반드시 필요한 제작 과정이었다. 후련해진 솔라는 완성된 코드를 만족스럽게 바라보았다. 그러다 문득, 조립 명령어의 괄호 안에 들어있던 작은 부품 하나가 눈에 들어왔다.
“알겠어. new Scanner()로 새 스캐너 장치를 만든다는 건 이해했어. 그런데 만들 때 괄호 안에 왜 System.in이라는 걸 꼭 넣어줘야 해? 이건 스캐너를 만드는 데 필요한 재료 같은 건가?“
3장: ‘System.in’은 무엇이며 왜 넣어주나요?
솔라의 손가락이 new Scanner()의 빈 괄호 안에서 망설였다. 설계도(Scanner)로 실제 장치(new Scanner())를 만드는 것까지는 이제 알겠다. 하지만 장치를 만들 때마다 꼭 System.in이라는 낯선 부품을 넣어줘야 하는 건 여전히 개운치 않았다. 마치 멋진 로봇을 조립하는데, 설명서에 ‘반드시 출처 불명의 돌멩이를 넣을 것’이라고 적힌 기분이었다.
‘어쩌면… 이 부품은 필수가 아닐지도 몰라.’
솔라는 호기심을 참지 못하고 new Scanner(System.in)에서 System.in 부분을 지워버렸다. 빈 괄호만 남겼다. new Scanner(). 스캐너 장치를 만드는 데 아무 재료도 쓰지 않겠다는 선언이었다.
엔터 키를 누르기도 전에, 코드 편집기는 즉시 불만을 표시했다. new Scanner() 아래에 붉은 밑줄이 그어졌다. 이번 오류는 ‘기호를 찾을 수 없다’와는 달랐다. ‘Scanner 클래스에 적합한 생성자를 찾을 수 없습니다(No suitable constructor found for Scanner())‘라는, 훨씬 구체적인 메시지였다.
“생성자? 이번엔 또 뭐야…”
솔라는 이 코드가 자신에게 계속해서 새로운 단어를 던지며 시험하는 것 같다고 느꼈다. import의 벽을 넘었더니 new가 나타났고, 이제는 생성자라는 단어가 앞을 가로막았다. 분명한 건, System.in이라는 부품 없이는 스캐너 장치가 아예 만들어지지 않는다는 사실이었다.
루나는 솔라의 화면에 뜬 새로운 오류 메시지를 보고는, 부엌으로 가더니 컵 두 개와 정수기 앞에 섰다. 그리고는 솔라를 불렀다.
“솔라, 이리 와서 물 한 잔 마실래?”
솔라가 어리둥절한 표정으로 다가가자, 루나는 컵 하나를 정수기 물받이 위에 놓았다. 하지만 물이 나오는 꼭지에서 살짝 비켜난 곳에 두었다. 그리고는 버튼을 눌렀다. 물줄기가 쏟아져 나왔지만, 컵을 스쳐 지나가며 그대로 배수구로 빠져나갔다.
“어… 언니, 컵을 옆으로 옮겨야지.”
솔라가 당연하다는 듯 말했다. 루나는 물을 멈추고 솔라를 바라봤다.
“왜? 여기 ‘물을 담을 수 있는 컵’도 있고, ‘물이 나오는 정수기’도 있는데. 컵에 물이 안 받아지네.”
장난 같은 루나의 말에 솔라는 순간 할 말을 잃었다. 너무나 당연해서 설명할 필요도 없는 상황이었다. 하지만 루나의 눈빛은 ‘설명해 봐’라고 말하고 있었다. 솔라는 잠시 생각하다 입을 열었다.
“컵이… 물이 나오는 ‘통로’ 바로 아래에 없으니까. 정수기에서 물이 나오는 곳과 컵의 입구를 연결해줘야 물이 담기지.”
“바로 그거야.”
루나가 컵을 꼭지 바로 아래로 옮기며 말했다. “네가 방금 조립하려던 스캐너 장치도 똑같아. new Scanner()는 ‘무엇이든 읽어 들일 수 있는 강력한 분석 장치’를 만드는 거야. 마치 비어있는 이 컵처럼. 그런데 이 장치는 어디에서 데이터를 읽어와야 할지 스스로 알지 못해. 그냥 준비만 되어 있을 뿐이지.”
루나는 솔라의 컴퓨터 화면을 가리켰다.
“컴퓨터에는 이미 정해진 표준 통로들이 있어. 네가 늘 쓰던 System.out.println()을 생각해 봐. 여기서 out은 ‘표준 출력 통로’를 뜻해. 이 통로는 모니터 화면과 연결되어 있지. 그래서 println 명령을 내리면 그 통로를 통해 글자들이 화면에 나타나는 거야.”
솔라의 눈이 커졌다. System.out을 수없이 썼지만 out의 의미를 생각해 본 적은 없었다. 그러자 System.in의 정체가 퍼즐 조각처럼 맞춰지기 시작했다.
“그럼… System.in은… ‘표준 입력 통로’인 거야? 그리고 그 통로는… 키보드랑 연결되어 있는 거고?”
“정확해. System.in은 자바가 컴퓨터 시스템과 약속한, 키보드로부터 데이터가 들어오는 고정된 ‘수도꼭지’인 셈이지. 그래서 new Scanner(System.in)이라는 코드는, ‘새로운 스캐너 장치를 만들되, 그 장치가 데이터를 받아올 통로로 표준 입력(키보드)을 지정한다’는 뜻이 되는 거야.”
루나의 설명은 System.in이 단순한 재료나 장식품이 아님을 명확히 보여주었다. 그것은 새로 만든 장치가 어디에 연결되어 누구와 소통할지를 정해주는, 가장 핵심적인 통로 지정자였다. 키보드라는 데이터의 원천과 스캐너라는 데이터 처리 장치를 이어주는 연결 호스. 그것이 System.in의 역할이었다.
솔라는 다시 자기 자리로 돌아왔다. 이제 Scanner sc = new Scanner(System.in); 이라는 코드는 더 이상 복잡하고 과한 절차의 나열이 아니었다. 하나의 완결된 이야기처럼 읽혔다.
“알겠다… 전부 다 이해했어.”
솔라는 작게 중얼거렸다. 그녀는 기존 코드를 모두 지우고, 처음부터 다시 한 줄 한 줄 직접 입력하기 시작했다. 마치 새로 배운 언어로 첫 문장을 작문하듯, 각 코드 라인의 의미를 되새기면서.
import java.util.Scanner;
‘먼저, 외부 도구 보관소에서 스캐너 설계도를 가져오고…’
Scanner sc = new Scanner(System.in);
‘그 설계도로 실제 작동할 스캐너 장치를 만들어 sc라고 이름 붙이자. 단, 이 장치는 키보드라는 표준 입력 통로에 연결해서 데이터를 받도록 설정하고.’
System.out.println("이름을 입력하세요:");
‘이제 표준 출력 통로를 통해 화면에 안내 문구를 보여주고…’
String name = sc.nextLine();
‘마지막으로, sc 장치를 사용해서 키보드 통로로 들어오는 한 줄의 글자를 통째로 읽어서, name이라는 이름표를 붙여주자.’
모든 단계가 왜 필요한지 명확히 이해하자, 코드를 작성하는 손끝에 자신감이 넘쳤다. 더 이상 Scanner 코드는 의미를 알 수 없는 주문의 나열이 아니었다. 외부 전문가의 설계도를 빌려와, 키보드에 연결된 실물 장치를 조립하고, 그 장치를 이용해 사용자와 대화하는, 논리적이고 체계적인 과정 그 자체였다. 화면의 커서가 조용히 다음 명령을 기다리고 있었다. 솔라는 미소를 지으며 실행 버튼을 눌렀다. 이제 이 코드는 솔라에게 온전히 길들여진 도구가 되어 있었다.