Comment passer des paramètres à une classe anonyme?

146

Est-il possible de passer des paramètres ou d'accéder à des paramètres externes à une classe anonyme? Par exemple:

int myVariable = 1;

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

Existe-t-il un moyen pour l'écouteur d'accéder à myVariable ou de se faire passer myVariable sans créer l'écouteur en tant que classe nommée réelle?

Lewis
la source
7
Vous pouvez référencer finaldes variables locales à partir de la méthode englobante.
Tom Hawtin - tackline
J'aime l'apparence de la suggestion d'Adam Mmlodzinski de définir une méthode privée qui initialise les instances privées myVariable et peut être appelée à l'accolade fermante en raison du retour this.
dlamblin
Cette question a des objectifs communs: stackoverflow.com/questions/362424/…
Alastair McCormack
Vous pouvez également utiliser les variables de classe globale à l'intérieur de la classe anonyme. Peut-être pas très propre, mais cela peut faire le travail.
Jori

Réponses:

78

Techniquement, non, car les classes anonymes ne peuvent pas avoir de constructeur.

Cependant, les classes peuvent référencer des variables à partir d'étendues contenant. Pour une classe anonyme, il peut s'agir de variables d'instance de la ou des classes contenant ou de variables locales marquées comme finales.

edit : Comme l'a souligné Peter, vous pouvez également passer des paramètres au constructeur de la superclasse de la classe anonyme.

Matthew Willis
la source
21
Une classe anonyme utilise les constructeurs de son parent. egnew ArrayList(10) { }
Peter Lawrey
Bon point. Ce serait donc une autre façon de passer un paramètre à une classe anonyme, bien qu'il soit probable que vous n'ayez pas le contrôle sur ce paramètre.
Matthew Willis
les classes anonymes n'ont pas besoin de constructeurs
newacct
4
Les classes anonymes peuvent avoir des initialiseurs d'instance, qui peuvent fonctionner comme des constructeurs sans paramètre dans des classes anonymes. Celles-ci sont exécutées dans le même ordre que les affectations de champs, c'est-à-dire après super()et avant le reste du constructeur réel. new someclass(){ fields; {initializer} fields; methods(){} }. C'est un peu comme un initialiseur statique mais sans le mot clé static. docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.6
Mark Jeronimus
Voir ce stackoverflow.com/a/3045185/1737819 il explique comment implémenter sans constructeur.
Développeur Marius Žilėnas
336

Oui, en ajoutant une méthode d'initialisation qui renvoie 'this', et en appelant immédiatement cette méthode:

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

Aucune déclaration «finale» nécessaire.

Adam Mlodzinski
la source
4
wow ... génial! Je suis tellement fatigué de créer un finalobjet de référence juste pour pouvoir obtenir des informations dans mes classes anonymes. Merci pour le partage!
Matt Klein
7
Pourquoi la init()fonction doit-elle revenir this? Je ne comprends pas vraiment la syntaxe.
Jori
11
car votre myButton.addActionListener (...) attend un objet ActionListener comme un objet qui est renvoyé lorsque vous appelez sa méthode.
1
Je suppose ... Je trouve cela plutôt moche moi-même, même si cela fonctionne. Je trouve que la plupart du temps, je peux simplement me permettre de rendre finales les variables et les paramètres de fonction nécessaires et de les référencer directement à partir de la classe interne, car ils ne sont généralement lus.
Thomas du
2
Plus simple: private int anonVar = myVariable;
Anm
29

Oui. vous pouvez capturer la variable, visible par la classe interne. la seule limitation est qu'il doit être définitif

aav
la source
Les variables d'instance référencées à partir d'une classe anonyme ne doivent pas nécessairement être définitives.
Matthew Willis
8
Les variables d'instance sont référencées via thisce qui est final.
Peter Lawrey
Que faire si je ne veux pas que la variable devienne final? Je ne trouve aucune alternative. Cela peut influencer le paramètre d'origine qui est censé être final.
Alston
20

Comme ça:

final int myVariable = 1;

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

Cela fera la magie

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

la source
8

Comme indiqué sur http://www.coderanch.com/t/567294/java/java/declare-constructor-anonymous-class, vous pouvez ajouter un initialiseur d'instance. C'est un bloc qui n'a pas de nom et qui est exécuté en premier (tout comme un constructeur).

On dirait qu'ils sont également abordés dans Pourquoi les initialiseurs d'instance Java? et En quoi un initialiseur d'instance est-il différent d'un constructeur? discute des différences par rapport aux constructeurs.

