JAVA匿名内部类(学习笔记)

在这篇文章中你可以了解:
        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;
	}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值