이번 문서의 목표: 이 파일을 다 읽으면 독학사 3단계 시험 지문에 나오는 Java 코드 조각을 처음부터 끝까지 막힘 없이 읽고, 어떤 순서로 실행되는지 손으로 추적할 수 있다.
왜 다시 기초 문법을 짚고 가는가
이 과목은 2단계 수준의 기초 프로그래밍(변수·제어문·함수·배열)을 이미 이수했다는 전제 위에서 설계되었습니다. 그래서 이 편은 Java 문법을 처음부터 가르치는 “입문”이 아니라, “시험 지문의 코드를 눈으로 읽을 때 걸리는 부분이 없도록 표기법과 규칙을 다시 맞추는 리마인드”입니다. 특히 C 계열 언어를 배운 학습자가 Java 코드를 볼 때 헷갈리는 지점(파일명과 클래스명의 관계, public static void main의 각 단어가 하는 일, 배열 선언 방식)을 짚습니다.
쉽게 말하면: 이 편은 새로운 것을 배우는 시간이 아니라, 이미 아는 것을 “시험에서 코드를 볼 때 바로 튀어나오도록” 다시 정렬하는 시간입니다.
1. 자바 파일 구조와 컴파일 단위
Java 소스 파일은 컴파일 단위(compilation unit)라고 부르며, 확장자는 .java입니다. 하나의 소스 파일 안에 클래스를 여러 개 선언할 수 있지만, public으로 선언된 클래스는 최대 하나이고, 그 public 클래스의 이름은 파일명과 정확히 일치해야 합니다.
// 파일명: Hello.java
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, OOP");
}
}Hello, OOP자주 틀리는 점: 시험 지문에서 파일명이 Hello.java인데 클래스 선언이 class Greeting으로 되어 있으면, 이는 Greeting이 public이 아니라는 뜻입니다(만약 public class Greeting이라면 컴파일 오류입니다). 지문에 파일명이 명시되어 있으면 반드시 클래스 이름과 대조해서 어떤 클래스가 public인지부터 확인하는 습관을 들여야 합니다.
2. main 메서드 — 실행의 시작점
Java 프로그램은 항상 다음 형태의 main 메서드에서 실행을 시작합니다.
public class Runner {
public static void main(String[] args) {
// 여기서부터 실행이 시작된다
}
}| 키워드 | 뜻 |
|---|---|
public | 접근 제어자(access modifier). 어떤 클래스에서든 이 메서드를 호출할 수 있게 공개한다는 뜻. Java 실행기가 프로그램 바깥에서 이 메서드를 호출하려면 반드시 필요하다 |
static | 정적(靜的). 객체를 만들지 않고도 클래스 이름만으로 바로 호출할 수 있다는 뜻. Java 실행기는 아직 어떤 객체도 만들지 않은 상태에서 프로그램을 시작해야 하므로 static이 필수다 |
void | 반환값 없음. main 메서드는 값을 돌려주지 않는다 |
main | 메서드 이름. Java 실행기가 이 정확한 이름을 찾아서 실행을 시작한다 |
String[] args | 문자열 배열 타입의 매개변수. 프로그램 실행 시 명령줄에서 전달된 인자를 받는다 |
쉽게 말하면:
public static void main(String[] args)는 “이 프로그램의 대문”입니다. Java 실행기는 클래스 안에 객체를 하나도 만들지 않은 채로 이 대문부터 두드려서 실행을 시작합니다.
자주 틀리는 점: static을 빼먹은 public void main(...)은 컴파일은 되지만, Java 실행기가 요구하는 시그니처(signature, 메서드 이름과 매개변수 목록의 조합)와 다르므로 “실행 진입점”으로 인식되지 않아 실행 시 오류가 납니다. 시험 지문에 이런 함정이 자주 등장하니 public static void main(String[] args) 철자 하나까지 정확히 기억해야 합니다.
3. 변수와 기본 자료형 — 값 타입과 참조 타입
Java의 자료형은 크게 기본형(primitive type)과 참조형(reference type)으로 나뉩니다. 이 구분은 이후 04편의 객체 참조를 이해하는 데 반드시 필요한 배경 지식입니다.
| 구분 | 예 | 저장하는 것 |
|---|---|---|
| 기본형 | int, double, char, boolean | 값 자체 |
| 참조형 | String, 배열, 클래스로 만든 객체 | 객체가 저장된 위치를 가리키는 참조(주소 개념) |
public class TypeDemo {
public static void main(String[] args) {
int a = 10;
int b = a;
b = 20;
System.out.println(a);
System.out.println(b);
}
}10
20int는 기본형이므로 b = a는 값 10을 복사합니다. 이후 b를 20으로 바꿔도 a는 영향을 받지 않습니다. 반면 참조형 변수는 값 자체가 아니라 “어디에 객체가 있는지”를 담고 있어서, 두 변수가 같은 객체를 가리키면 한쪽에서 객체 내용을 바꿀 때 다른 쪽에서도 그 변화가 보입니다. 이 차이는 04편에서 객체와 필드를 다룰 때 다시 구체적으로 확인합니다.
쉽게 말하면: 기본형 변수는 “값이 든 상자”이고, 참조형 변수는 “상자가 어디 있는지 적힌 쪽지”입니다. 쪽지를 복사하면 쪽지 두 장이 같은 상자를 가리킵니다.
4. 제어문 요약 — 조건과 반복
시험 지문에서 실행 순서를 추적하려면 제어문의 실행 순서를 정확히 알아야 합니다. 아래는 이미 아는 내용을 코드 추적 관점에서 다시 정리한 표입니다.
| 제어문 | 실행 순서 요약 |
|---|---|
if / else if / else | 조건을 위에서부터 순서대로 검사하다가, 참인 조건을 만나면 그 블록만 실행하고 나머지는 건너뛴다 |
for (초기화; 조건; 증감) | 초기화 한 번 → (조건 검사 → 본문 실행 → 증감) 반복 → 조건이 거짓이 되면 종료 |
while (조건) | 조건이 참인 동안 본문을 반복 실행. 조건 검사가 먼저이므로 처음부터 거짓이면 한 번도 실행되지 않는다 |
do { } while (조건); | 본문을 먼저 한 번 실행한 뒤 조건을 검사. 조건과 무관하게 최소 한 번은 실행된다 |
switch (값) { case ... } | 값과 일치하는 case부터 실행하며, break가 없으면 다음 case로 흘러 내려간다(fall-through) |
public class LoopDemo {
public static void main(String[] args) {
int sum = 0;
for (int i = 1; i <= 5; i++) {
sum += i;
}
System.out.println(sum);
}
}15중간 단계 표로 추적하면 다음과 같습니다.
| i | 조건 (i <= 5) | sum += i 후 sum |
|---|---|---|
| 1 | 참 | 1 |
| 2 | 참 | 3 |
| 3 | 참 | 6 |
| 4 | 참 | 10 |
| 5 | 참 | 15 |
| 6 | 거짓(종료) | - |
쉽게 말하면:
for문을 추적할 때는 눈으로 훑지 말고, 변수 값이 매 반복마다 어떻게 바뀌는지 표로 한 줄씩 그려보는 습관이 코드 추적형 문제의 핵심 대비 방법입니다.
5. 메서드 — 매개변수와 반환값
메서드(method)는 특정 동작을 이름 붙여 묶어 둔 코드 블록입니다. 절차형 언어의 “함수”와 개념은 같지만, Java에서는 메서드가 항상 어떤 클래스에 소속됩니다(이 소속 관계가 바로 객체지향의 시작점이며, 04편에서 자세히 다룹니다).
public class Calculator {
static int add(int x, int y) {
return x + y;
}
public static void main(String[] args) {
int result = add(3, 4);
System.out.println(result);
}
}7Java의 메서드 호출은 매개변수 전달 방식이 값에 의한 전달(pass by value)로 통일되어 있습니다. 기본형은 값 자체가 복사되어 전달되고, 참조형은 참조값(주소 개념)이 복사되어 전달됩니다. 즉 “참조 자체”가 아니라 “참조값의 복사본”이 넘어간다는 점이 정확한 표현입니다.
public class PassDemo {
static void increase(int n) {
n = n + 1;
}
public static void main(String[] args) {
int x = 5;
increase(x);
System.out.println(x);
}
}5increase 메서드 안에서 n을 아무리 바꿔도 n은 x 값의 복사본이므로, 메서드 호출이 끝난 뒤 원래의 x는 변하지 않습니다.
자주 틀리는 점: “Java는 객체를 참조로 전달한다”는 표현을 “메서드 안에서 참조형 매개변수 자체를 다른 객체로 바꾸면 호출한 쪽의 변수도 바뀐다”는 뜻으로 오해하는 경우가 많습니다. 실제로는 참조값의 복사본이 전달되므로, 메서드 안에서 매개변수가 다른 객체를 새로 가리키게 하는 것과 매개변수가 가리키는 객체 내부의 필드 값을 바꾸는 것은 결과가 다릅니다. 이 구분은 05편에서 객체 참조와 함께 다시 확인합니다.
6. 배열 — 선언과 인덱스
배열(array)은 같은 타입의 값을 여러 개 순서대로 담는 참조형 자료구조입니다.
public class ArrayDemo {
public static void main(String[] args) {
int[] scores = {90, 85, 77};
for (int i = 0; i < scores.length; i++) {
System.out.println(scores[i]);
}
}
}90
85
77| 표기 | 의미 |
|---|---|
int[] scores | scores는 int 값을 여러 개 담을 배열을 가리키는 참조형 변수 |
scores.length | 배열의 길이(원소 개수). 괄호 없는 필드이며, 문자열의 length()(메서드, 괄호 있음)와 표기가 다르니 혼동하지 않는다 |
scores[i] | 인덱스(index) i번째 원소. Java 배열의 인덱스는 0부터 시작한다 |
자주 틀리는 점: 배열의 인덱스는 0부터 length - 1까지입니다. scores[scores.length]처럼 길이와 같은 값을 인덱스로 쓰면 ArrayIndexOutOfBoundsException(배열 경계를 벗어났다는 예외, 13편에서 예외 계층과 함께 다룹니다)이 발생합니다.
7. 시험 지문 코드를 읽는 순서 — 코드 추적 요령
지필 시험에서 여러 클래스가 섞인 코드를 받으면, 다음 순서로 접근하는 것이 효율적입니다.
main이 있는 클래스부터 찾는다
여러 클래스가 함께 제시되어도 실행은 항상 public static void main이 있는 곳에서 시작합니다. 가장 먼저 이 지점을 찾습니다.
위에서 아래로, 한 줄씩 변수 표를 그린다
머릿속으로만 추적하지 말고, 나오는 변수 이름을 표의 열로 놓고 한 줄씩 값을 채워갑니다. 05_java-baseline에서 본 for문 추적표처럼, 이 습관은 이후 08~09편의 상속·다형성 코드 추적에서 그대로 확장됩니다.
메서드 호출을 만나면 그 메서드 본문으로 잠깐 이동한다
메서드가 호출되면 그 순간 실행 흐름이 메서드 본문으로 넘어갑니다. 반환값이 있으면 반환값을 받아 적고, 호출한 지점으로 돌아와 이어서 추적합니다.
출력문을 만날 때마다 그 시점의 값을 적어 둔다
System.out.println을 만날 때마다 그 시점의 변수 값을 별도로 받아 적어 두면, 최종 출력 순서를 헷갈리지 않습니다.
쉽게 말하면: 코드 추적은 “눈으로 훑어 답을 직감하는 것”이 아니라 “손으로 표를 그리며 컴퓨터가 하는 일을 그대로 흉내 내는 것”입니다. 이 습관을 04편부터 이어질 모든 코드 추적 문제에 그대로 적용합니다.
핵심 정리
- public 클래스 이름은 파일명과 일치해야 하며, 한 파일에 public 클래스는 하나만 둘 수 있다.
public static void main(String[] args)는 객체 없이도 호출되는 프로그램의 실행 진입점이며,static이 빠지면 진입점으로 인식되지 않는다.- 기본형은 값 자체를, 참조형은 참조값을 저장하며, 메서드 매개변수 전달은 항상 값에 의한 전달(값 자체 또는 참조값의 복사)이다.
- 제어문의 실행 순서를 표로 추적하는 습관이 코드 추적형 문제 대비의 핵심이다.
- 배열 인덱스는 0부터 시작하고, 길이와 같은 값을 인덱스로 쓰면 예외가 발생한다.
마무리 복습
int a = 10;
int b = a;
b = 20;
System.out.println(a);int x = 5;
increase(x);
System.out.println(x);