ฉันจะจัดการกับคำเตือนการส่งแบบไม่ตรวจสอบได้อย่างไร?


611

Eclipse ให้คำเตือนแก่ฉันในแบบฟอร์มต่อไปนี้:

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

นี่คือจากการเรียกไปยัง API ที่ฉันไม่สามารถควบคุมได้ซึ่งคืนค่า Object:

HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
  HashMap<String, String> theHash = (HashMap<String, String>)session.getAttribute("attributeKey");
  return theHash;
}

ฉันต้องการหลีกเลี่ยงคำเตือน Eclipse หากเป็นไปได้เนื่องจากในทางทฤษฎีแล้วพวกเขาระบุว่าอาจมีปัญหาโค้ดอย่างน้อย ฉันยังไม่พบวิธีที่ดีในการกำจัดอันนี้ ฉันสามารถแยกบรรทัดเดียวที่เกี่ยวข้องกับวิธีการด้วยตัวเองและเพิ่ม@SuppressWarnings("unchecked")ไปยังวิธีการนั้นจึง จำกัด ผลกระทบของการมีบล็อกของรหัสที่ฉันไม่สนใจคำเตือน ตัวเลือกใดดีกว่า ฉันไม่ต้องการปิดคำเตือนเหล่านี้ใน Eclipse

ก่อนที่ฉันจะมาที่รหัสมันง่ายกว่า แต่ก็ยังมีคำเตือนเจ็บใจ:

HashMap getItems(javax.servlet.http.HttpSession session) {
  HashMap theHash = (HashMap)session.getAttribute("attributeKey");
  return theHash;
}

ปัญหาเกิดขึ้นที่อื่นเมื่อคุณพยายามใช้แฮชที่คุณได้รับคำเตือน:

HashMap items = getItems(session);
items.put("this", "that");

Type safety: The method put(Object, Object) belongs to the raw type HashMap.  References to generic type HashMap<K,V> should be parameterized.

หากคุณใช้ HttpSession เช่นนั้นลองอ่านบทความของ Brian Goetz ในหัวข้อ: ibm.com/developerworks/library/j-jtp09238.html
Tom Hawtin - tackline

หากไม่สามารถหลีกเลี่ยงการโยนที่ไม่ได้เลือกได้ความคิดที่ดีคือการจับคู่กับสิ่งที่แสดงถึงประเภทของมันอย่างมีเหตุผล (เช่นenumอินสแตนซ์หรือแม้กระทั่งอินสแตนซ์Class<T>) ดังนั้นคุณจึงสามารถดูได้ทันทีและรู้ว่าปลอดภัย
ฟิลิป Guin



ฉันจะเพิ่มฉันพบว่าฉันสามารถเพิ่ม @SuppressWarnings ("ไม่ถูกตรวจสอบ") ที่ระดับวิธีการที่มีรหัสที่ละเมิดเท่านั้น ดังนั้นฉันจึงแตกรหัสออกเป็นกิจวัตรที่ฉันต้องทำ ฉันคิดเสมอว่าคุณสามารถทำได้ทันทีเหนือบรรทัดที่เป็นปัญหา
JGFMK

คำตอบ:


557

แน่นอนคำตอบที่ชัดเจนคือไม่ต้องโยนเฝือก

หากจำเป็นจริงๆอย่างน้อยพยายาม จำกัด ขอบเขตของ@SuppressWarningsคำอธิบายประกอบ ตามJavadocsมันสามารถไปที่ตัวแปรท้องถิ่น ด้วยวิธีนี้มันไม่ได้ส่งผลกระทบต่อวิธีการทั้งหมด

ตัวอย่าง:

@SuppressWarnings("unchecked")
Map<String, String> myMap = (Map<String, String>) deserializeMap();

มีวิธีการตรวจสอบว่าไม่เป็นจริงๆควรจะมีพารามิเตอร์ทั่วไปMap <String, String>คุณต้องรู้ล่วงหน้าว่าควรจะใช้พารามิเตอร์ใด (หรือคุณจะรู้เมื่อคุณได้รับClassCastException) นี่คือสาเหตุที่โค้ดสร้างคำเตือนเนื่องจากคอมไพเลอร์ไม่สามารถทราบได้ว่าปลอดภัยหรือไม่


112
+1 สำหรับการชี้ให้เห็นว่ามันสามารถไปที่ตัวแปรท้องถิ่น Eclipse เพียงข้อเสนอที่จะเพิ่มเข้าไปในวิธีการทั้ง ...
thSoft

17
Eclipse 3.7 (Indigo) รองรับการเพิ่มไม่ จำกัด ตัวแปรท้องถิ่น
sweetfa

78
คำเตือนไม่ได้เป็นเพียงเพราะคอมไพเลอร์ไม่ได้รู้ว่านักแสดงมีความปลอดภัย ตัวอย่างเช่นString s = (String) new Object() ;ไม่ได้รับการเตือนถึงแม้คอมไพเลอร์จะไม่ทราบว่าการโยนนั้นปลอดภัย คำเตือนเป็นเพราะคอมไพเลอร์ (a) ไม่ทราบว่าการร่ายนั้นปลอดภัยและ (b) จะไม่สร้างการตรวจสอบรันไทม์แบบสมบูรณ์ที่จุดของการร่าย จะมีการตรวจสอบว่าเป็นแต่จะไม่มีการตรวจสอบว่าเป็นHashmap HashMap<String,String>
Theodore Norvell

