본문 바로가기

실행 인자와 첫 회원가입

프로그램을 실행할 때 클래스 이름 뒤에 적은 값을 String[] args로 받고, 값이 없을 때도 오류 없이 끝나는 작은 회원가입 프로그램을 만듭니다.

지금까지 만든 프로그램은 출력할 문자열을 소스 코드에 직접 적었습니다. 그래서 회원가입 시작을 다른 문장으로 바꾸려면 소스를 고치고, 저장하고, 다시 컴파일해야 했습니다. 회원가입 프로그램이라면 가입할 사람의 이메일이 매번 다를 텐데, 그때마다 소스를 고칠 수는 없습니다. 프로그램은 그대로 두고 실행할 때마다 다른 값을 넣을 방법이 필요합니다.

가장 간단한 방법은 실행 명령에 값을 붙이는 것입니다. java -cp out Hello처럼 클래스 이름까지 적은 다음, 그 뒤에 띄어쓰기를 하고 값을 이어 적으면 됩니다. 이렇게 클래스 이름 뒤에 적는 값을 실행 인자(command-line argument)라고 합니다. Java는 실행 인자를 적은 순서대로 문자열 목록으로 만들어 main에 넘겨줍니다. 지금까지 그냥 따라 적었던 main(String[] args)의 괄호 안이 바로 그 목록을 받는 자리입니다.

java -cp out ArgumentEcho Java

이 명령에서 -cp out은 java 실행기가 쓰는 옵션이고 ArgumentEcho는 실행할 클래스의 이름입니다. 프로그램에 전달되는 실행 인자는 그 뒤의 Java 하나뿐입니다. 아래 도표는 이 명령이 어떻게 나뉘어 마지막 값만 args에 들어가는지 보여 줍니다.

명령줄의 -cp out은 launcher 옵션, ArgumentEcho는 실행할 클래스 이름, Java는 프로그램 인자이며, 마지막 값만 args[0]이 되고 args.length는 1이 되는 데이터 흐름입니다.

JAVA · DATA FLOW

클래스 이름 뒤의 값이 main의 문자열 배열이 된다

java launcher가 실행 옵션과 클래스 이름을 소비하고, 그 뒤의 값만 순서대로 String[] args에 전달합니다.

명령줄에서 String 배열까지의 인자 경계 PowerShell이 java, -cp out, ArgumentEcho, Java 토큰을 전달합니다. java launcher는 -cp out을 옵션으로, ArgumentEcho를 클래스 이름으로 소비하고 Java만 프로그램 인자로 분류합니다. main은 길이 1인 String 배열을 받아 args[0]에서 Java를 읽습니다. PowerShell 명령 java -cp out ArgumentEcho Java 옵션·클래스·인자를 구분 java launcher 경계를 해석하고 JVM을 시작 실행 옵션 -cp out 클래스 이름 ArgumentEcho 프로그램 인자 [ "Java" ] args에는 프로그램 인자만 전달 main(String[] args) 문자열 배열 length = 1 args[0] = "Java" 출력 결과 first=Java 전달 String[]
클래스 이름 뒤의 값이 main의 문자열 배열이 된다 셸→launcher→main→출력; launcher 안의 옵션·클래스·프로그램 인자 3개 분류를 그대로 보임. 상세 문구와 명령은 바로 아래 원래 설명에서 확인합니다. 토큰 전달 String[] 읽기 PowerShell 명령 java -cp out ArgumentEcho Java 옵션·클래스·인자를 구분 java launcher 경계 해석 · JVM 시작 실행 옵션 -cp out 클래스 이름 ArgumentEcho 프로그램 인자 [ "Java" ] main(String[] args) 문자열 배열 · 프로그램 인자만 length = 1 args[0] = "Java" 출력 결과 first=Java
  1. 셸이 토큰을 전달

    java -cp out ArgumentEcho Java를 명령 토큰으로 넘깁니다.

  2. launcher 옵션 소비

    -cp out은 클래스를 찾을 위치를 정하며 프로그램 인자가 아닙니다.

  3. 실행 클래스 선택

    ArgumentEcho는 실행할 클래스 이름이며 args에 들어가지 않습니다.

  4. 나머지 값으로 배열 구성

    Java만 String[] args에 들어가 length=1, args[0]="Java"가 됩니다.

  • 셸과 launcher가 소비하는 값
  • 프로그램에 전달되는 값

