内部类

64 阅读6分钟

内部类与静态内部类

内部类的按照声明方式分为两种:普通内部类和静态内部类,其中普通内部类包括具名内部类和匿名内部类。它们有以下区别:

  1. 普通内部类的对象与外部类对象紧密相联,依赖于外部类对象。而静态内部类使用 static 声明,它表示你不需要内部类对象与其外部类对象之间存在联系。如果内部类不需要直接访问外部类成员,优先使用静态内部类。
  2. 在普通内部类中可以直接访问外部类的所有成员,并且可以使用 外部类.this 去获得外部类对象的引用。对于静态内部类不能直接访问外部类的非静态成员,也不可以通过 外部类.this 去获得外部类对象。
  3. 普通内部类必须通过外部类的实例对象进行构造,而静态内部类不需要。对于普通内部类,当通过外部类对象创建内部类对象时,此时内部类对象会隐式捕获一个指向外部类对象的引用,因此,内部类自动拥有对外部类对象所有成员的访问权。
  4. 普通内部类与外部类对象相关联,不能声明任何 static 作用域的内容(字段、方法、内部类等),所有 static 相关的内容必须定义在外部类中,静态内部类可以声 static 相关的内容。
class OuterClass {

	private String id;

	/** 普通内部类 */
	class InnerClass {
		/** 可以直接访问外部类成员,其实是省略了 `外部类.this` */
		private String innerId = "inner_" + id;

		/** 通过 `外部类.this` 持有外部类对象 */ 
		OuterClass outer() {
			return OuterClass.this;
		}
	}
	
	/** 静态内部类 */
	static class StaticInnerClass {
		/** 可以声明 static 相关的内容 */
		static {
			System.out.println("load StaticInnerClass");
		}
	}
}

// 非静态内部类必须通过外部类实例对象进行构造
OuterClass.InnerClass inner = new OuterClass().new InnerClass();
// 静态内部类无需通过外部类实例对象进行构造
OuterClass.StaticInnerClass staticInner = new OuterClass.StaticInnerClass();

接口中的内部类

接口中也允许有内部类,接口中的内部类都是 public 和 static 的。如果你想要创建某些公共代码,使得它们被某个接口的所有不同实现所共有,那么使用接口内部类是个不错的选择。

public interface Foo {
	void show();
	// 内部类也可以实现外部接口 
	class FooA implements Foo {
		@Override
		public void show() {
			System.out.println("FooA...");
		}
	}
}

内部类的加载时机

内部类的加载与外部类没有关系,内部类的加载是在它们第一次被访问时才会被加载(外部类也一样),但是在加载内部类时,如果发现对应的外部类还没有加载,那么会先加载外部类,然后再加载内部类并进行初始化工作,类的初始化顺序为:静态变量 > 静态代码块 > 成员变量默认初始化 > 成员代码块 > 构造函数。

对于 Class 访问导致的类加载:通过 类名.class 访问类的 Class 对象并不会导致类的加载,而使用 Class.forName(className) 访问类的 Class 对象会导致类的加载。因此,除非你明确希望加载类,否则你都应该使用 类名.class 获取类的 Class 对象。

class Foo {
	static {
		System.out.println("Load Foo...");
	}
}
// 当执行这句代码时并加载 Foo,不会有任何输出信息
Class<Foo> fooClass = Foo.class;

// 当执行这句代码时将加载 Foo,你将看到输出信息
Class.forName("cn.xbhel.lang.Foo");

为什么需要内部类

Java 语言设计者为什么要增加这项语言特性呢?一般来说,使用内部类时,内部类通常继承自某个类或实现某个接口,内部类的代码操作它的外部类的对象,所以可以认为内部类提供了某种进入外部类的窗口。

如果只是为了继承某个类或实现某个接口,为什么不使用外部类呢?答案是:“如果这样能够满足需求,那么就应该这样做“。那么使用内部类实现与使用外部类有什么区别呢?这就要提到 Java 的单继承机制了,它限制了每个类只能继承一个类,而通过内部类,能够提供继承多个具体或抽象类的能力。

每个内部类都能独立地继承某个类或某个接口的实现,而无论外部类是否已经继承某个类,在内部类中,你能直接访问外部类的所有信息,并且能够拥有自己独立的状态信息,通过这一机制能够提供多重继承的能力。

假设你必须在一个类中实现两个接口,由于接口的灵活性,你有两种选择:使用单一类或使用内部类:

interface A {}
interface B {}

class X implements A, B {}
class Y implements A {
	B makeB() {
		// 匿名内部类; 可以直接访问 Y 的所有信息
		return new B() {};
	}
}

// 拥有 A 的能力
A x = new X();
B y = new Y();

// 拥有 B 的能力
B x = new X();
B y = new Y().makeB();

这两种方式看起来好像没有啥区别,它们都能正常工作,但如果不是实现接口而是继承具体的类或抽象类呢?此时就只有使用内部类才能实现多继承:

class A {}
abstract class B {}

class Y extends A {
	B makeB() {
		return new B() {};
	}
}

// 同时拥有 A、B 的能力
A x = new Y();
B y = new Y().makeB();

通过内部类除了解决多重继承的问题,还有一些其他的优势,如内部类并没有令人困惑的 is-a 关系,它就是一个独立的实体。

public interface Selector {
	boolean end();
	Object current();
	void next();
}

public class Sequence implements List<Object> {

	private Object[] items;
	private int next = 0;

	public Sequence(int size) {
		this.items = new Object[size];
	}

	@Override
	public void add(Object o) {
		if(next < items.length) {
			items[next++] = o;
		}
	}

	private Selector selector() {
		return new SequenceSelector();
	}

	/** 每个方法都用到了 items 属性,这是外部类的属性,但是在内部类中可以直接访问, */
	private class SequenceSelector implements Selector {
		private int i = 0;  
		@Override  
		public boolean end() { return i == items.length; }  
			
		@Override  
		public Object current() { return items[i]; }  
			
		@Override  
		public void next() { if(i < items.length) i++; }
	}  
}

如果 Sequence 不使用内部类,就必须声明 Sequence 是一个 Selector, 对于某个特定的 Sequence 只能有一个 Selector,然而使用内部类很容易就能拥有另一个方法 reverseSelector(),用它来生成一个反方向遍历序列的 Selector,只有内部类才有这种灵活性。

并且在 SequenceSelector 的每个方法都使用了 items 属性,但这并不是它的一部分,而是外部类的属性,然而在内部类中可以直接访问其外部类的方法和字段,就像自己拥有它们一样。

静态内部类单例模式

静态内部类被用来实现单例模式,利用类加载的特性以及静态内部类的初始化来保证线程安全和延迟加载。当 Singleton 类被加载时,静态内部类 SingletonHolder 不会被加载,只有当调用 getInstance() 获取单例对象时 SingletonHolder 才会被加载并初始化其静态变量 INSTANCE,从而实现了延迟加载,同时,JVM 保证了类的加载是线程安全的,因此这种方式也保证了线程安全性。

public final class Singleton {

	private Singleton() {}

	private static final class SingletonHolder {
		/** 在静态内部类中声明并初始化单例实例 */
		private static final Singleton INSTANCE = new Singleton();
	}

	/** 获取单例实例 */
	public static Singleton getInstance(){  
		return SingletonHolder.INSTANCE;  
	}
}

参考:Bruce Eckel《ON JAVA 8》