我有一些类Box
的遗留代码,可以将Serializable
数据放入Map
,Oracle JRE 1.8 Update 102
在使用Oracle JDK 1.7 Update 80
编译时在Oracle JDK 1.8 Updater 102
上正常运行。但是当我使用get
编译它时,它无法正常运行。我在使用通用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")));
}
}
实例输出格式化日期的SSCCE:
get
使用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的引用不明确
而其他函数在unchecked or unsafe operations
函数运行正常。
编译器打印出关于 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
的警告,我注意到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: 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
编译为1.8:
Class<T> clazz
有人可以解释为什么编译方式不同吗?
PS:我已经通过将get
作为<body>
<script type="text/javascript" src="file:///Macintosh%20HD/Applications/MAMP/htdocs/thesurebettor/Untitled-2.js"></script>
<select id = "typeoption">
<option value="0">Qualifying Bet</option>
<option value="1">Free Bet</option>
<option value="2">Risk Free Bet</option>
</select>
<div style='display:none;' id='sreturned'> Stake Returned
</div>
</body>
函数的附加参数来修复它。
答案 0 :(得分:8)
你的方法
public <T extends Serializable> T get(String key){
return (T) this.values.get(key);
}
从根本上被打破,因为它基本上说“无论来电者希望如何,我都会将其归还,只要它可以分配给Serializable
”。
有趣的是,我们每隔几周就有类似的破碎方法,the last one just yesterday。
关键是,如果你的方法承诺返回调用者的意愿,我可以写:
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[] & Serializable
的{{1}}(另请参阅here)。
Java 7和Java 8之间的区别在于,当您将此方法调用作为参数添加到另一个调用(也称为“嵌套方法调用”)时,Java 7编译器不会执行此类型推断。它总是使用类型参数的边界,即T
,并发现它必须执行varargs调用。
相比之下,Java 8考虑了所有可能性。它可以推断非数组类型并执行varargs调用,但它也可以推断数组类型并将其直接传递给方法Serializable
。规则很简单,非vararg调用总是首选。
修复很简单。不要做出你无法忍受的承诺。
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()
。因此,JRE类也从未以这种方式引用Serializable
。最值得注意的是,即使ObjectOutputStream.writeObject(…)
在其签名中引用Serializable
,也只是接受Serializable
。
答案 1 :(得分:1)
除非你打算让这个代码存储混合对象,否则我建议让这个类通用:
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
public class Box<T extends Serializable> implements Serializable {
private HashMap<String, T> values = new HashMap<>();
public T get(String key){
return this.values.get(key);
}
public void put(String key,
T value){
this.values.put(key,
value);
}
public static void main(String[] args){
Box<Date> box = new Box<>();
box.put("key", new Date());
System.out.println(String.format("%1$td.%1$tm.%1$tY",
box.get("key")));
}
}
完成后,我会抛弃Box
并使用HashMap<String, ...>