如何将参数传递给匿名类?


146

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

int myVariable = 1;

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

侦听器有什么方法可以访问myVariable或被传递myVariable而不将侦听器创建为实际的命名类?


7
您可以final从封闭方法中引用局部变量。
Tom Hawtin-大头钉

我确实喜欢Adam Mmlodzinski的建议,即定义一个私有方法来初始化私有myVariable实例,并且由于return可以在右括号处调用的外观this
dlamblin


您也可以从匿名类内部使用全局类变量。也许不是很干净,但是可以胜任。
2013年

Answers:


78

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

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

编辑:正如彼得指出,您还可以将参数传递给匿名类的超类的构造函数。


21
匿名类使用其父级的构造函数。例如new ArrayList(10) { }
Peter Lawrey

好点子。因此,这是将参数传递给匿名类的另一种方法,尽管您可能无法控制该参数。
马修·威利斯,

匿名类不需要构造函数
newacct 2012年

4
匿名类可以具有实例初始化器,它们可以用作匿名类中的无参数构造函数。它们以与字段分配相同的顺序执行,即在super()实际构造函数的其余部分之前和之后。new someclass(){ fields; {initializer} fields; methods(){} }。它有点像静态初始化器,但是没有static关键字。docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.6
Mark Jeronimus

请参阅此stackoverflow.com/a/3045185/1737819,其中说明了如何在没有构造函数的情况下实现。
开发人员MariusŽilėnas15年

336

是的,通过添加一个返回“ 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)  );

无需“最终”声明。


4
哇...太棒了!我厌倦了创建final引用对象,只是为了将信息获取到匿名类中。感谢你的分享!
马特·克莱因

7
为什么init()函数必须返回this?我真的不了解语法。
2013年

11
因为您的myButton.addActionListener(...)期望ActionListener对象作为在您调用其方法时返回的对象。

1
我想..我觉得这虽然很丑,但确实可行。我发现大多数时候我可以负担得起最终所需的变量和函数参数,并直接从内部类中引用它们,因为通常它们只是被读取。
Thomas

2
更简单:private int anonVar = myVariable;
2014年

29

是。您可以捕获内部类可见的变量。唯一的限制是它必须是最终的


从匿名类引用的实例变量不必是final afaik。
马修·威利斯,

8
通过this最终变量引用实例变量。
彼得·劳瑞

如果我不希望将变量更改为该怎么办final?我找不到其他选择。这可能会影响原定为的原点参数final
Alston 2014年

20

像这样:

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

http://www.coderanch.com/t/567294/java/java/declare-constructor-anonymous-class所示,您可以添加实例初始化程序。这是一个没有名称的块,首先被执行(就像构造函数一样)。

似乎在“ 为什么要使用Java实例初始化程序”中也进行了讨论如何初始化从构造不同的实例?讨论与构造函数的区别。


这不能解决所问的问题。您仍然会遇到访问局部变量的问题,因此您将需要使用Adam Mlodzinski或adarshr的解决方案
Matt Klein

1
@MattKlein对我来说,似乎可以解决它。实际上,这是同一件事,并且不太冗长。
haelix

1
这个问题想知道如何将参数传递给类,就像您需要参数的构造函数一样。该链接(应该在此处进行了概述)仅显示了如何具有一个无参数的实例初始化程序,该初始化程序无法回答问题。可以将这种技术与finalaav所述的变量一起使用,但是此答案中未提供该信息。到目前为止,最好的答案是Adam Mlodzinksi给出的答案(我现在仅使用此模式,不再需要决赛!)。我支持我的意见,即这不能回答所提出的问题。
马特·克莱因

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

,根据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);

这样做很好,它将匿名类限制为几个易于识别的变量,并且消除了必须使某些变量成为最终变量的可憎之处。
可悲的变量

3

其他人已经回答说匿名类只能访问最终变量。但是他们悬而未决的问题是如何保持原始变量为非最终变量。亚当·姆洛津斯基Adam Mlodzinski)提出了解决方案,但他肿了。有一个更简单的解决方案:

如果您不想myVariable成为最终版本,则必须将其包装在一个新的范围内,如果最终版本没有关系,则不要紧。

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)的答案没有做任何其他事情,但是代码更多。


这仍然没有额外的范围。它实际上与使用final的其他答案相同。
2013年

@AdamMlodzinski不,它实际上与您的答案相同,因为它在私有范围内引入了一个具有原始变量值的新变量。
2013年

实际上不是一样的。就您而言,您的内部类无法更改anonVar-因此,效果会有所不同。例如,如果您的内部类必须维护某种状态,则您的代码将必须使用某种带有setter的Object而不是原始的Object。
亚当·姆洛曾斯基

@AdamMlodzinski这不是问题。问题是如何访问外部变量而不将其定为最终变量。解决方案是制作最终副本。当然,很明显,可以在侦听器中对变量进行另一可变的复制。但是首先它没有被询问,其次它不需要任何init方法。我可以在示例中添加一行附加代码,以获取此附加变量。如果您是构建器模式的忠实拥护者,请随时使用它们,但是在这种情况下,则没有必要。
2013年

我看不出这与使用final可变解决方案有何不同。
凯文·拉夫

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

将一些值放入外部变量(不属于匿名类)的一种简单方法是如何!

同样,如果要获取外部变量的值,则可以创建一个返回所需内容的方法!

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

我以为匿名类基本上像lambdas一样,但是语法更差……事实证明这是事实,但是语法更糟,并且会导致(应该是)局部变量渗出到包含类中。

通过将最终变量置于父类的字段中,可以访问任何最终变量。

例如

接口:

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

类:

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

我不知道他们是否在Java 8中解决了这个问题(我被困在EE世界中,还没有得到8),但是在C#中看起来像这样:

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

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

            typeProcessor.doStuffThatUsesLambda();
        }
    }

您也不需要C#中的单独接口...我想念它!我发现自己在Java中进行了更糟糕的设计,并且重复了很多次,因为在Java中添加代码以重复使用某些东西所带来的代码量和复杂性要比仅仅复制和粘贴很多时间更糟糕。


看起来您可以使用的另一种技巧是拥有一个元素数组,如此处所述stackoverflow.com/a/4732586/962696
JonnyRaa 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.