Think in Java 5th/On Java 8阅读整理:内部类(篇1)

  之前买了第四版的Java编程思想,看了大半年了也才看完了面向对象部分,后面是越看越吃力,最近在网上看到有该系列第五版的电子书,但书名更改为"On Java 8",也是以Java 8为起点的书籍,所以决定直接在电脑上看电子版,而且书上的代码可以直接复制非常便于去实践,调试代码.这次同时也准备把看的内容整理下来,在其中增加点自己的理解,笔记啥的.那么此文章为本系列的第一篇,也决定直接从"内部类"这章开始,GO!

书籍电子版传送门

无底色文字:原文章
有底色文字:个人备注
代码:原文代码+个人测试代码+个人注释


一个定义在另一个类中的类,叫作内部类。

  内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可见性。然而必须要了解,内部类与组合是完全不同的概念,这一点很重要。在最初,内部类看起来就像是一种代码隐藏机制:将类置于其他类的内部。但是,你将会了解到,内部类远不止如此,它了解外围类,并能与之通信,而且你用内部类写出的代码更加优雅而清晰,尽管并不总是这样(而且 Java 8 的 Lambda 表达式和方法引用减少了编写内部类的需求)。

  最初,内部类可能看起来有些奇怪,而且要花些时间才能在设计中轻松地使用它们。对内部类的需求并非总是很明显的,但是在描述完内部类的基本语法与语义之后,"Why inner classes?"就应该使得内部类的益处明确显现了。

  本章剩余部分包含了对内部类语法更加详尽的探索,这些特性是为了语言的完备性而设计的,但是你也许不需要使用它们,至少一开始不需要。因此,本章最初的部分也许就是你现在所需的全部,你可以将更详尽的探索当作参考资料。

  以上就是稍微介绍了下内部类。提到了Java 8中与内部类相关的Lambda表达式,后面有章节专门讲解。以及对内部类的应用在初期阶段比较少并且浅,至少我现在刚开始工作还没怎么用。

创建内部类

创建内部类的方式就如同你想的一样——把类的定义置于外围类的里面:

// innerclasses/Parcel1.java
// Creating inner classes
public class Parcel1 {
    class Contents {
        private int i = 11;

        public int value() { return i; }
    }

    class Destination {
        private String label;

        Destination(String whereTo) {
            label = whereTo;
        }

        String readLabel() { return label; }
    }
    // Using inner classes looks just like
    // using any other class, within Parcel1:
    public void ship(String dest) {
        Contents c = new Contents();
        Destination d = new Destination(dest);
        System.out.println(d.readLabel());
    }

    public static void main(String[] args) {
        Parcel1 p = new Parcel1();
        p.ship("Tasmania");
    }
}

输出为:

Tasmania

  当我们在 ship() 方法里面使用内部类的时候,与使用普通类没什么不同。在这里,明显的区别只是内部类的名字是嵌套在 Parcel1 里面的。

  更典型的情况是,外部类将有一个方法,该方法返回一个指向内部类的引用,就像在 to() 和 contents() 方法中看到的那样:

// innerclasses/Parcel2.java
// Returning a reference to an inner class
public class Parcel2 {
    class Contents {
        private int i = 11;

        public int value() { return i; }
    }

    class Destination {
        private String label;

        Destination(String whereTo) {
            label = whereTo;
        }

        String readLabel() { return label; }
    }

    public Destination to(String s) {
        return new Destination(s);
    }

    public Contents contents() {
        return new Contents();
    }

    public void ship(String dest) {
        Contents c = contents();
        Destination d = to(dest);
        System.out.println(d.readLabel());
    }

    public static void main(String[] args) {
        Parcel2 p = new Parcel2();
        p.ship("Tasmania");
        Parcel2 q = new Parcel2();
        // Defining references to inner classes:
        Parcel2.Contents c = q.contents();
        Parcel2.Destination d = q.to("Borneo");
    }
}

输出为:

Tasmania

  如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须像在 main() 方法中那样,具体地指明这个对象的类型:OuterClassName.InnerClassName。(译者注:在外部类的静态方法中也可以直接指明类型 InnerClassName,在其他类中需要指明 OuterClassName.InnerClassName。)

  最后的这段话,理解了一会并且花了点时间进行验证,分别用:
1.外部类的静态方法创建内部类对象(不需要加前缀,如:InnerClassName)
2.其他类的静态方法创建内部类对象(需要前缀,如:OuterClassName.InnerClassName)
3.其他类的普通方法创建内部类对象(需要前缀,如:OuterClassName.InnerClassName)
  在以下为测试的代码:

// innerclasses/Parcel2.java
// Returning a reference to an inner class
public class Parcel2 {
    
    /**
     * @author: sunzhen
     * @date: 2019-08-29 19:10
     * @param: null
     * @return:
     * @throws:
     * @description: 测试静态方法使用内部类
    */
    public static void test1(){
        //外部类的静态方法中,创建内部类对象时,这个变量不需要加外部类名称的前缀(参考TestInnerClazz类的其他类创建内部类的例子)
        /*Contents contents = new Contents(); 不能直接创建,除非内部类也为static,或将该方法的static去除*/
        // 1. 接收
        //Contents contents = new Parcel2().contents();
        // 2. 利用.new语法创建内部类对象
        Contents contents = new Parcel2().new Contents();
        System.out.println(contents.value());

    }

    class Contents {
        private int i = 11;

        public int value() { return i; }
    }

    class Destination {
        private String label;

        Destination(String whereTo) {
            label = whereTo;
        }

        String readLabel() { return label; }
    }

    public Destination to(String s) {
        return new Destination(s);
    }

    public Contents contents() {
        return new Contents();
    }

    public void ship(String dest) {
        Contents c = contents();
        Destination d = to(dest);
        System.out.println(d.readLabel());
    }

    public static void main(String[] args) {
        /* 书中测试案例
        Parcel2 p = new Parcel2();
        p.ship("Tasmania");
        Parcel2 q = new Parcel2();
        // Defining references to inner classes:
        Parcel2.Contents c = q.contents();
        Parcel2.Destination d = q.to("Borneo");
        */

        //测试静态方法使用内部类,不需要加前缀
        test1();
    }
}
/**
 * @author: sunzhen
 * @date: 2019/8/29
 * @time: 19:22
 * @description: 配合Parcel2测试其内部类在该类(TestInnerClazz)中使用方式
 */
public class TestInnerClazz {

    /**
     * @author: sunzhen
     * @date: 2019-08-29 19:28
     * @param: null
     * @return:
     * @throws:
     * @description: 其他类静态方法创建Parcel2类的Contents内部类对象,需要加外部类类名前缀
    */
    public static void test01(){
        Parcel2 parcel = new Parcel2();
        // 1. 接收
        //Parcel2.Contents contents = parcel.contents();
        // 2. 利用.new语法创建内部类对象
        Parcel2.Contents contents = parcel.new Contents();
        System.out.println(contents.value());
    }

    /**
     * @author: sunzhen
     * @date: 2019-08-29 19:28
     * @param: null
     * @return:
     * @throws:
     * @description: 其他类普通方法创建Parcel2类的Contents内部类对象,需要加外部类类名前缀
     */
    public void test02(){
        Parcel2 parcel = new Parcel2();
        // 1. 接收
        //Parcel2.Contents contents = parcel.contents();
        // 2. 利用.new语法创建内部类对象
        Parcel2.Contents contents = parcel.new Contents();
        System.out.println(contents.value());
    }

    public static void main(String[] args) {
        TestInnerClazz innerClazz = new TestInnerClazz();
        //其他类静态方法创建Parcel2类的Contents内部类对象,需要加外部类类名前缀
        innerClazz.test01();
        //其他类普通方法创建Parcel2类的Contents内部类对象,需要加外部类类名前缀
        innerClazz.test02();
    }

}

链接外部类

  到目前为止,内部类似乎还只是一种名字隐藏和组织代码的模式。这些是很有用,但还不是最引人注目的,它还有其他的用途。当生成一个内部类的对象时,此对象与制造它的外围对象(enclosing object)之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外围类的所有元素的访问权。

// innerclasses/Sequence.java
// Holds a sequence of Objects
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;
    }
    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 Selector selector() {
        return new SequenceSelector();
    }
    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();
        }
    }
}

输出为:

0 1 2 3 4 5 6 7 8 9

  Sequence 类只是一个固定大小的 Object 的数组,以类的形式包装了起来。可以调用 add() 在序列末尾增加新的 Object(只要还有空间),要获取 Sequence 中的每一个对象,可以使用 Selector 接口。这是“迭代器”设计模式的一个例子,在本书稍后的部分将更多地学习它。Selector 允许你检查序列是否到末尾了(end()),访问当前对象(current()),以及移到序列中的下一个对象(next())。因为 Selector 是一个接口,所以别的类可以按它们自己的方式来实现这个接口,并且其他方法能以此接口为参数,来生成更加通用的代码。

  这里,SequenceSelector 是提供 Selector 功能的 private 类。可以看到,在 main() 中创建了一个 Sequence,并向其中添加了一些 String 对象。然后通过调用 selector() 获取一个 Selector,并用它在 Sequence 中移动和选择每一个元素。 最初看到 SequenceSelector,可能会觉得它只不过是另一个内部类罢了。但请仔细观察它,注意方法 end(),current() 和 next() 都用到了 items,这是一个引用,它并不是 SequenceSelector 的一部分,而是外围类中的一个 private 字段。然而内部类可以访问其外围类的方法和字段,就像自己拥有它们似的,这带来了很大的方便,就如前面的例子所示。

  所以内部类自动拥有对其外围类所有成员的访问权。这是如何做到的呢?当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。幸运的是,编译器会帮你处理所有的细节,但你现在可以看到:内部类的对象只能在与其外围类的对象相关联的情况下才能被创建(就像你应该看到的,内部类是非 static 类时)。构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错。不过绝大多数时候这都无需程序员操心。

主要用一个例子讲了内部类可以使用外部类的属性,以及上面一段说明了可以这么做的原因

使用 .this 和 .new

  如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和 this。这样产生的引用自动地具有正确的类型,这一点在编译期就被知晓并受到检查,因此没有任何运行时开销。下面的示例展示了如何使用 .this:

// innerclasses/DotThis.java
// Accessing the outer-class object
public class DotThis {
    void f() { System.out.println("DotThis.f()"); }

    public class Inner {
        public DotThis outer() {
            return DotThis.this;
            // A plain "this" would be Inner's "this"
        }
    }

    public Inner inner() { return new Inner(); }

    public static void main(String[] args) {
        DotThis dt = new DotThis();
        DotThis.Inner dti = dt.inner();
        dti.outer().f();
    }
}

输出为:

DotThis.f()

  有时你可能想要告知某些其他对象,去创建其某个内部类的对象。要实现此目的,你必须在 new 表达式中提供对其他外部类对象的引用,这是需要使用 .new 语法,就像下面这样:

// innerclasses/DotNew.java
// Creating an inner class directly using .new syntax
public class DotNew {
    public class Inner {}
    public static void main(String[] args) {
        DotNew dn = new DotNew();
        DotNew.Inner dni = dn.new Inner();
    }
}

  要想直接创建内部类的对象,你不能按照你想象的方式,去引用外部类的名字 DotNew,而是必须使用外部类的对象来创建该内部类对象,就像在上面的程序中所看到的那样。这也解决了内部类名字作用域的问题,因此你不必声明(实际上你不能声明)dn.new DotNew.Innero。

下面你可以看到将 .new 应用于 Parcel 的示例:

// innerclasses/Parcel3.java
// Using .new to create instances of inner classes
public class Parcel3 {
    class Contents {
        private int i = 11;
        public int value() { return i; }
    }
    class Destination {
        private String label;
        Destination(String whereTo) { label = whereTo; }
        String readLabel() { return label; }
    }
    public static void main(String[] args) {
        Parcel3 p = new Parcel3();
        // Must use instance of outer class
        // to create an instance of the inner class:
        Parcel3.Contents c = p.new Contents();
        Parcel3.Destination d =
                p.new Destination("Tasmania");
    }
}

  在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗地连接到建它的外部类对象上。但是,如果你创建的是嵌套类(静态内部类),那么它就不需要对外部类对象的引用。

这节主要讲了内部类创建外部类对象用.this语法,外部类或其他类创建内部类对象用.new语法。当然最后一段也提到如果是静态内部类,就不需要外部类对象引用,下面的章节可能会介绍这种情况,然后再实践一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值