Java 실습 첫날, IntelliJ에 코드를 짜다가 두 번째 줄에 빨간 줄이 그어졌다. 디버깅도 안 됐다. 살펴보니 static void main(String args[])라고 적어놓고 public을 빠뜨린 게 원인이었다. 그동안 그냥 외워서 쓰던 public static void main(String[] args)인데, 다섯 단어나 되는 이 한 줄에서 각 단어가 왜 거기 있어야 하는지 한 번도 제대로 생각해본 적이 없다는 걸 그때 깨달았다. C를 다루다가 Java로 넘어오니 더 헷갈렸다.
public static void main(String[] args) {
// ...
}
public부터 보자. JVM이 이 메서드를 클래스 외부에서 호출해야 하는데, JVM은 내가 짠 클래스 내부의 일부가 아니라 완전히 바깥에 있는 시스템이다. 그래서 외부에서 접근 가능하도록 public이어야 한다. private static void main(...)으로 짜면 컴파일은 되는데 실행할 때 JVM이 main을 못 찾아서 "Main method not found" 에러가 뜬다.
그다음 static이 사실 제일 중요한 키워드다. 일반 인스턴스 메서드는 객체를 먼저 만들어야 호출 가능한데, 프로그램이 막 시작하는 순간에는 아직 어떤 객체도 존재하지 않는다. JVM이 Main 클래스를 메모리에 로드했을 뿐 new Main()을 한 적은 없다. static 메서드는 객체 없이 클래스 이름으로 바로 호출할 수 있어서, JVM이 Main.main(args)처럼 클래스를 통해 직접 부를 수 있게 된다. static이 빠지면 non-static method main cannot be referenced from a static context 에러가 난다.
void는 반환값이 없다는 뜻인데, JVM이 정한 시그니처가 그렇다. C랑 비교하면 재밌는 차이다.
// C
int main(int argc, char *argv[]) {
return 0; // 종료 코드
}
// Java
public static void main(String[] args) {
// 반환 안 함
}
C는 종료 상태 코드를 return 0;으로 넘기는데 Java는 void라 그게 안 된다. 그럼 종료 코드는 어떻게 주냐면 System.exit(1); 같은 걸 쓴다.
main은 키워드가 아니라 그냥 이름이다. JVM이 시작점으로 찾는 이름이 정확히 main이라 이렇게 쓰는 거지 언어 차원에서 예약된 단어는 아니다. mainn이나 Main으로 바꾸면 컴파일은 되지만 JVM이 실행할 때 못 찾는다.
마지막으로 String[] args는 JVM이 사용자가 명령줄에 적은 인자들을 문자열 배열로 넘겨주는 자리다. args라는 이름은 관습일 뿐 강제는 아니다.
java Main hello world 123
이렇게 실행하면 args[0]은 "hello", args[1]은 "world", args[2]는 "123", args.length는 3이 된다. C와 비교하면 argc가 따로 없다는 게 차이인데, Java 배열은 자기 길이를 알고 있어서 args.length로 바로 접근하면 된다.
main 안에서 키보드 입력을 받을 때는 보통 Scanner를 쓴다.
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("이름을 입력하세요: ");
String name = scanner.next();
System.out.print("나이를 입력하세요: ");
int age = scanner.nextInt();
System.out.println(name + "님은 " + age + "살입니다.");
scanner.close();
}
}
Scanner는 java.util 패키지에 있어서 맨 위에 import를 박아야 한다. IntelliJ에서는 Scanner라고 치고 Alt+Enter를 누르면 자동으로 추가해준다. next()는 공백 전까지 한 단어, nextLine()은 줄 끝까지, nextInt()/nextDouble()/nextBoolean()은 각각 int·double·boolean을 읽는다.
여기서 처음에 진짜 헷갈렸던 게 next()/nextInt() 다음에 nextLine()을 부르는 조합이다. nextInt()는 공백이나 개행 직전까지만 읽고 멈추기 때문에, 입력 버퍼에 개행 문자가 그대로 남는다. 그 상태에서 nextLine()을 부르면 남아있던 개행을 즉시 읽어서 빈 문자열을 반환하고 끝나버린다.
int age = scanner.nextInt(); // 25 입력 → age=25, 버퍼에 \n 남음
String name = scanner.nextLine(); // 즉시 빈 문자열 반환! 입력 못 받음
해결은 nextInt() 다음에 더미로 nextLine()을 한 번 더 불러서 남은 개행을 처리해주는 거다.
int age = scanner.nextInt();
scanner.nextLine(); // 남은 개행 처리
String name = scanner.nextLine();
String[] args와 String args[]는 둘 다 문법적으로 맞다. 배열 표기 []가 타입 뒤에 붙느냐 변수명 뒤에 붙느냐의 차이일 뿐이고, C/C++에서는 변수명 뒤가 자연스럽지만 Java 컨벤션은 타입 뒤(String[] args)를 권장한다. 가변 인자 문법인 String... args도 되는데 main에서는 거의 안 쓴다.
public과 static의 순서를 바꿔서 static public void main(...)으로 써도 컴파일은 된다. 다만 다들 public static void로 쓰니까 그냥 관습을 따르는 게 가독성에 좋다.
IntelliJ에서 빨간 줄이 뜨는 흔한 원인을 정리하면, public 빠뜨림, static 빠뜨림, String[] args 빠뜨림, 클래스 이름과 파일명 불일치(public class Main인데 파일명이 Main.java가 아닌 경우), 세미콜론 빠뜨림 정도다.
main도 메서드라서 오버로딩이 가능한데, JVM이 시작점으로 인식하는 건 정확히 public static void main(String[]) 하나뿐이다.
public class Main {
public static void main(String[] args) {
main(42); // 우리가 직접 호출
main("hello"); // 우리가 직접 호출
}
public static void main(int n) { System.out.println("int: " + n); }
public static void main(String s) { System.out.println("String: " + s); }
}
JVM은 String[]를 받는 main을 호출하고, 그 안에서 우리가 다른 오버로드를 직접 부를 수 있다. 실제로는 거의 안 쓰는 패턴이다.
참고한 자료는 DigitalOcean의 "Understanding public static void main(String[] args) in Java", GeeksforGeeks의 Java main() Method 설명, Oracle Java Language Specification의 메서드 선언 부분, 그리고 Coderanch 포럼에서 main 시그니처가 왜 그렇게 생겼는지 다룬 글이다.
public static void main(String[] args)는 단순 주문이 아니라 Java 실행기가 시작 메서드를 찾는 규칙과 연결된다. static이라 객체 생성 없이 호출할 수 있고, String[] args에는 명령행 인자가 들어온다. Scanner는 표준 입력 스트림을 토큰 단위로 읽는 별도 도구다.
nextInt() 뒤에 nextLine()을 바로 호출하면 남은 줄바꿈을 읽는 문제도 토큰 입력과 줄 입력의 경계를 알면 설명된다. 입력 방식을 섞을 때는 한 줄을 읽고 직접 변환하거나, 남은 개행을 소비하는 코드를 의도적으로 둔다.
| AI 에이전트 4종을 한 화면에서 보려고 로컬 관제 대시보드를 만들었다 (0) | 2026.07.27 |
|---|---|
| ChatGPT Plus, GitHub Copilot, Google Colab Pro 뭐가 달라? (0) | 2026.07.25 |
| 터미널에서 Java 컴파일하기 (javac -d bin과 패키지 디렉토리 구조) (0) | 2026.07.24 |
| 사각형 충돌 판정에서 원 충돌까지 (AABB의 원리와 일반화) (0) | 2026.07.24 |
| C 컴파일의 4단계와 링커 — gcc -c는 뭐고 -o는 뭐가 다른가 (0) | 2026.07.24 |