Java 03

Main 클래스, main 메서드, println으로 첫 Java 프로그램 읽기

첫 코드부터 public, static, void, main, System.out.println이 몰려와서 무엇이 실행 입구이고 무엇이 출력 명령인지 헷갈린다.

근거 · 교안 p21-p29

Main 클래스, main 메서드, println으로 첫 Java 프로그램 읽기 대표 이미지

1장: 실행 입구 찾기: 프로그램은 어디서 시작하나요?

솔라의 노트북 화면에는 이제 막 배우기 시작한 Java 코드가 떠 있었다. 단 몇 줄뿐인 짧은 프로그램. 하지만 솔라의 시선은 한 줄에 단단히 붙들려 있었다.

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

책에서는 public static void main(String[] args)이 문장이 Java 프로그램의 ‘실행 시작점’이라고 설명했다. 하지만 솔라의 눈에는 그저 의미를 알 수 없는 단어들의 나열, 길고 복잡한 하나의 주문처럼 보일 뿐이었다.

“이게 시작점이라는데… 꼭 암호를 푸는 것 같아.”

솔라가 혼잣말처럼 중얼거렸다. 옆에서 조용히 자신의 책을 읽던 언니 루나가 솔라의 화면으로 시선을 돌렸다.

“어디가 제일 헷갈리는데?”

“전부. public static void main… 이 단어들을 전부 알아야 프로그램이 시작한다는 뜻인가? 그리고 그 바로 아래 줄에 있는 System.out.println은 또 뭐고? 둘 다 컴퓨터한테 뭔가를 시키는 명령 같은데, 뭐가 시작이고 뭐가 행동인지 모르겠어.”

솔라의 말에는 답답함이 묻어났다. 마치 처음 보는 건물의 정문과 비상구가 똑같이 생긴 문처럼 보여 어디로 들어가야 할지 모르는 기분이었다. 둘 다 문은 문인데, 역할이 완전히 다르지 않은가.

루나는 솔라의 말에 고개를 끄덕이더니, 설명 대신 마우스에 손을 얹었다.

“한번 직접 확인해보자. 일단 이 코드를 실행해볼래?”

솔라는 ‘실행(Run)’ 버튼을 눌렀다. 잠시 후, 화면 아래쪽 콘솔 창에 Hello, World!라는 문장이 깔끔하게 찍혔다.

“실행은 잘 되네. 약속대로 글자가 나왔어.”

“좋아. 이제 딱 한 글자만 바꿔보자.”

루나가 말했다.

“저기 main이라는 단어 보이지? 그 뒤에 숫자 1을 붙여서 main1로 만들어봐.”

솔라는 의아했지만, 루나가 시키는 대로 mainmain1으로 수정했다. 다른 것은 아무것도 건드리지 않았다. public, static, void 같은 낯선 단어들도, 괄호와 중괄호도 모두 그대로였다.

public class Main {
    public static void main1(String[] args) { // 'main'을 'main1'로 변경
        System.out.println("Hello, World!");
    }
}

“자, 이제 다시 실행해봐.”

솔라는 아까와 똑같이 실행 버튼을 눌렀다. 하지만 이번에는 콘솔 창에 반가운 Hello, World! 대신, 붉은색의 오류 메시지가 길게 나타났다. 성공적으로 실행되지 않았다는 뜻이었다.

솔라는 눈을 가늘게 뜨고 오류 메시지를 읽어 내려갔다.

”…main 메서드를 찾을 수 없습니다… (Main method not found...)”

순간 솔라의 머릿속에 무언가 번쩍했다.

“어? 언니, 이거 봐. main1이라고 바꿨더니 main을 못 찾겠다고 화를 내는데?”

“그렇지. 다른 건 그대로인데도 말이야.”

루나는 차분하게 대답했다.

