Tại sao trình biên dịch chọn phương thức chung này với tham số loại lớp khi được gọi với loại giao diện không liên quan?


11

Hãy xem xét hai lớp và giao diện sau đây:

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

Tại sao cuộc gọi thứ hai để mandatorygọi phương thức quá tải với Class2, nếu getInterface1Interface1không có mối quan hệ với 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;
    }
}

Tôi hiểu rằng Java 8 đã phá vỡ tính tương thích với 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

Và với Java 8 (cũng được thử nghiệm với 11 và 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
Điểm mấu chốt: quá tải phương thức trong Java mang lại rất nhiều điều bất ngờ, nó chỉ nên được sử dụng hết sức cẩn thận. Phân biệt hai tình trạng quá tải chỉ bằng một ràng buộc của một tham số loại là yêu cầu sự cố, như thể hiện bằng độ phức tạp của câu trả lời. Về cơ bản, bạn đang yêu cầu mỗi người đọc mã của bạn đọc và hiểu câu trả lời đó trước khi họ có thể hiểu mã của bạn. Đặt khác nhau: nếu chương trình của bạn bị hỏng khi suy luận kiểu được cải thiện, bạn không ở trong lãnh thổ an toàn. Chúc may mắn!
Stephan Herrmann

Câu trả lời:


4

Các quy tắc suy luận kiểu đã nhận được một đại tu đáng kể trong Java 8, đáng chú ý nhất là suy luận kiểu mục tiêu đã được cải thiện nhiều. Vì vậy, trong khi trước Java 8, trang đối số phương thức không nhận được bất kỳ suy luận nào, mặc định là loại đã xóa ( Class1cho getClass1()Interface1cho getInterface1()), trong Java 8, loại áp dụng cụ thể nhất được suy ra. JLS cho Java 8 đã giới thiệu một chương mới Chương 18. Kiểu suy luận thiếu trong JLS cho Java 7.


Loại áp dụng cụ thể nhất cho <T extends Interface1><X extends RequiredClass & BottomInterface>, trong đó RequiredClassmột lớp được yêu cầu bởi ngữ cảnh và BottomInterfacelà loại dưới cùng cho tất cả các giao diện (bao gồm Interface1).

Lưu ý: Mỗi loại Java có thể được biểu diễn dưới dạng SomeClass & SomeInterfaces. Vì RequiredClasslà kiểu con của SomeClassBottomInterfacelà kiểu con của SomeInterfaces, Xlà kiểu con của mọi kiểu Java. Do đó, Xlà một loại dưới cùng Java.

Xphù hợp với cả chữ ký public static <T> void mandatory(T o)public static <T extends Class2> void mandatory(T o)phương thức vì Xlà loại dưới cùng của Java.

Vì vậy, theo §15.12.2 , mandatory(getInterface1())gọi mandatory()phương thức nạp chồng cụ thể nhất , là public static <T extends Class2> void mandatory(T o)do <T extends Class2>cụ thể hơn <T>.

Dưới đây là cách bạn có thể chỉ định rõ ràng getInterface1()tham số loại để làm cho nó trả về kết quả khớp với public static <T extends Class2> void mandatory(T o)chữ ký phương thức:

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

Nhất cụ thể áp dụng kiểu cho <T extends Class1><Y extends Class1 & BottomInterface>, nơi BottomInterfacelà một loại dưới cho tất cả các giao diện.

Ykhớp public static <T> void mandatory(T o)chữ ký phương thức, nhưng nó không khớp với public static <T extends Class2> void mandatory(T o)chữ ký phương thức vì Ykhông mở rộng Class2.

Vì vậy, mandatory(getClass1())gọi public static <T> void mandatory(T o)phương thức.

Không giống như getInterface1(), bạn không thể chỉ định rõ ràng getClass1()tham số loại để làm cho nó trả về kết quả khớp với public static <T extends Class2> void mandatory(T o)chữ ký phương thức:

                       java: interface expected here
                                     
public static <T extends Class1 & C̲l̲a̲s̲s̲2> void helper() {
    mandatory(Test.<T>getClass1());
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.