37

我正在学习 Java 中的泛型,我接近了一段非常有趣的代码。我知道在 Java 中将一种类型的列表添加到另一种类型的列表是非法的。

List<Integer> integerList = new ArrayList<Integer>();
List<String> stringList=integerList;

所以在第二行我得到一个编译时错误。
但是如果我在这样的类中创建一个泛型方法,

class  GenericClass <E>{
    void genericFunction(List<String> stringList) {
        stringList.add("foo");
    }
    // some other code
}

在主类中调用带有 Integer 列表的方法,我没有收到任何错误。

public class Main {
  public static void main(String args[]) {

     GenericClass genericClass=new GenericClass();
     List<Integer> integerList= new ArrayList<Integer>();
     integerList.add(100);
     genericClass.genericFunction(integerList);
     System.out.println(integerList.get(0));
     System.out.println(integerList.get(1));
  }
}

输出
100
英尺

为什么我没有收到任何错误?

4

5 回答 5

23

您没有收到任何编译时错误,因为以GenericClass<E>原始方式使用:

GenericClass genericClass = new GenericClass();,

您实际上是在告诉编译器禁用泛型类型检查,因为您不在乎。

所以 :

void genericFunction(List<String> stringList)

变成

void genericFunction(List stringList)为编译器。

您可以尝试以下方法:GenericClass<?> genericClass,您会立即注意到编译器意识到泛型的不当使用,并且会显示错误:

The method genericFunction(List<String>) in the type GenericClass<capture#1-of ?> is not applicable for the arguments (List<Integer>)

此外,如果您尝试在运行时获取第二个位置对象的类:

System.out.println(integerList.get(1).getClass());

,你会得到一个错误: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

于 2014-08-20T09:12:35.250 回答
19

您将 Generic 与raw type混合在一起。它会编译得很好,但在运行时它可能会失败,因为通用信息在运行时丢失。

应使用泛型在编译时跟踪此类错误。

什么是原始类型以及为什么我们不应该使用它更好地解释了它?详细地。


警告:类型安全:该方法genericFunction(List)属于原始类型GenericClass。对泛型类型的引用GenericClass<E>应该被参数化。

如果您有两个具有不同通用类型 List 的名称相同的方法,则会导致编译时错误。如果下面的示例代码可以证明方法参数,编译器无法解析通用类型。

示例代码:(编译器错误 - 不是有效的重载方法)

void genericFunction(List<String> stringList){...}
void genericFunction(List<Integer> stringList){...}

进行一些更改并重试:

class  GenericClass <E>{
    void genericFunction(List<E> stringList) {
        ...
    }
    // some other code
}

...

GenericClass<String> genericClass=new GenericClass<String>(); // Genreric object
List<Integer> integerList= new ArrayList<Integer>();
integerList.add(100);
genericClass.genericFunction(integerList); // compile time error

以这种方式创建方法

class GenericClass<E> {
    private List<E> list = new ArrayList<E>();

    public void addAll(List<E> newList) {
        list.addAll(newList);
    }

    public void add(E e) {
        list.add(e);
    }

    public E get(int index) {
        return list.get(index);
    }
    // some other code
}
于 2014-08-20T09:13:10.873 回答
12

发生这种情况是因为(对我来说非常令人惊讶)您已经关闭了整个 GenericClass类的泛型类型检查。

您需要知道,首先,您在这里构造了一个没有类型参数的泛型类:

GenericClass genericClass = new GenericClass();

并且显然,因此,您的以下代码:

class GenericClass<E> {
    void genericFunction(List<String> stringList) {
        stringList.add("foo");
    }
    // some other code
}

精炼为:

class GenericClass {
    void genericFunction(List stringList) {
        stringList.add("foo");
    }
    // some other code
}

请注意,它List 变成了一个原始类型,这让我很惊讶。

您可以在这里找到完整的答案,参考 JLS:https : //stackoverflow.com/a/662257/2057294,由 Jon Skeet 解释。

我认为发生这种情况是公平的(尽管不是您所期望的),因为如果您决定使用原始类型,则假定您使用的是 Java 4 或更低版本并且根本无法访问泛型,所以它最好不要为不涉及已擦除类中的泛型类型的方法提供它们。

于 2014-08-20T09:54:51.720 回答
6

添加到其他答案。

实际上,您正在将原始(未参数化)类型与参数化类型混合,这会导致类型丢失和明显正确的参数传递到genericFunction类型安全检查不应该允许的情况。

问题仍然是为什么类型会在未参数化的对象中List<String>丢失。GenericClass编译器在您的情况下“禁用”类型检查的原因是类型擦除机制,它(简单地说)您的原始对象属于具有以下内容的类:

class  GenericClass {
    void genericFunction(List stringList) {
        stringList.add("foo");
    }
    // some other code
}

如您所见,它不会对正在传递的列表的内容进行任何类型检查(它会在任何地方擦除类型参数。更重要的是,类型擦除还会从integerList变量中删除参数化类型,这使得它完全适合作为genericFunction方法的参数。

因此,正如其他人所指出的,最好帮助 Java 保持其安全的类型检查机制完好无损:

     GenericClass<?> genericClass=new GenericClass<Integer>();
     List<Integer> integerList= new ArrayList<Integer>();
     integerList.add(100);
     // no-no here
     genericClass.genericFunction(integerList);
     System.out.println(integerList.get(0));
     System.out.println(integerList.get(1));

但话又说回来,如果你这样做了,编译器就会完成它的工作,然后向你发射火箭筒。

在此处阅读有关类型擦除的更多信息。这是一个很好的特性,它允许进行泛型类型检查,同时仍然保持对泛型前 Java 版本的向后兼容性。

于 2014-08-20T09:22:45.857 回答
0
class  GenericClass <E>{
    void genericFunction(List<String> stringList) {
        stringList.add("foo");
    }
    // some other code
}

当你写在类名之后。它指定类型参数。它是一个通用功能。这引入了类型变量 E,它可以在类中的任何地方使用。类型变量可以是您指定的任何非原始类型:任何类类型、任何接口类型、任何数组类型,甚至是另一个类型变量。

于 2014-08-27T03:10:39.037 回答