Есть ли разница между непосредственным назначением конечной переменной и назначением конечной переменной в конструкторе?

есть ли разница между этими двумя инициализациями конечной переменной value?

class Test {
    final int value = 7;
    Test() {}
}

а также

class Test {
    final int value;
    Test() {
        value = 7;
    }
}

--

EDIT: более сложный пример, включающий подклассы. В этом случае на стандартный вывод печатается «0», но 7 печатается, если я назначаю значение напрямую.

import javax.swing.*;
import java.beans.PropertyChangeListener;

class TestBox extends JCheckBox {

    final int value;

    public TestBox() {
        value = 7;
    }

    public void addPropertyChangeListener(PropertyChangeListener l) {
        System.out.println(value);
        super.addPropertyChangeListener(l); 
    }

    public static void main(String... args) {
        JFrame frame = new JFrame();
        JPanel panel = new JPanel();
        frame.setContentPane(panel);
        panel.add(new TestBox());
        frame.pack();
        frame.setVisible(true);
    }
}

person scravy    schedule 02.12.2011    source источник
comment
Этот пример немного неясен: где вызывается addPropertyChangeListener()?   -  person Viruzzo    schedule 02.12.2011
comment
@Viruzzo Я подозреваю, что он вызывается в конструкторе JCheckBox или JComponent или ... Код представляет собой урезанный пример пользовательского компонента JCheckBox, который я сейчас создаю, который имеет некоторые специальные свойства (например, некоторые другие свойства, для которых я хочу иметь возможность регистрировать PropertyChangeListeners)   -  person scravy    schedule 02.12.2011
comment
Если конструктор суперкласса вызовет метод подкласса, это будет ошибкой, если вообще возможно. Этот пример, вероятно, ошибочен из-за использования JFrame и возможных проблем с параллелизмом.   -  person Viruzzo    schedule 02.12.2011
comment
@Viruzzo addPropertyChangeListener — это виртуальный метод (как и все методы в Java), поэтому конструктор суперклассов будет вызывать TextBox.addPropertyChangeListener, если он вообще вызывает addPropertyChangeListener. Или я что-то не так понимаю? О каких проблемах параллелизма вы думаете в отношении потока событий AWT (поскольку я не создаю никаких других потоков)?   -  person scravy    schedule 02.12.2011
comment
Значение case = 0 возможно только в том случае, если TestBox.addPropertyChangeListener() вызывается внутри TestBox.super(), и это кажется очень странным поведением, или... Пробовали ли вы использовать одинарные фигурные скобки вместо двойных в JFrame и Инициализация JPanel? Я не могу проверить прямо сейчас, но это может иметь смысл.   -  person Viruzzo    schedule 02.12.2011
comment
Я изменил второй пример, чтобы он был немного менее неясным. Те же эффекты.   -  person scravy    schedule 02.12.2011


Ответы (7)


Пробовал на очень простом примере, и да, при доступе к значению в родительском конструкторе оно унитализируется (как и должно быть), если оно не является окончательным, и инициализируется при объявлении. Этот процесс описан в EJP, но с шагом #0: финальные значения инициализируются указанным значением, если оно есть.

person Viruzzo    schedule 02.12.2011

Существует разница в уровне байт-кода:

Исходный код:

  final int value;

  public TestBox() {
      value = 7;
  }

Производит следующий код из addPropertyChangeListener:

   0:   getstatic       #3; 
   3:   aload_0
   4:   getfield        #2; 
   7:   invokevirtual   #4; 

И исходный код:

final int value = 7;

public TestBox() {      
}

Производит следующий код из addPropertyChangeListener:

   0:   getstatic       #3; 
   3:   bipush  7
   5:   invokevirtual   #4; 

Так что есть небольшая разница. Но не практично.

Кажется, что компилятор может обрабатывать конечную переменную как константу, если она инициализирована в операторе определения. Конечно, разные компиляторы могут делать это по-разному.

