Как передать параметры анонимному классу?

Можно ли передать параметры или получить доступ к внешним параметрам анонимному классу? Например:

int myVariable = 1;

myButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        // How would one access myVariable here?
    }
});

Есть ли способ для слушателя получить доступ к myVariable или быть переданным myVariable без создания слушателя как фактического именованного класса?

12 ответов

Решение

Технически нет, потому что анонимные классы не могут иметь конструкторов.

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

редактирование: как указал Питер, вы также можете передавать параметры в конструктор суперкласса анонимного класса.

Да, добавив метод инициализатора, который возвращает 'this', и немедленно вызвав этот метод:

int myVariable = 1;

myButton.addActionListener(new ActionListener() {
    private int anonVar;
    public void actionPerformed(ActionEvent e) {
        // How would one access myVariable here?
        // It's now here:
        System.out.println("Initialized with value: " + anonVar);
    }
    private ActionListener init(int var){
        anonVar = var;
        return this;
    }
}.init(myVariable)  );

Не требуется "окончательная" декларация.

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

Как это:

final int myVariable = 1;

myButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        // Now you can access it alright.
    }
});

Это сделает волшебство

int myVariable = 1;

myButton.addActionListener(new ActionListener() {

    int myVariable;

    public void actionPerformed(ActionEvent e) {
        // myVariable ...
    }

    public ActionListener setParams(int myVariable) {

        this.myVariable = myVariable;

        return this;
    }
}.setParams(myVariable));

Как показано на http://www.coderanch.com/t/567294/java/java/declare-constructor-anonymous-class вы можете добавить инициализатор экземпляра. Это блок, который не имеет имени и исполняется первым (как конструктор).

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

Мое решение состоит в том, чтобы использовать метод, который возвращает реализованный анонимный класс. Обычные аргументы могут быть переданы методу и доступны в анонимном классе.

Например: (из некоторого кода GWT для обработки изменения текстового поля):

/* Regular method. Returns the required interface/abstract/class
   Arguments are defined as final */
private ChangeHandler newNameChangeHandler(final String axisId, final Logger logger) {

    // Return a new anonymous class
    return new ChangeHandler() {
        public void onChange(ChangeEvent event) {
            // Access method scope variables           
            logger.fine(axisId)
        }
     };
}

В этом примере на новый анонимный метод класса будет ссылаться:

textBox.addChangeHandler(newNameChangeHandler(myAxisName, myLogger))

ИЛИ, используя требования ОП:

private ActionListener newActionListener(final int aVariable) {
    return new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            System.out.println("Your variable is: " + aVariable);
        }
    };
}
...
int myVariable = 1;
newActionListener(myVariable);

Вы можете использовать простые лямбда-выражения ("лямбда-выражения могут захватывать переменные")

int myVariable = 1;
ActionListener al = ae->System.out.println(myVariable);
myButton.addActionListener( al );

или даже функция

Function<Integer,ActionListener> printInt = 
    intvar -> ae -> System.out.println(intvar);

int myVariable = 1;
myButton.addActionListener( printInt.apply(myVariable) );

Использование функции является отличным способом реорганизации декораторов и адаптеров, см. Здесь

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

Другие люди уже ответили, что анонимные классы могут получить доступ только к конечным переменным. Но они оставляют открытым вопрос, как сохранить исходную переменную не окончательной. Адам Млодзинский дал решение, но это довольно раздутый. Существует гораздо более простое решение проблемы:

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

int myVariable = 1;

{
    final int anonVar = myVariable;

    myButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            // How would one access myVariable here?
            // Use anonVar instead of myVariable
        }
    });
}

Адам Млодзинский не делает ничего другого в своем ответе, но с гораздо большим количеством кода.

Простой способ поместить какое-либо значение во внешнюю переменную (не принадлежит классу anonymus) - вот как!

Таким же образом, если вы хотите получить значение внешней переменной, вы можете создать метод, который возвращает то, что вы хотите!

public class Example{

    private TypeParameter parameter;

    private void setMethod(TypeParameter parameter){

        this.parameter = parameter;

    }

    //...
    //into the anonymus class
    new AnonymusClass(){

        final TypeParameter parameterFinal = something;
        //you can call setMethod(TypeParameter parameter) here and pass the
        //parameterFinal
        setMethod(parameterFinal); 

        //now the variable out the class anonymus has the value of
        //of parameterFinal

    });

 }

Если "myVariable" является полем, вы можете использовать уточненное this:

      public class Foo {
    int myVariable = 1;

    new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            Foo.this.myVariable = 8;
        }
    });
}

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

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

Например

Интерфейс:

public interface TextProcessor
{
    public String Process(String text);
}

учебный класс:

private String _key;

public String toJson()
{
    TextProcessor textProcessor = new TextProcessor() {
        @Override
        public String Process(String text)
        {
            return _key + ":" + text;
        }
    };

    JSONTypeProcessor typeProcessor = new JSONTypeProcessor(textProcessor);

    foreach(String key : keys)
    {
        _key = key;

        typeProcessor.doStuffThatUsesLambda();
    }

Я не знаю, разобрались ли они в java 8 (я застрял в мире EE и еще не получил 8), но в C# это будет выглядеть так:

    public string ToJson()
    {
        string key = null;
        var typeProcessor = new JSONTypeProcessor(text => key + ":" + text);

        foreach (var theKey in keys)
        {
            key = theKey;

            typeProcessor.doStuffThatUsesLambda();
        }
    }

Вам не нужен отдельный интерфейс в C# либо... Я скучаю по нему! Я делаю худшие проекты в java и повторяюсь больше, потому что объем кода + сложность, которую вы должны добавить в java для повторного использования чего-либо, хуже, чем просто копировать и вставлять много времени.

Другие вопросы по тегам