在此之前,我们需要了解,内部类与组合是完全不同的概念,虽然它看起来像是一种代码隐藏机制,但其实内部类是了解外围类,并且能与之通信。下面,我们深刻探讨内部类的用法与底层原理。
内部类的定义:将一个类的定义放在另一个类的定义内部,我们称之为内部类。
内部类可以分为四种:
-
成员内部类:和成员变量一个级别
-
局部内部类:在方法里的内部类(方法域内生效 或者 方法内某一段代码块域内生效)
-
匿名内部类:基本上属于接口的实现类,一次性使用的场景。
-
静态内部类:static 修饰的成员内部类。
举例子 1:创建一个内部类
//外围类public class CreateInnerClass { //内部类1 class Contents{ private int i = 11; public int value() { return i; } }
//内部类2 class Destination { private String label; public Destination(String whereTo) { label = whereTo; } String readLabel() { return label; } }
//外围类方法 public void setDest(String dest) { // 外围类的方法内部,完成内部类的构造 Contents c = new Contents(); Destination d = new Destination(dest); System.out.println(d.readLabel()); }
//外围类方法2 public Destination getAnotherDestination(){ return new Destination("anotherDestination"); } //外围类方法3 public Contents getAnotherContents(){ return new Contents(); }
public static void main(String[] args) { //新建外围类 CreateInnerClass innerClass = new CreateInnerClass(); innerClass.setDest("Tasmania"); //外部类可以拥有返回指向内部类引用的方法。 Destination anotherDestination = innerClass.getAnotherDestination(); Contents anotherContents = innerClass.getAnotherContents(); }}
复制代码
代码分析:
如果从外部类的非静态方法之外的任意位置,创建某个内部类的对象,那么需要通过外围类引用,具体的指明这个对象的类型:OuterClassName.InnerClassName。
内部类拥有其外围类的所有元素的访问权。
当生成一个内部类的对象时,此对象与制造它的外围对象(enclosing object)之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。
举例子 2:内部类访问外围类的私有成员变量
首先,定义一个内部类行为的规范接口 Selector:
public interface Selector { boolean end(); Object current(); void next();}
复制代码
然后,在外围类里定义这个内部类:
//外围类public class Sequence { //数组,私有对象 private Object[] items; private int next = 0; //构造器 public Sequence(int size) { items = new Object[size]; } public void add(Object x) { if (next < items.length) { items[next++] = x; } } //外围类构造内部类的方法 public Selector selector() { return new SequenceSelector(); }
//内部类,私有描述符 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++; } } }}
复制代码
最后,我们运行测试用例:
public static void main(String[] args) { Sequence sequence = new Sequence(10); for (int i = 0; i < 10; i++) { sequence.add(Integer.toString(i)); } //获取内部类,通过内部类来访问外围类的私有成员属性 Selector selector = sequence.selector(); while (!selector.end()) { System.out.print(selector.current() + " "); selector.next(); }}
复制代码
内部类自动拥有对其外围类所有成员的访问权。
这是如何做到的呢?当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。 然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。这里面是编译器帮忙处理了所有的细节,因此得到结论:内部类的对象只能在与其外围类的对象相关联的情况下才能被创建(前提是:该内部类是非 static,静态内部类是其他情况,且看下文)。
.this:内部类链接到外围类引用的手段
举例子 3:.this 的用法
/** * <p> * 若要生成对外部对象的引用,使用类名+“.this”的方式。 * </p> */public class DotThis { //外围类方法1 void f() { System.out.println("DotThis.f()"); } //外围类方法2:创建内部类 public Inner inner() { return new Inner(); } //内部类 public class Inner { // 返回外部类的引用 public DotThis outerClass() { //类名.this返回外围类引用 return DotThis.this; } } public static void main(String[] args) { DotThis dotThis = new DotThis(); //创建了一个内部类 DotThis.Inner dti = dotThis.inner(); }}
复制代码
.new:使用外围类引用创建内部类的手段
举例子 4:.new 的用法
/** * <p> * 希望其他对象创建其某个内部类的对象,使用.new获得对外部类对象的引用。 * </p> */public class DotNew { //外围类仅仅提供了一个内部类,但是没有开放任何方法给客户端来调用,以达到从内部类的目的。 public class Inner{ } public Inner getInner(){ return new Inner(); } public static void main(String[] args) { //这种情况,因为外围类没有提供获取内部类Inner的方法,但我们又需要创建这个内部类,那就使用.new DotNew dn = new DotNew(); //通过.new关键字创建一个内部类 DotNew.Inner dni = dn.new Inner(); //普通方法创建一个内部类 DotNew.Inner dni2 = dn.getInner(); }}
复制代码
内部类的向上转型
这个更加容易理解,内部类可以继承抽象类,也可以实现接口。可以参考:《通过接口引用对象》与《设计模式的六大原则》。
举例子 5:内部类的向上转型
内部类需要实现的接口
public interface Conents { int value();}public interface Destination { String readLabel();}
复制代码
内部类的封装
public class TestInterfaceInnerClass { private class PContents implements Conents { private int i = 11; @Override public int value() { return i; } } protected class PDestination implements Destination { private String label; private PDestination(String whereTo) { label = whereTo; } @Override public String readLabel() { return label; } } public Destination destination(String s) { return new PDestination(s); } public Conents contents() { return new PContents(); }}
复制代码
测试 demo
public class TestDemo extends TestInterfaceInnerClass{ public static void main(String[] args) { TestInterfaceInnerClass p = new TestInterfaceInnerClass(); Conents contents = p.contents(); Destination tasmania = p.destination("Tasmania"); }}
复制代码
代码分析:
内部类的用武之地:将内部类向上转型为基类时,尤其是转型为一个接口的时候,所得到的直接指向基类或接口的引用,能够很方便地隐藏实现细节;且这些接口的实现对于调用方,能够完全不可见的。
内部类还可以继续嵌套内部类。
举例子 6:多重嵌套的内部类
/** * <p> * 内部类嵌套 * </p> */class MNA{ private void f() {} class A { private void g() {} public class B { void h() { g(); f(); } } }}public class MultiNestingAccess { public static void main(String[] args) { MNA mna = new MNA(); //通过外围类引用才能继续创建内部类A类 MNA.A mnaa = mna.new A(); //通过A类引用才能继续创建A类的内部类 MNA.A.B mnaab = mnaa.new B(); mnaab.h(); }}
复制代码
代码分析:
对于多重嵌套的非静态内部类,我们需要层次分明的构建每一次嵌套的对象,然后通过引用来创建对象。
本节是内部类学习的上部分,我们复习了:内部类的基础用法,外围类 &内部类的引用关系,以及多重嵌套内部类的定义使用。下部分我们详细讲解四种内部类的定义与使用,以及继承语法对内部类的影响。
《源码系列》
《经典书籍》
《Java并发编程实战:第2章 影响线程安全性的原子性和加锁机制》
《Java并发编程实战:第3章 助于线程安全的三剑客:final & volatile & 线程封闭》
《服务端技术栈》
《算法系列》
《设计模式》