ความแตกต่างระหว่าง <คืออะไร ขยายฐาน> และ <T ขยายฐาน>?


29

ในตัวอย่างนี้:

import java.util.*;

public class Example {
    static void doesntCompile(Map<Integer, List<? extends Number>> map) {}
    static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

    static void function(List<? extends Number> outer)
    {
        doesntCompile(new HashMap<Integer, List<Integer>>());
        compiles(new HashMap<Integer, List<Integer>>());
    }
}

doesntCompile() ไม่สามารถคอมไพล์ด้วย:

Example.java:9: error: incompatible types: HashMap<Integer,List<Integer>> cannot be converted to Map<Integer,List<? extends Number>>
        doesntCompile(new HashMap<Integer, List<Integer>>());
                      ^

ขณะcompiles()ที่คอมไพเลอร์ยอมรับ

คำตอบนี้อธิบายว่าความแตกต่างเพียงอย่างเดียวคือไม่เหมือน<? ...>กัน<T ...>ให้คุณอ้างอิงประเภทในภายหลังซึ่งดูเหมือนจะไม่เป็นเช่นนั้น

ความแตกต่างระหว่าง<? extends Number>และ<T extends Number>ในกรณีนี้คืออะไรและทำไมไม่รวบรวมครั้งแรก


ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
Samuel Liew

[1] ประเภท Java ทั่วไป: ความแตกต่างระหว่างรายการ <? ขยายหมายเลข> และรายการ <T ขยายหมายเลข>ดูเหมือนจะถามคำถามเดียวกัน แต่ในขณะที่มันอาจเป็นที่สนใจ [2] แม้ว่าคำถามนี้จะเป็นคำถามที่ดี แต่ชื่อเรื่องก็ไม่ได้สะท้อนถึงคำถามที่ถูกถามในประโยคสุดท้าย
skomisa

นี่เป็นคำตอบที่นี่แล้วJava Generic List <List <? ขยายจำนวน >>
M Qunh QuyếtNguyễn

แล้วคำอธิบายที่นี่ล่ะ?
jrook

คำตอบ:


14

โดยการกำหนดวิธีการที่มีลายเซ็นต่อไปนี้:

static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

และกล่าวอ้างเหมือน:

compiles(new HashMap<Integer, List<Integer>>());

ใน jls §8.1.2เราพบว่า (ส่วนที่น่าสนใจเป็นตัวหนาโดยฉัน):

ประกาศคลาทั่วไปกำหนดชุดของประเภทแปร (§4.5) หนึ่งสำหรับแต่ละภาวนาเป็นไปได้ของส่วนประเภทพารามิเตอร์โดยข้อโต้แย้งประเภท ประเภทพารามิเตอร์ทั้งหมดเหล่านี้ใช้คลาสเดียวกัน ณ เวลาทำงาน

ในคำอื่น ๆ ชนิดถูกจับคู่กับประเภทของการป้อนข้อมูลและได้รับมอบหมายT ลายเซ็นจะกลายเป็นได้อย่างมีประสิทธิภาพIntegerstatic void compiles(Map<Integer, List<Integer>> map)

เมื่อพูดถึงdoesntCompileวิธีการ jls จะกำหนดกฎของการพิมพ์ย่อย ( §4.5.1 , เป็นตัวหนาโดยฉัน):

อาร์กิวเมนต์ชนิด T1 ถูกกล่าวว่ามีอาร์กิวเมนต์ชนิดอื่น T2 ซึ่งเขียน T2 <= T1 หากชุดของประเภทที่แสดงด้วย T2 นั้นเป็นเซตย่อยของชุดประเภทที่แสดงด้วย T1 ภายใต้การปิดแบบสะท้อนและการสลับของกฎต่อไปนี้ ( โดยที่ <: หมายถึงการพิมพ์ย่อย (§4.10)):

  • ? ขยาย T <=? ขยาย S ถ้า T <: S

  • ? ขยาย T <=?

  • ? Super T <=? super S ถ้า S <: T

  • ? Super T <=?

  • ? Super T <=? ขยายวัตถุ

  • T <= T

  • T <=? ยืด T

  • T <=? ซุปเปอร์ที

ซึ่งหมายความว่าที่? extends NumberจริงมีIntegerหรือแม้กระทั่งList<? extends Number>มีList<Integer>แต่มันไม่ใช่กรณีและMap<Integer, List<? extends Number>> Map<Integer, List<Integer>>ข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อนั้นสามารถพบได้ในเธรด SOนี้ คุณยังสามารถสร้างเวอร์ชันด้วย?wildcard ได้โดยประกาศว่าคุณคาดว่าจะมีประเภทย่อยของList<? extends Number>:

public class Example {
    // now it compiles
    static void doesntCompile(Map<Integer, ? extends List<? extends Number>> map) {}
    static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

    public static void main(String[] args) {
        doesntCompile(new HashMap<Integer, List<Integer>>());
        compiles(new HashMap<Integer, List<Integer>>());
    }
}

