Mã băm của ArrayList chứa chính nó như là một phần tử


38

Chúng ta có thể tìm thấy hashcodemột trong listđó có chứa chính nó element?

Tôi biết đây là một thực hành tồi, nhưng đây là những gì người phỏng vấn hỏi.

Khi tôi chạy đoạn mã sau, nó sẽ ném StackOverflowError:

public class Main {
    public static void main(String args[]) {
        ArrayList<ArrayList> a = new ArrayList();
        a.add(a);
        a.hashCode();
    }
}

Bây giờ ở đây tôi có hai câu hỏi:

  1. Tại sao có một StackOverflowError?
  2. Có thể tìm mã băm theo cách này?

7
Bởi vì bạn thêm Danh sách vào chính nó. thử a.hashCode () mà không có câu lệnh add
Jens

Khi bạn đặt một đối tượng vào một danh sách mảng, bạn đang lưu trữ tham chiếu đến đối tượng. Trong trường hợp của bạn, bạn đang đặt một phù thủy ArrayList chính là tham chiếu.
Vishwa Ratna


Ok, tôi hiểu lý do tại sao có stackoverflow, ai đó có thể giúp tôi giải thích vấn đề số 2- Làm thế nào để tìm thấy điều này
Joker

9
Như những người khác đã trả lời, điều này là không thể, theo định nghĩa của Listgiao diện, hashCodedanh sách phụ thuộc vào các thành viên của nó. Cho rằng danh sách là thành viên của chính nó, mã băm của nó phụ thuộc vào danh sách, nó hashCodephụ thuộc vào hashCode... và cứ thế, gây ra đệ quy vô hạn và StackOverflowErrorbạn đang chạy vào. Bây giờ câu hỏi là: tại sao bạn yêu cầu một danh sách để chứa chính nó? Tôi có thể đảm bảo với bạn rằng bạn có thể đạt được bất cứ điều gì mà bạn đang cố gắng làm, theo cách tốt hơn, mà không yêu cầu thành viên đệ quy như thế này.
Alexander - Tái lập Monica

Câu trả lời:


36

Mã băm để Listthực hiện tuân thủ đã được chỉ định trong giao diện :

Trả về giá trị mã băm cho danh sách này. Mã băm của danh sách được xác định là kết quả của phép tính sau:

 int hashCode = 1;
 for (E e : list)
     hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());

Điều này đảm bảo rằng list1.equals(list2)ngụ ý rằng list1.hashCode()==list2.hashCode()đối với bất kỳ hai danh sách, list1list2, theo yêu cầu của hợp đồng chung Object.hashCode().

Điều này không yêu cầu việc triển khai trông giống hệt như vậy (xem Cách tính mã băm cho luồng theo cách tương tự như List.hashCode () cho một giải pháp thay thế), nhưng mã băm chính xác cho danh sách chỉ chứa chính nó nói cách khác, một số x == 31 + xphải truelà số không thể tính được một số phù hợp.


1
@Holger, Eirc muốn thay thế mã của toàn bộ hàm hashCode()để trả về 0. Điều này về mặt kỹ thuật giải quyết x == 31 + xnhưng bỏ qua yêu cầu x phải có ít nhất 1.
bxk21

4
@EricDuminil quan điểm của câu trả lời của tôi là, hợp đồng mô tả một logic ArrayListthực hiện theo nghĩa đen, dẫn đến đệ quy, nhưng cũng không có triển khai thay thế phù hợp. Lưu ý rằng tôi đã đăng câu trả lời của mình tại thời điểm OP đã hiểu lý do tại sao việc triển khai cụ thể này dẫn đến một StackOverflowError, điều này đã được giải quyết trong các câu trả lời khác. Vì vậy, tôi tập trung vào sự bất khả thi chung của việc thực hiện tuân thủ hoàn thành trong thời gian hữu hạn với một giá trị.
Holger

2
@pdem không quan trọng việc thông số kỹ thuật có sử dụng mô tả dài dòng của thuật toán, công thức, mã giả hoặc mã thực tế hay không. Như đã nói trong câu trả lời, mã được đưa ra trong thông số kỹ thuật không loại trừ việc triển khai thay thế nói chung. Hình thức của thông số kỹ thuật không nói lên điều gì về việc phân tích có xảy ra hay không. Câu tài liệu giao diện Trong khi cho phép các danh sách chứa chính chúng là các phần tử, thì hết sức thận trọng: các phương thức bằng và hashCode không còn được xác định rõ trong danh sách như vậy cho thấy một phân tích như vậy đã xảy ra.
Holger

