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

Den*_*sca 42 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 newList() {
        return (T) new ArrayList<Integer>();
    }
}
Run Code Online (Sandbox Code Playgroud)

有趣的是,如果我修改方法的签名newList<T extends 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>();
    }
}
Run Code Online (Sandbox Code Playgroud)

Hol*_*ger 35

如果在方法中声明类型参数,则允许调用者为其选择实际类型,只要该实际类型将满足约束.该类型不必是实际的具体类型,它可以是抽象类型,类型变量或交集类型,在其他更通俗的单词中,假设类型.因此,正如Mureinik所说,可能有一种类型扩展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>();
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,newList()不能满足返回这种类型的期望,但这是该方法的定义(或实现)的问题.投放时,你应该得到一个"选中"的警告ArrayListT.唯一可能的正确实现将返回null此处,这使得该方法非常无用.

重复初始语句的要点是泛型方法的调用者选择类型参数的实际类型.相反,当你声明一个类似的泛型

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>();
    }
}
Run Code Online (Sandbox Code Playgroud)

type参数是类合同的一部分,因此创建实例的人将选择该实例的实际类型.实例方法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>();
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Mur*_*nik 20

我猜这是因为List是一个界面.如果我们忽视的事实是Stringfinal一秒钟,你可以在理论上,有一个类,extends String(这意味着你可以将其分配到s),但implements List<Integer>(这意味着它可以从返回newList()).一旦将返回类型从interface(T extends List)更改为具体类(T extends ArrayList),编译器就可以推断出它们不能彼此分配,并产生错误.

当然,这String实际上是分解的final,我们可以期待编译器将此考虑在内.恕我直言,这是一个错误,虽然我必须承认我不是编译专家,并且可能有一个很好的理由final在此时忽略修饰符.

  • 在编译时类是"final"的事实在这种情况下从未被考虑过,因为没有保证类在运行时仍然是"final". (8认同)
  • 嗯,这实际上是一个非常好的观点。我没有考虑过有一个类在理论上可以扩展 String 并实现 List 的可能性。正如您所说,这并不真正适用,因为 String 是最终的,但这是一个有趣的想法。 (2认同)
  • 为什么这应该是一个"bug"? - 一个类型是它自己的一个实例:`String s ="blah"; System.out.println((s instanceof String));`打印出`true`.因此,所有类的所有类的集合(SomeClass instanceof SomeParentClass)== true`将排除`SomeParentClass`本身,这似乎很奇怪.因此,具体类`SomeClass`匹配指定其所有子类型的泛型,例如`List <T extends SomeClass`.毕竟,[任何集合都是其自身的一个子集](https://proofwiki.org/wiki/Set_is_Subset_of_Itself). (2认同)
  • @DenisRosca,Holger刚才解释了为什么没有充分理由阻止`T extends FinalClass`. (2认同)

Tam*_*Rev 6

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

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

无法编译:

String s =  Main.<String>newList(); // this doesn't compile anymore
System.out.println(s);
Run Code Online (Sandbox Code Playgroud)

通过编译步骤:

List<Integer> l =  Main.<ArrayList<Integer>>newList(); // this compiles and works well
System.out.println(l);
Run Code Online (Sandbox Code Playgroud)

指定type参数

类型参数仅提供编译时检查.这是设计,java使用类型擦除为泛型类型.为了使编译器适合您,您必须在代码中指定这些类型.

在实例创建时输入参数

最常见的情况是指定对象实例的模式.即列表:

List<String> list = new ArrayList<>();
Run Code Online (Sandbox Code Playgroud)

在这里我们可以看到List<String>指定列表项的类型.另一方面,新的ArrayList<>()没有.它使用钻石操作员代替.即java编译器根据声明推断出类型.

方法调用时的隐式类型参数

当您调用静态方法时,您必须以另一种方式指定类型.有时您可以将其指定为参数:

public static <T extends Number> T max(T n1, T n2) {
    if (n1.doubleValue() < n2.doubleValue()) {
        return n2;
    }
    return n1;
}
Run Code Online (Sandbox Code Playgroud)

您可以像这样使用它:

int max = max(3, 4); // implicit param type: Integer
Run Code Online (Sandbox Code Playgroud)

或者像这样:

double max2 = max(3.0, 4.0); // implicit param type: Double
Run Code Online (Sandbox Code Playgroud)

方法调用时的显式类型参数:

比如说,这就是你可以创建一个类型安全的空列表:

List<Integer> noIntegers = Collections.<Integer>emptyList();
Run Code Online (Sandbox Code Playgroud)

type参数<Integer>传递给方法emptyList().唯一的限制是你必须指定类.即你不能这样做:

import static java.util.Collections.emptyList;
...
List<Integer> noIntegers = <Integer>emptyList(); // this won't compile
Run Code Online (Sandbox Code Playgroud)

运行时类型令牌

如果这些技巧都不能帮助您,那么您可以指定运行时类型标记.即你提供一个类作为参数.一个常见的例子是EnumMap:

private static enum Letters {A, B, C}; // dummy enum
...
public static void main(String[] args) {
    Map<Letters, Integer> map = new EnumMap<>(Letters.class);
}
Run Code Online (Sandbox Code Playgroud)