기준

객체를 값처럼 쓰려면 비교 기준이 필요하고, 상태를 분기하려면 복사 기준이 필요하다. DFS와 시뮬레이션에서는 이 둘이 자주 터진다.

상태 관리 지도

flowchart TD State["상태를 다음 분기로 넘김"] --> Mutable{"내부 객체가 변하는가"} Mutable -->|아니오| Share["공유해도 안전"] Mutable -->|예| Branch{"분기마다 달라지는가"} Branch -->|예| DeepCopy["deep copy"] Branch -->|아니오| Reuse["재사용 가능"] DeepCopy --> Compare{"Set Map key로 쓰는가"} Compare -->|예| Equality["equals hashCode 필요"] Compare -->|아니오| Done["복사 기준만 확인"]

2차원 객체 배열을 얕게 복사

발생 장면

BOJ19236 청소년 상어에서 격자 배열만 새로 만들고 내부 Fish 객체는 같은 참조를 공유했다.

Fish[][] copy = new Fish[4][4];
copy[r][c] = grid[r][c]; // 같은 Fish 객체

한 분기에서 물고기를 움직이면 다른 분기 상태도 같이 바뀐다.

고치는 기준

Fish[][] copy = new Fish[4][4];
copy[r][c] = grid[r][c] == null ? null : new Fish(grid[r][c]);

상세: 얕은 복사

instanceof 확인 후 캐스팅을 빼먹음

if (other instanceof Position) {
    return row == other.row; // other는 여전히 Object
}

Java 8 기준에서는 명시적으로 캐스팅한다.

if (other instanceof Position) {
    Position p = (Position) other;
    return row == p.row && col == p.col;
}

상세: instanceof 후 캐스팅 누락

제네릭 배열을 바로 만들려 함

List<Integer>[] graph = new List<Integer>[n]; // 불가

코딩테스트에서는 아래 둘 중 하나로 정리한다.

선택코드기준
배열 유지List<Integer>[] graph = new ArrayList[n];빠르고 흔하지만 경고가 날 수 있음
리스트 중첩List<List<Integer>> graph = new ArrayList<>();타입 안정성이 더 좋음

상세: 제네릭 배열 생성

객체 비교 기준 없이 key로 사용

HashSet<Pos>에 좌표를 넣었는데 같은 좌표가 중복 저장되면 equals()hashCode()를 의심한다.

record Pos(int row, int col) {
}

record를 쓸 수 없으면 두 메서드를 직접 만든다.

복사보다 undo를 고집

이동, 교환, 방향 회전이 섞이면 되돌리기 코드가 길어진다. 상태 크기가 작으면 deep copy가 더 단순할 수 있다.

관련: BOJ19236 회고