좋습니다. 아래 코드를 한 줄 한 줄 디버깅하듯이 해설해드릴게요. 예제 값도 함께 넣어가며 변수의 변화까지 추적해서 완전히 해부합니다.
public class Main {
static int x = 5;
public static void main(String[] args) {
int x = 10;
System.out.println(test(x));
System.out.println(x);
}
public static int test(int x) {
x += 5;
change();<- 매개변수를 못받음
return x;
}
public static void change() {
x += 10;
}
}
public static void change(int x) {
x += 10;
} // 만약 받으려면 이렇게 바꿔야됨
우리는 main()에서 시작하고, 그 안에서 test(x) 호출, 그리고 change() 호출, 마지막으로 출력됩니다.
변수 x는 클래스에 static int x = 5;와 main 내부의 int x = 10; 두 개가 존재합니다. 이름은 같지만 서로 다른 변수입니다.
static int x = 5;
클래스 전체에서 공유되는 정적 변수 x를 선언하고 5로 초기화.
여기서 x는 Main.x라고 불러도 됨.
현재 상태:
Main.x = 5
public static void main(String[] args) {
int x = 10;
main 함수 안에서 x라는 지역 변수를 선언하고 10으로 초기화.
이 x는 static 변수 x를 가립니다 (shadowing).
현재 상태:
Main.x = 5 (static 변수)
x = 10 (main 함수 안 지역 변수)
System.out.println(test(x));
test 함수에 인자로 넘김.test(10)이 호출됩니다.public static int test(int x) {
test 메서드의 지역 변수 x는 main에서 넘어온 10입니다.x = 10x += 5;
x = 10 + 5 = 15
현재 상태:
test 함수 안의 x = 15
change();
change() 메서드를 호출합니다.public static void change() {
x += 10;
}
x는 static 변수 Main.x입니다. (test 함수의 x 아님)Main.x = 5, 여기에 10을 더함 → Main.x = 15return x;
x는 test 메서드 안의 지역 변수 x = 15System.out.println(test(x));는 15 출력System.out.println(x);
x는 main 함수의 지역 변수 → 여전히 10test 안에서 x를 복사해서 넘겼기 때문에 원본은 바뀌지 않음15
10
15
10
| 포인트 | 설명 |
|---|---|
| 변수 shadowing | main의 지역 변수 x가 static x를 가림 |
| 인자 전달 방식 | 자바는 기본형은 값 복사(pass by value) → 원본 x = 10은 변하지 않음 |
| static 접근 | change()는 클래스 변수 x를 사용함 (Main.x) |
좋습니다! 정보처리기사 실기나 자바 기본기 테스트에서 자주 나오는 배열 + 스코프(scope) 관련 함정 문제를 아래에 준비했습니다.
public class Main {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
modify(arr);
System.out.println(arr[0]);
}
public static void modify(int[] arr) {
arr[0] = 10;
arr = new int[]{4, 5, 6};
arr[0] = 100;
}
}
A. 1
B. 10
C. 4
D. 100
배열은 참조형(reference type)이기 때문에 함수에 넘기면 영향을 줄 수도 있고 안 줄 수도 있습니다. 그 차이가 시험 포인트입니다!
int[] arr = {1, 2, 3};
arr는 힙(Heap)에 만들어진 배열 [1, 2, 3]을 가리키는 참조 변수
현재 상태:
arr → [1, 2, 3]
modify(arr);
modify 함수에 넘김arr[0] = 10;
이건 원래 배열을 건드림 → 원래 배열의 첫 번째 값이 1 → 10으로 바뀜
현재 상태:
arr → [10, 2, 3] (main에서도 이렇게 바뀜)
arr = new int[]{4, 5, 6};
arr이 새 배열 [4, 5, 6]을 가리키도록 변경됨modify 함수 안에서만 유효한 참조 변경 → main에서는 영향 없음arr[0] = 100;
이건 새로 생성한 배열의 첫 번째 요소 바꿈 →
modify 함수의 arr → [100, 5, 6]
main 함수의 arr → 여전히 [10, 2, 3]
System.out.println(arr[0]);
arr은 여전히 [10, 2, 3]| 개념 | 설명 |
|---|---|
| 배열은 참조형 | 함수에 배열을 넘기면 "주소"가 복사됨 |
| 함수 안에서 원소 변경 | 원본 배열에 영향 있음 |
함수 안에서 arr = new ... | 참조 변수만 바뀜, 원본 배열과는 단절 |
| 스코프 | 함수 안에서의 arr은 main의 arr과 같은 객체를 가리키지만, 새로 할당하면 따로 논다 |
main:
arr ─────┐
▼
[1, 2, 3]
↓
modify() 호출
↓
arr[0] = 10 → [10, 2, 3]
arr = new int[]{4,5,6} → [100, 5, 6] (함수 내 한정)
↳ main의 arr은 변하지 않음!
좋아요! 이어서 배열 + for-each문에 관련된 실기 스타일의 함정 문제를 드릴게요. 이번 문제는 배열 복사, for-each문, 값 전달 개념이 제대로 이해되지 않으면 100% 틀리게 만드는 문제입니다.
public class Main {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
for (int num : arr) {
num = num * 2;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
}
A. 2 4 6
B. 1 2 3
C. 0 0 0
D. 컴파일 에러
for-each 문은 배열 요소를 꺼내는 반복문이지만, 값을 복사해서 가져옵니다.arr[i] = ... 형식처럼 직접 접근해야 함num = num * 2는 arr의 값을 바꾸지 않음int[] arr = {1, 2, 3};
[1, 2, 3]for (int num : arr) {
num = num * 2;
}
num은 arr 배열의 각 값을 "복사"해서 담는 지역 변수| 반복 순서 | num의 값 | num = num * 2 실행 | arr 내용 |
|---|---|---|---|
| 1 | 1 | 2 (num만 바뀜) | [1, 2, 3] |
| 2 | 2 | 4 | [1, 2, 3] |
| 3 | 3 | 6 | [1, 2, 3] |
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
arr[0], arr[1], arr[2] 그대로 출력됨 → 1 2 3| 구분 | 설명 |
|---|---|
| for-each문 | 배열 값을 복사해 와서 변수에 담음 (원본 못 바꿈) |
| for문 + 인덱스 | 배열 값 변경 가능 (arr[i] = ...) |
| 실기 스타일 | "배열을 두 배로 바꾸는 코드"처럼 보이지만, 실제론 아무 영향 없음 |
for (int num : arr) {
num *= 2;
}
이걸 arr에 반영하려면 이렇게 써야 함:
for (int i = 0; i < arr.length; i++) {
arr[i] *= 2;
}
| 개념 | 설명 |
|---|---|
int num : arr | arr[i] 값을 복사해서 num에 담음 |
num *= 2 | num만 바뀌고, arr은 안 바뀜 |
| 배열 변경하려면? | arr[i] = arr[i] * 2;처럼 인덱스를 써야 함 |
좋아요! 이번에는 배열 + final + 메서드 전달 + 스코프 관련 함정을 섞은 실기 스타일 문제를 준비했어요.
이건 정말 많은 사람들이 직관과 실제 결과가 다르다는 이유로 틀리는 문제예요.
public class Main {
public static void main(String[] args) {
int[] a = null;
try {
a[0] = 1;
System.out.println("정상 실행");
} catch (Exception e) {
System.out.println("예외 발생");
}
}
}
A. 정상 실행
B. 예외 발생
C. 아무 것도 출력되지 않음
D. 컴파일 에러
int[] a = null; → a는 아무 배열도 가리키고 있지 않음a[0] = 1; → null을 참조해서 접근 시도 → NullPointerException 발생int[] a = null;
배열 참조 변수 a 선언
null을 대입 → 아무 배열도 가리키지 않음
현재 상태:
a → null
a[0] = 1;
a가 null인데 a[0]에 접근 시도try {
...
} catch (Exception e) {
System.out.println("예외 발생");
}
"예외 발생" 출력| 내용 | 설명 |
|---|---|
null 참조 | 객체 또는 배열이 메모리에 없음. 접근 시 오류 발생 |
a[0] | 배열이 존재해야 접근 가능. null 상태에서 접근 → NullPointerException |
| try-catch | 예외 발생 시 프로그램 멈추지 않고 catch로 넘어감 |
String[] arr = new String[3];
System.out.println(arr[0].length());
arr 자체는 null 아님 → 컴파일 에러 아님arr[0]은 아직 null → NullPointerException🔹 실기에서는 배열은 만들어졌는데 요소는 null인 상황도 많이 냅니다.
| 상황 | 발생 예외 |
|---|---|
배열 자체가 null → a[0] 접근 | NullPointerException |
배열 요소가 null → arr[0].length() | NullPointerException |
배열 인덱스 초과 → arr[10] | ArrayIndexOutOfBoundsException |
| 배열 생성 없이 사용 | 컴파일 에러 (int[] a; a[0] = 1;) |
좋습니다!
이번에는 정보처리기사 실기 스타일로 자주 나오는 문자열 배열에서 null vs 빈 문자열("")의 차이를 문제로 내고, 각 줄을 완전 디버깅하듯 해설해드릴게요.
이건 정말 많이들 헷갈리는데, 실기 시험에서도 자주 꼬아서 냅니다.
public class Main {
public static void main(String[] args) {
String[] arr = new String[3];
arr[0] = "";
arr[1] = null;
arr[2] = "hi";
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i].length());
}
}
}
A.
0
0
2
B.
0
Exception
C.
Exception
D.
0
Exception
2
null과 ""의 차이| 표현 | 의미 | 설명 |
|---|---|---|
null | 참조 없음 | 변수 자체가 아무 객체도 가리키지 않음 |
"" | 빈 문자열 | 길이가 0인 문자열 객체를 가리킴 (new String("")) |
String[] arr = new String[3];
arr = [null, null, null]
arr[0] = "";
"".length()는 0 반환arr = ["", null, null]
arr[1] = null;
arr = ["", null, null]
arr[2] = "hi";
"hi" 대입arr = ["", null, "hi"]
arr[0] = ""System.out.println(arr[0].length());
""는 빈 문자열 → 길이 00arr[1] = nullSystem.out.println(arr[1].length());
null.length()는 객체가 없는데 메서드를 호출하므로 → NullPointerException 발생👉 프로그램 강제 종료
"0"은 출력되지만, 그 다음에 예외 발생하고 프로그램 중단됨 → 0도 안 보이는 경우도 있음"Exception" 출력으로 간주됨.equals() 비교| 표현 | 안전 여부 | 설명 |
|---|---|---|
"".equals(str) | ✅ 안전 | null이어도 에러 안남 |
str.equals("") | ❌ 위험 | str이 null이면 NPE 발생 |
String s = null;
System.out.println("".equals(s)); // true 또는 false → 안전
System.out.println(s.equals("")); // NullPointerException → 위험
| 표현 | 의미 | .length() |
|---|---|---|
null | 아무 객체도 참조 안 함 | ❌ 예외 발생 |
"" | 길이 0인 문자열 | ✅ 0 반환 |
String[] arr = new String[3]; // 전부 null
for (String s : arr) {
if (s.equals("")) { // ❌ 여기서 터짐
...
}
}
이럴 땐 반드시 null 체크 먼저 해야 함:
if (s != null && s.equals("")) { // ✅ 안전한 코드
좋습니다!
이번에는 StringBuilder vs StringBuffer의 차이를 헷갈리게 만드는 문제를 드릴게요.
정보처리기사 실기에서 멀티스레드 + 문자열 처리, 또는 성능 비교 관련 문제에서 자주 나오는 함정입니다.
public class Main {
public static void main(String[] args) {
StringBuilder sb1 = new StringBuilder("A");
StringBuffer sb2 = new StringBuffer("A");
method(sb1);
method(sb2);
System.out.println(sb1);
System.out.println(sb2);
}
public static void method(Object obj) {
if (obj instanceof StringBuilder) {
((StringBuilder) obj).append("B");
} else if (obj instanceof StringBuffer) {
((StringBuffer) obj).append("B");
}
}
}
A.
A
A
B.
AB
AB
C.
AB
A
D.
A
AB
| 구분 | StringBuilder | StringBuffer |
|---|---|---|
| 스레드 안전성 | ❌ (비동기, non-thread-safe) | ✅ (동기화, thread-safe) |
| 성능 | 빠름 | 느림 |
| 사용 목적 | 단일 스레드 환경 | 멀티 스레드 환경 |
❗하지만 이 문제에서는 스레드와 관련이 없습니다. → 같이 동작함
StringBuilder sb1 = new StringBuilder("A");
"A"로 초기화StringBuffer sb2 = new StringBuffer("A");
"A"로 초기화method(sb1);
instanceof StringBuilder → true)((StringBuilder) obj).append("B"); 실행됨sb1 = "AB"
method(sb2);
instanceof StringBuffer → true)((StringBuffer) obj).append("B"); 실행됨sb2 = "AB"
System.out.println(sb1); // "AB"
System.out.println(sb2); // "AB"
| 요소 | 설명 |
|---|---|
StringBuilder, StringBuffer | 둘 다 append()로 문자열 변경 가능 |
instanceof | 타입 체크해서 안전하게 캐스팅 |
| 스레드 여부 | 여기선 단일 스레드이므로 둘 다 정상 작동 |
→ 정답: 3번
아주 좋습니다!
이번에는 실기에서 은근히 자주 꼬아서 나오는 **StringBuilder를 메서드에 넘겼을 때, 원본에 반영되는가?**에 대한 문제를 드릴게요.
이건 값 복사 vs 참조 전달, 그리고 .append()의 작동 방식까지 정확히 이해해야 맞출 수 있는 문제입니다.
public class Main {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("Hi");
modify(sb);
System.out.println(sb);
}
public static void modify(StringBuilder sb) {
sb.append(" there");
sb = new StringBuilder("Oops");
sb.append("!");
}
}
A. Hi there!
B. Oops!
C. Hi
D. Hi there
| 포인트 | 설명 |
|---|---|
StringBuilder는 참조형 | 객체를 참조하는 변수임 |
| 메서드에 넘기면? | 참조값이 복사되어 전달됨 (원본을 직접 바꿀 수 있음) |
하지만 sb = new StringBuilder(...) | 이건 메서드 내 지역 변수만 바뀜. 원본과 단절 |
StringBuilder sb = new StringBuilder("Hi");
sb → "Hi" 문자열을 갖는 StringBuilder 객체를 가리킴
현재 상태:
sb ─────┐
▼
"Hi"
modify(sb);
sb의 참조값(주소) 가 메서드에 넘겨짐modify 함수의 sb도 같은 "Hi" 객체를 가리킴sb.append(" there");
현재 sb는 "Hi"를 가리키고 있음 → 거기에 " there" 붙임
결과:
sb (main과 공유 중) → "Hi there"
sb = new StringBuilder("Oops");
sb는 이제 "Oops"라는 새로운 StringBuilder 객체를 가리킴"Hi there"sb.append("!");
"Oops" 객체에 "!"를 붙임 → "Oops!"System.out.println(sb);
"Hi"에서 "Hi there"로 바뀌었고,"Oops"와는 전혀 관계 없음| 구문 | 영향 여부 | 이유 |
|---|---|---|
sb.append(...) | ✅ 영향 있음 | 원래 객체를 수정함 |
sb = new StringBuilder(...) | ❌ 영향 없음 | 지역 변수만 바뀜 (원본 참조와 단절됨) |
main:
sb ─────┐
▼
"Hi" → append(" there") → "Hi there"
modify:
sb (같은 객체 참조)
append(" there") → "Hi there"
sb = new StringBuilder("Oops") → 🔗 끊김 (main과 단절)
append("!") → "Oops!"
main:
sb → 여전히 "Hi there"