ชนิดดิบคืออะไรและทำไมเราไม่ควรใช้


662

คำถาม:

  • Java คืออะไรชนิดใดบ้างและทำไมฉันมักได้ยินว่าไม่ควรใช้รหัสใหม่
  • อะไรคือทางเลือกถ้าเราไม่สามารถใช้ประเภทดิบและมันจะดีกว่า?

บทเรียน Java ยังคงใช้ JComboBox ที่ทำให้เกิดคำเตือนนี้ Combobox เวอร์ชันใดจะไม่ทำให้เกิดการเตือนนี้ docs.oracle.com/javase/tutorial/uiswing/components/ …
SuperStar

1
โปรดทราบว่าเหตุผลที่มีประเภท raw นั้นมีไว้เพื่อรองรับความเข้ากันได้กับ Java 1.4 และรุ่นเก่ากว่าซึ่งไม่มี generics เลย
Jesper

คำตอบ:


744

ประเภทดิบคืออะไร?

ข้อกำหนดภาษา Java กำหนดประเภท rawดังต่อไปนี้:

JLS 4.8 ประเภทดิบ

ประเภทดิบถูกกำหนดให้เป็นหนึ่งใน:

  • ชนิดการอ้างอิงที่เกิดขึ้นโดยการใช้ชื่อของการประกาศชนิดทั่วไปโดยไม่มีรายการอาร์กิวเมนต์ชนิดประกอบ

  • ประเภทอาร์เรย์ที่มีประเภทองค์ประกอบเป็นประเภทดิบ

  • ไม่ใช่staticประเภทสมาชิกของประเภทดิบRที่ไม่ได้รับการถ่ายทอดจาก superclass หรือ superinterface Rของ

นี่คือตัวอย่างที่แสดงให้เห็น:

public class MyType<E> {
    class Inner { }
    static class Nested { }

    public static void main(String[] args) {
        MyType mt;          // warning: MyType is a raw type
        MyType.Inner inn;   // warning: MyType.Inner is a raw type

        MyType.Nested nest; // no warning: not parameterized type
        MyType<Object> mt1; // no warning: type parameter given
        MyType<?> mt2;      // no warning: type parameter given (wildcard OK!)
    }
}

นี่MyType<E>คือประเภทที่กำหนดพารามิเตอร์ ( JLS 4.5 ) มันเป็นเรื่องธรรมดาที่จะเรียกขานหมายถึงประเภทนี้เป็นเพียงแค่MyTypeสั้น ๆ MyType<E>แต่ในทางเทคนิคชื่อเป็น

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

MyType.Nestedไม่ใช่ประเภทที่มีการกำหนดพารามิเตอร์แม้ว่าจะเป็นประเภทสมาชิกของประเภทที่ปรับพารามิเตอร์MyType<E>แล้วก็ตามเนื่องจากเป็นstaticประเภท

mt1และmt2มีการประกาศด้วยพารามิเตอร์ประเภทจริงดังนั้นจึงไม่ได้เป็นประเภทดิบ


มีอะไรพิเศษเกี่ยวกับประเภทวัตถุดิบบ้าง

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

List names = new ArrayList(); // warning: raw type!
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // not a compilation error!

โค้ดด้านบนทำงานได้ดี แต่สมมติว่าคุณมีสิ่งต่อไปนี้ด้วย:

for (Object o : names) {
    String name = (String) o;
    System.out.println(name);
} // throws ClassCastException!
  //    java.lang.Boolean cannot be cast to java.lang.String

ตอนนี้เราทำงานเป็นปัญหาที่ใช้เวลาเพราะมีอะไรบางอย่างที่ไม่ได้เป็นnamesinstanceof String

สมมุติถ้าคุณต้องการnamesที่จะมีเพียงStringคุณอาจอาจจะยังคงใช้เป็นชนิดดิบและตนเองตรวจสอบทุก addตัวเองแล้วโยนตนเองจะทุกรายการจากString ดียิ่งขึ้นแม้ว่าจะไม่ใช้ชนิด raw และให้คอมไพเลอร์ทำงานทั้งหมดให้คุณควบคุมพลังของ generics Javanames

List<String> names = new ArrayList<String>();
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // compilation error!

แน่นอนถ้าคุณไม่ต้องการที่namesจะให้Booleanแล้วคุณสามารถประกาศเป็นList<Object> namesและรหัสข้างต้นจะรวบรวม

ดูสิ่งนี้ด้วย


ชนิด raw แตกต่างจากการใช้<Object>เป็นพารามิเตอร์ชนิดอย่างไร

ต่อไปนี้เป็นข้อความจากEffective Java 2nd Edition รายการ 23: อย่าใช้ประเภท raw ในรหัสใหม่ :

