해시 시작(주의-자바 해시 위주라서 좀 )

류한선·2025년 7월 2일

실기연습-2

목록 보기
67/95

좋아, 정보처리기사 실기에서는 기본 문법, 흐름 제어, 자료구조의 이해 부족을 노리는 함정 문제가 자주 나와. 아래는 그러한 스타일의 기초 문법+헷갈리기 쉬운 코드 흐름+함정 포인트가 섞인 연습 문제야.


✅ 문제 1: 배열과 반복문, 참조 개념 문제 (난이도 ★★☆)

다음 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;
  • refarr같은 배열을 참조함. 즉 ref를 통해 값을 바꾸면 arr도 같이 바뀜.
for (int i = 0; i < arr.length; i++) {
    ref[i] += i;
}
  • ref[0] += 01 + 0 = 1
  • ref[1] += 12 + 1 = 3
  • ref[2] += 23 + 2 = 5

그래서 배열은 [1, 3, 5]가 됨

for (int val : arr) {
    System.out.print(val + " ");
}
  • 출력: 1 3 5

✅ 정답: C. 1 3 5


⚠️ 포인트 해설:

  • ref = arr;얕은 복사 (같은 배열 참조)
  • ref[i] += i;는 결국 arr의 내용도 바꾼다
  • for-each는 값을 복사해서 가져오기 때문에 직접 변경은 못하지만 출력용에는 문제 없음

✅ 문제 2: if 조건과 문자열 비교의 함정 (난이도 ★★☆)

다음 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)에 저장됨
  • bnew String("test")힙 메모리에 새 객체 생성됨

주소가 다름

그래서 "같다" 가 아니라 "다르다" 출력됨

✅ 정답: B. 다르다


✅ 문제 3: switch문에서 break 빠뜨림 (난이도 ★★★)

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부터 실행됨

  • break 없으므로 fall-through 발생

→ 순서대로 보면

  • case 2: result += "B";
  • case 3: result += "C";
  • break → 멈춤

"BC" 출력

✅ 정답: B. BC


계속 연습하고 싶다면:

  • 배열 복사/참조 (=, clone, Arrays.copyOf)
  • 문자열 비교 (==, .equals)
  • 조건문에서 null 체크 없이 .equals() 호출하는 오류
  • switch-case에서 break 없는 문제
  • for-each로 값을 바꿨다고 착각하는 문제
  • 전역 변수와 지역 변수 shadowing 문제

다음 문제 원해?

원하면 다음 주제로 난이도 높은 함정 문제 더 만들어줄 수 있어:

  • ✔️ 컬렉션 (List/Set/Map)
  • ✔️ static과 인스턴스
  • ✔️ call by value vs reference
  • ✔️ 메서드 오버로딩/오버라이딩
  • ✔️ 예외 처리 흐름

원하는 주제를 말해줘. 예제와 함정 포함해서 바로 만들어줄게.

좋아. 지금부터 정보처리기사 실기 스타일로, 자바 기본기 부족하면 헷갈릴 수 있는 고난이도 코드 문제를 네 가지 주제에 따라 각각 1문제씩 낼게.
각 문제는 코드 흐름 이해 + 컬렉션/메모리/예외 흐름 트랩이 숨겨져 있어.
모두 객관식 + 상세 풀이로 줄게.


✅ 문제 1: 컬렉션(List, Set, Map)의 참조 및 불변성 트랩 (난이도 ★★★★)

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. 컴파일 에러


✅ 풀이:

지금 이 타이밍에서 무슨 일이 벌어지는가?

  1. list[A]set.add(list)set[A] 추가됨

  2. 그 후 list.add("B") 하면 set에 있는 리스트 객체도 [A, B]가 되어버림 (얕은 복사니까!)

  3. 그런데 set.contains(Arrays.asList("A", "B")) 는 새로 만든 리스트 객체를 찾음

    • 자바에서 HashSet.contains()hashCode() + equals() 둘 다 동일해야 찾음
  4. 문제는:

    • list가 add될 당시: [A] → 그때의 hashCode 저장됨
    • 지금은 [A, B] → hashCode 변경됨
  5. HashSet은 내부 구조상, 요소의 hashCode가 변하면 contains()가 실패함

🔴 그래서 set.contains(...)false 나옴

✅ 정답: B. false


✅ 문제 2: static vs 인스턴스 혼용 함정 (난이도 ★★★★)

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. 컴파일 에러


