Jenis keamanan: Pemain tidak dicentang

266

Dalam file konteks aplikasi musim semi saya, saya memiliki sesuatu seperti:

<util:map id="someMap" map-class="java.util.HashMap" key-type="java.lang.String" value-type="java.lang.String">
    <entry key="some_key" value="some value" />
    <entry key="some_key_2" value="some value" />   
</util:map>

Di kelas java, implementasinya terlihat seperti:

private Map<String, String> someMap = new HashMap<String, String>();
someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");

Di Eclipse, saya melihat peringatan yang mengatakan:

Jenis keamanan: Pemain yang tidak dicentang dari Object ke HashMap

Apa kesalahan yang telah aku perbuat? Bagaimana cara mengatasi masalah ini?

DragonBorn
sumber
Saya datang dengan rutin untuk benar-benar memeriksa para pemain ke parameter HashMap, yang menghilangkan peringatan pemain yang tidak dicentang: tautan Saya akan mengatakan ini adalah solusi "benar", tetapi apakah nilainya layak diperdebatkan atau tidak. :)
skiphoppy

Jawaban:

249

Pertama-tama, Anda membuang-buang memori dengan HashMappanggilan kreasi baru . Baris kedua Anda sama sekali mengabaikan referensi untuk hashmap yang dibuat ini, membuatnya kemudian tersedia untuk pengumpul sampah. Jadi, jangan lakukan itu, gunakan:

private Map<String, String> someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");

Kedua, kompiler mengeluh bahwa Anda melemparkan objek ke a HashMaptanpa memeriksa apakah itu a HashMap. Tetapi, bahkan jika Anda melakukannya:

if(getApplicationContext().getBean("someMap") instanceof HashMap) {
    private Map<String, String> someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");
}

Anda mungkin masih mendapatkan peringatan ini. Masalahnya adalah, getBeanpengembalian Object, jadi tidak diketahui apa jenisnya. Mengubahnya secara HashMaplangsung tidak akan menyebabkan masalah dengan kasus kedua (dan mungkin tidak akan ada peringatan dalam kasus pertama, saya tidak yakin seberapa hebatnya kompiler Java dengan peringatan untuk Java 5). Namun, Anda mengubahnya menjadi a HashMap<String, String>.

HashMaps benar-benar peta yang mengambil objek sebagai kunci dan memiliki objek sebagai nilai, HashMap<Object, Object>jika Anda mau. Dengan demikian, tidak ada jaminan bahwa ketika Anda mendapatkan kacang Anda itu dapat direpresentasikan sebagai HashMap<String, String>karena Anda dapat memiliki HashMap<Date, Calendar>karena representasi non-generik yang dikembalikan dapat memiliki objek apa pun.

Jika kode dikompilasi, dan Anda dapat mengeksekusi String value = map.get("thisString");tanpa kesalahan, jangan khawatir tentang peringatan ini. Tetapi jika peta tidak sepenuhnya dari kunci string ke nilai string, Anda akan mendapatkan ClassCastExceptionsaat runtime, karena obat generik tidak dapat memblokir ini terjadi dalam kasus ini.

MetroidFan2002
sumber
12
Ini beberapa waktu yang lalu, tetapi saya sedang mencari jawaban pada tipe yang memeriksa Set <CustomClass> sebelum casting, dan Anda tidak dapat menggunakan generic parametrized generic. misal if (event.getTarget instanceof Set <CustomClass>) Anda hanya dapat mengetikkan cek generik dengan? dan itu tidak akan menghapus peringatan pemain. misal if (event.getTarget instanceof Set <?>)
garlicman
315

Masalahnya adalah bahwa pemeran adalah pemeriksaan runtime - tetapi karena jenis penghapusan, saat runtime sebenarnya tidak ada perbedaan antara HashMap<String,String>dan HashMap<Foo,Bar>untuk yang lain Foodan Bar.

Gunakan @SuppressWarnings("unchecked")dan tahan hidung Anda. Oh, dan kampanye untuk obat generik yang diperbarui di Jawa :)

