Tại sao các mảng trong Java không ghi đè bằng ()?


8

Tôi đã làm việc với một HashSetngày khác, trong đó có phần này được viết trong thông số:

[add ()] thêm phần tử e đã chỉ định vào tập hợp này nếu tập hợp này không chứa phần tử e2 sao cho (e == null? e2 == null: e.equals (e2))

Tôi đã sử dụng char[]trong HashSetcho đến khi tôi nhận ra rằng, căn cứ vào hợp đồng này, nó không tốt hơn so với một ArrayList! Vì nó sử dụng không bị ghi đè .equals(), các mảng của tôi sẽ chỉ được kiểm tra tính bằng nhau tham chiếu, điều này không đặc biệt hữu ích. Tôi biết điều đó Arrays.equals()tồn tại, nhưng điều đó không giúp ích gì khi một người đang sử dụng các bộ sưu tập như HashSet.

Vì vậy, câu hỏi của tôi là, tại sao các mảng Java sẽ không ghi đè bằng?


3
trộn các mảng bản địa và bộ sưu tập không thực sự được khuyến khích
ratchet freak

1
Đó là nguyên tắc của vấn đề, tôi nghĩ vậy. Một mảng là cấu trúc dữ liệu mệnh lệnh nguyên thủy thứ hai sau các biến có thể thay đổi. Nó chỉ là ncác khe cắm bộ nhớ đủ lớn để mỗi cái giữ một số giá trị của loại được Tdán liên tục. Chúng là các khối xây dựng cho các bộ sưu tập phù du tinh vi hơn. Những gì bạn muốn là một List.
Doval

1
@ratchetfreak Tôi thường nghe mọi người nói vậy, nhưng không bao giờ tại sao. Tại sao nó là một ý tưởng tồi?
Richard Tingle

@RichardTingle Mảng không phải lúc nào cũng kết hợp tốt với thuốc generic, mảng không thể lặp lại được, toString()đại diện của chúng hầu như vô dụng và hầu như không có lợi thế nào khi sử dụng một cái ArrayList.
Doval

@Doval Nhưng đó là tất cả lý do để không sử dụng mảng nào cả (mà tôi đồng ý với 95% thời gian). Đôi khi tôi cần sử dụng mảng vì chúng chơi đẹp với đồ họa 3D nhưng tôi muốn ánh xạ một phím cho chúng. Vì vậy, Map<Key, int[]>luôn luôn cảm thấy tự nhiên. Nhưng tôi luôn lo lắng rằng có một điều gì đó khủng khiếp đang chờ đợi tôi
Richard Tingle

Câu trả lời:


8

Có một quyết định thiết kế để sớm đưa ra trong Java:

Là mảng nguyên thủy? hoặc họ là đối tượng?

Câu trả lời là, không thực sự ... hoặc cả hai nếu bạn nhìn nó theo một cách khác. Họ làm việc khá chặt chẽ với chính hệ thống và phụ trợ của jvm.

Một ví dụ về điều này là phương thức java.lang.System.arraycopy () cần lấy một mảng thuộc bất kỳ loại nào. Vì vậy, mảng cần có khả năng kế thừa một cái gì đóđó là một Object. Và mảng là một phương pháp bản địa.

