อะไรคือความแตกต่างระหว่าง 'E', 'T' และ '?' สำหรับ Java generics?


261

ฉันเจอโค้ด Java เช่นนี้:

public interface Foo<E> {}

public interface Bar<T> {}

public interface Zar<?> {}

อะไรคือความแตกต่างระหว่างทั้งสามข้อข้างต้นและสิ่งที่เรียกว่าการประกาศคลาสหรืออินเตอร์เฟสแบบนี้ใน Java?


1
ฉันสงสัยว่ามีความแตกต่างใด ๆ ฉันเดาว่ามันเป็นเพียงชื่อของพารามิเตอร์ประเภทนั้น และเป็นคนสุดท้ายแม้ถูกต้อง?
CodesInChaos

คำตอบ:


231

ไม่มีความแตกต่างระหว่างสองคนแรกพวกเขาแค่ใช้ชื่อที่แตกต่างกันสำหรับพารามิเตอร์ประเภท ( EหรือT)

อันที่สามไม่ใช่การประกาศที่ถูกต้อง - ?ใช้เป็นไวด์การ์ดซึ่งใช้เมื่อแสดงอาร์กิวเมนต์ชนิดเช่นList<?> foo = ...หมายถึงที่fooอ้างถึงรายการบางประเภท แต่เราไม่รู้

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


1
ดูเหมือนว่าลิงก์ไปยัง PDF จะใช้งานไม่ได้ ฉันพบสิ่งที่ดูเหมือนจะเป็นสำเนาที่นี่แต่ฉันไม่สามารถมั่นใจได้ 100% เนื่องจากฉันไม่ทราบว่าต้นฉบับมีลักษณะอย่างไร
จอห์น

2
@John: ใช่นั่นคือสิ่งนั้น จะแก้ไขลิงค์ในไม่ว่าจะเป็นลิงค์ Oracle หรือ ...
Jon Skeet

มีอะไรนอกเหนือจาก T, E และ? ใช้ในยาชื่อสามัญ? ถ้าเป็นเช่นนั้นพวกเขาหมายถึงอะไรและหมายความว่าอย่างไร
sofs1 1

1
@ sofs1: ไม่มีอะไรพิเศษเกี่ยวกับTและE- เป็นเพียงตัวระบุ คุณสามารถเขียนKeyValuePair<K, V>เช่น ?มีความหมายพิเศษว่า
Jon Skeet

215

การประชุมมากกว่าสิ่งอื่นใด

  • T มีความหมายว่าเป็นประเภท
  • Eมีไว้เพื่อเป็นองค์ประกอบ ( List<E>: รายการองค์ประกอบ)
  • Kเป็นกุญแจ (ในMap<K,V>)
  • V คือค่า (เป็นค่าส่งคืนหรือค่าที่แม็พ)

พวกเขาสามารถใช้แทนกันได้อย่างสมบูรณ์ (ความขัดแย้งในการประกาศเดียวกันแม้จะมี)


20
ตัวอักษรระหว่าง <> เป็นเพียงชื่อ สิ่งที่คุณอธิบายในคำตอบเป็นเพียงแบบแผน มันไม่จำเป็นต้องเป็นตัวอักษรใหญ่และเล็ก คุณสามารถใช้ชื่อใดก็ได้ที่คุณชอบเหมือนกับที่คุณให้คลาสตัวแปร ฯลฯ ชื่อใดก็ได้ที่คุณชอบ
Jesper

คำอธิบายโดยละเอียดและชัดเจนยิ่งขึ้นมีอยู่ในบทความนี้oracle.com/technetwork/articles/java/…
fgul

6
คุณไม่ได้อธิบายเครื่องหมายคำถาม downvoted
shinzou

129

คำตอบก่อนหน้านี้อธิบายพารามิเตอร์ประเภท (T, E, ฯลฯ ) แต่อย่าอธิบายไวด์การ์ด "" "หรือความแตกต่างระหว่างพวกเขาดังนั้นฉันจะพูดถึงมัน

ก่อนอื่นต้องชัดเจน: พารามิเตอร์ wildcard และ type ไม่เหมือนกัน โดยที่พารามิเตอร์ type กำหนดประเภทของตัวแปร (เช่น T) ที่แสดงถึงชนิดของขอบเขต wildcard ไม่ได้: wildcard เพิ่งกำหนดชุดของประเภทที่อนุญาตที่คุณสามารถใช้สำหรับประเภททั่วไป หากไม่มีการ จำกัด ( extendsหรือsuper) สัญลักษณ์แทน "ใช้ประเภทใดก็ได้ที่นี่"

อักขระตัวแทนมาระหว่างวงเล็บมุมเสมอและมีความหมายเฉพาะในบริบทของประเภททั่วไป:

public void foo(List<?> listOfAnyType) {...}  // pass a List of any type

ไม่เคย

public <?> ? bar(? someType) {...}  // error. Must use type params here

หรือ

public class MyGeneric ? {      // error
    public ? getFoo() { ... }   // error
    ...
}

ทำให้เกิดความสับสนมากขึ้นเมื่อพวกเขาทับซ้อนกัน ตัวอย่างเช่น:

List<T> fooList;  // A list which will be of type T, when T is chosen.
                  // Requires T was defined above in this scope
List<?> barList;  // A list of some type, decided elsewhere. You can do
                  // this anywhere, no T required.

