Вызывает ли автоупаковка valueOf()?

Я пытаюсь определить, гарантированно ли верны следующие утверждения:

((Boolean)true) == Boolean.TRUE
((Boolean)true) == Boolean.valueOf(true)
((Integer)1) == Integer.valueOf(1)

Я всегда предполагал, что автоупаковка эквивалентна вызову valueOf() для соответствующего типа. Каждое обсуждение, которое я видел на тема кажется поддержите мое предположение. Но все, что я смог найти в JLS, это следующее (§5.1.7):

Если упаковываемое значение p представляет собой целочисленный литерал типа int между -128 и 127 включительно (§3.10.1), или логический литерал true или false (§3.10.3), или символьный литерал между '\u0000' и '\u007f' включительно (§3.10.3). 3.10.4), тогда пусть a и b будут результатом любых двух преобразований p в боксе. Всегда бывает так, что a == b.

Это описывает поведение, идентичное, похожее* на поведение valueOf(). Но, похоже, нет никакой гарантии, что valueOf() действительно вызывается, а это означает, что теоретически может существовать реализация, которая хранит отдельный выделенный кеш для автоматически упакованных значений. В таком случае может не быть равенства идентичности между кэшированными автоматически упакованными значениями и обычными кэшированными упакованными значениями.

В учебнике Oracle по автоупаковке говорится, что li.add(i) скомпилировано на li.add(Integer.valueOf(i)), где i – это int. Но я не знаю, следует ли считать учебник авторитетным источником.


*Это немного более слабая гарантия, чем valueOf(), так как она относится только к литеральным значениям.


person shmosel    schedule 16.07.2015    source источник
comment
@ Jean-FrançoisSavard Это не дубликат 408661. На самом деле я связался с этим в своем вопросе. Я знаю, что обычно он компилируется в valueOf(); мой вопрос заключается в том, дает ли JLS какие-либо гарантии в этом отношении.   -  person shmosel    schedule 16.07.2015
comment
Интересный теоретический вопрос. Могу я спросить, где вы хотите применить это?   -  person Andrei I    schedule 05.08.2015
comment
На такой вопрос сложно (но не невозможно) дать окончательный ответ, потому что он (технически говоря) требует, чтобы вы прочитали всю JLS от начала до конца и убедились, что таких гарантий нет. (Я задал вопрос с той же проблемой некоторое время назад.) При этом я искал весь JLS для valueOf, и ни одно из попаданий не было связано с автобоксом (только вещи о Enum.valueOf и т. д.). По-моему, это решает.   -  person aioobe    schedule 06.08.2015
comment
Вот мысль: если бы я написал компилятор, который не использует valueOf, или если бы javac переключился на другое решение, то любой выдаваемый новый байт-код был бы несовместим со старым байт-кодом, поскольку старый байт-код использовал valueOf для autoboxing, и два значения autoboxing должны (по крайней мере, при некоторых обстоятельствах) быть ссылочно эквивалентными. Теперь, чтобы связать это с формальным доказательством, нужно найти что-то в JLS, устанавливающее определенные гарантии для разделенных компиляций. Я сомневаюсь, что JLS охватывает такие темы.   -  person aioobe    schedule 06.08.2015
comment
@aioobe Что, если другое решение также использует механизм, основанный на кэшированных значениях?   -  person Jean-François Savard    schedule 06.08.2015
comment
Если этот другой механизм использует набор кэшированных значений, отличный от значений, кэшированных Integer.valueOf, то старый код автоупаковки не будет совместим с новым кодом автоупаковки. (Или, скорее, новый компилятор не будет совместим со старым компилятором, несмотря на то, что оба придерживаются JLS, что кажется немного противоречивым.)   -  person aioobe    schedule 06.08.2015
comment
@aioobe Но что, если этот механизм использует тот же набор кэшированных значений? Предположим, что для некоторых будущих функций (например, Value Types проекта Valhalla) необходимо сделать некоторые дополнения к механизму автоупаковки, которые вынуждают нас реализовывать более сложную архитектуру, чем просто вызов valueOf, но все же в корне изначально получаем кэшированные значения, затем мы автобоксинг больше не использует внутреннее значение valueOf, но при этом будет совместим со старым байт-кодом (я думаю). Я пытаюсь указать здесь, что я не думаю, что можно гарантировать такую ​​​​функцию...   -  person Jean-François Savard    schedule 06.08.2015
comment
... На самом деле, здесь мы должны гарантировать, что на данный момент valueOf является наиболее подходящим методом для автоупаковки и что нет причин (по крайней мере, очевидных) поступать иначе.   -  person Jean-François Savard    schedule 06.08.2015
comment
Если бы этот другой механизм использовал тот же набор значений, я думаю, компиляторы все равно были бы совместимы. Но если бы он использовал другой набор кэшированных значений, два компилятора были бы несовместимы (даже если бы оба придерживались JLS). Очевидно, что использование valueOf является подходящим и лучшим решением, но этот вопрос не об этом.   -  person aioobe    schedule 06.08.2015
comment
@Jean-FrançoisSavard Если обе реализации используют один и тот же кеш, для меня не имеет значения, действительно ли они используют valueOf(), поскольку разницы в поведении не будет.   -  person shmosel    schedule 06.08.2015
comment
@shmosel Я знаю это, просто пытаюсь указать, что мы не можем гарантировать, как это делается внутри.   -  person Jean-François Savard    schedule 06.08.2015
comment
@aioobe Согласно вашим рассуждениям, любое неуказанное наблюдаемое поведение неявно становится частью определения JLS. Это противоречит общепринятому пониманию того, что любая спецификация не должна зависеть от недокументированного поведения. Это также технически невозможно, так как ваше правило может быть применено к двум компиляторам с различным недокументированным поведением, а фактическая спецификация может быть только одна.   -  person shmosel    schedule 06.08.2015
comment
В какой-то степени да... Вот почему я разместил это как комментарий, начинающийся с Вот мысль :-) Чтобы было ясно, я не считаю, что valueOf требуется.   -  person aioobe    schedule 06.08.2015
comment
@Jean-FrançoisSavard, как функция гипотетического будущего «заставит нас реализовать более сложную архитектуру, чем просто вызов valueOf», но в то же время предоставит значения, совместимые с существующими классами, вызывающими valueOf? Если valueOf продолжает делать правильные вещи, я не вижу причин не вызывать его в скомпилированном классе. JVM может внедрить что-нибудь более причудливое, заменив вызов во время выполнения. Так же, как это может произойти уже сегодня.   -  person Holger    schedule 09.01.2020


