Hai lựa chọn đánh đổi giữa tính đối xứng và nguyên tắc thay thế Liskov, không có đáp án đúng cho mọi trường hợp.
instanceof cho phép so sánh giữa lớp cha và lớp con, nhưng vỡ đối xứng nếu lớp con thêm state và override equals:
Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, RED);
p.equals(cp); // true - Point chỉ so x, y
cp.equals(p); // false - ColorPoint so thêm màuHợp đồng equals yêu cầu a.equals(b) == b.equals(a). Vi phạm khiến List.contains, Set, Map cho kết quả phụ thuộc thứ tự tham số.
getClass() giữ đối xứng tuyệt đối vì chỉ hai object đúng cùng class mới bằng nhau, nhưng phá LSP: một subclass không thêm state (proxy Hibernate/CGLIB, subclass chỉ để log) sẽ không bao giờ bằng instance gốc — đây là nguồn lỗi kinh điển với JPA entity khi so sánh entity đã load lazy.
Cách chọn thực dụng:
- Class là final hoặc record → dùng instanceof, không có subclass thì không có vấn đề gì.
- Class có thể bị proxy (JPA entity) → instanceof và chỉ so theo id nghiệp vụ.
- Cần cho phép kế thừa mà vẫn đúng đối xứng → dùng getClass(), hoặc mẫu canEqual mà bài viết của Odersky/Spoon/Venners mô tả.
Dù chọn cách nào, hashCode() phải dựa trên đúng tập field mà equals() dùng.