Tại sao C # và Java sử dụng đẳng thức tham chiếu làm mặc định cho '=='?


32

Tôi đã suy nghĩ một lúc tại sao Java và C # (và tôi chắc chắn các ngôn ngữ khác) mặc định để tham chiếu bình đẳng cho ==.

Trong lập trình tôi làm (điều chắc chắn chỉ là một tập hợp nhỏ của các vấn đề lập trình), tôi hầu như luôn muốn có sự bằng nhau logic khi so sánh các đối tượng thay vì đẳng thức tham chiếu. Tôi đã cố gắng nghĩ tại sao cả hai ngôn ngữ này lại đi theo con đường này thay vì đảo ngược nó và có ==sự bình đẳng logic và sử dụng .ReferenceEquals()cho sự bình đẳng tham chiếu.

Rõ ràng việc sử dụng đẳng thức tham chiếu rất đơn giản để thực hiện và nó mang lại hành vi rất nhất quán, nhưng có vẻ như nó không phù hợp với hầu hết các thực tiễn lập trình mà tôi thấy ngày nay.

Tôi không muốn tỏ ra thờ ơ với các vấn đề khi cố gắng thực hiện so sánh logic và rằng nó phải được thực hiện trong mỗi lớp. Tôi cũng nhận ra rằng những ngôn ngữ này đã được thiết kế từ lâu, nhưng câu hỏi chung vẫn tồn tại.

Có một số lợi ích chính của việc mặc định cho điều này mà tôi đơn giản là thiếu, hoặc có vẻ hợp lý rằng hành vi mặc định phải là đẳng thức logic và mặc định trở lại tham chiếu đẳng thức mà một đẳng thức logic không tồn tại cho lớp?


3
Vì các biến là tham chiếu? Vì các biến hoạt động như con trỏ, nên có ý nghĩa đối với chúng được so sánh như thế
Daniel Gratzer

C # sử dụng đẳng thức logic cho các loại giá trị như cấu trúc. Nhưng "đẳng thức logic mặc định" phải là gì đối với hai đối tượng thuộc các kiểu tham chiếu khác nhau? Hoặc cho hai đối tượng trong đó một đối tượng thuộc loại A được thừa hưởng từ B? Luôn luôn "sai" như đối với cấu trúc? Ngay cả khi bạn có cùng một đối tượng được tham chiếu hai lần, đầu tiên là A, sau đó là B? Không có ý nghĩa nhiều với tôi.
Doc Brown

3
Nói cách khác, bạn đang hỏi tại sao trong C #, nếu bạn ghi đè Equals(), nó không tự động thay đổi hành vi của ==?
Svick

Câu trả lời:


29

C # làm điều đó bởi vì Java đã làm. Java đã làm vì Java không hỗ trợ quá tải toán tử. Vì đẳng thức giá trị phải được xác định lại cho mỗi lớp, nó không thể là toán tử, mà thay vào đó phải là một phương thức. IMO đây là một quyết định kém. Nó dễ dàng hơn nhiều để viết và đọc a == bhơn a.equals(b), và tự nhiên hơn nhiều đối với các lập trình viên có kinh nghiệm C hoặc C ++, nhưng a == bhầu như luôn luôn sai. Lỗi từ việc sử dụng những ==nơi .equalsđược yêu cầu đã lãng phí vô số hàng ngàn giờ lập trình viên.


7
Tôi nghĩ rằng có nhiều người ủng hộ quá tải nhà điều hành cũng như có những kẻ gièm pha, vì vậy tôi sẽ không nói "đó là một quyết định tồi" như một tuyên bố tuyệt đối. Ví dụ: trong dự án C ++ tôi làm việc trong chúng tôi đã quá tải ==cho nhiều lớp và vài tháng trước tôi phát hiện ra một vài nhà phát triển không biết những gì ==đang thực sự làm. Luôn có rủi ro này khi ngữ nghĩa của một số cấu trúc không rõ ràng. Các equals()ký hiệu nói với tôi rằng tôi đang sử dụng một phương pháp tùy chỉnh và rằng tôi phải tìm nó lên đâu đó. Điểm mấu chốt: Tôi nghĩ rằng quá tải toán tử là một vấn đề mở nói chung.
Giorgio

9
Tôi muốn nói rằng Java không có quá tải toán tử do người dùng định nghĩa . Rất nhiều toán tử có nghĩa kép (quá tải) trong Java. Nhìn vào +ví dụ, trong đó bổ sung (của các giá trị số) và nối chuỗi cùng một lúc.
Joachim Sauer

