기준
객체를 값처럼 쓰려면 비교 기준이 필요하고, 상태를 분기하려면 복사 기준이 필요하다. 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;
}
제네릭 배열을 바로 만들려 함
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 회고