9
น่าเศร้าถึงแม้ว่านักแสดงและการเตือนจะใช้สำหรับการมอบหมายแต่คำอธิบายประกอบจะต้องมีการประกาศตัวแปร ... ดังนั้นหากการประกาศและการมอบหมายอยู่ในสถานที่ต่างกัน (พูดนอกและภายในบล็อก 'ลอง' ตามลำดับ) ตอนนี้ Eclipse จะสร้างคำเตือนสองคำ: เพี้ยนที่ไม่ได้ตรวจสอบดั้งเดิมและการวินิจฉัย "คำอธิบายประกอบที่ไม่จำเป็น" ใหม่
Ti Strga

6
วิธีแก้ปัญหาสำหรับการเพิ่มความคิดเห็นที่จำเป็นต้องมาพร้อมกับการประกาศตัวแปรท้องถิ่นซึ่งอาจอยู่ในขอบเขตที่แตกต่างกันในบรรทัดที่แตกต่างจากการโยนที่เกิดขึ้นจริงคือการสร้างตัวแปรท้องถิ่นภายในขอบเขตของการโยนโดยเฉพาะเพื่อดำเนินการโยนในบรรทัดเดียวกัน เป็นการประกาศ จากนั้นกำหนดตัวแปรนี้ให้กับตัวแปรจริงซึ่งอยู่ในขอบเขตที่แตกต่างกัน นี่เป็นวิธีที่ฉันใช้ในการระงับคำเตือนในการส่งไปยังตัวแปรอินสแตนซ์เนื่องจากการเพิ่มความคิดเห็นไม่สามารถใช้ที่นี่ได้เช่นกัน
Jeff Lockhart

168

น่าเสียดายที่ไม่มีตัวเลือกที่ดีที่นี่ จำไว้ว่าเป้าหมายทั้งหมดนี้คือเพื่อรักษาความปลอดภัยของประเภท " Java Generics " เสนอวิธีแก้ปัญหาสำหรับการจัดการกับไลบรารี่ดั้งเดิมที่ไม่ใช่แบบทั่วไปและมีวิธีหนึ่งที่เรียกว่า "เทคนิคการวนลูปเปล่า" ในหัวข้อ 8.2 โดยพื้นฐานแล้วทำการบล็อกที่ไม่ปลอดภัยและหยุดคำเตือน จากนั้นวนรอบแผนที่ดังนี้:

@SuppressWarnings("unchecked")
Map<String, Number> map = getMap();
for (String s : map.keySet());
for (Number n : map.values());

หากพบประเภทที่ไม่คาดคิดคุณจะได้รับรันไทม์ClassCastExceptionแต่อย่างน้อยก็จะเกิดขึ้นใกล้กับแหล่งที่มาของปัญหา


6
ดีกว่าคำตอบที่ได้รับจาก skiphoppy มากด้วยเหตุผลหลายประการ: 1) รหัสนี้สั้นกว่ามาก 2) รหัสนี้จริง ๆ แล้วโยน ClassCastException ตามที่คาดไว้ 3) รหัสนี้ไม่ได้ทำสำเนาแผนที่เต็มรูปแบบ 4) ลูปสามารถห่อได้อย่างง่ายดายในวิธีแยกต่างหากที่ใช้ในการยืนยันซึ่งจะลบประสิทธิภาพการทำงานที่โดนในโค้ดการผลิตได้อย่างง่ายดาย
Stijn de Witt

6
ไม่มีความเป็นไปได้ที่คอมไพเลอร์ Java หรือคอมไพเลอร์ JIT จะตัดสินว่าผลลัพธ์ของโค้ดนี้ไม่ได้ถูกใช้งานและ "ปรับ" โดยไม่ทำการคอมไพล์หรือไม่?
RenniePet

1
มันไม่ใช่รหัสตายจริง ๆ ถ้ามันสามารถทิ้งข้อยกเว้นได้ ฉันไม่รู้พอเกี่ยวกับคอมไพเลอร์ของ JIT ที่ใช้อยู่ในวันนี้เพื่อรับประกันว่าจะไม่มีใครสับสนได้ แต่ฉันรู้สึกมั่นใจอย่างเป็นธรรมในการบอกว่าพวกเขาไม่ควรทำ
GrandOpener

3
สิ่งนี้ยังไม่รับประกันความปลอดภัยของประเภทเนื่องจากยังคงใช้แผนที่เดียวกันอยู่ ในตอนแรกมันอาจถูกนิยามเป็น Map <Object, Object> ที่เพิ่งมี Strings และ Numbers เข้ามาหลังจากนั้นหากมีการเพิ่มบูลีนผู้ใช้รหัสนี้จะสับสนและยากที่จะติดตามความประหลาดใจ วิธีเดียวที่จะรับประกันความปลอดภัยของประเภทคือการคัดลอกลงในแผนที่ใหม่ด้วยประเภทที่ร้องขอที่รับประกันสิ่งที่ได้รับอนุญาตให้เข้าไป
2219808

112

ว้าว; ฉันคิดว่าฉันหาคำตอบสำหรับคำถามของฉันเอง ฉันแค่ไม่แน่ใจว่ามันคุ้มค่า! :)

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

แต่คุณสามารถทำการตรวจสอบแต่ละรายการในแฮชพร้อมกับอินสแตนซ์และในการทำเช่นนั้นคุณสามารถสร้างแฮชใหม่ที่ปลอดภัยต่อประเภทได้ และคุณจะไม่ยั่วยุคำเตือนใด ๆ

ขอบคุณ mmyers และ Esko Luontola ฉันได้กำหนดค่าโค้ดที่ฉันเขียนไว้ที่นี่เพื่อให้สามารถห่อในคลาสยูทิลิตี้ที่ไหนสักแห่งและใช้สำหรับ HashMap แปรสภาพ หากคุณต้องการทำความเข้าใจให้ดีขึ้นและไม่คุ้นเคยกับยาชื่อสามัญฉันแนะนำให้ดูประวัติการแก้ไขของคำตอบนี้

