소스: java-src/intermediate/07_object_methods (Member, Point, Order, Main).
cd java-src/intermediate/07_object_methods
javac -encoding UTF-8 *.java && java -Dstdout.encoding=UTF-8 Mainstatic class Plain { final long id; Plain(long id) { this.id = id; } } // equals 정의 없음
Plain a = new Plain(1), b = new Plain(1);
System.out.println("a == b: " + (a == b) + " a.equals(b): " + a.equals(b));
System.out.println("list.contains(new Plain(1)): " + List.of(a).contains(new Plain(1)));
System.out.println("HashSet 크기: " + new HashSet<>(List.of(a, b)).size());a == b: false a.equals(b): false ← Object.equals 는 == 와 같다
list.contains(new Plain(1)): false ← contains 는 equals 로 찾는다
HashSet 크기: 2 ← 중복 제거가 안 된다Point p = new Point(1, 2), q = new Point(1, 2); // Point 는 equals 만 정의
Set<Point> set = new HashSet<>(); set.add(p); set.add(q);
Map<Point, String> map = new HashMap<>(); map.put(p, "A");
System.out.println("map.get(new Point(1,2)): " + map.get(new Point(1, 2)));p.equals(q): true p.hashCode()==q.hashCode(): false
HashSet 크기: 2 set.contains(new Point(1,2)): false
map.get(new Point(1,2)): null ← 해시가 다르니 다른 버킷을 뒤진다
List.contains 는 여전히 동작: true (해시를 안 쓰니까)equals 는 true 인데 HashSet 에 둘 다 들어가고 map.get 은 null 입니다. 마지막 줄이 이 버그가 늦게 발견되는 이유입니다. List 로 테스트하면 통과합니다.
Member m1 = new Member(7, "김철수", "kim@a.com"), m2 = new Member(7, "김철수(개명)", "kim@b.com");
Set<Member> set = new HashSet<>(List.of(m1, m2));
Map<Member, Integer> points = new HashMap<>(); points.put(m1, 100); points.merge(m2, 50, Integer::sum);
Order o1 = new Order(1, "kim", new BigDecimal("1000"), LocalDate.of(2026, 9, 9));
Order o3 = new Order(1, "kim", new BigDecimal("1000.0"), LocalDate.of(2026, 9, 9));m1.equals(m2): true (id 만 같으면 같은 회원) sameProfile: false
HashSet 크기: 1 contains(new Member(7,...)): true
포인트 합산: {Member{id=7, name=김철수, email=kim@a.com}=150}
m1.equals(null): false m1.equals("7"): false
record 자동 equals: o1.equals(o2) = true, o1.equals(o3) = false ← BigDecimal 1000 vs 1000.0 은 equals 가 false
record 자동 toString: Order[no=1, customer=kim, amount=1000, date=2026-09-09]id 가 같은 두 Member 가 하나로 합쳐져 포인트가 150 이 됐습니다. merge 가 m2 를 키로 넣었는데 m1 과 같다고 판단해 더한 것입니다. record 는 한 줄로 같은 일을 하지만, BigDecimal 스케일 차이까지 다르게 보는 점은 주의해야 합니다.
기본 toString: Main$Plain@446cdf90 ← 클래스명@해시(16진수). 로그에 찍히면 아무 정보가 없다
오버라이드: Member{id=3, name=이영희, email=lee@a.com}
문자열 결합·printf·로그 프레임워크·디버거 모두 toString 을 부른다: [Member{id=3, name=이영희, email=lee@a.com}]Main$Plain 의 $ 는 내부 클래스라는 뜻이고, @446cdf90 은 기본 hashCode 의 16진수입니다. 실행할 때마다 달라집니다.
orders.sort(null); // Comparable: 날짜 → 번호
orders.sort(Comparator.comparing(Order::amount).reversed().thenComparing(Order::no));
orders.sort(Comparator.comparing(Order::customer, Comparator.nullsLast(Comparator.naturalOrder())));
TreeSet<Order> byAmount = new TreeSet<>(Comparator.comparing(Order::amount)); byAmount.addAll(orders);자연 순서(날짜→번호): [4, 3, 1, 2]
금액 내림차순, 같으면 번호: [1:12000, 2:5000, 3:5000, 4:700]
고객명 오름차순, null 은 마지막: [kim, lee, park, null]
금액 기준 TreeSet 크기: 3 (주문 4건 중 5000원 두 건이 '같다' 고 판단되어 하나 탈락)nullsLast 가 없었다면 customer 가 null 인 주문 4 에서 NullPointerException 이 납니다. TreeSet 이 4건 중 3건만 담은 것이 2.8 의 함정입니다.
이름 변경 후 contains: true (id 기준 해시라 안전. 해시에 쓴 필드를 바꿨다면 false 가 된다)
hashCode 없는 Point: contains(자기 자신)=true, contains(같은 값)=false
BigDecimal 1.0 vs 1.00 → equals false, compareTo 0 ← 금액 비교는 compareTo==0 으로
compareTo 를 뺄셈으로 만들면: -2147483639 (양수여야 하는데 음수 → 정렬이 뒤집힌다). Integer.compare = 1Integer.MAX_VALUE - (-10) 은 수학적으로 양수지만 int 범위를 넘어 음수가 됩니다. 정렬이 "가끔" 틀리는 버그의 전형입니다.