ความปลอดภัยของประเภท: นักแสดงที่ไม่ได้ตรวจสอบ


265

ในไฟล์บริบทของฤดูใบไม้ผลิแอปพลิเคชันของฉันฉันมีลักษณะดังนี้:

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

ในคลาส java การใช้งานมีลักษณะดังนี้:

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

ใน Eclipse ฉันเห็นคำเตือนที่แจ้งว่า:

ความปลอดภัยของประเภท: นักแสดงที่ไม่ได้ตรวจสอบจาก Object ไปยัง HashMap

ฉันทำอะไรผิด? ฉันจะแก้ไขปัญหาได้อย่างไร


ฉันคิดกิจวัตรประจำวันเพื่อตรวจสอบการส่งข้อมูลไปยัง HashMap ที่กำหนดพารามิเตอร์ซึ่งกำจัดคำเตือนการส่งสัญญาณที่ไม่ได้ตรวจสอบ: ลิงก์ที่ฉันบอกว่านี่เป็นโซลูชันที่ "ถูกต้อง" แต่ไม่ว่าจะคุ้มค่าหรือไม่ :)
skiphoppy


คำตอบ:


248

ก่อนอื่นคุณต้องสูญเสียความทรงจำด้วยการHashMapสร้างสายใหม่ บรรทัดที่สองของคุณไม่สนใจการอ้างอิงถึง hashmap ที่สร้างขึ้นนี้อย่างสมบูรณ์ทำให้สามารถใช้งานได้กับตัวรวบรวมขยะ ดังนั้นอย่าใช้:

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

ประการที่สองคอมไพเลอร์บ่นว่าคุณส่งวัตถุไปที่ a HashMapโดยไม่ตรวจสอบว่าเป็นHashMapหรือไม่ แต่แม้ว่าคุณจะต้องทำ:

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

คุณอาจจะยังคงได้รับคำเตือนนี้ ปัญหาคือgetBeanส่งคืนObjectดังนั้นจึงไม่ทราบว่าเป็นประเภทใด การแปลงเป็นไฟล์HashMapโดยตรงจะไม่ทำให้เกิดปัญหากับกรณีที่สอง (และอาจไม่มีคำเตือนในกรณีแรกฉันไม่แน่ใจว่าคอมไพเลอร์ Java นั้นมีคำเตือนสำหรับ Java 5) HashMap<String, String>แต่คุณจะถูกแปลงเป็น

HashMaps เป็นแผนที่จริง ๆ ที่นำวัตถุเป็นคีย์และมีวัตถุเป็นค่าHashMap<Object, Object>หากคุณต้องการ ดังนั้นจึงไม่มีการรับประกันว่าเมื่อคุณได้รับถั่วของคุณว่ามันสามารถแสดงเป็นHashMap<String, String>เพราะคุณอาจมีHashMap<Date, Calendar>เพราะการเป็นตัวแทนที่ไม่ใช่ทั่วไปที่ส่งกลับสามารถมีวัตถุใด ๆ

หากรหัสรวบรวมและคุณสามารถดำเนินการได้String value = map.get("thisString");โดยไม่มีข้อผิดพลาดไม่ต้องกังวลกับคำเตือนนี้ แต่ถ้าแผนที่ไม่สมบูรณ์ของคีย์สตริงถึงค่าสตริงคุณจะได้รับClassCastExceptionณ รันไทม์เนื่องจากข้อมูลทั่วไปไม่สามารถบล็อกสิ่งนี้ไม่ให้เกิดขึ้นในกรณีนี้


12
เมื่อไม่นานมานี้ แต่ฉันกำลังมองหาคำตอบเกี่ยวกับการตรวจสอบชุด <CustomClass> ก่อนการแสดงและคุณไม่สามารถอินสแตนซ์ของ parametrized generic ได้ เช่นถ้า (event.getTarget อินสแตนซ์ของ Set <CustomClass>) คุณสามารถพิมพ์ตรวจสอบทั่วไปที่มี? และนั่นจะไม่ลบคำเตือนการร่าย เช่นถ้า (event.getTarget instanceof Set <?>)
garlicman

315

ปัญหาคือว่านักแสดงคือการตรวจสอบรันไทม์ - แต่เนื่องจากประเภทลบออกที่รันไทม์มีจริงความแตกต่างระหว่างไม่มีHashMap<String,String>และHashMap<Foo,Bar>อื่น ๆ ใด ๆและFooBar

ใช้@SuppressWarnings("unchecked")จมูกค้างไว้ โอ้และแคมเปญสำหรับ reified generics ใน Java :)


