ทำไมคอมไพเลอร์เลือกวิธีการทั่วไปนี้ด้วยพารามิเตอร์ประเภทคลาสเมื่อเรียกใช้ด้วยประเภทอินเตอร์เฟสที่ไม่เกี่ยวข้อง?


11

พิจารณาสองคลาสและอินเทอร์เฟซต่อไปนี้:

public class Class1 {}
public class Class2 {}
public interface Interface1 {}

ทำไมการเรียกครั้งที่สองเพื่อmandatoryเรียกใช้เมธอดโอเวอร์โหลดด้วยClass2ถ้าgetInterface1และInterface1ไม่มีความสัมพันธ์กับClass2?

public class Test {

    public static void main(String[] args) {
        Class1 class1 = getClass1();
        Interface1 interface1 = getInterface1();

        mandatory(getClass1());     // prints "T is not class2"
        mandatory(getInterface1()); // prints "T is class2"
        mandatory(class1);          // prints "T is not class2"
        mandatory(interface1);      // prints "T is not class2"
    }

    public static <T> void mandatory(T o) {
        System.out.println("T is not class2");
    }

    public static <T extends Class2> void mandatory(T o) {
        System.out.println("T is class2");
    }

    public static <T extends Class1> T getClass1() {
        return null;
    }

    public static <T extends Interface1> T getInterface1() {
        return null;
    }
}

ฉันเข้าใจว่า Java 8 เข้ากันไม่ได้กับ Java 7:

$ /usr/lib/jvm/java-8-openjdk-amd64/bin/javac -source 1.7 -target 1.7 *java; /usr/lib/jvm/java-8-openjdk-amd64/bin/java Test
warning: [options] bootstrap class path not set in conjunction with -source 1.7
1 warning
T is not class2
T is not class2
T is not class2
T is not class2

และด้วย Java 8 (ทดสอบด้วย 11 และ 13):

$ /usr/lib/jvm/java-8-openjdk-amd64/bin/javac *java; /usr/lib/jvm/java-8-openjdk-amd64/bin/java Test                        
T is not class2
T is class2
T is not class2
T is not class2

1
Bottom line: การใช้เมธอดมากเกินไปใน Java นำมาซึ่งความประหลาดใจมากมายควรใช้ด้วยความระมัดระวังอย่างยิ่งเท่านั้น การแยกการโหลดเกินพิกัดสองครั้งโดยขอบเขตของพารามิเตอร์ประเภทคือการถามปัญหาดังที่แสดงให้เห็นโดยความซับซ้อนของคำตอบ คุณจะขอให้ผู้อ่านรหัสของคุณอ่านและเข้าใจคำตอบนั้นก่อนที่พวกเขาจะเข้าใจรหัสของคุณ ใส่โปรแกรมที่แตกต่างกัน: หากโปรแกรมของคุณหยุดทำงานเมื่อการอนุมานประเภทดีขึ้นคุณจะไม่อยู่ในขอบเขตที่ปลอดภัย โชคดี!
เตฟานมานน์

คำตอบ:


4

กฎของการอนุมานประเภทได้รับการยกเครื่องที่สำคัญใน Java 8 การอนุมานประเภทเป้าหมายที่สะดุดตาที่สุดได้รับการปรับปรุงให้ดีขึ้นมาก ดังนั้นในขณะที่ก่อนหน้า Java 8 เว็บไซต์อาร์กิวเมนต์วิธีการไม่ได้รับการอนุมานใด ๆ เริ่มต้นที่จะลบประเภท ( Class1สำหรับgetClass1()และInterface1สำหรับgetInterface1()) ใน Java 8 ประเภทที่ใช้งานได้เฉพาะเจาะจงมากที่สุดคืออนุมาน JLS สำหรับ Java 8 แนะนำบทใหม่บทที่ 18 การอนุมานประเภทที่หายไปใน JLS สำหรับ Java 7


ชนิดที่สามารถใช้งานได้เฉพาะเจาะจงมากที่สุด<T extends Interface1>คือคือ<X extends RequiredClass & BottomInterface>, ซึ่งRequiredClassเป็นคลาสที่บริบทต้องการและBottomInterfaceเป็นประเภทด้านล่างสำหรับอินเตอร์เฟสทั้งหมด (รวมถึงInterface1)

หมายเหตุ: แต่ละประเภท Java SomeClass & SomeInterfacesสามารถแสดงเป็น เนื่องจากRequiredClassเป็นประเภทย่อยของSomeClassและBottomInterfaceเป็นชนิดย่อยของSomeInterfaces, Xเป็นชนิดย่อยของทุกประเภท Java ดังนั้นจึงXเป็นประเภท Java ล่าง

Xจับคู่ทั้งสองpublic static <T> void mandatory(T o)และpublic static <T extends Class2> void mandatory(T o)ลายเซ็นวิธีการเนื่องจากXเป็นประเภท Java ล่าง

ดังนั้นตาม§15.12.2 , mandatory(getInterface1())เรียกเฉพาะเจาะจงมากที่สุดการบรรทุกเกินพิกัดของmandatory()วิธีการซึ่งเป็นpublic static <T extends Class2> void mandatory(T o)ตั้งแต่เป็นเฉพาะเจาะจงมากขึ้นกว่า<T extends Class2><T>

นี่คือวิธีที่คุณสามารถระบุgetInterface1()พารามิเตอร์ประเภทอย่างชัดเจนเพื่อให้มันส่งกลับผลลัพธ์ที่ตรงกับpublic static <T extends Class2> void mandatory(T o)ลายเซ็นวิธีการ:

public static <T extends Class2 & Interface1> void helper() {
    mandatory(Test.<T>getInterface1()); // prints "T is class2"
}

ชนิดที่สามารถใช้งานได้เฉพาะเจาะจงมากที่สุดสำหรับ<T extends Class1>คือ<Y extends Class1 & BottomInterface>ที่BottomInterfaceประเภทด้านล่างสำหรับอินเทอร์เฟซทั้งหมด

Y ไม้ขีด public static <T> void mandatory(T o)ลายเซ็นวิธี แต่ก็ไม่ตรงกับpublic static <T extends Class2> void mandatory(T o)ลายเซ็นวิธีตั้งแต่ไม่ขยายYClass2

ดังนั้นวิธีการmandatory(getClass1())โทรpublic static <T> void mandatory(T o)

แตกต่างกับgetInterface1()คุณจะไม่สามารถระบุอย่างชัดเจนgetClass1()พารามิเตอร์ชนิดที่จะทำให้มันกลับมาผลที่ตรงกับpublic static <T extends Class2> void mandatory(T o)ลายเซ็นวิธี:

                       java: interface expected here
                                     
public static <T extends Class1 & C̲l̲a̲s̲s̲2> void helper() {
    mandatory(Test.<T>getClass1());
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.