在【Java学习笔记之二十五】初步认知Java内部类中对匿名内部类做了一个简单的介绍,但是内部类还存在很多其他细节问题,所以就衍生出这篇博客。在这篇博客中你可以了解到匿名内部类的使用、匿名内部类要注意的事项、如何初始化匿名内部类、匿名内部类使用的形参为何要为final。
一、使用匿名内部类内部类
匿名内部类由于没有名字,所以它的创建方式有点儿奇怪。创建格式如下:
new 父类构造器(参数列表)|实现接口()
{//匿名内部类的类体部分
}
在这里我们看到使用匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用。当然这个引用是隐式的。
1 public abstract classBird {2 privateString name;3
4 publicString getName() {5 returnname;6 }7
8 public voidsetName(String name) {9 this.name =name;10 }11
12 public abstract intfly();13 }14
15 public classTest {16
17 public voidtest(Bird bird){18 System.out.println(bird.getName() + "能够飞 " + bird.fly() + "米");19 }20
21 public static voidmain(String[] args) {22 Test test = newTest();23 test.test(newBird() {24
25 public intfly() {26 return 10000;27 }28
29 publicString getName() {30 return "大雁";31 }32 });33 }34 }35 ------------------
36 Output:37 大雁能够飞 10000米
在Test类中,test()方法接受一个Bird类型的参数,同时我们知道一个抽象类是没有办法直接new的,我们必须要先有实现类才能new出来它的实现类实例。所以在mian方法中直接使用匿名内部类来创建一个Bird实例。
由于匿名内部类不能是抽象类,所以它必须要实现它的抽象父类或者接口里面所有的抽象方法。
对于这段匿名内部类代码其实是可以拆分为如下形式:
1 public class WildGoose extendsBird{2 public intfly() {3 return 10000;4 }5
6 publicString getName() {7 return "大雁";8 }9 }10
11 WildGoose wildGoose = newWildGoose();12 test.test(wildGoose);
在这里系统会创建一个继承自Bird类的匿名类的对象,该对象转型为对Bird类型的引用。
对于匿名内部类的使用它是存在一个缺陷的,就是它仅能被使用一次,创建匿名内部类时它会立即创建一个该类的实例,该类的定义会立即消失,所以匿名内部类是不能够被重复使用。对于上面的实例,如果我们需要对test()方法里面内部类进行多次使用,建议重新定义类,而不是使用匿名内部类。
二、注意事项
在使用匿名内部类的过程中,我们需要注意如下几点:
1、使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
2、匿名内部类中是不能定义构造函数的。
3、匿名内部类中不能存在任何的静态成员变量和静态方法。
4、匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
5、匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
三、使用的形参为何要为final
我们给匿名内部类传递参数的时候,若该形参在内部类中需要被使用,那么该形参必须要为final。也就是说:当所在的方法的形参需要被内部类里面使用时,该形参必须为final。
为什么必须要为final呢?
首先我们知道在内部类编译成功后,它会产生一个class文件,该class文件与外部类并不是同一class文件,仅仅只保留对外部类的引用。当外部类传入的参数需要被内部类调用时,从java程序的角度来看是直接被调用:
1 public classOuterClass {2 public void display(finalString name,String age){3 classInnerClass{4 voiddisplay(){5 System.out.println(name);6 }7 }8 }9 }
从上面代码中看好像name参数应该是被内部类直接调用?其实不然,在java编译之后实际的操作如下:
1 public classOuterClass$InnerClass {2 publicInnerClass(String name,String age){3 this.InnerClass$name =name;4 this.InnerClass$age =age;5 }6
7
8 public voiddisplay(){9 System.out.println(this.InnerClass$name + "----" + this.InnerClass$age );10 }11 }
所以从上面代码来看,内部类并不是直接调用方法传递的参数,而是利用自身的构造器对传入的参数进行备份,自己内部方法调用的实际上时自己的属性而不是外部方法传递进来的参数。
直到这里还没有解释为什么是final?在内部类中的属性和外部方法的参数两者从外表上看是同一个东西,但实际上却不是,所以他们两者是可以任意变化的,也就是说在内部类中我对属性的改变并不会影响到外部的形参,而然这从程序员的角度来看这是不可行的,毕竟站在程序的角度来看这两个根本就是同一个,如果内部类该变了,而外部方法的形参却没有改变这是难以理解和不可接受的,所以为了保持参数的一致性,就规定使用final来避免形参的不改变。
简单理解就是,拷贝引用,为了避免引用值发生改变,例如被外部类的方法修改等,而导致内部类得到的值不一致,于是用final来让该引用不可改变。
故如果定义了一个匿名内部类,并且希望它使用一个其外部定义的参数,那么编译器会要求该参数引用是final的。
四、匿名内部类初始化
我们一般都是利用构造器来完成某个实例的初始化工作的,但是匿名内部类是没有构造器的!那怎么来初始化匿名内部类呢?使用构造代码块!利用构造代码块能够达到为匿名内部类创建一个构造器的效果。
1 public classOutClass {2 public InnerClass getInnerClass(final int age,finalString name){3 return newInnerClass() {4 intage_ ;5 String name_;6 //构造代码块完成初始化工作
7 {8 if(0 < age && age < 200){9 age_ =age;10 name_ =name;11 }12 }13 publicString getName() {14 returnname_;15 }16
17 public intgetAge() {18 returnage_;19 }20 };21 }22
23 public static voidmain(String[] args) {24 OutClass out = newOutClass();25
26 InnerClass inner_1 = out.getInnerClass(201, "chenssy");27 System.out.println(inner_1.getName());28
29 InnerClass inner_2 = out.getInnerClass(23, "chenssy");30 System.out.println(inner_2.getName());31 }32 }