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

问题描述

我有类 Box 的遗留代码,用于将 Serializable 数据放入 Map ,它在使用 Oracle JDK 1.7 Update 80 上运行正常$ 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运行它:

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

其他函数在 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 (另请参阅在其签名中引用 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:

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

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编译通用方法时发生故障的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

05-26 19:12
查看更多