Ответы (4)


Сначала я подумал, что ваш вопрос был обманом Какой код генерирует компилятор для автобокс?

Однако после вашего комментария к @ElliottFrisch я понял, что все по-другому:

Я знаю, что компилятор ведет себя именно так. Я пытаюсь выяснить, гарантировано ли такое поведение.

Для других читателей предположим, что «ведет себя таким образом» означает использование valueOf.

Помните, что для Java существует несколько компиляторов. Чтобы быть «законными», они должны следовать контракту, указанному в JLS. Поэтому, пока все правила здесь соблюдаются, нет никакой гарантии того, как автобокс будет реализован внутри.

Но я не вижу причин не использовать valueOf, особенно потому, что он использует кэшированные значения и является рекомендуемым способом в соответствии с эта статья Джозефа Д. Дарси.

person Jean-François Savard    schedule 16.07.2015
comment
Пока компилятор создает байт-код Java, неиспользование valueOf нарушило бы совместимость с другими классами, созданными компилятором, использующим valueOf, особенно со всеми классами JRE эталонной реализации, поскольку гарантия того, что один и тот же объект предоставляется в определенных ситуациях, должна работать. по классам. Это одна из вещей, указанных в этом большом разрыве между JLS и JVMS, к которому привязан компилятор. - person Holger; 09.01.2020
comment
@Holger Можете ли вы привести пример того, что может быть затронуто? - person shmosel; 09.01.2020
comment
@shmosel JLS подразумевает, что когда у вас есть класс A с static Integer foo() { return 42; } и класс B с static Integer bar() { return 42; }, тогда A.foo() == B.bar() и не говорится «только при использовании одного и того же компилятора». Таким образом, когда один класс компилируется с javac, используя Integer.valueOf(int), другой класс должен получить Integer, также возвращаемый Integer.valueOf(int), независимо от того, какой компилятор использовался для B. Он может вставлять дополнительные механизмы кэширования, но источник объекта должен быть тем же, чтобы гарантировать гарантированное равенство ссылок. - person Holger; 09.01.2020

Пока это не упомянуто в спецификации языка, не гарантируется, что автоупаковка эквивалентна вызову статических valueOf методов. Это аспект реализации, а не часть спецификации преобразования упаковки. Теоретически реализация может свободно использовать другой механизм, если он соответствует правилу, которое вы упомянули из JLS.

На практике существует множество отчетов об ошибках Sun JDK (например, JDK-4990346 и JDK-6628737), которые явно подразумевают, что когда автоупаковка была введена в Java 5, намерение состояло в том, чтобы компилятор полагался на valueOf, как указано в JDK-6628737:

Статические фабричные методы Integer.valueOf(int), Long.valueOf(long) и т. д. были введены в JDK 5 для javac для реализации поведения кэширования, требуемого спецификацией автоупаковки.

Но это только для javac, не обязательно для всех компиляторов.

person M A    schedule 01.08.2015

Автоупаковка абсолютно реализована с использованием valueOf() ... в OpenJDK. Если это ваша реализация, читайте дальше... если нет, перейдите ниже.