ความแตกต่างระหว่างประเภท raw Listและประเภทที่กำหนดพารามิเตอร์List<Object>คืออะไร ก่อนหน้านี้พูดอย่างหลวม ๆ ได้ยกเลิกการตรวจสอบประเภททั่วไปในขณะที่คนหลังบอกกับคอมไพเลอร์อย่างชัดเจนว่ามันสามารถถือวัตถุประเภทใดก็ได้ ขณะที่คุณสามารถส่งผ่านList<String>ไปยังพารามิเตอร์ของชนิดคุณจะไม่สามารถผ่านมันไปยังพารามิเตอร์ของชนิดList List<Object>มีกฎระเบียบ Subtyping สำหรับยาชื่อสามัญที่มีและList<String>เป็นชนิดย่อยของชนิดดิบแต่ไม่ใช่ชนิดแปร,List List<Object>เป็นผลให้คุณสูญเสียความปลอดภัยประเภทถ้าคุณใช้ชนิดดิบเหมือนListList<Object>แต่ไม่ได้ถ้าคุณใช้ชนิดแปรเช่น

เพื่อแสดงให้เห็นจุดที่พิจารณาวิธีการต่อไปซึ่งจะใช้เวลาและผนวกList<Object>new Object()

void appendNewObject(List<Object> list) {
   list.add(new Object());
}

Generics ใน Java นั้นไม่แปรเปลี่ยน A List<String>ไม่ใช่ a List<Object>ดังนั้นต่อไปนี้จะสร้างคำเตือนคอมไพเลอร์:

List<String> names = new ArrayList<String>();
appendNewObject(names); // compilation error!

หากคุณได้ประกาศappendNewObjectให้ใช้ชนิด raw Listเป็นพารามิเตอร์ดังนั้นสิ่งนี้จะรวบรวมและคุณจะสูญเสียความปลอดภัยของประเภทที่คุณได้รับจาก generics

ดูสิ่งนี้ด้วย


ชนิด raw แตกต่างจากการใช้<?>เป็นพารามิเตอร์ชนิดอย่างไร

List<Object>, List<String>ฯลฯ ล้วนList<?>แต่เป็นที่น่าดึงดูดใจที่จะบอกว่าพวกเขาเป็นเพียงคนListเดียว แต่มีความแตกต่างที่สำคัญ: ตั้งแต่List<E>กำหนดเท่านั้นadd(E)คุณจะไม่สามารถเพิ่มเพียงวัตถุใด ๆ List<?>โดยพลไป บนมืออื่น ๆ ตั้งแต่ประเภทดิบListไม่ได้มีความปลอดภัยชนิดที่คุณสามารถเพียงเกี่ยวกับอะไรไปaddList

พิจารณารูปแบบต่อไปนี้ของตัวอย่างก่อนหน้า:

static void appendNewObject(List<?> list) {
    list.add(new Object()); // compilation error!
}
//...

List<String> names = new ArrayList<String>();
appendNewObject(names); // this part is fine!

คอมไพเลอร์ทำงานได้อย่างยอดเยี่ยมในการปกป้องคุณจากการละเมิดประเภทที่ไม่คงที่ของList<?>! หากคุณได้ประกาศพารามิเตอร์เป็นชนิดดิบแล้วรหัสจะรวบรวมและคุณต้องการละเมิดคงที่ประเภทของList listList<String> names


ประเภทดิบคือการลบประเภทนั้น

กลับไปที่ JLS 4.8:

เป็นไปได้ที่จะใช้เป็นประเภทการลบประเภทพารามิเตอร์หรือการลบประเภทอาร์เรย์ที่มีประเภทองค์ประกอบเป็นประเภทพารามิเตอร์ ประเภทดังกล่าวจะเรียกว่าเป็นชนิดดิบ

[ ... ]

ซูเปอร์คลาส (ตามลำดับซูเปอร์อินเทอร์เฟซ) ของชนิด raw คือการลบของซูเปอร์คลาส (superinterfaces) ของการกำหนดพารามิเตอร์ประเภททั่วไป

ประเภทของคอนสตรัค, วิธีการเช่นหรือไม่ใช่staticข้อมูลประเภทดิบCที่ไม่ได้รับมรดกมาจาก superclasses หรือ superinterfaces Cมันเป็นชนิดดิบที่สอดคล้องกับการลบของชนิดในการประกาศทั่วไปที่สอดคล้องกับ

ในแง่ที่ง่ายขึ้นเมื่อใช้ประเภทดิบตัวสร้างวิธีการอินสแตนซ์และstaticฟิลด์ที่ไม่ถูกลบก็จะถูกลบเช่นกัน

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

class MyType<E> {
    List<String> getNames() {
        return Arrays.asList("John", "Mary");
    }

