在 Java 7 中是否允许从 Number 转换为 double? (自动装箱)

Is casting from Number to double allowed in Java 7? (Autoboxing)

一位同事检查了这段代码:

    Number n = ...;
    double nr = n == null ? 0.0 : (double) n;

然后另一位同事抱怨说这没有编译,这正是我所期望的。然而,事实证明我已经从 SVN 中提取了这段代码并且一切正常。我们都在 eclipse 中将 Java 版本设置为 1.7,结果证明代码在 eclipse 4.4.2 (Luna) 下编译正常但在 4.2.2 下失败。

我通过将演员表替换为 n.doubleValue() 解决了这个问题。

现在真正的问题是:为什么这首先被接受?它当然应该在转换为 Double 而不是 double 时起作用,但我认为不允许直接从 Number 转换为 double。那么,这是同时修复的 eclipse 4.2.2 中的一个错误,还是 eclipse 4.4.2 默默地接受不应编译的代码(恕我直言,这将是一个错误)?

抽象class数字是classes BigDecimal、BigInteger、Byte、Double、Float、Integer、Long 和 Short 的超class。

Double 是 double 的包装器 class,支持自动装箱但不支持数字。换句话说,Number 可以包含许多不是 Double 的包装器 classes,因此您需要显式转换。 试试这个代码:

Number n = 78.3145;
double nr = (double) n;
Double d = 3.1788;
double dr = d;
System.out.println(n);
System.out.println(dr);

如评论中所述,Number 没有相应的原始类型,但 Java 编译器似乎很聪明,一旦你进行了转换,它就会在后台执行转换。

这是一个示例测试用例

打包测试;

public class 数字自动装箱 {

public static void main(String[] args) {
    Number n =new Long(1);
    double nr = (double) n;
    Integer i= 1;//boxing
    Integer j= new Integer(1);
    int k=j;//unboxing
    System.out.print("n="+n+" nr=" + nr + " i="+ i + " k=" + k);
}

}

我反编译了 .class(我在 windows 上测试了 jdk 7 和 8,结果是一样的)这就是结果

import java.io.PrintStream;

public class NumberAutoboxing
{
  public static void main(String[] args)
  {
    Number n = new Long(1L);
    double nr = ((Double)n).doubleValue();
    Integer i = Integer.valueOf(1);
    Integer j = new Integer(1);
    int k = j.intValue();
    System.out.print("n=" + n + " nr=" + nr + " i=" + i + " k=" + k);
  }
}

如您所见,从数字到双精度的转换方式与拆箱示例中的方式相同(从整数到整数); 那是因为编译器将 castdouble 更改为 Double,从而允许拆箱。似乎有两个 comilation fases(或者我推测的类似的东西)

  1. 意识到因为 n 是一个数字,演员表必须从 双倍到双倍
  2. 开箱

在 Java 7 中,必须对原始类型的转换系统稍作更改,以允许使用从方法句柄的方法签名派生的 MethodHandles. When invoking a method handle, the javac compiler generates a so-called polymorhic signature。这些多态签名是通过使用强制转换暗示参数的类型来创建的。例如,绑定签名为 double, long -> int 的方法时,需要进行以下转换:

Number foo = 42d, bar = 43L;
int ignored = (int) methodHandle.invoke((double) object, (long) bar);

然而MethodHandle::invoke的源代码签名定义为Object[] -> Object,没有直接将值转换为原始类型,无法生成多态签名。

显然,要实现这一点,必须更改 Java 编译器以允许此类以前不合法的强制转换。虽然理论上可以将这种转换的使用限制在用 @PolymorhicSignature 注释的方法中,但这会导致一个奇怪的异常,为什么它现在通常可以在 javac 在不创建多态签名时生成适当的字节代码。然而,原始类型仍然代表它们自己的运行时类型,这在另一个答案中指出,该答案在 MethodHandle

之外发布了此类转换的生成字节码
Object foo = 42;
int.class.cast(foo);

会导致运行时异常。

但是,我同意 JLS 中未必适当讨论的评论,但我 found a thread mentioning this specification gap。有人提到一旦 lambda 表达式到位就应该相应地更新规范,但是 Java 8 的 JLS 似乎没有提到这样的转换或 @PolymorphicSignature 。同时声明[a]ny 未明确允许的转换是禁止的.

可以说,JLS 目前落后于 javac 实现,Eclipse 编译器肯定没有正确地选择它。您可以将其与一些 corner-cases of generic type inference 进行比较,其中几个 IDE 编译器的行为与 javac 直到今天都不同。