좋아, 정보처리기사 실기에서는 기본 문법, 흐름 제어, 자료구조의 이해 부족을 노리는 함정 문제가 자주 나와. 아래는 그러한 스타일의 기초 문법+헷갈리기 쉬운 코드 흐름+함정 포인트가 섞인 연습 문제야.
다음 Java 코드를 실행했을 때, 출력 결과는?
public class TrapProblem1 {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
int[] ref = arr;
for (int i = 0; i < arr.length; i++) {
ref[i] += i;
}
for (int val : arr) {
System.out.print(val + " ");
}
}
}
A. 1 2 3
B. 0 2 4
C. 1 3 5
D. 0 3 6
int[] arr = {1, 2, 3};
arr는 배열 [1, 2, 3]을 가리킴.int[] ref = arr;
ref는 arr과 같은 배열을 참조함. 즉 ref를 통해 값을 바꾸면 arr도 같이 바뀜.for (int i = 0; i < arr.length; i++) {
ref[i] += i;
}
ref[0] += 0 → 1 + 0 = 1ref[1] += 1 → 2 + 1 = 3ref[2] += 2 → 3 + 2 = 5그래서 배열은
[1, 3, 5]가 됨
for (int val : arr) {
System.out.print(val + " ");
}
1 3 5 ref = arr; → 얕은 복사 (같은 배열 참조)ref[i] += i;는 결국 arr의 내용도 바꾼다for-each는 값을 복사해서 가져오기 때문에 직접 변경은 못하지만 출력용에는 문제 없음다음 Java 코드를 실행했을 때 출력 결과는?
public class TrapProblem2 {
public static void main(String[] args) {
String a = "test";
String b = new String("test");
if (a == b) {
System.out.println("같다");
} else {
System.out.println("다르다");
}
}
}
A. 같다
B. 다르다
C. 컴파일 에러
D. 예외 발생
a == b 는 문자열 내용이 아니라 주소(참조) 를 비교함.
a는 문자열 리터럴 → 문자열 풀(pool)에 저장됨b는 new String("test") → 힙 메모리에 새 객체 생성됨→ 주소가 다름
그래서 "같다" 가 아니라 "다르다" 출력됨
✅ 정답: B. 다르다
public class TrapProblem3 {
public static void main(String[] args) {
int num = 2;
String result = "";
switch (num) {
case 1:
result += "A";
case 2:
result += "B";
case 3:
result += "C";
break;
default:
result += "D";
}
System.out.println(result);
}
}
A. ABC
B. BC
C. BCD
D. ABCD
num == 2 이므로 case 2부터 실행됨
→ 순서대로 보면
→ "BC" 출력
✅ 정답: B. BC
=, clone, Arrays.copyOf)==, .equals)null 체크 없이 .equals() 호출하는 오류switch-case에서 break 없는 문제for-each로 값을 바꿨다고 착각하는 문제원하면 다음 주제로 난이도 높은 함정 문제 더 만들어줄 수 있어:
원하는 주제를 말해줘. 예제와 함정 포함해서 바로 만들어줄게.
좋아. 지금부터 정보처리기사 실기 스타일로, 자바 기본기 부족하면 헷갈릴 수 있는 고난이도 코드 문제를 네 가지 주제에 따라 각각 1문제씩 낼게.
각 문제는 코드 흐름 이해 + 컬렉션/메모리/예외 흐름 트랩이 숨겨져 있어.
모두 객관식 + 상세 풀이로 줄게.
import java.util.*;
public class CollectionTrap {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
Set<List<String>> set = new HashSet<>();
set.add(list);
list.add("B");
System.out.println(set.contains(Arrays.asList("A", "B")));
}
}
A. true
B. false
C. 예외 발생
D. 컴파일 에러
list는 [A] → set.add(list) → set에 [A] 추가됨
그 후 list.add("B") 하면 set에 있는 리스트 객체도 [A, B]가 되어버림 (얕은 복사니까!)
그런데 set.contains(Arrays.asList("A", "B")) 는 새로 만든 리스트 객체를 찾음
HashSet.contains() 는 hashCode() + equals() 둘 다 동일해야 찾음문제는:
list가 add될 당시: [A] → 그때의 hashCode 저장됨[A, B] → hashCode 변경됨HashSet은 내부 구조상, 요소의 hashCode가 변하면 contains()가 실패함
🔴 그래서 set.contains(...)는 false 나옴
✅ 정답: B. false
public class StaticTrap {
static int count = 0;
public void increment() {
count++;
}
public static void main(String[] args) {
StaticTrap a = new StaticTrap();
StaticTrap b = new StaticTrap();
a.increment();
b.increment();
System.out.println(StaticTrap.count);
}
}
A. 0
B. 1
C. 2
D. 컴파일 에러
count는 static → 모든 인스턴스가 공유함a.increment() → count = 1b.increment() → count = 2static 변수는 객체가 달라도 하나의 값을 공유하므로 결과는 2
✅ 정답: C. 2
public class ReferenceTrap {
public static void modify(int[] arr) {
arr = new int[]{4, 5, 6};
arr[0] = 99;
}
public static void main(String[] args) {
int[] data = {1, 2, 3};
modify(data);
System.out.println(data[0]);
}
}
A. 99
B. 4
C. 1
D. 컴파일 에러
modify(data) → data 배열이 넘어감arr = new int[]{4,5,6} 하면 arr이라는 지역 변수만 바뀌고, data는 안 바뀜그래서 data는 여전히 {1,2,3}이고 출력은 1
✅ 정답: C. 1
public class ExceptionTrap {
public static void main(String[] args) {
try {
System.out.println("A");
throw new RuntimeException("예외!");
} catch (Exception e) {
System.out.println("B");
return;
} finally {
System.out.println("C");
}
}
}
A. A
B. A B
C. A B C
D. A C
"A" 출력"B" 출력return 이 있어도 finally는 반드시 실행됨"C" 출력됨A B C✅ 정답: C. A B C
| 개념 | 핵심 포인트 | 트랩 요소 |
|---|---|---|
| List/Set 참조 | HashSet은 hashCode와 equals로 비교 | mutable 객체 hashCode 변경 |
| static 변수 | 객체와 상관없이 공유 | 인스턴스 메서드에서 static 변수 접근 가능 |
| call by value | 참조값도 값으로 복사됨 | 함수 안에서 새 배열로 바꾸면 외부 영향 없음 |
| 예외 흐름 | finally는 항상 실행 | return, break 있어도 finally 실행됨 |
좋아, 아주 날카로운 질문이야.
문제 1번의 핵심은 HashSet과 hashCode()가 연관된 작동 원리인데, 여길 잘 이해하면 자바에서의 객체 비교 방식과 mutable 객체를 Set에 쓰면 안 되는 이유도 완전히 잡을 수 있어.
false가 되는지 완벽히 해설할게Set<T> set = new HashSet<>();
자바의 HashSet은 내부적으로 HashMap<T, Object> 구조를 사용해 구현돼 있고,
요소가 추가되거나 조회될 때 두 가지를 사용함:
hashCode() : 요소가 들어갈 버킷(슬롯) 위치 결정equals() : 같은 버킷에 있을 수 있는 여러 값 중에서 실제 동등한지 비교List<String> list = new ArrayList<>();
list.add("A");
Set<List<String>> set = new HashSet<>();
set.add(list); // list는 현재 [A]
list.add("B"); // list는 [A, B] 로 바뀜
System.out.println(set.contains(Arrays.asList("A", "B")));
set.add(list) → list.hashCode()를 기준으로 특정 버킷에 저장됨
list.add("B") → list 내용이 바뀌면, hashCode() 값이 바뀜
→ HashSet은 이 리스트가 어디 있는지 못 찾음!
이후 contains(Arrays.asList("A", "B"))를 호출해도
📌 정리하면:
list가mutable객체라 내부가 바뀌면 HashSet은 그걸 못 추적함
Object.hashCode() 규칙:
- 같은 객체이면 항상 같은 hashCode를 반환해야 함
- equals()가 true인 두 객체는 반드시 같은 hashCode를 가져야 함
- hashCode가 같다고 해서 equals가 true일 필요는 없음
그리고 HashSet은 다음 규칙으로 작동함:
HashSet.contains(x)
1. x.hashCode() → 어떤 버킷에 있는지 확인
2. 그 버킷 안에서 equals() 비교로 정확한 값 찾기
Arrays.asList("A", "B")
"A", "B" 라는 값 자체는 list와 동일함[A, B].equals([A, B])는 true하지만, HashSet은 hashCode가 달라지면 같은 객체라도 위치를 못 찾음
즉, "주소 바뀜" 같은 효과임
List<String> list1 = new ArrayList<>();
list1.add("A");
list1.add("B");
List<String> list2 = Arrays.asList("A", "B");
System.out.println(list1.equals(list2)); // ✅ true
System.out.println(list1.hashCode() == list2.hashCode()); // ✅ true
그러나,
Set<List<String>> set = new HashSet<>();
set.add(list1); // 저장 당시의 hashCode 기준
list1.add("C"); // 내부가 바뀌면 hashCode도 바뀜!
System.out.println(set.contains(list2)); // ❌ false
이유:
list1이 변하면서 HashSet이 인덱싱했던 위치와 다른 해시값이 되어버려서
아예 그 자리에 가서 비교조차 안 됨
HashSet은 요소를 hashCode 기준으로 저장하고,
나중에contains()등으로 찾을 때도 같은 hashCode 기준으로만 탐색함.그래서
hashCode()값이 변하면, 동일 객체라도 찾지 못함.
hashCode() 기반 컬렉션 (HashMap, HashSet)에 넣은 뒤에는 객체를 수정하지 않는다좋아, 이제부터 "해시의 세계" 를 이해할 수 있도록 기초부터 고급 개념까지 차근차근 설명해줄게.
너는 해시가 왜 필요한지, 어디서 쓰이는지, 어떻게 작동하는지 개념과 이유 중심으로 완벽하게 이해하게 될 거야.
"어떤 값을 고정된 크기의 고유한 숫자로 바꾸는 과정"
→ 이걸 해싱(hashing)이라고 해.
이게 바로
hashCode()의 역할이야.
해시를 쓰는 이유는 딱 하나:
빠르게 찾기 위해서 (검색 속도 = O(1))
| 자료구조 | 자료 찾는 시간 복잡도 | 방법 |
|---|---|---|
List | O(n) (하나씩 검사) | 반복문으로 탐색 |
HashSet | O(1) ~ O(log n) | 해시값으로 바로 위치 찾아감 |
→ 즉, HashSet.contains(x)는 x.hashCode()를 계산해서
한 방에 그 위치로 점프해서 검사함.
→ 훨씬 빠름!
| 자료구조 / 개념 | 설명 |
|---|---|
HashMap<K,V> | Key의 hashCode로 빠르게 값을 찾음 |
HashSet<T> | 중복 제거하면서 빠르게 저장/탐색 |
String.hashCode() | 문자열을 숫자로 바꿔서 비교 효율 향상 |
equals() + hashCode() | 두 객체가 같은지 비교할 때 사용 |
| 해시 알고리즘(SHA, MD5 등) | 보안 분야에서 사용, 고유 식별자 생성 |
| 로드 밸런싱 / 분산 해시 | 서버 분산에서 "누가 담당할지" 빠르게 결정 |
| Git | 커밋 내용 → 해시값(SHA-1) 으로 저장 |
hashCode() 규칙 (암기해도 좋음)1. x.equals(y) == true 이면
→ 반드시 x.hashCode() == y.hashCode() 여야 함
2. x.hashCode() == y.hashCode() 라고 해서
→ 반드시 x.equals(y) 라는 보장은 없음
3. hashCode() 값이 바뀌지 않도록
→ 객체는 가능한 한 "불변(immutable)"으로 만들 것
HashSet<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("apple"); // 무시됨
apple.hashCode()가 이미 존재하면equals()도 검사해서 중복이면 추가 안 됨💣 문제는 객체가 내부적으로 바뀌면, hashCode도 바뀌어서
"자기가 자기를 못 찾는 상태" 가 됨
→ 그래서 Set.contains()가 실패하거나 Map.get()이 null 나옴
Set<List<String>> set = new HashSet<>();
List<String> list = new ArrayList<>();
list.add("A");
set.add(list);
list.add("B");
System.out.println(set.contains(list)); // ❌ false
✅ 해결 방법:
List → Collections.unmodifiableList(...)로 감싸기@Override
public boolean equals(Object o) {
return this.id == ((User)o).id;
}
@Override
public int hashCode() {
return Integer.hashCode(id);
}
| 문제 | 설명 |
|---|---|
| 해시 충돌 (Collision) | 서로 다른 객체가 같은 hashCode 가질 수 있음 |
| 해결 방법 | equals()로 최종 비교 / 체이닝 방식 사용 |
| 보안 위험 | 의도적으로 충돌을 유발해 해시 테이블 성능 저하시키는 공격도 있음 → HashDoS |
| 개념 | 핵심 내용 |
|---|---|
| 해시란 | 값을 고유한 숫자(hashCode)로 변환 |
| 쓰임 | HashMap, HashSet, 보안 등 |
| equals vs hashCode | 둘 다 반드시 함께 override |
| mutable 객체 문제 | hashCode가 바뀌면, contains/get 실패 |
| 성능 이점 | 탐색 속도 O(1) |
| 충돌 해결 | equals(), 체이닝 구조 등 |
HashMap 내부 구조: 배열 + 연결 리스트 (Java 8부터 Tree 구조)LinkedHashSet, TreeSet, EnumMap, IdentityHashMap의 차이Object.hashCode()의 디폴트 동작String이 왜 해시 구조에 최적화되어 있는가?필요하면 HashMap과 HashSet의 내부 구조도 그림과 예제로 하나씩 해설해줄게.
혹은 equals()와 hashCode()를 직접 구현하는 연습 문제도 줄 수 있어.
어떤 방향으로 더 확장해서 공부하고 싶어?
HashMap 구조 해부?String의 해시 동작?equals/hashCode 연습?