클래스 이름 뒤에 둔 프로그램 인자만 순서를 유지한 문자열 배열이 되며, 배열 위치는 0부터 시작합니다.

args에서 값을 꺼내는 두 가지 방법

String[]는 문자열(String) 여러 개를 순서대로 담는 배열(array) 타입이고, args는 그 배열에 붙인 이름입니다. 배열은 3장에서 자세히 배우므로, 지금은 다음 두 가지만 알면 됩니다.

  • args.length는 전달된 실행 인자의 개수입니다.
  • args[0]은 첫 번째 실행 인자입니다. 배열의 위치 번호는 0부터 세므로, 두 번째 값은 args[1]입니다.

두 가지를 모두 써 보는 프로그램을 만들어 보겠습니다. 다음 내용을 src/ArgumentEcho.java로 저장하세요. 전달받은 인자의 개수와 첫 번째 인자를 출력합니다.

src/ArgumentEcho.java — 완전한 파일
public class ArgumentEcho {
    public static void main(String[] args) {
        System.out.println("count=" + args.length);
        System.out.println("first=" + args[0]);
    }
}
javac -d out src/ArgumentEcho.java
java -cp out ArgumentEcho Java
count=1
first=Java

실행 인자로 Java 하나를 주었으므로 args.length는 1이고 args[0]은 "Java"입니다. 그래서 첫 줄에는 count=1이, 둘째 줄에는 first=Java가 출력됩니다. 코드의 "count=" + args.length에서 +는 덧셈이 아니라 앞뒤 값을 이어 붙여 문자열 하나를 만듭니다. +가 언제 덧셈이 되고 언제 문자열을 잇는지는 2장에서 배웁니다.

이제 소스는 그대로 두고 실행 명령만 바꿔 보세요. 다시 컴파일할 필요는 없습니다. 실행 인자는 컴파일된 코드의 일부가 아니라 실행할 때마다 새로 전달되는 값이기 때문입니다. 출력은 다음 표처럼 달라집니다.

실행 명령출력
java -cp out ArgumentEcho Java studycount=2, first=Java
java -cp out ArgumentEcho "Java study"count=1, first=Java study
java -cp out ArgumentEcho 45count=1, first=45

첫 줄처럼 값을 띄어 적으면 각각 별개의 인자가 됩니다. 공백이 실행 인자를 나누는 기준이기 때문입니다. 공백이 들어간 값을 하나의 인자로 넘기려면 둘째 줄처럼 따옴표로 묶습니다. 따옴표는 '이 안의 공백에서는 나누지 말라'는 표시일 뿐 값의 일부가 아니므로, 명령을 실행 인자로 나누는 과정에서 지워집니다. 그래서 프로그램에는 따옴표 없이 Java study만 전달됩니다. 셋째 줄의 45도 숫자가 아니라 "45"라는 문자열로 들어옵니다. 실행 인자는 언제나 문자열이기 때문이며, 이 값을 숫자로 바꿔 계산하는 방법은 2장에서 다룹니다.

인자 없이 실행하면 생기는 일

이번에는 실행 인자를 하나도 주지 않고 실행해 보겠습니다. 소스는 그대로이므로 main은 여전히 args.length를 출력한 뒤 args[0]을 읽습니다. 첫 번째 인자가 없을 때 이 두 문장이 각각 어떻게 되는지 보세요.

java -cp out ArgumentEcho
count=0
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
    at ArgumentEcho.main(ArgumentEcho.java:4)

