@lemonguge
2015-06-23T02:28:48.000000Z
字数 8185
阅读 355
JAVA
可以将一个类的定义放在另一个类的定义内部,这就是内部类。
具体地指明这个内部类对象的类型:OuterClassName.InnerClassName。当在外围类中可以直接使用InnerClassName作为引用类型,外围类名可以省略;在其他类中则必须使用OuterClassName.InnerClassName(组合类名)作为引用类型。
内部类同样会生成.class文件,它的文件名会是这样的形式:Outer$Inner.class。如果是匿名内部类,在$后会简单加入一个数字作为标识符。
对于内部类,我们首先会想到的一个问题是,什么原因而导致了内部类的产生。
在现实生活中分析事物("Outer")时,发现该事物描述中还有事物("Inner"),而且这个事物("Outer")还在访问被描述事物("Inner")的内容。
举个简单的例子,冰箱可以制冷,每一台冰箱里都有制冷设备,这个制冷设备的作用是制冷,冰箱之所以能制冷,都是因为这个制冷设备在工作。
// 冰箱public class Fridge {// 制冷设备class ColdMachine {public void work() {System.out.println("start to cold..");}}public ColdMachine get() {return new ColdMachine();}// 冰箱在使用制冷设备的制冷功能public void start() {get().work();}public static void main(String[] args) {new Fridge().start();}} /* Output:start to cold..*///:~
通过上面这个小程序,相信大家不会再对内部类那么陌生了。
我们接着会想到这么一个问题,为什么需要内部类?
一般说来,内部类继承自某个类或实现某个接口,这将会引出另一个问题,为什么不通过外部类来实现那个接口呢?答案是:“如果外部类可以这样做,那么就应该这样做。要是外部类无法这样做,这时就可以使用内部类来实现我们的需求。”下面将对于这种情况给出两个小程序,让大家更清楚的了解。
// 电脑上的接口interface UsbPc{ public void show(); }// 电视上的接口interface UsbTv{ public String show(); }// U盘class Udisk implements UsbPc{public void show(){System.out.println("PC show..");}}// 由于新的产品,无法同时实现电脑和电视的接口,因为不兼容。这时我们可以通过内部类来解决这种问题,下面我们的新产品即将登场。class Xdisk extends Udisk { // 这款X盘是U盘的进化版,因此属于同一个继承体系class ImplTv implements UsbTv {@Overridepublic String show() {return "TV show..";}}// 开启电视接口模式UsbTv startTvModel() {return new ImplTv();}}// 对这款产品进行测试public class Test{// 插在电脑上运行public static void run(UsbPc a){a.show();}// 插在电视上运行public static void run(UsbTv b){System.out.println(b.show());}public static void main(String[] args){Xdisk e = new Xdisk();run(e);run(e.startTvModel()); // 开启电视接口模式}} /* Output:PC show..TV show..*///:~
// 小汽车class Car{public void run(){System.out.println("run..");}}// 直升机class Helicopter{public void fly(){System.out.println("fly..");};}// 由于java只能单继承,所以无法同时继承于小汽车和直升机,但是我们还都想使用这两个类的功能,虽然可以用组合has-a来实现,但是在思想上不合理,不应该为了功能而抛弃思想!// 超级汽车XCar,现在小汽车的进化版class XCar extends Car{class XFly extends Helicopter{ }// 开启飞行模式,变身直升机public Helicopter startFlyModel(){return new XFly();}}// 对这款产品进行测试public class Test {// 对小汽车测试跑的功能public static void testRun(Car c){c.run();}// 对直升机测试飞行功能public static void testFly(Helicopter f){f.fly();}public static void main(String[] args) {XCar xCar = new XCar();testRun(xCar);testFly(xCar.startFlyModel()); // 开启飞行模式}} /* Output:run..fly..*///:~
内部类看起来就像是一种代码隐藏机制,将类置于其他类的内部,但是在后面会了解到,内部类远不止如此。内部类是一种非常有用的特性,它了解外围类,并能与之通信。
内部类与组合是完全不同的概念,这一点很重要。
has-a的关系。在这里,笔者将除了嵌套类(“被static修饰的内部类”)以外的内部类称为普通内部类。大家也许会想,不应该是介绍内部类吗,标题为什么叫做联系。因为普通内部类对象和其外围类对象之间是有联系的,而嵌套类对象与其外围类对象是没有联系的。
这种联系在代码中的体现也是非常严谨的,在拥有外部类对象之前是不可能创建普通内部类对象的,因为普通内部类对象在创建时,必定会秘密捕获一个创建它的外围类对象引用。
由于有了联系,所以普通内部类对象能访问其外围类对象的所有成员,且不需要任何特殊条件。此外,普通内部类还拥有其外围类所有元素的访问权。
普通内部类自动拥有其外围类所有成员的访问权,就像自己拥有那些外围类成员似的,这是如何做的呢?当一个外围类对象创建了一个内部类对象时,在此内部类对象中含有一个引用指向那个外围类对象,当访问外围类的成员时,就是那个引用来选择外围类的成员。
如果需要在内部类中获取外部类对象的引用,使用OuterClassName.this,其中this为当前内部类对象。这是一种固定的语法,没有什么太多好讲的。不过有一点需要记住,这样产生的引用自动具有正确的类型,在编译期就被知晓并受到检查,即没有任何运行时开销。
不过获取内部类对象有两种方式:.new和new,下面会进行详细的讲解。
public class Outer {class Inner { } // 普通内部类public Inner get(){Inner in = new Inner();// 等价于Outer.Inner in = this.new Inner(); // this为外围类对象的引用,产生联系// ! Outer.Inner in = this.new Outer.Inner(); // 不能通过编译// Inner in = new Outer.Inner(); // 却是可行的return in;}public static void show(){Outer out = new Outer();Inner in = out.new Inner(); // 通过外围类对象创建内部类对象,产生联系// 上面两句等价于Outer.Inner in = new Outer().new Inner();// ! Inner in = out.new Outer.Inner(); // 不能通过编译}}
相信大家会对那两句不能通过编译的代码感到好奇,为什么不可以这样做呢?下面是编译器的报错信息。
"Cannot allocate the member type Outer.Inner using its compound name when qualified by an enclosing instance. The member type name is resolved relatively to the qualifying instance type"
当我们通过.new的方式来创建一个普通内部类对象时,因为显式地指定了外围类对象"an enclosing instance",所以编译器会帮我们自动加入当前外围类对象的类名,即为Outer.Inner。可是当我们显式地声明为Outer.Inner时,会因为编译器找不到Outer.Outer.Inner这样的类而报错。这是编译器帮我们处理的细节,无需我们来操心。
组合类名
OuterClassName.InnerClassName不能在.new这种方式中存在。
如果是通过new关键字来创建普通内部类对象时,new Inner()等价于new Outer.Inner()。
new这种方式一定是在外围类的非静态方法中,此时组合类名或者内部类名都可以用来创建内部类。
可以把一个普通成员内部类当作一个成员变量来看待,具有与成员变量类似的性质,不能被覆盖!各自在自己的命名空间。
class Egg {private Yolk y;protected class Yolk {public Yolk() {print("Egg.Yolk()");}}public Egg() {print("New Egg()");y = new Yolk(); // 创建了父类的内部类对象}}public class BigEgg extends Egg {public class Yolk {public Yolk() {print("BigEgg.Yolk()");}}public static void main(String[] args) {new BigEgg();}} /* Output:New Egg()Egg.Yolk()*///:~
当我们创建一个普通内部类(只能为成员内部类,包括嵌套类)对象时,内部类的构造器必须与创建它的外围类对象引用联系起来。假设我们创建了一个普通内部类的子类,当创建这个子类对象时,父类(即普通内部类)的构造器进栈时,也会需要与一个外围类对象关联,这个时候该怎么做呢?
回想一下我们之前在外围类中创建普通内部类对象的情景,如果使用new的方式,那这个内部类对象会和this(调用方法的那个外围类对象)隐式的关联;如果使用.new的方式,那这个内部类对象会和我们指定的外围类对象显式的关联。
所以我们在普通内部类的子类中,如果调用父类的构造器时,必须显示地指明一个外围类对象来进行关联。
enclosingClassReference.super(..);
class WithInner {class Inner {public Inner() {System.out.println("no cons..");}public Inner(int i) {System.out.println("int cons.." + i);}}}public class InheritInner extends WithInner.Inner {// ! InheritInner() {} // Won't compileInheritInner(WithInner wi) {wi.super();}InheritInner() {new WithInner().super(1);}public static void main(String[] args) {WithInner wi = new WithInner();InheritInner ii = new InheritInner(wi);new InheritInner();}} /*no cons..int cons..1*///:~
class Egg2 {protected class Yolk {public Yolk() {print("Egg2.Yolk()");}public void f() {print("Egg2.Yolk.f()");}}private Yolk y = new Yolk();public Egg2() {print("New Egg2()");}public void insertYolk(Yolk yy) {y = yy;}public void g() {y.f();}}public class BigEgg2 extends Egg2 {public class Yolk extends Egg2.Yolk {public Yolk() {print("BigEgg2.Yolk()");}public void f() {print("BigEgg2.Yolk.f()");}}public BigEgg2() {insertYolk(new Yolk());}public static void main(String[] args) {Egg2 e2 = new BigEgg2();e2.g();}} /* Output:Egg2.Yolk() //父类中的成员y显式初始化New Egg2() //父类构造器初始化Egg2.Yolk() //子类构造器中,创建了一个子类内部类对象,Egg2.Yolk先进行初始化BigEgg2.Yolk() //子类内部类构造器初始化BigEgg2.Yolk.f() //方法调用机制,后期绑定*///:~
我们可以在任意的作用域内嵌入一个普通内部类,可以帮它当作一个局部变量看待,所以不能被static所修饰。
局部内部类除了可以访问此外围类的所有成员,还可以访问当前代码块内被
final修饰的局部变量。
为什么只能访问局部中被final修饰的局部变量?为了更好的解释这个问题,我们从一段小代码来讲解。
interface Inn {public abstract void innerMethod();}class Outer {public Inn outerMethod() {final int x = 9; // 将局部变量x定义为finalclass Inner implements Inn {public void innerMethod() {System.out.println("inner ..." + x);}}return new Inner();}}
假设上面这段代码中的局部变量x没有被final修饰,当我们使用外部类Outer中的outerMethod()方法来获取到一个实现Inn接口的对象。outerMethod()方法使用到了局部变量x,在方法出栈之后,变量x便被回收。如果我们使用outerMethod()方法获取到的对象中的innerMethod()方法时,会由于没有了变量x,而导致报错。之所以将x定义为final时,因为被final修饰变量的值不能被覆盖,即最终值,JVM将会固定值9代替该局部变量x。
在innerMethod()方法中,便为"System.out.println("inner ..." + 9);"。
匿名内部类就是局部内部类的简写格式,必须继承或者实现一个外部类或者接口。
匿名内部类具体表现为一个匿名子类对象。
匿名内部类不可能有构造器,因为它根本就没有名字!
如果基类需要一个有参数的构造器,在创建匿名子类对象时传入合适的参数给基类的构造器即可,这些参数可以不要求定义为final,因为这些参数被传递给匿名类的基类的构造器,并不在匿名类内部被直接使用,所以也就不用担心这些参数会因为生命周期过短而被回收导致内部类中没有这些变量产生报错。
abstract class Base {private int i;public Base(int i) {System.out.println("Base constructor, i = " + i);this.i = i;}public int getI() {return i;}public abstract void f();}public class Anony {public static Base getBase(int i) { // 不要求为finalreturn new Base(i) {{System.out.println("Inside instance initializer");}public void f() {System.out.println("In anonymous f().." + getI());}};}public static void main(String[] args) {Base base = getBase(47);base.f();}} /* Output:Base constructor, i = 47Inside instance initializerIn anonymous f()..47*///:~
内部类如果定义在外围类中成员的位置,就可以把这个内部类当作一个成员来看待。虽然看起来很陌生而且很奇怪,但这是可行的,此时内部类可以被成员的访问权限所修饰(private、包访问权限、protected、public),还可以被static、final关键字修饰。其中被static修饰的内部类,我们把这样的类叫做嵌套类。
static成员,也不能含有嵌套类。对嵌套类的理解,可以把它当成一个普通类来看待,把组合类名当作其类名。
public class Outer {static class Inner { } // 嵌套类public Inner get(){// 因为在外围类中,所以外围类名可以省略Inner in = new Inner(); // 等价于Outer.Inner in = new Outer.Inner();return in;}}
当调用嵌套类的静态成员时,可以直接使用“外围类名.内部类名.静态成员”。
class Outer{int num = 3;class Inner{int num = 4;void show(){int num = 5;// 3 Outer2.this指向了外部类对象,访问的是外部类中的成员变量3// 4 this指向了内部类对象,访问的是内部类中的成员变量4// 5 由于栈中有num,便不会去堆中的对象中查询,直接使用栈中的5System.out.println(Outer2.this.num+" "+this.num+" "+num);//3 4 5}}void method(){new Inner().show(); // new Outer.Inner().show();}}class Test{public static void main(String[] args){new Outer().method();}} /* Output:3 4 5*///:~
如果一个类继承于嵌套类,不像普通内部类那样,在子类的构造器中需要指明一个外围类对象的引用,而是当成一个普通类即可。
class WithInner {static class Inner {public Inner() {System.out.println("no cons..");}public Inner(int i) {System.out.println("int cons.." + i);}}}public class InheritInner extends WithInner.Inner {InheritInner() {super(1);}public static void main(String[] args) {new InheritInner();}} /*int cons..1*///:~