“솔라, 예전에 우리가 자바 코드를 실행하는 건 자바 가상 머신(JVM)이라는 기계라고 이야기했던 거 기억나? 그 기계는 프로그램을 받으면, 어디서부터 일을 시작해야 할지 약속된 장소를 찾아다녀. 마치 수많은 문이 있는 커다란 건물에서 ‘정문’이라고 쓰인 간판을 찾는 것처럼.”

솔라는 두 개의 코드, 성공한 코드와 실패한 코드를 번갈아 보았다. 실패한 코드에도 public, static, void는 모두 있었다. System.out.println도 그대로였다. 바뀐 것은 오직 main이라는 이름뿐이었다.

그제야 솔라는 깨달았다. public static void main(String[] args)이라는 긴 문장이 통째로 하나의 주문이 아니었다. 그 안에서 가장 핵심적인 역할을 하는 열쇠, JVM이 찾는 간판은 바로 main이라는 이름 그 자체였던 것이다.

“아… 알겠다! public이나 static 같은 다른 단어들이 아니라, main이라는 이름이 중요한 거였구나. JVM은 다른 건 보지도 않고 무조건 main이라고 이름 붙은 곳을 찾아와서 거기서부터 프로그램을 시작하는 거네. 내가 그걸 main1로 바꾸니까, JVM이 정문을 못 찾고 헤매다가 그냥 돌아가 버린 거고.”

솔라는 방금 전까지 거대한 벽처럼 느껴졌던 코드 한 줄에서 명확한 표지판 하나를 발견한 기분이었다. 이제 다른 Java 코드를 보더라도 어디서부터 이야기가 시작될지, 그 입구를 찾아낼 자신감이 생겼다. main이라는 세 글자만 찾으면 되니까.

생각이 정리되자 새로운 궁금증이 고개를 들었다.

솔라는 다시 화면을 가리켰다. main이라는 입구 안쪽에 놓인 System.out.println("Hello, World!"); 줄이었다.

“좋아. 그럼 main이 프로그램의 시작을 알리는 현관문인 건 알겠어. 그럼 이 println이라는 건 뭐야? 현관문으로 들어온 손님한테 ‘안녕하세요!’라고 인사하는 행동 같은 건가?“

2장: 화면에 말 걸기: 콘솔에 글자를 출력하는 방법

솔라의 질문에 루나는 대답 대신, 솔라의 노트북에 떠 있는 코드를 조용히 수정했다. main 메서드의 중괄호 {} 안에 유일하게 존재하던 한 줄. 루나는 그 System.out.println("Hello, World!"); 코드를 망설임 없이 지워버렸다.

이제 화면에 남은 코드는 뼈대만 앙상했다. 프로그램의 입구인 main은 그대로였지만, 그 안은 텅 비어 있었다. 마치 현관문은 활짝 열려 있는데, 집 안에는 아무도, 아무 가구도 없는 텅 빈 공간처럼 보였다.

public class Main {
    public static void main(String[] args) {
        // 이 안이 비어 있습니다.
    }
}

“자, 솔라. 이 상태로 다시 실행해볼래?”

루나가 말했다. 솔라는 고개를 갸웃하며 실행 버튼을 눌렀다. 잠시 후, 프로그램은 아무런 오류 메시지 없이 종료되었다. 하지만 화면 아래 콘솔 창에는 아무것도 나타나지 않았다. Hello, World!는커녕, 점 하나 찍히지 않은 깨끗한 빈 공간이었다.

“어라? 오류는 안 나네. 분명히 프로그램이 실행된 건데… 왜 아무 일도 안 일어나지?”

솔라가 의아해하며 중얼거렸다. 이전 장에서 mainmain1로 바꿨을 때는 ‘정문을 못 찾겠다’며 시끄럽게 오류를 뿜어냈던 것과는 전혀 다른 반응이었다.

“솔라, 네가 아까 한 비유가 아주 정확했어. main은 현관문, 그 안에 있는 코드는 손님에게 할 행동이라고 했지?”

“응.”