    public static void main(String[] args) {
        MyType rawType = new MyType();
        // unchecked warning!
        // required: List<String> found: List
        List<String> names = rawType.getNames();
        // compilation error!
        // incompatible types: Object cannot be converted to String
        for (String str : rawType.getNames())
            System.out.print(str);
    }
}

เมื่อเราใช้ดิบMyType, getNamesกลายเป็นลบเช่นกันเพื่อที่จะส่งกลับดิบList!

JLS 4.6ยังคงอธิบายต่อไปนี้:

การลบประเภทยังจับคู่ลายเซ็นของ Constructor หรือเมธอดเข้ากับลายเซ็นที่ไม่มีประเภทที่กำหนดพารามิเตอร์หรือตัวแปรประเภท การลบนวกรรมิกหรือวิธีลายเซ็นsเป็นลายเซ็นประกอบด้วยชื่อเดียวกับชื่อและตัวหนังสือของทุกชนิดของพารามิเตอร์อย่างเป็นทางการได้รับในss

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

การลบลายเซ็นของวิธีการทั่วไปไม่มีพารามิเตอร์ชนิด

รายงานข้อผิดพลาดต่อไปนี้มีความคิดบางอย่างจาก Maurizio Cimadamore, คอมไพเลอร์ dev และ Alex Buckley หนึ่งในผู้เขียนของ JLS ว่าทำไมพฤติกรรมแบบนี้ควรเกิดขึ้น: https://bugs.openjdk.java.net/browse / (โดยย่อมันทำให้ข้อมูลจำเพาะง่ายขึ้น)


หากไม่ปลอดภัยทำไมจึงอนุญาตให้ใช้ประเภทดิบ

นี่เป็นอีกคำพูดจาก JLS 4.8:

การใช้งานประเภท raw จะได้รับอนุญาตให้เป็นสัมปทานสำหรับความเข้ากันได้ของรหัสดั้งเดิมเท่านั้น การใช้ชนิดข้อมูลดิบในโค้ดที่เขียนหลังจากการแนะนำทั่วไปในภาษาการเขียนโปรแกรม Java นั้นไม่สนับสนุนอย่างยิ่ง เป็นไปได้ว่าภาษาการเขียนโปรแกรมจาวาในอนาคตจะไม่อนุญาตให้ใช้ชนิดข้อมูลดิบ

Java 2nd Edition ที่มีประสิทธิภาพมีสิ่งนี้เพื่อเพิ่ม:

ระบุว่าคุณไม่ควรใช้ประเภทดิบทำไมนักออกแบบภาษาจึงอนุญาต เพื่อให้เข้ากันได้

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

โดยสรุปแล้วไม่ควรใช้ประเภทดิบในรหัสใหม่ คุณควรใช้ชนิดแปร


ไม่มีข้อยกเว้น?

น่าเสียดายเนื่องจาก Java generics ไม่ได้รับการตรวจสอบซ้ำมีข้อยกเว้นสองประการที่ประเภท raw จะต้องใช้ในรหัสใหม่:

  • ตัวอักษรระดับเช่นList.classไม่List<String>.class
  • instanceofตัวถูกดำเนินการเช่นo instanceof Setไม่o instanceof Set<String>

ดูสิ่งนี้ด้วย


16
คุณหมายความว่าอย่างไร "Java generics นั้นไม่ได้รับการแก้ไขใหม่"
Carl G

7
สำหรับข้อยกเว้นที่สองไวยากรณ์o instanceof Set<?>ได้รับอนุญาตให้หลีกเลี่ยงชนิด raw (แม้ว่าจะเป็นเพียงผิวเผินในกรณีนี้)
Paul Bellora

1
ประเภท Raw มีประโยชน์อย่างมากและลดรหัสสำเร็จรูปในกรณีที่การค้นหา JNDI สำหรับ bean ซึ่งขยายส่วนต่อประสาน สิ่งนี้จะช่วยแก้ไขความจำเป็นในการเขียนnremote beans สำหรับแต่ละคลาสที่นำไปใช้งานด้วยรหัสที่เหมือนกัน
djmj

8
"Non-reified" เป็นอีกวิธีหนึ่งในการบอกว่าพวกเขาถูกลบไปแล้ว คอมไพเลอร์รู้ว่าพารามิเตอร์ทั่วไปคืออะไร แต่ข้อมูลนั้นจะไม่ถูกส่งต่อไปยังไบต์ที่สร้างขึ้น JLS ต้องการให้คลาสตัวอักษรนั้นไม่มีพารามิเตอร์ชนิด
Erick G. Hagstrom

2
@ OldCurmudgeon นั่นน่าสนใจ ฉันหมายถึงอย่างเป็นทางการมันไม่ใช่เพราะระดับตัวอักษรที่กำหนดเป็นเพียงTypeName.classที่TypeNameเป็นตัวระบุธรรมดา ( jls ) พูดตามสมมุติฉันเดาว่ามันอาจเป็นได้ทั้ง อาจเป็นเบาะแสList<String>.classเป็นตัวแปรที่ JLS เรียกว่าข้อผิดพลาดของคอมไพเลอร์โดยเฉพาะดังนั้นหากพวกเขาเคยเพิ่มลงในภาษาที่ฉันคาดหวังว่าเป็นสิ่งที่พวกเขาใช้
Radiodef

62

Java คืออะไรชนิดใดบ้างและทำไมฉันมักได้ยินว่าไม่ควรใช้รหัสใหม่

ประเภทดิบเป็นประวัติศาสตร์โบราณของภาษาจาวา ในการเริ่มต้นมีCollectionsและพวกเขาถือObjectsอะไรมากและไม่มีอะไรน้อย การทำงานทุกอย่างตามที่Collectionsต้องการปลดเปลื้องจากObjectประเภทที่ต้องการ

List aList = new ArrayList();
String s = "Hello World!";
aList.add(s);
String c = (String)aList.get(0);

ขณะนี้ใช้งานได้เกือบตลอดเวลามีข้อผิดพลาดเกิดขึ้น

List aNumberList = new ArrayList();
String one = "1";//Number one
aNumberList.add(one);
Integer iOne = (Integer)aNumberList.get(0);//Insert ClassCastException here

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

List<String> aNumberList = new ArrayList<String>();
aNumberList.add("one");
Integer iOne = aNumberList.get(0);//Compile time error
String sOne = aNumberList.get(0);//works fine

สำหรับการเปรียบเทียบ:

// Old style collections now known as raw types
List aList = new ArrayList(); //Could contain anything
// New style collections with Generics
List<String> aList = new ArrayList<String>(); //Contains only Strings

ซับซ้อนกว่าอินเตอร์เฟสเปรียบเทียบได้:

//raw, not type save can compare with Other classes
class MyCompareAble implements CompareAble
{
   int id;
   public int compareTo(Object other)
   {return this.id - ((MyCompareAble)other).id;}
}
//Generic
class MyCompareAble implements CompareAble<MyCompareAble>
{
   int id;
   public int compareTo(MyCompareAble other)
   {return this.id - other.id;}
}

โปรดทราบว่ามันเป็นไปไม่ได้ที่จะใช้CompareAbleส่วนต่อประสานกับcompareTo(MyCompareAble)ประเภท raw ทำไมคุณไม่ควรใช้:

  • การObjectจัดเก็บใด ๆใน a Collectionจะต้องถูกร่ายก่อนที่จะสามารถใช้งานได้
  • การใช้ข้อมูลทั่วไปช่วยให้สามารถตรวจสอบเวลาการคอมไพล์ได้
  • การใช้ประเภท raw เหมือนกับการจัดเก็บแต่ละค่าเป็น Object

สิ่งที่คอมไพเลอร์ทำ: Generics เข้ากันได้แบบย้อนหลังพวกเขาใช้คลาส Java เดียวกับชนิด raw เวทมนตร์ส่วนใหญ่เกิดขึ้นในเวลารวบรวม

List<String> someStrings = new ArrayList<String>();
someStrings.add("one");
String one = someStrings.get(0);

จะถูกรวบรวมเป็น:

List someStrings = new ArrayList();
someStrings.add("one"); 
String one = (String)someStrings.get(0);

นี่เป็นรหัสเดียวกับที่คุณจะเขียนถ้าคุณใช้ประเภทดิบโดยตรง คิดว่าฉันไม่แน่ใจว่าเกิดอะไรขึ้นกับCompareAbleอินเทอร์เฟซฉันเดาว่ามันจะสร้างสองcompareToฟังก์ชั่นอันหนึ่งและอีกอันหนึ่งจะMyCompareAbleรับObjectและส่งต่อไปยังส่วนแรกหลังจากส่งมันออกมา

อะไรคือทางเลือกสำหรับประเภทดิบ: ใช้ยาสามัญ


30

ชนิด raw คือชื่อของคลาสหรืออินเตอร์เฟสทั่วไปโดยไม่มีอาร์กิวเมนต์ชนิดใด ๆ ตัวอย่างเช่นกำหนดคลาส Box ทั่วไป:

public class Box<T> {
    public void set(T t) { /* ... */ }
    // ...
}

ในการสร้างประเภทพารามิเตอร์Box<T>คุณต้องระบุอาร์กิวเมนต์ประเภทจริงสำหรับพารามิเตอร์ประเภทเป็นทางการT:

Box<Integer> intBox = new Box<>();

หากไม่ได้ระบุอาร์กิวเมนต์ประเภทจริงคุณจะสร้างประเภท raw ของBox<T>:

Box rawBox = new Box();

ดังนั้นเป็นชนิดดิบประเภททั่วไปBox Box<T>อย่างไรก็ตามคลาสที่ไม่ใช่แบบทั่วไปหรือชนิดอินเตอร์เฟสไม่ใช่ชนิด raw

ประเภท Raw แสดงขึ้นในรหัสดั้งเดิมเนื่องจากมีคลาส API จำนวนมาก (เช่นคลาส Collections) ไม่ใช่แบบทั่วไปก่อน JDK 5.0 เมื่อใช้ประเภทดิบคุณเป็นหลักได้รับพฤติกรรมก่อน generics - Boxช่วยให้คุณObjects สำหรับความเข้ากันได้แบบย้อนหลังการกำหนดประเภทพารามิเตอร์ให้เป็นประเภท raw นั้นได้รับอนุญาต:

Box<String> stringBox = new Box<>();
Box rawBox = stringBox;               // OK

แต่ถ้าคุณกำหนดชนิด raw ให้กับชนิดที่มีพารามิเตอร์คุณจะได้รับคำเตือน:

Box rawBox = new Box();           // rawBox is a raw type of Box<T>
Box<Integer> intBox = rawBox;     // warning: unchecked conversion

คุณจะได้รับคำเตือนหากคุณใช้ประเภท raw เพื่อเรียกใช้เมธอดทั่วไปที่กำหนดไว้ในประเภททั่วไปที่สอดคล้องกัน:

Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8);  // warning: unchecked invocation to set(T)

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

ส่วนการลบประเภทมีข้อมูลเพิ่มเติมเกี่ยวกับวิธีการที่คอมไพเลอร์ Java ใช้ประเภทดิบ

ข้อผิดพลาดที่ไม่ได้ตรวจสอบ

ตามที่กล่าวไว้ก่อนหน้านี้เมื่อผสมรหัสดั้งเดิมกับรหัสทั่วไปคุณอาจพบข้อความเตือนที่คล้ายกับข้อความต่อไปนี้:

หมายเหตุ: Example.java ใช้การดำเนินการที่ไม่ได้ตรวจสอบหรือไม่ปลอดภัย

หมายเหตุ: คอมไพล์ด้วย -Xlint: ไม่ได้ตรวจสอบรายละเอียด

สิ่งนี้สามารถเกิดขึ้นได้เมื่อใช้ API แบบเก่าที่ทำงานกับชนิดข้อมูลดิบดังที่แสดงในตัวอย่างต่อไปนี้:

public class WarningDemo {
    public static void main(String[] args){
        Box<Integer> bi;
        bi = createBox();
    }

    static Box createBox(){
        return new Box();
    }
}

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

การคอมไพล์ตัวอย่างก่อนหน้าใหม่ด้วย -Xlint: ไม่ถูกตรวจสอบเผยให้เห็นข้อมูลเพิ่มเติมต่อไปนี้:

WarningDemo.java:4: warning: [unchecked] unchecked conversion
found   : Box
required: Box<java.lang.Integer>
        bi = createBox();
                      ^
1 warning

หากต้องการปิดใช้งานคำเตือนที่ไม่ได้ตรวจสอบอย่างสมบูรณ์ให้ใช้แฟล็ก -Xlint: -Checked @SuppressWarnings("unchecked")คำอธิบายประกอบยับยั้งคำเตือนไม่ถูกตรวจสอบ หากคุณไม่คุ้นเคยกับ@SuppressWarningsไวยากรณ์ดูที่คำอธิบายประกอบ

แหล่งที่มาดั้งเดิม: Java Tutorials


21

ประเภท "raw" ใน Java เป็นคลาสที่ไม่ใช่แบบทั่วไปและจัดการกับออบเจ็กต์ "raw" แทนที่จะเป็นพารามิเตอร์ชนิดทั่วไปชนิดปลอดภัย

ตัวอย่างเช่นก่อนที่ Java generics จะพร้อมใช้งานคุณจะต้องใช้คลาสคอลเล็กชันดังนี้:

LinkedList list = new LinkedList();
list.add(new MyObject());
MyObject myObject = (MyObject)list.get(0);

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

เมื่อใช้ generics คุณจะลบ "ไม่ทราบ" ปัจจัยออกเนื่องจากคุณต้องระบุประเภทของวัตถุที่สามารถไปในรายการได้อย่างชัดเจน:

LinkedList<MyObject> list = new LinkedList<MyObject>();
list.add(new MyObject());
MyObject myObject = list.get(0);

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


3
โดยเฉพาะอย่างยิ่งประเภท raw คือสิ่งที่คุณจะได้รับเมื่อคุณละเว้นพารามิเตอร์ประเภทสำหรับประเภททั่วไป ประเภท Raw เป็นคุณลักษณะที่เข้ากันได้ย้อนหลังเท่านั้นและอาจมีการลบออก คุณสามารถรับพฤติกรรมที่คล้ายกันโดยใช้? พารามิเตอร์ตัวแทน
John Flatness

@zerocrates: คล้ายกัน แต่แตกต่างกัน! ?ยังคงใช้ข้อเสนอความปลอดภัยประเภท ฉันครอบคลุมในคำตอบของฉัน
polygenelubricants

19
 private static List<String> list = new ArrayList<String>();

คุณควรระบุพารามิเตอร์ประเภท

คำเตือนแนะนำว่าควรกำหนดพารามิเตอร์ประเภทที่กำหนดไว้เพื่อรองรับข้อมูลทั่วไปแทนที่จะใช้รูปแบบดิบ

Listถูกกำหนดให้รองรับข้อมูลทั่วไป: public class List<E>. ซึ่งช่วยให้การดำเนินงานที่ปลอดภัยประเภทมากมายที่มีการตรวจสอบรวบรวมเวลา


3
ตอนนี้ถูกแทนที่ด้วยการอนุมานเพชรใน Java 7 -private static List<String> list = new ArrayList<>();
Ian Campbell

14

ประเภท raw คืออะไรและทำไมฉันมักได้ยินว่าไม่ควรใช้รหัสใหม่

A "ประเภทดิบ" คือการใช้ระดับทั่วไปโดยไม่ได้ระบุอาร์กิวเมนต์ชนิด (s) สำหรับประเภทแปรของ (s), เช่นใช้แทนList List<String>เมื่อ generics ถูกนำเข้าสู่ Java หลายชั้นได้รับการปรับปรุงให้ใช้ generics การใช้คลาสเหล่านี้เป็น "raw type" (โดยไม่ระบุอาร์กิวเมนต์ชนิด) อนุญาตให้ใช้รหัสดั้งเดิมเพื่อคอมไพล์ได้

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

อะไรคือทางเลือกถ้าเราไม่สามารถใช้ประเภทดิบและมันจะดีกว่า?

ทางเลือกที่ต้องการคือการใช้คลาสทั่วไปตามที่ต้องการ - ด้วยอาร์กิวเมนต์ชนิดที่เหมาะสม (เช่น List<String> ) สิ่งนี้ช่วยให้โปรแกรมเมอร์สามารถระบุชนิดเฉพาะเจาะจงมากขึ้นบ่งบอกถึงความหมายมากขึ้นกับผู้ดูแลในอนาคตเกี่ยวกับการใช้งานตัวแปรหรือโครงสร้างข้อมูลที่ตั้งใจไว้และช่วยให้คอมไพเลอร์บังคับใช้ความปลอดภัยประเภทที่ดีขึ้น ข้อดีเหล่านี้ร่วมกันอาจปรับปรุงคุณภาพรหัสและช่วยป้องกันการแนะนำข้อผิดพลาดในการเขียนโค้ด

ตัวอย่างเช่นสำหรับวิธีการที่โปรแกรมเมอร์ต้องการให้แน่ใจว่าตัวแปร List ที่เรียกว่า 'names' มีเฉพาะ Strings:

List<String> names = new ArrayList<String>();
names.add("John");          // OK
names.add(new Integer(1));  // compile error

1
อาอยากทดลองคัดลอกpolygenelubricantsการอ้างอิง "raw type" จากstackoverflow.com/questions/2770111/…ไปยังคำตอบของฉันเอง แต่ฉันคิดว่าฉันจะทิ้งไว้เพื่อใช้ในคำตอบของเขา / เธอ
Bert F

1
ใช่ฉันได้คัดลอกและวางส่วนนั้นทุกที่ที่ผู้คนใช้ประเภทดิบใน stackoverflow และในที่สุดก็ตัดสินใจที่จะมีคำถามหนึ่งคำถามเพื่ออ้างอิงต่อจากนี้ไป ฉันหวังว่ามันจะเป็นผลดีต่อชุมชน
polygenelubricants

1
@polygenel น้ำมันหล่อลื่นที่ฉันสังเกตเห็น - เราตอบคำถามบางคำถามเดียวกัน :-)
Bert F

1
@ ha9u63ar: แน่นอน โดยทั่วไปแล้วคำตอบที่กระชับและเรียบง่ายอย่างน้อยก็ดีเท่าคำตอบที่ยาวและเป็นที่ยอมรับ
displayName

