使用JDK 8编译通用方法时发生故障 [英] Broken cast on compiling generic method with JDK 8

查看:105
本文介绍了使用JDK 8编译通用方法时发生故障的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有类 Box 的遗留代码,用于将 Serializable 数据放入 Map ,它在使用 Oracle JDK 1.7 Update 80 Oracle JRE 1.8 Update 102 上运行正常$ C>。但是,当我使用 Oracle JDK 1.8更新程序102 进行编译时,它无法正常运行。我有一个通用的 get 函数的问题。



一个SSCCE,它从 b









$ import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;

public class Box实现Serializable {

private HashMap< String,Serializable> values = new HashMap< String,Serializable>();

public< T extends Serializable> T get(String key){

return(T)this.values.get(key);
}

public void put(String key,
Serializable value){

this.values.put(key,
value) ;


public static void main(String [] args){

Box box = new Box();
box.put(key,
new Date());

System.out.println(String.format(%1 $ td。%1 $ tm。%1 $ tY,
box.get(key)));


$ / code $ / pre>

使用JDK编译时,出现以下异常1.8和我使用JRE 1.8运行它:


线程main中的异常java.lang.ClassCastException:java.util.Date不能是投射到[Ljava.lang.Object;
在Box.main(Box.java:31)


一些方法如System.out.println会产生编译器错误当与获取函数一起使用时
$ b


错误:对println的引用不明确 p>

其他函数在 get 函数中正常运行。



编译器输出一个关于 unchecked或unsafe operations 的警告,我注意到主要方法被编译为不同的字节码:



编译为1.7:

  public static void main(java.lang.String [ ]); 
Code:
0:new#8 // class Box
3:dup
4:invokespecial#9 // Method< init>:()V
7:astore_1
8:aload_1
9:ldc#10 // String key
11:new#11 // class java / util / Date
14:dup
15:invokespecial#12 //方法java / util / Date。< init>:()V
18:invokevirtual#13 //方法put :( Ljava / lang / String; Ljava / io / Serializable;)V
21:getstatic#14 //字段java / lang / System.out:Ljava / io / PrintStream;
24:ldc#15 // String%1 $ td。%1 $ tm。%1 $ tY
26:iconst_1
27:anewarray#16 // class java / lang / Object
30:dup
31:iconst_0
32:aload_1
33:ldc#10 //字符串键$ b $ 35:invokevirtual#17 //方法get :( Ljava /郎/字符串;)Ljava / IO /串行化;
38:aastore
39:invokestatic#18 //方法java / lang / String.format:(Ljava / lang / String; [Ljava / lang / Object;)Ljava / lang / String;
42:invokevirtual#19 //方法java / io / PrintStream.println:(Ljava / lang / String;)V
45:return

编译为1.8:

  public static void main(java。 lang.String []); 
Code:
0:new#8 // class Box
3:dup
4:invokespecial#9 // Method< init>:()V
7:astore_1
8:aload_1
9:ldc#10 // String key
11:new#11 // class java / util / Date
14:dup
15:invokespecial#12 //方法java / util / Date。< init>:()V
18:invokevirtual#13 //方法put :( Ljava / lang / String; Ljava / io / Serializable;)V
21:getstatic#14 //字段java / lang / System.out:Ljava / io / PrintStream;
24:ldc#15 //字符串%1 $ td。%1 $ tm。%1 $ tY
26:aload_1
27:ldc#10 //字符串键
29:invokevirtual#16 //方法get:(Ljava / lang / String;)Ljava / io / Serializable;
32:checkcast#17 // class[Ljava / lang / Object;
35:invokestatic#18 //方法java / lang / String.format:(Ljava / lang / String; [Ljava / lang / Object;)Ljava / lang / String;
38:invokevirtual#19 //方法java / io / PrintStream.println:(Ljava / lang / String;)V
41:return

有人可以解释为什么它的编译方式不同吗? b
$ b

通过给予 Class< T>来修复它。 clazz 作为获取函数的附加参数。

解决方案

您的方法

  public< T extends Serializable> T get(String key){

return(T)this.values.get(key);
}

基本上被打破了,因为它基本上说无论呼叫者希望如何,它只要它可以赋值给 Serializable



有趣的是,我们每隔几周就会有类似的破坏方法在这里,昨天的最后一个



关键点是,如果你的方法承诺返回任何调用者的愿望,我可以写:

  Date date = box.get(key ); 

还有

  String str = box.get(key); 
String [] obj = box.get(key);

所有这些类型 Date String String [] 可赋值给 Serializable 。不太直观的是,你甚至可以写出

  Object [] obj = box.get(key); 

尽管 Object [] 不是 Serializable ,因为可能存在 Object [] 的子类型,它是 Serializable 。所以编译器会推断出 Object []&可串行化用于 T (另请参阅此处)。




Java 7与Java 8的区别在于,Java 7编译器在执行此类型推断时您将此方法调用作为参数传递给另一个调用(又名嵌套方法调用)。它始终使用类型参数的边界,即 Serializable ,并发现它必须执行可变参数调用。



相比之下,Java 8考虑了所有可能性。它可以推断非数组类型并执行可变参数调用,但它也可以推断数组类型并将其直接传递给方法 String.format(String,Object [])。规则很简单,总是首选非可变参数调用。

修复很简单。

  public Serializable get(String key){
return this。 values.get(键);
}

并让调用者明确地进行类型转换。

 日期日期=(日期)box.get(key); 

或者在需要任意对象时不投射:

  System.out.println(String.format(%1 $ td。%1 $ tm。%1 $ tY,box.get(key))) ; 

这就是一个复杂的变体

  System.out.printf(%1 $ td。%1 $ tm。%1 $ tY%n,box.get(key)); 

或者,您可以使用 Class 对象以指定预期的类型:

  public< T extends Serializable> T get(String key,Class< T> type){
return type.cast(this.values.get(key));
}

...

  Date date = box.get(key,Date.class); 

顺便说一下,明确指出 Serializable 没有真正的好处。有很多地方可以返回可序列化的对象,例如,参见 Collections.emptyList(),而不需要声明 Serializable 。因此,JRE类永远不会以这种方式引用 Serializable 。最值得注意的是,甚至没有 ObjectOutputStream.writeObject(...) 在其签名中引用 Serializable ,但只接受 Object


I have some legacy code with class Box to put and get Serializable data into a Map, which runs fine on Oracle JRE 1.8 Update 102 when compiled with Oracle JDK 1.7 Update 80. But it don't run properly when I compile it with Oracle JDK 1.8 Updater 102. I had some problems with a generic get function.

A SSCCE which outputs a formatted date from a Box instance using a problematic generic get function:

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;

public class Box implements Serializable{

   private HashMap<String, Serializable> values = new HashMap<String, Serializable>();

   public <T extends Serializable> T get(String key){

      return (T) this.values.get(key);
   }

   public void put(String key,
                   Serializable value){

      this.values.put(key,
                      value);
   }

   public static void main(String[] args){

      Box box = new Box();
      box.put("key",
              new Date());

      System.out.println(String.format("%1$td.%1$tm.%1$tY",
                                       box.get("key")));
   }
}

I get the following exception when it is compiled with JDK 1.8 and I run it with JRE 1.8:

Exception in thread "main" java.lang.ClassCastException: java.util.Date cannot be cast to [Ljava.lang.Object; at Box.main(Box.java:31)

Some Methods like System.out.println produces a compiler error when used with the get function

error: reference to println is ambiguous

while other function runs fine with the get function.

The compiler prints out a warning about unchecked or unsafe operations and I noticed the main method is compiled to different byte code:

Compiled with 1.7:

  public static void main(java.lang.String[]);
    Code:
       0: new           #8                  // class Box
       3: dup
       4: invokespecial #9                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #10                 // String key
      11: new           #11                 // class java/util/Date
      14: dup
      15: invokespecial #12                 // Method java/util/Date."<init>":()V
      18: invokevirtual #13                 // Method put:(Ljava/lang/String;Ljava/io/Serializable;)V
      21: getstatic     #14                 // Field java/lang/System.out:Ljava/io/PrintStream;
      24: ldc           #15                 // String %1$td.%1$tm.%1$tY
      26: iconst_1
      27: anewarray     #16                 // class java/lang/Object
      30: dup
      31: iconst_0
      32: aload_1
      33: ldc           #10                 // String key
      35: invokevirtual #17                 // Method get:(Ljava/lang/String;)Ljava/io/Serializable;
      38: aastore
      39: invokestatic  #18                 // Method java/lang/String.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
      42: invokevirtual #19                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      45: return

Compiled with 1.8:

  public static void main(java.lang.String[]);
    Code:
       0: new           #8                  // class Box
       3: dup
       4: invokespecial #9                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #10                 // String key
      11: new           #11                 // class java/util/Date
      14: dup
      15: invokespecial #12                 // Method java/util/Date."<init>":()V
      18: invokevirtual #13                 // Method put:(Ljava/lang/String;Ljava/io/Serializable;)V
      21: getstatic     #14                 // Field java/lang/System.out:Ljava/io/PrintStream;
      24: ldc           #15                 // String %1$td.%1$tm.%1$tY
      26: aload_1
      27: ldc           #10                 // String key
      29: invokevirtual #16                 // Method get:(Ljava/lang/String;)Ljava/io/Serializable;
      32: checkcast     #17                 // class "[Ljava/lang/Object;"
      35: invokestatic  #18                 // Method java/lang/String.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
      38: invokevirtual #19                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      41: return

Can somebody explain why it is compiled differently?

PS: I already fixed it by giving Class<T> clazz as additional parameter to the get function.

解决方案

Your method

public <T extends Serializable> T get(String key){

  return (T) this.values.get(key);
}

is fundamentally broken as it basically says "whatever the caller wishes, I will return it, as long as it is assignable to Serializable".

Interestingly, we have similar broken methods every few weeks here, the last one just yesterday.

The key point is, if your method promises to return whatever the caller wishes, I could write:

Date date=box.get("key");

but also

String str=box.get("key");
String[] obj=box.get("key");

As all these types, Date, String, or String[] are assignable to Serializable. Less intuitively, you can even write

Object[] obj=box.get("key");

despite Object[] is not Serializable, because there could be a subtype of Object[] that is Serializable. So the compiler will infer Object[] & Serializable for T (see also here).


The difference between Java 7 and Java 8 is that the Java 7 compiler did not perform this type inference when you put this method invocation as an argument to another invocation (aka "nested method call"). It always used the bounds of the type parameter, i.e. Serializable and found that it has to perform a varargs invocation.

In contrast, Java 8 considers all possibilities. It can infer a non-array type and perform a varargs invocation, but it can also infer an array type and pass it directly to the method String.format(String,Object[]). The rules are simple, a non-vararg invocation is always preferred.

The fix is simple. Don’t make promises you can’t hold.

public Serializable get(String key) {
   return this.values.get(key);
}

and let the caller do the type cast explicitly.

Date date=(Date)box.get("key");

or no cast when an arbitrary object is needed:

System.out.println(String.format("%1$td.%1$tm.%1$tY", box.get("key")));

which is by the way a convoluted variant of

System.out.printf("%1$td.%1$tm.%1$tY%n", box.get("key"));

Alternatively, you can use a Class object to specify the expected type:

public <T extends Serializable> T get(String key, Class<T> type) {
   return type.cast(this.values.get(key));
}

Date date=box.get("key", Date.class);

By the way, referring to Serializable explicitly has no real benefit. There are plenty of place, where serializable objects are returned, see Collections.emptyList(), for example, without declaring Serializable. Consequently, the JRE classes never refer to Serializable this way either. Most notably, not even ObjectOutputStream.writeObject(…) refers to Serializable in its signature, but just accepts Object.

这篇关于使用JDK 8编译通用方法时发生故障的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