public static <K, V> HashMap<K, V> castHash(HashMap input,
                                            Class<K> keyClass,
                                            Class<V> valueClass) {
  HashMap<K, V> output = new HashMap<K, V>();
  if (input == null)
      return output;
  for (Object key: input.keySet().toArray()) {
    if ((key == null) || (keyClass.isAssignableFrom(key.getClass()))) {
        Object value = input.get(key);
        if ((value == null) || (valueClass.isAssignableFrom(value.getClass()))) {
            K k = keyClass.cast(key);
            V v = valueClass.cast(value);
            output.put(k, v);
        } else {
            throw new AssertionError(
                "Cannot cast to HashMap<"+ keyClass.getSimpleName()
                +", "+ valueClass.getSimpleName() +">"
                +", value "+ value +" is not a "+ valueClass.getSimpleName()
            );
        }
    } else {
        throw new AssertionError(
            "Cannot cast to HashMap<"+ keyClass.getSimpleName()
            +", "+ valueClass.getSimpleName() +">"
            +", key "+ key +" is not a " + keyClass.getSimpleName()
        );
    }
  }
  return output;
}

นั่นเป็นงานจำนวนมากอาจเป็นรางวัลเล็ก ๆ น้อย ๆ ... ฉันไม่แน่ใจว่าฉันจะใช้หรือไม่ ฉันขอขอบคุณความคิดเห็นใด ๆ ว่าผู้คนคิดว่ามันคุ้มค่าหรือไม่ นอกจากนี้ฉันขอขอบคุณข้อเสนอแนะการปรับปรุง: มีสิ่งที่ดีกว่าที่ฉันสามารถทำได้นอกเหนือจากการโยน AssertionErrors? มีบางอย่างที่ดีกว่าที่ฉันสามารถโยนได้หรือไม่? ฉันควรทำให้เป็นข้อยกเว้นที่เลือก


68
สิ่งนี้ทำให้เกิดความสับสน แต่ฉันคิดว่าสิ่งที่คุณทำคือการแลกเปลี่ยน ClassCastExceptions สำหรับ AssertionErrors
Dustin Getz

59
เพื่อนนั่นไม่คุ้มค่าแน่นอน! ลองนึกภาพผู้น่าสงสารที่ต้องกลับมาแก้ไขรหัสบางอย่างที่มีอยู่ในนั้น ฉันไม่ชอบระงับคำเตือน แต่ฉันคิดว่านั่นเป็นความชั่วร้ายที่น้อยกว่าที่นี่
Craig B

69
ไม่ใช่แค่ว่ามันน่าเกลียดสับสนสับสน (เมื่อคุณไม่สามารถหลีกเลี่ยงความคิดเห็นมากมายที่สามารถเดินโปรแกรมเมอร์บำรุงรักษาผ่านมัน); วนซ้ำทุกองค์ประกอบในคอลเลกชันจะเปลี่ยนการร่ายจาก O (1) เป็นการดำเนินการ O (n) นี่คือสิ่งที่ไม่เคยคาดคิดมาก่อนและสามารถเปลี่ยนเป็นความลึกลับที่น่ากลัวได้
Dan Is Fiddling โดย Firelight

22
@DanNeely คุณถูกต้อง โดยทั่วไปแล้วไม่มีใครควรทำเช่นนี้
skiphoppy

4
ความคิดเห็นบางอย่าง ... ลายเซ็นของวิธีการผิดเพราะไม่ "โยน" สิ่งที่น่ารังเกียจมันแค่คัดลอกแผนที่ที่มีอยู่ลงในแผนที่ใหม่ นอกจากนี้ยังอาจได้รับการปรับโครงสร้างใหม่เพื่อยอมรับแผนที่ใด ๆ และไม่ต้องพึ่งพา HashMap เอง (เช่นใช้ Map และส่งคืนแผนที่ในลายเซ็นวิธีการแม้ว่าประเภทภายในคือ HashMap) คุณไม่จำเป็นต้องทำการแคสต์หรือการเก็บข้อมูลลงในแผนที่ใหม่ - ถ้าคุณไม่โยนข้อผิดพลาดในการยืนยันแล้วแผนที่ที่ระบุมีประเภทที่ถูกต้องภายในตอนนี้ การสร้างแผนที่ใหม่ด้วยประเภททั่วไปนั้นไม่มีประโยชน์อย่างที่คุณสามารถทำให้มันดิบและใส่อะไรก็ได้
MetroidFan2002

51

ในการกำหนดค่าตามความชอบ Eclipse ไปที่ Java-> คอมไพเลอร์ -> ข้อผิดพลาด / คำเตือน -> ประเภททั่วไปและทำIgnore unavoidable generic type problemsเครื่องหมายในกล่องกาเครื่องหมาย

สิ่งนี้สอดคล้องกับเจตนาของคำถามคือ

ฉันต้องการหลีกเลี่ยงคำเตือน Eclipse ...

ถ้าไม่ใช่วิญญาณ


1
อ่าขอบคุณมากสำหรับเรื่องนี้ :) ฉันได้รับuses unchecked or unsafe operations.ข้อผิดพลาด "" javacแต่การเพิ่ม@SuppressWarnings("unchecked")Eclipse ทำให้ไม่มีความสุขการอ้างว่าการปราบปรามนั้นไม่จำเป็น การไม่ทำเครื่องหมายในช่องนี้จะทำให้ Eclipse และjavacทำงานเหมือนกันซึ่งเป็นสิ่งที่ฉันต้องการ การระงับคำเตือนอย่างชัดเจนในโค้ดนั้นชัดเจนกว่าการปราบปรามใน Eclipse
dimo414

