将整数列表分配到字符串列表中

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
}

在主类中使用整数列表调用该方法时,我没有收到任何错误。

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
foo

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


24
你是否收到过警告,可能是有关原始类型的? - chrylis -cautiouslyoptimistic-
@chrylis - 那就是答案。他正在使用原始类型。编译器不关心你放什么。 :) - TheLostMind
5
GenericClass genericClass 使用了原始类型,因此 genericClass 上的泛型类型检查将被直接禁用。尝试使用 GenericClass<?> genericClass,你应该会收到一个错误提示。 - Thomas
4
是的,但奇怪的是,genericFunction() 要求一个 List<String>,而不是 List<E>,然而,给它一个 List<Integer> 也能够运行,而如果 GenericClass 不是泛型类,那么这种情况就不会起作用。 - Florent Bayle
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.

19
你将泛型和原始类型混合使用。它在编译时可以通过,但在运行时可能会失败,因为泛型信息在运行时丢失。
应该使用泛型来在编译时跟踪此类错误。
详细解释请参见什么是原始类型,为什么不应该使用它?

警告: 类型安全: 方法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
}

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://dev59.com/d3RB5IYBdhLWcg3wUFvB#662257,由Jon Skeet解释。

我认为这种情况是公平的(虽然不是您所期望的),因为如果您决定使用原始类型,则假定您使用Java 4或更低版本并且无论如何都无法访问泛型,因此它也可能不提供它们用于未涉及从被擦除的类中获得泛型类型的方法。


5
+1 表示 "你已经关闭了整个GenericClass类的泛型类型检查"。我必须承认,我既不知道也没有预料到这一点。 - Axel

6

补充其他答案。

实际上,您正在将未参数化的类型与参数化类型混合使用,这会导致类型丢失,并且在类型安全检查不应该允许您进行的情况下,似乎正确地传递参数给genericFunction

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

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版本的同时,实现泛型类型检查。


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

当您在类名后面写入

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接