앞 글에서 배운 대로 읽어 보면, count=0이 출력됐으니 클래스를 찾았고 main의 첫 문장까지는 실행됐습니다. 실행 인자가 없을 때 args는 null이 아니라 길이가 0인 빈 배열이라서, args.length는 문제없이 0을 돌려줍니다. 그다음이 예외입니다. ArrayIndexOutOfBoundsException은 배열에 없는 위치를 읽으려 했다는 뜻이고, Index 0 out of bounds for length 0은 길이가 0인 배열에서 0번 위치를 찾았다는 설명입니다. 스택 트레이스의 ArgumentEcho.java:4는 args[0]을 읽은 4번째 줄을 가리킵니다.

이 실패는 사용자가 인자를 빠뜨렸기 때문에 일어났지만, 고칠 곳은 프로그램입니다. 사용자는 언제든 인자를 빠뜨릴 수 있으니, 프로그램이 이 상황을 예상하고 알맞게 안내해야 합니다. 방법은 간단합니다. args[0]을 읽기 전에 args.length로 값이 있는지부터 확인하는 것입니다.

값이 있는지 먼저 확인하기

확인하는 코드를 쓰려면 아직 배우지 않은 문법 두 가지가 필요합니다. if는 괄호 안의 조건이 참일 때만 중괄호 안의 문장을 실행합니다. 조건식 args.length == 0에서 ==는 양쪽 값이 같은지 비교하는 연산자이므로, 이 조건은 '인자가 하나도 없는가'를 묻습니다. 그리고 return은 main의 실행을 그 자리에서 끝냅니다. 조건과 분기는 2장에서 자세히 배우니, 지금은 이 한 가지 모양만 쓰겠습니다.

ArgumentEcho를 다음처럼 고쳐 저장하세요. 인자가 없으면 사용법을 알려 주고 끝내며, 인자가 있을 때만 args[0]을 읽습니다.

src/ArgumentEcho.java — 안전하게 고친 완전한 파일
public class ArgumentEcho {
    public static void main(String[] args) {
        if (args.length == 0) {
            System.out.println("사용법: java -cp out ArgumentEcho <값>");
            return;
        }

        System.out.println("count=" + args.length);
        System.out.println("first=" + args[0]);
    }
}

사용법 문장의 <값>은 그 자리에 실제 값을 넣으라는 표시입니다. 명령어 사용법을 적을 때 흔히 쓰는 표기이므로 <와 >까지 입력하지는 않습니다. 이제 다시 컴파일하고, 인자 없이 한 번, 인자를 주고 한 번 실행합니다. 이번에는 소스를 고쳤으니 컴파일을 꼭 다시 해야 합니다.

javac -d out src/ArgumentEcho.java
java -cp out ArgumentEcho
java -cp out ArgumentEcho variables
사용법: java -cp out ArgumentEcho <값>
count=1
first=variables

첫 번째 실행에서는 args.length가 0이므로 if 안으로 들어가 사용법을 출력하고, return에서 main이 끝납니다. 그래서 args[0]을 읽는 줄까지 내려가지 않고 예외도 생기지 않습니다. 두 번째 실행에서는 args.length가 1이라 조건이 거짓이므로 if 안을 건너뛰고, 그 아래의 두 출력문이 실행되어 count=1과 first=variables가 나옵니다.

여기서 중요한 것은 순서입니다. 개수를 확인하는 if가 args[0]을 읽는 줄보다 앞에 있어야 합니다. 확인을 뒤에 두면 인자가 없을 때 확인하기도 전에 args[0]을 읽다가 예외가 납니다.

첫 회원가입 프로그램

같은 방법으로 회원 이메일 하나를 받아 가입 결과를 출력하는 프로그램을 만들어 보겠습니다. 다음 내용을 src/MemberSignup.java로 저장하세요.

src/MemberSignup.java — 완전한 파일
public class MemberSignup {
    public static void main(String[] args) {
        if (args.length == 0) {
            System.out.println("사용법: java -cp out MemberSignup <email>");
            return;
        }

        System.out.println("회원 이메일: " + args[0]);
        System.out.println("가입 완료");
    }
}
javac -d out src/MemberSignup.java
java -cp out MemberSignup [email protected]
회원 이메일: [email protected]
가입 완료