Mảng cũng hài hước ở chỗ chúng có thể giữ nguyên thủy ( int, char, double, vv ... trong khi các bộ sưu tập khác chỉ có thể giữ các đối tượng. Hãy nhìn xem, ví dụ, tại java.util.Arrays và xấu xí của bằng phương pháp này. Điều này đã được đưa vào như một suy nghĩ sau. DeepEquals (Object [], Object []) không được thêm cho đến 1.5 trong khi phần còn lại của lớp Arrays được thêm vào 1.2.

Vì các đối tượng này là mảng , chúng cho phép bạn thực hiện một số thứ ở mức bộ nhớ hoặc gần mức bộ nhớ - thứ mà Java thường ẩn khỏi bộ mã hóa. Điều này cho phép một số điều được thực hiện nhanh hơn với chi phí chủ yếu là phá vỡ mô hình đối tượng.

Có một sự đánh đổi sớm trong hệ thống giữa tính linh hoạt và một số hiệu suất. Hiệu suất đã thắng và sự thiếu linh hoạt được gói gọn trong các bộ sưu tập khác nhau. Mảng trong Java là một Đối tượng được triển khai mỏng trên đầu loại nguyên thủy (ban đầu) dành cho làm việc với hệ thống khi bạn cần.

Đối với hầu hết các phần, mảng thô là những thứ mà dường như các nhà thiết kế ban đầu đã cố gắng bỏ qua và chỉ giấu trong hệ thống. Và họ muốn nó nhanh (Java ban đầu có một số vấn đề về tốc độ). Đó là một mụn cóc trên thiết kế mà mảng không phải là Mảng đẹp, nhưng đó là thứ cần thiết khi bạn muốn phơi bày thứ gì đó càng gần hệ thống càng tốt. Đối với vấn đề đó, các ngôn ngữ đương đại của Java thời kỳ đầu cũng có vấn đề này - người ta không thể thực hiện một .equals()mảng trên C ++.

Cả Java và C ++ đều có cùng một đường dẫn cho mảng - một thư viện bên ngoài thực hiện các hoạt động cần thiết trên mảng thay vì Mảng ... và đề nghị các lập trình viên sử dụng các kiểu nguyên gốc tốt hơn trừ khi họ thực sự biết họ đang làm gì và tại sao họ lại làm làm theo cách đó

Do đó, cách tiếp cận cấy .equals trong một mảng là sai, nhưng nó cũng sai tương tự mà các lập trình viên đến từ C ++ biết. Vì vậy, đã chọn điều sai ít nhất về mặt hiệu suất - hãy để nó là việc triển khai Đối tượng: hai Đối tượng bằng nhau khi và chỉ khi chúng được đề cập đến cùng một đối tượng.

Bạn cần mảng phải là một cấu trúc giống như nguyên thủy để có thể giao tiếp với các ràng buộc riêng - một cái gì đó càng gần với mảng C cổ điển càng tốt. Nhưng không giống như các nguyên thủy khác, bạn cần mảng để có thể được chuyển qua làm tham chiếu, và do đó, là một Đối tượng. Vì vậy, nó còn hơn cả nguyên thủy với một số hack Object ở bên cạnh và một số giới hạn kiểm tra.


+1 và cảm ơn câu trả lời. Đây là những gì tôi thực sự tìm kiếm, quan điểm thiết kế đằng sau quyết định, không chỉ là ý tưởng tồi.
Azar

1
@Azar có một số cuộc thảo luận tại C2: Mảng Java nên là Đối tượng hạng nhất có một số mã cho thấy một số vụ tấn công xảy ra đằng sau hậu trường với một mảng ... cùng với những người khác nghĩ rằng mảng không phải là Đối tượng đẹp.

2
May mắn thay cho C ++, làm cho một lớp mảng thông minh hơn không cần đấm bốc. Java không may mắn như vậy.
Thomas Eding

3

Trong Java, mảng là các đối tượng giả. Tham chiếu đối tượng có thể chứa các mảng và chúng có các phương thức Object tiêu chuẩn, nhưng chúng rất nhẹ so với một bộ sưu tập thực sự. Mảng làm chỉ đủ để đáp ứng các hợp đồng của một đối tượng và sử dụng các cài đặt mặc định của equals, hashCodetoStringkhá thận trọng.

Hãy xem xét một Object[]. Một phần tử của mảng này có thể là bất cứ thứ gì phù hợp với một đối tượng, bao gồm một mảng khác. Nó có thể là một nguyên thủy đóng hộp, một ổ cắm, bất cứ điều gì. Bình đẳng có nghĩa là gì trong trường hợp đó? Vâng, nó phụ thuộc vào những gì thực sự trong mảng. Đó không phải là điều được biết đến trong trường hợp chung khi ngôn ngữ đang được thiết kế. Bình đẳng được xác định cả bởi chính mảng cũng như nội dung của nó .

Đây là lý do tại sao có một Arrayslớp người trợ giúp có các phương thức để tính toán đẳng thức (bao gồm cả bằng sâu), mã băm, v.v. Tuy nhiên, những phương thức đó được xác định rõ theo như những gì họ làm. Nếu bạn cần chức năng khác nhau, hãy viết phương pháp của riêng bạn để so sánh hai mảng cho sự bình đẳng dựa trên nhu cầu của chương trình của bạn.


Mặc dù không hoàn toàn là một câu trả lời cho câu hỏi của bạn, tôi nghĩ rằng có liên quan để nói rằng bạn thực sự nên sử dụng các bộ sưu tập thay vì mảng. Chỉ chuyển đổi thành một mảng khi giao tiếp với API yêu cầu mảng. Mặt khác, các bộ sưu tập cung cấp loại an toàn tốt hơn, hợp đồng được xác định rõ hơn và thường dễ sử dụng hơn mảng.


Mảng là đối tượng thực sự. Thực tế rằng chúng là loại tổng hợp đa phần tử duy nhất có nghĩa là bất kỳ bộ sưu tập có kích thước biến đổi nào khác phải được hỗ trợ bởi các mảng hoặc các đối tượng O (N) khác bên ngoài chính nó, do đó, các mảng so sánh sẽ "nhẹ". Tôi nghĩ vấn đề cơ bản hơn không phải là mảng rất nhẹ, mà là có rất nhiều cách sử dụng chúng. Xem câu trả lời của tôi dưới đây.
supercat

1
"Bình đẳng có nghĩa là gì trong trường hợp đó?" - ừm, làm thế nào mà các mảng có cùng độ dài, cộng với mọi đối tượng ở mọi chỉ số trong cả nguồn và đích phải bằng nhau theo hợp đồng bằng ()? có vẻ như những gì bạn mong đợi và là hợp đồng chính xác được Arrays.equals () thực hiện.
Jeffrey Blattman

@JeffreyBlattman - điều gì xảy ra, sau đó, nếu một mảng Object chứa chính nó?
Jules

@JeffreyBlattman sau đó đưa nó lên với các tác giả của ngôn ngữ đã thực hiện công bằng tham chiếu cho mảng, nhưng cung cấp Arrays.equals()cho sự bình đẳng sâu sắc.

@Jules "chuyện gì xảy ra, sau đó, nếu một mảng Object chứa chính nó?" điều tương tự xảy ra nếu một đối tượng chứa chính nó. nếu bạn thực hiện một giá trị bằng ngây thơ, thì bạn sẽ bị tràn stack.
Jeffrey Blattman

1

Khó khăn cơ bản với các mảng ghi đè equalslà một biến kiểu như int[]có thể được sử dụng theo ít nhất ba cách khác nhau về cơ bản và ý nghĩa của việc equalsthay đổi tùy thuộc vào cách sử dụng. Đặc biệt, một lĩnh vực loại int[]...

  1. ... Có thể gói gọn một chuỗi các giá trị trong một mảng sẽ không bao giờ được sửa đổi, nhưng có thể được chia sẻ tự do với mã không sửa đổi nó.

  2. ... Có thể gói gọn quyền sở hữu độc quyền của một container chứa số nguyên có thể bị thay đổi theo ý muốn của chủ sở hữu.

  3. ... có thể xác định một thùng chứa số nguyên mà một số thực thể khác đang sử dụng để đóng gói trạng thái của nó và do đó đóng vai trò là kết nối với trạng thái của thực thể khác đó.

Nếu một lớp có một int[]trường foođược sử dụng cho một trong hai mục đích đầu tiên, thì các trường hợp xynên xem xét x.fooy.foođóng gói cùng một trạng thái nếu chúng giữ cùng một dãy số; tuy nhiên, nếu trường được sử dụng cho mục đích thứ ba, tuy nhiên, x.fooy.foosẽ chỉ gói gọn cùng một trạng thái nếu chúng xác định cùng một mảng [tức là chúng tham chiếu bằng nhau]. Nếu Java đã bao gồm các loại khác nhau cho ba cách sử dụng ở trên và nếu equalslấy tham số xác định cách sử dụng tham chiếu, thì nó sẽ phù hợp int[]để sử dụng đẳng thức trình tự cho hai cách sử dụng đầu tiên và đẳng thức tham chiếu cho lần sử dụng thứ ba. Không có cơ chế như vậy tồn tại, tuy nhiên.

Cũng lưu ý rằng int[]trường hợp này là loại mảng đơn giản nhất. Đối với các mảng chứa tham chiếu đến các lớp khác Objecthoặc các kiểu mảng, sẽ có các khả năng bổ sung.

  1. Một tham chiếu đến một mảng không thay đổi có thể chia sẻ, gói gọn những thứ sẽ không bao giờ thay đổi.

  2. Tham chiếu đến một mảng không thay đổi có thể chia sẻ, xác định những thứ thuộc sở hữu của các thực thể khác.

  3. Một tham chiếu đến một mảng thuộc sở hữu độc quyền bao gồm các tham chiếu đến những thứ sẽ không bao giờ thay đổi.

  4. Tham chiếu đến một mảng thuộc sở hữu độc quyền bao gồm các tham chiếu đến các mục thuộc sở hữu độc quyền.

  5. Tham chiếu đến một mảng thuộc sở hữu độc quyền xác định những thứ thuộc sở hữu của các thực thể khác.

  6. Một tham chiếu xác định một mảng thuộc sở hữu của một số thực thể khác.

Trong trường hợp 1, 3 và 4, hai tham chiếu mảng nên được coi là bằng nhau nếu các mục tương ứng là "giá trị bằng nhau". Trong trường hợp 2 và 5, hai tham chiếu mảng nên được coi là bằng nhau nếu chúng xác định cùng một chuỗi các đối tượng. Trong trường hợp 6, hai tham chiếu mảng chỉ được coi là bằng nhau nếu chúng xác định cùng một mảng.

Để equalshành xử hợp lý với các loại tổng hợp, họ cần có một số cách để biết cách sử dụng tài liệu tham khảo. Thật không may, hệ thống loại của Java không có cách nào để chỉ ra điều đó.


-2

Ghi đè mảng equals()hashCode()phụ thuộc vào nội dung sẽ làm cho chúng tương tự như các bộ sưu tập - các loại có thể thay đổi với hằng số không đổi hashCode(). Các loại thay đổi hashCode()hoạt động kém khi được lưu trữ trong bảng băm và các ứng dụng khác dựa trên hashCode()giá trị cố định.

Set<List<Integer>> data = new HashSet<List<Integer>>();
List<Integer> datum = new ArrayList<Integer>();
datum.add(1);
data.add(datum);
assert data.contains(datum); // true
datum.add(2);
assert data.contains(datum); // false, WAT???

Mặt khác, mảng có hashCode () tầm thường, có thể được sử dụng làm khóa bảng băm và vẫn có thể thay đổi.

Set<int[]> data = new HashSet<int[]>(67);
int[] datum = new int[]{1, 2};
data.add(datum);
System.out.println(data.contains(datum)); //true
datum[0] = 78;
System.out.println(data.contains(datum)); //true
//PROFIT!!!

3
Không có mảng trong đó. Các int[]kiểu mảng.

@MichaelT, đó là điểm chính.
Basilevs

Tại sao các downvote? Người trả lời đã làm rất nhiều việc. Có gì không ổn à?
Tom Au

3
@TomAu câu hỏi là về thiết kế của mảng trong java, và bản chất đối tượng psuedo của nó và các quyết định thiết kế đằng sau sự lựa chọn đó (để làm cho mảng chỉ sử dụng đẳng thức tham chiếu chứ không phải là một đẳng thức sâu). Câu trả lời này, mặt khác, cố gắng trình bày một giải pháp mã cho cách viết lại mã - đó không phải là những gì câu hỏi yêu cầu.

@MichaelT, điều này minh họa sự thành thạo logic bình đẳng dựa trên danh tính so với người phụ thuộc vào nhà nước.
Basilevs
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.