Wie übergebe ich Parameter an eine anonyme Klasse?

146

Ist es möglich, Parameter zu übergeben oder auf externe Parameter an eine anonyme Klasse zuzugreifen? Beispielsweise:

int myVariable = 1;

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

Gibt es eine Möglichkeit für den Listener, auf myVariable zuzugreifen oder myVariable zu übergeben, ohne den Listener als tatsächlich benannte Klasse zu erstellen?

Lewis
quelle
7
Sie können finallokale Variablen aus der einschließenden Methode referenzieren .
Tom Hawtin - Tackline
Ich mag das Aussehen von Adam Mmlodzinskis Vorschlag, eine private Methode zu definieren, die private myVariable-Instanzen initialisiert und aufgrund der Rückkehr an der schließenden Klammer aufgerufen werden kann this.
Dlamblin
Diese Frage hat einige gemeinsame Ziele: stackoverflow.com/questions/362424/…
Alastair McCormack
Sie können die globalen Klassenvariablen auch innerhalb der anonymen Klasse verwenden. Vielleicht nicht sehr sauber, aber es kann den Job machen.
Jori

Antworten:

78

Technisch gesehen nein, da anonyme Klassen keine Konstruktoren haben können.

Klassen können jedoch auf Variablen verweisen, die Bereiche enthalten. Bei einer anonymen Klasse können dies Instanzvariablen aus den enthaltenden Klassen oder lokale Variablen sein, die als endgültig markiert sind.

edit : Wie Peter betonte, können Sie Parameter auch an den Konstruktor der Oberklasse der anonymen Klasse übergeben.

Matthew Willis
quelle
21
Eine anonyme Klassenverwendung verwendet die Konstruktoren ihres übergeordneten Elements. zBnew ArrayList(10) { }
Peter Lawrey
Guter Punkt. Dies wäre also eine andere Möglichkeit, einen Parameter an eine anonyme Klasse zu übergeben, obwohl Sie wahrscheinlich keine Kontrolle über diesen Parameter haben.
Matthew Willis
anonyme Klassen brauchen keine Konstruktoren
newacct
4
Anonyme Klassen können Instanzinitialisierer haben, die in anonymen Klassen als parameterlose Konstruktoren fungieren können. Diese werden in derselben Reihenfolge wie Feldzuweisungen ausgeführt, dh nach super()und vor dem Rest des eigentlichen Konstruktors. new someclass(){ fields; {initializer} fields; methods(){} }. Es ist wie ein statischer Initialisierer, jedoch ohne das statische Schlüsselwort. docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.6
Mark Jeronimus
In diesem Stackoverflow.com/a/3045185/1737819 wird beschrieben, wie ohne Konstruktor implementiert wird.
Entwickler Marius Žilėnas
336

Ja, indem Sie eine Initialisierungsmethode hinzufügen, die 'this' zurückgibt, und diese Methode sofort aufrufen:

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

Keine "endgültige" Erklärung erforderlich.

Adam Mlodzinski
quelle
4
wow ... genial! Ich bin es so leid, ein finalReferenzobjekt zu erstellen, nur damit ich Informationen in meine anonymen Klassen bekommen kann. Ich danke Ihnen für das Teilen!
Matt Klein
7
Warum muss die init()Funktion zurückkehren this? Ich verstehe die Syntax nicht wirklich.
Jori
11
weil Ihr myButton.addActionListener (...) ein ActionListener-Objekt als Objekt erwartet, das zurückgegeben wird, wenn Sie seine Methode aufrufen.
1
Ich denke ... ich finde das selbst ziemlich hässlich, obwohl es funktioniert. Die meiste Zeit kann ich es mir einfach leisten, die notwendigen Variablen und Funktionsparameter endgültig zu machen und sie direkt aus der inneren Klasse zu referenzieren, da sie normalerweise nur gelesen werden.
Thomas
2
Einfacher: private int anonVar = myVariable;
Anm
29

Ja. Sie können Variablen erfassen, die für die innere Klasse sichtbar sind. Die einzige Einschränkung ist, dass es endgültig sein muss

aav
quelle
Instanzvariablen, auf die von einer anonymen Klasse verwiesen wird, müssen nicht endgültig sein.
Matthew Willis
8
Es wird auf Instanzvariablen verwiesen, über thisdie final ist.
Peter Lawrey
Was ist, wenn ich nicht möchte, dass die Variable geändert wird final? Ich kann keine Alternative finden. Dies kann den Ursprungsparameter beeinflussen, für den er ausgelegt ist final.
Alston
20

So was:

final int myVariable = 1;

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

Dies wird die Magie tun

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

quelle
8

Wie unter http://www.coderanch.com/t/567294/java/java/declare-constructor-anonymous-class gezeigt , können Sie einen Instanzinitialisierer hinzufügen. Es ist ein Block, der keinen Namen hat und zuerst ausgeführt wird (genau wie ein Konstruktor).

Sieht so aus, als würden sie auch unter Warum Java-Instanz-Initialisierer besprochen ? und Wie unterscheidet sich ein Instanzinitialisierer von einem Konstruktor? diskutiert Unterschiede zu Konstruktoren.

Rob Russell
quelle
Dies löst die gestellte Frage nicht. Sie werden immer noch das Problem haben, auf lokale Variablen zuzugreifen, daher müssen Sie entweder die Lösung von Adam Mlodzinski oder adarshr
Matt Klein
1
@ MattKlein Für mich sieht es so aus, als würde es es lösen. Eigentlich ist es dasselbe und weniger ausführlich.
Haelix
1
Die Frage wollte wissen, wie Parameter an die Klasse übergeben werden, wie Sie es mit einem Konstruktor tun würden, der Parameter benötigt. Der Link (der hier hätte zusammengefasst werden sollen) zeigt nur, wie man einen parameterlosen Instanzinitialisierer hat, der die Frage nicht beantwortet. Diese Technik könnte mit finalVariablen verwendet werden, wie von aav beschrieben, aber diese Informationen wurden in dieser Antwort nicht bereitgestellt. Die mit Abstand beste Antwort ist die von Adam Mlodzinksi (ich verwende jetzt ausschließlich dieses Muster, keine Endrunde mehr!). Ich stehe zu meinem Kommentar, dass dies die gestellte Frage nicht beantwortet.
Matt Klein
7

Meine Lösung besteht darin, eine Methode zu verwenden, die die implementierte anonyme Klasse zurückgibt. Regelmäßige Argumente können an die Methode übergeben werden und sind in der anonymen Klasse verfügbar.

Zum Beispiel: (von einem GWT-Code zur Behandlung einer Textfeldänderung):

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

In diesem Beispiel wird auf die neue anonyme Klassenmethode verwiesen mit:

textBox.addChangeHandler(newNameChangeHandler(myAxisName, myLogger))

ODER unter Verwendung der Anforderungen des OP:

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
quelle
Dies ist gut, es beschränkt die anonyme Klasse auf einige leicht zu identifizierende Variablen und beseitigt die Abscheulichkeiten, einige Variablen endgültig machen zu müssen.
Elende Variable
3

Andere Personen haben bereits geantwortet, dass anonyme Klassen nur auf endgültige Variablen zugreifen können. Sie lassen jedoch die Frage offen, wie die ursprüngliche Variable nicht endgültig bleiben soll. Adam Mlodzinski gab eine Lösung, ist aber ziemlich aufgebläht. Es gibt eine viel einfachere Lösung für das Problem:

Wenn Sie nicht myVariableendgültig sein möchten , müssen Sie es in einen neuen Bereich einschließen, in dem es keine Rolle spielt, ob es endgültig ist.

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 Mlodzinski tut in seiner Antwort nichts anderes als viel mehr Code.