이메일에는 공백이 없으므로 따옴표 없이 적어도 하나의 인자로 전달됩니다. 인자 없이 java -cp out MemberSignup만 실행하면 ArgumentEcho와 마찬가지로 사용법 한 줄을 출력하고 끝납니다. 아래 단계별 도표에서 두 경로를 차례로 확인해 보세요. args.length가 0이면 사용법을 출력하고 return하며, 1 이상일 때만 args[0]을 이메일로 읽습니다.

길이를 확인한 뒤에만 첫 인자를 사용한다

args.length 검사 뒤 없는 args[0] 접근을 피하고 return하거나 가입 출력을 하는 실제 제어 흐름입니다. 인수 없음/있음의 도달·미도달을 단계로 구별하면 안전 조건이 선명합니다.

args.length가 0이면 사용법을 출력하고 return하며, 1 이상이면 args[0]을 이메일로 읽어 회원 이메일과 가입 완료를 출력하는 회원가입 입력 경계입니다.

JAVA · FLOWCHART

길이를 확인한 뒤에만 첫 인자를 사용한다

args[0]을 읽기 전에 args.length를 분기하면 인자가 없는 실행과 회원가입 실행이 모두 안전하게 끝납니다.

args 길이 검사와 회원가입의 두 실행 경로 main이 String 배열을 받습니다. args.length가 0인지 검사해 예이면 사용법을 출력하고 return으로 종료합니다. 아니오이면 args[0]을 이메일로 읽고 회원 이메일과 가입 완료를 출력한 뒤 종료합니다. main(String[] args) 명령에서 받은 문자열 목록 인수가 없는가? args[0] 접근 전 사용법 출력 MemberSignup <email> args[0] 읽기 [email protected] return main 종료 가입 결과 출력 회원 이메일 · 가입 완료 정상 종료 없는 args[0]을 읽지 않음 예 · 0 아니요 · 1+
길이를 확인한 뒤에만 첫 인자를 사용한다 main과 길이 판정; 인자없음→사용법→return, 인자있음→첫값읽기→결과출력→정상종료. 상세 문구와 명령은 바로 아래 원래 설명에서 확인합니다. 예 · 0개 아니요 · 1+ main(String[] args) 명령의 문자열 목록 받음 읽기 전에 길이를 확인 인자가 없는가? args.length == 0 사용법 출력 MemberSignup <email> 첫 값 미접근 args[0] 읽기 member@ example.com 첫 이메일 return main 종료 가입 결과 회원 이메일 가입 완료 정상 종료 없는 args[0]을 읽지 않음 형식 검사·저장은 아직 안 함

args.length == 0을 먼저 묻습니다.

예 · 인자 0개

  1. 사용법 MemberSignup <email>을 출력합니다.

  2. return으로 main을 끝내므로 args[0]을 읽지 않습니다.

아니요 · 인자 1개 이상

  1. args[0]을 이메일 문자열로 읽습니다.

  2. 회원 이메일과 가입 완료를 출력하고 정상 종료합니다.

  • 인자 없음: 사용법 후 종료
  • 인자 있음: 첫 값 사용

검사가 접근보다 먼저 와야 존재하지 않는 args[0]을 읽지 않습니다. 이 단계는 이메일 형식 검증이나 저장소 없이 입력 유무만 판정합니다.

이 예의 조건

  • 인수 없음/있음은 각각 독립 main 입력이다.
관계도의 보충 설명
원 도표의 설명과 주석
구분내용
전체 읽기 정보길이를 확인한 뒤에만 첫 인자를 사용한다 args.length가 0이면 사용법을 출력하고 return하며, 1 이상이면 args[0]을 이메일로 읽어 회원 이메일과 가입 완료를 출력하는 회원가입 입력 경계입니다. JAVA · FLOWCHART 길이를 확인한 뒤에만 첫 인자를 사용한다 args[0]을 읽기 전에 args.length를 분기하면 인자가 없는 실행과 회원가입 실행이 모두 안전하게 끝납니다. args.length == 0 을 먼저 묻습니다. 예 · 인자 0개 사용법 MemberSignup <email> 을 출력합니다. return 으로 main을 끝내므로 args[0]을 읽지 않습니다. 아니요 · 인자 1개 이상 args[0] 을 이메일 문자열로 읽습니다. 회원 이메일과 가입 완료를 출력하고 정상 종료합니다. 인자 없음: 사용법 후 종료 인자 있음: 첫 값 사용 검사가 접근보다 먼저 와야 존재하지 않는 args[0]을 읽지 않습니다. 이 단계는 이메일 형식 검증이나 저장소 없이 입력 유무만 판정합니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
입력 배열