"syping ที่แข็งแรงกว่า" คืออะไร
carloswm85

12

คอมไพเลอร์ต้องการให้คุณเขียนสิ่งนี้:

private static List<String> list = new ArrayList<String>();

เพราะมิฉะนั้นคุณสามารถเพิ่มประเภทใดก็ได้ที่คุณชอบlistทำให้อินสแตนซ์new ArrayList<String>()ไม่มีจุดหมาย Java generics เป็นคุณสมบัติการคอมไพล์เวลาเท่านั้นดังนั้นวัตถุที่สร้างด้วยnew ArrayList<String>()จะยอมรับIntegerหรือJFrameองค์ประกอบอย่างมีความสุขหากได้รับมอบหมายให้อ้างอิง "raw type" List- ตัววัตถุเองไม่รู้อะไรเกี่ยวกับประเภทที่ควรจะมีเฉพาะคอมไพเลอร์เท่านั้น


12

ที่นี่ฉันกำลังพิจารณาหลายกรณีซึ่งคุณสามารถอธิบายแนวคิดได้

1. ArrayList<String> arr = new ArrayList<String>();
2. ArrayList<String> arr = new ArrayList();
3. ArrayList arr = new ArrayList<String>();

กรณีที่ 1

ArrayList<String> arrมันเป็นArrayListตัวแปรอ้างอิงที่มีประเภทStringที่อ้างอิงกับวัตถุชนิดArralyList Stringมันหมายความว่ามันสามารถถือวัตถุชนิดสตริงเท่านั้น

มันเข้มงวดที่จะStringไม่เป็นประเภท Raw ดังนั้นจะไม่มีการเตือน

    arr.add("hello");// alone statement will compile successfully and no warning.

    arr.add(23);  //prone to compile time error.
     //error: no suitable method found for add(int)

กรณีที่ 2

ในกรณีArrayList<String> arrนี้เป็นประเภทที่เข้มงวด แต่วัตถุของคุณnew ArrayList();เป็นประเภทดิบ

    arr.add("hello"); //alone this compile but raise the warning.
    arr.add(23);  //again prone to compile time error.
    //error: no suitable method found for add(int)

นี่arrคือประเภทที่เข้มงวด integerดังนั้นมันจะยกข้อผิดพลาดรวบรวมเวลาเมื่อมีการเพิ่ม

คำเตือน : - เป็นRawชนิดของวัตถุมีการอ้างอิงไปยังประเภทอ้างอิงตัวแปรของStrictArrayList

กรณีที่ 3

ในกรณีArrayList arrนี้เป็นประเภทดิบ แต่วัตถุของคุณnew ArrayList<String>();เป็นประเภทที่เข้มงวด

    arr.add("hello");  
    arr.add(23);  //compiles fine but raise the warning.

มันจะเพิ่มประเภทของวัตถุใด ๆ ลงไปเพราะมันarrเป็นประเภทที่ดิบ

คำเตือน : - Strictวัตถุประเภทมีการrawอ้างอิงถึงตัวแปรอ้างอิงประเภท


8

ดิบประเภทคือการขาดของพารามิเตอร์ชนิดเมื่อใช้ประเภททั่วไป

วัตถุดิบชนิดไม่ควรใช้เพราะอาจก่อให้เกิดข้อผิดพลาด runtime เช่นการใส่doubleลงไปในสิ่งที่ควรจะเป็นSetของints

Set set = new HashSet();
set.add(3.45); //ok

เมื่อรับสิ่งของจากSetคุณไม่ทราบว่ามีอะไรออกมา สมมติว่าคุณคาดหวังว่ามันจะเป็นทั้งหมดintของคุณจะถูกเหวี่ยงลงสู่Integer; ข้อยกเว้นที่รันไทม์เมื่อdouble 3.45 มาพร้อม

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

Set<Integer> set = new HashSet<Integer>();
set.add(3.45); //NOT ok.

7

นี่คืออีกกรณีที่ประเภทดิบจะกัดคุณ:

public class StrangeClass<T> {
  @SuppressWarnings("unchecked")
  public <X> X getSomethingElse() {
    return (X)"Testing something else!";
  }

  public static void main(String[] args) {
    final StrangeClass<String> withGeneric    = new StrangeClass<>();
    final StrangeClass         withoutGeneric = new StrangeClass();
    final String               value1,
                               value2;

    // Compiles
    value1 = withGeneric.getSomethingElse();

    // Produces compile error:
    // incompatible types: java.lang.Object cannot be converted to java.lang.String
    value2 = withoutGeneric.getSomethingElse();
  }
}

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


5

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

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

private static List<String> list = new ArrayList<String>();

4

หน้ากวดวิชา

