使用 JDK 8 编译泛型方法时发生损坏

2024-01-05

我有一些带有类的遗留代码Box放置和获取Serializable数据转化为Map,运行良好Oracle JRE 1.8 Update 102当编译时Oracle JDK 1.7 Update 80。但是当我编译它时它无法正常运行Oracle JDK 1.8 Updater 102。我在使用泛型时遇到了一些问题get功能。

一个 SSCCE,它输出一个格式化的日期Box使用有问题的泛型的实例get功能:

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

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

线程“main”中的异常 java.lang.ClassCastException: java.util.Date 无法转换为 [Ljava.lang.Object; 在 Box.main(Box.java:31)

某些方法(如 System.out.println)与以下方法一起使用时会产生编译器错误get功能

错误:对 println 的引用不明确

而其他功能运行良好get功能。

编译器打印出一条警告unchecked or unsafe operations我注意到 main 方法被编译为不同的字节代码:

用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

用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

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

PS:我已经通过给予修复了它Class<T> clazz作为附加参数get功能。


你的方法

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

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

从根本上来说,它基本上是说“无论调用者希望什么,我都会返回它,只要它可以分配给Serializable”.

有趣的是,我们每隔几周就会有类似的损坏方法,最后一张就在昨天 https://stackoverflow.com/q/40703415/2711488.

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

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

but also

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

与所有这些类型一样,Date, String, or String[]可分配给Serializable。不太直观,你甚至可以写

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

despite Object[] is not Serializable,因为可能有一个子类型Object[]那是Serializable。所以编译器会推断Object[] & Serializable for T(也可以看看here https://stackoverflow.com/a/36403072/2711488).


Java 7 和 Java 8 之间的区别在于,当您将此方法调用作为另一个调用(也称为“嵌套方法调用”)的参数时,Java 7 编译器不会执行此类型推断。它始终使用类型参数的边界,即Serializable并发现它必须执行可变参数调用。

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

修复方法很简单。不要做出你无法兑现的承诺。

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

并让调用者显式进行类型转换。

Date date=(Date)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(…) https://docs.oracle.com/javase/8/docs/api/java/io/ObjectOutputStream.html#writeObject-java.lang.Object-指的是Serializable在其签名中,但只是接受Object.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 JDK 8 编译泛型方法时发生损坏 的相关文章

随机推荐