Ô nhiễm heap có thể thông qua tham số varargs


433

Tôi hiểu điều này xảy ra với Java 7 khi sử dụng varargs với một kiểu chung;

Nhưng câu hỏi của tôi là ..

Chính xác thì Eclipse có nghĩa là gì khi nói rằng "việc sử dụng nó có thể gây ô nhiễm heap?"

Làm thế nào để @SafeVarargschú thích mới ngăn chặn điều này?




Tôi đang thấy điều này trong trình soạn thảo của mình:Possible heap pollution from parameterized vararg type
Alexander Mills

Câu trả lời:


252

Ô nhiễm heap là một thuật ngữ kỹ thuật. Nó đề cập đến các tài liệu tham khảo có một loại không phải là siêu kiểu của đối tượng mà chúng trỏ đến.

List<A> listOfAs = new ArrayList<>();
List<B> listOfBs = (List<B>)(Object)listOfAs; // points to a list of As

Điều này có thể dẫn đến "không thể giải thích" ClassCastException.

// if the heap never gets polluted, this should never throw a CCE
B b = listOfBs.get(0); 

@SafeVarargskhông ngăn chặn điều này cả. Tuy nhiên, có những phương pháp có thể chứng minh sẽ không gây ô nhiễm, trình biên dịch không thể chứng minh được. Trước đây, người gọi các API như vậy sẽ nhận được các cảnh báo gây phiền nhiễu hoàn toàn vô nghĩa nhưng phải bị loại bỏ tại mọi trang web cuộc gọi. Bây giờ tác giả API có thể ngăn chặn nó một lần tại trang web khai báo.

Tuy nhiên, nếu phương pháp thực sự không an toàn, người dùng sẽ không còn được cảnh báo.


2
Vì vậy, có phải chúng ta đang nói rằng heap bị ô nhiễm bởi vì nó chứa các tham chiếu có loại không phải là những gì chúng ta có thể mong đợi? (Danh sách <A> so với Danh sách <B> trong ví dụ của bạn)
hertzsprung


30
Câu trả lời này là một lời giải thích tốt về ô nhiễm đống là gì, nhưng nó không thực sự giải thích lý do tại sao các varargs rất có khả năng gây ra nó để đảm bảo một cảnh báo cụ thể.
Dolda2000

4
Tôi cũng vậy, tôi thiếu thông tin làm thế nào để đảm bảo rằng mã của tôi không chứa vấn đề này (ví dụ: làm thế nào để tôi biết nó đủ cứng để thêm @SafeVarargs)
Daniel Alder

236

Khi bạn tuyên bố

public static <T> void foo(List<T>... bar) trình biên dịch chuyển đổi nó thành

public static <T> void foo(List<T>[] bar) sau đó

public static void foo(List[] bar)

Nguy hiểm sau đó phát sinh là bạn sẽ gán nhầm các giá trị không chính xác vào danh sách và trình biên dịch sẽ không kích hoạt bất kỳ lỗi nào. Ví dụ: nếu Tlà một Stringthì đoạn mã sau sẽ biên dịch không có lỗi nhưng sẽ thất bại khi chạy:

// First, strip away the array type (arrays allow this kind of upcasting)
Object[] objectArray = bar;

// Next, insert an element with an incorrect type into the array
objectArray[0] = Arrays.asList(new Integer(42));

// Finally, try accessing the original array. A runtime error will occur
// (ClassCastException due to a casting from Integer to String)
T firstElement = bar[0].get(0);

Nếu bạn đã xem lại phương pháp để đảm bảo rằng nó không chứa các lỗ hổng như vậy thì bạn có thể chú thích nó @SafeVarargsđể ngăn chặn cảnh báo. Đối với giao diện, sử dụng @SuppressWarnings("unchecked").

Nếu bạn nhận được thông báo lỗi này:

Phương pháp Varargs có thể gây ra ô nhiễm đống từ tham số varargs không thể xác định lại

và bạn chắc chắn rằng việc sử dụng của bạn an toàn thì bạn nên sử dụng @SuppressWarnings("varargs")thay thế. Xem @SafeVarargs có chú thích thích hợp cho phương pháp này không? https://stackoverflow.com/a/14252221/14731 cho một lời giải thích hay về loại lỗi thứ hai này.

Người giới thiệu:


2
Tôi nghĩ rằng tôi hiểu rõ hơn. Nguy hiểm đến khi bạn bỏ varargs đến Object[]. Miễn là bạn không tham gia Object[], có vẻ như bạn sẽ ổn thôi.
djeikyb

3
Như một ví dụ về một điều ngu ngốc bạn có thể làm : static <T> void bar(T...args) { ((Object[])args)[0] = "a"; }. Rồi gọi bar(Arrays.asList(1,2));.
djeikyb

1
@djeikyb nếu nguy hiểm chỉ phát sinh nếu tôi sử dụng Object[]tại sao trình biên dịch sẽ kích hoạt cảnh báo nếu tôi không? Rốt cuộc, khá dễ dàng để kiểm tra điều này vào thời gian biên dịch (trong trường hợp tôi không chuyển nó sang một chức năng khác có chữ ký tương tự, trong trường hợp đó, chức năng kia sẽ kích hoạt cảnh báo). Tôi không tin rằng đây thực sự là cốt lõi của cảnh báo ("Bạn an toàn nếu bạn không tham gia") và tôi vẫn không hiểu trong trường hợp nào tôi ổn.
Qw3ry

5
@djeikyb Bạn có thể làm chính xác điều ngu ngốc tương tự mà không có varargs tham số (ví dụ bar(Integer...args)). Vì vậy, điểm của cảnh báo này là gì?
Vasiliy Vlasov

3
@VasiliyVlasov Vấn đề này chỉ liên quan đến các varargs được tham số hóa. Nếu bạn cố gắng làm điều tương tự với các mảng không gõ, bộ thực thi sẽ ngăn bạn chèn sai loại vào mảng. Trình biên dịch được cảnh báo bạn rằng thời gian chạy sẽ không thể ngăn chặn hành vi không chính xác bởi vì các loại tham số không rõ trong thời gian chạy (ngược lại, mảng làm biết loại của các yếu tố phi chung của họ trong thời gian chạy).
Gili

8

@SafeVarargs không ngăn chặn nó xảy ra, tuy nhiên, nó bắt buộc trình biên dịch chặt chẽ hơn khi biên dịch mã sử dụng nó.

http://docs.oracle.com/javase/7/docs/api/java/lang/SafeVarargs.html giải thích điều này chi tiết hơn.

Ô nhiễm heap là khi bạn nhận được một ClassCastExceptionthao tác trên giao diện chung và nó chứa một loại khác so với khai báo.


Các hạn chế bổ sung của trình biên dịch về việc sử dụng nó dường như không liên quan.
Paul Bellora

6

Khi bạn sử dụng varargs, nó có thể dẫn đến việc tạo một Object[]để giữ các đối số.

Do phân tích thoát, JIT có thể tối ưu hóa việc tạo mảng này. (Một trong số ít lần tôi thấy nó làm như vậy) Nó không được đảm bảo để tối ưu hóa, nhưng tôi sẽ không lo lắng về điều đó trừ khi bạn thấy vấn đề của nó trong trình hồ sơ bộ nhớ của bạn.

AFAIK @SafeVarargschặn cảnh báo của trình biên dịch và không thay đổi cách JIT hành xử.


6
Thú vị mặc dù nó không thực sự trả lời câu hỏi của anh ấy về @SafeVarargs.
Paul Bellora

1
Không. Đó không phải là ô nhiễm đống. "Ô nhiễm heap xảy ra khi một biến của loại tham số tham chiếu đến một đối tượng không thuộc loại tham số đó." Tham chiếu: docs.oracle.com/javase/tutorial/java/generics/,
Doradus

1

Lý do là bởi vì các vararg cung cấp tùy chọn được gọi với một mảng đối tượng không tham số. Vì vậy, nếu loại của bạn là Danh sách <A> ..., thì nó cũng có thể được gọi với loại Danh sách [] không có varargs.

Đây là một ví dụ:

public static void testCode(){
    List[] b = new List[1];
    test(b);
}

@SafeVarargs
public static void test(List<A>... a){
}

Như bạn có thể thấy Danh sách [] b có thể chứa bất kỳ loại người tiêu dùng nào và mã này sẽ biên dịch. Nếu bạn sử dụng varargs, thì bạn vẫn ổn, nhưng nếu bạn sử dụng định nghĩa phương thức sau khi xóa kiểu - void test (List []) - thì trình biên dịch sẽ không kiểm tra các loại tham số mẫu. @SafeVarargs sẽ ngăn chặn cảnh báo này.

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.