Câu trả lời thực sự cho
Tại sao có một Comparator
giao diện nhưng không Hasher
và Equator
?
là, trích dẫn lịch sự của Josh Bloch :
Các API Java ban đầu được thực hiện rất nhanh theo thời hạn chặt chẽ để đáp ứng một cửa sổ thị trường đóng cửa. Nhóm Java ban đầu đã làm một công việc đáng kinh ngạc, nhưng không phải tất cả các API đều hoàn hảo.
Vấn đề chỉ nằm trong lịch sử của Java, như với các vấn đề tương tự khác, ví dụ .clone()
so với Cloneable
.
tl; dr
đó là vì lý do lịch sử là chủ yếu; hành vi / trừu tượng hiện tại đã được giới thiệu trong JDK 1.0 và sau đó không được sửa chữa vì hầu như không thể làm như vậy với việc duy trì khả năng tương thích mã ngược.
Đầu tiên, hãy tổng hợp một vài sự kiện Java nổi tiếng:
- Java, từ khi bắt đầu cho đến ngày nay, đã tự hào tương thích ngược, yêu cầu các API kế thừa vẫn được hỗ trợ trong các phiên bản mới hơn,
- như vậy, gần như mọi cấu trúc ngôn ngữ được giới thiệu với JDK 1.0 tồn tại cho đến ngày nay,
Hashtable
, .hashCode()
& .equals()
đã được triển khai trong JDK 1.0, ( Hashtable )
Comparable
/ Comparator
đã được giới thiệu trong JDK 1.2 ( Có thể so sánh ),
Bây giờ, nó theo sau:
- Hầu như không thể và vô nghĩa khi trang bị thêm
.hashCode()
và .equals()
cho các giao diện riêng biệt trong khi vẫn duy trì khả năng tương thích ngược sau khi mọi người nhận ra có sự trừu tượng hóa tốt hơn so với việc đưa chúng vào superobject, bởi vì, ví dụ như mọi người lập trình Java đều biết rằng mọi người đều Object
có chúng và họ đã có để duy trì ở đó một cách vật lý để cung cấp khả năng tương thích mã được biên dịch (JVM) - và thêm một giao diện rõ ràng cho mọi Object
lớp con thực sự triển khai chúng sẽ làm cho mớ hỗn độn này (sic!) thành Clonable
một ( Bloch thảo luận về lý do tại sao Clonizable , cũng được thảo luận trong ví dụ EJ 2nd và nhiều nơi khác, bao gồm SO),
- họ chỉ để chúng ở đó cho thế hệ tương lai có nguồn WTF liên tục.
Bây giờ, bạn có thể hỏi "những gì Hashtable
có với tất cả điều này"?
Câu trả lời là: hashCode()
/ equals()
hợp đồng và các kỹ năng thiết kế ngôn ngữ không tốt của các nhà phát triển Java cốt lõi vào năm 1995/1996.
Trích dẫn từ Spec 1.0 Language Spec, ngày 1996 - 4.3.2 Lớp Object
, tr.41:
Các phương thức equals
và hashCode
được khai báo vì lợi ích của hashtables, chẳng hạn như java.util.Hashtable
(§21.7). Phương thức bằng xác định một khái niệm về đẳng thức đối tượng, dựa trên giá trị, không tham chiếu, so sánh.
(lưu ý tuyên bố này chính xác đã được thay đổi trong các phiên bản sau này, để nói, trích dẫn: The method hashCode is very useful, together with the method equals, in hashtables such as java.util.HashMap.
, làm cho nó không thể thực hiện trực tiếp Hashtable
- hashCode
- equals
kết nối mà không đọc JLS lịch sử!)
Nhóm Java đã quyết định họ muốn có một bộ sưu tập kiểu từ điển tốt và họ đã tạo ra Hashtable
(ý tưởng tốt cho đến nay), nhưng họ muốn lập trình viên có thể sử dụng nó với càng ít mã / đường cong học tập càng tốt (rất tiếc! và, vì vẫn chưa có chung chung [đó là JDK 1.0], điều đó có nghĩa là mọi người Object
được đưa vào Hashtable
sẽ phải thực hiện một cách rõ ràng một số giao diện (và các giao diện vẫn chỉ mới bắt đầu từ lúc đó ... Comparable
thậm chí còn chưa!) , làm cho điều này trở thành một biện pháp ngăn chặn để sử dụng nó cho nhiều người - hoặc Object
sẽ phải ngầm thực hiện một số phương pháp băm.
Rõ ràng, họ đã đi với giải pháp 2, vì những lý do đã nêu ở trên. Yup, bây giờ chúng tôi biết họ đã sai. ... thật dễ dàng để trở nên thông minh trong nhận thức muộn màng. cười thầm
Bây giờ, hashCode()
yêu cầu mọi đối tượng có nó phải có một equals()
phương thức riêng biệt - vì vậy nó cũng khá rõ ràng equals()
phải được đưa vào Object
.
Kể từ khi mặc định triển khai những phương pháp trên có hiệu lực a
& b
Object
s cơ bản là vô dụng bằng cách dự phòng (làm a.equals(b)
bằng đến a==b
và a.hashCode() == b.hashCode()
xấp xỉ bằng để a==b
cũng có, trừ khi hashCode
và / hoặc equals
được overriden, hoặc bạn GC hàng trăm ngàn Object
s trong vòng đời của ứng dụng của bạn 1 ) , thật an toàn khi nói rằng chúng được cung cấp chủ yếu như một biện pháp dự phòng và để thuận tiện cho việc sử dụng. Đây chính xác là cách chúng ta có được một thực tế nổi tiếng là luôn ghi đè cả hai .equals()
& .hashCode()
nếu bạn có ý định thực sự so sánh các đối tượng hoặc lưu trữ băm chúng. Chỉ ghi đè một trong số chúng mà không có cái kia là một cách tốt để vặn mã của bạn (bằng cách so sánh kết quả xấu hoặc giá trị va chạm xô cực kỳ cao) - và nhận được xung quanh nó là một nguồn gây nhầm lẫn & lỗi liên tục cho người mới bắt đầu (tìm kiếm SO để xem nó cho chính mình) và phiền toái thường xuyên cho những người dày dạn hơn.
Ngoài ra, lưu ý rằng mặc dù C # xử lý bằng và mã băm theo cách tốt hơn một chút, bản thân Eric Lippert nói rằng họ đã mắc lỗi gần như tương tự với C # mà Sun đã làm với Java nhiều năm trước khi thành lập C # :
Nhưng tại sao mọi đối tượng nên có thể tự băm để chèn vào bảng băm? Có vẻ như một điều kỳ lạ để yêu cầu mọi đối tượng có thể làm. Tôi nghĩ rằng nếu chúng ta thiết kế lại hệ thống loại từ đầu ngày hôm nay, việc băm có thể được thực hiện khác đi, có lẽ với một IHashable
giao diện. Nhưng khi hệ thống loại CLR được thiết kế, không có loại chung và do đó cần có bảng băm mục đích chung để có thể lưu trữ bất kỳ đối tượng nào.
Tất nhiên, 1Object#hashCode
vẫn có thể va chạm, nhưng phải mất một chút nỗ lực để làm điều đó, hãy xem: http://bugs.java.com/bugdatabase/view_orms.do?orms_id=6809470 và báo cáo lỗi được liên kết để biết chi tiết; /programming/1381060/hashcode-uniquety/1381114#1381114 bao quát chủ đề này sâu hơn.
Person
thực hiện dự kiếnequals
vàhashCode
hành vi. Sau đó bạn sẽ có mộtHashMap<PersonWrapper, V>
. Đây là một ví dụ trong đó cách tiếp cận OOP thuần túy không thanh lịch: không phải mọi thao tác trên một đối tượng đều có ý nghĩa như một phương thức của đối tượng đó. Toàn bộ JavaObject
loại là một hỗn hợp của các trách nhiệm khác nhau - chỉgetClass
,finalize
vàtoString
phương pháp dường như từ xa chính đáng bằng cách thực hành tốt nhất hiện nay.