уроки - создание графического интерфейса на java




Почему только конечные переменные доступны в анонимном классе? (9)

  1. a может быть только окончательным здесь. Зачем? Как переназначить метод onClick() не сохраняя его как частный член?

    private void f(Button b, final int a){
        b.addClickHandler(new ClickHandler() {
    
            @Override
            public void onClick(ClickEvent event) {
                int b = a*5;
    
            }
        });
    }
    
  2. Как я могу вернуть 5 * a когда он нажал? Я имею в виду,

    private void f(Button b, final int a){
        b.addClickHandler(new ClickHandler() {
    
            @Override
            public void onClick(ClickEvent event) {
                 int b = a*5;
                 return b; // but return type is void 
            }
        });
    }
    

Анонимный класс является внутренним классом, и строгое правило применяется к внутренним классам (JLS 8.1.3) :

Любая локальная переменная, параметр формального метода или параметр обработчика исключений, используемые, но не объявленные во внутреннем классе, должны быть объявлены окончательными . Любая локальная переменная, используемая, но не объявленная во внутреннем классе, должна быть обязательно назначена перед телом внутреннего класса .

Я еще не нашел причины или объяснения в jls или jvms, но мы знаем, что компилятор создает отдельный файл класса для каждого внутреннего класса, и он должен удостовериться, что методы, объявленные в этом файле класса ( на уровне байтового кода), по крайней мере, имеют доступ к значениям локальных переменных.

Джона есть полный ответ - я держу этот один удаленный, потому что может интересоваться правилом JLS)


Возможно, этот трюк дает вам идею

Boolean var= new anonymousClass(){
    private String myVar; //String for example
    @Overriden public Boolean method(int i){
          //use myVar and i
    }
    public String setVar(String var){myVar=var; return this;} //Returns self instane
}.setVar("Hello").method(3);

Как отмечается в комментариях, некоторые из них становятся неактуальными в Java 8, где final может быть неявным. Однако только эффективная конечная переменная может использоваться в анонимном внутреннем классе или лямбда-выражении.

В основном это связано с тем, как Java управляет closures .

Когда вы создаете экземпляр анонимного внутреннего класса, любые переменные, которые используются в этом классе, имеют свои значения, скопированные с помощью автогенерированного конструктора. Это позволяет компилятору автоматически генерировать различные дополнительные типы для хранения логического состояния «локальных переменных», как, например, компилятор C # ... (Когда C # захватывает переменную в анонимной функции, она действительно захватывает переменную - закрытие может обновлять переменную таким образом, который видна основной части метода, и наоборот).

Поскольку значение было скопировано в экземпляр анонимного внутреннего класса, было бы странно, если бы переменная могла быть изменена остальной частью метода - у вас мог бы быть код, который, казалось, работал с устаревшей переменной ( потому что это эффективно то, что будет происходить ... вы будете работать с копией, сделанной в другое время). Аналогично, если вы можете вносить изменения внутри анонимного внутреннего класса, разработчики могут ожидать, что эти изменения будут видны внутри тела входящего метода.

Создание переменной final удаляет все эти возможности - поскольку значение не может быть изменено вообще, вам не нужно беспокоиться о том, будут ли такие изменения видны. Единственные способы, позволяющие методу и анонимному внутреннему классу видеть изменения друг друга, - использовать изменяемый тип некоторого описания. Это может быть сам охватывающий класс, массив, изменяемый тип оболочки ... что-то в этом роде. В принципе, это немного похоже на связь между одним и тем же способом: изменения, внесенные в параметры одного метода, не видны его вызывающей стороне, но изменения, внесенные в объекты, на которые ссылаются параметры, видны.

Если вы заинтересованы в более детальном сравнении закрытия Java и C #, у меня есть article которая идет дальше. Я хотел сосредоточиться на стороне Java в этом ответе :)


Когда внутри тела метода определен анонимный внутренний класс, все переменные, объявленные окончательными в объеме этого метода, доступны из внутреннего класса. Для скалярных значений после его назначения значение конечной переменной не может измениться. Для значений объекта ссылка не может быть изменена. Это позволяет компилятору Java «захватить» значение переменной во время выполнения и хранить копию как поле во внутреннем классе. Когда внешний метод завершен и его стек стека удален, исходная переменная исчезла, но частная копия внутреннего класса сохраняется в собственной памяти класса.

( http://en.wikipedia.org/wiki/Final_%28Java%29 )


Ну, в Java переменная может быть окончательной не только как параметр, но как поле уровня класса, например

public class Test
{
 public final int a = 3;

или как локальная переменная, например

public static void main(String[] args)
{
 final int a = 3;

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

public class Test
{
 public int a;
 public void doSomething()
 {
  Runnable runnable =
   new Runnable()
   {
    public void run()
    {
     System.out.println(a);
     a = a+1;
    }
   };
 }
}

Вы не можете иметь переменную как final и дать ей новое значение. final означает именно это: значение неизменное и окончательное.

И так как это окончательно, Java может безопасно скопировать его в локальные анонимные классы. Вы не получаете некоторую ссылку на int (тем более, что вы не можете иметь ссылки на примитивы, такие как int в Java, просто ссылки на объекты ).

Он просто копирует значение a в неявный int, называемый a в вашем анонимном классе.


Попробуйте этот код,

Создайте список массивов и поместите в него значение и верните его:

private ArrayList f(Button b, final int a)
{
    final ArrayList al = new ArrayList();
    b.addClickHandler(new ClickHandler() {

         @Override
        public void onClick(ClickEvent event) {
             int b = a*5;
             al.add(b);
        }
    });
    return al;
}

Причина, по которой доступ ограничивается только локальными конечными переменными, заключается в том, что если все локальные переменные станут доступными, их сначала необходимо будет скопировать в отдельный раздел, где внутренние классы могут иметь к ним доступ и поддерживать несколько копий изменяемые локальные переменные могут приводить к несогласованным данным. В то время как конечные переменные неизменяемы и, следовательно, любое количество копий им не окажет никакого влияния на согласованность данных.


Существует трюк, позволяющий анонимному классу обновлять данные во внешней области.

private void f(Button b, final int a) {
    final int[] res = new int[1];
    b.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
            res[0] = a * 5;
        }
    });

    // But at this point handler is most likely not executed yet!
    // How should we now res[0] is ready?
}

Однако этот трюк не очень хорош из-за проблем с синхронизацией. Если обработчик вызывается позже, вам необходимо: 1) синхронизировать доступ к res, если обработчик был вызван из другого потока; 2) нужно иметь какой-то флаг или указание, что res был обновлен

Этот трюк работает нормально, хотя, если анонимный класс вызывается в том же потоке немедленно. Подобно:

// ...

final int[] res = new int[1];
Runnable r = new Runnable() { public void run() { res[0] = 123; } };
r.run();
System.out.println(res[0]);

// ...

private void f(Button b, final int a[]) {

    b.addClickHandler(new ClickHandler() {

        @Override
        public void onClick(ClickEvent event) {
            a[0] = a[0] * 5;

        }
    });
}






anonymous-class