26

คุณสามารถสร้างคลาสยูทิลิตี้ดังต่อไปนี้และใช้เพื่อระงับการเตือนที่ไม่ถูกตรวจสอบ

public class Objects {

    /**
     * Helps to avoid using {@code @SuppressWarnings({"unchecked"})} when casting to a generic type.
     */
    @SuppressWarnings({"unchecked"})
    public static <T> T uncheckedCast(Object obj) {
        return (T) obj;
    }
}

คุณสามารถใช้มันได้ดังต่อไปนี้:

import static Objects.uncheckedCast;
...

HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
      return uncheckedCast(session.getAttribute("attributeKey"));
}

มีการพูดคุยเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ที่นี่: http://cleveralias.blogs.com/th/t/spearmints/2006/01/suppresswarning.html


18
ไม่ใช่ downvoting แต่ wrapper ไม่ได้เพิ่มอะไรอย่างแม่นยำเพียงแค่หยุดการเตือน
ดัสตินเก็ตซ์

3
+1 เนื่องจากโซลูชันนี้จะไม่เสียบรรทัดรหัสที่มีค่า
Tino

1
@ErikE มากเกินไป จอภาพขนาดใหญ่ที่ใหญ่กว่าและมีความละเอียดสูงกว่าจะให้พื้นที่สำหรับทุกบรรทัดที่เสียไปโต๊ะที่ใหญ่กว่าสำหรับวางจอภาพที่ใหญ่กว่าทั้งหมดห้องที่ใหญ่กว่าเพื่อวางโต๊ะที่ใหญ่กว่าและเจ้านายที่ชาญฉลาด ..
Tino

1
@ErikE Scrollbars เพื่อviอะไร คุณล้อเล่นใช่ไหม
Tino

21

สิ่งนี้ยาก แต่นี่เป็นความคิดปัจจุบันของฉัน:

หาก API ของคุณส่งคืนออบเจ็กต์คุณจะไม่สามารถทำสิ่งใดได้ไม่ว่าคุณจะทำแบบนั้นหรือไม่ คุณปล่อยให้ Java โยน ClassCastExceptions หรือคุณสามารถตรวจสอบแต่ละองค์ประกอบด้วยตัวคุณเองแล้วโยน Assertions หรือ IllegalArgumentExceptions หรือบางอย่าง แต่การตรวจสอบรันไทม์เหล่านี้จะเทียบเท่ากันทั้งหมด คุณต้องระงับการคอมไพล์โดยไม่ จำกัดเวลาการโยนไม่ว่าคุณจะทำอะไรในรันไทม์

ฉันแค่ต้องการที่จะตาบอดและให้ JVM ทำการตรวจสอบรันไทม์สำหรับฉันเพราะเรา "รู้" ว่า API ควรจะกลับมาอย่างไรและมักจะยินดีที่จะสันนิษฐานว่า API ทำงานได้ ใช้ยาชื่อสามัญทุกที่ที่อยู่เหนือนักแสดงหากคุณต้องการ คุณไม่ได้ซื้ออะไรเลยจริงๆเพราะคุณยังมีนักแสดงตาบอดคนเดียว แต่อย่างน้อยคุณก็สามารถใช้ยาสามัญจากที่นั่นเพื่อให้ JVM สามารถช่วยคุณหลีกเลี่ยงการปลดคนตาบอดในรหัสอื่น ๆ ของคุณ

ในกรณีนี้สมมุติว่าคุณสามารถเห็นการเรียกไปยัง SetAttribute และดูประเภทที่กำลังเข้ามาดังนั้นเพียงแค่การคัดเลือกคนตาบอดให้เป็นแบบเดียวกันในเส้นทางที่ไม่ได้ผิดศีลธรรม เพิ่มความคิดเห็นที่อ้างอิง SetAttribute และทำได้ด้วย


16

นี่เป็นตัวอย่างสั้น ๆที่หลีกเลี่ยงคำเตือน "ไม่ จำกัด นักส่ง"โดยใช้สองกลยุทธ์ที่กล่าวถึงในคำตอบอื่น ๆ

  1. ส่งผ่าน Class ของประเภทความสนใจเป็นพารามิเตอร์ที่ runtime ( Class<T> inputElementClazz) จากนั้นคุณสามารถใช้:inputElementClazz.cast(anyObject);

  2. สำหรับการเลือกประเภทของคอลเลคชั่นให้ใช้ไวลด์การ์ด? แทนที่จะเป็นประเภท T ทั่วไปเพื่อรับทราบว่าคุณไม่ทราบว่าจะคาดหวังอะไรจากรหัสดั้งเดิม ( Collection<?> unknownTypeCollection) หลังจากทั้งหมดนี้คือสิ่งที่ "ไม่ถูกตรวจสอบหล่อ" เตือนต้องการที่จะบอกเรา: เราไม่สามารถมั่นใจได้ว่าเราได้รับดังนั้นสิ่งที่ซื่อสัตย์ที่จะทำคือการใช้Collection<T> Collection<?>หากจำเป็นอย่างยิ่งชุดของประเภทที่รู้จักก็ยังสามารถสร้างขึ้นได้ ( Collection<T> knownTypeCollection)

