2015-08-29 18 views
18

Tôi đã đọc khoảng varargs heap pollution và tôi không thực sự nhận được các loại chênh lệch hoặc không thể tái tạo sẽ chịu trách nhiệm về các vấn đề chưa tồn tại mà không có tính tổng quát. Thật vậy, tôi có thể dễ dàng thay thếô nhiễm đống ô nhiễm: thỏa thuận lớn là gì?

public static void faultyMethod(List<String>... l) { 
    Object[] objectArray = l; // Valid 
    objectArray[0] = Arrays.asList(42); 
    String s = l[0].get(0); // ClassCastException thrown here 
} 

với

public static void faultyMethod(String... l) { 
    Object[] objectArray = l; // Valid 
    objectArray[0] = 42; // ArrayStoreException thrown here 
    String s = l[0]; 
} 

thứ hai chỉ đơn giản là sử dụng các hiệp phương sai của mảng, mà thực sự là vấn đề ở đây. (Ngay cả khi List<String> có thể được tái tạo, tôi đoán nó vẫn sẽ là một lớp con của Object và tôi vẫn có thể gán bất kỳ đối tượng nào cho mảng.) Tất nhiên tôi có thể thấy có sự khác biệt nhỏ giữa hai, nhưng mã này bị lỗi cho dù nó sử dụng Generics hay không.

sao họ có ý nghĩa gì bởi đống ô nhiễm (nó làm cho tôi suy nghĩ về việc sử dụng bộ nhớ nhưng vấn đề duy nhất mà họ nói về tiềm năng loại unsafetiness), và làm thế nào là nó khác nhau từ bất kỳ loại vi phạm sử dụng hiệp phương sai mảng?

+0

Câu hỏi hay, cho phép tôi thêm dòng đối tượngArray [0] = 42; là một trong những thực sự ném ArrayStoreException. – Victor

Trả lời

12

Bạn nói đúng rằng vấn đề chung (và cơ bản) là với hiệp phương sai của mảng. Nhưng trong hai ví dụ bạn đưa ra, đầu tiên là nguy hiểm hơn, bởi vì có thể sửa đổi cấu trúc dữ liệu của bạn và đặt chúng vào trạng thái sẽ phá vỡ sau này.

Cân nhắc nếu ví dụ đầu tiên của bạn đã không được kích hoạt các ClassCastException:

public static void faultyMethod(List<String>... l) { 
    Object[] objectArray = l;   // Valid 
    objectArray[0] = Arrays.asList(42); // Also valid 
} 

Và dưới đây là cách ai đó sử dụng nó:

List<String> firstList = Arrays.asList("hello", "world"); 
List<String> secondList = Arrays.asList("hello", "dolly"); 
faultyMethod(firstList, secondList); 
return secondList.isEmpty() 
    ? firstList 
    : secondList; 

Vì vậy, bây giờ chúng tôi có một List<String> mà thực sự chứa một Integer, và nó trôi nổi xung quanh, một cách an toàn. Tại một số thời điểm sau — có thể sau này, và nếu nó được tuần tự hóa, có thể nhiều sau đó và trong một JVM khác — người nào đó cuối cùng sẽ thực hiện String s = theList.get(0). Thất bại này rất xa so với những gì gây ra nó mà nó có thể rất khó để theo dõi.

Lưu ý rằng dấu vết ngăn xếp của ClassCastException không cho chúng tôi biết lỗi thực sự xảy ra ở đâu; nó chỉ cho chúng ta biết ai đã kích hoạt nó. Nói cách khác, nó không cung cấp cho chúng tôi nhiều thông tin về cách sửa lỗi; và đó là những gì làm cho nó lớn hơn một ArrayStoreException.

+0

Vâng, đó là kết luận tôi đã đến để đọc hai câu trả lời khác, mà dường như không nói rõ nó như bạn nhưng đã giúp tôi hiểu nó. Humpf, tôi sẽ có một thời gian khó khăn để chọn người chiến thắng. – Dici

+0

@yshavit, tôi đã nghiên cứu câu trả lời của bạn và thấy nó rất thú vị, cảm ơn bạn đã chia sẻ ... tôi muốn thêm nếu bạn sử dụng phương thức 'faultyMethod (Danh sách .... l)'; truyền các phần tử như các phần tử riêng lẻ như 'faultyMethod (firstList, secondList)' hoặc một mảng ẩn danh như 'faultyMethod (new List [] {firstList, secondList})' bạn muốn bất kỳ vấn đề nào bên ngoài phương thức vì đối tượng đang được sửa đổi là mảng liệt kê rõ ràng hoặc ngầm định (varg args làm điều đó cho bạn) phục vụ như là đối số duy nhất của phương thức. – Victor

+0

Tôi có nghĩa là bạn sẽ gặp sự cố bên ngoài phạm vi chức năng nếu bạn làm những việc như sau: 'Danh sách [] array = new List [] {firstList, secondList}; faultyMethod (mảng); System.out.println (mảng [0] + "" + mảng [1]); ' – Victor

7

Sự khác biệt giữa một mảng và Danh sách là mảng kiểm tra tham chiếu của nó. ví dụ.

Object[] array = new String[1]; 
array[0] = new Integer(1); // fails at runtime. 

tuy nhiên

List list = new ArrayList<String>(); 
list.add(new Integer(1)); // doesn't fail. 
+2

Vâng, tôi biết điều này. Tôi đoán những gì bạn có ý nghĩa bởi đó là mảng được coi là an toàn hơn vì tôi sẽ không thể lừa anh ta trong thời gian chạy ('ArrayStoreException') trong khi' List' sẽ không phàn nàn, và do đó họ tạo ra một cảnh báo tại thời gian biên dịch? – Dici

