为什么在Java中的匿名类中添加公共字段不起作用?

Why does adding a public field to an anonymous class in Java not work?

我有一个如下定义的示例类:

1
2
3
4
5
6
7
public class FooBar {

  void method1(Foo foo){ // Should be overwritten
    ...
  }

}

后来,当我尝试这个:

1
2
3
4
5
6
7
8
9
FooBar fooBar = new FooBar(){
  public String name = null;
  @Override
  void method1(Foo foo){
    ...
  }
};

fooBar.name ="Test";

我收到一个错误,说明名称字段不存在。 为什么?


因为变量"fooBar"的类型是FooBar(所述变量中对象的运行时类型是实现FooBar的匿名类的运行时类型,也是FooBar的子类型)...

...并且类型FooBar没有所述成员。因此,编译错误。 (请记住,变量"fooBar"可以包含符合FooBar的任何对象,即使没有name的对象,因此编译器会拒绝非类型安全的代码。)

编辑:对于一个解决方案,请参阅不可响应的答案,该答案使用本地类声明来创建新的命名类型(以替换帖子中的匿名类型)。

Java不支持这样做的方法(主要是:Java不支持有用的类型推断),尽管以下方法确实有效,即使不是很有用:

1
2
3
4
5
6
7
(new foobar(){
  public String name = null;
  @Override
  void method1(Foo foo){
    ...
  }
}).name ="fred";

快乐的编码。

Scala和C#都支持所需的类型推断,因此支持局部变量的匿名类型特化。 (尽管C#不支持匿名扩展现有类型)。但是,Java没有。


本地班级会这样做

1
2
3
4
5
6
7
8
9
10
{
    class MyFooBar extends FooBar{
        String name = null;
        ...
    };

    MyFooBar fooBar = new MyFooBar();

    fooBar.name ="Test";
}


试试这个。

1
2
3
4
5
@SafeVarargs
public static < T > void runWithObject(T object, Consumer< T >... progs) {
    for (Consumer< T > prog : progs)
        prog.accept(object);
}

1
2
3
4
5
6
7
8
9
10
11
runWithObject(
    new FooBar() {
        String name = null;
        @Override
        void method1(Foo foo) {
            System.out.println("name=" + name);
        }
    },
    object -> object.name ="Test",
    object -> object.method1(new Foo())
);

结果:

1
name=Test

或者您可以在Java 10或更高版本中使用var

1
2
3
4
5
6
7
8
9
10
var fooBar = new FooBar() {
    public String name = null;

    @Override
    void method1(Foo foo) {
        System.out.println("name=" + name);
    }
};
fooBar.name ="Test";
fooBar.method1(new Foo());

你也可以这样做

1
2
3
4
5
6
7
Boolean var= new anonymousClass(){
    private String myVar; //String for example
    @Overriden public Boolean method(int i){
          //use myVar and i
    }
    public String setVar(String var){myVar=var; return this;} //Returns self instane
}.setVar("Hello").method(3);

正如大家所说,这是由于静态类型和FooBar类不包含name。所以它不会起作用。

我想指出Anonymous类的建议用法。

匿名类(或接近闭包,也许是lambdas。相似但不相同)来自函数式编程范例,其中状态应该是不可变的。

话虽这么说,你为什么要用这样的课程?当你需要快速而简短的事情时,不一定要完整的课程。例:

1
2
3
4
5
6
7
MyTask() //This is a method
{
    new Thread(new Runnable() { //Anonymous class
        public void run()
        {}
    }).start();
}

将实现仅包含在函数/类中的理解很重要。

scope of the variables defined in the Anonymous class (or closed-over function) should only be used inside the Anonymous class,无法从其他程序代码访问。

因此,你不应该(并且无论如何不能)设置fooBar.name ="Test";


FooBar类型是FooBar,它没有这样的变量,因此无法编译代码。您可以通过反射访问它。


您正在创建FooBar类型的对象。编译器只知道为类/接口FooBar定义的成员。

请记住,java是一种静态语言,而不是动态语言。它不会在运行时检查对象是否存在,它会在编译时根据类型声明进行检查。


FooBar是对类型为FooBar的对象的引用,并且此类对象没有字段name。就那么简单。由于它是一个匿名类型,引用该字段的唯一方法是通过其this引用。