รหัสดั้งเดิมที่เชื่อมต่อในตัวอย่างด้านล่างมีแอตทริบิวต์ "อินพุต" ใน StructuredViewer (StructuredViewer เป็นต้นไม้หรือวิดเจ็ตตาราง "อินพุต" เป็นโมเดลข้อมูลที่อยู่ด้านหลัง) "อินพุต" นี้อาจเป็น Java Collection ชนิดใดก็ได้

public void dragFinished(StructuredViewer structuredViewer, Class<T> inputElementClazz) {
    IStructuredSelection selection = (IStructuredSelection) structuredViewer.getSelection();
    // legacy code returns an Object from getFirstElement,
    // the developer knows/hopes it is of type inputElementClazz, but the compiler cannot know
    T firstElement = inputElementClazz.cast(selection.getFirstElement());

    // legacy code returns an object from getInput, so we deal with it as a Collection<?>
    Collection<?> unknownTypeCollection = (Collection<?>) structuredViewer.getInput();

    // for some operations we do not even need a collection with known types
    unknownTypeCollection.remove(firstElement);

    // nothing prevents us from building a Collection of a known type, should we really need one
    Collection<T> knownTypeCollection = new ArrayList<T>();
    for (Object object : unknownTypeCollection) {
        T aT = inputElementClazz.cast(object);
        knownTypeCollection.add(aT);
        System.out.println(aT.getClass());
    }

    structuredViewer.refresh();
}

โดยปกติโค้ดข้างต้นสามารถให้ข้อผิดพลาดรันไทม์ถ้าเราใช้รหัสดั้งเดิมกับชนิดข้อมูลที่ไม่ถูกต้อง (เช่นถ้าเราตั้งค่าอาร์เรย์เป็น "อินพุต" ของ StructuredViewer แทนที่จะเป็น Java Collection)

ตัวอย่างการเรียกใช้เมธอด:

dragFinishedStrategy.dragFinished(viewer, Product.class);

13

ในโลกเซสชัน HTTP คุณไม่สามารถหลีกเลี่ยงการส่งเนื่องจาก API เขียนด้วยวิธีนั้น (รับและส่งคืนเท่านั้น Object )

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

แทนที่ HashMap ด้วยคลาสเฉพาะ:

import java.util.AbstractMap;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Attributes extends AbstractMap<String, String> {
    final Map<String, String> content = new HashMap<String, String>();

    @Override
    public Set<Map.Entry<String, String>> entrySet() {
        return content.entrySet();
    }

    @Override
    public Set<String> keySet() {
        return content.keySet();
    }

    @Override
    public Collection<String> values() {
        return content.values();
    }

    @Override
    public String put(final String key, final String value) {
        return content.put(key, value);
    }
}

จากนั้นส่งไปที่คลาสนั้นแทนMap<String,String>และทุกอย่างจะถูกตรวจสอบในสถานที่ที่คุณเขียนโค้ดของคุณ ไม่คาดคิดในClassCastExceptionsภายหลัง


นี่เป็นคำตอบที่เป็นประโยชน์จริงๆ
GPrathap

10

ใน Android Studio หากคุณต้องการปิดการตรวจสอบคุณสามารถใช้:

//noinspection unchecked
Map<String, String> myMap = (Map<String, String>) deserializeMap();

2
สิ่งนี้ใช้ได้ใน IntelliJ IDE ด้วย
neXus

8

ในกรณีพิเศษนี้ฉันจะไม่เก็บแผนที่ไว้ใน HttpSession โดยตรง แต่แทนที่จะเป็นตัวอย่างของคลาสของฉันเองซึ่งในนั้นจะมีแผนที่ (รายละเอียดการใช้งานของชั้นเรียน) จากนั้นคุณสามารถมั่นใจได้ว่าองค์ประกอบในแผนที่เป็นประเภทที่เหมาะสม

แต่ถ้าคุณต้องการตรวจสอบว่าเนื้อหาของแผนที่เป็นประเภทที่ถูกต้องคุณสามารถใช้รหัสดังนี้:

public static void main(String[] args) {
    Map<String, Integer> map = new HashMap<String, Integer>();
    map.put("a", 1);
    map.put("b", 2);
    Object obj = map;

    Map<String, Integer> ok = safeCastMap(obj, String.class, Integer.class);
    Map<String, String> error = safeCastMap(obj, String.class, String.class);
}

@SuppressWarnings({"unchecked"})
public static <K, V> Map<K, V> safeCastMap(Object map, Class<K> keyType, Class<V> valueType) {
    checkMap(map);
    checkMapContents(keyType, valueType, (Map<?, ?>) map);
    return (Map<K, V>) map;
}

private static void checkMap(Object map) {
    checkType(Map.class, map);
}

private static <K, V> void checkMapContents(Class<K> keyType, Class<V> valueType, Map<?, ?> map) {
    for (Map.Entry<?, ?> entry : map.entrySet()) {
        checkType(keyType, entry.getKey());
        checkType(valueType, entry.getValue());
    }
}

private static <K> void checkType(Class<K> expectedType, Object obj) {
    if (!expectedType.isInstance(obj)) {
        throw new IllegalArgumentException("Expected " + expectedType + " but was " + obj.getClass() + ": " + obj);
    }
}

1
น่ากลัว; ฉันคิดว่าฉันสามารถรวมเข้ากับคำตอบของฉันเพื่อกำหนดพารามิเตอร์และหลีกเลี่ยงการระงับคำเตือนทั้งหมด!
skiphoppy

1
+1 น่าจะเป็นสูตรที่ดีที่สุด (เข้าใจง่ายและดูแลรักษา) เพื่อทำอย่างปลอดภัยด้วยการตรวจสอบรันไทม์
Tino

8

