繁体   English   中英

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

[英]Why does this generic code compile in 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>();
    }
}

有趣的是,如果我用<T extends ArrayList<Integer>>修改newList方法的签名,它就不再起作用了。

评论和回复后更新:如果我将泛型类型从方法移动到类,则代码不再编译:

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>();
    }
}

如果在方法中声明类型参数,则允许调用者为其选择实际类型,只要该实际类型满足约束条件即可。 该类型不必是实际的具体类型,它可以是抽象类型、类型变量或交集类型,换句话说,更通俗地说,是一种假设类型。 因此,正如 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>();
    }
}

当然, 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>();
    }
}

类型参数是类契约的一部分,因此创建实例的人将为该实例选择实际类型。 实例方法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>();
    }
}

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

我猜这是因为List是一个接口。 如果我们暂时忽略Stringfinal的事实,理论上你可以有一个extends String的类(意味着你可以将它分配给s )但implements List<Integer> (意味着它可以从newList()返回newList() )。 一旦您将返回类型从接口( T extends List )更改为具体类( T extends ArrayList ),编译器可以推断它们不能相互分配,并产生错误。

这当然会失效,因为String实际上是final ,我们可以期望编译器考虑到这一点。 恕我直言,这是一个错误,尽管我必须承认我不是编译器专家,此时可能有充分的理由忽略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);

指定类型参数

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

实例创建时的类型参数

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

List<String> list = new ArrayList<>();

在这里我们可以看到List<String>指定了列表项的类型。 另一方面, new ArrayList<>()没有。 它使用菱形运算符代替。 即 java 编译器根据声明推断类型。

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

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

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

你可以像这样使用它:

int max = max(3, 4); // implicit param type: Integer

或者像这样:

double max2 = max(3.0, 4.0); // implicit param type: Double

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

例如,这就是创建类型安全的空列表的方法:

List<Integer> noIntegers = Collections.<Integer>emptyList();

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

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

运行时类型令牌

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

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

暂无
暂无

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

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