6. 연습 문제
문제 1
Money(BigDecimal amount, String currency) 클래스를 일반 클래스로 만들고, 통화가 같고 금액이 compareTo 기준으로 같으면 동등하도록 equals/hashCode 를 작성하세요. new Money("1.0", "KRW") 와 new Money("1.00", "KRW") 가 HashSet 에서 하나로 합쳐져야 합니다.
정답 보기
public final class Money {
private final BigDecimal amount; private final String currency;
public Money(String amount, String currency) { this.amount = new BigDecimal(amount); this.currency = currency; }
@Override public boolean equals(Object o) {
return o instanceof Money m && currency.equals(m.currency) && amount.compareTo(m.amount) == 0;
}
@Override public int hashCode() { return Objects.hash(amount.stripTrailingZeros(), currency); } // 1.0 과 1.00 의 해시를 같게: 뒤 0 제거 후 계산
}hashCode 에서 amount 를 그대로 쓰면 1.0 과 1.00 의 해시가 달라 규약(같으면 해시도 같다)을 어깁니다. stripTrailingZeros() 로 정규화한 값을 해시에 넣어야 합니다. 더 간단한 방법은 생성자에서 스케일을 통일하고 record 로 만드는 것입니다(변형 3).
문제 2
Order 를 "고객명 오름차순(null 은 마지막) → 날짜 내림차순 → 금액 내림차순" 으로 정렬하는 Comparator 를 한 식으로 작성하고, Main 의 주문 4건에 적용한 결과를 예측한 뒤 실행으로 확인하세요.
정답 보기
Comparator<Order> cmp = Comparator
.comparing(Order::customer, Comparator.nullsLast(Comparator.naturalOrder()))
.thenComparing(Order::date, Comparator.reverseOrder())
.thenComparing(Order::amount, Comparator.reverseOrder());
orders.sort(cmp);
// 예측: kim(1) → lee(2) → park(3) → null(4). 같은 고객이 없으므로 2·3차 기준은 이 데이터에서 작동하지 않는다thenComparing(키 추출, 비교자) 형태로 각 단계마다 방향을 따로 줄 수 있습니다. reversed() 는 앞의 전체를 뒤집으므로 중간에 끼우면 의도와 달라집니다. 단계별 방향은 Comparator.reverseOrder() 를 두 번째 인자로 주는 것이 안전합니다.
문제 3
Point 에 hashCode 를 추가한 뒤, HashSet 에 넣고 필드를 바꿀 수 없음(final)을 확인하세요. 그런 다음 Point 를 record 로 바꾸면 소스가 몇 줄로 줄어드는지, record 로 바꿀 수 없는 경우는 어떤 경우인지 설명하세요.
정답 보기
public record Point(int x, int y) { } // equals·hashCode·toString·접근자·생성자 전부 자동. 12줄 → 1줄record 로 바꿀 수 없는 경우: (1) 필드 일부만 식별자인 엔티티(Member 의 id), (2) 생성 후 값이 바뀌어야 하는 객체(record 는 모든 필드가 final), (3) 다른 클래스를 상속해야 하는 경우(record 는 상속 불가, 인터페이스 구현만 가능), (4) JPA 엔티티처럼 기본 생성자와 세터를 요구하는 프레임워크 대상.