“방금 우리가 한 일은, 현관문은 그대로 둔 채 집 안에서 손님을 맞이할 사람과 행동을 전부 없애버린 거야. 그러니 JVM이라는 손님은 약속대로 main이라는 현관문을 열고 들어왔지만, 안에서 아무런 할 일이 없으니 그냥 그대로 집을 나간 거지.”

그제야 솔라의 얼굴에 아하, 하는 표정이 떠올랐다. main과 그 안의 코드는 역할이 완전히 분리되어 있었다. main의 역할은 오직 JVM에게 ‘여기서부터 시작하세요!’라고 알려주는 것뿐, 그 자체로는 아무런 행동도 하지 않았다. 화면에 글자를 찍는 것 같은 구체적인 ‘행동’은 그 안쪽에 별도로 적어주어야만 했다.

“그럼 System.out.println이라는 게 바로 그 ‘행동’을 시키는 진짜 명령이었구나! 현관문이 아니라, ‘어서 오세요!’라고 외치는 목소리 그 자체였네.”

“맞아. 이제 그 목소리를 다시 넣어보자. 이번엔 네가 직접.”

루나는 방금 지웠던 빈 중괄호 안을 커서로 가리켰다.

System.out.println이라고 입력하고, 괄호 안에 따옴표를 넣은 다음, 네가 화면에 보여주고 싶은 말을 아무거나 써봐.”

솔라는 잠시 고민하더니, 키보드를 두드리기 시작했다.

public class Main {
    public static void main(String[] args) {
        System.out.println("나의 이름은 솔라");
    }
}

“됐어?”

“응. 이제 실행해봐.”

솔라는 기대를 품고 실행 버튼을 눌렀다. 그러자 콘솔 창에 방금 자신이 입력했던 문장이 그대로 나타났다.

나의 이름은 솔라

솔라는 신기한 듯 화면을 바라보았다. 이번에는 단순히 주어진 코드를 따라 친 것이 아니었다. 자신이 원하는 문장을 직접 입력했고, 컴퓨터가 그 말을 그대로 화면에 보여준 첫 경험이었다.

“와… 진짜 내가 쓴 대로 나오네.”

솔라는 재미있다는 듯이 따옴표 안의 문장을 “언니, 안녕!”으로 바꾸고 다시 실행했다. 결과는 예상대로였다. 콘솔 창에는 언니, 안녕!이 찍혔다.

이제 솔라는 mainSystem.out.println을 헷갈리지 않을 자신이 있었다. main은 프로그램의 시작 위치를 알려주는 약속된 주소지일 뿐. 그 안에서 실제로 무언가를 하게 만드는 힘은 System.out.println이라는 구체적인 명령문에 있었다. 마치 편지 봉투에 적힌 주소(main)와 편지지 안에 담긴 내용(System.out.println 안의 글자)이 다른 역할을 하는 것처럼.

“이제 확실히 알겠어. System.out.println()은 화면에 글자를 출력하라는 명령이구나. 괄호 안에 뭘 넣느냐에 따라 컴퓨터가 하는 말이 달라지는 거고.”

“정확해. 그 명령을 조금 더 자세히 들여다보면, System이 가진 out(출력) 장치를 통해 println(한 줄을 출력해라) 하고 시키는 구조야. 지금은 그냥 System.out.println()이 ‘화면에 한 줄 출력!’이라는 뜻을 가진 하나의 세트라고 기억해두면 돼.”

루나의 설명에 솔라는 고개를 끄덕였다. 이제 코드의 두 줄은 더 이상 뒤죽박죽 섞인 암호가 아니었다. 하나는 시작을 알리는 문, 다른 하나는 구체적인 행동을 지시하는 명령. 역할이 명확히 구분되자 마음이 한결 편안해졌다.

자신감을 얻은 솔라는 콘솔에 출력된 자신의 메시지를 뿌듯하게 바라보다가, 문득 새로운 궁금증이 생겼다.