person SKi    schedule 02.12.2011
comment
Вау. Спасибо за разборку. Этот small difference на самом деле взрывает целый компонент в моем программном обеспечении (это всего лишь урезанный пример, который я разместил здесь). - person scravy; 02.12.2011
comment
взрывается? Это странно, потому что функциональность должна быть такой же. Должны возникать только несущественные различия в скорости или использовании памяти. - person SKi; 02.12.2011
comment
В полном коде есть объект PropertyChangeSupport-Object, который еще не создан при вызове метода addPropertyChangeListener(). Теперь я обращаюсь к нему, только если он не равен нулю, иначе я получаю NPE при создании CheckBox. Я просто задавался вопросом, как это может быть. - person scravy; 02.12.2011

Распространенное неправильное толкование переменной final состоит в том, что она не может изменить свое значение. Фактическое значение модификатора final (JLS 4.5.4) заключается в том, что «переменная final может быть назначена только один раз».

Вы столкнулись с одной из ситуаций, когда можно оценить так называемую «пустую конечную» (объявленную, но еще не назначенную) переменную, чтобы она оценивалась как значение по умолчанию для указанного типа данных, даже если она была назначена позже. другое значение.

person jarnbjo    schedule 02.12.2011

Другое дело, что во втором случае вы можете присвоить другое значение в зависимости от того, какой конструктор был вызван или какие параметры были ему переданы, НЕТ.

person SJuan76    schedule 02.12.2011
comment
всегда ли это верно, то есть для подклассов? - person scravy; 02.12.2011
comment
@scravy: конструктор подклассов должен будет вызвать super(...), который инициализирует конечное поле на основе аргумента, переданного в super(...). Так что да, это справедливо для подклассов - person JB Nizet; 02.12.2011
comment
Я добавил еще один пример, связанный с подклассами, который я абсолютно не могу объяснить (кроме того, что с JCheckBox происходит что-то странное). - person scravy; 02.12.2011

Нет, нет. Единственная разница заключается в порядке, используемом для инициализации полей: поля, инициализируемые напрямую, инициализируются перед теми, которые инициализируются в конструкторе.

person JB Nizet    schedule 02.12.2011

Я не думаю, что есть какая-то разница. Но значение, которое вам нужно в нем, может помочь вам решить, что использовать.

  1. Если переменная является окончательной и ей присваивается фиксированное значение в конструкторе, то нет необходимости назначать ее в конструкторе.
  2. Если переменная является окончательной и ей присваиваются разные значения, переданные в качестве аргумента в конструкторе, вам необходимо присвоить ее в конструкторе.

В основном используется первый случай. Потому что, насколько мне известно, final varibales — это не что иное, как константы.

person Harry Joy    schedule 02.12.2011

Конструктор выполняется в следующем порядке:

  1. super() вызывается.
  2. Инициализируются локальные переменные и вызываются анонимные блоки инициализатора {}.
  3. Вызывается код в самом конструкторе. Обратите внимание, что если конструктор сам явно вызывает super(), об этом позаботится пункт #1.

Таким образом, ответ на ваш вопрос заключается в том, что инициализация будет перемещена с № 2 в версии объявления с инициализатором на № 3 в версии инициализации в конструкторе. Однако, если у вас нет анонимных блоков инициализатора {} или, возможно, полей, которые инициализируются с использованием предыдущих инициализаций других полей, было бы невозможно определить разницу.

person user207421    schedule 02.12.2011
comment
так что super() (#1) вызывается даже перед прямым присвоением final int value = 7; (#2)? - person scravy; 02.12.2011
comment
@scravy (#1) да (#2) поля, а не переменные, но вы поняли; Блоки инициализатора такие же, как статические блоки инициализатора, но для примера: в основном вы помещаете некоторый код между {} вне любого метода, и он вызывается на шаге № 2. - person Viruzzo; 02.12.2011
comment
@EJP Я добавил еще один пример, связанный с вызовом подкласса super. Если я правильно понял, super() следует вызывать до инициализации value в любом случае, однако я вижу 0 один раз и 7, если я назначаю переменную напрямую. - person scravy; 02.12.2011