2
@pdem bạn đang đảo ngược nó. Tôi không bao giờ nói rằng danh sách phải bằng nhau vì mã băm. Các danh sách bằng nhau, theo định nghĩa. Arrays.asList("foo")bằng Collections.singletonList("foo"), bằng List.of("foo")bằng new ArrayList<>(List.of("foo")). Tất cả các danh sách này là bằng nhau, điểm. Sau đó, vì các danh sách này bằng nhau, chúng phải có cùng mã băm. Không phải cách khác. Vì chúng phải có cùng mã băm, nên nó phải được xác định rõ. Bất kể họ định nghĩa nó như thế nào (trở lại trong Java 2), nó phải được xác định rõ, để được tất cả các triển khai đồng ý.
Holger

2
@pdem chính xác, một triển khai tùy chỉnh, không thực hiện Listhoặc có dấu hiệu cảnh báo lớn, không trộn lẫn với các danh sách thông thường, so sánh với IdentityHashMapvà của nó Lớp này không phải là triển khai Bản đồ có mục đích chung! " cảnh báo. Trong trường hợp trước, bạn đã ổn với việc triển khai Collectionnhưng cũng thêm các phương thức truy cập dựa trên chỉ mục kiểu danh sách. Sau đó, không tồn tại ràng buộc bình đẳng, nhưng nó vẫn hoạt động trơn tru với các loại bộ sưu tập khác.
Holger

23

Kiểm tra việc thực hiện bộ xương của hashCodephương pháp trong AbstractListlớp.

public int hashCode() {
    int hashCode = 1;
    for (E e : this)
        hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
    return hashCode;
}

Đối với mỗi yếu tố trong danh sách, cuộc gọi này hashCode. Trong trường hợp danh sách của bạn có chính nó là yếu tố duy nhất. Bây giờ cuộc gọi này không bao giờ kết thúc. Phương thức gọi chính nó đệ quy và đệ quy tiếp tục quanh co cho đến khi nó gặp StackOverflowError. Vì vậy, bạn không thể tìm thấy hashCodetheo cách này.


Vì vậy, câu trả lời là không có cách nào để tìm mã băm theo cách này?
Joker

3
Có, vì điều kiện đệ quy
springe

Không chỉ vậy, nó được định nghĩa theo cách này.
chrylis -on đình công-

14

Bạn đã xác định một danh sách (bệnh lý) có chứa chính nó.

Tại sao có StackOverflowError?

Theo javadocs (tức là đặc điểm kỹ thuật), mã băm của a Listđược xác định theo chức năng của mã băm của từng thành phần của nó. Nó nói rằng:

"Mã băm của danh sách được xác định là kết quả của phép tính sau:"

int hashCode = 1;
    for (E e : list)
         hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());

Vì vậy, để tính toán mã băm của a, trước tiên bạn tính mã băm của a. Đó là đệ quy vô hạn và nhanh chóng dẫn đến một tràn ngăn xếp.

Có thể tìm mã băm theo cách này?

Không. Nếu bạn xem xét đặc tả thuật toán ở trên theo thuật ngữ toán học, mã băm của Listchính nó là một hàm không tính toán được . Không thể tính toán theo cách này (sử dụng thuật toán trên) hoặc bất kỳ cách nào khác .


Tôi không biết tại sao câu trả lời này thấp hơn hai câu hỏi kia vì nó thực sự trả lời các câu hỏi của OP với lời giải thích.
Neyt

1
@Neyt một số người dùng chỉ đọc câu trả lời ở trên cùng.
Holger

8

Không, tài liệu có câu trả lời

Các tài liệu về cấu trúc Danh sách một cách rõ ràng rằng:

Lưu ý: Mặc dù các danh sách được phép chứa chính chúng là các phần tử được phép, nhưng hết sức thận trọng: các phương thức bằng và hàm hashCode không còn được xác định rõ trong danh sách đó.

Không có gì nhiều để nói ngoài điều đó - theo đặc tả Java, bạn sẽ không thể tính toán mã băm cho một danh sách chứa chính nó; các câu trả lời khác đi vào chi tiết tại sao nó lại như vậy, nhưng vấn đề là nó được biết và có chủ ý.


