为什么这个通用代码在Java8中编译?

为什么这个通用代码在Java8中编译?,java,generics,compiler-errors,java-8,Java,Generics,Compiler Errors,Java 8,我偶然发现了一段代码,我想知道为什么它能成功编译: public class Main { public static void main(String[] args) { String s = newList(); // why does this line compile? System.out.println(s); } private static <T extends List<Integer>> T

我偶然发现了一段代码,我想知道为什么它能成功编译:

public class Main {
    public static void main(String[] args) {
        String s =  newList(); // why does this line compile?
        System.out.println(s);
    }

    private static <T extends List<Integer>> T newList() {
        return (T) new ArrayList<Integer>();
    }
}

我猜这是因为
List
是一个接口。如果我们暂时忽略
String
final
这一事实,理论上,您可以拥有一个
扩展String
(意味着您可以将其分配给
s
)但
实现List
(意味着它可以从
newList()
返回)的类。将返回类型从接口(
T extends List
)更改为具体类(
T extends ArrayList
)后,编译器可以推断它们之间不可赋值,并产生错误


当然,这是错误的,因为
String
实际上是
final
,我们可以期望编译器考虑到这一点。嗯,这是一个bug,尽管我必须承认我不是编译器专家,在这一点上可能有一个很好的理由忽略
final
修饰符。

我不知道为什么要编译它。另一方面,我可以解释如何充分利用编译时检查

因此,
newList()
是一个通用方法,它有一个类型参数。如果指定此参数,编译器将为您检查:

编译失败:

String s =  Main.<String>newList(); // this doesn't compile anymore
System.out.println(s);
List<Integer> l =  Main.<ArrayList<Integer>>newList(); // this compiles and works well
System.out.println(l);
或者像这样:

int max = max(3, 4); // implicit param type: Integer
double max2 = max(3.0, 4.0); // implicit param type: Double
方法调用时的显式类型参数:

String s =  Main.<String>newList(); // this doesn't compile anymore
System.out.println(s);
List<Integer> l =  Main.<ArrayList<Integer>>newList(); // this compiles and works well
System.out.println(l);
例如,这是创建类型安全空列表的方法:

List<Integer> noIntegers = Collections.<Integer>emptyList();
运行时类型令牌

如果这些技巧都不能帮助您,那么您可以指定一个。即,您提供一个类作为参数。一个常见的例子是:

私有静态枚举字母{A,B,C};//虚拟枚举
...
公共静态void main(字符串[]args){
Map Map=新的枚举映射(Letters.class);
}

如果在方法中声明类型参数,则允许调用方为其选择实际类型,只要该实际类型满足约束。该类型不必是实际的具体类型,它可能是抽象类型、类型变量或交叉类型,换句话说,更通俗的说法是,一种假设类型。因此,可能存在一个扩展
String
并实现
List
的类型。我们不能手动为调用指定交集类型,但可以使用类型变量来演示逻辑:

public class Main {
    public static <X extends String&List<Integer>> void main(String[] args) {
        String s = Main.<X>newList();
        System.out.println(s);
    }

    private static <T extends List<Integer>> T newList() {
        return (T) new ArrayList<Integer>();
    }
}
类型参数是类契约的一部分,因此创建实例的人将为该实例选择实际类型。实例方法
main
是该类的一部分,必须遵守该契约。您不能选择所需的
t
T
的实际类型已经设置好,在Java中,您通常甚至无法找到
T
是什么

泛型编程的关键是编写独立于为类型参数选择的实际类型的代码

但是请注意,您可以创建另一个独立实例,使用您喜欢的任何类型并调用该方法,例如

public class SomeClass<T extends List<Integer>> {
    public <X extends String&List<Integer>> void main(String[] args) {
        String s = new SomeClass<X>().newList();
        System.out.println(s);
    }

    private T newList() {
        return (T) new ArrayList<Integer>();
    }
}
公共类SomeClass{
公共void main(字符串[]参数){
字符串s=newsomeclass().newList();
系统输出打印项次;
}
私人T newList(){
返回(T)新的ArrayList();
}
}

在这里,新实例的创建者选择该实例的实际类型。如前所述,实际类型不需要是具体类型。

Hmm,这实际上是一个很好的观点。我没有考虑过有一个理论上可以扩展字符串和实现列表的类的可能性。正如您所说,这并不真正适用,因为字符串是final,但这是一个有趣的想法。在编译时类是
final
的事实在这种情况下从未被考虑,因为无法保证类在运行时仍然是
final
。为什么这会是一个“bug”?--类型本身就是一个实例:
String s=“blah”;System.out.println((字符串的实例))打印出
true
。因此,似乎奇怪的是,所有类的集合
all SomeClass,其中(SomeParentClass的SomeClass instanceof SomeParentClass)=true将排除
SomeParentClass
本身。因此,具体类
SomeClass
与指定其所有子类型的泛型匹配,例如
List@DenisRosca,Holger刚才解释了为什么没有好的理由阻止
T extends FinalClass
@DenisRosca,关于我之前的评论,我试图证明为什么
T extends String
也应该接受
String
。相关的原因是,如果存在满足约束条件的任何类型(T extends List&T extends String)[即,它是一个存在的量化],然而,第二个版本只能在与类中的声明匹配的所有可能类型T也扩展字符串[即,它是一个通用量化]的情况下工作。您能简化解释吗?简单的答案是您可以在
括号内指定类型参数。否则,我会解释更长的时间,这样你就可以看到所有的选择。
private static enum Letters {A, B, C}; // dummy enum
...
public static void main(String[] args) {
    Map<Letters, Integer> map = new EnumMap<>(Letters.class);
}
public class Main {
    public static <X extends String&List<Integer>> void main(String[] args) {
        String s = Main.<X>newList();
        System.out.println(s);
    }

    private static <T extends List<Integer>> T newList() {
        return (T) new ArrayList<Integer>();
    }
}
public class SomeClass<T extends List<Integer>> {
    public  void main(String[] args) {
        String s = newList(); // this doesn't compile anymore
        System.out.println(s);
    }

    private T newList() {
        return (T) new ArrayList<Integer>();
    }
}
public class SomeClass<T extends List<Integer>> {
    public <X extends String&List<Integer>> void main(String[] args) {
        String s = new SomeClass<X>().newList();
        System.out.println(s);
    }

    private T newList() {
        return (T) new ArrayList<Integer>();
    }
}