Як передати параметри анонімному класу?


146

Чи можливо передавати параметри або отримати доступ до зовнішніх параметрів до анонімного класу? Наприклад:

int myVariable = 1;

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

Чи є спосіб, щоб слухач отримав доступ до myVariable або був переданий myVariable, не створюючи слухача як фактично названого класу?


7
Ви можете посилатись на finalлокальні змінні з методу вкладу.
Том Хотін - тайклін

Мені подобається, як виглядає пропозиція Адама Ммлодзінського щодо визначення приватного методу, який ініціалізує приватні екземпляри myVariable і можуть бути викликані в кінцевій дузі через повернення this.
dlamblin

Це питання має деякі спільні цілі: stackoverflow.com/questions/362424/…
Alastair McCormack,

Ви також можете використовувати змінні глобального класу всередині анонімного класу. Можливо, не дуже чисто, але це може зробити свою роботу.
Джорі

Відповіді:


78

Технічно ні, оскільки анонімні класи не можуть мати конструкторів.

Однак класи можуть посилатися на змінні, що містять області застосування. Для анонімного класу це можуть бути змінні екземпляри із класу, що містить клас, або локальних змінних, які позначені остаточними.

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


21
Використання анонімного класу використовують конструктори його батьківського. наприкладnew ArrayList(10) { }
Пітер Лоурі

Гарна думка. Тож це був би інший спосіб передати параметр анонімному класу, хоча, швидше за все, ви не матимете контролю над цим параметром.
Меттью Вілліс

анонімні класи не потребують конструкторів
newacct

4
Анонімні класи можуть мати ініціалізатори інстанцій, які можуть функціонувати як конструктори без параметрів в анонімних класах. Вони виконуються в тому ж порядку, що і польові призначення, тобто після super()і перед рештою фактичного конструктора. new someclass(){ fields; {initializer} fields; methods(){} }. Це ніби статичний ініціалізатор, але без статичного ключового слова. docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.6
Марк Єронімус

Дивіться цей stackoverflow.com/a/3045185/1737819, в якому йдеться про те, як реалізувати без конструктора.
розробник Marius Žilėnas

336

Так, додавши метод ініціалізатора, який повертає "це", і негайно викликавши цей метод:

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

29

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


Змінні екземпляра, на які посилається анонімний клас, не повинні бути остаточними afaik.
Меттью Вілліс

8
На змінні екземпляра посилаються, через thisякі є остаточними.
Пітер Лорі

Що робити, якщо я не хочу, щоб змінна була змінена final? Я не можу знайти жодної альтернативи. Це може вплинути на початковий параметр, який призначений final.
Елстон

20

Подобається це:

final int myVariable = 1;

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

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 Instance? і чим ініціалізатор примірника відрізняється від конструктора? обговорює відмінності від конструкторів.


Це не вирішує поставлене питання. У вас все ще буде проблема доступу до локальних змінних, тому вам або потрібно буде використовувати рішення Адама Млодзінського або adarshr
Метт Клейн

1
@MattKlein Мені здається, що це вирішує. Насправді те саме, і менш багатослівне.
haelix

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

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


Це все ще працює без зайвих можливостей. Це ефективно так само, як і інші відповіді, використовуючи остаточний.
Адам Млодзінський

@AdamMlodzinski Ні, це фактично те саме, що і ваша відповідь, оскільки вона вводить нову змінну зі значенням вихідної змінної в приватній області.
припинення

Це фактично не те саме. У вашому випадку ваш внутрішній клас не може вносити зміни до anonVar - таким чином, ефект відрізняється. Якщо ваш внутрішній клас повинен, скажімо, підтримувати деякий стан, ваш код повинен буде використовувати якийсь об’єкт із сеттером, а не примітив.
Адам Млодзінський

@AdamMlodzinski Про це не було питання. Питання полягало в тому, як отримати доступ до зовнішньої змінної, не роблячи себе остаточним. І рішення - зробити остаточну копію. І звичайно очевидно, що можна зробити додаткову копію змінної змінної у слухачі. Але по-перше, його не запитували, а по-друге, він не потребує ніякого initметоду. Я можу додати один додатковий рядок коду до свого прикладу, щоб мати цю додаткову змінну. Якщо ви великий шанувальник моделей будівельників, сміливо їх використовуйте, але в цьому випадку вони не потрібні.
закінчення

Я не бачу, як це відрізняється від використання 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) );

Використання функції - це чудовий спосіб рефакторних декораторів та адаптерів, дивіться тут

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


1

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

Таким же чином, якщо ви хочете отримати значення зовнішньої змінної, ви можете створити метод, який повертає те, що ви хочете!

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

    });

 }

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


виглядає як інший хак ви можете використовувати, щоб мати масив один елемент , як зазначено тут stackoverflow.com/a/4732586/962696
JonnyRaa
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.