main이 명령 문자열 목록을 받습니다.

  • 입력 형태: String[] args
  • 검사: args.length
  • 첫 원소 접근 전에 배열 길이를 알아야 한다.
길이 가드

0이면 사용법을 출력하고 return합니다.

  • 인수 없음: MemberSignup <email>
  • args[0]: 미접근
  • 없을 수 있는 첫 원소를 읽기 전에 호출을 끝낸다.
정상 가입

인수가 있으면 첫 이메일과 완료 결과를 출력합니다.

  • 가드를 통과한 경로에서만 첫 값을 사용한다.

길이를 확인한 뒤에만 첫 인자를 사용한다

args.length 검사 뒤 없는 args[0] 접근을 피하고 return하거나 가입 출력을 하는 실제 제어 흐름입니다. 인수 없음/있음의 도달·미도달을 단계로 구별하면 안전 조건이 선명합니다.

args.length가 0이면 사용법을 출력하고 return하며, 1 이상이면 args[0]을 이메일로 읽어 회원 이메일과 가입 완료를 출력하는 회원가입 입력 경계입니다.

JAVA · FLOWCHART

길이를 확인한 뒤에만 첫 인자를 사용한다

args[0]을 읽기 전에 args.length를 분기하면 인자가 없는 실행과 회원가입 실행이 모두 안전하게 끝납니다.

args 길이 검사와 회원가입의 두 실행 경로 main이 String 배열을 받습니다. args.length가 0인지 검사해 예이면 사용법을 출력하고 return으로 종료합니다. 아니오이면 args[0]을 이메일로 읽고 회원 이메일과 가입 완료를 출력한 뒤 종료합니다. main(String[] args) 명령에서 받은 문자열 목록 인수가 없는가? args[0] 접근 전 사용법 출력 MemberSignup <email> args[0] 읽기 [email protected] return main 종료 가입 결과 출력 회원 이메일 · 가입 완료 정상 종료 없는 args[0]을 읽지 않음 예 · 0 아니요 · 1+
길이를 확인한 뒤에만 첫 인자를 사용한다 main과 길이 판정; 인자없음→사용법→return, 인자있음→첫값읽기→결과출력→정상종료. 상세 문구와 명령은 바로 아래 원래 설명에서 확인합니다. 예 · 0개 아니요 · 1+ main(String[] args) 명령의 문자열 목록 받음 읽기 전에 길이를 확인 인자가 없는가? args.length == 0 사용법 출력 MemberSignup <email> 첫 값 미접근 args[0] 읽기 member@ example.com 첫 이메일 return main 종료 가입 결과 회원 이메일 가입 완료 정상 종료 없는 args[0]을 읽지 않음 형식 검사·저장은 아직 안 함

args.length == 0을 먼저 묻습니다.

예 · 인자 0개

  1. 사용법 MemberSignup <email>을 출력합니다.

  2. return으로 main을 끝내므로 args[0]을 읽지 않습니다.

아니요 · 인자 1개 이상

  1. args[0]을 이메일 문자열로 읽습니다.

  2. 회원 이메일과 가입 완료를 출력하고 정상 종료합니다.

  • 인자 없음: 사용법 후 종료
  • 인자 있음: 첫 값 사용

검사가 접근보다 먼저 와야 존재하지 않는 args[0]을 읽지 않습니다. 이 단계는 이메일 형식 검증이나 저장소 없이 입력 유무만 판정합니다.

이 예의 조건

  • 인수 없음/있음은 각각 독립 main 입력이다.

관계도의 보충 설명