+0

@Dici vâng, đây là ý của tôi. +1 –

5

Từ các tài liệu liên quan, tôi tin rằng những gì Oracle nghĩa bởi "ô nhiễm đống" là phải có giá trị dữ liệu được về mặt kỹ thuật cho phép đặc tả JVM, nhưng không được công nhận bởi các quy tắc cho generics trong ngôn ngữ lập trình Java.

Để cung cấp cho bạn một ví dụ, giả sử chúng ta định nghĩa một List chứa đơn giản như thế này:

class List<E> { 
    Object[] values; 
    int len = 0; 

    List() { values = new Object[10]; } 

    void add(E obj) { values[len++] = obj; } 
    E get(int i) { return (E)values[i]; } 
} 

Đây là một ví dụ về mã đó là chung chung và an toàn:

List<String> lst = new List<String>(); 
lst.add("abc"); 

Đây là một ví dụ về mã sử dụng các loại thô (bỏ qua generics) nhưng vẫn tôn trọng loại an toàn ở cấp ngữ nghĩa, bởi vì giá trị chúng tôi đã thêm có loại tương thích:

String x = (String)lst.values[0]; 

The twist - bây giờ đây là mã mà làm việc với các loại nguyên liệu và làm điều gì đó xấu, gây "ô nhiễm đống":

lst.values[lst.len++] = new Integer("3"); 

Đoạn mã trên làm việc vì mảng là loại Object[], mà có thể lưu trữ Integer. Bây giờ khi chúng tôi cố gắng để lấy lại giá trị, nó sẽ gây ra một ClassCastException - lúc hồi (đó là cách sau khi tham nhũng xảy ra), thay vì ở add thời gian:

String y = lst.get(1); // ClassCastException for Integer(3) -> String 

Lưu ý rằng ClassCastException xảy ra trong hiện tại của chúng tôi khung ngăn xếp, không ngay cả trong List.get(), bởi vì các diễn viên trong List.get() là một no-op tại thời gian chạy do hệ thống xóa loại Java.

Về cơ bản, chúng tôi đã chèn Integer vào một số List<String> bằng cách bỏ qua Generics.Sau đó, khi chúng tôi cố gắng get() một phần tử, đối tượng danh sách không thể duy trì lời hứa của nó rằng nó phải trả về một String (hoặc null).

+0

Vâng, tôi biết điều đó nhưng tôi nghĩ rằng với bạn câu trả lời và Peter Lawrey của tôi có thể thấy lý do tại sao họ đã làm điều đó. Mảng sẽ ném một ngoại lệ ngay tại thời điểm chèn trong khi một danh sách chung (ví dụ) sẽ chỉ thất bại khi đọc giá trị, có thể không xảy ra hoặc xảy ra lâu sau khi chèn, làm cho gỡ lỗi khó hơn. Đó là ý của bạn? – Dici

2

Trước khi dùng generics, hoàn toàn không có khả năng loại thời gian chạy của đối tượng không phù hợp với loại tĩnh của đối tượng. Điều này rõ ràng là một tài sản rất mong muốn.

Chúng tôi có thể truyền một đối tượng đến loại thời gian chạy không chính xác nhưng dàn diễn viên sẽ không thành công ngay lập tức, tại vị trí chính xác của quá trình truyền; lỗi dừng lại ở đó.

Object obj = "string"; 
((Integer)obj).intValue(); 
// we are not gonna get an Integer object 

Với sự ra đời của thuốc generic, cùng với loại chô bôi (thư mục gốc của mọi điều xấu xa), bây giờ nó có thể là một phương thức trả về String lúc biên dịch, nhưng trả Integer khi chạy. Điều này là fucked lên. Và chúng ta nên làm mọi thứ có thể để ngăn chặn nó từ nguồn. Đó là lý do tại sao trình biên dịch rất thanh nhạc về mọi cảnh của các phôi không được kiểm soát.

Điều tồi tệ nhất về ô nhiễm đống là hành vi thời gian chạy không xác định! Trình biên dịch/thời gian chạy khác nhau có thể thực thi chương trình theo nhiều cách khác nhau. Xem case1case2

+0

Cảm ơn. Bây giờ tôi đã có tâm trí rõ ràng hơn về điều này, tôi nghi ngờ các phương pháp cầu phải chịu trách nhiệm cho kỳ lạ 'ClassCastException' đôi khi tôi nhận được khi serializing đóng cửa với Spark – Dici

1

Chúng khác nhau vì ClassCastExceptionArrayStoreException là khác nhau.

Quy tắc kiểm tra kiểu biên dịch Generics phải đảm bảo rằng bạn không thể đặt một số ClassCastException ở nơi bạn không đặt một diễn viên rõ ràng, trừ khi mã của bạn (hoặc một số mã bạn gọi hoặc gọi cho bạn) đã làm điều gì đó không an toàn biên dịch-thời gian, trong trường hợp bạn nên (hoặc bất kỳ mã nào đã làm điều không an toàn nên) nhận được một cảnh báo thời gian biên dịch về nó.

ArrayStoreException, mặt khác, là một phần bình thường của cách mảng hoạt động trong Java và ngày tháng Generics. Không thể kiểm tra kiểu biên dịch theo thời gian để ngăn chặn ArrayStoreException do cách hệ thống kiểu cho mảng được thiết kế trong Java.

Các vấn đề liên quan