实例化内部类的奇怪语法

43

我没想到在Java的这个阶段还会遇到完全不同的语法, 但是我刚刚遇到了:

下面代码的确切上下文和它应该做什么不太相关 - 它只是为了提供一些背景。

我正在试图在IT Mill Toolkit中合成一个事件,所以我写了下面这行代码:

buttonClick(new Button.ClickEvent(button));

然而,Eclipse给出了以下错误信息:

Button的封闭实例不可访问。必须使用Button类型的封闭实例限定分配(例如,在x是Button实例的情况下使用x.new A())。

当我将上面那行代码重写如下时,它就不再报错了:

buttonClick(button.new ClickEvent(button)); // button instanceof Button
所以我的问题是:后一种语法确切地是什么意思,为什么第一个片段不起作用? Java在抱怨什么,第二个版本中它在做什么?

背景信息:Button和Button.ClickEvent都是非抽象公共类。


有趣的问题,但标题可能可以更加精致。 - mafu
你可以自由提出建议。当时我不知道怎么称呼这种语法,所以标题很遗憾地保留了下来,变得很奇怪。 - Henrik Paul
6个回答

71

内部类(例如 Button.ClickEvent)需要引用外部类(Button)的实例。

这个语法使用 button 的值来设置新创建的 Button.ClickEvent 的外部类引用。

以下是一个示例 - 忽略封装等问题,仅用于演示目的:

class Outer
{
    String name;

    class Inner
    {
        void sayHi()
        {
            System.out.println("Outer name = " + name);
        }
    }
}

public class Test
{
    public static void main(String[] args)
    {
        Outer outer = new Outer();
        outer.name = "Fred";

        Outer.Inner inner = outer.new Inner();
        inner.sayHi();
    }
}

请参见规范第8.1.3节,了解有关内部类和封闭实例的更多信息。


7
如果内部类在运行时不需要引用外部类,你可以在内部类声明中使用static关键字来消除这个要求,比如上面的例子中的static class Inner,尽管很明显你将无法访问Outer.name属性...! - Bill Michell
@JonSkeet 我在谈论 outer.new 结构。 - Inquisitive
@Inquisitive:刚在http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.9找到了它——这是一个“限定类实例创建表达式”。 - Jon Skeet
@JonSkeet 谢谢。我记得在 Effective Java 书中读到过一个 qualified this 结构,但我现在想不起来具体是什么了。我认为 "qualified this" 和 "qualified class instance creation" 是相关的。你知道它们之间的关系吗? - Inquisitive
@Inquisitive:我怀疑“qualified this”是在内部类中,试图获取封闭类的相关实例。 - Jon Skeet
显示剩余2条评论

10

Button.ClickEvent是一个非静态的内部类,因此该类的实例只能存在于Button的实例中。

在您的第二个代码示例中,您有一个Button的实例,并且创建了一个嵌套在此Button实例中的ClickEvent的实例...


9
Java中的非静态内部类包含一个指向声明它的外部类实例的隐藏引用。因此,你最初收到的错误消息告诉你,如果没有指定它要附加到的外部类实例,就不能创建内部类的新实例。
也许你之前没见过这种语法的原因是,内部类通常在外部类的方法中分配,在那里编译器会自动处理这个问题。

3
为了避免把自己和其他程序员弄糊涂,你可以将内部类始终设置为静态的。如果需要引用外部类,则可以在构造函数中显式传递它。请保留HTML标记。

或者将内部类设为私有,并坚持使用隐式的 this。 - Tom Hawtin - tackline

2

实际上你可以这样做,但是你需要在Button内声明ClickEventstatic,然后你就不应该有任何使用语法的问题:

buttonClick(new Button.ClickEvent(button));

基本上,static 使得类 ClickEvent 直接属于类 Button 而不是 Button 的特定实例(即 new Button())。
以下是 @Jon Skeet 的示例:
// Button.java
class Button
{

    public static class ClickEvent
    {
        public ClickEvent(Button b)
        {
            System.out.println("Instance: " + this.toString());
        }
    }
}

// Test.java
public class Test
{
    public static void main(String[] args)
    {
        Button button = new Button();
        buttonClick(new Button.ClickEvent(button));
    }

    public static void buttonClick (Button.ClickEvent ce) {
    }
}

-1

如果你输入了正确的代码,它就可以编译通过。

buttonClick(new Button().ClickEvent(button));

使用

buttonClick(new Button.ClickEvent(button));

而不是

buttonClick(new Button.ClickEvent());

因为构造函数是一种方法,当你在Java中调用一个方法时,即使参数列表为空,也必须传递参数列表。


实际上不行,因为它试图使用该方法调用ClickEvent(Button),但找不到... - Henrik Paul
new Button().new ClickEvent(button) ? 新建按钮().新建点击事件(按钮)? - Tom Hawtin - tackline

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