“언니, 그런데 이건 항상 한 줄만 출력되네. 만약 내가 자기소개를 여러 줄에 걸쳐서 하고 싶으면 어떡해? 그리고 코드에 ‘이 부분은 내 이름을 출력하는 곳이야’ 같은 메모를 남겨두고 싶은데, 그런 건 어떻게 하지?“

3장: 코드와 대화하기: 출력 꾸미고 코드 설명하기

솔라의 질문이 채 끝나기도 전에, 루나는 이미 키보드 위로 손을 옮기고 있었다. 화면에는 솔라가 방금 전 뿌듯하게 실행했던 코드가 남아 있었다.

public class Main {
    public static void main(String[] args) {
        System.out.println("나의 이름은 솔라");
    }
}

루나는 아무 말 없이 따옴표 안의 문장을 수정했다. 솔라가 미처 그 변화를 다 읽기도 전에, 루나는 코드를 다시 한 번 바꿨다. 이번에는 System.out.println 줄 바로 위, 비어있던 공간에 새로운 문장을 추가했다. 화면에는 이제 솔라가 한 번도 본 적 없는 기호들이 나타나 있었다.

public class Main {
    public static void main(String[] args) {
        // 이 부분은 솔라의 자기소개입니다.
        System.out.println("나의 이름은 솔라\n만나서 반가워요.");
    }
}

“자, 뭐가 달라졌는지 보지 말고, 그냥 다시 실행해볼래?”

루나가 마우스에서 손을 떼며 말했다. 솔라는 호기심 가득한 눈으로 실행 버튼을 눌렀다. 이전과는 또 다른 결과가 콘솔 창에 나타났다.

나의 이름은 솔라
만나서 반가워요.

“어! 줄이 바뀌었어!”

솔라가 외쳤다. 분명 System.out.println 명령은 한 번만 썼는데, 마치 두 번 쓴 것처럼 결과가 두 줄에 걸쳐 출력된 것이다. 솔라는 재빨리 코드와 결과를 비교했다.

“잠깐만… \n 이라는 글자는 화면에 안 나왔네? 대신 거기서 줄이 바뀐 거야?”

솔라는 System.out.println 안에 쓴 모든 글자가 그대로 출력될 거라고 생각했다. 하지만 \n은 흔적도 없이 사라지고, 그 자리에 줄바꿈이라는 효과만 남겼다. 마치 보이지 않는 손이 엔터키를 눌러준 것 같았다.

“그리고 이 위에 쓴 문장은… 아예 아무것도 안 나왔네?”

솔라는 코드의 // 이 부분은 솔라의 자기소개입니다. 라는 줄을 손가락으로 가리켰다. 이 문장은 콘솔 출력에 아무런 영향도 주지 않았다.

“맞아. 지금 솔라 네가 두 가지 종류의 ‘숨겨진 약속’을 발견한 거야.”

루나가 차분히 말했다.

“첫 번째 약속은 출력 화면을 위한 거야. \(백슬래시) 기호는 컴퓨터에게 ‘지금부터 평범한 글자가 아니라 특별한 명령이야’라고 알려주는 신호탄 같은 거야. 그래서 \n은 n이라는 글자가 아니라 ‘new line’, 즉 새로운 줄을 만들라는 명령으로 받아들이지. 이런 걸 이스케이프 문자(escape sequence) 라고 불러.”

솔라는 ‘이스케이프’라는 단어를 곱씹었다. ‘탈출하다’라는 뜻. 평범한 문자의 나열에서 탈출해 특별한 기능을 수행한다는 의미로 다가왔다.

“그럼 저 슬래시 두 개 //는 뭐야? 두 번째 약속?”

“응. 그건 출력 화면이 아니라, 코드를 읽는 사람을 위한 약속이야. // 뒤에 오는 글자들은 그 줄이 끝날 때까지 컴퓨터가 완전히 무시해. 프로그램 실행에는 아무런 영향을 주지 않고, 오직 코드를 보는 사람에게 설명을 남기기 위해 사용되지. 이걸 주석(comment) 이라고 해.”

