1、内部类链接到外部类
普通内部类可以访问外部类的一切,包括私有成员与方法。示例:
public class Tree {
class MyRoot implements Interface.Root {
@Override
public void show() {
System.out.println("Root of " + NAME);
}
}
private String NAME = "Tree";
private MyRoot r = new MyRoot();
void show() {
r.show();
}
public static void main(String[] args) {
Tree t = new Tree();
t.show();
// 这样子不可以
// Interface.Root r = new Tree.MyRoot();
// 这样子可以
Interface.Root x = t.new MyRoot();
x.show();
}
}
可以看到,普通内部类MyRoot访问了外部类Tree的私有成员”NAME”,天然就可以,不需要额外的什么语法。
Interface.Root r = new Tree.MyRoot()这句话是不可以的,因为MyRoot中的show方法访问了外部类的私有成员NAME,它的存在依赖于外部类的存在,因此不允许单独创建普通内部类。普通内部类的创建要依赖于外部类:Interface.Root x = t.new MyRoot();
这样,当调用x.show()时,show()方法就知道要访问”t”这个实例中的NAME属性,而不是其它。
2、用内部类实现集合类迭代器
普通内部类作为可以独立使用的类,拥有访问外部类一切成员与方法的特性,类似与C++中的友元类,在某些情景下,有不可代替的作用。如实现集合类的迭代器,示例如下:
package com.zhangxf.test;
interface Selector {
boolean end();
Object current();
void next();
}
public class Sequence {
private Object[] o;
private int next = 0;
public Sequence(int size) {
o = new Object[size];
}
public void add(Object x) {
if (next < o.length) {
o[next] = x;
next++;
}
}
private class SSelector implements Selector {
int i = 0;
public boolean end () {
return i == o.length;
}
public Object current () {
return o[i];
}
public void next () {
if (i < o.length) i ++;
}
}
public Selector getSelector() {
return new SSelector();
}
public static void main(String[] args) {
Sequence seq = new Sequence(10);
for (int i = 0; i < 10; i ++) {
seq.add(i);
}
Selector sel = seq.getSelector();
while (!sel.end()) {
System.out.println((int)sel.current());
sel.next();
}
}
}
上例中,Sequence类是一个集合类,它只有一个构建函数与一个add()方法,很简单。同时通过私有普通内部类,实现了Selector接口,Selector接口是一个迭代器。Sequence类另外提供了一个返回内部类句柄的getSelector()方法。
可以看到,任何集合类都可以实现Selector接口。
我们可以通过Selector接口,以统一的方式访问任何实现了Selector接口的集合类,只所以能这样,是因为使用了普通内部类实现Selector接口,而普通内部类对外部类拥有所有的访问权限。
同时,每个返回的Selector实例都有自己的状态,因为普通内部类也可以有自己的成员
当然,内部类的这个特性不只用来实现迭代器,这里只是展示他的特性。
3、静态内部类
静态内部类在形式上与普通内部类的区别是在定义时在前边加上static限定符。静态内部类有很多限制,因为它是静态的,并不属于某个外部态实例,而是属于整个外部类,因此静态内部类不可访问外部类的非静态成员也方法。另外,静态内部类不可以有自己的静态成员与静态内部类,但可以有静态方法。
静态类有特殊的用处。在写某个类时,一般会同时在类中加入main()方法用于执行测试逻辑,这样结果就是测试代码与将来要发布的代码混合在一起,显然开发者并不希望将自己的测试代码发布出去。另外测试代码分布在各个文件中,非常分散不易于管理。使用静态内部类,可以将所有测试代码集中到一个文件中,实现测试代码与发布代码的分离,这样发布的时候就可以过滤掉测试代码,示例如下。
不使用静态内部类时:
//////////////////////////////////文件A.java///////////////////////////////////
public class A {
void show( ) {
System.out.println("I am class A");
}
public static void main(String[] args) {
A a = new A();
a.show();
}
}
///////////////////////////////////文件B.java//////////////////////////////////
public class B {
void show() {
System.out.println("I am class B");
}
public static void main(String[] args) {
B b = new B();
b.show();
}
}
测试代码分布在A.java与B.java中,并且会随代码一起发布。
使用静态内部类后:
//////////////////////////////////文件A.java///////////////////////////////////
public class A {
void show( ) {
System.out.println("I am class A");
}
}
///////////////////////////////////文件B.java//////////////////////////////////
public class B {
void show() {
System.out.println("I am class B");
}
}
///////////////////////////////////文件Tester.java////////////////////////////
public class Tester {
public static class TestA {
public static void main(String[] args) {
A a = new A();
a.show();
}
}
public static class TestB {
public static void main(String[] args) {
B b = new B();
b.show();
}
}
}
测试代码集中在Tester.java中,可以控制它不发布。内部静态类TestA会编译成Tester$TestA.class,TestB也一样的结果。像JUNIT之类的测试框架可能用到了这种机制。
利用静态内部类实现单例模式:
public class Singleton {
private Singleton() {}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
4、内部类覆盖
假如有一个类A,它定义了内部类X,我们从A派生出一个新类B,并在B内重新定义X,这样B内的X就会覆盖A内X的实现,从而生新一个新类,其它一切不变。这个想法看起来有用,实际上行不通。测试代码如下:
class Egg {
protected class Yolk {
public Yolk() {
System.out.println("Egg.Yolk()");
}
}
private Yolk y;
public Egg() {
System.out.println("New Egg()");
y = new Yolk();
}
}
public class BigEgg extends Egg {
public class Yolk {
public Yolk() {
System.out.println("BigEgg.Yolk()");
}
}
public static void main(String[] args) {
BigEgg x = new BigEgg();
BigEgg.Yolk y = x.new Yolk();
Egg.Yolk z = ((Egg)x).new Yolk();
}
}
输出如下:
New Egg()
Egg.Yolk() // 仍然是Egg中的Yolk而非派生类的BigEgg.Yolk。
BigEgg.Yolk() // 这个是main中的第二行代码输出的内容,依托第一行创建的实例,生成了BigEgg中Yolk的实例
Egg.Yolk() // 第三行代码的输出,依托于第一行创建的实例,但必需先向上转换成Egg,生成Egg中Yolk的实例
可以看到Egg中Yolk的实例定义方式是Egg.Yolk,而BigEgg中的Yolk定义方式是BigEgg.Yolk,后者并没有覆盖前者。
5、内部类继承
假如有一个类A,它定义了内部类X,我们从A派生出一个新类B,并在B内重新定义X,但是B中的X继承A中的X,这个可以。但要注意刚才说的覆盖问题,A中的X与B中的X是两个不同的东西,后者并不会覆盖前者:
package com.zhangxf.test;
class Egg {
protected class Yolk {
public Yolk() {
System.out.println("Egg.Yolk()");
}
public void show () {
System.out.println("Egg.show()");
}
}
private Yolk y;
public Egg() {
System.out.println("New Egg()");
y = new Yolk();
}
public void show () {
y.show();
}
}
public class BigEgg extends Egg {
public class Yolk extends Egg.Yolk {
public Yolk() {
System.out.println("BigEgg.Yolk()");
}
public void show () {
System.out.println("BigEgg.show()");
}
}
public static void main(String[] args) {
Egg x = new BigEgg();
x.show();
}
}
BigEgg的内部类Yolk继承了Egg的内部类,但是后者的内部类仍然不会覆盖前者的内部类。main代码中,将BigEgg向上转型成Egg,然后调用其方法show(),显示内容仍然是Egg中的Yolk实现而不是BigEgg中的Yolk。输出内容如下:
New Egg()
Egg.Yolk()
Egg.show()