Java编译器选择错误的重载 [英] Java compiler choosing wrong overload

查看:108
本文介绍了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)重载。这会导致单元测试失败,并显示以下错误消息:

For years, this unit test has been passing. Then after upgrading to Java 8, in certain environments, when the code is compiled via javac, it chooses the StringUtils.isNullOrEmpty(String) overload. This causes the unit test to fail with the following error message:

java.lang.ClassCastException:java.util.Date无法强制转换为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)的另一个上失败。

The unit tests passes when compiled and run on my machine via ant (ant 1.9.6, jdk_8_u60, Windows 7 64bit) but fails on another with the same versions of ant and java (ant 1.9.6 jdk_8_u60, Ubuntu 12.04.4 32bit).

Java的类型推断,其中在编译时从所有适用的重载中选择最具体的重载,在Java 8中进行了更改。我认为我的问题与此有关。

Java's type inference, which chooses the most specific overload from all applicable overloads when compiling, has been changed in Java 8. I assume my issue has something to do with this.

我知道编译器看到了MyProperties.getProperty(...)方法的返回类型为T,而不是Date。由于编译器不知道getProperty(...)方法的返回类型,为什么它选择StringUtils.isNullorEmpty(String)而不是StringUtils.isNullorEmpty(Object) - 它应该始终有效?

I know the compiler sees the return type of the MyProperties.getProperty(...) method as T, not Date. Since the compiler doesn't know the return type of the getProperty(...) method, why is it choosing StringUtils.isNullorEmpty(String) instead of StringUtils.isNullorEmpty(Object) - which should always work?

这是Java中的错误还是Java 8类型推断更改的结果?另外,为什么使用相同版本的java的不同环境会以不同方式编译此代码?

Is this a bug in Java or just a result of Java 8's type inference changes? Also, why would different environments using the same version of java compile this code differently?

推荐答案

此代码闻起来。是的,这是在Java 7下传递的,是的,它可以在Java 7中运行,但是这里有一些肯定是错误的

This code smells. Yes, this passes under Java 7, and yes it runs alright with Java 7, but there is something definitely wrong here.

首先,让我们来看看谈谈这种通用类型。

First, let's talk about this generic type.

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

您能否一目了然地推断 T 吗?如果我使用IntelliJ在Java 7兼容模式下运行那些强制转换,我会回到这个非常有帮助的 ClassCastException

Can you at a glance infer what T should be? If I run those casts in Java 7 compliance mode with IntelliJ at that exact line, I get back this very helpful ClassCastException:


无法将java.util.Date强制转换为T

所以这意味着在某种程度上,Java知道这里有一些东西,但它选择将该转换从(T)更改为(对象)

So this implies that at some level, Java knew there was something off here, but it elected to change that cast instead from (T) to (Object).

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

在这种情况下,演员阵容是多余的,你得到一个<$正如您所料,来自地图的c $ c>对象。然后,调用正确的重载。

In this case, the cast is redundant, and you get back an Object from the map, as you would expect. Then, the correct overload is called.

现在,在Java 8中,事情变得更加明智;因为你没有真正为 getProperty 方法提供类型,所以它会爆炸,因为真的无法强制转换 java.util.Date T

Now, in Java 8, things are a bit more sane; since you don't truly provide a type to the getProperty method, it blows up, since it really can't cast java.util.Date to T.

最终,我对主要观点进行了掩饰:

Ultimately, I'm glossing over the main point:

你甚至不需要需要泛型。您的代码可以处理 String Object ,并且您的地图只包含 Object s无论如何。

You don't even need generics here. Your code can handle either a String or an Object, and your map only contains Objects anyway.

你应该只从返回对象 getProperty 方法,因为无论如何你只能从你的地图返回。

You should only return Object from the getProperty method, since that's what you can only return from your map anyhow.

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

这意味着您不再能够直接致电 进入方法,签名为 String (因为你现在传递对象),但这确实意味着你的破碎的泛型代码最终会被搁置。

It does mean that you no longer get the ability to call directly into the method with a signature of String (since you're passing an Object in now), but it does mean that your broken generics code can finally be put to rest.

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

If you really want to preserve this behavior though, you would have to introduce a new parameter into your function that actually allowed you to specify which type of object you wanted back from your map.

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

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

Then you could invoke your method thus:

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

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

Now we are absolutely certain as to what T is, and Java 8 is content with this code. This is still a bit of a smell, since you're storing things in a Map<String, Object>; if you've got the Object overridden method and you can guarantee that all objects in that map have a meaningful toString, then I would personally avoid the above code.

这篇关于Java编译器选择错误的重载的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