如何向匿名类传递参数?

155

是否可以传递参数或访问匿名类的外部参数?例如:

int myVariable = 1;

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

有没有办法让监听器访问到我的变量或者在不将监听器创建为实际命名类的情况下将变量传递给它?


8
您可以从封闭的方法中引用“final”局部变量。 - Tom Hawtin - tackline
我很喜欢Adam Mmlodzinski的建议,即定义一个私有方法来初始化私有的myVariable实例,并且可以在闭括号处调用该方法以返回“this”。 - dlamblin
这个问题与以下问题有一些共同的目标:https://dev59.com/FHRC5IYBdhLWcg3wP-n5 - Alastair McCormack
你也可以在匿名类中使用全局类变量。这样做可能不太规范,但它能完成任务。 - Jori
12个回答

356
是的,通过添加一个返回'this'的初始化方法并立即调用该方法来实现:
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)  );

不需要 'final' 声明。


5
哇……太棒了!我已经厌倦了只为了将信息传递给我的匿名类而创建一个“final”参考对象。谢谢你的分享! - Matt Klein
7
为什么init()函数必须返回this?我不太理解这个语法。 - Jori
12
因为你的 myButton.addActionListener(...) 方法需要一个ActionListener对象作为参数,该方法在调用时将返回该对象。 - user494461
1
ceving,这有什么问题吗?如果您想初始化一个final变量,没有必要将其添加到匿名类中 - 您可以使用(或像您在下面的答案中所做的那样)在匿名类外部声明的final变量。但是,如果您的匿名类扩展了一些其他类,该类会初始化一些final变量,那么您仍然需要将它们传递给构造函数。 - Adam Mlodzinski
2
更简洁的写法:private int anonVar = myVariable; - Anm
显示剩余8条评论

81

从技术上讲,不行,因为匿名类不能有构造函数。

但是,类可以引用包含作用域的变量。对于匿名类,这些可以是包含类的实例变量或标记为final的局部变量。

编辑:正如Peter所指出的,您还可以向匿名类的超类构造函数传递参数。


21
匿名类使用其父类的构造函数,例如new ArrayList(10) { } - Peter Lawrey
好的观点。这将是向匿名类传递参数的另一种方式,尽管您可能无法控制该参数。 - Matthew
匿名类不需要构造函数。 - newacct
4
匿名类可以有实例初始化器,这些初始化器可以在匿名类中充当无参构造函数。它们的执行顺序与字段赋值相同,即在 super() 之后和实际构造函数的其余部分之前执行。语法为:new someclass(){ fields; {initializer} fields; methods(){} }。这有点像静态初始化程序,但没有 static 关键字。具体可见 Java 语言规范第 8.6 节。 - Mark Jeronimus
看这个 https://dev59.com/E3A75IYBdhLWcg3w49cR#3045185 它说了如何在没有构造函数的情况下实现。 - Developer Marius Žilėnas

31

是的,你可以捕获变量并将其暴露给内部类。唯一的限制是它必须是final


据我所知,从匿名类引用的实例变量不必是final。 - Matthew
8
实例变量通过关键字 this 来引用,而 this 是一个常量。 - Peter Lawrey
如果我不想将变量更改为“final”怎么办?我找不到任何替代方法。这可能会影响旨在为“final”的原始参数。 - Alston

22

就像这样:

final int myVariable = 1;

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

14

这将会产生神奇的效果

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

这并没有解决所提出的问题。你仍然会面临访问本地变量的问题,因此你需要使用Adam Mlodzinski或adarshr的解决方案。 - Matt Klein
1
@MattKlein 对我来说,它看起来解决了这个问题。实际上是同样的东西,而且更简洁。 - haelix
1
问题是想知道如何将参数传递到类中,就像需要参数的构造函数一样。链接(应该在此进行总结)仅显示如何拥有一个不带参数的实例初始化程序,这并没有回答问题。这种技术可以与final变量一起使用,正如aav所述,但是这个答案没有提供这方面的信息。到目前为止,最好的答案是由Adam Mlodzinksi给出的(我现在专门使用这种模式,不再使用final!)。我坚持认为这没有回答所问的问题。 - Matt Klein

7

我的解决方案是使用一个返回实现的匿名类的方法。常规参数可以传递给此方法,并在匿名类中使用。

例如:(用于处理文本框更改的一些GWT代码):

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

在这个例子中,新的匿名类方法将被引用为:

textBox.addChangeHandler(newNameChangeHandler(myAxisName, myLogger))
或者,根据原帖的要求:
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);

这很好,它将匿名类限制为一些易于识别的变量,并消除了必须将某些变量设置为final的可恶之处。 - Miserable Variable

3
其他人已经回答了匿名类只能访问final变量的问题。但他们没有解决如何保持原始变量为非final的问题。Adam Mlodzinski 提供了一个解决方案,但它相当冗长。对于这个问题有一个更简单的解决方案:如果您不想让myVariable成为final,您必须将其包装在一个新的作用域中,在这个作用域中它是否是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
        }
    });
}

亚当·姆洛金斯基在他的答案中没有做任何其他事情,只是用更多的代码实现了相同的功能。

这仍然可以在没有额外作用域的情况下工作。它与使用final的其他答案实际上是相同的。 - Adam Mlodzinski
@AdamMlodzinski 不,它与您的答案实际上是相同的,因为它在私有作用域中引入了一个具有原始变量值的新变量。 - ceving
这并不是完全相同的。在您的情况下,您的内部类无法更改anonVar - 因此,效果是不同的。 如果您的内部类必须维护某些状态,则代码必须使用具有setter的某种对象,而不是原始类型。 - Adam Mlodzinski
@AdamMlodzinski 那不是问题所在。问题是如何在不使其最终的情况下访问外部变量。解决方案是制作一个最终的副本。当然,人们可以在监听器中制作额外的可变副本。但首先没有被要求,第二个它不需要任何 init 方法。我可以添加一行额外的代码到我的例子中,以便拥有这个额外的变量。如果你是建造者模式的铁杆粉丝,请随意使用它们,但在这种情况下并不需要。 - ceving
我不明白这与使用“final”变量解决方案有什么不同。 - Kevin Rave

3
您可以使用普通的 Lambda 表达式("Lambda 表达式可以捕获变量"),具体请参见此处
int myVariable = 1;
ActionListener al = ae->System.out.println(myVariable);
myButton.addActionListener( al );

甚至是一个函数
Function<Integer,ActionListener> printInt = 
    intvar -> ae -> System.out.println(intvar);

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

使用函数是重构装饰器和适配器的好方法,详情请见此处
我刚开始学习lambda,如果你发现有错误,请随意留下评论。

1
如果“myVariable”是一个字段,你可以使用限定的this:
public class Foo {
    int myVariable = 1;

    new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            Foo.this.myVariable = 8;
        }
    });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接