ceving
quelle
Dies funktioniert immer noch ohne den zusätzlichen Umfang. Es ist praktisch das gleiche wie die anderen Antworten mit final.
Adam Mlodzinski
@AdamMlodzinski Nein, es entspricht praktisch Ihrer Antwort, da eine neue Variable mit dem Wert der ursprünglichen Variablen in einem privaten Bereich eingeführt wird.
2.
Es ist nicht effektiv dasselbe. In Ihrem Fall kann Ihre innere Klasse keine Änderungen an anonVar vornehmen - daher ist der Effekt unterschiedlich. Wenn Ihre innere Klasse beispielsweise einen bestimmten Status beibehalten muss, muss Ihr Code eine Art Objekt mit einem Setter anstelle eines Grundelements verwenden.
Adam Mlodzinski
@AdamMlodzinski Das war nicht die Frage. Die Frage war, wie man auf die äußere Variable zugreift, ohne sich selbst endgültig zu machen. Und die Lösung besteht darin, eine endgültige Kopie zu erstellen. Und natürlich ist es offensichtlich, dass man im Listener eine zusätzliche veränderbare Kopie der Variablen erstellen kann. Aber erstens wurde es nicht gefragt und zweitens erfordert es keine initMethode. Ich kann meinem Beispiel eine zusätzliche Codezeile hinzufügen, um diese zusätzliche Variable zu erhalten. Wenn Sie ein großer Fan von Builder-Mustern sind, können Sie sie gerne verwenden, aber in diesem Fall sind sie nicht erforderlich.
Ceving
Ich sehe nicht, wie sich dies von der Verwendung einer finalvariablen Lösung unterscheidet.
Kevin Rave
3

Sie können einfache Lambdas verwenden ("Lambda-Ausdrücke können Variablen erfassen")

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

oder sogar eine Funktion

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

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

Die Verwendung von Function ist eine großartige Möglichkeit, Dekorateure und Adapter umzugestalten. siehe hier)

Ich habe gerade angefangen, etwas über Lambdas zu lernen. Wenn Sie also einen Fehler entdecken, können Sie gerne einen Kommentar schreiben.

ZiglioUK
quelle
1

Eine einfache Möglichkeit, einer externen Variablen einen Wert zu geben (gehört nicht zur Anonymus-Klasse), ist wie folgt!

Auf die gleiche Weise können Sie eine Methode erstellen, die das zurückgibt, was Sie möchten, wenn Sie den Wert einer externen Variablen erhalten möchten!

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
quelle
-2

Ich dachte, anonyme Klassen wären im Grunde genommen wie Lambdas, aber mit schlechterer Syntax ... dies stellt sich als wahr heraus, aber die Syntax ist noch schlechter und führt dazu, dass (was sollte) lokale Variablen in die enthaltende Klasse ausbluten.

Sie können auf keine endgültigen Variablen zugreifen, indem Sie sie in Felder der übergeordneten Klasse umwandeln.

Z.B

Schnittstelle:

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

Klasse:

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

Ich weiß nicht, ob sie das in Java 8 geklärt haben (ich stecke in der EE-Welt fest und habe noch keine 8), aber in C # würde es so aussehen:

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

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

            typeProcessor.doStuffThatUsesLambda();
        }
    }

Sie brauchen auch keine separate Schnittstelle in c # ... Ich vermisse es! Ich finde mich dabei, schlechtere Designs in Java zu erstellen und mich mehr zu wiederholen, weil die Menge an Code + Komplexität, die Sie in Java hinzufügen müssen, um etwas wiederzuverwenden, schlimmer ist, als viel zu kopieren und einzufügen.

JonnyRaa
quelle
Es sieht so aus, als ob ein anderer Hack, den Sie verwenden können, darin besteht, ein Array mit einem Element zu haben, wie hier erwähnt. stackoverflow.com/a/4732586/962696
JonnyRaa