How to pass parameters to anonymous class?
Asked Answered
B

12

156

Is it possible to pass parameters, or access external parameters to an anonymous class? For example:

int myVariable = 1;

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

Is there any way for the listener to access myVariable or be passed myVariable without creating the listener as an actual named class?

Balthazar answered 24/2, 2011 at 16:4 Comment(4)
You can reference final local variables from the enclosing method.Misconceive
I do like the look of Adam Mmlodzinski's suggestion of defining a private method that initializes private myVariable instance(s) and can be called at the closing brace due to returning this.Jamey
This question has some shared goals of: #362924Helium
You can also use the global class variables from inside the anonymous class. Perhaps not very clean, but it can do the job.Sos
G
81

Technically, no, because anonymous classes can't have constructors.

However, classes can reference variables from containing scopes. For an anonymous class these can be instance variables from the containing class(es) or local variables that are marked final.

edit: As Peter pointed out, you can also pass parameters to the constructor of the superclass of the anonymous class.

Gullible answered 24/2, 2011 at 16:8 Comment(5)
An anonymous class use use the constructors of its parent. e.g new ArrayList(10) { }Irrelevant
Good point. So that would be another way to pass a parameter to an anonymous class, although it is likely you won't have control over that parameter.Gullible
anonymous classes don't need constructorsHalfhour
Anonymous classes can have Instance Initializers, which can function as parameter-less constructors in anonymous classes. These are executed in the same order as field assignments, i.e. after super() and before the rest of the actual constructor. new someclass(){ fields; {initializer} fields; methods(){} }. It's sort of like a static initializer but without the static keyword. docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.6Evasive
See this https://mcmap.net/q/121119/-constructors-in-inner-classes-implementing-interfaces it says how to implement without constructor.Mythology
K
357

Yes, by adding an initializer method that returns 'this', and immediately calling that method:

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

No 'final' declaration needed.

Kyat answered 30/8, 2012 at 22:34 Comment(11)
This is pretty great! Say the interface is returning anonVar somewhere though, and anonVar were maybe a collection, it'd be nice if you could set it as final, but then you'd be back at the other way to do this.Jamey
dlamblin, if anonVar were a Collection, declaring it final wouldn't protect the contents if you expose it with a getter method. Rather, your getter method would need to either return a copy of the Collection, or wrap it using one of the Collections.unmodifiableCollection() variants. If you're suggesting to set it final as one might do with captured constructor arguments, then, no, you can't do that - you would need to make a named class instead.Kyat
wow... brilliant! I'm so tired of creating a final reference object just so I can get info into my anonymous classes. Thank you for sharing!Marroquin
Why does the init() function have to return this? I don't get the syntax really.Sos
because your myButton.addActionListener(...) expects an ActionListener object as an object which is returned when you call its method.Unstuck
I guess.. I find that rather ugly myself though, though it works. I find most of the time I can simply afford to make the necessary variables and function parameters final and directly reference them from the inner class, since usually they are only getting read.Wilt
The problem with this solution is, that the initialization takes place out of the constructor and this means, that the variables to be initialized (here anonVar) can not be final any more. This approach works for all but final variables.Mota
ceving, how is that a problem? If you want to initialzie a final variable, there is no need to add it to your anonymous class - you can use (or add, as you do in your answer below) a final variable declared outside of the anonymous class. If, however, your anonymous class extends some other class which initializes some final variables, you would have to pass those to the constructor anyway.Kyat
Why is it, init() can be private?Galaxy
it reminds me of javascript :) Good solution though.Waterscape
Simpler: private int anonVar = myVariable;Homochromatic
G
81

Technically, no, because anonymous classes can't have constructors.

However, classes can reference variables from containing scopes. For an anonymous class these can be instance variables from the containing class(es) or local variables that are marked final.

edit: As Peter pointed out, you can also pass parameters to the constructor of the superclass of the anonymous class.

Gullible answered 24/2, 2011 at 16:8 Comment(5)
An anonymous class use use the constructors of its parent. e.g new ArrayList(10) { }Irrelevant
Good point. So that would be another way to pass a parameter to an anonymous class, although it is likely you won't have control over that parameter.Gullible
anonymous classes don't need constructorsHalfhour
Anonymous classes can have Instance Initializers, which can function as parameter-less constructors in anonymous classes. These are executed in the same order as field assignments, i.e. after super() and before the rest of the actual constructor. new someclass(){ fields; {initializer} fields; methods(){} }. It's sort of like a static initializer but without the static keyword. docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.6Evasive
See this https://mcmap.net/q/121119/-constructors-in-inner-classes-implementing-interfaces it says how to implement without constructor.Mythology
D
31

yes. you can capture variable, visible to the inner class. the only limitation is that it has to be final

Dishevel answered 24/2, 2011 at 16:5 Comment(3)
Instance variables referenced from an anonymous class do not have to be final afaik.Gullible
Instance variables are referenced via this which is final.Irrelevant
What if I don't want the variable be changed to final? I can't find any alternative. This may influence the origin parameter which is designed to be final.Decorator
J
22

