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

146

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

int myVariable = 1;

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

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

Льюис
источник
7
Вы можете ссылаться на finalлокальные переменные из включающего метода.
Том Хотин - tackline
Мне очень нравится, как Адам Ммлодзинский предлагает определить закрытый метод, который инициализирует частные экземпляры myVariable и может вызываться в закрывающей скобке из-за возврата this.
Дламблин
Этот вопрос имеет несколько общих целей: stackoverflow.com/questions/362424/…
Alastair McCormack
Вы также можете использовать глобальные переменные класса внутри анонимного класса. Возможно, не очень чистый, но он может сделать работу.
Джори

Ответы:

78

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

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

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

Мэтью Уиллис
источник
21
Анонимный класс использует конструкторы своего родителя. напр.new ArrayList(10) { }
Питер Лори
Хорошая точка зрения. Так что это был бы еще один способ передать параметр анонимному классу, хотя, скорее всего, у вас не будет контроля над этим параметром.
Мэтью Уиллис
анонимным классам не нужны конструкторы
newacct
4
Анонимные классы могут иметь инициализаторы экземпляров, которые могут функционировать как конструкторы без параметров в анонимных классах. Они выполняются в том же порядке, что и назначения полей, т. Е. После super()и до остальной части фактического конструктора. new someclass(){ fields; {initializer} fields; methods(){} }, Это похоже на статический инициализатор, но без ключевого слова static. docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.6
Марк Иеронимус
Посмотрите этот stackoverflow.com/a/3045185/1737819, в котором говорится, как реализовать без конструктора.
Разработчик Мариус Жиленас
337

Да, добавив метод инициализатора, который возвращает '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)  );

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

Адам Млодзинский
источник
4
вау ... гениально! Я так устал от создания finalссылочного объекта, чтобы я мог получать информацию в свои анонимные классы. Спасибо, что поделились!
Мэтт Кляйн
7
Почему init()функция должна возвращаться this? Я не понимаю синтаксис на самом деле.
Джори
11
потому что ваш myButton.addActionListener (...) ожидает объект ActionListener как объект, который возвращается при вызове его метода.
1
Я думаю .. я нахожу это довольно уродливым, хотя, хотя это работает. Я считаю, что большую часть времени я могу позволить себе сделать необходимые переменные и параметры функции окончательными и напрямую ссылаться на них из внутреннего класса, поскольку обычно они только читаются.
Томас
2
Проще: private int anonVar = myVariable;
августа
29

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

AAV
источник
Переменные экземпляра, на которые ссылается анонимный класс, не обязательно должны быть окончательными.
Мэтью Уиллис
8
Переменные экземпляра ссылаются через thisкоторый является окончательным.
Питер Лори
Что если я не хочу, чтобы переменная была изменена на final? Я не могу найти альтернативу. Это может повлиять на параметр источника, который предназначен для final.
Олстон
20

Как это:

final int myVariable = 1;

myButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        // Now you can access it alright.
    }
});
adarshr
источник
14

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

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

источник
8

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

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

Роб Рассел
источник
Это не решает вопрос, который задают. У вас все еще будет проблема доступа к локальным переменным, поэтому вам нужно либо использовать решение от Адама Млодзинского, либо от adarshr
Мэтт Кляйн,
1
@ MattKlein Для меня это выглядит так, как будто это решает проблему. На самом деле это то же самое и менее многословно.
Хеликс
1
Вопрос хотел знать, как передать параметры в класс, как вы это сделали бы с конструктором, который требует параметры. Ссылка (которая должна была быть обобщена здесь) только показывает, как получить инициализатор экземпляра без параметров, который не отвечает на вопрос. Этот метод может использоваться с finalпеременными, как описано aav, но эта информация не была предоставлена ​​в этом ответе. Безусловно, лучший ответ - тот, который дал Адам Млодзинкси (я теперь использую этот образец исключительно, больше никаких финалов!). Я поддерживаю мой комментарий, что это не отвечает на заданный вопрос.
Мэтт Кляйн
7

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

Например: (из некоторого кода 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);
Аластер МакКормак
источник
Это хорошо, это ограничивает анонимный класс несколькими легко идентифицируемыми переменными и устраняет мерзость необходимости сделать некоторые переменные окончательными.
Мизерная переменная
3

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

Если вы не хотите 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
        }
    });
}

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

ceving
источник
Это все еще работает без дополнительного объема. Это фактически так же, как и другие ответы, использующие окончательный вариант.
Адам Млодзински
@ AdamMlodzinski Нет, это фактически то же самое, что и ваш ответ, потому что он вводит новую переменную со значением исходной переменной в частной области видимости.
ceving
Это не то же самое. В вашем случае ваш внутренний класс не может вносить изменения в anonVar - таким образом, эффект отличается. Если ваш внутренний класс должен, скажем, поддерживать какое-то состояние, ваш код должен будет использовать некоторый вид Object с установщиком, а не примитивом.
Адам Млодзински
@AdamMlodzinski Это был не вопрос. Вопрос заключался в том, как получить доступ к внешней переменной, не делая себя окончательным. И решение состоит в том, чтобы сделать окончательную копию. И, конечно же, очевидно, что в слушателе можно создать дополнительную изменяемую копию переменной. Но сначала об этом не спрашивали, а во-вторых, не требуется никакого initметода. Я могу добавить еще одну строку кода в мой пример, чтобы иметь эту дополнительную переменную. Если вы большой поклонник шаблонов построения, не стесняйтесь использовать их, но в этом случае они не нужны.
ceving
Я не вижу, как это отличается от использования finalрешения переменной.
Кевин Рэйв
3

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

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

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

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

ZiglioUK
источник
1

Простой способ поместить какое-либо значение во внешнюю переменную (не принадлежит классу 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

    });

 }
heronsanches
источник
-2

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

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

Например

Интерфейс:

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 для повторного использования чего-либо, хуже, чем просто копировать и вставлять большую часть времени.

JonnyRaa
источник
похоже на еще один хак, который вы можете использовать, чтобы иметь массив из одного элемента, как упомянуто здесь stackoverflow.com/a/4732586/962696
JonnyRaa