14
ฉันจะใช้ยาสามัญประจำชวาของ Java เพื่อ NSMutableWhty ที่ไม่ได้ตีพิมพ์ซึ่งรู้สึกเหมือนก้าวกระโดดสิบปีย้อนหลังทุกวันในสัปดาห์ อย่างน้อย Java กำลังพยายาม
Dan Rosenstark

12
เผง หากคุณยืนยันในการตรวจสอบประเภทคุณสามารถทำได้ด้วย HashMap <?,?> เท่านั้นและจะไม่ลบคำเตือนออกเนื่องจากเหมือนกับการไม่พิมพ์การตรวจสอบประเภททั่วไป มันไม่ใช่จุดจบของโลก แต่น่ารำคาญที่คุณถูกจับไม่ว่าจะเป็นการเตือนหรืออยู่กับมัน
garlicman

5
@JonSkeet สามัญ reified คืออะไร?
SasQ

89

ในฐานะที่เป็นข้อความข้างต้นจะเห็นรายการที่ไม่สามารถแตกต่างระหว่างList<Object>และหรือList<String>List<Integer>

ฉันแก้ไขข้อผิดพลาดนี้สำหรับปัญหาที่คล้ายกัน:

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

ด้วยดังต่อไปนี้:

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

คำอธิบาย: การแปลงชนิดแรกตรวจสอบว่าวัตถุนั้นเป็นรายการโดยไม่สนใจเกี่ยวกับประเภทที่เก็บไว้ภายใน (เนื่องจากเราไม่สามารถตรวจสอบประเภทภายในที่ระดับรายการ) ตอนนี้จำเป็นต้องมีการแปลงที่สองเนื่องจากคอมไพเลอร์รู้เพียงว่า List มีวัตถุบางประเภท สิ่งนี้จะตรวจสอบประเภทของแต่ละวัตถุในรายการตามที่มีการเข้าถึง


3
คุณพูดถูกเพื่อนของฉัน แทนที่จะโยนรายการเพียงแค่วนซ้ำและโยนแต่ละองค์ประกอบคำเตือนจะไม่ปรากฏน่ากลัว
juan Isaza

2
สิ่งนี้ลบคำเตือนแล้ว แต่ฉันก็ยังไม่มั่นใจ: P
mumair

1
ใช่รู้สึกเหมือนการปิดบังคอมไพเลอร์ แต่ไม่ใช่ runtime: D ดังนั้นฉันไม่เห็นความแตกต่างระหว่างสิ่งนี้และ @SuppressWarnings ("ไม่ถูกตรวจสอบ")
channae

1
ที่น่ากลัว! ความแตกต่างที่สำคัญของการใช้ @SupressWarning คือว่าการใช้คำอธิบายประกอบจะเป็นการกำจัดคำเตือนจาก IDE และเครื่องมือวิเคราะห์โค้ด แต่ถ้าคุณใช้การคอมไพล์แฟล็ก -Werror คุณจะพบข้อผิดพลาด การใช้วิธีการนี้ทั้งคำเตือนได้รับการแก้ไขแล้ว
Edu Costa

30

คำเตือนก็คือ คำเตือน. บางครั้งคำเตือนนั้นไม่เกี่ยวข้อง พวกเขาคุ้นเคยกับสิ่งที่คอมไพเลอร์คิดว่าอาจเป็นปัญหา แต่อาจไม่ใช่

ในกรณีของการปลดเปลื้องมันมักจะให้คำเตือนในกรณีนี้ หากคุณแน่ใจว่านักแสดงเฉพาะจะปลอดภัยคุณควรพิจารณาเพิ่มคำอธิบายประกอบแบบนี้ (ฉันไม่แน่ใจเกี่ยวกับไวยากรณ์) ก่อนหน้าบรรทัด:

@SuppressWarnings (value="unchecked")

14
-1: ไม่ควรยอมรับคำเตือน หรือระงับคำเตือนประเภทนี้หรือแก้ไข จะมีช่วงเวลาที่คุณจะต้องรับคำเตือนมากมายและคุณจะไม่เห็นสิ่งที่เกี่ยวข้องอีกเลย
ezdazuzena

10
คุณไม่สามารถหลีกเลี่ยงการเตือนในชั้นเรียนได้เมื่อทำการส่งข้อมูลทั่วไปเกี่ยวกับพารามิเตอร์เช่นแผนที่ดังนั้นนี่คือคำตอบที่ดีที่สุดสำหรับคำถามเดิม
muttonUp

9

