Mengapa kompiler memilih metode generik ini dengan parameter tipe kelas ketika dipanggil dengan tipe antarmuka yang tidak terkait?

11

Pertimbangkan dua kelas dan antarmuka berikut:

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

Mengapa panggilan kedua untuk mandatorymemanggil metode kelebihan beban dengan Class2, jika getInterface1dan Interface1tidak memiliki hubungan dengan 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;
    }
}

Saya mengerti bahwa Java 8 merusak kompatibilitas dengan 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

Dan dengan Java 8 (juga diuji dengan 11 dan 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
membeku
sumber
1
Intinya: metode overloading di Jawa membawa begitu banyak kejutan, itu harus digunakan dengan sangat hati-hati. Membedakan dua kelebihan hanya oleh batasan parameter tipe meminta masalah, seperti yang ditunjukkan oleh kompleksitas jawaban. Anda pada dasarnya meminta setiap pembaca kode Anda untuk membaca dan memahami jawaban itu sebelum mereka dapat memahami kode Anda. Dengan kata lain: jika program Anda rusak ketika inferensi tipe ditingkatkan, Anda tidak berada di wilayah aman. Semoga berhasil!
Stephan Herrmann

Jawaban:

4

Aturan inferensi tipe telah menerima perbaikan yang signifikan di Jawa 8, terutama inferensi tipe target telah banyak diperbaiki. Jadi, sedangkan sebelum Java 8 situs metode argumen tidak menerima inferensi, default ke tipe terhapus ( Class1untuk getClass1()dan Interface1untuk getInterface1()), di Java 8 tipe yang paling spesifik yang berlaku disimpulkan. JLS untuk Java 8 memperkenalkan bab baru Bab 18. Ketik Inferensi yang hilang di JLS untuk Java 7.


Jenis yang paling spesifik untuk diaplikasikan <T extends Interface1>adalah <X extends RequiredClass & BottomInterface>, di mana RequiredClasskelas dibutuhkan oleh konteks, dan BottomInterfacemerupakan tipe terbawah untuk semua antarmuka (termasuk Interface1).

Catatan: Setiap tipe Java dapat direpresentasikan sebagai SomeClass & SomeInterfaces. Karena RequiredClassini adalah sub-tipe dari SomeClass, dan BottomInterfaceadalah sub-tipe dari SomeInterfaces, Xadalah sub-tipe dari setiap tipe Java. Oleh karena itu, Xadalah tipe bawah Java.

Xcocok dengan kedua public static <T> void mandatory(T o)dan public static <T extends Class2> void mandatory(T o)metode tanda tangan karena Xadalah tipe bawah Jawa.

Jadi, menurut §15.12.2 , mandatory(getInterface1())memanggil mandatory()metode kelebihan beban paling spesifik , yang public static <T extends Class2> void mandatory(T o)karena <T extends Class2>itu lebih spesifik daripada <T>.

Inilah cara Anda dapat secara eksplisit menentukan getInterface1()parameter tipe untuk membuatnya mengembalikan hasil yang cocok public static <T extends Class2> void mandatory(T o)dengan tanda tangan metode:

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

Jenis yang paling spesifik untuk diaplikasikan <T extends Class1>adalah <Y extends Class1 & BottomInterface>, di mana BottomInterfaceadalah tipe terbawah untuk semua antarmuka.

Ycocok public static <T> void mandatory(T o)dengan tanda tangan metode, tetapi tidak cocok public static <T extends Class2> void mandatory(T o)dengan tanda tangan metode karena Ytidak diperpanjang Class2.

Begitu mandatory(getClass1())panggilan public static <T> void mandatory(T o)metode.

Berbeda dengan dengan getInterface1(), Anda tidak bisa secara eksplisit menentukan getClass1()parameter tipe untuk membuatnya mengembalikan hasil yang cocok public static <T extends Class2> void mandatory(T o)dengan tanda tangan metode:

                       java: interface expected here
                                     
public static <T extends Class1 & C̲l̲a̲s̲s̲2> void helper() {
    mandatory(Test.<T>getClass1());
}
Pisang
sumber