1.如何使用匿名内部类
2.使用中要注意什么
3.如何初始化匿名内部类
4.传递给匿名内部类的形参为何要为final。
1.使用匿名内部类
可以把匿名内部类看成一个类,但是这个类没有名字。匿名内部类相当于为抽象类创建了一个子类,但是这个子类没有构造器(不能有),可以认为有且只有默认构造器,默认构造器会调用抽象类构造器。
由于匿名内部类没有名字,所以它的创建方式也相对特殊。创建格式如下:
new 父类构造器(参数列表)|实现接口()
{
//匿名内部类的类体部分
}
java编程思想:匿名内部类与正规的继承相比有些受限,原因在于,虽然匿名内部类既可以扩展类,也可以实现接口,但是不能两者兼备。而且只能实现一个接口。
2.使用中要注意什么
在使用匿名内部类的过程中,我们需要注意如下几点:
1、使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
2、匿名内部类中是不能定义构造函数的。
3、匿名内部类中不能存在任何的静态成员变量和静态方法。
说明:内部类的对象脱离了其外围类的对象就不会存在,静态变量的作用就是让该类的所有对象共享一个状态。这个类的所有对象都可以获取和修改这个状态。如果仅仅是这个目的,就可以推出这个状态也是所有外部对象所共享的状态,因此这个定义就可以提升至外围类中定义,没有必要在内部类中定义,因此在JAVA中不允许在内部类中声明静态变量,但是可以允许其继承父类的静态变量,因为父类可能有很多子类,这些子类不一定是用作内部类。说白了,内部类的用途就是利用外围类对象的资源做事。
代码说明:
class Outer {
int outerX;
Inner inner1 = new Inner();
Inner inner2 = new Inner();
class Inner {
int innerX;
}
public static void main(String[] A) {
Outer outer1 = new Outer();
Outer outer2 = new Outer();
System.out.println(outer1.inner1.getClass());
System.out.println(outer1.inner1.getClass() == outer2.inner1.getClass());
}
}
那么,如果innerX是static则必然存在以下等式:
outer1.inner1.innerX=outer1.inner2.innerX,And
outer2.inner1.innerX=outer2.inner2.innerX,And
outer1.inner1.innerX=outer2.inner1.innerX,Hence
outer1.inner2.innerX=outer2.inner2.innerX
既然这样,完全可以将其提升到外部类。
4、匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
5、匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
3.匿名内部类如何初始化
对于普通类,我们一般可以通过调用构造器进行初始化,另外的方法就是静态和非静态代码块。由于匿名内部类没有自己的构造器(可以认为有一个默认构造器,但是不可见。),对于一些需要在初始化时做的工作,可以使用代码块。
说明:静态代码块只在类初次被调用时执行一次初始化,而非静态代码块是依赖对象的,每次实例化时都会执行。
package com.li.test.classes;
/**
* 匿名内部类初始化
*/
import static net.mindview.util.Print.print;
abstract class Base {
public Base(int i) {
print("Base constructor, i = " + i);
}
public abstract void f();
}
public class AnonymousConstructor {
public static Base getBase(int i) {
return new Base(i) {
{
print("Inside instance initializer");// 利用非静态代码块执行一些初始化操作
}
public void f() {
}
};
}
public static void main(String[] args) {
Base base = getBase(47);
base.f();
}
}
4.为什么传递给匿名内部类的参数必须是final
局部匿名类在源代码编译后也是要生成对应的class文件的(一般会是A$1.class这种形式的文件),那么这个二进制文件是独立于其外围类(A.class)的,就是说它无法知道A类中方法的变量。但是A$1.class又确实要访问A类对应方法的局部变量的值。。。怎么办呢?于是干脆就要求“匿名内部类调用的方法内局部变量必须为final”,这样A$1.class访问A类方法局部变量部分就直接用常量来表示 这是一个编译器设计的问题,如果你了解java的编译原理的话很容易理解。首先,内部类被编译的时候会生成一个单独的内部类的.class文件,这个文件并不与外部类在同一class文件中。当外部类传的参数被内部类调用时,从java程序的角度来看是直接的调用例如:public void dosome(final String a,final int b){
class Dosome{public void dosome(){System.out.println(a+b)}};
Dosome some=new Dosome();
some.dosome();}
从代码来看好像是那个内部类直接调用的a参数和b参数,但是实际上不是,在java编译器编译以后实际的操作代码是:
class Outer$Dosome{
public Dosome(final String a,final int b){
this.Dosome$a=a;
this.Dosome$b=b;}public void dosome(){
System.out.println(this.Dosome$a+this.Dosome$b);} }}
从以上代码看来,内部类并不是直接调用方法传进来的参数,而是内部类将传进来的参数通过自己的构造器备份到了自己的内部,自己内部的方法调用的实际是自己的属性而不是外部类方法的参数。这样理解就很容易得出为什么要用final了,因为两者从外表看起来是同一个东西,实际上却不是这样,如果内部类改掉了这些参数的值也不可能影响到原参数,然而这样却失去了参数的一致性,因为从编程人员的角度来看他们是同一个东西,如果编程人员在程序设计的时候在内部类中改掉参数的值,但是外部调用的时候又发现值其实没有被改掉,这就让人非常的难以理解和接受,为了避免这种尴尬的问题存在,所以编译器设计人员把内部类能够使用的参数设定为必须是final来规避这种莫名其妙错误的存在。
假设:
我们在方法内定义了一个 匿名的 Thread 子类,他使用了方法的局部参数,然后我让这个线程运行去,因为是不同的线程,那么当我这个方法的启动线程的语句执行过了,而且我修改了这个参数或局部变量,那么那个线程启动执行的时候是不是会出现莫名其妙的问题:运行时刻能访问到的变量太难以捉摸了,我是该复制一份过去给新线程运行时使用还是到时候再来取呢(再来取时已经物是人非了)?
Java 为了消除这个编程中可能出现的歧义,使用方法内的内部类时如果访问了方法的参数或局部变量,那么它应该是 final 的。
package com.li.test.classes;
/**
* 匿名内部类测试
*/
import static net.mindview.util.Print.print;
abstract class Base {
public Base(Student stu) {
print("Base constructor, i = " + stu);
}
public abstract void f();
}
public class AnonymousConstructor {
public static Base getBase(final Student stu) {
return new Base(stu) {
{
print("Inside instance initializer");
}
public void f() {
stu.setname("liping");
print("In anonymous f()" + stu);
}
};
}
public static void main(String[] args) {
Student stu = new Student(1, "li", 25, "nothing");
Base base = getBase(stu);
base.f();
System.out.println(stu);
}
}
package com.li.test.classes;
public class Student implements {
int id;// 学号
String name;// 姓名
int age;// 年龄
String department; // 系别
public Student(int id, String name, int age, String department) {
this.id = id;
this.name = name;
this.age = age;
this.department = department;
}
public void setname(String name) {
this.name = name;
}
public String toString() {
return name;
}
}