원 도표의 설명과 주석
구분내용
전체 읽기 정보길이를 확인한 뒤에만 첫 인자를 사용한다 args.length가 0이면 사용법을 출력하고 return하며, 1 이상이면 args[0]을 이메일로 읽어 회원 이메일과 가입 완료를 출력하는 회원가입 입력 경계입니다. JAVA · FLOWCHART 길이를 확인한 뒤에만 첫 인자를 사용한다 args[0]을 읽기 전에 args.length를 분기하면 인자가 없는 실행과 회원가입 실행이 모두 안전하게 끝납니다. args.length == 0 을 먼저 묻습니다. 예 · 인자 0개 사용법 MemberSignup <email> 을 출력합니다. return 으로 main을 끝내므로 args[0]을 읽지 않습니다. 아니요 · 인자 1개 이상 args[0] 을 이메일 문자열로 읽습니다. 회원 이메일과 가입 완료를 출력하고 정상 종료합니다. 인자 없음: 사용법 후 종료 인자 있음: 첫 값 사용 검사가 접근보다 먼저 와야 존재하지 않는 args[0]을 읽지 않습니다. 이 단계는 이메일 형식 검증이나 저장소 없이 입력 유무만 판정합니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
입력 배열

main이 명령 문자열 목록을 받습니다.

  • 입력 형태: String[] args
  • 검사: args.length
  • 첫 원소 접근 전에 배열 길이를 알아야 한다.
길이 가드

0이면 사용법을 출력하고 return합니다.

  • 인수 없음: MemberSignup <email>
  • args[0]: 미접근
  • 없을 수 있는 첫 원소를 읽기 전에 호출을 끝낸다.
정상 가입

인수가 있으면 첫 이메일과 완료 결과를 출력합니다.

  • 가드를 통과한 경로에서만 첫 값을 사용한다.

다만 이 프로그램이 출력하는 '가입 완료'는 이름만 그럴듯할 뿐, 실제로 가입을 처리하지는 않습니다. 이메일 형식을 검사하지 않으므로 java -cp out MemberSignup wrong처럼 실행해도 회원 이메일: wrong과 가입 완료가 그대로 출력됩니다. 회원 정보를 어디에 저장하지도 않으므로, 프로그램이 끝나면 입력했던 이메일도 사라집니다. 또 인자를 두 개 이상 주면 첫 번째 값만 쓰고 나머지는 무시합니다. 지금은 입력을 받아 출력하는 흐름만 연습하는 것입니다. 조건으로 입력을 검사하는 방법은 2장에서, 여러 회원을 저장하는 방법은 배열로 회원 목록 관리하기에서 차례로 더해 갑니다.

더 깊이: 오류로 끝났다는 것을 알리는 종료 코드

프로그램은 끝날 때 운영체제에 종료 코드(exit code)라는 정수 하나를 돌려줍니다. 관례상 0은 정상 종료를 뜻하고, 0이 아닌 값은 오류로 끝났다는 뜻입니다. 이 장의 예제처럼 main 하나만 실행하는 프로그램이라면, java 명령은 main이 끝까지 실행되거나 return으로 끝날 때 0을 돌려줍니다. 앞에서처럼 처리하지 않은 예외로 멈추면 1을 돌려줍니다. 그래서 위의 MemberSignup은 인자가 없어 사용법만 출력하고 끝난 경우에도 정상 종료(0)로 보고됩니다.

사람이 터미널에서 직접 실행할 때는 출력만 보면 되니 이 정도로 충분합니다. 하지만 이 프로그램을 다른 스크립트나 빌드 도구가 실행한다면, 사용법이 출력된 실행을 실패로 알아차릴 수 있어야 합니다. 그럴 때는 return 대신 System.exit(1)로 0이 아닌 종료 코드를 돌려주고, 사용법은 System.out 대신 오류 메시지용 출력인 System.err로 내보내는 것이 관례입니다. 터미널에서는 두 출력이 똑같이 보입니다. 하지만 >로 프로그램의 출력을 파일에 저장하면(리다이렉션) System.out의 내용만 파일에 담기고, System.err로 쓴 내용은 화면에 그대로 남습니다. 종료 코드는 PowerShell에서는 $LASTEXITCODE, macOS·Linux 셸에서는 $?를 출력해 확인할 수 있습니다.