그제야 솔라는 두 기호의 역할이 명확히 구분되기 시작했다. \n은 컴퓨터에게 보내는 비밀 메시지, 출력물을 꾸미기 위한 지시였다. 반면 //는 미래의 나, 혹은 함께 일하는 동료에게 보내는 쪽지, 코드에 대한 설명서였다. 하나는 컴퓨터와의 대화, 다른 하나는 사람과의 대화였던 셈이다.

“그렇구나! System.out.println 안에 넣는다고 다 똑같은 글자가 아니었네. 어떤 건 화면 모양을 바꾸는 마법 주문(\n)이고, 어떤 건 컴퓨터는 아예 쳐다보지도 않는 나만의 메모(//)였구나.”

솔라는 이제 코드를 보는 눈이 조금 달라진 것을 느꼈다. 이전에는 모든 글자가 컴퓨터를 위한 명령이라고 생각했지만, 이제는 그 안에도 여러 역할과 규칙이 있다는 것을 알게 되었다.

자신감이 붙은 솔라가 말했다.

“좋아, 언니. 그럼 나 한번 시험해봐. 다른 사람이 쓴 코드를 주고, 내가 제대로 읽을 수 있는지 확인해줘.”

루나는 미소를 지으며 새로운 코드 파일을 하나 열었다. 솔라가 지금까지 본 것보다는 조금 더 긴 코드였다.

public class Greeting {
    // 프로그램 실행이 시작되는 곳입니다.
    public static void main(String[] args) {
        System.out.println("================");
        System.out.println("이름: 솔라");
        System.out.println("취미: 코딩\t특기: 질문하기"); // 탭(\t)으로 간격 맞춤
        System.out.println("================");
    }
}

솔라는 잠시 숨을 고르고 코드를 천천히 읽어 내려갔다. 더 이상 낯선 암호 해독이 아니었다. 배운 내용을 바탕으로 한 논리적인 추리였다.

“음… 일단 main이 여기 있으니까 여기서부터 시작하겠네. 컴퓨터는 먼저 ============== 이걸 한 줄 출력하고. 그 다음 줄에 ‘이름: 솔라’를 출력하고. 어, 그 다음 줄은… \t? 이것도 이스케이프 문자 같은데, n이 new line이었으니까 t는… tab인가?”

“한번 그렇게 예상하고 계속 읽어봐.”

“좋아, 그럼 ‘취미: 코딩’을 출력하고, 탭키를 누른 것처럼 큰 간격을 벌린 다음에 ‘특기: 질문하기’가 같은 줄에 찍힐 것 같아. 그리고 이 // 뒤에 있는 ‘탭으로 간격 맞춤’이라는 글자는 그냥 설명이니까 무시하고. 마지막으로 ==============를 또 출력하고 끝나겠네.”

솔라는 자신의 추리에 확신을 담아 말했다. 루나는 아무 말 없이 실행 버튼을 눌러 솔라의 예상이 맞았음을 보여주었다. 콘솔에는 솔라가 예측한 그대로의 결과가 깔끔하게 나타났다.

================
이름: 솔라
취미: 코딩	특기: 질문하기
================

솔라는 화면에 나타난 결과를 보며 환하게 웃었다. 이제 그녀에게 Java 첫 코드는 더 이상 알 수 없는 기호의 집합이 아니었다. 어디서 시작해서(main), 어떤 말을 하고(System.out.println), 그 말을 어떻게 꾸미고(\n, \t), 코드에 어떤 설명을 남겼는지(//) 명확하게 읽어낼 수 있는, 잘 짜인 한 편의 설명서처럼 보였다. 이제 어떤 코드를 만나든, 그 코드와 대화할 첫 번째 방법을 알게 된 것이다.