Một thể hiện có thể bằng một số thể hiện khác của một loại cụ thể hơn không?


25

Tôi đã đọc bài viết này: Cách viết một phương thức bình đẳng trong Java .

Về cơ bản, nó cung cấp một giải pháp cho một phương thức equals () hỗ trợ kế thừa:

Point2D twoD   = new Point2D(10, 20);
Point3D threeD = new Point3D(10, 20, 50);
twoD.equals(threeD); // true
threeD.equals(twoD); // true

Nhưng nó có phải là một ý tưởng tốt? hai trường hợp này có vẻ bằng nhau nhưng có thể có hai mã băm khác nhau. Điều đó có sai một chút không?

Tôi tin rằng điều này sẽ đạt được tốt hơn bằng cách đúc các toán hạng thay thế.


1
Ví dụ với các điểm màu như được đưa ra trong liên kết có ý nghĩa hơn đối với tôi. Tôi sẽ xem xét rằng một điểm 2D (x, y) có thể được xem là điểm 3D có thành phần Z bằng 0 (x, y, 0) và tôi muốn đẳng thức trả về sai trong trường hợp của bạn. Trên thực tế, trong bài viết, một Điểm màu được nói rõ ràng là khác với Điểm và luôn trả về sai.
coredump

10
Không có gì tệ hơn những hướng dẫn phá vỡ những quy ước thông thường ... Phải mất nhiều năm để loại bỏ những thói quen đó ra khỏi các lập trình viên.
corsiKa

3
@coredump Xử lý điểm 2D là có ztọa độ 0 có thể là một quy ước hữu ích cho một số ứng dụng (hệ thống CAD sớm xử lý dữ liệu kế thừa xuất hiện trong tâm trí). Nhưng đó là một quy ước tùy ý. Các mặt phẳng trong không gian có 3 chiều trở lên có thể có các hướng tùy ý ... đó là điều khiến các vấn đề thú vị trở nên thú vị.
ben rudgers

Câu trả lời:


71

Điều này không nên bình đẳng vì nó phá vỡ tính siêu việt . Hãy xem xét hai biểu thức sau:

new Point3D(10, 20, 50).equals(new Point2D(10, 20)) // true
new Point2D(10, 20).equals(new Point3D(10, 20, 60)) // true

Vì đẳng thức là bắc cầu, điều này có nghĩa là biểu thức sau đây cũng đúng:

new Point3D(10, 20, 50).equals(new Point3D(10, 20, 60))

Nhưng tất nhiên - không phải vậy.

Vì vậy, ý tưởng đúc của bạn là chính xác - mong rằng trong Java, truyền đơn giản có nghĩa là truyền kiểu tham chiếu. Những gì bạn thực sự muốn ở đây là một phương thức chuyển đổi sẽ tạo ra một Point2Dđối tượng mới từ một Point3Dđối tượng. Điều này cũng sẽ làm cho biểu thức có ý nghĩa hơn:

twoD.equals(threeD.projectXY())

1
Bài viết mô tả các triển khai phá vỡ tính siêu việt và cung cấp một loạt các công việc xung quanh. Trong một miền nơi chúng tôi cho phép các điểm 2D, chúng tôi đã quyết định rằng chiều thứ ba không thành vấn đề. và do đó (10, 20, 50)tương đương (10, 20, 60)là tốt. Chúng tôi chỉ quan tâm 1020.
ben rudgers

1
Nên Point2Dcó một projectXYZ()phương pháp để cung cấp một Point3Dđại diện của chính nó? Nói cách khác, nên thực hiện biết nhau?
hjk

4
@hjk Việc loại bỏ Point2Dcó vẻ đơn giản hơn vì việc chiếu các điểm 2D yêu cầu xác định mặt phẳng của chúng trong không gian 3D trước tiên. Nếu điểm 2D biết đó là mặt phẳng thì đó đã là điểm 3D. Nếu không, nó không thể chiếu. Tôi nhớ đến Abbott's Flatland .
ben rudgers

@benrudgers Tuy nhiên, bạn có thể xác định một Plane3Dđối tượng sẽ xác định một mặt phẳng trong không gian 3D, mặt phẳng đó có thể có một liftphương thức (2D-> 3D đang nâng lên, không chiếu) sẽ chấp nhận một Point2Dvà một số cho "trục thứ ba "- khoảng cách từ mặt phẳng dọc theo mặt phẳng bình thường. Để dễ sử dụng, bạn có thể định nghĩa các mặt phẳng chung là hằng số tĩnh, do đó bạn có thể làm những việc nhưPlane3D.XY.lift(new Point2D(10, 20), 50).equals(new Point3D(10, 20, 50))
Idan Arye

