Java 编译器选择了错误的重载

标签 java

<分区>

@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;
    }

}

多年来,这个单元测试一直通过。然后升级到Java 8后,在某些环境下,通过javac编译代码时,选择了StringUtils.isNullOrEmpty(String)重载。这会导致单元测试失败并显示以下错误消息:

java.lang.ClassCastException: java.util.Date 无法转换为 java.lang.String 在 com.foo.bar.StringUtils_UT.test(StringUtils_UT.java:35)

单元测试通过 ant(ant 1.9.6、jdk_8_u60、Windows 7 64 位)在我的机器上编译和运行时通过,但在另一个具有相同版本的 ant 和 java(ant 1.9.6 jdk_8_u60、Ubuntu 12.04。 4 32 位)。

Java 的 type inference ,它在编译时从所有适用的重载中选择最具体的重载,已在 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);
}

你能一眼看出是什么吗T 应该是?如果我在 Java 7 合规模式下使用 IntelliJ 在该行运行这些强制转换,我会得到这非常有用的信息 ClassCastException :

Cannot cast java.util.Date to T

所以这意味着在某种程度上,Java 知道这里有问题,但它选择更改该转换而不是 (T)(Object) .

@SuppressWarnings("unchecked")
public <T> Object getProperty(String name) {
    return (Object) properties.get(name);
}

在这种情况下,转换是多余的,你会得到一个 Object如您所料,从 map 上看。然后,调用正确的重载。

现在,在 Java 8 中,事情变得更理智了;因为您并没有真正为 getProperty 提供类型方法,它爆炸了,因为它真的不能施放 java.util.DateT .


最后,我掩盖了要点:

泛型的这种使用是不正确的。

在这里您甚至不需要泛型。您的代码可以处理 StringObject ,并且您的 map 仅包含 Object无论如何。

你应该只返回 Object来自 getProperty方法,因为这是您无论如何只能从 map 返回的内容。

public Object getProperty(String name) {
    return properties.get(name);
}

这确实意味着您不再能够直接调用带有String 签名的方法。 (因为您现在传递的是 Object),但这确实意味着您损坏的泛型代码终于可以安息了。


如果您真的想要保留这种行为,则必须在您的函数中引入一个新参数,该参数实际上允许您指定您想要从 map 返回的对象类型。

@SuppressWarnings("unchecked")
public <T> T getProperty(String name, Class<T> clazz) {
    return (T) properties.get(name);
}

然后你可以这样调用你的方法:

StringUtils.isNullOrEmpty(props.getProperty("value", Date.class));

现在我们绝对确定T是什么是的,Java 8 满足于此代码。这仍然有点味道,因为您将东西存储在 Map<String, Object> 中;如果你有 Object覆盖方法,您可以保证该 map 中的所有对象都具有有意义的 toString , 那么我个人会避免上面的代码。

关于Java 编译器选择了错误的重载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32507155/

相关文章:

java - Spring 如何使用 Java 8 类,却又在 Java 7 上运行?

java - 如何使用 .map()/.filters() 添加不同类中存在的不同变量

java - 如何让转义字符适用于 MS Word API 中的换行符?

java - Maven - 查看依赖树而不构建项目

java - JSch SSH 连接在初始化反向隧道时抛出 NPE

java - 在JPanel上绘制坐标系

java - Spring mvc 与 hibernate,如何添加 Restful 服务端点?

java - 将泛型用于 Java 类的属性

java 。如何使Jtable特定单元格不可选?

java - Webdriver 使用 sendKeys 输入长字符串的最简单方法?