ฉันเจอโค้ด Java เช่นนี้:
public interface Foo<E> {}
public interface Bar<T> {}
public interface Zar<?> {}
อะไรคือความแตกต่างระหว่างทั้งสามข้อข้างต้นและสิ่งที่เรียกว่าการประกาศคลาสหรืออินเตอร์เฟสแบบนี้ใน Java?
ฉันเจอโค้ด Java เช่นนี้:
public interface Foo<E> {}
public interface Bar<T> {}
public interface Zar<?> {}
อะไรคือความแตกต่างระหว่างทั้งสามข้อข้างต้นและสิ่งที่เรียกว่าการประกาศคลาสหรืออินเตอร์เฟสแบบนี้ใน Java?
คำตอบ:
ไม่มีความแตกต่างระหว่างสองคนแรกพวกเขาแค่ใช้ชื่อที่แตกต่างกันสำหรับพารามิเตอร์ประเภท ( E
หรือT
)
อันที่สามไม่ใช่การประกาศที่ถูกต้อง - ?
ใช้เป็นไวด์การ์ดซึ่งใช้เมื่อแสดงอาร์กิวเมนต์ชนิดเช่นList<?> foo = ...
หมายถึงที่foo
อ้างถึงรายการบางประเภท แต่เราไม่รู้
ทั้งหมดนี้คือยาชื่อสามัญซึ่งเป็นหัวข้อที่ค่อนข้างใหญ่ คุณอาจต้องการที่จะเรียนรู้เกี่ยวกับมันผ่านแหล่งข้อมูลต่อไปนี้แม้ว่าจะมีหลักสูตรที่พร้อมให้บริการมากกว่านี้:
T
และE
- เป็นเพียงตัวระบุ คุณสามารถเขียนKeyValuePair<K, V>
เช่น ?
มีความหมายพิเศษว่า
การประชุมมากกว่าสิ่งอื่นใด
T
มีความหมายว่าเป็นประเภท E
มีไว้เพื่อเป็นองค์ประกอบ ( List<E>
: รายการองค์ประกอบ) K
เป็นกุญแจ (ในMap<K,V>
) V
คือค่า (เป็นค่าส่งคืนหรือค่าที่แม็พ) พวกเขาสามารถใช้แทนกันได้อย่างสมบูรณ์ (ความขัดแย้งในการประกาศเดียวกันแม้จะมี)
คำตอบก่อนหน้านี้อธิบายพารามิเตอร์ประเภท (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()
เป็นประเภทที่ปลอดภัย
ตัวแปรชนิด <T> สามารถเป็นชนิดที่ไม่ใช่แบบดั้งเดิมที่คุณระบุ: ชนิดคลาสใด ๆ , ประเภทอินเตอร์เฟสใด ๆ , ประเภทอาร์เรย์ใด ๆ หรือแม้กระทั่งตัวแปรชนิดอื่น
ชื่อพารามิเตอร์ชนิดที่ใช้มากที่สุดคือ:
ใน Java 7 จะได้รับอนุญาตให้ยกตัวอย่างเช่นนี้:
Foo<String, Integer> foo = new Foo<>(); // Java 7
Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 6
ชื่อพารามิเตอร์ชนิดที่ใช้มากที่สุดคือ:
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
คอมไพเลอร์จะสร้างการจับภาพสำหรับสัญลักษณ์ตัวแทนแต่ละรายการ (เช่นเครื่องหมายคำถามในรายการ) เมื่อสร้างฟังก์ชันเช่น:
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())
}