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




уроки создание (12)

  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 
            }
        });
    }
    

Answers

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

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

Я помню, что в Smalltalk вы получите незаконный магазин, созданный, когда вы сделаете такую ​​модификацию.


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

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

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;
}

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

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


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

    b.addClickHandler(new ClickHandler() {

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

        }
    });
}

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

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

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

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


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

class A {
    int k = 0;
    private void f(Button b, int a){
        b.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
            k = a * 5;
        }
    });
}

теперь вы можете получить значение K и использовать его там, где хотите.

Ответ: почему:

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

Поскольку локальный внутренний класс не является членом класса или пакета, он не объявляется с уровнем доступа. (Будьте уверены, однако, что его собственные члены имеют уровни доступа, как в обычном классе.)


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

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]);

// ...

Чтобы понять обоснование этого ограничения, рассмотрим следующую программу:

public class Program {

    interface Interface {
        public void printInteger();
    }
    static Interface interfaceInstance = null;

    static void initialize(int val) {
        class Class implements interface {
            @Override
            public void printInteger() {
                System.out.println(val);
            }
        }
        interfaceInstance = new Class();
    }

    public static void main(String[] args) {
        initialize(12345);
        interfaceInstance.printInteger();
    }
}

ИнтерфейсInstance остается в памяти после возврата метода initialize , но параметр val не работает. JVM не может получить доступ к локальной переменной за пределами ее области, поэтому Java делает последующий вызов printInteger , копируя значение val в неявное поле с тем же именем в интерфейсе InterfaceInstance . Говорят, что интерфейсInstance захватил значение локального параметра. Если параметр не был окончательным (или фактически окончательным), его значение могло бы измениться, становясь не синхронизированным с захваченным значением, что потенциально вызывает неинтуитивное поведение.


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

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, чтобы избежать проблемы с алмазом при множественном наследовании .

Если вы хотите, чтобы все ваши методы были реализованы вашим клиентом, вы идете на интерфейс. Это означает, что вы разрабатываете приложение в абстрактном виде.

Вы выбираете абстрактный класс, если уже знаете, что общего. Например, возьмите абстрактный класс Car. На более высоком уровне вы реализуете общие автомобильные методы calculateRPM(). Это распространенный метод, и вы позволяете клиенту реализовать свое собственное поведение, как
calculateMaxSpeed()и т. Д. Вероятно, вы бы объяснили, указав несколько примеров в реальном времени, с которыми вы столкнулись в повседневной работе.





java event-handling anonymous-class