✅ 풀이:

  • countstatic → 모든 인스턴스가 공유
  • a.increment() → count = 1
  • b.increment() → count = 2

static 변수는 객체가 달라도 하나의 값을 공유하므로 결과는 2

✅ 정답: C. 2


✅ 문제 3: call by value vs reference + 배열 트랩 (난이도 ★★★★)

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 배열이 넘어감
  • BUT! 자바는 call by value
    → 즉, 참조값이 복사돼서 넘어감
  1. arr = new int[]{4,5,6} 하면 arr이라는 지역 변수만 바뀌고, data는 안 바뀜
  2. arr[0] = 99 → data와는 상관없음

그래서 data는 여전히 {1,2,3}이고 출력은 1

✅ 정답: C. 1


✅ 문제 4: 예외 처리 흐름과 finally 트랩 (난이도 ★★★★★)

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


✅ 풀이:

  1. try 블록 → "A" 출력
  2. 예외 발생 → catch 블록으로 이동 → "B" 출력
  3. return 이 있어도 finally는 반드시 실행됨
  4. "C" 출력됨
    → 출력 순서: A B C

✅ 정답: C. A B C


🎯 요약 복습:

개념핵심 포인트트랩 요소
List/Set 참조HashSethashCodeequals로 비교mutable 객체 hashCode 변경
static 변수객체와 상관없이 공유인스턴스 메서드에서 static 변수 접근 가능
call by value참조값도 값으로 복사됨함수 안에서 새 배열로 바꾸면 외부 영향 없음
예외 흐름finally는 항상 실행return, break 있어도 finally 실행됨

좋아, 아주 날카로운 질문이야.
문제 1번의 핵심은 HashSethashCode()가 연관된 작동 원리인데, 여길 잘 이해하면 자바에서의 객체 비교 방식mutable 객체를 Set에 쓰면 안 되는 이유도 완전히 잡을 수 있어.


🔍 set.contains(Arrays.asList("A", "B")) 이 왜 false가 되는지 완벽히 해설할게


✅ 자바의 HashSet 내부 동작 구조 요약

Set<T> set = new HashSet<>();

자바의 HashSet은 내부적으로 HashMap<T, Object> 구조를 사용해 구현돼 있고,
요소가 추가되거나 조회될 때 두 가지를 사용함:

  1. hashCode() : 요소가 들어갈 버킷(슬롯) 위치 결정
  2. 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"))를 호출해도

    • hashCode()가 다르므로 → 해시 버킷부터 안 맞음
    • equals()까지 비교도 안 감

📌 정리하면: listmutable 객체라 내부가 바뀌면 HashSet은 그걸 못 추적함


🧠 이걸 이해하려면 hashCode() 규칙을 정확히 알아야 해


✅ 자바에서 hashCode의 규칙 (공식 문서 기준)

Object.hashCode() 규칙:
- 같은 객체이면 항상 같은 hashCode를 반환해야 함
- equals()true인 두 객체는 반드시 같은 hashCode를 가져야 함
- hashCode가 같다고 해서 equals가 true일 필요는 없음

그리고 HashSet은 다음 규칙으로 작동함:

HashSet.contains(x)

1. x.hashCode() → 어떤 버킷에 있는지 확인
2. 그 버킷 안에서 equals() 비교로 정확한 값 찾기

✅ Arrays.asList("A", "B")는 왜 안 맞는가?