[1] ผมคิดว่าคุณหมายมากกว่า? extends Number ? extends Numeric[2] การยืนยันของคุณว่า"ไม่ใช่สำหรับรายการ <? ขยายหมายเลข> และรายการ <Integer>"ไม่ถูกต้อง ในฐานะที่เป็น @VinceEmigh ได้ชี้ให้เห็นแล้วคุณสามารถสร้างวิธีการstatic void demo(List<? extends Number> lst) { }และเรียกมันว่าสิ่งนี้demo(new ArrayList<Integer>());หรือสิ่งนี้demo(new ArrayList<Float>());และรหัสรวบรวมและเรียกใช้ตกลง หรือฉันอาจจะเข้าใจผิดหรือเข้าใจผิดในสิ่งที่คุณพูด?
skomisa

@ คุณพูดถูกทั้งสองกรณี เมื่อพูดถึงประเด็นที่สองของคุณฉันเขียนมันด้วยวิธีที่ทำให้เข้าใจผิด ฉันหมายถึงList<? extends Number>เป็นพารามิเตอร์ประเภทของแผนที่ทั้งหมดไม่ใช่ของตัวเอง ขอบคุณมากสำหรับความคิดเห็น
Andronicus

@skomisa จากเหตุผลเดียวกันไม่ได้มีList<Number> สมมติว่าคุณมีฟังก์ชั่นList<Integer> static void check(List<Number> numbers) {}เมื่อกล่าวอ้างกับมันไม่ได้รวบรวมคุณต้องกำหนดวิธีการที่เป็นcheck(new ArrayList<Integer>()); static void check(List<? extends Number> numbers) {}แผนที่มันเหมือนกัน แต่มีการซ้อนมากกว่า
Andronicus

1
@skomisa เช่นเดียวกับNumberเป็นพารามิเตอร์ประเภทของรายการและคุณต้องเพิ่ม? extendsเพื่อให้ covariant List<? extends Number>เป็นพารามิเตอร์ประเภทของMapและยังต้องการความ? extendsแปรปรวนร่วม
Andronicus

1
ตกลง. เนื่องจากคุณให้โซลูชันสำหรับ wildcard หลายระดับ (aka "wildcard ที่ซ้อนกัน" ?) และเชื่อมโยงกับการอ้างอิง JLS ที่เกี่ยวข้องจึงมีความโปรดปราน
skomisa

6

ในการโทร:

compiles(new HashMap<Integer, List<Integer>>());

T Map<Integer,List<Integer>>จะจับคู่กับจำนวนเต็มดังนั้นประเภทของการโต้แย้งเป็น ไม่ใช่กรณีของวิธีการdoesntCompile: ประเภทของการโต้แย้งยังคงอยู่Map<Integer, List<? extends Number>>สิ่งที่อาร์กิวเมนต์ที่เกิดขึ้นจริงในการโทร; และนั่นไม่สามารถกำหนดได้HashMap<Integer, List<Integer>>ได้

UPDATE

ในdoesntCompileวิธีการไม่มีสิ่งใดป้องกันคุณให้ทำสิ่งนี้:

static void doesntCompile(Map<Integer, List<? extends Number>> map) {
    map.put(1, new ArrayList<Double>());
}

เห็นได้ชัดว่ามันไม่สามารถยอมรับHashMap<Integer, List<Integer>>ว่าเป็นข้อโต้แย้ง


แล้วการโทรที่ถูกต้องจะdoesntCompileเป็นเช่นไร? แค่อยากรู้เกี่ยวกับเรื่องนั้น
Xtreme Biker

1
@XtremeBiker doesntCompile(new HashMap<Integer, List<? extends Number>>());จะทำงานdoesntCompile(new HashMap<>());ได้ดี
skomisa

@XtremeBiker แม้สิ่งนี้จะใช้ได้ Map <Integer, List <? ขยาย Number >> map = HashMap ใหม่ <จำนวนเต็ม, List <? ขยายหมายเลข >> (); map.put (null, ArrayList ใหม่ <Integer> ()); doesntCompile (แผนที่);
MOANkey

"ที่ไม่สามารถกำหนดได้จากHashMap<Integer, List<Integer>>" คุณช่วยอธิบายเพิ่มเติมได้ไหมว่าทำไมมันถึงไม่สามารถกำหนดได้?
Dev Null

@DevNull ดูการอัปเดตของฉันด้านบน
Maurice Perry

2

ตัวอย่างที่เรียบง่ายของการสาธิต ตัวอย่างเดียวกันสามารถเห็นภาพเหมือนด้านล่าง

static void demo(List<Pair<? extends Number>> lst) {} // doesn't work
static void demo(List<? extends Pair<? extends Number>> lst) {} // works
demo(new ArrayList<Pair<Integer>()); // works
demo(new ArrayList<SubPair<Integer>()); // works for subtype too

public static class Pair<T> {}
public static class SubPair<T> extends Pair<T> {}

List<Pair<? extends Number>>เป็นสัญลักษณ์แทนหลายระดับในขณะที่List<? extends Number>เป็นสัญลักษณ์แทนมาตรฐาน

