ความแตกต่างระหว่าง List, List <?>, List <T>, List <E> และ List <Object>


194

อะไรคือความแตกต่างระหว่างList, List<?>, List<T>, List<E>และList<Object>?

1. รายการ

List: typesafeเป็นชนิดดิบดังนั้นจึงไม่ มันจะสร้างข้อผิดพลาดรันไทม์เฉพาะเมื่อแคสต์ไม่ดี เราต้องการข้อผิดพลาดในการรวบรวมเวลาเมื่อนักแสดงไม่ดี ไม่แนะนำให้ใช้

2. รายการ <?>

List<?>เป็นอักขระตัวแทนที่ไม่มีขีด จำกัด แต่ฉันไม่แน่ใจว่ามันมีไว้เพื่ออะไร? ฉันสามารถพิมพ์ได้List<?>โดยไม่มีปัญหา:

public static void test(List<?> list){
    System.out.println(list);   // Works
}

ทำไมฉันไม่สามารถเพิ่มรายการลงใน a List<?>?

public static void test(List<?> list){
    list.add(new Long(2));     // Error
    list.add("2");             // Error
    System.out.println(list);
}

3. รายการ <T>

public static void test(List<T> list){   // T cannot be resolved
    System.out.println(list);
}

ฉันไม่เข้าใจไวยากรณ์นี้ ฉันเห็นบางสิ่งเช่นนี้และใช้งานได้:

public <T> T[] toArray(T[] a){
    return a;   
}

บางครั้งผมเห็น<T>หรือ<E>หรือ,<U> <T,E>พวกเขาเหมือนกันทั้งหมดหรือพวกเขาเป็นตัวแทนของสิ่งที่แตกต่างกันอย่างไร

4. รายการ <Object>

สิ่งนี้ทำให้เกิดข้อผิดพลาด "วิธีtest(List<Object>)นี้ใช้ไม่ได้กับการโต้แย้งList<String>":

public static void test(List<Object> list){
    System.out.println(list);
}

ถ้าฉันลองทำเช่นนี้ฉันจะได้ "ไม่สามารถส่งจากList<String>ไปยังList<Object>":

test((List<Object>) names);

ฉันสับสน. Stringเป็น subclass ของObjectดังนั้นทำไมไม่ได้เป็นList<String>subclass ของList<Object>?

คำตอบ:


77

1) ถูกต้อง

2) คุณสามารถนึกได้ว่าเป็นรายการ "อ่านอย่างเดียว" ซึ่งคุณไม่สนใจเกี่ยวกับประเภทของรายการเช่นสามารถใช้โดยวิธีการที่ส่งคืนความยาวของรายการ

3) T, E และ U เหมือนกัน แต่ผู้คนมักจะใช้เช่น T สำหรับประเภท, E สำหรับองค์ประกอบ, V สำหรับค่าและ K สำหรับคีย์ วิธีการที่คอมไพล์บอกว่ามันใช้อาร์เรย์ของบางชนิดและส่งกลับอาร์เรย์ของชนิดเดียวกัน

4) คุณไม่สามารถผสมส้มและแอปเปิ้ลได้ คุณจะสามารถเพิ่มวัตถุไปยังรายการสตริงของคุณถ้าคุณสามารถส่งรายการสตริงไปยังวิธีการที่คาดว่ารายการวัตถุ (และไม่ใช่วัตถุทั้งหมดที่เป็นสตริง)


2
+1 2สำหรับรายการเท่านั้นอ่านต่อ 2ผมเขียนรหัสบางอย่างที่จะแสดงให้เห็นถึงนี้ tyvm
Thang Pham

ทำไมผู้คนถึงใช้List<Object>เพื่อ?
Thang Pham

3
เป็นวิธีการสร้างรายการที่ยอมรับรายการประเภทใด ๆ ที่คุณไม่ค่อยได้ใช้
Kaj

จริงๆแล้วฉันไม่คิดว่าจะมีใครใช้เพราะคุณไม่สามารถมีตัวระบุได้เลย
if_zero_equals_one

1
@if_zero_equals_one ใช่ แต่คุณจะได้รับคำเตือนคอมไพเลอร์ (มันจะเตือนและบอกว่าคุณกำลังใช้ประเภท raw) และคุณไม่ต้องการรวบรวมรหัสของคุณด้วยคำเตือน
Kaj

