1、写在开头
在此之前,我们需要了解,内部类与组合是完全不同的概念,虽然它看起来像是一种代码隐藏机制,但其实内部类是了解外围类,并且能与之通信。下面,我们深刻探讨内部类的用法与底层原理。
2、内部类是什么
内部类的定义:将一个类的定义放在另一个类的定义内部,我们称之为内部类。
内部类可以分为四种:
举例子 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。
3、内部类链接外围类
内部类拥有其外围类的所有元素的访问权。
当生成一个内部类的对象时,此对象与制造它的外围对象(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,静态内部类是其他情况,且看下文)。
4、内部类引用:.this&.new 与向上转型
.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");
}
}
复制代码
代码分析:
内部类的用武之地:将内部类向上转型为基类时,尤其是转型为一个接口的时候,所得到的直接指向基类或接口的引用,能够很方便地隐藏实现细节;且这些接口的实现对于调用方,能够完全不可见的。
5、多重嵌套内部类
内部类还可以继续嵌套内部类。
举例子 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();
}
}
复制代码
代码分析:
对于多重嵌套的非静态内部类,我们需要层次分明的构建每一次嵌套的对象,然后通过引用来创建对象。
6、总结
本节是内部类学习的上部分,我们复习了:内部类的基础用法,外围类 &内部类的引用关系,以及多重嵌套内部类的定义使用。下部分我们详细讲解四种内部类的定义与使用,以及继承语法对内部类的影响。
7、延伸阅读
《源码系列》
《JDK之Object 类》
《JDK之BigDecimal 类》
《JDK之String 类》
《JDK之Lambda表达式》
《经典书籍》
《Java并发编程实战:第1章 多线程安全性与风险》
《Java并发编程实战:第2章 影响线程安全性的原子性和加锁机制》
《Java并发编程实战:第3章 助于线程安全的三剑客:final & volatile & 线程封闭》
《服务端技术栈》
《Docker 核心设计理念》
《Kafka史上最强原理总结》
《HTTP的前世今生》
《算法系列》
《读懂排序算法(一):冒泡&直接插入&选择比较》
《读懂排序算法(二):希尔排序算法》
《读懂排序算法(三):堆排序算法》
《读懂排序算法(四):归并算法》
《读懂排序算法(五):快速排序算法》
《读懂排序算法(六):二分查找算法》
《设计模式》
《设计模式之六大设计原则》
《设计模式之创建型(1):单例模式》
《设计模式之创建型(2):工厂方法模式》
《设计模式之创建型(3):原型模式》
《设计模式之创建型(4):建造者模式》
评论