Jon Skeet
sumber
14
Saya akan mengambil obat generik Java yang sudah diperbaiki atas NSMutable apa pun yang tidak diketik, yang terasa seperti lompatan sepuluh tahun ke belakang, setiap hari dalam seminggu. Setidaknya Java sedang mencoba.
Dan Rosenstark
12
Persis. Jika Anda bersikeras memeriksa tipe, ini hanya dapat dilakukan dengan HashMap <?,?> Dan itu tidak akan menghapus peringatan karena sama seperti tidak mengetik memeriksa tipe generik. Ini bukan akhir dari dunia, tetapi menjengkelkan bahwa Anda tertangkap menekan peringatan atau hidup dengannya.
garlicman
5
@ JonSkeet Apa itu generik terverifikasi?
SasQ
89

Seperti yang ditunjukkan oleh pesan di atas, Daftar tidak dapat dibedakan antara a List<Object>dan a List<String>atau List<Integer>.

Saya telah memecahkan pesan kesalahan ini untuk masalah yang serupa:

List<String> strList = (List<String>) someFunction();
String s = strList.get(0);

dengan yang berikut ini:

List<?> strList = (List<?>) someFunction();
String s = (String) strList.get(0);

Penjelasan: Konversi tipe pertama memverifikasi bahwa objek adalah Daftar tanpa memperhatikan tipe yang ada di dalamnya (karena kami tidak dapat memverifikasi tipe internal di level Daftar). Konversi kedua sekarang diperlukan karena kompiler hanya tahu Daftar berisi beberapa jenis objek. Ini memverifikasi jenis setiap objek dalam Daftar saat diakses.

Larry Landry
sumber
3
kamu benar temanku Alih-alih casting daftar, hanya iterate dan cor masing-masing elemen, peringatan tidak akan muncul, luar biasa.
juan Isaza
2
Ini menghapus peringatan tetapi saya masih tidak yakin: P
mumair
1
Ya terasa seperti menutup mata kompiler tetapi tidak runtime: D Jadi saya tidak melihat perbedaan antara ini dan @SuppressWarnings ("tidak dicentang")
channae
1
Luar biasa! Perbedaan utama menggunakan @SupressWarning adalah bahwa penggunaan anotasi menghilangkan peringatan dari alat analisis kode dan IDE Anda, tetapi jika Anda menggunakan kompilasi flag -Werror Anda akan berakhir dengan kesalahan. Dengan menggunakan pendekatan ini, kedua peringatan diperbaiki.
Edu Costa
30

Peringatan hanya itu. Sebuah peringatan. Terkadang peringatan tidak relevan, terkadang tidak. Mereka digunakan untuk menarik perhatian Anda pada sesuatu yang menurut kompiler bisa menjadi masalah, tetapi mungkin tidak.

Dalam hal pemain, itu selalu akan memberikan peringatan dalam kasus ini. Jika Anda benar-benar yakin bahwa para pemeran tertentu akan aman, maka Anda harus mempertimbangkan untuk menambahkan anotasi seperti ini (saya tidak yakin dengan sintaks) sebelum baris:

@SuppressWarnings (value="unchecked")
David M. Karr
sumber
14
-1: peringatan tidak boleh diterima. Atau menekan peringatan semacam ini atau memperbaikinya. Akan datang momen di mana Anda akan harus banyak peringatan dan Anda tidak akan melihat yang relevan sekali.
ezdazuzena
10
Anda tidak dapat benar-benar menghindari peringatan pemeran kelas saat melakukan pengecoran generik berparameter yaitu Peta, jadi ini adalah jawaban terbaik untuk pertanyaan awal.
muttonUp
9

Anda menerima pesan ini karena getBean mengembalikan referensi Objek dan Anda memasukkannya ke jenis yang benar. Java 1.5 memberi Anda peringatan. Itulah sifat menggunakan Java 1.5 atau lebih baik dengan kode yang berfungsi seperti ini. Spring memiliki versi typesafe

someMap=getApplicationContext().getBean<HashMap<String, String>>("someMap");

pada daftar todo-nya.

David Nehme
sumber
6

Jika Anda benar-benar ingin menghilangkan peringatan, satu hal yang dapat Anda lakukan adalah membuat kelas yang memanjang dari kelas generik.

Misalnya, jika Anda mencoba menggunakannya