1
Bạn đã nói lý do tại sao nó nằm ngoài đặc điểm kỹ thuật, vì vậy nó giải thích rằng đó không phải là một lỗi. Đó là phần còn thiếu trong các câu trả lời khác.
pdem

3

Câu trả lời của Ravindra đưa ra một lời giải thích tốt cho điểm 1. Để nhận xét về câu hỏi 2:

Có thể tìm mã băm theo cách này?

Một cái gì đó là hình tròn ở đây. Ít nhất một trong hai điều này phải sai trong bối cảnh lỗi tràn ngăn xếp này:

  • rằng mã băm của danh sách phải tính đến các yếu tố của nó
  • danh sách đó là thành phần của chính nó

Bây giờ, bởi vì chúng ta đang đối phó với một ArrayList , điểm đầu tiên đã được sửa. Nói cách khác, có thể bạn cần một cách triển khai khác để có thể tính toán một cách có ý nghĩa mã băm của danh sách đệ quy ... Người ta có thể mở rộng ArrayListvà bỏ qua việc thêm mã băm của các phần tử, đại loại như

for (E e : this)
  if(e == this) continue; //contrived rules
  hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());

Sử dụng một lớp như vậy thay vì ArrayList, bạn có thể.

Với ArrayList, điểm thứ hai là sai. Vì vậy, nếu người phỏng vấn có nghĩa là "Có thể tìm mã băm theo cách này (với một danh sách mảng)?" , thì câu trả lời là không, bởi vì điều đó thật vô lý.


1
Việc tính toán mã băm được ủy thác của các Listhợp đồng . Không có thực hiện hợp lệ có thể bỏ qua chính nó. Từ đặc điểm kỹ thuật, bạn có thể lấy được rằng nếu bạn tìm thấy một intsố mà x == 31 + xtrue, sau đó bạn có thể thực hiện một hợp lệ short-cut ...
Holger

Tôi hoàn toàn không hiểu những gì @Holger đang nói. Nhưng có 2 vấn đề với giải pháp: Thứ nhất: Điều này chỉ giải quyết vấn đề khi danh sách này là một mục của chính nó chứ không phải nếu danh sách có mục của một mục của chính nó (lớp đệ quy sâu hơn) Thứ hai: Nếu Danh sách tự bỏ qua có thể bằng một danh sách trống.
Jonas Michel

1
@JonasMichel Tôi không đề xuất một giải pháp. Tôi chỉ tranh luận về mặt triết học xung quanh sự vô lý của câu hỏi 2. Nếu chúng ta phải tính mã băm cho danh sách đó, thì nó sẽ không hoạt động trừ khi chúng ta loại bỏ ràng buộc của danh sách mảng (và Holger củng cố điều đó bằng cách nói Listchính thức ra lệnh logic mã băm được tuân thủ bởi các triển khai)
ernest_k

Sự hiểu biết (hạn chế) của tôi là Listcung cấp một phép tính mã băm, nhưng chúng ta có thể ghi đè lên nó. Yêu cầu thực sự duy nhất là mã băm chung: nếu các đối tượng bằng nhau, thì mã băm phải bằng nhau. Việc thực hiện này tuân theo yêu cầu đó.
Teepeemm

1
@Teepeemm Listlà một giao diện. Nó định nghĩa một hợp đồng , nó không cung cấp một thực hiện. Tất nhiên, hợp đồng bao gồm cả hai, bình đẳng và mã băm. Vì hợp đồng bình đẳng là nó phải đối xứng, nên nếu bạn thay đổi một danh sách thực hiện, bạn sẽ phải thay đổi tất cả các thực thi danh sách hiện có, nếu không, bạn thậm chí sẽ phá vỡ hợp đồng cơ bản java.lang.Object.
Holger

1

Bởi vì khi bạn gọi cùng một chức năng với từ cùng một chức năng thì nó sẽ tạo ra điều kiện đệ quy không bao giờ kết thúc. Và để ngăn chặn hoạt động này, JAVA sẽ trở lạijava.lang.StackOverflowError

Dưới đây là mã ví dụ giải thích kịch bản tương tự:

public class RefTest {

    public static void main(String... strings) {
        RefTest rf = new RefTest();
        rf.message(message("test"));
    }

    public static String message2(String s){
        return message(s);
    }

    public static String message(String s){
        return message2(s);
    }

}   
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.