การสร้างอินสแตนซ์ที่ถูกต้องที่เป็นรูปธรรมของประเภท wild card List<? extends Number>รวมถึงNumberและชนิดย่อยใด ๆ ของNumberในกรณีList<Pair<? extends Number>>ที่เป็นอาร์กิวเมนต์ประเภทของการโต้แย้งประเภทและตัวเองมีการสร้างอินสแตนซ์ที่เป็นรูปธรรมของประเภททั่วไป

Generics มีค่าคงที่ดังนั้นชนิดป่าการ์ดสามารถยอมรับได้Pair<? extends Number> Pair<? extends Number>>ประเภท Inner ? extends Numberเป็น covariant แล้ว คุณต้องสร้างประเภทการปิดล้อมเป็น covariant เพื่อให้ความแปรปรวนร่วม


มัน<Pair<Integer>>ใช้งานไม่ได้<Pair<? extends Number>>แต่ทำงานได้<T extends Number> <Pair<T>>อย่างไร?
jaco0646

@ jaco0646 คุณเป็นหลักถามคำถามเดียวกับ OP และคำตอบจาก Andronicusได้รับการยอมรับ ดูตัวอย่างรหัสในคำตอบนั้น
skomisa

@skomisa ใช่ฉันถามคำถามเดียวกันด้วยเหตุผลสองประการข้อหนึ่งคือคำตอบนี้ดูเหมือนจะไม่ตอบคำถามของ OP; แต่สองคือฉันพบคำตอบนี้ง่ายกว่าที่จะเข้าใจ ฉันไม่สามารถปฏิบัติตามคำตอบจาก Andronicus ในทางใด ๆ ที่นำไปสู่ผมที่จะเข้าใจซ้อน VS ยาชื่อสามัญที่ไม่ซ้อนกันหรือแม้กระทั่งเทียบกับT ?ส่วนหนึ่งของปัญหาคือเมื่อ Andronicus มาถึงจุดสำคัญของคำอธิบายของเขาเขากลับไปที่หัวข้ออื่นที่ใช้เพียงตัวอย่างเล็กน้อย ฉันหวังว่าจะได้คำตอบที่ชัดเจนและสมบูรณ์ยิ่งขึ้นที่นี่
jaco0646

1
@ jaco0646 ตกลง เอกสาร"Java Generics คำถามที่พบบ่อย - อาร์กิวเมนต์ชนิด"โดย Angelika Langer มีคำถามที่พบบ่อยชื่อwildcard หลายระดับ (เช่นซ้อนกัน) หมายถึงอะไร? . นั่นเป็นแหล่งข้อมูลที่ดีที่สุดที่ฉันรู้ในการอธิบายปัญหาที่เกิดขึ้นในคำถามของ OP กฎสำหรับ wildcard ที่ซ้อนกันจะไม่ตรงไปตรงมาหรือไม่ซับซ้อน
skomisa

1

ฉันขอแนะนำให้คุณดูในเอกสารของสัญลักษณ์ตัวแทนทั่วไปโดยเฉพาะแนวทางสำหรับการใช้สัญลักษณ์แทน

พูดวิธีการของคุณอย่างตรงไปตรงมา #doesntCompile

static void doesntCompile(Map<Integer, List<? extends Number>> map) {}

และโทรเช่น

doesntCompile(new HashMap<Integer, List<Integer>>());

พื้นฐานไม่ถูกต้อง

เพิ่มการบังคับใช้ทางกฎหมาย :

    static void doesntCompile(Map<Integer, List<? extends Number>> map) {
        List<Double> list = new ArrayList<>();
        list.add(0.);
        map.put(0, list);
    }

เป็นเรื่องที่ดีจริงๆเพราะ Double ขยาย Number ดังนั้นการวางList<Double>จึงดีอย่างแน่นอนList<Integer>ใช่มั้ย

อย่างไรก็ตามคุณยังคงถือว่าถูกกฎหมายที่จะส่งผ่านnew HashMap<Integer, List<Integer>>()จากตัวอย่างของคุณที่นี่หรือไม่

คอมไพเลอร์ไม่คิดอย่างนั้นและพยายามอย่างที่สุดที่จะหลีกเลี่ยงสถานการณ์เช่นนี้

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

    static <T extends Number> void compiles(Map<Integer, List<T>> map) {
        List<Double> list = new ArrayList<>();
        list.add(10.);
        map.put(10, list); // does not compile
    }

โดยทั่วไปคุณสามารถใส่อะไร แต่List<T>ที่ว่าทำไมมันปลอดภัยที่จะเรียกวิธีการนั้นด้วยnew HashMap<Integer, List<Integer>>()หรือnew HashMap<Integer, List<Double>>()หรือหรือnew HashMap<Integer, List<Long>>()new HashMap<Integer, List<Number>>()

โดยสังเขปคุณกำลังพยายามที่จะโกงคอมไพเลอร์และมันก็ป้องกันการโกงอย่างเป็นธรรม

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

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