Arrays.asList("A", "B")
  • 새로 만든 리스트이지만 "A", "B" 라는 값 자체는 list와 동일함
  • equals()는 true일 수 있음
    [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() 값이 변하면, 동일 객체라도 찾지 못함.


✅ 실무에서는 어떻게 방지할까?

  • HashSet에 넣을 객체는 되도록 불변 객체 (immutable) 로 만든다
  • hashCode() 기반 컬렉션 (HashMap, HashSet)에 넣은 뒤에는 객체를 수정하지 않는다
  • 배열이나 리스트처럼 mutable한 객체를 key나 Set 요소로 쓰지 않는다

❗ 추가 트랩 팁 (시험에 나올 수 있는 유형)

  1. mutable key를 HashMap에 key로 넣고 값 바꾸면 못 찾는 경우
  2. hashCode()는 같지만 equals()는 다르면 다른 객체로 취급됨
  3. null-safe equals(), Objects.hash()를 직접 구현하지 않으면 예외나기 쉬움

좋아, 이제부터 "해시의 세계" 를 이해할 수 있도록 기초부터 고급 개념까지 차근차근 설명해줄게.
너는 해시가 왜 필요한지, 어디서 쓰이는지, 어떻게 작동하는지 개념과 이유 중심으로 완벽하게 이해하게 될 거야.


✅ 1. 해시(Hash)란 무엇인가?

🔸 핵심 개념:

"어떤 값을 고정된 크기의 고유한 숫자로 바꾸는 과정"
→ 이걸 해싱(hashing)이라고 해.


🧠 비유로 쉽게 설명하면:

  • 책 내용을 읽고 요약해서 "책 번호" 를 만든다고 생각해보자.
  • 내용이 다른 책이면 번호도 달라야 하고,
    내용이 같으면 같은 번호여야 해.

이게 바로 hashCode()의 역할이야.


✅ 2. 해시가 왜 필요한가?

해시를 쓰는 이유는 딱 하나:

빠르게 찾기 위해서 (검색 속도 = O(1))


🔍 예: 리스트(List) vs 해시 기반 Set

자료구조자료 찾는 시간 복잡도방법
ListO(n) (하나씩 검사)반복문으로 탐색
HashSetO(1) ~ O(log n)해시값으로 바로 위치 찾아감

→ 즉, HashSet.contains(x)x.hashCode()를 계산해서
한 방에 그 위치로 점프해서 검사함.
→ 훨씬 빠름!


✅ 3. 해시가 사용되는 곳

자료구조 / 개념설명
HashMap<K,V>Key의 hashCode로 빠르게 값을 찾음
HashSet<T>중복 제거하면서 빠르게 저장/탐색
String.hashCode()문자열을 숫자로 바꿔서 비교 효율 향상
equals() + hashCode()두 객체가 같은지 비교할 때 사용
해시 알고리즘(SHA, MD5 등)보안 분야에서 사용, 고유 식별자 생성
로드 밸런싱 / 분산 해시서버 분산에서 "누가 담당할지" 빠르게 결정
Git커밋 내용 → 해시값(SHA-1) 으로 저장

✅ 4. 자바의 hashCode() 규칙 (암기해도 좋음)

1. x.equals(y) == true 이면
   → 반드시 x.hashCode() == y.hashCode() 여야 함

2. x.hashCode() == y.hashCode() 라고 해서
   → 반드시 x.equals(y) 라는 보장은 없음

3. hashCode() 값이 바뀌지 않도록
   → 객체는 가능한 한 "불변(immutable)"으로 만들 것

✅ 5. 왜 해시 충돌이 생기면 안 될까?

HashSet<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("apple"); // 무시됨
  • apple.hashCode()가 이미 존재하면
    equals()도 검사해서 중복이면 추가 안 됨

💣 문제는 객체가 내부적으로 바뀌면, hashCode도 바뀌어서
"자기가 자기를 못 찾는 상태" 가 됨
→ 그래서 Set.contains()가 실패하거나 Map.get()이 null 나옴


✅ 6. mutable 객체를 HashSet/HashMap에서 쓰면 안 되는 이유

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

이유:

  • 리스트 내용이 바뀌면 → hashCode()가 바뀜
  • 해시 테이블에서 기존 위치로 못 찾아감

✅ 해결 방법:

  • ListCollections.unmodifiableList(...)로 감싸기
  • 또는 내용을 바꾸지 말 것

✅ 7. 자바에서 equals()와 hashCode()는 반드시 같이 override 해야 함

@Override
public boolean equals(Object o) {
    return this.id == ((User)o).id;
}

@Override
public int hashCode() {
    return Integer.hashCode(id);
}
  • equals()만 override 하고 hashCode()를 안 하면?
    → HashSet, HashMap에서 동일 객체를 서로 다른 것으로 처리

✅ 8. 해시의 한계와 충돌 문제

문제설명
해시 충돌 (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이 왜 해시 구조에 최적화되어 있는가?

필요하면 HashMapHashSet의 내부 구조도 그림과 예제로 하나씩 해설해줄게.
혹은 equals()hashCode()를 직접 구현하는 연습 문제도 줄 수 있어.

어떤 방향으로 더 확장해서 공부하고 싶어?

  • HashMap 구조 해부?
  • String의 해시 동작?
  • 사용자 정의 클래스에서의 equals/hashCode 연습?
  • 실전 실기 문제로 연습?

0개의 댓글