Нежелательная магия автобокса на номерах
Следующая программа печатает соответственно '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