Jak przekazać parametry do anonimowej klasy?

146

Czy można przekazać parametry lub uzyskać dostęp do parametrów zewnętrznych do anonimowej klasy? Na przykład:

int myVariable = 1;

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

Czy istnieje sposób, aby detektor uzyskał dostęp do myVariable lub został przekazany do myVariable bez tworzenia detektora jako rzeczywistej nazwanej klasy?

Chwytak
źródło
7
Możesz odwoływać się do finalzmiennych lokalnych z otaczającej metody.
Tom Hawtin - tackline
Podoba mi się sugestia Adama Mmlodzińskiego dotycząca zdefiniowania metody prywatnej, która inicjuje prywatne instancje myVariable i może zostać wywołana w nawiasie zamykającym z powodu powrotu this.
dlamblin
To pytanie ma kilka wspólnych celów: stackoverflow.com/questions/362424/ ...
Alastair McCormack
Możesz również użyć globalnych zmiennych klasowych z wnętrza klasy anonimowej. Może niezbyt czysty, ale może wykonać zadanie.
Jori,

Odpowiedzi:

78

Technicznie nie, ponieważ klasy anonimowe nie mogą mieć konstruktorów.

Jednak klasy mogą odwoływać się do zmiennych z zakresów zawierających. W przypadku klasy anonimowej mogą to być zmienne instancji z klas zawierających je lub zmienne lokalne, które są oznaczone jako końcowe.

edycja : Jak zauważył Peter, możesz również przekazać parametry do konstruktora nadklasy klasy anonimowej.

Matthew Willis
źródło
21
Anonimowe użycie klasy używa konstruktorów jej rodzica. np.new ArrayList(10) { }
Peter Lawrey
Słuszna uwaga. Byłby to więc inny sposób przekazywania parametru do anonimowej klasy, chociaż prawdopodobnie nie będziesz mieć kontroli nad tym parametrem.
Matthew Willis,
anonimowe klasy nie potrzebują konstruktorów
newacct
4
Klasy anonimowe mogą mieć inicjatory wystąpienia, które mogą działać jako konstruktory bez parametrów w klasach anonimowych. Są one wykonywane w tej samej kolejności, co przypisania pól, tj. Za super()i przed resztą rzeczywistego konstruktora. new someclass(){ fields; {initializer} fields; methods(){} }. To coś w rodzaju statycznego inicjatora, ale bez słowa kluczowego static. docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.6
Mark Jeronimus
Zobacz ten stackoverflow.com/a/3045185/1737819, gdzie jest mowa o implementacji bez konstruktora.
Deweloper Marius Žilėnas
336

Tak, przez dodanie metody inicjującej, która zwraca „this” i natychmiastowe wywołanie tej metody:

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

Nie jest wymagana deklaracja „końcowa”.

Adam Mlodziński
źródło
4
wow ... genialny! Jestem tak zmęczony tworzeniem finalobiektu referencyjnego, aby móc uzyskać informacje na moich anonimowych zajęciach. Dziękuję za udostępnienie!
Matt Klein,
7
Dlaczego init()funkcja musi wrócić this? Tak naprawdę nie rozumiem składni.
Jori
11
ponieważ Twój myButton.addActionListener (...) oczekuje obiektu ActionListener jako obiektu, który jest zwracany po wywołaniu jego metody.
1
Myślę, że ja sam uważam to za brzydkie, chociaż działa. W większości przypadków po prostu mogę sobie pozwolić na ostateczne utworzenie niezbędnych zmiennych i parametrów funkcji i bezpośrednie odniesienie do nich z klasy wewnętrznej, ponieważ zwykle są one tylko odczytywane.
Thomas
2
Prościej: private int anonVar = myVariable;
Anm,
29

tak. możesz przechwycić zmienną, widoczną dla klasy wewnętrznej. jedynym ograniczeniem jest to, że musi być ostateczna

aav
źródło
Zmienne instancji, do których odwołuje się anonimowa klasa, nie muszą być ostateczne.
Matthew Willis,
8
Odwoływanie się do zmiennych instancji thisjest ostateczne.
Peter Lawrey
A jeśli nie chcę, aby zmienna została zmieniona na final? Nie mogę znaleźć alternatywy. Może to wpłynąć na parametr pochodzenia, który ma być final.
Alston,
20

Lubię to:

final int myVariable = 1;

myButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        // Now you can access it alright.
    }
});
adarshr
źródło
14

To zrobi magię

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

źródło
8

Jak pokazano na http://www.coderanch.com/t/567294/java/java/declare-constructor-anonymous-class , możesz dodać inicjator instancji. Jest to blok, który nie ma nazwy i jest wykonywany jako pierwszy (tak jak konstruktor).

Wygląda na to, że są one również omówione w artykule Dlaczego inicjatory instancji Java? i czym różni się inicjator wystąpienia od konstruktora? omawia różnice od konstruktorów.

Rob Russell
źródło
To nie rozwiązuje zadawanego pytania. Nadal będziesz mieć problem z dostępem do zmiennych lokalnych, więc albo będziesz musiał użyć rozwiązania od Adama Mlodzińskiego lub adarshr
Matt Klein
1
@MattKlein Dla mnie wygląda na to, że to rozwiązuje. W rzeczywistości jest to to samo, ale mniej szczegółowe.
haelix
1
Pytanie dotyczyło sposobu przekazywania parametrów do klasy, tak jak w przypadku konstruktora, który wymaga parametrów. Link (który powinien zostać tutaj podsumowany) pokazuje tylko, jak mieć inicjalizator wystąpienia bez parametrów, który nie odpowiada na pytanie. Ta technika może być używana ze finalzmiennymi opisanymi przez aav, ale ta informacja nie została podana w tej odpowiedzi. Zdecydowanie najlepszą odpowiedzią jest ta, której udzielił Adam Mlodzinksi (teraz używam wyłącznie tego wzoru, koniec finałów!). Podtrzymuję mój komentarz, że to nie odpowiada na zadane pytanie.
Matt Klein,
7