src/MemberSignup.java — 종료 코드를 돌려주는 변형
public class MemberSignup {
    public static void main(String[] args) {
        if (args.length == 0) {
            System.err.println("사용법: java -cp out MemberSignup <email>");
            System.exit(1);
        }

        System.out.println("회원 이메일: " + args[0]);
        System.out.println("가입 완료");
    }
}

이 변형을 인자 없이 실행하면 화면에는 같은 사용법이 출력되지만 종료 코드는 1이 됩니다. System.exit는 JVM 종료를 시작하고 호출한 곳으로 돌아오지 않으므로, 그 아래 줄은 실행되지 않습니다. 이 교재의 앞부분 예제는 흐름을 단순하게 유지하려고 return과 System.out을 그대로 씁니다.

아직 배우지 않은 단어들

이 장의 코드에는 정식으로 배우지 않은 단어가 아직 여럿 남아 있습니다. String[] args의 뜻은 이제 알게 됐지만, public static void main의 나머지 단어와 이 글에서 처음 쓴 if, ==는 아직입니다. 한꺼번에 다 이해하려 하기보다, 지금 필요한 만큼의 뜻만 알고 각 단어를 자세히 배우는 곳을 기억해 두면 됩니다.

표현지금 알면 되는 뜻자세히 배우는 곳
class코드를 묶는 프로그램 단위4장 클래스
public실행기처럼 클래스 밖에서도 부를 수 있게 공개한다5장 접근 제어
static객체를 만들지 않아도 실행기가 바로 부를 수 있다6장 static 메서드
void실행을 마친 뒤 돌려주는 값이 없다3장 반환
main실행기가 찾아서 처음 실행하는 메서드의 이름3장 메서드
String[] args실행 인자를 담은 문자열 배열3장 배열
if조건이 참일 때만 중괄호 안을 실행한다2장 if 조건 분기
==두 값이 같은지 비교한다2장 비교 연산

이 단어들은 각 장에서 더 작은 예제로 다시 만납니다. 그때 이 선언을 다시 읽어 보면 단어 하나하나가 왜 필요한지 보일 것입니다. 한편 args라는 이름은 관례일 뿐이므로 main(String[] values)처럼 바꿔도 프로그램은 똑같이 동작합니다. 다만 Java 개발자 대부분이 args라는 이름에 익숙하니 그대로 쓰는 편이 좋습니다.

더 깊이: Java 25에서 짧아진 main

이 교재가 기준으로 삼는 Java 25에서는 void main()처럼 public, static, 매개변수를 생략한 짧은 시작점 선언이 정식 기능이 되었습니다(JEP 512, 간결한 소스 파일과 인스턴스 main 메서드). 이렇게 선언한 main은 static이 아니므로, 실행기가 먼저 그 클래스의 객체를 만든 다음 main을 호출합니다. 실행 인자가 필요하면 void main(String[] args)처럼 매개변수를 그대로 둡니다. 새 형태가 생겼다고 기존 형태가 바뀐 것은 아니어서, 지금까지 쓴 public static void main(String[] args)는 Java 25에서도 그대로 동작합니다. 이 교재는 실행 인자를 받는 모습이 잘 보이도록 계속 이 전통적인 형태를 사용합니다.

정리

  • 클래스 이름 뒤에 적은 실행 인자는 적은 순서대로 String[] args에 담겨 main에 전달됩니다. 실행기 옵션(-cp out)과 클래스 이름은 args에 들어가지 않습니다.
  • 실행 인자는 공백으로 나뉘고, 공백이 들어간 값은 따옴표로 묶어 하나로 전달합니다. 숫자처럼 보여도 언제나 문자열입니다.
  • 실행 인자가 없으면 args는 길이가 0인 빈 배열입니다. 이때 args[0]을 읽으면 ArrayIndexOutOfBoundsException이 발생합니다.
  • args[0]을 읽기 전에 if (args.length == 0)으로 확인하고, 값이 없으면 사용법을 출력한 뒤 return으로 끝냅니다. 확인이 읽기보다 앞에 있어야 합니다.
  • 실행 인자는 실행할 때마다 새로 전달되므로, 값만 바꿀 때는 다시 컴파일할 필요가 없습니다.

