首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >Java 8自动装箱+泛型:变量与方法的不同行为

Java 8自动装箱+泛型:变量与方法的不同行为
EN

Stack Overflow用户
提问于 2017-01-11 19:43:00
回答 4查看 1.4K关注 0票数 17

我发现了一段从Java 7切换到Java 8后停止编译的代码。它没有任何新的Java 8特性,比如lambda或stream。

我将有问题的代码范围缩小到以下情况:

代码语言:javascript
复制
GenericData<Double> g = new GenericData<>(1d);
Double d = g == null ? 0 : g.getData(); // type error!!!

您可能会猜到,GenericData的构造函数有一个该泛型类型的参数,而getData()方法只返回该泛型类型。(有关完整的源代码,请参阅下面)。

现在让我困扰的是,在Java 7中,代码编译得很好,而在Java 8中,我得到了以下错误:

代码语言:javascript
复制
CompileMe.java:20: error: incompatible types: bad type in conditional expression
Double d = g == null ? 0 : g.getData();
                       ^
int cannot be converted to Double

看起来Java7能够完成从int -> Double -> Double的转换,但是Java8尝试立即从int -> double转换失败了。

我发现特别有趣的是,当我将代码从getData()更改为data时,Java8确实接受代码,即通过变量本身而不是GenericData方法来访问方法的值:

代码语言:javascript
复制
Double d2 = g == null ? 0 : g.data; // now why does this work...

所以我这里有两个问题:

  1. 为什么Java8不能推断出像Java7这样的类型,并在自动装箱double到double之前将int转换为Double?
  2. 为什么这个问题只出现在泛型方法上,而不是泛型变量上?

完整的源代码:

代码语言:javascript
复制
public class CompileMe {
    public void foo() {
        GenericData<Double> g = new GenericData(1d);
        Double d = g == null ? 0 : g.getData(); // type error!!!
        Double d2 = g == null ? 0 : g.data; // now why does this work...
    }
}

class GenericData<T> {
    public T data;
    public GenericData(T data) {
        this.data = data;
    }
    public T getData() {
        return data;
    }
}

要测试它,请运行编译器,如下所示:

代码语言:javascript
复制
javac -source 1.7 -target 1.7 CompileMe.java   # ok (just warnings)
javac -source 1.8 -target 1.8 CompileMe.java   # error (as described above)

最后,以防万一:我运行的是Windows8和Java 1.8.0_112 (64位)。

EN

回答 4

Stack Overflow用户

发布于 2017-01-11 23:36:05

方法调用表达式是特殊的,因为它们可以是Poly表达式,取决于目标类型。

考虑以下示例:

代码语言:javascript
复制
static Double aDouble() {
    return 0D;
}
…
Double d = g == null ? 0 : aDouble();

编译过程中没有任何问题

代码语言:javascript
复制
static <T> T any() {
    return null;
}
…
Double d = g == null ? 0 : any();

在这里,any()的调用是一个Poly表达式,编译器必须推断T := Double。这将重现相同的错误。