Moim rozwiązaniem jest użycie metody, która zwraca zaimplementowaną klasę anonimową. Do metody można przekazywać zwykłe argumenty, które są dostępne w klasie anonimowej.

Na przykład: (z jakiegoś kodu GWT do obsługi zmiany pola tekstowego):

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

W tym przykładzie do nowej anonimowej metody klasy będzie odwoływać się:

textBox.addChangeHandler(newNameChangeHandler(myAxisName, myLogger))

LUB , korzystając z wymagań PO:

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);
Alastair McCormack
źródło
To dobrze, ogranicza anonimową klasę do kilku łatwych do zidentyfikowania zmiennych i usuwa obrzydliwości związane z koniecznością ostatecznego określania niektórych zmiennych.
Nędzna zmienna
3

Inni ludzie już odpowiedzieli, że klasy anonimowe mają dostęp tylko do zmiennych końcowych. Ale pozostawiają otwarte pytanie, jak zachować oryginalną zmienną jako nieostateczną. Adam Mlodziński podał rozwiązanie, ale jest dość rozdęty. Istnieje znacznie prostsze rozwiązanie tego problemu:

Jeśli nie chcesz myVariablebyć ostateczny, musisz umieścić go w nowym zakresie, w którym nie ma znaczenia, czy jest ostateczny.

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

Adam Mlodziński nie robi nic więcej w swojej odpowiedzi, ale z dużo większym kodem.

ceving
źródło
To nadal działa bez dodatkowego zakresu. W rzeczywistości jest to to samo, co inne odpowiedzi przy użyciu wersji ostatecznej.
Adam Mlodziński
@AdamMlodziński Nie, w praktyce jest to to samo, co Twoja odpowiedź, ponieważ wprowadza nową zmienną o wartości oryginalnej zmiennej w zakresie prywatnym.
ceving
W rzeczywistości nie jest to to samo. W twoim przypadku twoja klasa wewnętrzna nie może dokonać zmian w anonVar - stąd efekt jest inny. Jeśli twoja klasa wewnętrzna musi, powiedzmy, utrzymać jakiś stan, twój kod musiałby używać pewnego rodzaju obiektu z ustawiaczem, a nie prymitywem.
Adam Mlodziński
@AdamMlodziński To nie było pytanie. Pytanie brzmiało, jak uzyskać dostęp do zmiennej zewnętrznej, nie czyniąc siebie ostatecznym. Rozwiązaniem jest wykonanie ostatecznej kopii. I oczywiście jest oczywiste, że można wykonać dodatkową mutowalną kopię zmiennej w odbiorniku. Ale najpierw o to nie pytano, a po drugie nie wymaga żadnej initmetody. Mogę dodać jedną dodatkową linię kodu do mojego przykładu, aby mieć tę dodatkową zmienną. Jeśli jesteś wielkim fanem wzorów budowniczych, możesz z nich korzystać, ale w tym przypadku nie są one konieczne.
ceving
Nie widzę, jak to się różni od finalrozwiązania zmiennego.
Kevin Rave,
3

Możesz używać zwykłych wyrażeń lambda („wyrażenia lambda mogą przechwytywać zmienne”)

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

lub nawet funkcja

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

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

Korzystanie z funkcji to świetny sposób na refaktoryzację dekoratorów i adapterów, patrz tutaj

Właśnie zacząłem się uczyć o lambdach, więc jeśli zauważysz błąd, napisz komentarz.

ZiglioUK
źródło
1

Prosty sposób na umieszczenie wartości w zmiennej zewnętrznej (nie należy do klasy anonymus) jest następujący!

W ten sam sposób, jeśli chcesz uzyskać wartość zmiennej zewnętrznej, możesz utworzyć metodę, która zwraca to, co chcesz!

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

    });

 }
czaple
źródło
-2

Myślałem, że anonimowe klasy są w zasadzie podobne do lambd, ale z gorszą składnią ... okazuje się, że jest prawdą, ale składnia jest jeszcze gorsza i powoduje (co powinno być) lokalne zmienne wyciekające do klasy zawierającej.

Nie możesz uzyskać dostępu do żadnych zmiennych końcowych, przekształcając je w pola klasy nadrzędnej.

Na przykład

Berło:

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

klasa:

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

Nie wiem, czy załatwili to w Javie 8 (utknąłem w świecie EE i jeszcze nie mam 8), ale w C # wyglądałoby to tak:

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

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

            typeProcessor.doStuffThatUsesLambda();
        }
    }

Nie potrzebujesz też osobnego interfejsu w c # ... Tęsknię za tym! Uważam, że robię gorsze projekty w Javie i powtarzam się częściej, ponieważ ilość kodu + złożoność, którą trzeba dodać w Javie, aby ponownie użyć czegoś, jest gorsza niż zwykłe kopiowanie i wklejanie dużo czasu.

JonnyRaa
źródło
wygląda na to, że kolejny hack, którego możesz użyć, to mieć tablicę z jednym elementem, jak wspomniano tutaj stackoverflow.com/a/4732586/962696
JonnyRaa