14
Làm thế nào có a == bthể tự nhiên hơn cho các lập trình viên có kinh nghiệm C, vì C không hỗ trợ quá tải toán tử do người dùng định nghĩa? (Ví dụ: cách C để so sánh các chuỗi là strcmp(a, b) == 0, không a == b.)
svick

Về cơ bản, đây là những gì tôi nghĩ, nhưng tôi đoán tôi sẽ hỏi những người có nhiều kinh nghiệm hơn để đảm bảo rằng tôi không thiếu thứ gì đó rõ ràng.
Khóa kéo

4
@svick: trong C, không có loại chuỗi, cũng không có loại tham chiếu nào. Các hoạt động chuỗi được thực hiện thông qua char *. Có vẻ rõ ràng với tôi rằng so sánh hai con trỏ cho bình đẳng không giống như so sánh chuỗi.
kevin cline

15

Câu trả lời ngắn gọn: Tính nhất quán

Tuy nhiên, để trả lời đúng câu hỏi của bạn, tôi đề nghị chúng ta lùi một bước và xem xét vấn đề về sự bình đẳng có nghĩa là gì trong ngôn ngữ lập trình. Có ít nhất BA khả năng khác nhau, được sử dụng trong các ngôn ngữ khác nhau:

  • Bình đẳng tham chiếu : có nghĩa là a = b là đúng nếu a và b tham chiếu đến cùng một đối tượng. Sẽ không đúng nếu a và b đề cập đến các đối tượng khác nhau, ngay cả khi tất cả các thuộc tính của a và b là như nhau.
  • Bình đẳng nông : có nghĩa là a = b là đúng nếu tất cả các thuộc tính của các đối tượng mà a và b tham chiếu là giống hệt nhau. Bình đẳng nông có thể dễ dàng được thực hiện bằng cách so sánh bitwise của không gian bộ nhớ đại diện cho hai đối tượng. Xin lưu ý rằng bình đẳng tham chiếu ngụ ý bình đẳng nông
  • Bình đẳng sâu : có nghĩa là a = b là đúng nếu mỗi thuộc tính trong a và b giống hệt nhau hoặc bằng nhau sâu sắc. Xin lưu ý rằng bình đẳng sâu được ngụ ý bởi cả bình đẳng tham chiếu và bình đẳng nông. Theo nghĩa này, bình đẳng sâu sắc là hình thức bình đẳng yếu nhất và bình đẳng tham chiếu là mạnh nhất.

Ba loại đẳng thức này thường được sử dụng vì chúng thuận tiện để thực hiện: cả ba kiểm tra đẳng thức đều có thể dễ dàng được tạo bởi trình biên dịch (trong trường hợp đẳng thức sâu, trình biên dịch có thể cần sử dụng các bit thẻ để ngăn vòng lặp vô hạn nếu cấu trúc được so sánh có tài liệu tham khảo tròn). Nhưng có một vấn đề khác: không ai trong số này có thể phù hợp.

Trong các hệ thống không tầm thường, sự bình đẳng của các đối tượng thường được định nghĩa là một cái gì đó giữa bình đẳng sâu và tham chiếu. Để kiểm tra xem chúng ta có muốn coi hai đối tượng là bằng nhau trong một bối cảnh nhất định hay không, chúng ta có thể yêu cầu một số thuộc tính được so sánh bằng vị trí của nó trong bộ nhớ và các đối tượng khác bằng sự bình đẳng sâu sắc, trong khi một số thuộc tính có thể được phép hoàn toàn khác nhau. Những gì chúng tôi thực sự muốn là một loại bình đẳng của người Viking, một loại thực sự tốt đẹp, thường được gọi là bình đẳng ngữ nghĩa văn học . Mọi thứ đều bình đẳng nếu chúng bằng nhau, trong miền của chúng tôi. =)

Vì vậy, chúng tôi có thể trở lại câu hỏi của bạn:

Có một số lợi ích chính của việc mặc định cho điều này mà tôi đơn giản là thiếu, hoặc có vẻ hợp lý rằng hành vi mặc định phải là đẳng thức logic và mặc định trở lại đẳng thức tham chiếu nếu một đẳng thức logic không tồn tại cho lớp?

Chúng ta có ý nghĩa gì khi chúng ta viết 'a == b' bằng bất kỳ ngôn ngữ nào? Lý tưởng nhất, nó phải luôn giống nhau: Bình đẳng ngữ nghĩa. Nhưng điều đó không thể.

Một trong những cân nhắc chính là, ít nhất là đối với các loại đơn giản như số, chúng tôi hy vọng rằng hai biến bằng nhau sau khi gán cùng một giá trị. Xem bên dưới:

var a = 1;
var b = a;
if (a == b){
    ...
}
a = 3;
b = 3;
if (a == b) {
    ...
}

Trong trường hợp này, chúng tôi hy vọng rằng 'a bằng b' trong cả hai câu lệnh. Bất cứ điều gì khác sẽ là điên rồ. Hầu hết (nếu không phải tất cả) các ngôn ngữ tuân theo quy ước này. Do đó, với các loại đơn giản (còn gọi là giá trị), chúng ta biết cách đạt được sự bình đẳng ngữ nghĩa. Với các đối tượng, đó có thể là một cái gì đó hoàn toàn khác. Xem bên dưới:

var a = new Something(1);
var b = a;
if (a == b){
    ...
}
b = new Something(1);
a.DoSomething();
b.DoSomething();
if (a == b) {
    ...
}

Chúng tôi hy vọng rằng 'nếu' đầu tiên sẽ luôn luôn đúng. Nhưng bạn mong đợi điều gì ở 'nếu'? Nó thực sự phụ thuộc. 'DoS Something' có thể thay đổi đẳng thức (ngữ nghĩa) của a và b không?

Vấn đề với sự bình đẳng ngữ nghĩa là nó không thể được trình biên dịch tự động tạo ra cho các đối tượng, cũng như nó không rõ ràng từ các bài tập . Một cơ chế phải được cung cấp cho người dùng để xác định bình đẳng ngữ nghĩa. Trong các ngôn ngữ hướng đối tượng, cơ chế đó là một phương thức được kế thừa: bằng . Đọc một đoạn mã OO, chúng tôi không thể mong đợi một phương thức có cùng triển khai chính xác trong tất cả các lớp. Chúng ta đã quen với sự kế thừa và quá tải.

Tuy nhiên, với các nhà khai thác, chúng tôi mong đợi hành vi tương tự. Khi bạn thấy 'a == b', bạn sẽ mong đợi cùng một loại đẳng thức (từ 4 ở trên) trong mọi tình huống. Vì vậy, hướng đến sự thống nhất, các nhà thiết kế ngôn ngữ đã sử dụng sự bình đẳng tham chiếu cho tất cả các loại. Nó không nên phụ thuộc vào việc một lập trình viên có ghi đè một phương thức hay không.

PS: Ngôn ngữ Dee hơi khác so với Java và C #: toán tử bằng có nghĩa là bình đẳng nông cho các loại đơn giản và bình đẳng ngữ nghĩa cho các lớp do người dùng định nghĩa (có trách nhiệm thực hiện thao tác = nằm với người dùng - không được cung cấp mặc định). Vì, đối với các loại đơn giản, bình đẳng nông luôn là bình đẳng ngữ nghĩa, ngôn ngữ là nhất quán. Tuy nhiên, giá mà nó phải trả là toán tử bằng được mặc định không xác định cho các loại do người dùng xác định. Bạn phải thực hiện nó. Và, đôi khi, những điều đó thật nhàm chán.


2
When you see ‘a == b’ you should expect the same type of equality (from the 4 above) in all situations.Các nhà thiết kế ngôn ngữ của Java đã sử dụng đẳng thức tham chiếu cho các đối tượng và bình đẳng ngữ nghĩa cho các nguyên thủy. Tôi không rõ ràng rằng đây là quyết định đúng đắn hoặc quyết định này "nhất quán" hơn là cho phép ==quá tải đối với sự bình đẳng về ngữ nghĩa của các đối tượng.
Charles Salvia

Họ đã sử dụng "tương đương với đẳng thức tham chiếu" cho nguyên thủy. Khi bạn sử dụng "int i = 3", không có con trỏ nào cho số đó, vì vậy bạn có thể sử dụng tài liệu tham khảo. Với các chuỗi, loại "nguyên thủy", nó rõ ràng hơn: bạn phải sử dụng ".i INTERN ()" hoặc gán trực tiếp (Chuỗi s = "abc") để sử dụng == (đẳng thức tham chiếu).
Hbas

1
PS: C #, mặt khác, không phù hợp với chuỗi của nó. Và IMHO, trong trường hợp này, điều đó tốt hơn nhiều.
Hbas

@CharlesSalvia: Trong Java, nếu abcùng loại, biểu thức a==bkiểm tra xem abgiữ cùng một thứ hay không. Nếu một trong số họ giữ tham chiếu đến đối tượng # 291 và đối tượng kia giữ tham chiếu đến đối tượng # 572, thì họ không giữ điều tương tự. Các nội dung của đối tượng # 291 và # 572 có thể tương đương, nhưng các biến tự giữ những điều khác nhau.
supercat