26

สำหรับส่วนสุดท้าย: แม้ว่า String จะเป็นชุดย่อยของ Object แต่ List <String> ไม่ได้รับการสืบทอดจาก List <Object>


11
จุดที่ดีมาก; หลายคนคิดว่าเพราะคลาส C สืบทอดมาจากคลาส P รายการนั้น <C> นั้นก็สืบทอดมาจากรายการ <P> ในขณะที่คุณชี้ให้เห็นว่านี่ไม่ใช่กรณี สาเหตุที่เป็นเพราะถ้าเราสามารถส่งจาก List <String> ไปที่ List <Object> จากนั้นเราสามารถใส่ Objects ลงในรายการนั้นดังนั้นจึงเป็นการละเมิดสัญญาดั้งเดิมของ List <String> เมื่อพยายามดึงองค์ประกอบ
ปีเตอร์

2
+1 จุดดีเช่นกัน แล้วทำไมผู้คนถึงใช้List<Object>?
Thang Pham

9
List <Object> สามารถใช้เพื่อเก็บรายการของออบเจ็กต์ของคลาสที่แตกต่างกัน
Farshid Zaker

20

สัญกรณ์List<?>หมายถึง "รายการของบางสิ่ง (แต่ฉันไม่ได้พูดอะไร)" เนื่องจากรหัสที่ใช้testงานได้กับวัตถุชนิดใดก็ได้ในรายการจึงเป็นพารามิเตอร์วิธีการที่เป็นทางการ

ใช้พารามิเตอร์ประเภท (เช่นในจุดที่ 3 ของคุณ) ต้องการให้ประกาศพารามิเตอร์ประเภท ไวยากรณ์ของ Java สำหรับที่จะใส่<T>ด้านหน้าของฟังก์ชั่น นี่คล้ายกับการประกาศชื่อพารามิเตอร์อย่างเป็นทางการกับวิธีการก่อนที่จะใช้ชื่อในส่วนเนื้อหา

เกี่ยวกับการList<Object>ไม่ยอมรับList<String>ที่จะทำให้ความรู้สึกเพราะStringไม่ได้Object; มันเป็น subclass Objectของ public static void test(List<? extends Object> set) ...การแก้ไขคือการประกาศ แต่แล้วซ้ำซ้อนเพราะทุกระดับโดยตรงหรือโดยอ้อมขยายextends ObjectObject


ทำไมผู้คนถึงใช้List<Object>เพื่อ?
Thang Pham

10
ฉันคิดว่า "รายการของบางสิ่งบางอย่าง" มีความหมายที่ดีกว่าList<?>เนื่องจากรายการมีประเภทเฉพาะ แต่ไม่ทราบ List<Object>จะเป็น "รายการอะไรก็ได้" เพราะมันสามารถมีอะไรก็ได้
ColinD

1
@ ColinD - ฉันหมายถึง "อะไร" ในแง่ของ "สิ่งใดสิ่งหนึ่ง" แต่คุณพูดถูก มันหมายถึง "รายชื่อบางอย่าง แต่ฉันจะไม่บอกคุณว่า"
Ted Hopp

@ColinD เขาหมายถึงว่าทำไมคุณถึงพูดคำซ้ำอีกครั้ง? ใช่มันถูกเขียนด้วยคำที่แตกต่างกันเล็กน้อย แต่ความหมายเหมือนกัน ...
user25

14

เหตุผลที่คุณไม่สามารถลงList<String>ไปList<Object>ก็คือว่ามันจะช่วยให้คุณละเมิดข้อ จำกัด List<String>ของ

คิดเกี่ยวกับสถานการณ์ต่อไปนี้: ถ้าฉันมีก็ควรจะมีเพียงวัตถุชนิดList<String> String(ซึ่งเป็นfinalชั้นเรียน)

ถ้าฉันสามารถโยนว่าไปList<Object>แล้วที่ช่วยให้ฉันเพื่อเพิ่มรายการที่จึงละเมิดสัญญาเดิมของObjectList<String>

ดังนั้นในทั่วไปถ้าระดับCสืบทอดจากคลาสPคุณไม่สามารถพูดได้ว่ายังสืบทอดมาจากGenericType<C>GenericType<P>

