简体   繁体   English

快速 java generics 问题

[英]Quick java generics question

I don't think I really understand Java generics.我不认为我真的了解 Java generics。 What's the difference between these two methods?这两种方法有什么区别? And why does the second not compile, with the error shown below.以及为什么第二个无法编译,错误如下所示。

Thanks谢谢

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

For the second method to compile, you have to cast the result of newInstace() to T :对于要编译的第二种方法,您必须将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;
}

Regarding the difference between the two methods, let's forget about the implementation, and consider only the signature.关于这两种方法的区别,让我们忘记实现,只考虑签名。

After the code is compiled, both methods will have exactly the same signature (so the compiler would give an error if the have the same name).编译代码后,两个方法将具有完全相同的签名(因此如果名称相同,编译器会出错)。 This happens because of what is called type erasure .发生这种情况是因为所谓的类型擦除

In Java, all the type parameters disappear after compilation. Java中,编译后所有类型参数消失。 They are replaced by the most generic possible raw type.它们被最通用的原始类型所取代。 In this case, both methods will be compiled as List add2(List) .在这种情况下,两种方法都将编译为List add2(List)

Now, this will show the difference between the two methods:现在,这将显示两种方法之间的区别:

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!
  }
}

The line marked as // ERROR!标记为// ERROR! won't compile.不会编译。

In the first method, add1 , the compiler knows that it can assign the result to a variable of type ArrayList<Integer> , because the signature states that the return type of the method is exactly the same as that of the parameter.在第一个方法add1中,编译器知道它可以将结果分配给ArrayList<Integer>类型的变量,因为签名声明方法的返回类型与参数的返回类型完全相同。 Since the parameter is of type ArrayList<Integer> , the compiler will infer T to be ArrayList<Integer> , which will allow you to assign the result to an ArrayList<Integer> .由于参数是ArrayList<Integer>类型,编译器将推断TArrayList<Integer> ,这将允许您将结果分配给ArrayList<Integer>

In the second method, all the compiler knows is that it will return an instance of List<Integer> .在第二种方法中,编译器只知道它将返回一个List<Integer>的实例。 It cannot be sure that it will be an ArrayList<Integer> , so you have to make an explicit cast, ArrayList<Integer> l2 = (ArrayList<Integer>) add2(l);无法确定它将是ArrayList<Integer> ,因此您必须进行显式转换, ArrayList<Integer> l2 = (ArrayList<Integer>) add2(l); . . Note that this won't solve the problem: you are simply telling the compiler to stop whining and compile the code.请注意,这不会解决问题:您只是告诉编译器停止抱怨并编译代码。 You will still get an warning ( unchecked cast ), which can be silenced by annotating the method with @SuppressWarnings("unchecked") .您仍然会收到一个警告(未经检查的演员表),可以通过使用@SuppressWarnings("unchecked")注释方法来消除警告。 Now the compiler will be quiet, but you might still get a ClassCastException at runtime!现在编译器会安静下来,但你可能仍然会在运行时收到ClassCastException

The first one is specified to accept a List<Integer> and return a List<Integer>.第一个指定接受 List<Integer> 并返回 List<Integer>。 List being an interface, the implication is that an instance of some concrete class that implements List is being passed as a parameter and an instance of some other concrete class that implements List is returned as a result, without any further relationship between these two classes other than that they both implement List. List 是一个接口,这意味着实现 List 的一些具体 class 的实例作为参数传递,结果返回实现 List 的一些其他具体 class 的实例,而这两个类之间没有任何进一步的关系而不是他们都实现了List。

The second one tightens that up: it is specified to accept some class that implements List<Integer> as a parameter, and return an instance of exactly that same class or a descendant class as the result.第二个加强了这一点:它指定接受一些实现 List<Integer> 作为参数的 class,并返回完全相同的 class 或后代 class 的实例作为结果。

So for example you could call the second one like so:因此,例如,您可以像这样调用第二个:

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

