Нежелательная магия автобокса на номерах

Следующая программа печатает соответственно 'false' и 'true':

Number n = true ? new Long(1) : new Double(2.0);
System.out.println(n instanceof Long);
System.out.println(n instanceof Double);

Так что это будет не длинный, а двойной. Тем не менее, он работает, как и предполагалось на обычных классах:

class B {}
class D1 extends B {}
class D2 extends B {}

это выведет "true":

B b = true ? new D1() : new D2();
System.out.println(b instanceof D1);

Это означает, что он работает не так, как в примере выше.

Я уверен, что есть что-то, связанное с автобоксом, но так ли это на самом деле? Почему она использует бокс, когда класс Number является суперклассом Long и Double, чтобы выражение могло быть оценено как Number?

Это действительно боль, потому что при печати n он печатает как двойное значение. (Я знаю, что это легко обойти, но сводило меня с ума)

2 ответа

Решение

Давайте возьмем книгу языкового адвоката здесь: JLS §15.25

Тип условного выражения определяется следующим образом:

  • Если второй и третий операнды имеют одинаковый тип (который может быть нулевым), то это тип условного выражения.

Long и Double не одного типа - не применяется.

  • Если один из второго и третьего операндов имеет примитивный тип T, а тип другого является результатом применения преобразования в бокс (§5.1.7) к T, то тип условного выражения - T.

Ни одно из значений не является примитивным - не применяется.

  • Если один из второго и третьего операндов имеет нулевой тип, а тип другого является ссылочным типом, то тип условного выражения является этим ссылочным типом.

Ни одно из значений не является нулевым - не применяется.

  • В противном случае, если второй и третий операнды имеют типы, которые можно преобразовать ( §5.1.8) в числовые типы, то существует несколько случаев:
    • [... особые случаи для byte/short/char и их эквивалентов в штучной упаковке...]
    • В противном случае двоичное числовое продвижение (§ 5.6.2) применяется к типам операндов, а тип условного выражения является продвинутым типом второго и третьего операндов.

Это правило действительно здесь, что означает, что тип результата условного оператора такой, как если бы оба значения были распакованы. Предположим, что причина в том, что в противном случае Number n = bool ? 1 : 2.0 а также Number n = bool ? new Long(1) : new Double(2.0) имеют разные значения. Такое поведение также будет неожиданным и, что еще хуже, непоследовательным.

Его просто посмотрите на байт-код, и вы увидите (просто изменил ваш пример)

Number n = true ? new Long(166666) : new Double(24444.0);
System.out.println(Boolean.toString(n instanceof Long));
System.out.println(Boolean.toString(n instanceof Double));

Байт код

_new 'Java /Lang /Long'

dup
ldc 166666
invokespecial 'java/lang/Long.<init>','(J)V'
invokevirtual 'java/lang/Long.longValue','()J'
l2d
invokestatic 'java/lang/Double.valueOf','(D)Ljava/lang/Double;'
astore 1

Основной момент l2d это делает следующие шаги

Извлекает длинное целое число из стека, преобразует его в число с плавающей запятой двойной точности и помещает двойное число обратно в стек. Обратите внимание, что это может привести к потере точности (значение в двойном равно 54 битам по сравнению с 64 битами для длинных), но не к потере величины (поскольку диапазон двойного больше, чем диапазон длинного). Округление выполняется с использованием режима округления до ближайшего стандарта IEEE 754.

И после этого все хорошо, так что у вас будет экземпляр Double, но с Long Value! Если вы посмотрите в режиме отладки, вы увидите, что наш номер - Double, но значение из Long. Это описано выше в байт-коде.

Мы можем видеть это в байт-коде

getstatic 'java/lang/System.out','Ljava/io/PrintStream;'
aload 1
_instanceof 'java/lang/Long'
invokestatic 'java/lang/Boolean.toString','(Z)Ljava/lang/String;'
invokevirtual 'java/io/PrintStream.println','(Ljava/lang/String;)V'
getstatic 'java/lang/System.out','Ljava/io/PrintStream;'
aload 1
_instanceof 'java/lang/Double'
invokestatic 'java/lang/Boolean.toString','(Z)Ljava/lang/String;'
invokevirtual 'java/io/PrintStream.println','(Ljava/lang/String;)V'
return
Другие вопросы по тегам