ฟังก์ชันยูทิลิตี้ Objects.Unchecked ในคำตอบข้างต้นโดย Esko Luontola เป็นวิธีที่ดีในการหลีกเลี่ยงความยุ่งเหยิงของโปรแกรม

หากคุณไม่ต้องการให้ SuppressWarnings ใช้วิธีการทั้งหมด Java บังคับให้คุณวางไว้บนโลคอล หากคุณต้องการนักแสดงในสมาชิกก็สามารถนำไปสู่รหัสเช่นนี้:

@SuppressWarnings("unchecked")
Vector<String> watchedSymbolsClone = (Vector<String>) watchedSymbols.clone();
this.watchedSymbols = watchedSymbolsClone;

การใช้ยูทิลิตีนั้นสะอาดกว่ามากและยังคงชัดเจนว่าคุณกำลังทำอะไรอยู่:

this.watchedSymbols = Objects.uncheckedCast(watchedSymbols.clone());

หมายเหตุ: ฉันรู้สึกว่าเป็นเรื่องสำคัญที่จะต้องเพิ่มว่าบางครั้งคำเตือนหมายถึงคุณกำลังทำสิ่งผิดปกติเช่น:

ArrayList<Integer> intList = new ArrayList<Integer>();
intList.add(1);
Object intListObject = intList; 

 // this line gives an unchecked warning - but no runtime error
ArrayList<String> stringList  = (ArrayList<String>) intListObject;
System.out.println(stringList.get(0)); // cast exception will be given here

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


5

การยับยั้งการเตือนไม่ใช่วิธีแก้ปัญหา คุณไม่ควรใช้การร่ายสองระดับในคำสั่งเดียว

HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {

    // first, cast the returned Object to generic HashMap<?,?>
    HashMap<?, ?> theHash = (HashMap<?, ?>)session.getAttribute("attributeKey");

    // next, cast every entry of the HashMap to the required type <String, String>
    HashMap<String, String> returingHash = new HashMap<>();
    for (Entry<?, ?> entry : theHash.entrySet()) {
        returingHash.put((String) entry.getKey(), (String) entry.getValue());
    }
    return returingHash;
}

1
คำถามห้าปีของเขา? คุณต้องทำงานมากขนาดนั้นเหรอ? ให้ Java มีประเภทการลบ hashmap ที่สองควรจะเหมือนกันกับครั้งแรกที่ runtime; ฉันคิดว่ามันจะมีประสิทธิภาพมากขึ้นและหลีกเลี่ยงการคัดลอกถ้าคุณทำซ้ำผ่านรายการและตรวจสอบว่าพวกเขาทั้งหมดของสตริง หรือ TBH ตรวจสอบแหล่งที่มาของเซิร์ฟเล็ต JAR ที่คุณกำลังใช้และตรวจสอบว่าใส่สตริงไว้
รูปี

1
ถึงวันนี้ฉันเห็นคำเตือนนี้ในโครงการ ปัญหาของเขาไม่ใช่การตรวจสอบประเภท แต่คำเตือนที่เกิดจาก "การใส่" ในแผนที่ที่ไม่ได้รับการถ่ายทอด
abbas

2

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

HashMap<String, Object> test = new HashMap();

ซึ่งจะสร้างคำเตือนเมื่อคุณต้องทำ

HashMap<String, Object> test = new HashMap<String, Object>();

มันอาจจะคุ้มค่าที่จะดู

Generics ในภาษาโปรแกรม Java

หากคุณไม่คุ้นเคยกับสิ่งที่ต้องทำ


1
น่าเสียดายที่ไม่ใช่เรื่องง่าย เพิ่มรหัสแล้ว
skiphoppy

1
ฉันมาที่นี่เพื่อค้นหาคำตอบของปัญหาที่แตกต่างกันเล็กน้อย: และคุณบอกฉันอย่างที่ฉันต้องการ! ขอบคุณ!
Staticsan

2

ฉันอาจเข้าใจผิดคำถาม (ตัวอย่างและคู่ของเส้นโดยรอบจะดี) แต่ทำไมคุณไม่ใช้อินเทอร์เฟซที่เหมาะสมเสมอ (และ Java5 +)? ฉันเห็นเหตุผลว่าทำไมคุณจะเคยต้องการที่จะโยนไปแทนHashMap Map<KeyType,ValueType>ในความเป็นจริงฉันไม่สามารถจินตนาการใด ๆเหตุผลที่จะกำหนดประเภทของตัวแปรในการแทนHashMapMap

และทำไมเป็นแหล่งที่มาObject? มันเป็นประเภทพารามิเตอร์ของการรวบรวมมรดกหรือไม่? ถ้าเป็นเช่นนั้นใช้ generics และระบุประเภทที่คุณต้องการ


2
ฉันค่อนข้างแน่ใจว่าการเปลี่ยนมาใช้แผนที่ในกรณีนี้จะไม่เปลี่ยนแปลงอะไรเลย แต่ขอบคุณสำหรับเคล็ดลับการเขียนโปรแกรมซึ่งอาจเปลี่ยนวิธีที่ฉันทำบางสิ่งให้ดีขึ้น แหล่งที่มาของวัตถุคือ API ที่ฉันไม่สามารถควบคุมได้ (เพิ่มรหัส)
skiphoppy

2

ถ้าฉันต้องใช้ API ที่ไม่รองรับ Generics .. ฉันพยายามแยกสายเหล่านั้นด้วยชุดคำสั่ง wrapper โดยใช้สองสามบรรทัดให้มากที่สุด ฉันใช้คำอธิบายประกอบ SuppressWarnings และเพิ่มการปลดเปลื้องความปลอดภัยประเภทในเวลาเดียวกัน