but not the first, unless you added a typecast.但不是第一个,除非您添加了类型转换。

What use that is is another question.那有什么用是另一个问题。 ;-) ;-)

@Bruno Reis has explained the compile error. @Bruno Reis 解释了编译错误。

And why does the second not compile, with the error shown below.以及为什么第二个无法编译,错误如下所示。

The error shown is actually reporting that you have tried to run code that failed to compile.显示的错误实际上是报告您尝试运行无法编译的代码 It is a better idea to configure your IDE to not run code with compilation errors.最好将 IDE 配置为不运行出现编译错误的代码。 Or if you insist on letting that happen, at least report the actual compilation error together with the line number, etc.或者如果你坚持让这种情况发生,至少报告实际的编译错误以及行号等。

"I don't think I really understand Java generics." “我不认为我真的了解 Java generics。”

Nobody does...没人会...

The issue is related to the interesting return type of getClass() .该问题与有趣的getClass()返回类型有关。 See its javadoc.请参阅它的 javadoc。 And this recent thread .这个最近的线程

In both of your examples, lst.getClass() returns Class<? extends List>在您的两个示例中, lst.getClass()返回Class<? extends List> Class<? extends List> , consequently, newInstance() returns ? extends List Class<? extends List> ,因此newInstance()返回? extends List ? extends List - or more formally, a new type parameter W introduced by javac where W extends List ? extends List - 或者更正式地说,一个由 javac 引入的新类型参数W ,其中W extends List

In your first example, we need to assign W to List<Integer> .在您的第一个示例中,我们需要将W分配给List<Integer> This is allowed byassignment conversion .这可以通过赋值转换来实现。 First, W can be converted to List because List is a super type of W .首先, W可以转换为List因为ListW的超类型。 Then since List is raw type, the optional unchecked conversion is allowed, which converts List to List<Integer> , with a mandatory compiler warning.然后由于List是原始类型,因此允许可选的未经检查的转换,它将List转换为List<Integer> ,并带有强制性编译器警告。

In the 2nd example, we need to assign W to T .在第二个示例中,我们需要将W分配给T We are out of luck here, there's no path to convert from W to T .我们在这里不走运,没有从W转换为T的路径。 It makes sense because as far as javac knows at this point, W and T could be two unrelated subclass of List .这是有道理的,因为据 javac 目前所知, WT可能是List的两个不相关的子类。

Of course, we know W is T , the assignment would have been safe if allowed.当然,我们知道WT ,如果允许,分配将是安全的。 The root problem here, is that getClass() loses type information.这里的根本问题是getClass()丢失了类型信息。 If x.getClass() returns Class<? extends X>如果x.getClass()返回Class<? extends X> Class<? extends X> without erasure, both of your examples will compile without even warning. Class<? extends X>而不擦除,您的两个示例都将在没有警告的情况下编译。 They indeed are type safe.它们确实是类型安全的。

Generics are a way to guarantee type safety. Generics 是一种保证类型安全的方法。 Eg:例如:

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 

types.类型。

By default arrays in Java are typeSafe.默认情况下,Java 中的 arrays 是类型安全的。 An integer array is only meant to contain integer and nothing else. integer 数组仅用于包含 integer,仅此而已。

Now:现在:

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

You willa gain get an exception like what it is not possible to cast Object instance to integer.您将获得一个异常,例如无法将 Object 实例转换为 integer。

The reason is that ArrayList stores object and not primitive like the above array.原因是 ArrayList 存储 object 而不是像上面的数组那样原始。

The correct way would be to explicitly cast to an integer.You have to do this because type safety is not yet guaranteed.正确的方法是显式转换为 integer。您必须这样做,因为尚未保证类型安全。 eg:例如:

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

To employ type safety for collections, you simply specify the type of objects that your collection contains.要为 collections 使用类型安全,您只需指定集合包含的对象的类型。 eg:例如:

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


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

eg:例如:

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

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

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