Rob Russell
la source
Cela ne résout pas la question posée. Vous aurez toujours le problème d'accès aux variables locales, vous devrez donc soit utiliser la solution d'Adam Mlodzinski ou adarshr
Matt Klein
1
@MattKlein Pour moi, on dirait que ça résout le problème. C'est la même chose en fait, et moins verbeux.
haelix
1
La question voulait savoir comment passer des paramètres dans la classe, comme vous le feriez avec un constructeur qui nécessite des paramètres. Le lien (qui aurait dû être résumé ici) montre uniquement comment avoir un initialiseur d'instance sans paramètre, qui ne répond pas à la question. Cette technique pourrait être utilisée avec des finalvariables comme décrit par aav, mais cette information n'a pas été fournie dans cette réponse. De loin, la meilleure réponse est celle donnée par Adam Mlodzinksi (j'utilise maintenant ce modèle exclusivement, plus de finales!). Je maintiens mon commentaire selon lequel cela ne répond pas à la question posée.
Matt Klein
7

Ma solution consiste à utiliser une méthode qui retourne la classe anonyme implémentée. Des arguments réguliers peuvent être passés à la méthode et sont disponibles dans la classe anonyme.

Par exemple: (à partir d'un code GWT pour gérer un changement de zone de texte):

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

Pour cet exemple, la nouvelle méthode de classe anonyme serait référencée avec:

textBox.addChangeHandler(newNameChangeHandler(myAxisName, myLogger))

OU , en utilisant les exigences du 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
la source
C'est bien, cela restreint la classe anonyme à quelques variables faciles à identifier et supprime les abominations d'avoir à rendre certaines variables définitives.
Variable misérable
3

D'autres personnes ont déjà répondu que les classes anonymes ne peuvent accéder qu'aux variables finales. Mais ils laissent la question ouverte de savoir comment garder la variable d'origine non définitive. Adam Mlodzinski a donné une solution mais c'est assez gonflé. Il existe une solution beaucoup plus simple au problème:

Si vous ne voulez myVariablepas être définitif, vous devez l'envelopper dans une nouvelle portée où cela n'a pas d'importance, si elle est définitive.

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 ne fait rien d'autre dans sa réponse mais avec beaucoup plus de code.

plafond
la source
Cela fonctionne toujours sans la portée supplémentaire. C'est effectivement la même chose que les autres réponses utilisant final.
Adam Mlodzinski
@AdamMlodzinski Non, c'est effectivement la même chose que votre réponse, car elle introduit une nouvelle variable avec la valeur de la variable d'origine dans une portée privée.
ceving
Ce n'est pas effectivement la même chose. Dans votre cas, votre classe interne ne peut pas apporter de modifications à anonVar - ainsi, l'effet est différent. Si votre classe interne doit, par exemple, maintenir un état, votre code devra utiliser une sorte d'objet avec un setter plutôt qu'une primitive.
Adam Mlodzinski
@AdamMlodzinski Ce n'était pas la question. La question était de savoir comment accéder à la variable externe sans se rendre définitive. Et la solution est de faire une copie finale. Et bien sûr, il est évident que l'on peut faire une copie mutable supplémentaire de la variable dans l'auditeur. Mais d'abord, il n'a pas été demandé et deuxièmement, il ne nécessite aucune initméthode. Je peux ajouter une ligne de code supplémentaire à mon exemple afin d'avoir cette variable supplémentaire. Si vous êtes un grand fan des modèles de construction, n'hésitez pas à les utiliser, mais ils ne sont pas nécessaires dans ce cas.
ceving
Je ne vois pas en quoi cela diffère de l'utilisation d' finalune solution variable.
Kevin Rave
3

Vous pouvez utiliser des lambdas simples ("les expressions lambda peuvent capturer des variables")

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

ou même une fonction

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

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

Utiliser Function est un excellent moyen de refactoriser les décorateurs et les adaptateurs, voir ici

Je viens de commencer à en apprendre davantage sur les lambdas, donc si vous repérez une erreur, n'hésitez pas à écrire un commentaire.

ZiglioUK
la source
1

Un moyen simple de mettre de la valeur dans une variable externe (n'appartient pas à la classe anonymus) est de savoir comment suivre!

De la même manière, si vous voulez obtenir la valeur d'une variable externe, vous pouvez créer une méthode qui retourne ce que vous voulez!

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

    });

 }
héronsanches
la source
-2

Je pensais que les classes anonymes étaient fondamentalement comme des lambdas mais avec une syntaxe pire ... cela s'avère être vrai mais la syntaxe est encore pire et provoque (ce qui devrait être) des variables locales à saigner dans la classe contenant.

Vous ne pouvez accéder à aucune variable finale en les transformant en champs de la classe parent.

Par exemple

Interface:

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

Je ne sais pas s'ils ont réglé cela en java 8 (je suis coincé dans le monde EE et je n'ai pas encore 8) mais en C #, cela ressemblerait à ceci:

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

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

            typeProcessor.doStuffThatUsesLambda();
        }
    }

Vous n'avez pas non plus besoin d'une interface séparée en c # ... ça me manque! Je me surprends à faire de pires conceptions en java et à me répéter davantage parce que la quantité de code + la complexité que vous devez ajouter en java pour réutiliser quelque chose est pire que de simplement copier et coller la plupart du temps.

JonnyRaa
la source
ressemble à un autre hack que vous pouvez utiliser est d'avoir un tableau à un élément, comme mentionné ici stackoverflow.com/a/4732586/962696
JonnyRaa