((Boolean)true) == Boolean.TRUE
((Boolean)true) == Boolean.valueOf(true)

В документации Java указано, что Boolean.valueOf() всегда возвращает Boolean.TRUE или Boolean.FALSE, поэтому ваши эталонные сравнения в этих случаях будут успешными.

((Integer)1) == Integer.valueOf(1)

Для этого конкретного примера в реализации OpenJDK с настройками по умолчанию он вероятно будет работать благодаря тому факту, что вы выбрали значение ‹ 128, которое кэшируется при запуске (хотя это можно переопределить как командную строку аргумент). Он может также работать для больших значений, если он используется достаточно часто, чтобы его можно было кэшировать. Если вы не работаете с «безопасными» предположениями о целочисленном кеше, не ожидайте, что сравнение ссылок будет равенством.

Long, Short, Character и Byte кстати тоже реализуют это кэширование, но в отличие от Integer оно не настраивается. Byte всегда будет работать, если вы сравниваете ссылки autobox/valueOf(), поскольку, очевидно, вы не можете выйти за пределы допустимого диапазона. Неудивительно, что Float и Double всегда будут создавать новый экземпляр.


Теперь, в чисто общих терминах? см. этот раздел JLS — вам ДОЛЖНЫ быть даны одинаковые ссылки для boolean и любых int или char в диапазоне от -128 до 127. Ни для чего другого нет гарантий.

person Olipro    schedule 07.08.2015

В учебнике Oracle по автоупаковке прозаично указано, что li.add(i) компилируется в li.add(Integer.valueOf(i)), где i — целое число. Но я не знаю, следует ли считать учебник авторитетным источником.

Я использую Oracle Java 1.7.0_72, похоже, он использует valueOf. Ниже приведен некоторый код и байт-код для него. Байт-код показывает, что он использует valueOf.

public class AutoBoxing {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        Integer x = 5;
        int i = x;
        System.out.println(x.toString());
    }

}





Compiled from "AutoBoxing.java"
public class testing.AutoBoxing {
  public testing.AutoBoxing();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_5
       1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       4: astore_1
       5: aload_1
       6: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
       9: istore_2
      10: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      13: aload_1
      14: invokevirtual #5                  // Method java/lang/Integer.toString:()Ljava/lang/String;
      17: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      20: return

Но я не знаю, что использует Open JDK. Попробую.

person Jose Martinez    schedule 31.07.2015
comment
Как это отвечает на вопрос? - person Jean-François Savard; 31.07.2015
comment
@ Jean-FrançoisSavard, я не нашел в исходном сообщении предложений, оканчивающихся вопросительными знаками. Так что я догадался, что последнее предложение прозвучало для меня как вопрос. Пост состоит из нескольких утверждений, обнаружений и заканчивается на But I don't know whether the tutorial should be considered an authoritative source. . Так что для меня это выглядело как вопрос, достойный решения. - person Jose Martinez; 31.07.2015
comment
@JoseMartinez, первое предложение было вопросом, несмотря на отсутствие вопросительного знака. Если бы я искал неофициальные доказательства, я мог бы ответить на свой вопрос, просто выполнив свой собственный код. - person shmosel; 02.08.2015
comment
Я не совсем понимаю. JLS не упоминает реализацию. Oracle Java упоминает, как он это делает. Что именно вы ищете, помимо повторения вам этих фактов? Единственная другая вещь, о которой вы могли бы просить, что, как мне кажется, вы и сделали, - это доказательство того, что Oracle Java действительно использует valueOf(). - person Jose Martinez; 02.08.2015
comment
Это, безусловно, лучший ответ. Если вам интересно, использует ли он valueOf, вот код, посмотрите сами. Затем вы говорите, что JLS не упоминает реализацию. Это достаточно хорошо! - person darijan; 06.08.2015
comment
@darijan Этого недостаточно. Корректность вашего кода не должна зависеть от среды его компиляции или выполнения, особенно на языке Java. - person shmosel; 06.08.2015
comment
Да, но это зависит от версии JDK. И в этом весь смысл. Вам необходимо проверить реализацию для любого интересующего вас JDK. Вы упомянули конкретно для Oracle JDK (и не упомянули конкретную версию JDK). Вы упомянули, что не знаете, является ли документ, который вы читаете, авторитетным источником. Есть ли конкретная версия Oracle JDK, о которой вам интересно узнать, или другая версия JDK, например Open JDK? - person Jose Martinez; 06.08.2015
comment
@JoseMartinez Когда я упоминал Oracle JDK? - person shmosel; 06.08.2015
comment
@JoseMartinez Даже в Oracle JDK использование неуказанной функции может нарушить гарантии обратной совместимости более поздней версии. - person shmosel; 06.08.2015