NB ฉันได้แสดงความคิดเห็นเกี่ยวกับเรื่องนี้ในคำตอบก่อนหน้านี้ แต่ต้องการที่จะขยายมัน


ใช่ฉันอัปโหลดทั้งความคิดเห็นและคำตอบของคุณเนื่องจากเป็นคำอธิบายที่ดีมาก ตอนนี้ที่ไหนและทำไมคนจะใช้List<Object>?
Thang Pham

3
โดยทั่วไปคุณไม่ควรใช้List<Object>เพราะมันเอาชนะจุดประสงค์ของยาชื่อสามัญ อย่างไรก็ตามมีบางกรณีที่รหัสเก่าอาจมีการListยอมรับประเภทที่แตกต่างกันดังนั้นคุณอาจต้องการติดตั้งรหัสใหม่เพื่อใช้การกำหนดพารามิเตอร์ประเภทเพื่อหลีกเลี่ยงคำเตือนคอมไพเลอร์สำหรับประเภทดิบ (แต่ฟังก์ชั่นจะไม่เปลี่ยนแปลง)
Peter


5

ให้เราพูดถึงพวกเขาในบริบทของประวัติศาสตร์ Java;

  1. List:

รายการหมายความว่าสามารถรวมวัตถุใดก็ได้ รายการอยู่ในรุ่นก่อนหน้า Java 5.0; รายการแนะนำ Java 5.0 สำหรับความเข้ากันได้แบบย้อนหลัง

List list=new  ArrayList();
list.add(anyObject);
  1. List<?>:

?หมายถึงวัตถุที่ไม่รู้จักไม่ใช่วัตถุใด ๆ การ?แนะนำสัญลักษณ์ตัวแทนใช้สำหรับการแก้ปัญหาที่สร้างโดย Generic Type; เห็นสัญลักษณ์ ; แต่สิ่งนี้ยังทำให้เกิดปัญหาอื่น:

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error
  1. List< T> List< E>

หมายถึงการประกาศทั่วไปที่สถานที่ตั้งของประเภท T หรือ E ใน Lib โครงการของคุณ

  1. List< Object> หมายถึงการแปรสภาพทั่วไป

5

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

public class FooHandler<T>
{
   public void operateOnFoo(T foo) { /*some foo handling code here*/}

}

คุณกำลังบอกว่าFooHandler's operateOnFooวิธีการที่คาดว่าตัวแปรประเภท "T" ซึ่งมีการประกาศในการประกาศชั้นเรียนด้วยตัวเองในใจคุณสามารถเพิ่มวิธีอื่นในภายหลังเช่น

public void operateOnFoos(List<T> foos)

ในทุกกรณีทั้ง T, E หรือ U มีตัวระบุทั้งหมดของพารามิเตอร์ประเภทคุณสามารถมีพารามิเตอร์ประเภทมากกว่าหนึ่งซึ่งใช้ไวยากรณ์

public class MyClass<Atype,AnotherType> {}

ใน ponint ที่คุณมาถึงแม้ว่า efectively Sting เป็นประเภทย่อยของวัตถุในคลาสทั่วไปไม่มีความสัมพันธ์ดังกล่าวList<String>ไม่ใช่ประเภทย่อยของList<Object>พวกเขาเป็นสองประเภทที่แตกต่างจากมุมมองของคอมไพเลอร์นี่เป็นคำอธิบายที่ดีที่สุดในบล็อกนี้


5

ทฤษฎี

String[] สามารถร่ายไปได้ Object[]

แต่

List<String>List<Object>ไม่สามารถโยนไป

การปฏิบัติ

สำหรับรายการจะละเอียดกว่านั้นเพราะในเวลารวบรวมประเภทของพารามิเตอร์ List ที่ส่งไปยังเมธอดจะไม่ถูกตรวจสอบ นิยามของเมธอดเช่นกันอาจบอกได้ว่าList<?>- จากมุมมองของคอมไพเลอร์มันเทียบเท่า นี่คือเหตุผลที่ตัวอย่าง # 2 ของ OP ให้ข้อผิดพลาดรันไทม์ไม่ได้รวบรวมข้อผิดพลาด

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

A. ดังนั้นรหัสนี้จะไม่ให้ข้อผิดพลาดในการคอมไพล์หรือรันไทม์และจริง ๆ แล้ว (และอาจจะแปลกใจใช่ไหม):