นี่เป็นเพียงความชอบส่วนตัวที่จะทำให้ทุกอย่างเรียบร้อยที่สุด


2

รับอันนี้เร็วกว่าการสร้าง HashMap ใหม่ถ้ามันมีอยู่แล้ว แต่ยังปลอดภัยเนื่องจากแต่ละองค์ประกอบถูกตรวจสอบกับประเภทของมัน ...

@SuppressWarnings("unchecked")
public static <K, V> HashMap<K, V> toHashMap(Object input, Class<K> key, Class<V> value) {
       assert input instanceof Map : input;

       for (Map.Entry<?, ?> e : ((HashMap<?, ?>) input).entrySet()) {
           assert key.isAssignableFrom(e.getKey().getClass()) : "Map contains invalid keys";
           assert value.isAssignableFrom(e.getValue().getClass()) : "Map contains invalid values";
       }

       if (input instanceof HashMap)
           return (HashMap<K, V>) input;
       return new HashMap<K, V>((Map<K, V>) input);
    }

key.isAssignableFrom(e.getKey().getClass())สามารถเขียนเป็นkey.isInstance(e.getKey())
user102008

1

เพียงพิมพ์ก่อนที่คุณจะส่ง

Object someObject = session.getAttribute("attributeKey");
if(someObject instanceof HashMap)
HashMap<String, String> theHash = (HashMap<String, String>)someObject;  

และสำหรับใครก็ตามที่ถามว่าเป็นเรื่องปกติที่จะรับสิ่งของที่คุณไม่แน่ใจ การใช้งาน "SOA" แบบดั้งเดิมส่งผ่านวัตถุต่าง ๆ ที่คุณไม่ควรเชื่อถือ (ความน่ากลัว!)

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


8
น่าเสียดายที่ยาชื่อสามัญทำให้เป็นไปไม่ได้ มันไม่ใช่แค่ HashMap แต่เป็น HashMap ที่มีข้อมูลประเภท และถ้าฉันกำจัดข้อมูลนั้นฉันจะผลักดันการเตือนไปที่อื่น
skiphoppy

1

เกือบทุกปัญหาในวิทยาการคอมพิวเตอร์สามารถแก้ไขได้โดยการเพิ่มระดับของการอ้อม * หรืออะไรบางอย่าง

Mapดังนั้นแนะนำไม่ใช่วัตถุทั่วไปที่มีความสูงกว่าระดับที่ ด้วยบริบทที่ไม่มีมันจะไม่ดูน่าเชื่อถือมาก แต่อย่างไรก็ตาม:

public final class Items implements java.io.Serializable {
    private static final long serialVersionUID = 1L;
    private Map<String,String> map;
    public Items(Map<String,String> map) {
        this.map = New.immutableMap(map);
    }
    public Map<String,String> getMap() {
        return map;
    }
    @Override public String toString() {
        return map.toString();
    }
}

public final class New {
    public static <K,V> Map<K,V> immutableMap(
        Map<? extends K, ? extends V> original
    ) {
        // ... optimise as you wish...
        return Collections.unmodifiableMap(
            new HashMap<String,String>(original)
        );
    }
}

static Map<String, String> getItems(HttpSession session) {
    Items items = (Items)
        session.getAttribute("attributeKey");
    return items.getMap();
}

* ยกเว้นการอ้อมในระดับมากเกินไป


1
คำพูดนั้นมาจากศาสตราจารย์ David Wheeler ผู้ล่วงลับไปแล้ว en.wikipedia.org/wiki/…
Stephen C

1

นี่เป็นวิธีหนึ่งที่ฉันจัดการเมื่อฉันแทนที่การequals()ดำเนินการ

public abstract class Section<T extends Section> extends Element<Section<T>> {
    Object attr1;

    /**
    * Compare one section object to another.
    *
    * @param obj the object being compared with this section object
    * @return true if this section and the other section are of the same
    * sub-class of section and their component fields are the same, false
    * otherwise
    */       
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            // this exists, but obj doesn't, so they can't be equal!
            return false;
        }

        // prepare to cast...
        Section<?> other;

        if (getClass() != obj.getClass()) {
            // looks like we're comparing apples to oranges
            return false;
        } else {
            // it must be safe to make that cast!
            other = (Section<?>) obj;
        }

        // and then I compare attributes between this and other
        return this.attr1.equals(other.attr1);
    }
}

ดูเหมือนว่าจะใช้งานได้ใน Java 8 (เรียบเรียงด้วย-Xlint:unchecked)


0

หากคุณแน่ใจว่าประเภทที่ส่งคืนโดย session.getAttribute () คือ HashMap คุณจะไม่สามารถพิมพ์ไปยังประเภทที่แน่นอนนั้น แต่พึ่งพาการตรวจสอบ HashMap ทั่วไปเท่านั้น

HashMap<?,?> getItems(javax.servlet.http.HttpSession session) {  
    HashMap<?,?> theHash = (HashMap<?,?>)session.getAttribute("attributeKey");
    return theHash;
} 

จากนั้น Eclipse จะทำให้คำเตือนแปลกใจ แต่แน่นอนว่าสิ่งนี้อาจทำให้เกิดข้อผิดพลาดขณะทำงานซึ่งยากต่อการแก้ไข ฉันใช้วิธีนี้ในบริบทที่ไม่เกี่ยวกับการดำเนินการเท่านั้น


0