2
@CharlesSalvia Nó được thiết kế theo cách mà bạn có thể thấy a == bvà biết những gì nó làm. Tương tự như vậy, bạn có thể thấy a.equals(b)và cho rằng quá tải equals. Nếu a == bcác cuộc gọi a.equals(b)(nếu được thực hiện), nó được so sánh bằng tham chiếu hoặc theo nội dung? Đừng nhớ? Bạn phải kiểm tra lớp A. Mã không còn nhanh để đọc nếu bạn thậm chí không chắc chắn những gì đang được gọi. Sẽ như thể các phương thức có cùng chữ ký được cho phép và phương thức được gọi phụ thuộc vào phạm vi hiện tại là gì. Những chương trình như vậy sẽ không thể đọc được.
Neil

0

Tôi đã cố gắng nghĩ tại sao cả hai ngôn ngữ này lại đi theo con đường này thay vì đảo ngược nó và có == là sự bình đẳng logic và sử dụng .ReferenceEquals () cho sự bình đẳng tham chiếu.

Bởi vì cách tiếp cận sau sẽ gây nhầm lẫn. Xem xét:

if (null.ReferenceEquals(null)) System.out.println("ok");

Mã này nên in "ok", hay nó nên ném NullPointerException?


-2

Đối với Java và C #, lợi ích nằm ở việc hướng đối tượng của họ.

Từ quan điểm hiệu suất - việc viết mã cũng dễ dàng hơn cũng nên nhanh hơn: vì OOP dự định các phần tử khác biệt logic được đại diện bởi các đối tượng khác nhau, kiểm tra đẳng thức tham chiếu sẽ nhanh hơn, xem xét rằng các đối tượng có thể trở nên khá lớn.

Từ quan điểm logic - sự bình đẳng của một đối tượng đối với một đối tượng khác không nhất thiết phải rõ ràng như so sánh với các thuộc tính của đối tượng cho sự bình đẳng (ví dụ như null == null được giải thích một cách hợp lý như thế nào?

Tôi nghĩ những gì nó sôi nổi, là quan sát của bạn rằng "bạn luôn muốn bình đẳng logic hơn bình đẳng tham chiếu". Sự đồng thuận giữa các nhà thiết kế ngôn ngữ có lẽ ngược lại. Cá nhân tôi thấy khó đánh giá điều này, vì tôi thiếu trải nghiệm lập trình phổ rộng. Một cách thô bạo, tôi sử dụng công bằng tham chiếu nhiều hơn trong các thuật toán tối ưu hóa và công bằng logic hơn trong việc xử lý các tập dữ liệu.


7
Bình đẳng tham chiếu không có gì để làm với hướng đối tượng. Thực tế hoàn toàn ngược lại: một trong những tính chất cơ bản của hướng đối tượng là các đối tượng có cùng hành vi không thể phân biệt được. Một đối tượng phải có khả năng mô phỏng một đối tượng khác. (Rốt cuộc, OO đã được phát minh để mô phỏng!) Bình đẳng tham chiếu cho phép bạn phân biệt giữa hai đối tượng khác nhau có cùng hành vi, nó cho phép bạn phân biệt giữa một đối tượng mô phỏng và đối tượng thực. Do đó, Equality tham chiếu phá vỡ hướng đối tượng. Một chương trình OO không được sử dụng Equality Reference.
Jörg W Mittag

@ JörgWMittag: Để thực hiện chương trình hướng đối tượng đúng cách đòi hỏi phải có phương tiện hỏi đối tượng X xem trạng thái của nó có bằng trạng thái của Y [một điều kiện có khả năng nhất thời] không, và cũng là phương tiện hỏi đối tượng X có tương đương với Y không [X chỉ tương đương với Y nếu trạng thái của nó được đảm bảo vĩnh viễn bằng Y]. Có các phương thức ảo riêng biệt cho tương đương và bình đẳng nhà nước sẽ tốt, nhưng đối với nhiều loại, bất bình đẳng tham chiếu sẽ hàm ý không tương đương, và không có lý do gì để dành thời gian cho việc gửi phương thức ảo để chứng minh điều đó.
supercat

-3

.equals()so sánh các biến theo nội dung của chúng. thay vì ==so sánh các đối tượng bởi nội dung của chúng ...

sử dụng các đối tượng là chính xác hơn sử dụng .equals()


3
Bạn giả định là không chính xác. .equals () làm bất cứ điều gì .equals () đã được mã hóa để làm. Nó thường là theo nội dung, nhưng nó không phải là. Ngoài ra, nó không chính xác hơn để sử dụng .equals (). Nó chỉ phụ thuộc vào những gì bạn đang cố gắng để hoàn thành.
Khóa kéo
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.