Like this:

final int myVariable = 1;

myButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        // Now you can access it alright.
    }
});
Judges answered 24/2, 2011 at 16:6 Comment(0)
F
14

This will do the magic

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));
Franzoni answered 18/12, 2014 at 13:46 Comment(0)
L
8

As shown at http://www.coderanch.com/t/567294/java/java/declare-constructor-anonymous-class you can add an instance initializer. It's a block that doesn't have a name and gets executed first (just like a constructor).

Looks like they're also discussed at Why java Instance initializers? and How is an instance initializer different from a constructor? discusses differences from constructors.

Lingerfelt answered 18/2, 2013 at 1:0 Comment(3)
This doesn't solve the question being asked. You will still have the problem of accessing local variables, so you'll either need to use the solution from Adam Mlodzinski or adarshrMarroquin
@MattKlein To me, it looks like it solves it. It's the same thing actually, and less verbose.Theta
The question wanted to know how to pass parameters into the class, like you would with a constructor that requires parameters. The link (which should have been summarized here) only shows how to have a parameter-less instance initializer, which doesn't answer the question. This technique could be used with final variables as described by aav, but that info wasn't provided in this answer. By far, the best answer is the one given by Adam Mlodzinksi (I now use this pattern exclusively, no more finals!). I stand by my comment that this doesn't answer the question asked.Marroquin
H
7

My solution is to use a method that returns the implemented anonymous class. Regular arguments may be passed to the method and are available within the anonymous class.

For example: (from some GWT code to handle a Text box change):

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

For this example, the new anonymous class-method would be referenced with:

textBox.addChangeHandler(newNameChangeHandler(myAxisName, myLogger))

OR, using the OP's requirements:

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);
Helium answered 18/4, 2013 at 9:11 Comment(1)
This is good, it restricts the anonymous class to an few easy-to-identify variables and removes abominations of having to make some variables final.Hern
M
3

Other people have already answered that anonymous classes can access only final variables. But they leave the question open how to keep the original variable non-final. Adam Mlodzinski gave a solution but is is pretty bloated. There is a much simpler solution for the problem:

If you do not want myVariable to be final you have to wrap it in a new scope where it does not matter, if it is final.

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 does not do anything else in his answer but with much more code.

Mota answered 30/10, 2013 at 16:40 Comment(5)
This still works without the extra scope. It's effectively the same as the other answers using final.Kyat
@AdamMlodzinski No it is effectively the same as your answer, because it introduces a new variable with the value of the original variable in a private scope.Mota
It is not effectively the same. In your case, your inner class cannot make changes to anonVar - thus, the effect is different. If your inner class has to, say, maintain some state, your code would have to use some sort of Object with a setter rather than a primitive.Kyat
@AdamMlodzinski That was not the question. The question was how to access the outer variable without making itself final. And the solution is to make a final copy. And of course it is obvious that one can make an additional mutable copy of the variable in the listener. But first it was not asked and second it does not require any init method. I can add one additional line of code to my example in order to have this additional variable. If you are a great fan of builder patterns feel free to use them, but they are not necessary in this case.Mota
I don't see how this is different than using final variable solution.Report
W
3

You can use plain lambdas ("lambda expressions can capture variables")

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

or even a Function

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

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

Using Function is a great way to refactor Decorators and Adapters, see here

I've just started learning about lambdas, so if you spot a mistake, feel free to write a comment.

Waterlogged answered 14/3, 2017 at 21:46 Comment(0)
W
1

A simple way for put some value into a external variable(doesn't belong for anonymus class) is how folow!

In the same way if you want get the value of a external variable you can create a method that return what you want!

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

    });

 }
Wiper answered 3/7, 2014 at 22:41 Comment(0)
P
1

If "myVariable" is a field, you can use a qualified this:

public class Foo {
    int myVariable = 1;

    new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            Foo.this.myVariable = 8;
        }
    });
}
Phrygia answered 2/1, 2022 at 19:33 Comment(0)
P
-2

I thought anonymous classes were basically like lambdas but with worse syntax... this turns out to be true but the syntax is even worse and causes (what should be) local variables to bleed out into the containing class.

You can access none final variables by making them into fields of the parent class.

Eg

Interface:

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

class:

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

I dont know if they've sorted this out in java 8 (I'm stuck in EE world and not got 8 yet) but in C# it would look like this:

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

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

            typeProcessor.doStuffThatUsesLambda();
        }
    }

You dont need a seperate interface in c# either... I miss it! I find myself making worse designs in java and repeating myself more because the amount of code + complexity you have to add in java to reuse something is worse than just copy and pasting a lot of the time.

Pensionary answered 9/9, 2014 at 14:6 Comment(1)
looks like another hack you can use is to have a one element array as mentioned here https://mcmap.net/q/86788/-why-are-only-final-variables-accessible-in-anonymous-classPensionary

© 2022 - 2024 — McMap. All rights reserved.