@IdanArye Tôi đã nhận xét về đề xuất rằng các điểm 2D nên có phương pháp chiếu. Đối với các mặt phẳng với các phương pháp nâng, tôi nghĩ rằng nó sẽ yêu cầu hai đối số có ý nghĩa: một điểm 2D và mặt phẳng mà nó được giả định là trên, tức là nó thực sự cần phải là một phép chiếu nếu nó không sở hữu điểm ... và nếu nó sở hữu điểm, tại sao không sở hữu một điểm 3D và loại bỏ một loại dữ liệu có vấn đề và mùi của một phương pháp bị loại bỏ? YMMV.
ben rudgers

10

Tôi rời khỏi việc đọc bài báo nghĩ về sự khôn ngoan của Alan J. Perlis:

Epigram 9. Tốt hơn là có 100 chức năng hoạt động trên một cấu trúc dữ liệu hơn 10 chức năng trên 10 cấu trúc dữ liệu.

Thực tế là việc có được "sự bình đẳng" là một vấn đề khiến nhà phát minh Scala của Martin Orderky phải thức dậy vào ban đêm nên tạm dừng về việc liệu ghi đè lên equalscây thừa kế có phải là một ý tưởng hay không.

Điều gì xảy ra khi chúng ta không may mắn nhận được một ColoredPointhình học là do hình học của chúng ta thất bại vì chúng ta đã sử dụng tính kế thừa để phổ biến các kiểu dữ liệu thay vì tạo ra một kiểu tốt. Điều này mặc dù phải quay lại và sửa đổi nút gốc của cây thừa kế để thực hiện equalscông việc. Tại sao không chỉ cần thêm một zvà một colorđể Point?

Lý do chính đáng sẽ làm điều đó PointColoredPointhoạt động trong các lĩnh vực khác nhau ... ít nhất là nếu các tên miền đó không bao giờ trộn lẫn. Tuy nhiên, nếu đó là trường hợp, chúng ta không cần phải ghi đè equals. So sánh ColoredPointPointcho sự bình đẳng chỉ có ý nghĩa trong một miền thứ ba nơi họ được phép trộn lẫn. Và trong trường hợp đó, có lẽ tốt hơn là nên có "sự bình đẳng" phù hợp với miền thứ ba đó thay vì cố gắng áp dụng ngữ nghĩa bình đẳng từ một hoặc một hoặc cả hai miền không bị ràng buộc. Nói cách khác, "bình đẳng" nên được định nghĩa cục bộ tại nơi chúng ta có bùn chảy từ cả hai phía vì chúng ta có thể không muốn ColoredPoint.equals(pt)thất bại trước các trường hợp Pointngay cả khi tác giả ColoredPointnghĩ rằng đó là một ý tưởng hay vào sáu tháng trước lúc 2 giờ sáng .


6

Khi các vị thần lập trình cũ đang phát minh ra lập trình hướng đối tượng với các lớp, họ đã quyết định khi nói đến thành phần và kế thừa có hai mối quan hệ cho một đối tượng: "là một" và "có một".
Điều này đã giải quyết một phần vấn đề của các lớp con khác với các lớp cha nhưng làm cho chúng có thể sử dụng được mà không vi phạm mã. Bởi vì một thể hiện của lớp con "là một" đối tượng siêu lớp và có thể được thay thế trực tiếp cho nó, mặc dù lớp con có nhiều hàm thành viên hoặc thành viên dữ liệu hơn, "có" đảm bảo nó sẽ thực hiện tất cả các chức năng của cha mẹ và có tất cả các chức năng của nó các thành viên. Vì vậy, bạn có thể nói Point3D "là" Điểm và Điểm2D "là" Điểm nếu cả hai đều thừa hưởng từ Điểm. Ngoài ra, Point3D có thể là một lớp con của Point2D.

Tuy nhiên, sự bình đẳng giữa các lớp là vấn đề cụ thể theo miền, và ví dụ trên không rõ ràng về những gì lập trình viên cần để chương trình hoạt động chính xác. Nói chung, các quy tắc miền toán học được tuân theo và các giá trị của dữ liệu sẽ tạo ra sự bình đẳng nếu bạn giới hạn phạm vi so sánh chỉ trong trường hợp này là hai chiều, nhưng không phải nếu bạn so sánh tất cả các thành viên dữ liệu.

Vì vậy, bạn nhận được một bảng thu hẹp công bằng:

Both objects have same values, limited to subset of shared members

Child classes can be equal to parent classes if parent and childs
data members are the same.

Both objects entire data members are the same.

Objects must have all same values and be similar classes. 

Objects must have all same values and be the same class type. 

Equality is determined by specific logical conditions in the domain.

Only Objects that both point to same instance are equal. 

Bạn thường chọn các quy tắc nghiêm ngặt nhất mà bạn có thể vẫn sẽ thực hiện tất cả các chức năng cần thiết trong miền vấn đề của bạn. Các bài kiểm tra đẳng thức tích hợp cho các số được thiết kế hạn chế nhất có thể cho mục đích toán học, nhưng lập trình viên có nhiều cách để làm điều đó nếu đó không phải là mục tiêu, bao gồm làm tròn lên / xuống, cắt, gt, lt, v.v. . Các đối tượng có dấu thời gian thường được so sánh theo thời gian tạo của chúng và do đó mỗi trường hợp phải là duy nhất để các phép so sánh trở nên rất cụ thể.

