fir*_*uel 4 java generics java-8
我有一些遗留代码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")));
}
}
Run Code Online (Sandbox Code Playgroud)
使用JDK 1.8编译时,我得到以下异常,并使用JRE 1.8运行它:
线程"main"中的异常java.lang.ClassCastException:java.util.Date不能转换为[Ljava.lang.Object; 在Box.main(Box.java:31)
与get函数一起使用时,某些方法(如System.out.println)会产生编译器错误
错误:对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
Run Code Online (Sandbox Code Playgroud)
用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
Run Code Online (Sandbox Code Playgroud)
有人可以解释为什么编译方式不同吗?
PS:我已经通过给函数提供Class<T> clazz附加参数来修复它get.
你的方法
public <T extends Serializable> T get(String key){
return (T) this.values.get(key);
}
Run Code Online (Sandbox Code Playgroud)
从根本上打破,因为它基本上说"无论来电者的意愿如何,只要它可以分配给我,我就会把它归还Serializable".
有趣的是,我们每隔几周就有类似的破碎方法,最后一个就在昨天.
关键是,如果你的方法承诺返回调用者的意愿,我可以写:
Date date=box.get("key");
Run Code Online (Sandbox Code Playgroud)
但是也
String str=box.get("key");
String[] obj=box.get("key");
Run Code Online (Sandbox Code Playgroud)
由于所有这些类型Date,String或者String[]是分配给Serializable.不那么直观,你甚至可以写
Object[] obj=box.get("key");
Run Code Online (Sandbox Code Playgroud)
尽管Object[]不是Serializable,因为有可能是一个亚型Object[]是Serializable.所以编译器会推断出Object[] & Serializable的T(见这里).
Java 7和Java 8之间的区别在于,当您将此方法调用作为参数添加到另一个调用(也称为"嵌套方法调用")时,Java 7编译器不执行此类型推断.它总是使用类型参数的边界,即Serializable发现它必须执行varargs调用.
相比之下,Java 8考虑了所有可能性.它可以推断非数组类型并执行varargs调用,但它也可以推断数组类型并将其直接传递给方法String.format(String,Object[]).规则很简单,非vararg调用总是首选.
修复很简单.不要做出你无法忍受的承诺.
public Serializable get(String key) {
return this.values.get(key);
}
Run Code Online (Sandbox Code Playgroud)
并让调用者明确地进行类型转换.
Date date=(Date)box.get("key");
Run Code Online (Sandbox Code Playgroud)
或者在需要任意对象时不进行强制转换:
System.out.println(String.format("%1$td.%1$tm.%1$tY", box.get("key")));
Run Code Online (Sandbox Code Playgroud)
顺便说一下,这是一个错综复杂的变种
System.out.printf("%1$td.%1$tm.%1$tY%n", box.get("key"));
Run Code Online (Sandbox Code Playgroud)
或者,您可以使用Class对象指定期望的类型:
public <T extends Serializable> T get(String key, Class<T> type) {
return type.cast(this.values.get(key));
}
Run Code Online (Sandbox Code Playgroud)
...
Date date=box.get("key", Date.class);
Run Code Online (Sandbox Code Playgroud)
顺便说一句,Serializable明确指出没有实际的好处.有很多地方可以返回可序列化的对象Collections.emptyList(),例如,请参阅,而不进行声明Serializable.因此,JRE类也从未引用过Serializable这种方式.最值得注意的是,甚至没有在其签名中ObjectOutputStream.writeObject(…)提及Serializable,而是接受Object.
| 归档时间: |
|
| 查看次数: |
595 次 |
| 最近记录: |