探寻内部类的奥秘(上)

1、写在开头

在此之前,我们需要了解,内部类与组合是完全不同的概念,虽然它看起来像是一种代码隐藏机制,但其实内部类是了解外围类,并且能与之通信。下面,我们深刻探讨内部类的用法与底层原理。

2、内部类是什么

​内部类的定义:将一个类的定义放在另一个类的定义内部,我们称之为内部类。​

内部类可以分为四种:

  • 成员内部类:和成员变量一个级别

  • 局部内部类:在方法里的内部类(方法域内生效 或者 方法内某一段代码块域内生效)

  • 匿名内部类:基本上属于接口的实现类,一次性使用的场景。

  • 静态内部类:static 修饰的成员内部类。

picture.image

举例子 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):建造者模式

picture.image

0
0
0
0
关于作者
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论