Yếu tố thiết kế trong trường hợp này là xác định các cách hiệu quả để so sánh các đối tượng. Đôi khi một so sánh đệ quy của tất cả các thành viên dữ liệu đối tượng là những gì bạn phải làm và điều đó có thể rất tốn kém nếu bạn có rất nhiều đối tượng có nhiều thành viên dữ liệu. Các lựa chọn thay thế là chỉ so sánh các giá trị dữ liệu có liên quan hoặc để đối tượng tạo ra giá trị băm của các thành viên dữ liệu liên quan để so sánh nhanh với các đối tượng tương tự khác, giữ cho các bộ sưu tập được sắp xếp và cắt tỉa để so sánh nhanh hơn và ít cpu hơn và có thể cho phép các đối tượng là giống hệt nhau trong dữ liệu được loại bỏ và một con trỏ trùng lặp đến một đối tượng được đặt vào vị trí của nó.


2

Quy tắc là, bất cứ khi nào bạn ghi đè hashcode(), bạn ghi đè equals()và ngược lại. Đây có phải là một ý tưởng tốt hay không phụ thuộc vào mục đích sử dụng. Cá nhân, tôi sẽ đi với một phương pháp khác ( isLike()hoặc tương tự) để đạt được hiệu quả tương tự.


1
Có thể OK để ghi đè hashCode mà không ghi đè bằng. Ví dụ, người ta sẽ làm điều đó để kiểm tra một thuật toán băm khác nhau cho cùng một điều kiện đẳng thức.
Patricia Shanahan

1

Nó thường hữu ích cho các lớp không công khai có phương thức kiểm tra tương đương cho phép các đối tượng thuộc các loại khác nhau coi nhau là "bằng nhau" nếu chúng đại diện cho cùng một thông tin, nhưng vì Java không cho phép các lớp có thể mạo danh từng lớp khác thường là tốt để có một loại trình bao bọc công khai duy nhất trong trường hợp có thể có các đối tượng tương đương với các biểu diễn khác nhau.

Ví dụ, hãy xem xét một lớp đóng gói một ma trận 2D bất biến của các doublegiá trị. Nếu một phương thức bên ngoài yêu cầu ma trận nhận dạng có kích thước 1000, thì một phương thức thứ hai yêu cầu ma trận đường chéo và vượt qua một mảng chứa 1000, và một phần ba yêu cầu ma trận 2D và truyền một mảng 1000x1000 trong đó các phần tử trên đường chéo chính đều là 1.0 và tất cả các số khác đều bằng 0, các đối tượng được cung cấp cho cả ba lớp có thể sử dụng các cửa hàng sao lưu khác nhau bên trong [lớp đầu tiên có một trường duy nhất cho kích thước, lớp thứ hai có mảng nghìn phần tử và lớp thứ ba có một mảng 1000 phần tử] nhưng nên báo cáo lẫn nhau như nhau [vì cả ba gói gọn một ma trận bất biến 1000x1000 với các ma trận trên đường chéo và số 0 ở mọi nơi khác].

Ngoài thực tế là nó che giấu sự tồn tại của các loại cửa hàng sao lưu riêng biệt, trình bao bọc cũng sẽ hữu ích cho việc tạo điều kiện so sánh, vì việc kiểm tra các mục tương đương thường sẽ là một quá trình gồm nhiều bước. Hỏi mục đầu tiên nếu nó biết liệu nó có bằng mục thứ hai không; nếu nó không biết, hãy hỏi cái thứ hai nếu nó biết nó có bằng cái thứ nhất không. Nếu không đối tượng nào biết, thì hãy hỏi từng mảng về nội dung của các thành phần riêng lẻ của nó [người ta có thể thêm các kiểm tra khác trước khi quyết định thực hiện tuyến so sánh vật phẩm riêng lẻ chậm dài].

Lưu ý rằng phương pháp kiểm tra tương đương cho từng đối tượng trong kịch bản này sẽ cần trả về giá trị ba trạng thái ("Có, tôi tương đương", "Không tôi không tương đương" hoặc "Tôi không biết"), vì vậy phương pháp "bằng" bình thường sẽ không phù hợp. Mặc dù bất kỳ đối tượng nào cũng có thể trả lời "Tôi không biết" khi được hỏi về bất kỳ đối tượng nào khác, việc thêm logic vào ví dụ ma trận đường chéo sẽ không bận tâm hỏi bất kỳ ma trận nhận dạng hoặc ma trận đường chéo nào về bất kỳ yếu tố nào ngoài đường chéo chính sẽ giúp so sánh rất nhiều giữa các yếu tố đó các loại.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.