码出高效读书笔记:重新思考接口和抽象类以及内部类

1、接口和抽象类

正如面向对象四大特征:抽象、继承、封装、多态所述,定义类的过程就是抽象和封装的过程。而接口和抽象类则是对实体类进行更高层次的抽象,仅定义公共行为和特征。两者的共同点是都不能被实例化,但可以通过定义引用变量指向实例对象。

下表示接口和抽象类的语法区别:

语法维度 抽象类 接口
定义关键字 abstract interface
子类继承或实现关键字 extends implements
方法实现 可以有 不能有,JDK8以后,允许有default实现
方法访问控制符 无限制 有限制,默认是public  abstract类型
属性访问控制符 无限制 有限制,默认是public static final类型
静态方法 可以有 不能有,JDK8以后,允许有
static{}静态代码块 可以有 不能有
本类型之间扩展 单继承 多继承
本类型之间扩展关键字 extends extends

抽象类在被继承时体现的是is-a关系,而接口再被实现时体现的是can-do关系。

与接口相比,抽象类通常是对同类事物相对具体的抽象,通常包含抽象方法、实体方法、属性变量。如果一个抽象类只有一个抽象方法,那么它等同于一个接口。

is-a关系需要符合里式代换原则(即父类适用的地方,用子类来替代同样适用。举个例子:警匪片中,警察经常说,放下武器!而对面的匪徒有的使用匕首,有的使用手枪,这些都是武器的子类。父类出现的地方,即“放下武器”,那么,“放下匕首”、“放下手枪”都是对的)。

can-do关系要符合接口隔离原则。实现类要有能力去实现并执行接口中定义的行为,例如Plan can fly.Brid can fly.中应该把fly这个动作/功能定义成一个接口,而不是把fly()放在某个抽象类中,再由Plane和Brid利用is-a关系去继承此抽象类。因为严格意义上来说,除了fly这个行为外,在Plane和Brid两者中很难再找到相同或者相似的特征。


抽象类是模板设计,而接口是契约式设计。

抽象类包含一组相对具体的特征,性格偏内向,比如某品牌特定型号的汽车,底盘结构、控制电路、刹车系统等是抽象出来的共同特征,但根据动感型、舒适性、豪华型的区分,内饰、车头灯、显示屏等可以存在不同版本的实现。

接口是开放的,性格偏外箱,它就像是一份合同,定义了方法名、参数、返回值,甚至是抛出的异常类型。谁都可以来实现它,但如果向实现它的类就必须遵守这份接口约定合同,比如,任何类型的车辆都必须实现如下的接口:

public interface VehicleSafe{
    /**
     * @param initSpeed 刹车时的初始速度
     * @param brakeTime 从initSpeed开始刹车到停止行驶的时间,单位是毫秒
     * @return 从initSpeed开始刹车到停止行驶的距离
     */
    double brake(int initSpeed,int brakeTime);

刹车是一个开放式的强制行为规范,任何车辆都必须具有刹车的能力,要明确在特定初速度的情况下,刹车时间多长,刹车距离多长。此规范对任何车辆都是强约束的,这就是契约。


接口是顶级的“类”,对应关键字是interface,但是编译后的字节码扩展名还是.class。抽象类是二当家,接口位于顶层,而抽象类对各个接口进行了组合,然后实现部分接口行为,其中AbstaractCollection是最典型的抽象类:

public abstract class AbstractCollection<E> implements Collection<E>{
    //Collection定义的抽象方法,但本类没有实现
    //Collection接口定义的方法,size()这个方法对于链表和顺序表有不同的实现方式
    public abstract int size();

    //实现Collection接口的这个方法,因为对AbstractCollection的子类它们的判空方式是一致的,这就是
    //模板式设计,对于所有它的子类,实现共同的方法体,通过多态调用到子类的具体size()实现
    
    public boolean isEmpty(){
        //实现Collection的方法
        return size() == 0;
    }

    //其他属性和部分方法实现.....
}

Java语言中类的继承采用单继承形式,避免继承泛滥、菱形继承、循环继承,甚至“四不像”实现类的出现。在JVM中,一个类如果有多个直接父类,那么绑定机制会变得非常复杂。

接口继承接口,关键字是extends,而不是implements,允许多重继承是因为接口有契约式的行为约定,没有任何具体实现和属性,某个实体类在实现多重继承后的借口时,只是说明“can do many things”。

当纠结定义接口还是抽象类时,优先推荐定义为接口,遵循接口隔离原则,按某个维度划分为多个接口,然后再用抽象类去implements某些接口,这样做可方便后续的扩展和重构。

2、内部类

在一个.java的源文件中,只能定义一个类名与文件名完全一致的公开类,并使用public class关键字来修饰。但是在面向对象语言中,任何一个类都可以在内部定义另外一个类,前者为外部类,后者为内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。

比如:属性字段private static String str,由访问控制符、是否静态类型、属性类型、变量名组成。而内部类private static class Inner{},也是按照这样的顺序来定义的。类型可以为class、enum,甚至是interface,当然在内部类中定义接口是不推荐的。

内部类可以是静态和非静态的,它可以出现在属性定义、方法体和表达式中,甚至可以匿名出现(匿名内部类),具体分为如下四种:

  1. 静态内部类,如:static class StaticInnerClass{};
  2. 成员内部类,如:private class InstanceInnerClass{};
  3. 局部内部类,定义在方法或者表达式内部;
  4. 匿名内部类,如:(new Thread(){}).start()。

如下是最精简的4中内部类定义方式:

public class OuterClass{
    
    //成员内部类
    private class InstanceInnerClass{}

    //静态内部类
    static class StaicInnerClass{}

    public static void main(String[] args){
        //两个匿名内部类
        (new Thread() {}).start();
        (new Thread() {}).start();

        //两个方法内部类
        class MethodClass1{}
        class MethodClass2{}
    }
}

无论是什么类型的内部类,都会编译成一个独立的.class文件,上面这个类编译以后会产生如下的.class文件:

  • OuterClass.class
  • OuterClass$1.class
  • OuterClass$1MethodClass1.class
  • OuterClass$1MethodClass2.class
  • OuterClass$2.class
  • OuterClass$InstanceInnerClass.class
  • OuterClass$StaticInnerClass.class

外部类与内部类之间使用$符号分隔,匿名内部类使用数字进行编号,而方法内部类,在类名前还有一个编号来标识是哪个方法。

匿名内部类和静态内部类是比较常用的方式

猜你喜欢

转载自blog.csdn.net/weixin_41047704/article/details/85602014