private Map<String, String> someMap = new HashMap<String, String>();

Anda dapat membuat kelas baru seperti itu

public class StringMap extends HashMap<String, String>()
{
    // Override constructors
}

Lalu saat Anda menggunakannya

someMap = (StringMap) getApplicationContext().getBean("someMap");

Kompiler TIDAK tahu apa jenis (tidak lagi generik), dan tidak akan ada peringatan. Ini mungkin tidak selalu menjadi solusi yang sempurna, beberapa mungkin berpendapat jenis ini mengalahkan tujuan kelas generik, tetapi Anda masih menggunakan kembali semua kode yang sama dari kelas generik, Anda hanya menyatakan pada waktu kompilasi jenis apa Anda ingin menggunakan.

kelinci
sumber
3

Solusi untuk menghindari peringatan yang tidak dicentang:

class MyMap extends HashMap<String, String> {};
someMap = (MyMap)getApplicationContext().getBean("someMap");
ochakov
sumber
Sepertinya peretasan bukan solusi.
Malwinder Singh
1
- Kelas serialable MyMap tidak menyatakan bidang serialVersionUID akhir statis tipe panjang: {
Ulterior
1

Solusi lain, jika Anda sering menemukan objek yang sama dan tidak ingin membuang sampah sembarangan @SupressWarnings("unchecked"), buatlah metode dengan anotasi. Dengan cara ini Anda memusatkan para pemeran, dan semoga mengurangi kemungkinan kesalahan.

@SuppressWarnings("unchecked")
public static List<String> getFooStrings(Map<String, List<String>> ctx) {
    return (List<String>) ctx.get("foos");
}
Jeremy
sumber
1

Kode di bawah ini menyebabkan Jenis keamanan Peringatan

Map<String, Object> myInput = (Map<String, Object>) myRequest.get();

Penanganan masalah

Buat Objek Peta baru tanpa menyebutkan parameter karena jenis objek yang disimpan dalam daftar tidak diverifikasi.

Langkah 1: Buat Peta sementara baru

Map<?, ?> tempMap = (Map<?, ?>) myRequest.get();

Langkah 2: Instantiate the Map utama

Map<String, Object> myInput=new HashMap<>(myInputObj.size());

Langkah 3: Iterasi Peta sementara dan atur nilainya ke dalam Peta utama

 for(Map.Entry<?, ?> entry :myInputObj.entrySet()){
        myInput.put((String)entry.getKey(),entry.getValue()); 
    }
Andy
sumber
0

Apa kesalahan yang telah aku perbuat? Bagaimana cara mengatasi masalah ini?

Di sini:

Map<String,String> someMap = (Map<String,String>)getApplicationContext().getBean("someMap");

Anda menggunakan metode lawas yang umumnya tidak ingin kita gunakan karena mengembalikan Object:

Object getBean(String name) throws BeansException;

Metode yang disukai untuk mendapatkan (untuk singleton) / membuat (untuk prototipe) kacang dari pabrik kacang adalah:

<T> T getBean(String name, Class<T> requiredType) throws BeansException;

Menggunakannya seperti:

Map<String,String> someMap = app.getBean(Map.class,"someMap");

akan mengkompilasi tetapi masih dengan peringatan konversi yang tidak dicentang karena semua Mapobjek tidak harus Map<String, String>objek.

Tetapi <T> T getBean(String name, Class<T> requiredType) throws BeansException;tidak cukup dalam kelas generik kacang seperti koleksi generik karena itu mengharuskan untuk menentukan lebih dari satu kelas sebagai parameter: tipe koleksi dan tipe generiknya.

Dalam skenario semacam ini dan secara umum, pendekatan yang lebih baik adalah tidak menggunakan BeanFactorymetode langsung tetapi membiarkan kerangka kerja untuk menyuntikkan kacang.

Deklarasi kacang:

@Configuration
public class MyConfiguration{

    @Bean
    public Map<String, String> someMap() {
        Map<String, String> someMap = new HashMap();
        someMap.put("some_key", "some value");
        someMap.put("some_key_2", "some value");
        return someMap;
    }
}

Injeksi kacang:

@Autowired
@Qualifier("someMap")
Map<String, String> someMap;
davidxxx
sumber