简体   繁体   English

为什么这个涉及通用接口方法的程序编译?

[英]Why does this program involving a generic interface method compile?

The following code throws a ClassCastException at runtime, and the line public String foo() { return "bar"; } 以下代码在运行时抛出ClassCastException ,并且行public String foo() { return "bar"; } public String foo() { return "bar"; } generates a warning "found 'java.lang.String', required 'T'". public String foo() { return "bar"; }生成一个警告“找到‘java.lang.String中’,需要‘T’”。 I understand the ClassCastException (the interface method is called with T equal to Integer yet foo returns a String ) and I understand the warning (it's trying to warn us about exactly this problem). 我理解ClassCastException (调用接口方法, T等于Integerfoo返回一个String ),我理解警告(它试图警告我们这个问题)。 But what I don't understand is why the program compiles at all. 但我不明白的是程序编译的原因。 Why is a method returning a String allowed to override a method returning a T ? 为什么返回String的方法允许覆盖返回T的方法?

public class Main {

    interface MyInterface {

        <T> T foo();
    }

    static class MyClass implements MyInterface {

        @Override
        public String foo() { return "bar"; }
    }

    public static void main(String[] args) {
        int a = ((MyInterface) new MyClass()).<Integer>foo();
    }
}

When naively declaring <T> T foo(); 当天真地宣布<T> T foo(); the compiler will try to infer the result type of foo from the variable where it will be assigned. 编译器将尝试从将被赋值的变量中推断出foo的结果类型。 This is the reason why this compiles. 这就是编译的原因。 It can be easily tested: 它可以很容易地测试:

interface MyInterface {
    <T> T foo();
}

class MyClass implements MyInterface {
    @Override
    public String foo() { return "bar"; }
}

public class Main {
    public static void main(String[] args) {
        MyInterface myInterface = new MyClass();
        //the result of foo will be String
        String bar = myInterface.foo();
        System.out.println(bar); //prints "bar"
        try {
            //the result of foo at compile time will be Integer
            Integer fail = myInterface.foo();
            System.out.println(fail); //won't be executed
        } catch (ClassCastException e) {
            //for test purposes only. Exceptions should be managed better
            System.out.println(e.getMessage()); //prints "java.lang.String cannot be cast to java.lang.Integer"
        }
    }
}

And the result at compile time cannot be Object . 并且编译时的结果不能是Object If it were object, then you would have to add a manual type cast, which is not the case. 如果它是对象,那么你将不得不添加一个手动类型转换,但事实并非如此。

In short, declaring a method like that is useless and can only bring confusion and chaos to the programmers. 简而言之,声明这样的方法是无用的,只会给程序员带来混乱和混乱。

This method declaration becomes useful in one of these cases: 此方法声明在以下情况之一中变得有用:

  • When declaring the generic <T> at top level of the interface/class: 在接口/类的顶级声明泛型<T>时:

     interface MyInterface<T> { T foo(); } class MyClass implements MyInterface<String> { @Override //can only return String here. Compiler can check this public String foo() { return "bar"; } } 
  • When passing Class<T> as argument, which enables the compiler to infer the result type and raise a proper compiler error when this condition is not met: 传递Class<T>作为参数时,它使编译器能够在不满足此条件时推断结果类型并引发正确的编译器错误:

     interface MyInterface { <T> T foo(Class<T> clazz); } class MyClass implements MyInterface { @Override public <T> T foo(Class<T> clazz) { try { return clazz.newInstance(); } catch (InstantiationException e) { e.printStackTrace(System.out); } catch (IllegalAccessException e) { e.printStackTrace(System.out); } return null; } } public class Main { public static void main(String[] args) { MyInterface myInterface = new MyClass(); //uncomment line below to see the compiler error //Integer bar = myInterface.foo(String.class); //line below compiles and runs with no problem String bar = myInterface.foo(String.class); System.out.println(bar); } } 

Because declaring a method <T> T foo() is essentially identical to declaring it Object foo() . 因为声明方法<T> T foo()与声明Object foo()基本相同。 If you had some other connection to the type parameter somewhere (perhaps the interface was parameterized on T and the method was just T foo() ), then there would be some sort of link that might be violated. 如果你在某个地方有类型参数的其他连接(也许接口在T参数化,方法只是T foo() ),那么可能存在某种可能违反的链接。 In this case, however, you're just falling back to the standard rule that an override can return any more specific subtype of the supertype's return. 但是,在这种情况下,您只是回到标准规则,即覆盖可以返回超类型返回的任何更具体的子类型。

This is a surprisingly deep question. 这是一个非常深刻的问题。 The Java Language Specification writes : Java语言规范写道

An instance method mC declared in or inherited by class C , overrides from C another method mA declared in class A , iff all of the following are true: 一个实例方法mC中声明或由类继承C ,从覆盖C另一方法mA类声明A ,当且仅当满足以下所有条件都为真:

  • The signature of mC is a subsignature (§8.4.2) of the signature of mA . mC的签名是mA签名的子签名(§8.4.2)。
  • ... ...

and : 并且

Two methods or constructors, M and N , have the same signature if they have the same name, the same type parameters (if any) (§8.4.4), and, after adapting the formal parameter types of N to the the type parameters of M , the same formal parameter types. 两个方法或构造函数MN具有相同的签名,如果它们具有相同的名称,相同的类型参数(如果有的话)(第8.4.4节),并且在将形式参数类型N调整为类型参数之后M ,相同的形式参数类型。

This is clearly not true in our case, as MyInterface.foo declares a type parameter, but MyClass.foo does not. 在我们的例子中显然不是这样,因为MyInterface.foo声明了一个类型参数,但是MyClass.foo没有。

The signature of a method m1 is a subsignature of the signature of a method m2 if either: 方法m1的签名是方法m2的签名的子签名,如果:

  • m2 has the same signature as m1 , or m2m1具有相同的签名,或
  • the signature of m1 is the same as the erasure (§4.6) of the signature of m2 . m1的签名与m2签名的擦除(§4.6)相同。

The spec explains the need for that second condition as follows: 该规范解释了第二个条件的必要性如下:

The notion of subsignature is designed to express a relationship between two methods whose signatures are not identical, but in which one may override the other. 子签名的概念旨在表达两种方法之间的关系,这两种方法的签名不相同,但可以覆盖另一种方法。 Specifically, it allows a method whose signature does not use generic types to override any generified version of that method. 具体来说,它允许其签名不使用泛型类型的方法覆盖该方法的任何泛化版本。 This is important so that library designers may freely generify methods independently of clients that define subclasses or subinterfaces of the library. 这很重要,因此库设计者可以独立于定义库的子类或子接口的客户端自由地生成方法。

and indeed, that second condition is met in our case, as MyClass.foo has the signature foo() , which also is the erasure of the signature of MyInterface.foo . 事实上,在我们的情况下满足第二个条件,因为MyClass.foo具有签名foo() ,这也是MyInterface.foo签名的MyInterface.foo

That leaves the matter of the different return type. 这留下了不同回报类型的问题。 The spec writes : 规范写道

If a method declaration d1 with return type R1 overrides or hides the declaration of another method d2 with return type R2 , then d1 must be return-type-substitutable (§8.4.5) for d2 , or a compile-time error occurs. 如果具有返回类型R1的方法声明d1覆盖或隐藏具有返回类型R2的另一个方法d2的声明,则对于d2d1必须是return-type-substitutable(第8.4.5节),否则会发生编译时错误。

and : 并且

A method declaration d1 with return type R1 is return-type-substitutable for another method d2 with return type R2 iff any of the following is true: 返回类型为R1的方法声明d1是另一个方法d2的return-type-substitutable,返回类型为R2 iff以下任何一个为真:

  • ... ...

  • If R1 is a reference type then one of the following is true: 如果R1是引用类型,则满足以下条件之一:

    • R1, adapted to the type parameters of d2 (§8.4.4), is a subtype of R2. 适应d2(§8.4.4)类型参数的R1是R2的子类型。

    • R1 can be converted to a subtype of R2 by unchecked conversion (§5.1.9). 可以通过未经检查的转换将R1转换为R2的子类型(第5.1.9节)。

    • d1 does not have the same signature as d2 (§8.4.2), and R1 = |R2|. d1与d2(§8.4.2)没有相同的签名,R1 = | R2 |。

In our case, R1 = String and R2 = T. The first condition is therefore false, as String is not a subtype of T. However, String may be converted to T by unchecked conversion, making the second condition true. 在我们的例子中,R1 = String和R2 = T.因此,第一个条件是false,因为String不是T的子类型。但是,String可以通过未经检查的转换转换为T,使第二个条件成立。

The spec explains the need for the second and third conditions as follows: 该规范解释了第二和第三个条件的必要性如下:

An unchecked conversion is allowed in the definition, despite being unsound, as a special allowance to allow smooth migration from non-generic to generic code. 尽管不健全,但定义中允许进行未经检查的转换,作为允许从非泛型代码平滑迁移到通用代码的特殊容差。 If an unchecked conversion is used to determine that R1 is return-type-substitutable for R2, then R1 is necessarily not a subtype of R2 and the rules for overriding (§8.4.8.3, §9.4.1) will require a compile-time unchecked warning. 如果使用未经检查的转换来确定R1是R2的返回类型可替换,那么R1必然不是R2的子类型,并且覆盖规则(§8.4.8.3,§9.4.1)将需要编译时未经检查的警告。

That is, your code is accepted by the compiler because you inadvertently use two compiler features introduced to ease transition to generics by permitting a step by step generification of existing code. 也就是说,编译器接受了您的代码,因为您无意中使用了两个引入的编译器功能,以便通过允许逐步生成现有代码来简化向泛型的转换。 These features open a loop hole in the compile time type system, which may cause heap pollution and weird ClassCastExceptions in lines that may not even feature a cast in the source code. 这些功能在编译时类型系统中打开了一个循环漏洞,这可能会导致堆污染和奇怪的ClassCastExceptions行甚至可能在源代码中没有强制转换。 To alert you to this danger, the compiler is required to emit an unchecked warning. 为了提醒您这种危险,编译器需要发出未经检查的警告。 These features should therefore only be used for their intended purpose (compatiblity with non-generic legacy code), and otherwise avoided. 因此,这些特征仅应用于其预期目的(与非通用遗留代码的兼容性),否则应避免使用。

Because of type erasure the T part is just an Object in the byte code. 由于类型擦除, T部分只是字节代码中的一个Object You can return more specific type, in this case String . 您可以返回更具体的类型,在本例中为String

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM