Come passare i parametri alla classe anonima?


146

È possibile passare parametri o accedere a parametri esterni a una classe anonima? Per esempio:

int myVariable = 1;

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

Esiste un modo per l'ascoltatore di accedere a myVariable o passare myVariable senza creare l'ascoltatore come una vera classe denominata?


7
È possibile fare riferimento finalalle variabili locali dal metodo allegato.
Tom Hawtin - affronta il

Mi piace l'aspetto del suggerimento di Adam Mmlodzinski di definire un metodo privato che inizializza le istanze private myVariable e che può essere richiamato alla parentesi graffa di chiusura a causa del ritorno this.
dlamblin,

Questa domanda ha alcuni obiettivi condivisi di: stackoverflow.com/questions/362424/…
Alastair McCormack,

Puoi anche usare le variabili di classe globali dall'interno della classe anonima. Forse non molto pulito, ma può fare il lavoro.
Jori,

Risposte:


78

Tecnicamente no, perché le classi anonime non possono avere costruttori.

Tuttavia, le classi possono fare riferimento a variabili dal contenere ambiti. Per una classe anonima queste possono essere variabili di istanza dalle classi contenenti o dalle variabili locali contrassegnate come finali.

modifica : come ha sottolineato Peter, puoi anche passare i parametri al costruttore della superclasse della classe anonima.


21
Un uso anonimo della classe usa i costruttori del suo genitore. ad es.new ArrayList(10) { }
Peter Lawrey,

Buon punto. Quindi sarebbe un altro modo per passare un parametro a una classe anonima, anche se è probabile che tu non abbia il controllo su quel parametro.
Matthew Willis,

le classi anonime non hanno bisogno di costruttori
newacct

4
Le classi anonime possono avere inizializzatori di istanze, che possono funzionare come costruttori senza parametri nelle classi anonime. Questi vengono eseguiti nello stesso ordine delle assegnazioni di campo, cioè dopo super()e prima del resto del costruttore effettivo. new someclass(){ fields; {initializer} fields; methods(){} }. È un po 'come un inizializzatore statico ma senza la parola chiave statica. docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.6
Mark Jeronimus,

Vedi questo stackoverflow.com/a/3045185/1737819 che dice come implementare senza costruttore.
Sviluppatore Marius Žilėnas,

336

Sì, aggiungendo un metodo di inizializzazione che restituisce 'this' e chiamando immediatamente quel metodo:

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

Nessuna dichiarazione "finale" necessaria.


4
wow ... geniale! Sono così stanco di creare un finaloggetto di riferimento solo per poter ottenere informazioni nelle mie lezioni anonime. Grazie per aver condiviso!
Matt Klein,

7
Perché la init()funzione deve tornare this? Non capisco davvero la sintassi.
Jori,

11
perché myButton.addActionListener (...) prevede un oggetto ActionListener come oggetto che viene restituito quando si chiama il suo metodo.

1
Immagino .. Lo trovo piuttosto brutto, anche se funziona. Trovo che per la maggior parte del tempo posso semplicemente permettermi di rendere definitive le variabili necessarie e i parametri di funzione e di riferirle direttamente dalla classe interna, poiché di solito vengono solo lette.
Thomas,

2
Più semplice: private int anonVar = myVariable;
Anm,

29

sì. puoi acquisire variabili, visibili alla classe interna. l'unica limitazione è che deve essere definitiva


Le variabili di istanza a cui fa riferimento una classe anonima non devono essere definitive.
Matthew Willis,

8
Le variabili di istanza sono referenziate tramite thisquale è finale.
Peter Lawrey,

Cosa succede se non voglio che la variabile venga modificata final? Non trovo alcuna alternativa. Ciò può influenzare il parametro di origine progettato per essere final.
Alston,

20

Come questo:

final int myVariable = 1;

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

14

Questo farà la magia

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

Come mostrato su http://www.coderanch.com/t/567294/java/java/declare-constructor-anonymous-class è possibile aggiungere un inizializzatore di istanza. È un blocco che non ha un nome e viene eseguito per primo (proprio come un costruttore).

Sembra che siano anche discussi su Perché gli inizializzatori dell'istanza java?e In che modo un inizializzatore di istanza è diverso da un costruttore? discute le differenze rispetto ai costruttori.


Questo non risolve la domanda che viene posta. Avrai ancora il problema di accedere alle variabili locali, quindi dovrai utilizzare la soluzione di Adam Mlodzinski o Adarshr
Matt Klein

1
@MattKlein Per me, sembra che lo risolva. In realtà è la stessa cosa e meno dettagliata.
haelix,

1
La domanda voleva sapere come passare i parametri nella classe, come faresti con un costruttore che richiede parametri. Il collegamento (che avrebbe dovuto essere riassunto qui) mostra solo come avere un inizializzatore di istanza senza parametri, che non risponde alla domanda. Questa tecnica potrebbe essere utilizzata con finalvariabili come descritto da aav, ma le informazioni non sono state fornite in questa risposta. Di gran lunga la migliore risposta è quella data da Adam Mlodzinksi (ora uso esclusivamente questo modello, niente più finali!). Attendo il mio commento che questo non risponde alla domanda posta.
Matt Klein,

7

La mia soluzione è utilizzare un metodo che restituisce la classe anonima implementata. Argomenti regolari possono essere passati al metodo e sono disponibili all'interno della classe anonima.

Ad esempio: (da un codice GWT per gestire una modifica della casella di testo):

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

Per questo esempio, il nuovo metodo di classe anonimo verrebbe referenziato con:

textBox.addChangeHandler(newNameChangeHandler(myAxisName, myLogger))

OPPURE , utilizzando i requisiti del 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);

Questo va bene, limita la classe anonima ad alcune variabili facili da identificare e rimuove le abominazioni dal dover rendere definitive alcune variabili.
Miserabile variabile

3

Altre persone hanno già risposto che le classi anonime possono accedere solo alle variabili finali. Ma lasciano aperta la domanda su come mantenere la variabile originale non definitiva. Adam Mlodzinski dato una soluzione ma è piuttosto gonfio. Esiste una soluzione molto più semplice per il problema:

Se non vuoi myVariableessere definitivo, devi avvolgerlo in un nuovo ambito in cui non ha importanza, se è definitivo.

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 non fa nient'altro nella sua risposta ma con molto più codice.


Funziona ancora senza l'ambito extra. È effettivamente uguale alle altre risposte usando final.
Adam Mlodzinski il

@AdamMlodzinski No, è effettivamente uguale alla tua risposta, perché introduce una nuova variabile con il valore della variabile originale in un ambito privato.
ceving

Non è effettivamente lo stesso. Nel tuo caso, la tua classe interna non può apportare modifiche ad anonVar, quindi l'effetto è diverso. Se la tua classe interna deve, per esempio, mantenere un certo stato, il tuo codice dovrebbe usare una sorta di Oggetto con un setter piuttosto che una primitiva.
Adam Mlodzinski il

@AdamMlodzinski Non era questa la domanda. La domanda era come accedere alla variabile esterna senza rendersi definitiva. E la soluzione è fare una copia finale. E ovviamente è ovvio che si può fare una copia mutabile aggiuntiva della variabile nel listener. Ma prima non è stato chiesto e in secondo luogo non richiede alcun initmetodo. Posso aggiungere una riga di codice aggiuntiva al mio esempio per avere questa variabile aggiuntiva. Se sei un grande fan dei modelli di costruzione, sentiti libero di usarli, ma in questo caso non sono necessari.
ceving

Non vedo come sia diverso dall'uso della finalsoluzione variabile.
Kevin Rave, l'

3

Puoi usare semplici lambda ("le espressioni lambda possono catturare variabili")

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

o anche una funzione

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

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

L'uso della funzione è un ottimo modo per refactoring Decoratori e adattatori, vedi qui

Ho appena iniziato a conoscere lambda, quindi se noti un errore, sentiti libero di scrivere un commento.


1

Un modo semplice per mettere un po 'di valore in una variabile esterna (non appartiene alla classe anonima) è come folow!

Allo stesso modo se vuoi ottenere il valore di una variabile esterna puoi creare un metodo che restituisca quello che vuoi!

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

Pensavo che le classi anonime fossero fondamentalmente come lambda, ma con una sintassi peggiore ... questo risulta essere vero, ma la sintassi è ancora peggiore e causa la fuoriuscita delle variabili locali (che dovrebbero essere) nella classe contenente.

Non è possibile accedere a nessuna variabile finale facendole rientrare nei campi della classe genitore.

Per esempio

Interfaccia:

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

classe:

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

Non so se l'hanno risolto in Java 8 (sono bloccato nel mondo EE e non ne ho ancora 8) ma in C # sarebbe simile a questo:

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

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

            typeProcessor.doStuffThatUsesLambda();
        }
    }

Non hai nemmeno bisogno di un'interfaccia separata in c # ... Mi manca! Mi ritrovo a fare progetti peggiori in Java e ripetermi di più perché la quantità di codice + complessità che devi aggiungere in Java per riutilizzare qualcosa è peggiore della semplice copia e incolla del tempo.


sembra un altro mod è possibile utilizzare è quello di avere una matrice di un elemento come detto qui stackoverflow.com/a/4732586/962696
JonnyRaa
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.