这是第一个不一致的地方。虽然您的方法getData()引用了GenericData的类型参数T,但它不是泛型方法(这里不涉及/应该不涉及类型推断来确定TDouble

JLS §8.4.4. Generic Methods

如果方法声明了一个或多个类型变量,则该方法是泛型的

getData()没有声明类型变量,它只使用了一个。

JLS §15.12. Method Invocation Expressions

如果满足以下所有条件,则方法调用表达式是多边形表达式:

  • 由以下小节确定的要调用的方法是泛型的(§8.4.4),并且具有至少一个方法的类型参数的返回类型。

由于此方法调用不是多重表达式,因此它的行为应该类似于使用aDouble()调用的示例,而不是any()

但请注意§15.25.3

请注意,引用条件表达式不必将多边形表达式作为操作数包含,以便成为多边形表达式。由于它出现的上下文,它是一个多重表达式。例如,在下面的代码中,条件表达式是一个多边形表达式,并且每个操作数都被视为位于针对Class<? super Integer>__的赋值上下文中:

Class<?超级返回选择(boolean b,c1类,c2类){ Integer> b?c1 : c2;}

那么,它是引用条件表达式还是数值条件表达式呢?

§15.25. Conditional Operator ? :说:

根据第二个和第三个操作数表达式,有三种条件表达式:布尔条件表达式、数值条件表达式和引用条件表达式。分类规则如下:

  • 如果第二个和第三个操作数表达式都是布尔表达式,则条件表达式是布尔条件表达式。

  • 如果第二个和第三个操作数表达式都是数值表达式,则条件表达式是数值条件表达式。

为了对条件进行分类,以下表达式是数值表达式:

  • 具有可转换为数值类型的类型的独立形式的表达式(§15.2) (§4.2,§5.1.8)。
  • 带括号的数值表达式(§15.8.5)。
  • 可转换为数值类型的类的类实例创建表达式(§15.9)。
  • 选择的最具体方法(§15.12.2.5)具有可转换为数值类型的返回类型的方法调用表达式(§15.12)。
  • 数值条件表达式

  • 否则,条件表达式为引用条件表达式。

因此,根据这些规则,不排除泛型方法调用,所有显示的条件表达式都是数值条件表达式,并且应该起作用,因为只有“否则”它们才被认为是引用条件表达式。我测试的Eclipse版本编译了所有这些代码,但没有报告任何错误。

这导致了奇怪的情况,对于any()的情况,我们需要目标类型来发现它有一个数值返回类型,并推断条件是一个数值条件表达式,即一个独立的表达式。请注意,对于布尔条件表达式,有以下备注:

请注意,对于泛型方法,这是实例化方法的类型参数之前的类型。

但是对于数值条件表达式,无论是有意还是无意,都没有这样的注释。

但如上所述,这只适用于any()示例,因为getData()方法不是泛型的。

票数 14
EN

Stack Overflow用户

发布于 2017-01-12 22:31:13

这似乎是Oracle编译器的一个已知问题:Bug ID: JDK-8162708

引用:

对问题的描述:

如果在泛型类中有一个方法声明如下:

类Foo { public T getValue() { //返回值... }}

在一个三元运算符中调用上面的方法,如下所示

Foo foo =新的Foo<>();浮动f=新的随机().nextBoolean()?foo.getValue():0f;

您会从javac 1.8编译器中得到一个语法错误。

但是上面的代码使用javac 1.7和1.9编译时没有错误和警告。

解决方案:未解决

受影响的版本:8

从评论中:

此问题仅适用于8u,在7和9

中没有问题

票数 5
EN

Stack Overflow用户

发布于 2017-01-11 23:14:23

不得不说,这不是一个答案,仅仅是一个推理。以我在编译器方面的短暂经验(不是特定于Javac ),它可能与代码的解析方式有关。

在下面的反编译代码中,您可以看到调用方法GenericData.getData:()Ljava/lang/Object或引用字段GenericData.data:Ljava/lang/Object,它们都首先获得返回Object的值/方法,然后是一个cast

代码语言:javascript
复制
  stack=4, locals=4, args_size=1
     0: new           #2                  // class general/GenericData
     3: dup
     4: dconst_1
     5: invokestatic  #3                  // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
     8: invokespecial #4                  // Method general/GenericData."<init>":(Ljava/lang/Object;)V
    11: astore_1
    12: aload_1
    13: ifnonnull     23
    16: dconst_0
    17: invokestatic  #3                  // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
    20: goto          30
    23: aload_1
    24: invokevirtual #5                  // Method general/GenericData.getData:()Ljava/lang/Object;
    27: checkcast     #6                  // class java/lang/Double
    30: astore_2
    31: aload_1
    32: ifnonnull     39
    35: dconst_0
    36: goto          49
    39: aload_1
    40: getfield      #7                  // Field general/GenericData.data:Ljava/lang/Object;
    43: checkcast     #6                  // class java/lang/Double
    46: invokevirtual #8                  // Method java/lang/Double.doubleValue:()D
    49: invokestatic  #3                  // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
    52: astore_3
    53: return

如果将三元运算符表达式与等效的If -else进行比较:

代码语言:javascript
复制
Integer v = 10;
v = v != null ? 1 : 0;

     0: bipush        10
     2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     5: astore_1
     6: aload_1
     7: ifnull        14
    10: iconst_1
    11: goto          15
    14: iconst_0
    15: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
    18: astore_1
    19: return

Integer v = 10;
if (v != null)
    v = 1;
else
    v = 0;

     0: bipush        10
     2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     5: astore_1
     6: aload_1
     7: ifnull        18
    10: iconst_1
    11: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
    14: astore_1
    15: goto          23
    18: iconst_0
    19: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
    22: astore_1
    23: return

这两个版本没有显著差异。所以我不认为有一个隐藏的秘密在做所有的黑魔法。这是编译器如何解析整个表达式的结果,并基于上下文来找出一个类型,使所有组件都同样满意。例如,

代码语言:javascript
复制
Double val = 0; // compilation error: context is clear, 0 is an integer, so Integer.valueOf(i), but don't match expected type - Double
val = 0 + g.getData(); // OK, enough context to figure out the type should be Double

尽管如此,令人困惑的是为什么泛型字段可以工作,而泛型方法不能...

代码语言:javascript
复制
val = val == null ? 0 : g.data; // OK
val = val == null ? 0 : g.getData(); // Compilation error

编辑: Holger引用的文档似乎是一个很好的澄清。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/41590024

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档