快速java泛型问题

时间:2011-07-07 03:22:08

标签: java generics

我不认为我真的了解Java泛型。这两种方法有什么区别?为什么第二个没有编译,错误如下所示。

由于

static List<Integer> add2 (List<Integer> lst) throws Exception {
    List<Integer> res = lst.getClass().newInstance();
    for (Integer i : lst) res.add(i + 2);
    return res;
}

static <T extends List<Integer>> T add2 (T lst) throws Exception {
    T res = lst.getClass().newInstance();
    for (Integer i : lst) res.add(i + 2);
    return res;
}

Exception in thread "main" java.lang.RuntimeException: Uncompilable source code - incompatible types
  required: T
  found:    capture#1 of ? extends java.util.List

5 个答案:

答案 0 :(得分:6)

对于第二种编译方法,您必须将newInstace()的结果强制转换为T

static <T extends List<Integer>> T add2 (T lst) throws Exception {
  T res = (T) lst.getClass().newInstance();
  for (Integer i : lst) res.add(i + 2);
  return res;
}

关于两种方法之间的区别,让我们忘记实现,只考虑签名。

编译代码之后,两个方法都将具有完全相同的签名(因此如果具有相同的名称,编译器将给出错误)。这是因为所谓的类型擦除

在Java中,编译后所有类型参数都会消失。它们被最通用的原始类型取代。在这种情况下,两种方法都将编译为List add2(List)

现在,这将显示两种方法之间的区别:

class Main {
  static <T extends List<Integer>> T add1(T lst) { ... }
  static List<Integer> add2(List<Integer> lst) { ... }
  public static void main(String[] args) {
    ArrayList<Integer> l = new ArrayList<Integer>();
    ArrayList<Integer> l1 = add1(l);
    ArrayList<Integer> l2 = add2(l); // ERROR!
  }
}

标记为// ERROR!的行将无法编译。

在第一种方法add1中,编译器知道它可以将结果分配给类型为ArrayList<Integer>的变量,因为签名表明方法的返回类型与参数的那个。由于参数的类型为ArrayList<Integer>,因此编译器会将T推断为ArrayList<Integer>,这样您就可以将结果分配给ArrayList<Integer>

在第二种方法中,所有编译器都知道它将返回List<Integer>的实例。它无法确定它是ArrayList<Integer>,因此您必须进行显式转换,ArrayList<Integer> l2 = (ArrayList<Integer>) add2(l);。请注意,这不会解决问题:您只是告诉编译器停止抱怨并编译代码。您仍会收到警告(未经检查的强制转换),可以通过使用@SuppressWarnings("unchecked")注释方法来使其静音。现在编译器会很安静,但你可能仍然在运行时得到ClassCastException

答案 1 :(得分:3)

指定第一个接受List&lt; Integer&gt;并返回List&lt; Integer&gt;。列表是一个接口,暗示是一个实现List的具体类的实例作为参数传递,并且实现List的一些其他具体类的实例作为结果返回,而这两个类之间没有任何进一步的关系他们都实施了List。

第二个收紧它:它被指定接受一些实现List&lt; Integer&gt;的类。作为参数,并返回与结果完全相同的类或后代类的实例。

所以例如你可以这样调用第二个:

ArrayList list; // initialization etc not shown
ArrayList result = x.add2(list);

但不是第一个,除非你添加了一个类型转换。

这是另一个问题。 ; - )

@Bruno Reis解释了编译错误。

答案 2 :(得分:1)

  

为什么第二个没有编译,错误如下所示。

显示的错误是实际报告您尝试运行代码但编译失败。最好将IDE配置为不运行带编译错误的代码。或者如果你坚持让这种情况发生,至少要报告实际的编译错误以及行号等。

答案 3 :(得分:1)

  

“我认为我不太了解Java泛型。”

没有人......

该问题与getClass()的有趣返回类型有关。看它的javadoc。并this recent thread

在您的两个示例中,lst.getClass()都会返回Class<? extends List>,因此,newInstance()会返回? extends List - 或者更正式地说,是由W引入的新类型参数W extends List javac W

在第一个示例中,我们需要将List<Integer>分配给W。这是assignment conversion允许的。首先,List可以转换为List,因为WList的超级类型。然后,由于List是原始类型,因此允许使用可选的未经检查的转换,将List<Integer>转换为W,并使用强制编译器警告。

在第二个示例中,我们需要将T分配给W。我们在这里运气不佳,没有路径可以从T转换为W。这是有道理的,因为就javac而言,TList可能是W的两个不相关的子类。

当然,我们知道TgetClass(),如果允许,分配将是安全的。这里的根本问题是x.getClass()丢失了类型信息。如果Class<? extends X>在没有删除的情况下返回{{1}},那么您的两个示例都会在没有警告的情况下进行编译。它们确实是类型安全的。

答案 4 :(得分:0)

泛型是一种保证类型安全的方法。 例如:

int[] arr = new int[4];
   arr[0] = 4; //ok
   arr[1] = 5; //ok
   arr[2] = 9; //ok
   arr[3] = "Hello world"; // you will get an exception saying incompatible 

类型。

默认情况下,Java中的数组是typeSafe。整数数组仅用于表示    包含整数,没有别的。

现在:

ArrayList arr2 =new ArrayList();
   arr2.add(4); //ok
   arr2.add(5); //ok
   arr2.(9); //ok


   int a = arr2.get(0);
   int b = arr2.get(1);
   int c = arr3.get(2);

您将获得异常,例如无法转换Object    实例到整数。

原因是ArrayList存储对象而不是像    上面的数组。

正确的方法是显式转换为整数。你必须这样做    因为类型安全性尚未得到保证。    例如:

int a =(int)arr2.get(0);

要为集合使用类型安全性,只需指定集合包含的对象类型即可。  例如:

 ArrayList<Integer> a = new ArrayList<Integer>();


After insertion into the data structure, you can simply retrieve it like you 
would do with an array.

例如:

int a = arr2.get(0);