มีการทับซ้อนกันมากมายในสิ่งที่เป็นไปได้ด้วยคำจำกัดความของวิธีการ ต่อไปนี้คือใช้งานได้เหมือนกัน:

public <T> void foo(List<T> listOfT) {...}
public void bar(List<?> listOfSomething)  {...}

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

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

public class Foo <T extends Comparable<T> & Cloneable> {...}

อักขระตัวแทนสามารถมีขอบเขตที่ต่ำกว่า params ประเภทไม่สามารถ:

public void bar(List<? super Integer> list) {...}

ในด้านบนList<? super Integer>กำหนดIntegerเป็นขอบเขตล่างบนไวด์การ์ดหมายความว่าประเภทรายการจะต้องเป็นจำนวนเต็มหรือประเภทซุปเปอร์จำนวนเต็ม การแบ่งประเภททั่วไปนั้นเกินกว่าสิ่งที่ฉันต้องการกล่าวถึงในรายละเอียด ในระยะสั้นจะช่วยให้คุณสามารถกำหนดประเภททั่วไปประเภทที่สามารถ สิ่งนี้ทำให้มีความเป็นไปได้ที่จะรักษายาชื่อสามัญหลายรูปแบบ เช่นกับ:

public void foo(List<? extends Number> numbers) {...}

คุณสามารถผ่านList<Integer>, List<Float>, ฯลฯList<Byte> numbersหากไม่มีการกำหนดขอบเขตแบบนี้จะไม่ทำงาน - นั่นเป็นวิธีทั่วไป

สุดท้ายนี่คือคำนิยามของวิธีการที่ใช้ไวด์การ์ดในการทำสิ่งที่ฉันไม่คิดว่าคุณจะทำได้:

public static <T extends Number> void adder(T elem, List<? super Number> numberSuper) {
    numberSuper.add(elem);
}

numberSuperสามารถเป็น List of Number หรือ Supertype ใด ๆ ของ Number (เช่น, List<Object>) และelemต้องเป็น Number หรือชนิดย่อยใด ๆ ด้วยขอบเขตทั้งหมดคอมไพเลอร์สามารถมั่นใจได้ว่า.add()เป็นประเภทที่ปลอดภัย


"public void foo (รายการ <? ขยายหมายเลข> หมายเลข) {... }" ควร "ขยาย" เป็น "super"?
1a1a11a

1
ไม่จุดของตัวอย่างนั้นคือการแสดงลายเซ็นซึ่ง polymorphically สนับสนุนรายการของตัวเลขและประเภทย่อยของตัวเลข สำหรับสิ่งนี้คุณใช้ "การขยาย" นั่นคือ "ส่งรายการหมายเลขหรือสิ่งใดก็ตามที่ขยายหมายเลข" ให้กับฉัน (รายการ <Integer>, รายการ <Float> อะไรก็ตาม) วิธีการเช่นนี้อาจทำซ้ำผ่านรายการและสำหรับแต่ละองค์ประกอบ "e" ดำเนินการเช่น e.floatValue () ไม่สำคัญว่าคุณจะส่งผ่านประเภทย่อย (ส่วนขยาย) ใด - คุณจะสามารถ ".floatValue ()" ได้เสมอเนื่องจาก .floatValue () เป็นวิธีการของ Number
Hawkeye Parker

ในตัวอย่างสุดท้ายของคุณ "List <? super Number>" อาจเป็น "List <Number>" เนื่องจากวิธีการนี้ไม่อนุญาตให้มีอะไรที่ธรรมดากว่านี้อีก
jessarah

@ jessarah ไม่ บางทีตัวอย่างของฉันไม่ชัดเจน แต่ฉันพูดถึงในตัวอย่างที่ adder () สามารถรับ List <Object> (Object เป็น superclass ของ Number) หากคุณต้องการให้สามารถทำสิ่งนี้ได้จะต้องมีลายเซ็น "รายการ <? super Number>" นั่นคือจุดของ "super" ตรงนี้
Hawkeye Parker

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

27

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

ชื่อพารามิเตอร์ชนิดที่ใช้มากที่สุดคือ:

  • E - องค์ประกอบ (ใช้อย่างกว้างขวางโดย Java Collections Framework)
  • K - คีย์
  • N - จำนวน
  • T - ประเภท
  • V - ความคุ้มค่า

ใน Java 7 จะได้รับอนุญาตให้ยกตัวอย่างเช่นนี้:

Foo<String, Integer> foo = new Foo<>(); // Java 7
Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 6

3

ชื่อพารามิเตอร์ชนิดที่ใช้มากที่สุดคือ:

E - Element (used extensively by the Java Collections Framework)
K - Key
N - Number
T - Type
V - Value
S,U,V etc. - 2nd, 3rd, 4th types

คุณจะเห็นชื่อเหล่านี้ถูกใช้ทั่วทั้ง Java SE API


2

คอมไพเลอร์จะสร้างการจับภาพสำหรับสัญลักษณ์ตัวแทนแต่ละรายการ (เช่นเครื่องหมายคำถามในรายการ) เมื่อสร้างฟังก์ชันเช่น:

foo(List<?> list) {
    list.put(list.get()) // ERROR: capture and Object are not identical type.
}

อย่างไรก็ตามประเภททั่วไปเช่น V จะโอเคและทำให้เป็นวิธีการทั่วไป :

<V>void foo(List<V> list) {
    list.put(list.get())
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.