public static void main(String[] args) {
    List argsList = new ArrayList<String>();
    argsList.addAll(Arrays.asList(args));
    test(argsList);  // The object passed here is a List<String>
}

public static void test(List<Object> set) {
    List<Object> params = new ArrayList<>();  // This is a List<Object>
    params.addAll(set);       // Each String in set can be added to List<Object>
    params.add(new Long(2));  // A Long can be added to List<Object>
    System.out.println(params);
}

B. รหัสนี้จะให้ข้อผิดพลาด runtime:

public static void main(String[] args) {
    List argsList = new ArrayList<String>();
    argsList.addAll(Arrays.asList(args));
    test1(argsList);
    test2(argsList);
}

public static void test1(List<Object> set) {
    List<Object> params = set;  // Surprise!  Runtime error
}

public static void test2(List<Object> set) {
    set.add(new Long(2));       // Also a runtime error
}

C. รหัสนี้จะให้ข้อผิดพลาด runtime ( java.lang.ArrayStoreException: java.util.Collections$UnmodifiableRandomAccessList Object[]):

public static void main(String[] args) {
    test(args);
}

public static void test(Object[] set) {
    Object[] params = set;    // This is OK even at runtime
    params[0] = new Long(2);  // Surprise!  Runtime error
}

ใน B, พารามิเตอร์setไม่ได้เป็นพิมพ์Listที่รวบรวมเวลา: List<?>คอมไพเลอร์เห็นว่ามันเป็น มีข้อผิดพลาด runtime เป็นเพราะที่รันไทม์setจะกลายเป็นวัตถุที่เกิดขึ้นจริงผ่านจากและที่เป็นmain() List<String>A List<String>ไม่สามารถร่ายList<Object>ได้

ใน C พารามิเตอร์ต้องมีset Object[]ไม่มีข้อผิดพลาดในการรวบรวมและไม่มีข้อผิดพลาดรันไทม์เมื่อมันถูกเรียกด้วยString[]วัตถุเป็นพารามิเตอร์ นั่นเป็นเพราะบรรยากาศในการString[] Object[]แต่วัตถุที่แท้จริงที่ได้รับโดยtest()ยังคงเป็นString[]มันไม่ได้เปลี่ยนแปลง ดังนั้นวัตถุยังกลายเป็นparams String[]และองค์ประกอบ 0 ของ a String[]ไม่สามารถกำหนดให้Long!

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


ฉันลองตัวอย่างของคุณแล้วมันไม่ทำงาน: List<Object> cannot be applied to List<String>. คุณไม่สามารถส่งผ่านไปยังวิธีการที่คาดว่าArrayList<String> ArrayList<Object>
parsecer

ขอบคุณค่อนข้างช้าในวันที่ฉันมีตัวอย่าง tweaked เพื่อให้มันทำงานตอนนี้ การเปลี่ยนแปลงหลักคือการกำหนด argsList โดยทั่วไปใน main ()
radfast

4

ปัญหาที่ 2 ตกลงเนื่องจาก "System.out.println (set);" หมายถึง "System.out.println (set.toString ());" set เป็นตัวอย่างของ List ดังนั้น complier จะเรียกใช้ List.toString ();

public static void test(List<?> set){
set.add(new Long(2)); //--> Error  
set.add("2");    //--> Error
System.out.println(set);
} 
Element ? will not promise Long and String, so complier will  not accept Long and String Object

public static void test(List<String> set){
set.add(new Long(2)); //--> Error
set.add("2");    //--> Work
System.out.println(set);
}
Element String promise it a String, so complier will accept String Object

ปัญหาที่ 3: สัญลักษณ์เหล่านี้เหมือนกัน แต่คุณสามารถให้ข้อกำหนดที่แตกต่างกันได้ ตัวอย่างเช่น:

public <T extends Integer,E extends String> void p(T t, E e) {}

ปัญหาที่ 4: คอลเลกชันไม่อนุญาตให้พิมพ์พารามิเตอร์ความแปรปรวนร่วม แต่อาเรย์จะอนุญาตความแปรปรวนร่วม


0

คุณพูดถูก: สตริงเป็นส่วนย่อยของวัตถุ เนื่องจาก String มีความ "แม่นยำ" มากกว่า Object คุณควรใช้เพื่อใช้เป็นอาร์กิวเมนต์สำหรับ System.out.println ()

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