译自oracle官网:Anonymous Classes
匿名类使您能够使代码更加简洁。 它们使您能够同时声明和实例化一个类。 除了没有名字之外,它们就像local class。 如果您只需要使用一次local class,请使用它们。
声明匿名内部类
虽然本地类是类声明,但匿名类是表达式,这意味着您可以在另一个表达式中定义类。 以下示例HelloWorldAnonymousClasses在局部变量frenchGreeting和spanishGreeting的初始化语句中使用匿名类,但使用本地类来初始化变量englishGreeting:
public class HelloWorldAnonymousClasses {
interface HelloWorld {
public void greet();
public void greetSomeone(String someone);
}
public void sayHello() {
class EnglishGreeting implements HelloWorld {
String name = "world";
public void greet() {
greetSomeone("world");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Hello " + name);
}
}
HelloWorld englishGreeting = new EnglishGreeting();
HelloWorld frenchGreeting = new HelloWorld() {
String name = "tout le monde";
public void greet() {
greetSomeone("tout le monde");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Salut " + name);
}
};
HelloWorld spanishGreeting = new HelloWorld() {
String name = "mundo";
public void greet() {
greetSomeone("mundo");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Hola, " + name);
}
};
englishGreeting.greet();
frenchGreeting.greetSomeone("Fred");
spanishGreeting.greet();
}
public static void main(String... args) {
HelloWorldAnonymousClasses myApp =
new HelloWorldAnonymousClasses();
myApp.sayHello();
}
}
匿名内部类的语法
如前所述,匿名类是一个表达式。 匿名类表达式的语法就像构造函数的调用一样,除了在代码块中包含类定义之外。
考虑frenchGreeting对象的实例:
HelloWorld frenchGreeting = new HelloWorld() {
String name = "tout le monde";
public void greet() {
greetSomeone("tout le monde");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Salut " + name);
}
};
匿名类表达式由以下内容组成:
- new操作符
- 要实现的接口的名称或要扩展的类。 在这个例子中,匿名类正在实现接口HelloWorld。
- 包含构造函数参数的圆括号,就像普通的类实例创建表达式一样。 注意:当你实现一个接口时,没有构造函数,所以你使用一对空括号,就像这个例子。
- 类声明主体。 更具体地说,在主体中,方法声明是允许的,但语句不是。
因为匿名类定义是一个表达式,所以它必须是语句的一部分。 在这个例子中,匿名类表达式是实例化frenchGreeting对象的语句的一部分。 (这解释了为什么在右大括号后面有分号。)
Accessing Local Variables of the Enclosing Scope, and Declaring and Accessing Members of the Anonymous Class
访问封闭作用域内的局部变量,声明和访问匿名类的成员
像本地类一样,匿名类可以捕获变量; 他们对封闭范围的局部变量具有相同的访问权限:
- 匿名类可以访问其封闭类的成员。
- 匿名类不能访问其封闭范围中未声明为最终或有效最终的局部变量。
- 像嵌套类一样,匿名类中的类型声明(例如变量)会隐藏封闭范围中具有相同名称的其他任何声明。 有关更多信息,请参阅shawdow。
匿名类与在方法内部的本地类也具有相同的限制:
- 您无法在匿名类中声明静态初始化程序或成员接口。
- 匿名类可以有静态成员,只要它们是常量变量。
- 字段
- 额外的方法(即使他们没有实现超类型的任何方法)
- 实例初始值设定项
- 本地类
但是,您不能在匿名类中声明构造函数。
==========================
译文结束,以下是java为什么匿名内部类的参数引用时要用final的解释
注意事项
在使用匿名内部类的过程中,我们需要注意如下几点:
1、使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
2、匿名内部类中是不能定义构造函数的。
3、匿名内部类中不能存在任何的静态成员变量和静态方法。
4、匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
5、匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
三、使用的形参为何要为final
参考文件:http://android.blog.51cto.com/268543/384844
我们给匿名内部类传递参数的时候,若该形参在内部类中需要被使用,那么该形参必须要为final。也就是说:当所在的方法的形参需要被内部类里面使用时,该形参必须为final。
为什么必须要为final呢?
首先我们知道在内部类编译成功后,它会产生一个class文件,该class文件与外部类并不是同一class文件,仅仅只保留对外部类的引用。当外部类传入的参数需要被内部类调用时,从java程序的角度来看是直接被调用:
public class OuterClass { public void display(final String name,String age){ class InnerClass{ void display(){ System.out.println(name); } } } }
从上面代码中看好像name参数应该是被内部类直接调用?其实不然,在java编译之后实际的操作如下:
public class OuterClass$InnerClass { public InnerClass(String name,String age){ this.InnerClass$name = name; this.InnerClass$age = age; } public void display(){ System.out.println(this.InnerClass$name + "----" + this.InnerClass$age ); } }
所以从上面代码来看,内部类并不是直接调用方法传递的参数,而是利用自身的构造器对传入的参数进行备份,自己内部方法调用的实际上时自己的属性而不是外部方法传递进来的参数。
直到这里还没有解释为什么是final?在内部类中的属性和外部方法的参数两者从外表上看是同一个东西,但实际上却不是,所以他们两者是可以任意变化的,也就是说在内部类中我对属性的改变并不会影响到外部的形参,而然这从程序员的角度来看这是不可行的,毕竟站在程序的角度来看这两个根本就是同一个,如果内部类该变了,而外部方法的形参却没有改变这是难以理解和不可接受的,所以为了保持参数的一致性,就规定使用final来避免形参的不改变。
简单理解就是,拷贝引用,为了避免引用值发生改变,例如被外部类的方法修改等,而导致内部类得到的值不一致,于是用final来让该引用不可改变。
故如果定义了一个匿名内部类,并且希望它使用一个其外部定义的参数,那么编译器会要求该参数引用是final的。
四、匿名内部类初始化
我们一般都是利用构造器来完成某个实例的初始化工作的,但是匿名内部类是没有构造器的!那怎么来初始化匿名内部类呢?使用构造代码块!利用构造代码块能够达到为匿名内部类创建一个构造器的效果。
public class OutClass { public InnerClass getInnerClass(final int age,final String name){ return new InnerClass() { int age_ ; String name_; //构造代码块完成初始化工作 { if(0 < age && age < 200){ age_ = age; name_ = name; } } public String getName() { return name_; } public int getAge() { return age_; } }; } public static void main(String[] args) { OutClass out = new OutClass(); InnerClass inner_1 = out.getInnerClass(201, "chenssy"); System.out.println(inner_1.getName()); InnerClass inner_2 = out.getInnerClass(23, "chenssy"); System.out.println(inner_2.getName()); } }
如果还不理解匿名类访问封闭范围的局部变量,该局部变量必须为final或者有效final,
可以参阅知乎的解答:java为什么匿名内部类的参数引用时final?
java编译器支持闭包,但支持的不完整。说支持闭包,是因为编译器编译的时候悄悄对函数做了手脚,偷偷把外部环境中的局部变量,拷贝了一份到匿名内部类中。
用原话说就是:
java编译器实现的是capture-by-value,并没有实现capture-by-reference。
只有后者才能使匿名内部类和外部环境局部变量保持同步。
但java又不肯明说,只能粗暴地一刀切 ,既然内外不能同步,就不允许改变外部环境的局部变量。