ชนิด raw คือชื่อของคลาสหรืออินเตอร์เฟสทั่วไปโดยไม่มีอาร์กิวเมนต์ชนิดใด ๆ ตัวอย่างเช่นกำหนดคลาส Box ทั่วไป:

public class Box<T> {
    public void set(T t) { /* ... */ }
    // ...
}

ในการสร้างชนิดที่กำหนดพารามิเตอร์ของ Box คุณต้องระบุอาร์กิวเมนต์ประเภทที่เกิดขึ้นจริงสำหรับพารามิเตอร์ประเภทที่เป็นทางการ T:

Box<Integer> intBox = new Box<>();

หากไม่ได้ระบุอาร์กิวเมนต์ประเภทจริงคุณจะสร้างกล่องประเภท raw:

Box rawBox = new Box();

2

หลีกเลี่ยงประเภทดิบ

ประเภทดิบหมายถึงการใช้ประเภททั่วไปโดยไม่ระบุพารามิเตอร์ประเภท

ตัวอย่างเช่น ,

รายการเป็นชนิด raw ขณะที่List<String>เป็นชนิดที่กำหนดพารามิเตอร์

เมื่อมีการแนะนำ generics ใน JDK 1.5 ประเภท raw จะถูกเก็บไว้เพื่อรักษาความเข้ากันได้กับ Java รุ่นเก่ากว่า แม้ว่าการใช้ประเภทดิบยังคงเป็นไปได้

ควรหลีกเลี่ยง :

  • พวกเขามักจะต้องปลดเปลื้อง
  • พวกเขาไม่ปลอดภัยและประเภทข้อผิดพลาดที่สำคัญบางอย่างจะปรากฏที่รันไทม์เท่านั้น
  • พวกเขาแสดงออกน้อยลงและไม่ทำเอกสารด้วยตนเองในลักษณะเดียวกับประเภทที่กำหนดพารามิเตอร์ ตัวอย่าง

    import java.util.*;
    
    public final class AvoidRawTypes {
    
    void withRawType() {
    
        //Raw List doesn't self-document, 
        //doesn't state explicitly what it can contain
    
        List stars = Arrays.asList("Arcturus", "Vega", "Altair");
    
        Iterator iter = stars.iterator();
    
        while (iter.hasNext()) {
    
            String star = (String) iter.next(); //cast needed
    
            log(star);
        }
    
    }
    
    void withParameterizedType() {
    
        List < String > stars = Arrays.asList("Spica", "Regulus", "Antares");
    
        for (String star: stars) {
    
            log(star);
        }
    
    }
    
    private void log(Object message) {
    
        System.out.println(Objects.toString(message));
    
    }
    
    }

สำหรับการอ้างอิง : https://docs.oracle.com/javase/tutorial/java/generics/rawTypes.html


1

ฉันพบหน้านี้หลังจากทำแบบฝึกหัดตัวอย่างและมีการไขปริศนาที่เหมือนกัน

============== ฉันไปจากรหัสนี้ตามตัวอย่างที่ให้ไว้ ===============

public static void main(String[] args) throws IOException {

    Map wordMap = new HashMap();
    if (args.length > 0) {
        for (int i = 0; i < args.length; i++) {
            countWord(wordMap, args[i]);
        }
    } else {
        getWordFrequency(System.in, wordMap);
    }
    for (Iterator i = wordMap.entrySet().iterator(); i.hasNext();) {
        Map.Entry entry = (Map.Entry) i.next();
        System.out.println(entry.getKey() + " :\t" + entry.getValue());
    }

====================== ถึงรหัสนี้ ========================

public static void main(String[] args) throws IOException {
    // replace with TreeMap to get them sorted by name
    Map<String, Integer> wordMap = new HashMap<String, Integer>();
    if (args.length > 0) {
        for (int i = 0; i < args.length; i++) {
            countWord(wordMap, args[i]);
        }
    } else {
        getWordFrequency(System.in, wordMap);
    }
    for (Iterator<Entry<String, Integer>> i = wordMap.entrySet().iterator(); i.hasNext();) {
        Entry<String, Integer> entry =   i.next();
        System.out.println(entry.getKey() + " :\t" + entry.getValue());
    }

}

================================================== =============================

มันอาจจะปลอดภัยกว่า แต่ใช้เวลา 4 ชั่วโมงในการรื้อถอนปรัชญา ...


0

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

ตัวอย่างเช่นฟังก์ชัน deserialisation อาจส่งคืน a Listแต่ไม่ทราบประเภทองค์ประกอบของรายการ ดังนั้นListประเภทผลตอบแทนที่เหมาะสมคือที่นี่


คุณสามารถใช้ได้ ? เป็นพารามิเตอร์ประเภท
Dániel Kis

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