《OnJava8》精读(四) 接口与内部类

在这里插入图片描述

@

介绍


《On Java 8》是什么?

它是《Thinking In Java》的作者Bruce Eckel基于Java8写的新书。里面包含了对Java深入的理解及思想维度的理念。可以比作Java界的“武学秘籍”。任何Java语言的使用者,甚至是非Java使用者但是对面向对象思想有兴趣的程序员都该一读的经典书籍。目前豆瓣评分9.5,是公认的编程经典。

为什么要写这个系列的精读博文?

由于书籍读起来时间久,过程漫长,因此产生了写本精读系列的最初想法。除此之外,由于中文版是译版,读起来还是有较大的生硬感(这种差异并非译者的翻译问题,类似英文无法译出唐诗的原因),这导致我们理解作者意图需要一点推敲。再加上原书的内容很长,只第一章就多达一万多字(不含代码),读起来就需要大量时间。

所以,如果现在有一个人能替我们先仔细读一遍,筛选出其中的精华,让我们可以在地铁上或者路上不用花太多时间就可以了解这边经典书籍的思想那就最好不过了。于是这个系列诞生了。

一些建议

推荐读本书的英文版原著。此外,也可以参考本书的中文译版。我在写这个系列的时候,会尽量的保证以“陈述”的方式表达原著的内容,也会写出自己的部分观点,但是这种观点会保持理性并尽量少而精。本系列中对于原著的内容会以引用的方式体现。
最重要的一点,大家可以通过博客平台的评论功能多加交流,这也是学习的一个重要环节。

第十章 接口


本章总字数:11000
关键词:

  • 抽象类和方法
  • 创建接口
  • 接口适配

抽象类和方法

抽象方法只有声明没有方法体。如下:

abstract void f();

包含有抽象方法的类叫做——抽象类。如果一个类里含有抽象方法(无论是一个或者多个),该类必须声明为抽象。比如:

abstract class Basic {
    abstract void unimplemented();
}

如果创建一个继承抽象类的新类并为之创建对象,那么就必须为基类的所有抽象方法提供方法定义。如果不这么做(可以选择不做),新类仍然是一个抽象类,编译器会强制我们为新类加上 abstract 关键字。

如果你写了一个类,这个类里没有任何抽象方法,但是你对类声明为 abstract,那这个类依然无法被创建。有时候这种做法可以防止你未完成的类被别人调用:)

创建接口

使用interface 来创建接口。

// interfaces/PureInterface.java
// Interface only looked like this before Java 8
public interface PureInterface {
    int m1(); 
    void m2();
    double m3();
}

我们可以认为,接口是一种特殊的抽象类——所有的方法都是抽象的。所以很明显,接口也不能直接原来创建对象。

有意思的是,在Java8中,接口可以使用default 关键词。它可以用来标记一个默认的方法。

// interfaces/InterfaceWithDefault.java
interface InterfaceWithDefault {
    void firstMethod();
    void secondMethod();

    default void newMethod() {
        System.out.println("newMethod");
    }
}
...

InterfaceWithDefault i = new Implementation2();
i.firstMethod();
i.secondMethod();
i.newMethod();

这些方法都可以被成功调用。

Java 过去是一种严格要求单继承的语言:只能继承自一个类(或抽象类),但可以实现任意多个接口。多年后的现在,Java 通过默认方法具有了某种多继承的特性。结合带有默认方法的接口意味着结合了多个基类中的行为。

抽象类和接口

如果是在Java8之前,接口与抽象类的差异还很明显,但是在Java8加入了default 方法后,两者的选择产生了一些疑惑,以下是他们的区别:
在这里插入图片描述
不仅类可以继承,接口也可以。使用继承的方式,我们可以拓展接口。

interface Monster {
    void menace();
}
interface DangerousMonster extends Monster {
    void destroy();
}
class DragonZilla implements DangerousMonster {
    @Override
    public void menace() {}

    @Override
    public void destroy() {}
}

有没有想过这种可能,一个类继承自A、B、C三种接口。这些接口中都有一个名叫f的方法,且参数类型不同,那会怎样?
事实上,编译器会报错。因为继承的接口命名相同而出现了混淆。

当打算组合接口时,在不同的接口中使用相同的方法名通常会造成代码可读性的混乱,尽量避免这种情况。

接口适配

接口最强大的一点在于,可以规范某一系列类的标准。只要继承并实现这个接口,那一定就是符合我的接口标准的类。

利用这点,我们可以在实际项目中规定某些场景必须使用指定接口实现的类型。

打个比方,Animal 接口下有eat、sleep等方法。而Cat、Dog实现了该接口。我们现在要做一个动物园的模块。在动物园中出现的动物必须是Animal 接口的实现类,如果你传入非动物类,那是不被允许的。这样就规范了对象模型。

可以说:“只要对象遵循接口,就可以调用方法” ,这使得方法更加灵活,通用,并更具可复用性。

接口字段

在接口中是可以定义字段的。接口中的字段都是static 和 final的。

import java.util.*;

public interface RandVals {
    Random RAND = new Random(47);
    int RANDOM_INT = RAND.nextInt(10);
    long RANDOM_LONG = 10;
}	

第十一章 内部类


本章总字数:13000

关键词:

  • 内部类的概念
  • 创建内部类
  • .this 和 .new
  • 内部类作用域
  • 什么时候需要内部类
  • 内部类的一些特点

什么是内部类

作者在本章一开始就简明扼要的说出了定义——一个定义在另一个类中的类,叫作内部类。

内部类有自己的特殊性,它位于一个类的内部,所以他的访问级别是特殊的,同时它与“组合”的概念又不相同。值得一提,“ 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 里面的。

一个内部类拥有其所有外部对象的访问权。说到底这与我们之前了解到的引用有关。

当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。

.this 和 .new

.this用来供内部类调用外部类的对象。而.new则是在外部使用,目的是告诉外部类初始化指定的内部类。

看示例:

// 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 dn = new DotThis();
        DotThis.Inner dni = dn.new Inner();
    }
}

内部类的作用域

内部类可以被用在几乎任何一个地方。甚至在方法内部:

// innerclasses/Parcel5.java
// Nesting a class within a method
public class Parcel5 {
    public Destination destination(String s) {
        final class PDestination implements Destination {
            private String label;

            private PDestination(String whereTo) {
                label = whereTo;
            }

            @Override
            public String readLabel() { return label; }
        }
        return new PDestination(s);
    }

    public static void main(String[] args) {
        Parcel5 p = new Parcel5();
        Destination d = p.destination("Tasmania");
    }
}

除此之外,内部类还可以被写在任何一个作用域内,如 If语句中。

        if (b) {
            class TrackingSlip {
                private String id;
                TrackingSlip(String s) {
                    id = s;
                }
                String getSlip() {
                    return id;
                }
            }
            TrackingSlip ts = new TrackingSlip("slip");
            String s = ts.getSlip();
        }

什么时候需要内部类

原著的话是“为什么需要内部类”。但是我们更加实际,我们更想讨论什么时候才需要用到它。
从作者处得出的答案来看,主要有以下几点:

  1. 闭包(即实现某一系列功能的对象)
  2. 实现多重继承
  3. 一些应用程序框架中会使用

我们见到最多的情景是闭包。也就是在代码块中需要临时性的使用某一种对象功能时创建它。但是Java8 里出现了Lambda 后,这种情况正在被替代。

之后就是多重继承了。因为Java中不允许有多个基类,有一些场景中需要实现某种多重继承功能就有可能需要内部类。至于第3点已经不再重要(被主要用于用户图形界面中)。

控制框架是一类特殊的应用程序框架,它用来解决响应事件的需求。主要用来响应事件的系统被称作事件驱动系统。应用程序设计中常见的问题之一是图形用户接口(GUI),它几乎完全是事件驱动的系统。

内部类的一些特点

内部类可以继承、覆盖吗?

由于内部类的特殊性,一般很少去这么干。在这里我们也只是做了解。
首先,内部类是可以被继承和覆盖的。但是实现的方式比较特殊(主要是构造器部分)。

我们知道在Java中一个类产生一个.class文件,那内部类又如何呢?
事实上,内部类也会生成。只不过命名方式有些特别:

这些类文件的命名有严格的规则:外围类的名字,加上“$",再加上内部类的名字。

例如:

Counter.class
LocalInnerClass$1.class
LocalInnerClass$LocalCounter.class
LocalInnerClass.class

总结

如果说前几章的内容都是OOP语言的共通基础。那这两章内容就是有Java特色的编程了。像接口的default方法和内部类实现的多重继承,这些都比前几章要多花时间才能理解。强烈推荐大家收藏本系列。后面的章节也会在之后一一呈现。

posted @ 2021-01-23 21:08  Hi-Jimmy  阅读(330)  评论(0编辑  收藏  举报