연습 문제

  1. src/Welcome.java를 작성하세요. 실행 인자가 없으면 사용법: Welcome <name>을 출력하고, 있으면 안녕하세요, <name> 형식으로 인사를 출력합니다. 두 경우 모두 예외 없이 끝나야 합니다. 다음 명령으로 확인하세요.

    javac -d out src/Welcome.java
    java -cp out Welcome
    java -cp out Welcome 안동민
  2. 다음 명령을 실행하면 수정된 ArgumentEcho는 무엇을 출력할까요? 실행하기 전에 먼저 예상해 보세요.

    • java -cp out ArgumentEcho 1 2 3
    • java -cp out ArgumentEcho "a b" c
    • java -cp out ArgumentEcho -cp out
  3. 친구가 MemberSignup을 다음처럼 고쳤습니다. 인자 없이 실행하면 어떻게 될지 설명하고, 바르게 고치세요.

    src/MemberSignup.java — 순서가 바뀐 파일
    public class MemberSignup {
        public static void main(String[] args) {
            System.out.println("회원 이메일: " + args[0]);
    
            if (args.length == 0) {
                System.out.println("사용법: java -cp out MemberSignup <email>");
                return;
            }
    
            System.out.println("가입 완료");
        }
    }
해설 보기

1번은 본문의 MemberSignup과 같은 구조로 만들 수 있습니다. 먼저 args.length == 0인지 확인해 사용법을 출력하고 return으로 끝내고, 그 아래에서만 args[0]을 읽습니다.

src/Welcome.java
public class Welcome {
    public static void main(String[] args) {
        if (args.length == 0) {
            System.out.println("사용법: Welcome <name>");
            return;
        }

        System.out.println("안녕하세요, " + args[0]);
    }
}
사용법: Welcome <name>
안녕하세요, 안동민

첫 줄은 인자 없이 실행한 결과이고, 둘째 줄은 안동민을 넘긴 결과입니다. 이름을 바꿔 다시 실행해도 컴파일은 다시 할 필요가 없습니다. 바뀐 것은 실행 인자뿐이고 Welcome.class는 그대로이기 때문입니다.

2번의 첫 명령은 인자 세 개 "1", "2", "3"을 전달하므로 count=3, first=1을 출력합니다. 두 번째 명령은 따옴표로 묶은 a b가 하나의 인자가 되어 인자가 두 개이므로 count=2, first=a b를 출력합니다. 세 번째 명령이 조금 까다롭습니다. 클래스 이름 ArgumentEcho 뒤에 있는 것은 실행기 옵션이 아니라 모두 실행 인자이므로, -cp와 out이 그대로 args에 들어가 count=2, first=-cp가 출력됩니다. 반대로 java ArgumentEcho -cp out처럼 옵션을 클래스 이름 뒤에 적으면, 실행기는 -cp out을 옵션으로 보지 않으므로 현재 폴더에서 클래스를 찾다가 ClassNotFoundException으로 실패합니다.

3번은 인자 없이 실행하면 첫 줄의 args[0]에서 바로 ArrayIndexOutOfBoundsException이 발생합니다. 개수를 확인하는 if가 그 아래에 있어서 확인할 기회가 오기 전에 빈 배열을 읽기 때문입니다. 스택 트레이스는 args[0]을 읽은 3번째 줄을 가리킵니다. if 블록 전체를 args[0]을 읽는 줄보다 위로 옮기면 해결됩니다. 그러면 본문의 MemberSignup과 같은 코드가 됩니다.

다음 글

1장에서는 JDK를 설치하고, 프로그램을 컴파일해 실행하고, 오류를 읽고, 실행 인자로 값을 받는 데까지 왔습니다. 2장에서는 이메일, 나이, 활동 여부 같은 값을 변수에 담아 이름을 붙이고, 그 값으로 계산하고, 조건에 따라 다르게 실행하고, 같은 일을 반복하는 방법을 배웁니다.