คำถาม:
- Java คืออะไรชนิดใดบ้างและทำไมฉันมักได้ยินว่าไม่ควรใช้รหัสใหม่
- อะไรคือทางเลือกถ้าเราไม่สามารถใช้ประเภทดิบและมันจะดีกว่า?
คำตอบ:
ข้อกำหนดภาษา Java กำหนดประเภท rawดังต่อไปนี้:
ประเภทดิบถูกกำหนดให้เป็นหนึ่งใน:
ชนิดการอ้างอิงที่เกิดขึ้นโดยการใช้ชื่อของการประกาศชนิดทั่วไปโดยไม่มีรายการอาร์กิวเมนต์ชนิดประกอบ
ประเภทอาร์เรย์ที่มีประเภทองค์ประกอบเป็นประเภทดิบ
ไม่ใช่
static
ประเภทสมาชิกของประเภทดิบR
ที่ไม่ได้รับการถ่ายทอดจาก superclass หรือ superinterfaceR
ของ
นี่คือตัวอย่างที่แสดงให้เห็น:
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
ตอนนี้เราทำงานเป็นปัญหาที่ใช้เวลาเพราะมีอะไรบางอย่างที่ไม่ได้เป็นnames
instanceof 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
และรหัสข้างต้นจะรวบรวม
<Object>
เป็นพารามิเตอร์ชนิดอย่างไรต่อไปนี้เป็นข้อความจากEffective Java 2nd Edition รายการ 23: อย่าใช้ประเภท raw ในรหัสใหม่ :
ความแตกต่างระหว่างประเภท raw
List
และประเภทที่กำหนดพารามิเตอร์List<Object>
คืออะไร ก่อนหน้านี้พูดอย่างหลวม ๆ ได้ยกเลิกการตรวจสอบประเภททั่วไปในขณะที่คนหลังบอกกับคอมไพเลอร์อย่างชัดเจนว่ามันสามารถถือวัตถุประเภทใดก็ได้ ขณะที่คุณสามารถส่งผ่านList<String>
ไปยังพารามิเตอร์ของชนิดคุณจะไม่สามารถผ่านมันไปยังพารามิเตอร์ของชนิดList
List<Object>
มีกฎระเบียบ Subtyping สำหรับยาชื่อสามัญที่มีและList<String>
เป็นชนิดย่อยของชนิดดิบแต่ไม่ใช่ชนิดแปร,List
List<Object>
เป็นผลให้คุณสูญเสียความปลอดภัยประเภทถ้าคุณใช้ชนิดดิบเหมือนList
List<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
<?>
เป็นพารามิเตอร์ชนิดอย่างไรList<Object>
, List<String>
ฯลฯ ล้วนList<?>
แต่เป็นที่น่าดึงดูดใจที่จะบอกว่าพวกเขาเป็นเพียงคนList
เดียว แต่มีความแตกต่างที่สำคัญ: ตั้งแต่List<E>
กำหนดเท่านั้นadd(E)
คุณจะไม่สามารถเพิ่มเพียงวัตถุใด ๆ List<?>
โดยพลไป บนมืออื่น ๆ ตั้งแต่ประเภทดิบList
ไม่ได้มีความปลอดภัยชนิดที่คุณสามารถเพียงเกี่ยวกับอะไรไปadd
List
พิจารณารูปแบบต่อไปนี้ของตัวอย่างก่อนหน้า:
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 list
List<String> names
กลับไปที่ JLS 4.8:
เป็นไปได้ที่จะใช้เป็นประเภทการลบประเภทพารามิเตอร์หรือการลบประเภทอาร์เรย์ที่มีประเภทองค์ประกอบเป็นประเภทพารามิเตอร์ ประเภทดังกล่าวจะเรียกว่าเป็นชนิดดิบ
[ ... ]
ซูเปอร์คลาส (ตามลำดับซูเปอร์อินเทอร์เฟซ) ของชนิด raw คือการลบของซูเปอร์คลาส (superinterfaces) ของการกำหนดพารามิเตอร์ประเภททั่วไป
ประเภทของคอนสตรัค, วิธีการเช่นหรือไม่ใช่
static
ข้อมูลประเภทดิบC
ที่ไม่ได้รับมรดกมาจาก superclasses หรือ superinterfacesC
มันเป็นชนิดดิบที่สอดคล้องกับการลบของชนิดในการประกาศทั่วไปที่สอดคล้องกับ
ในแง่ที่ง่ายขึ้นเมื่อใช้ประเภทดิบตัวสร้างวิธีการอินสแตนซ์และ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
เป็นลายเซ็นประกอบด้วยชื่อเดียวกับชื่อและตัวหนังสือของทุกชนิดของพารามิเตอร์อย่างเป็นทางการได้รับในs
s
ประเภทผลตอบแทนของวิธีการและพารามิเตอร์ประเภทของวิธีการทั่วไปหรือตัวสร้างยังได้รับการลบหากลายเซ็นของวิธีการหรือตัวสร้างจะถูกลบ
การลบลายเซ็นของวิธีการทั่วไปไม่มีพารามิเตอร์ชนิด
รายงานข้อผิดพลาดต่อไปนี้มีความคิดบางอย่างจาก 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>
o instanceof Set<?>
ได้รับอนุญาตให้หลีกเลี่ยงชนิด raw (แม้ว่าจะเป็นเพียงผิวเผินในกรณีนี้)
n
remote beans สำหรับแต่ละคลาสที่นำไปใช้งานด้วยรหัสที่เหมือนกัน
TypeName.class
ที่TypeName
เป็นตัวระบุธรรมดา ( jls ) พูดตามสมมุติฉันเดาว่ามันอาจเป็นได้ทั้ง อาจเป็นเบาะแสList<String>.class
เป็นตัวแปรที่ JLS เรียกว่าข้อผิดพลาดของคอมไพเลอร์โดยเฉพาะดังนั้นหากพวกเขาเคยเพิ่มลงในภาษาที่ฉันคาดหวังว่าเป็นสิ่งที่พวกเขาใช้
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
จะต้องถูกร่ายก่อนที่จะสามารถใช้งานได้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
และส่งต่อไปยังส่วนแรกหลังจากส่งมันออกมา
อะไรคือทางเลือกสำหรับประเภทดิบ: ใช้ยาสามัญ
ชนิด 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
ช่วยให้คุณObject
s สำหรับความเข้ากันได้แบบย้อนหลังการกำหนดประเภทพารามิเตอร์ให้เป็นประเภท 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
ประเภท "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 เป็นสิ่งที่สามารถตรวจสอบได้ในเวลารวบรวม
?
ยังคงใช้ข้อเสนอความปลอดภัยประเภท ฉันครอบคลุมในคำตอบของฉัน
private static List<String> list = new ArrayList<String>();
คุณควรระบุพารามิเตอร์ประเภท
คำเตือนแนะนำว่าควรกำหนดพารามิเตอร์ประเภทที่กำหนดไว้เพื่อรองรับข้อมูลทั่วไปแทนที่จะใช้รูปแบบดิบ
List
ถูกกำหนดให้รองรับข้อมูลทั่วไป: public class List<E>
. ซึ่งช่วยให้การดำเนินงานที่ปลอดภัยประเภทมากมายที่มีการตรวจสอบรวบรวมเวลา
private static List<String> list = new ArrayList<>();
ประเภท 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
polygenelubricants
การอ้างอิง "raw type" จากstackoverflow.com/questions/2770111/…ไปยังคำตอบของฉันเอง แต่ฉันคิดว่าฉันจะทิ้งไว้เพื่อใช้ในคำตอบของเขา / เธอ
คอมไพเลอร์ต้องการให้คุณเขียนสิ่งนี้:
private static List<String> list = new ArrayList<String>();
เพราะมิฉะนั้นคุณสามารถเพิ่มประเภทใดก็ได้ที่คุณชอบlist
ทำให้อินสแตนซ์new ArrayList<String>()
ไม่มีจุดหมาย Java generics เป็นคุณสมบัติการคอมไพล์เวลาเท่านั้นดังนั้นวัตถุที่สร้างด้วยnew ArrayList<String>()
จะยอมรับInteger
หรือJFrame
องค์ประกอบอย่างมีความสุขหากได้รับมอบหมายให้อ้างอิง "raw type" List
- ตัววัตถุเองไม่รู้อะไรเกี่ยวกับประเภทที่ควรจะมีเฉพาะคอมไพเลอร์เท่านั้น
ที่นี่ฉันกำลังพิจารณาหลายกรณีซึ่งคุณสามารถอธิบายแนวคิดได้
1. ArrayList<String> arr = new ArrayList<String>();
2. ArrayList<String> arr = new ArrayList();
3. ArrayList arr = new ArrayList<String>();
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)
ในกรณี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
ชนิดของวัตถุมีการอ้างอิงไปยังประเภทอ้างอิงตัวแปรของStrict
ArrayList
ในกรณีArrayList arr
นี้เป็นประเภทดิบ แต่วัตถุของคุณnew ArrayList<String>();
เป็นประเภทที่เข้มงวด
arr.add("hello");
arr.add(23); //compiles fine but raise the warning.
มันจะเพิ่มประเภทของวัตถุใด ๆ ลงไปเพราะมันarr
เป็นประเภทที่ดิบ
คำเตือน : -
Strict
วัตถุประเภทมีการraw
อ้างอิงถึงตัวแปรอ้างอิงประเภท
ดิบประเภทคือการขาดของพารามิเตอร์ชนิดเมื่อใช้ประเภททั่วไป
วัตถุดิบชนิดไม่ควรใช้เพราะอาจก่อให้เกิดข้อผิดพลาด runtime เช่นการใส่double
ลงไปในสิ่งที่ควรจะเป็นSet
ของint
s
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.
นี่คืออีกกรณีที่ประเภทดิบจะกัดคุณ:
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
)
สิ่งที่พูดคือคุณlist
เป็นList
ของวัตถุที่ไม่ได้รับการรับรอง นั่นคือ Java ไม่ทราบว่ามีวัตถุประเภทใดในรายการ จากนั้นเมื่อคุณต้องการย้ำรายการคุณจะต้องโยนทุกองค์ประกอบเพื่อให้สามารถเข้าถึงคุณสมบัติขององค์ประกอบนั้น (ในกรณีนี้คือ String)
โดยทั่วไปเป็นความคิดที่ดีกว่าในการจำกัดความคอลเลกชันดังนั้นคุณจึงไม่มีปัญหาการแปลงคุณจะสามารถเพิ่มองค์ประกอบประเภท parametrized และตัวแก้ไขของคุณจะเสนอวิธีการที่เหมาะสมในการเลือก
private static List<String> list = new ArrayList<String>();
ชนิด raw คือชื่อของคลาสหรืออินเตอร์เฟสทั่วไปโดยไม่มีอาร์กิวเมนต์ชนิดใด ๆ ตัวอย่างเช่นกำหนดคลาส Box ทั่วไป:
public class Box<T> {
public void set(T t) { /* ... */ }
// ...
}
ในการสร้างชนิดที่กำหนดพารามิเตอร์ของ Box คุณต้องระบุอาร์กิวเมนต์ประเภทที่เกิดขึ้นจริงสำหรับพารามิเตอร์ประเภทที่เป็นทางการ T:
Box<Integer> intBox = new Box<>();
หากไม่ได้ระบุอาร์กิวเมนต์ประเภทจริงคุณจะสร้างกล่องประเภท raw:
Box rawBox = new Box();
หลีกเลี่ยงประเภทดิบ
ประเภทดิบหมายถึงการใช้ประเภททั่วไปโดยไม่ระบุพารามิเตอร์ประเภท
ตัวอย่างเช่น ,
รายการเป็นชนิด 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
ฉันพบหน้านี้หลังจากทำแบบฝึกหัดตัวอย่างและมีการไขปริศนาที่เหมือนกัน
============== ฉันไปจากรหัสนี้ตามตัวอย่างที่ให้ไว้ ===============
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 ชั่วโมงในการรื้อถอนปรัชญา ...
ประเภทดิบนั้นใช้ได้เมื่อพวกเขาแสดงสิ่งที่คุณต้องการแสดง
ตัวอย่างเช่นฟังก์ชัน deserialisation อาจส่งคืน a List
แต่ไม่ทราบประเภทองค์ประกอบของรายการ ดังนั้นList
ประเภทผลตอบแทนที่เหมาะสมคือที่นี่