Kemungkinan polusi timbunan melalui parameter varargs

433

Saya mengerti ini terjadi dengan Java 7 saat menggunakan varargs dengan tipe generik;

Tapi pertanyaan saya adalah ..

Apa sebenarnya yang dimaksud Eclipse ketika mengatakan "penggunaannya berpotensi mencemari tumpukan?"

Dan

Bagaimana cara @SafeVarargsanotasi baru mencegah hal ini?

hertzsprung
sumber
Saya melihat ini di editor saya:Possible heap pollution from parameterized vararg type
Alexander Mills

Jawaban:

252

Polusi tumpukan adalah istilah teknis. Ini merujuk pada referensi yang memiliki tipe yang bukan supertipe dari objek yang mereka tuju.

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

Ini dapat menyebabkan "tidak dapat dijelaskan" ClassCastException.

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

@SafeVarargstidak mencegah ini sama sekali. Namun, ada metode yang terbukti tidak akan mencemari tumpukan, kompiler tidak bisa membuktikannya. Sebelumnya, penelepon API semacam itu akan mendapatkan peringatan menjengkelkan yang sama sekali tidak ada gunanya tetapi harus ditekan di setiap situs panggilan. Sekarang penulis API dapat menekannya sekali di situs deklarasi.

Namun, jika metode ini sebenarnya tidak aman, pengguna tidak akan lagi diperingatkan.

Ben Schulz
sumber
2
Jadi, apakah kita mengatakan bahwa tumpukan itu tercemar karena mengandung referensi yang jenisnya tidak seperti yang kita harapkan? (Daftar <A> vs Daftar <B> dalam contoh Anda)
hertzsprung
30
Jawaban ini adalah penjelasan yang baik tentang apa tumpukan polusi, tetapi itu tidak benar-benar menjelaskan mengapa vararg sangat mungkin menyebabkannya sehingga memerlukan peringatan khusus.
Dolda2000
4
Saya juga, saya kehilangan informasi bagaimana memastikan bahwa kode saya tidak mengandung masalah ini (mis. Bagaimana saya tahu itu cukup keras untuk menambahkan @ SafeVarargs)
Daniel Alder
237

Saat Anda mendeklarasikan

public static <T> void foo(List<T>... bar) kompiler mengubahnya menjadi

public static <T> void foo(List<T>[] bar) lalu ke

public static void foo(List[] bar)

Bahaya kemudian muncul bahwa Anda akan keliru menetapkan nilai yang salah ke dalam daftar dan kompiler tidak akan memicu kesalahan apa pun. Misalnya, jika Ta Stringmaka kode berikut akan dikompilasi tanpa kesalahan tetapi akan gagal saat runtime:

// 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);

Jika Anda meninjau metode untuk memastikan bahwa itu tidak mengandung kerentanan seperti itu maka Anda dapat membuat anotasi dengan @SafeVarargsuntuk menekan peringatan. Untuk antarmuka, gunakan@SuppressWarnings("unchecked") .

Jika Anda mendapatkan pesan kesalahan ini:

Metode Varargs dapat menyebabkan tumpukan timbunan dari parameter varargs yang tidak dapat diverifikasi

dan Anda yakin bahwa penggunaan Anda aman maka Anda harus menggunakannya @SuppressWarnings("varargs"). Lihat Apakah @SafeVarargs anotasi yang sesuai untuk metode ini? dan https://stackoverflow.com/a/14252221/14731 untuk penjelasan yang bagus tentang jenis kesalahan kedua ini.

Referensi:

Gili
sumber
2
Saya pikir saya mengerti lebih baik. Bahaya datang ketika Anda melemparkan vararg ke Object[]. Selama Anda tidak menyerah Object[], sepertinya Anda harus baik-baik saja.
djeikyb
3
Sebagai contoh hal bodoh yang dapat Anda lakukan: static <T> void bar(T...args) { ((Object[])args)[0] = "a"; }. Lalu telepon bar(Arrays.asList(1,2));.
djeikyb
1
@djeikyb jika bahaya hanya muncul jika saya menyerah Object[]mengapa kompiler memicu peringatan jika saya tidak melakukannya? Bagaimanapun, seharusnya cukup mudah untuk memeriksa ini pada waktu kompilasi (jika saya tidak meneruskannya ke fungsi lain dengan tanda tangan yang serupa, dalam hal ini fungsi lainnya harus memicu peringatan). Saya tidak percaya ini benar-benar inti dari peringatan ("Anda aman jika Anda tidak melemparkan"), dan saya masih tidak mengerti dalam hal ini saya baik-baik saja.
Qw3tanggal
5
@djeikyb Anda dapat melakukan hal bodoh yang persis sama tanpa parameter parametrized (misalnya bar(Integer...args)). Jadi apa gunanya peringatan ini?
Vasiliy Vlasov
3
@VasiliyVlasov Masalah ini hanya relevan untuk vararg yang diparameterisasi. Jika Anda mencoba melakukan hal yang sama dengan array yang tidak diketik, runtime akan mencegah Anda memasukkan tipe yang salah ke dalam array. Compiler adalah peringatan Anda bahwa runtime akan dapat mencegah perilaku yang tidak benar karena jenis parameter tidak diketahui pada saat runtime (Sebaliknya, array yang tahu jenis elemen non-generik mereka pada saat runtime).
Gili
8

@SafeVarargs tidak mencegah hal itu terjadi, namun itu mengamanatkan bahwa kompiler lebih ketat ketika mengkompilasi kode yang menggunakannya.

http://docs.oracle.com/javase/7/docs/api/java/lang/SafeVarargs.html menjelaskan hal ini lebih detail.

Heap polusi adalah ketika Anda mendapatkan ClassCastExceptionsaat melakukan operasi pada antarmuka generik dan berisi jenis lain dari yang dinyatakan.

jontro
sumber
Pembatasan kompiler tambahan pada penggunaannya tampaknya tidak terlalu relevan.
Paul Bellora
6

Ketika Anda menggunakan varargs, itu bisa menghasilkan pembuatan Object[] untuk menampung argumen.

Karena lolos analisis, JIT dapat mengoptimalkan pembuatan array ini. (Salah satu dari beberapa kali saya menemukannya melakukannya) Tidak dijamin akan dioptimalkan, tetapi saya tidak akan khawatir tentang hal itu kecuali jika Anda melihat ada masalah di profil memori Anda.

AFAIK @SafeVarargsmenekan peringatan oleh kompiler dan tidak mengubah perilaku JIT.

Peter Lawrey
sumber
6
Menarik meskipun itu tidak benar-benar menjawab pertanyaannya @SafeVarargs.
Paul Bellora
1
Nggak. Bukan itu yang menumpuk polusi. "Polusi tumpukan terjadi ketika variabel tipe parameter mengacu pada objek yang bukan tipe parameter." Ref: docs.oracle.com/javase/tutorial/java/generics/…
Doradus
1

Alasannya adalah karena varargs memberikan opsi untuk dipanggil dengan array objek non-parametrized. Jadi jika tipe Anda adalah List <A> ..., itu juga bisa disebut dengan tipe List [] non-varargs.

Berikut ini sebuah contoh:

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

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

Seperti yang Anda lihat Daftar [] b dapat berisi semua jenis konsumen, namun kode ini mengkompilasi. Jika Anda menggunakan varargs, maka Anda baik-baik saja, tetapi jika Anda menggunakan definisi metode setelah type-erasure - void test (List []) - maka kompiler tidak akan memeriksa tipe parameter templat. @SafeVarargs akan menekan peringatan ini.

pengguna1122069
sumber