定义与创建
将一个类定义放在另一个类、方法、作用域、匿名类等地方,就是内部类;内部类只能由外部类对象创建(通过外部方法或者.new方法),内部类对象创建时必须已经有一个外部类对象,并且与之连接(在内部类中会创建一个指向其外部类对象的引用),内部类可以访问到外部类对象的所有成员(包括private);
如:示例代码中,在Parcel类的内部定义了内部类:Contents和Destination,在外部类方法ship中,可以直接创建内部类对象;但是在类外部,只能想创建外部类对象q,然后再通过该对象的contents()和to()方法来创建内部类对象;或者可以通过q2.new来调用内部类的构造方法;注意内部类的引用类型格式是OutClass.InnerClass;
在内部类中,可以使用OutClass.this来调用外部类对象(必然存在该对象,且和内部类对象连接);
当内部类指定是private时,只能在类的内部调用该内部类,外部类对象无法访问并且创建该内部类对象;protected内部类则只有外部类的子类可以访问;通过创建private内部类可以通过类来完成一些工作并且隐藏所有类定义的细节;
在作用域中使用类时,超过作用域该类就无法被访问;如示例代码展示了几种内部类的定义方式;
注意在Parcel7、Parcel8中使用了匿名类,通过基类和接口创建匿名类对象的同时创建类的定义,匿名类的意义为:创建继承自一个基类或者接口的匿名类对象,默认添加继承关系;
使用匿名类时,不要忘记类定义后的分号;
如果如Parcel9中匿名内部类使用外部类中定义的对象,那么编译器会要求其参数引用是final的(如外部类方法destination中),否则将会出错;
如AnonymousConstructor中,可以通过对实例的初始化实现一个匿名构造器功能;如果匿名类中没有直接使用i,则外部方法的参数不需要指定是final的;又如Parcel10中,由于if语句并不能在初始化时被执行,所有匿名类中对实例化的初始化实际效果就相当于是构造器;
匿名内部类只能实现接口和扩展类的其中一个;
使用内部类改进工厂方法
在上一节最后使用到了工厂方法,将多个子类的构造器使用通用接口包装,然后对该接口进行调用即可实现多种子类的构造活动;而由于子类的构造器是不可见的,并且没有必要创建作为工厂的具名类,而只需要一个单一的工厂对象即可,使用匿名内部类改进后的代码为:示例代码;改进后的代码更加具有实际意义(消除了每个子类单独的工厂类);
嵌套类与单元测试
当不需要内部类和外部类的对象之间有联系时,可以将内部类声明为static,这种方式称为嵌套类;
嵌套类由于申明了static,并不像内部类一样,具有一个外部类的对象引用;一旦创建了嵌套类,就说明了嵌套类中的对象并不需要外部类对象的引用,并且不能从嵌套类的对象中访问非静态的外部类对象,没有new方法和this;
正常情况下,接口中是不能放置任何代码的,但是嵌套类可以作为接口的一部分;由于接口中所有的成员都将变为static和public,类也一样,一旦放入接口中,该类就是一个嵌套类;如:示例代码;
通过在每个类中添加main()方法可以完成单元测试的工作,但是这样做会将带上已经编译后的额外代码,通过将main方法放置到嵌套类中,可以优化单元测试,如: 示例代码;在编译之后,每个嵌套类(以及内部类)都会产生一个OutClass$InnerClass.class类似的文件,单元测试的类也一样,只要运行java TestBed\$Tester即可进行测试工作;只要在发布时,简单的将该文件删除就可以删除单元测试的内容;
内部类应用
1. 实现多重继承
通过使用内部类独立继承自一个接口的实现完成多重继承的工作;同时由于外部类不能同时继承自两个基类,只能通过内部类完成多重继承;
如:示例代码中,A、B接口可以通过X、Y两种方式实现多重继承;C、D抽象类则只能通过Z来实现多重继承;
2. 在同一个外围类中,可以使用多个内部类以不同的方式实现同一个接口,这样可以在同一个类中完成一个接口的多种实现而不用另外创建一个新类,通过这种方式可以形成更加清晰的代码结构;如Sequence中实现ForwardSelector和ReverseSelector两种Selector接口实现:
interface Selector{
? ? char getCharAt(int i);
}
?
class Sequence {
? ? private String s;
? ? ? ? Sequence(String s){
? ? ? ? this.s = s;
? ? }
? ? private Selector fs(){
? ? ? ? return new Selector(){
? ? ? ? ? ? ?public char getCharAt(int i){
? ? ? ? ? ? ? ? ?return s.charAt(i);
? ? ? ? ? ? ?}
? ? ? ? };
? ? }
? ? private Selector rs(){
? ? ? ? return new Selector(){
? ? ? ? ? ? ?public char getCharAt(int i){
? ? ? ? ? ? ? ? ?int index = s.length() - i;
? ? ? ? ? ? ? ? ?return s.charAt(index);
? ? ? ? ? ? ?}
? ? ? ? };
? ? }
? ? public char forwardSelector(int i){return fs().getCharAt(i);}
? ? public char reverseSelector(int i){return rs().getCharAt(i);}
}
3. 实现回调
当一个外部类继承自一个接口的实现时,可以通过内部类完成接口的另一个实现;并在适当的适合进行选择;如:示例代码?中,Callee2继承自MyIncrement,该类是接口Incrementable的一个实现,Callee2在某些时候需要使用继承来的方法,但是同时如果又有对该接口的另外实现,则可以通过新建内部类:Closure来完成,在Closure中返回一个Callee2的钩子,并且只能调用increment方法;并且通过设置内部类为private以及getCallbackReference()方法来隐藏实现过程;
4. 控制框架的编写;
控制框架往往由单个类来创建,用内部类来表示解决问题所必须的各种不同的action(),从而使得实现的细节被封装起来;
注意要点
1. 内部类的继承
由于内部类必须要和一个外部类的对象连接才可以被实例化,所以在继承一个内部类时,在构造方法中应该引用一个其外部类的对象,并且必须使用:
outclassReference.super()
的方式进行引用;如:
class WithInner{
? ? class Inner{}
}
?
public class InheritInner extends WithInner.Inner {
? ? InheritInner(WithInner wi){
? ? ? ? wi.super();
? ? }
}
2. 内部类的覆盖
内部类和外部类是完全独立的个体,各自在自己的命名空间内,如:示例代码中,在new BigEgg()时,先调用基类构造方法,打印New Egg()以及调用基类的内部类Yolk构造方法;此时,BigEgg()中的Yolk类没有起到作用,但是,如果添加BigEgg的构造方法,并且在其中创建Yolk内部类,则是可以完成覆盖的(其实是生成了一个同名方法);真正完成内部类的覆盖,则需要使内部类也继承基类中的内部类,并完成改写操作,如下面的示例;
在示例代码中还可以看到内部类的初始化过程:
基类的内部类 -> 基类 -> 基类内部类 -> 子类内部类 -> 子类;
3. 局部内部类
当一个内部类在作用域内创建时,则会局部内部类,局部内部类不能访问说明符,不是外部类的一部分,而是作用域的一部分,所以可以访问作用域内的常量以及外部类的成员;
局部作用域常在方法体内定义,并且被用来重载构造器,或者生成多个内部类对象等;