@Test
public void test() {
MyProperties props = new MyProperties();
props.setProperty("value", new Date());
StringUtils.isNullOrEmpty(props.getProperty("value"));
}
public class MyProperties {
private Map<String, Object> properties = new HashMap<String, Object>();
public void setProperty(String name, Object value) {
properties.put(name, value);
}
@SuppressWarnings("unchecked")
public <T> T getProperty(String name) {
return (T) properties.get(name);
}
}
public class StringUtils {
public static boolean isNullOrEmpty(Object string) {
return isNullOrEmpty(valueOf(string));
}
public static String valueOf(Object string) {
if (string == null) {
return "";
}
return string.toString();
}
public static boolean isNullOrEmpty(String string) {
if (string == null || string.length() == 0) {
return false;
}
int strLength = string.length();
for (int i = 0; i < strLength; i++) {
char charAt = string.charAt(i);
if (charAt > ' ') {
return true;
}
}
return false;
}
}
Run Code Online (Sandbox Code Playgroud)
多年来,这个单元测试已经过去了.然后在升级到Java 8之后,在某些环境中,当通过javac编译代码时,它会选择StringUtils.isNullOrEmpty(String)重载.这会导致单元测试失败,并显示以下错误消息:
java.lang.ClassCastException: java.util.Date cannot be cast to java.lang.String
at com.foo.bar.StringUtils_UT.test(StringUtils_UT.java:35)
单元测试通过ant(ant 1.9.6,jdk_8_u60,Windows 7 64bit)在我的机器上编译和运行时通过,但是在使用相同版本的ant和java(ant 1.9.6 jdk_8_u60,Ubuntu 12.04.4 32bit)的另一个上失败.
Java的类型推断在编译时从所有适用的重载中选择了最具体的重载,在Java 8中已经改变了.我认为我的问题与此有关.
我知道编译器将MyProperties.getProperty(...)方法的返回类型视为T,而不是Date.由于编译器不知道getProperty(...)方法的返回类型,为什么它选择StringUtils.isNullorEmpty(String)而不是StringUtils.isNullorEmpty(Object) - 它应该始终有效?
这是Java中的错误还是Java 8类型推断更改的结果?另外,为什么使用相同版本的java的不同环境会以不同方式编译此代码?
这段代码闻起来很香.是的,这在Java 7下传递,是的,它在Java 7上运行正常,但这里肯定存在一些错误.
首先,我们来谈谈这种通用类型.
@SuppressWarnings("unchecked")
public <T> T getProperty(String name) {
return (T) properties.get(name);
}
Run Code Online (Sandbox Code Playgroud)
你能一眼看出T 应该是什么吗?如果我在具有IntelliJ的Java 7兼容模式下运行那些演员,那么我会回到这个非常有帮助的地方ClassCastException:
Cannot cast java.util.Date to T
因此,这意味着,在一定程度上,爪哇知道有什么在这里下车,但它选择从而是改变投(T)来(Object).
@SuppressWarnings("unchecked")
public <T> Object getProperty(String name) {
return (Object) properties.get(name);
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,演员阵容是多余的,你可以Object从地图上找回,就像你期望的那样.然后,调用正确的重载.
现在,在Java 8中,事情变得更加明智; 因为你没有真正为getProperty方法提供类型,所以它会爆炸,因为它实际上无法转换java.util.Date为T.
最终,我正在掩饰主要观点:
你甚至不需要泛型.您的代码可以处理a String或an Object,并且您的地图Object无论如何都只包含s.
你应该只Object从getProperty方法返回,因为那是你无论如何只能从地图返回的东西.
public Object getProperty(String name) {
return properties.get(name);
}
Run Code Online (Sandbox Code Playgroud)
它确实意味着你不再能够使用签名直接调用方法String(因为你现在正在传入Object),但它确实意味着你的破坏的泛型代码最终可以被搁置.
如果你真的想要保留这种行为,你必须在你的函数中引入一个新参数,它实际上允许你指定你想从地图中返回哪种类型的对象.
@SuppressWarnings("unchecked")
public <T> T getProperty(String name, Class<T> clazz) {
return (T) properties.get(name);
}
Run Code Online (Sandbox Code Playgroud)
然后你可以调用你的方法:
StringUtils.isNullOrEmpty(props.getProperty("value", Date.class));
Run Code Online (Sandbox Code Playgroud)
现在我们绝对肯定是什么T,Java 8满足于这段代码.这仍然有点气味,因为你把东西存放在Map<String, Object>; 如果你有被Object覆盖的方法,你可以保证该地图中的所有对象都有意义toString,那么我个人会避免上面的代码.
| 归档时间: |
|
| 查看次数: |
975 次 |
| 最近记录: |