คุณได้รับข้อความนี้เนื่องจาก getBean ส่งคืนการอ้างอิงวัตถุและคุณกำลังส่งไปยังประเภทที่ถูกต้อง Java 1.5 ให้คำเตือนแก่คุณ นั่นเป็นลักษณะของการใช้ Java 1.5 หรือดีกว่าด้วยโค้ดที่ใช้งานได้เช่นนี้ Spring มีเวอร์ชัน typesafe

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

ในรายการสิ่งที่ต้องทำ


6

หากคุณต้องการกำจัดคำเตือนจริงๆสิ่งหนึ่งที่คุณทำได้คือสร้างคลาสที่ขยายจากคลาสทั่วไป

ตัวอย่างเช่นหากคุณกำลังพยายามใช้

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

คุณสามารถสร้างคลาสใหม่เช่นนี้

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

จากนั้นเมื่อคุณใช้

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

คอมไพเลอร์ไม่ทราบประเภท (ไม่มีอีกต่อไป) ประเภทและจะไม่มีการเตือน นี่อาจไม่ใช่วิธีที่สมบูรณ์แบบเสมอไปบางคนอาจโต้แย้งจุดประสงค์ของการเรียนแบบสามัญ แต่คุณยังคงใช้รหัสเดียวกันทั้งหมดจากคลาสทั่วไปอีกครั้งคุณเพิ่งประกาศเวลาที่รวบรวม คุณต้องการใช้


3

วิธีแก้ปัญหาเพื่อหลีกเลี่ยงคำเตือนที่ไม่ถูกตรวจสอบ:

class MyMap extends HashMap<String, String> {};
someMap = (MyMap)getApplicationContext().getBean("someMap");

ดูเหมือนแฮ็คไม่ใช่คำตอบ
Malwinder Singh

1
- MyMap คลาสที่ต่อเนื่องไม่ได้ประกาศฟิลด์ serialVersionUID สุดท้ายที่เป็นแบบคงที่ซึ่งมีความยาวประเภท: {
Ul

1

อีกวิธีหนึ่งถ้าคุณพบว่าตัวเองหล่อวัตถุเดียวกันมากและคุณไม่ต้องการทิ้งขยะด้วยรหัสของคุณ@SupressWarnings("unchecked")ก็คือการสร้างวิธีการที่มีคำอธิบายประกอบ วิธีนี้คุณจะรวมศูนย์การแสดงและหวังว่าจะลดโอกาสที่จะเกิดข้อผิดพลาด

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

1

รหัสด้านล่างทำให้เกิดการเตือนความปลอดภัยประเภท

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

วิธีแก้ปัญหา

สร้างแผนที่วัตถุใหม่โดยไม่พูดถึงพารามิเตอร์เนื่องจากประเภทของวัตถุที่อยู่ในรายการไม่ได้รับการตรวจสอบ

ขั้นตอนที่ 1:สร้างแผนที่ชั่วคราวใหม่

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

ขั้นตอนที่ 2: ยกตัวอย่างแผนที่หลัก

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

ขั้นตอนที่ 3: ทำซ้ำแผนที่ชั่วคราวและตั้งค่าลงในแผนที่หลัก

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

0

ฉันทำอะไรผิด? ฉันจะแก้ไขปัญหาได้อย่างไร

ที่นี่:

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

คุณใช้วิธีดั้งเดิมที่เราไม่ต้องการใช้นับตั้งแต่กลับมาObject:

Object getBean(String name) throws BeansException;

วิธีที่จะได้รับ (สำหรับซิงเกิลตัน) / สร้าง (สำหรับต้นแบบ) ถั่วจากโรงงานถั่วคือ:

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

ใช้มันเช่น:

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

จะรวบรวม แต่ยังคงมีคำเตือนการแปลงที่ไม่ถูกตรวจสอบเนื่องจากMapวัตถุทั้งหมดไม่จำเป็นMap<String, String>วัตถุ

แต่ <T> T getBean(String name, Class<T> requiredType) throws BeansException;ไม่เพียงพอในคลาสทั่วไปของ bean เช่นคอลเล็กชันทั่วไปเนื่องจากต้องระบุมากกว่าหนึ่งคลาสเป็นพารามิเตอร์: ประเภทคอลเลกชันและประเภททั่วไป

ในสถานการณ์แบบนี้และโดยทั่วไปวิธีการที่ดีกว่าคือไม่ควรใช้โดยตรง BeanFactoryวิธีการแต่ให้เฟรมเวิร์คฉีดถั่ว

ประกาศถั่ว:

@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;
    }
}

การฉีดถั่ว:

@Autowired
@Qualifier("someMap")
Map<String, String> someMap;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.