Java 8 autoboxing +泛型:变量与方法的不同行为 [英] Java 8 autoboxing + generics: different behaviour with variable vs. method

查看:153
本文介绍了Java 8 autoboxing +泛型:变量与方法的不同行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我发现从Java 7切换到Java 8后停止编译的一段代码。它没有任何新的Java 8的东西,如lambda或流。



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

  GenericData< Double> g = new GenericData<>(1d); 
Double d = g == null? 0:g.getData(); //输入错误!

您大概可以猜到 GenericData 的s构造函数具有该泛型类型的一个参数,并且 getData()方法仅返回该泛型类型。 (有关完整的源代码,请参阅下面的内容。)

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

  CompileMe.java:20:错误:不兼容的类型:条件表达式中的错误类型
Double d = g == null ? 0:g.getData();
^
int不能转换为Double

看起来Java 7是能够完成从int - > double - > Double的转换,但是Java 8试图立即从int - > Double转换失败。



我特别感兴趣的是当我将它从 getData()更改为 data 确实接受了代码>,即通过变量本身而不是getter-method访问 GenericData 的值:

  Double d2 = g == null? 0:g.data; //现在为什么这么做... 

所以我在这里提出的两个问题是: p>


  1. 为什么Java 8无法推断像Java 7这样的类型,并且在自动装箱之前将我的int类型的值加倍为double?


  2. $ b

    完整的源代码:

  3. 为什么这个问题只发生在泛型方法而不是泛型变量?
  4. p>

      public class CompileMe {
    public void foo(){
    GenericData< Double> g =新的GenericData(1d);
    Double d = g == null? 0:g.getData(); //输入错误!
    Double d2 = g == null? 0:g.data; //现在为什么这样工作...
    }
    }

    class GenericData< T> {
    public T data;
    public GenericData(T data){
    this.data = data;
    }
    public T getData(){
    返回数据;






    $ b要测试它,运行编译器如下: p>

      javac -source 1.7 -target 1.7 CompileMe.java#ok(只是警告)
    javac -source 1.8 - 目标1.8 CompileMe .java#error(如上所述)

    最后如果重要,我运行Windows 8和Java 1.8.0_112(64-bit)。

    解决方案

    方法调用表达式的特殊之处在于它们可以是 Poly Expressions <



    请考虑以下示例:

      static Double aDouble(){
    return 0D;
    }
    ...
    Double d = g == null? 0:aDouble();

    这个编译没有任何问题

      static< T> T any(){
    return null;
    }
    ...
    Double d = g == null? 0:any();

    这里,调用 any()是一个表达式,编译器必须推断 T:= Double 。这再现了同样的错误。



    这是第一个不一致。尽管您的方法 getData()引用 GenericData T $ c>,它不是一个泛型方法(有/应该没有类型推断来确定 T Double here。



    JLS§8.4.4。通用方法


    一种方法是 generic em>如果它声明一个或多个类型变量


    getData() doesn 't声明类型变量,它只使用一个。



    JLS§15.12。方法调用表达式


    方法调用表达式是一个poly表达式,如果全部以下是真的:


    • ...

    • 要调用的方法,小节,是g eneric(§8.4.4),并且返回类型至少提到了一个方法的类型参数。

    由于这个方法调用不是一个poly表达式,它应该像 aDouble()调用的例子,而不是任何 any ()



    但是请注意§15.25.3


    请注意,引用条件表达式不必包含poly表达式作为操作数以便成为poly表达式。这是一种多元表达,仅凭其出现的上下文而已。例如,在下面的代码中,条件表达式是一个多表达式,并且每个操作数都被认为是在一个赋值上下文中,其目标是 Class< ;?超级整数>

      Class <?超整型>选择(boolean b,
    Class< Integer> c1,
    Class< Number> c2){
    return b? c1:c2;









    $ p $一个数字条件表达式?



    §15.25。有条件的运营商? :表示:


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


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

      ...

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

      For对条件进行分类的目的是,以下表达式是数字表达式:


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

      • 一个数值条件表达式。


    • 否则,条件表达式是一个引用条件表达式。因此,根据这些规则,不排除通用方法调用,所有显示的条件表达式都是数值条件表达式,并且应该起作用,因为只有否则它们才被认为是参考条件表达式。我测试过的Eclipse版本在不报告任何错误的情况下编译了所有这些版本。



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


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


      但是对于数字条件表达式,没有这样的注释,无论是否有意为之。



      但是,如上所述,无论如何,这仅适用于 any()示例,因为 getData()方法不是 通用的。


      I found a piece of code that after switching from Java 7 to Java 8 stopped compiling. It does not feature any of the new Java 8 stuff like lambda or streams.

      I narrowed the problematic code down to the following situation:

      GenericData<Double> g = new GenericData<>(1d);
      Double d = g == null ? 0 : g.getData(); // type error!!!
      

      You can probably guess that GenericData's constructor has one parameter of that generic type and the getData() method returns just that generic type. (For the complete source code see below.)

      Now what bothers me is that in Java 7 that code compiled just fine whereas with Java 8 I get the following error:

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

      It seems that Java 7 was able to do the transition from int -> double -> Double, but Java 8 fails with trying to immediately go from int -> Double.

      What I find funny in particular is that Java 8 does accept the code when I change it from getData() to data, i.e. access the GenericData's value via the variable itself instead of the getter-method:

      Double d2 = g == null ? 0 : g.data; // now why does this work...
      

      So the two questions I have here are:

      1. Why doesn't Java 8 infer the types like Java 7 and cast my int to double before autoboxing double to Double?
      2. Why does that problem only occur with the generic method but not the generic variable?

      Complete source code:

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

      To test it run the compiler as follows:

      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)
      

      Finally in case it matters: I run Windows 8 and Java 1.8.0_112 (64-bit).

      解决方案

      Method invocation expressions are special in that they may be Poly Expressions, subject to target typing.

      Consider the following examples:

      static Double aDouble() {
          return 0D;
      }
      …
      Double d = g == null ? 0 : aDouble();
      

      this compiles without any problems

      static <T> T any() {
          return null;
      }
      …
      Double d = g == null ? 0 : any();
      

      here, the invocation of any() is a Poly Expression and the compiler has to infer T := Double. This reproduces the same error.

      This is the first inconsistency. While your method getData() refers to the type parameter T of GenericData, it is not a generic method (there is/should be no type inference involved to determine that T is Double here.

      JLS §8.4.4. Generic Methods

      A method is generic if it declares one or more type variables

      getData() doesn’t declare type variables, it only uses one.

      JLS §15.12. Method Invocation Expressions:

      A method invocation expression is a poly expression if all of the following are true:

      • The method to be invoked, as determined by the following subsections, is generic (§8.4.4) and has a return type that mentions at least one of the method's type parameters.

      Since this method invocation is not a poly expression, it should behave like the example with the aDouble() invocation, rather than the any().

      But note §15.25.3:

      Note that a reference conditional expression does not have to contain a poly expression as an operand in order to be a poly expression. It is a poly expression simply by virtue of the context in which it appears. For example, in the following code, the conditional expression is a poly expression, and each operand is considered to be in an assignment context targeting Class<? super Integer>:

      Class<? super Integer> choose(boolean b,
                                    Class<Integer> c1,
                                    Class<Number> c2) {
          return b ? c1 : c2;
      }
      

      So, is it a reference conditional or a numeric conditional expression?

      §15.25. Conditional Operator ? : says:

      There are three kinds of conditional expressions, classified according to the second and third operand expressions: boolean conditional expressions, numeric conditional expressions, and reference conditional expressions. The classification rules are as follows:

      • If both the second and the third operand expressions are boolean expressions, the conditional expression is a boolean conditional expression.
      • If both the second and the third operand expressions are numeric expressions, the conditional expression is a numeric conditional expression.
        For the purpose of classifying a conditional, the following expressions are numeric expressions:
        • An expression of a standalone form (§15.2) with a type that is convertible to a numeric type (§4.2, §5.1.8).
        • A parenthesized numeric expression (§15.8.5).
        • A class instance creation expression (§15.9) for a class that is convertible to a numeric type.
        • A method invocation expression (§15.12) for which the chosen most specific method (§15.12.2.5) has a return type that is convertible to a numeric type.
        • A numeric conditional expression.
      • Otherwise, the conditional expression is a reference conditional expression.

      So according to these rules, not precluding generic method invocations, all of the shown conditional expressions are numeric conditional expression and should work, as only "otherwise" they are to be considered to be reference conditional expression. The Eclipse version, I tested, compiled all of them without reporting any error.

      This leads to the strange situation that for the any() case we need target typing to find out that it has a numeric return type and deducing that the conditional is a numeric conditional expression, i.e. a stand-alone expression. Note that for boolean conditional expressions, there is the remark:

      Note that, for a generic method, this is the type before instantiating the method's type arguments.

      but for numeric conditional expression, there’s no such note, whether intentional or not.

      But as said, this only applies to the any() example anyway, as the getData() method is not generic.

      这篇关于Java 8 autoboxing +泛型:变量与方法的不同行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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