สองวิธีวิธีหนึ่งซึ่งหลีกเลี่ยงแท็กได้อย่างสมบูรณ์ส่วนอีกวิธีหนึ่งใช้วิธีอรรถประโยชน์ที่ซุกซน แต่ดี
ปัญหาคือคอลเล็กชั่น pre-genericised ...
ฉันเชื่อว่ากฎง่ายๆคือ: "โยนวัตถุทีละอย่าง" - สิ่งนี้หมายความว่าเมื่อพยายามใช้คลาสดิบในโลก genericised นั่นคือเพราะคุณไม่รู้ อยู่ในแผนที่นี้ <?,?> (และแน่นอนว่า JVM อาจพบว่ามันไม่ใช่แผนที่!) เห็นได้ชัดเมื่อคุณคิดถึงมันว่าคุณไม่สามารถร่ายมันได้ หากคุณมี Map <String,?> map2 ดังนั้น HashSet <String> keys = (HashSet <String>) map2.keySet () ไม่ได้ให้คำเตือนแก่คุณแม้ว่าจะเป็นการกระทำของศรัทธา "สำหรับคอมไพเลอร์ (เพราะ มันอาจกลายเป็น TreeSet) ... แต่มันเป็นเพียงการกระทำเดียวของความศรัทธา

ป.ล. ต่อการคัดค้านที่วนซ้ำในวิธีแรกของฉัน "น่าเบื่อ" และ "ต้องใช้เวลา" คำตอบคือ "ไม่มีความเจ็บปวดไม่มีกำไร": คอลเลกชัน genericised รับประกันว่าจะมี Map.Entry <String, String> s และไม่มีอะไร อื่น. คุณต้องจ่ายเงินสำหรับการรับประกันนี้ เมื่อใช้ generics อย่างเป็นระบบการจ่ายเงินนี้สวยงามใช้รูปแบบของการปฏิบัติตามข้อกำหนดไม่ใช่เวลาที่เครื่อง!
โรงเรียนแห่งความคิดหนึ่งแห่งอาจบอกว่าคุณควรตั้งค่าของ Eclipse เพื่อทำข้อผิดพลาดในการปลดเปลื้องที่ไม่ถูกตรวจสอบแทนที่จะเป็นคำเตือน ในกรณีนี้คุณต้องใช้วิธีแรกของฉัน

package scratchpad;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

public class YellowMouse {

    // First way

    Map<String, String> getHashMapStudiouslyAvoidingSuppressTag(HttpSession session) {
      Map<?, ?> theHash = (Map<?, ?>)session.getAttribute("attributeKey");

      Map<String, String> yellowMouse = new HashMap<String, String>();
      for( Map.Entry<?, ?> entry : theHash.entrySet() ){
        yellowMouse.put( (String)entry.getKey(), (String)entry.getValue() );
      }

      return yellowMouse;
    }


    // Second way

    Map<String, String> getHashMapUsingNaughtyButNiceUtilityMethod(HttpSession session) {
      return uncheckedCast( session.getAttribute("attributeKey") );
    }


    // NB this is a utility method which should be kept in your utility library. If you do that it will
    // be the *only* time in your entire life that you will have to use this particular tag!!

    @SuppressWarnings({ "unchecked" })
    public static synchronized <T> T uncheckedCast(Object obj) {
        return (T) obj;
    }


}

ความจริงที่ว่าคุณไม่มีสิทธิ์แสดงความคิดเห็นไม่อนุญาตให้คุณแก้ไขคำตอบของผู้อื่นเพื่อเพิ่มความคิดเห็นของคุณ คุณแก้ไขคำตอบของผู้อื่นเพื่อปรับปรุงในการจัดรูปแบบไวยากรณ์ ... ไม่เพิ่มความคิดเห็นของคุณ เมื่อคุณไปถึง 50 ตัวแทนคุณจะสามารถแสดงความคิดเห็นได้ทุกที่ในระหว่างนี้ฉันค่อนข้างแน่ใจว่าคุณสามารถต้านทานได้ (หรือถ้าคุณทำไม่ได้จริงๆให้เขียนความคิดเห็นของคุณไปยังคำตอบที่มีอยู่ในโพสต์ของคุณ) (หมายเหตุสำหรับคนอื่น ๆ : ฉันเขียนสิ่งนี้เพราะฉันเห็น - และปฏิเสธ - ความคิดเห็นที่เขาเสนอแก้ไขไปยังโพสต์อื่น ๆ ในเครื่องมือกลั่นกรอง)
Matteo Italia

-1

สิ่งนี้ทำให้คำเตือนหายไป ...

 static Map<String, String> getItems(HttpSession session) {
        HashMap<?, ?> theHash1 = (HashMap<String,String>)session.getAttribute("attributeKey");
        HashMap<String,String> theHash = (HashMap<String,String>)theHash1;
    return theHash;
}

1
ไม่มันไม่ ที่จริงแล้วสิ่งนี้จะสร้างคำเตือนสองคำโดยที่สิ่งแรกมีอยู่
Stijn de Witt

อาโอเค. ไม่แน่ใจว่าทำไมฉันคิดอย่างนั้น
lukewm

-3

วิธีแก้ปัญหา: ปิดใช้งานคำเตือนนี้ใน Eclipse อย่า @SuppressWarnings เพียงปิดใช้งานอย่างสมบูรณ์

"โซลูชั่น" หลายตัวที่นำเสนอข้างต้นเป็นวิธีที่ไม่เหมาะสมทำให้โค้ดไม่สามารถอ่านได้เพื่อระงับการเตือนโง่ ๆ


9
ฉันขอถามทำไม การปิดใช้งานคำเตือนทั่วโลกจะซ่อนสถานที่อื่น ๆ ที่ปัญหานี้เป็นจริง การเพิ่ม@SuppressWarningsไม่ทำให้โค้ดอ่านไม่ได้เลย
MByD
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.