美文网首页读书想法简友广场
Java编程思想笔记四:复用类

Java编程思想笔记四:复用类

作者: 红薯的Java私房菜 | 来源:发表于2022-05-25 22:41 被阅读0次

复用类就是指在不复制代码的前提下,通过某种手段创建新类来复用代码。作者本章介绍了两种手段:组合和继承,此外,还介绍了一种介于组合和继承之间的方法:代理。

本文概览

一、组合语法

class WaterSource {
    private String s;
    WaterSource() {
        System.out.println("WaterSource()");
        s = "Constructed";
    }
}

private class SprinklerSystem {
    private String value;
    private WaterSource source = new WaterSource();
}

二、继承语法

2.1.语法

class Cleaner {
    private String s = "Cleaner";
    public void append(String a) { s += a; }
    public void scrub() {
        append("scrub()");
    }
}

public class Detergent extends Cleaner {
    public void scrub() {
        append("Detergent.scrub()");
        super.scrub();
    }
}
  1. 按照惯例,基类中的方法都定义为 private,方法都定义为 public;
  2. 基类的方法要想被导出类使用,必须定义为 public 的;
  3. 如果基类和导出类的方法名称重复时,导出类想显式使用基类中的方法,使用 super 关键字;

2.2.初始化基类

继承并不只是复制基类的接口,当创建一个导出类的对象时,该对象包含了一个基类的子对象。这个子对象和你用基类直接创建的对象是一样的,二者的区别就在于直接创建的基类对象来自外部,而基类的子对象被包装在导出类对象的内部。

导出类&基类初始化顺序:

  1. 导出类内部调用基类构造器,初始化基类子对象;
  2. 导出类调用自己的构造器,初始化导出类;

如果是默认构造器,编译器会为我们完成第 1、2 步,如果是带参数的构造器,我们必须要用 super 显示调用基类的构造器,否则编译器会报错。

2.3.清理

我们在《Java编程思想笔记二:初始化与清理》 里提过,在 Java 中的对象可以依靠垃圾回收器在必要的时候释放其内存,但我们并不知道垃圾回收器何时工作。因此,当我们想要在某些时候清理一些东西,就必须手动来完成。按照常规,这个方法一般放在 finally 子句中,以预防异常出现。

清理方法中,需要注意基类清理方法和成员对象清理方法的调用顺序:

  1. 先清理执行类中的所有对象,对象的清理顺序和生成顺序正好相反(后构造的先清理);
  2. 调用基类的清理方法。

2.4.名称屏蔽

我们在使用继承时,导出类和基类经常会有形同名称、相同入参的方法,这经常会让人疑惑。如果想避免这种情况出现,可以在继承类中定义方法时用 @Override 注解:

class Lisa extends Homer {
    @Override
    void doh(Milhouse m) {
        System.out.println("doh(Milhouse m)");
    }
}

如果基类 Homer 中也存在 doh(Milhouse m) 方法,编译时就会报错,这样可以有效避免上述疑惑,这一点我们在项目开发中基本都会用到。

2.5.向上转型

将导出类引用转换为基类引用的动作我们称为向上转型。之所以称为向上转型,是以传统的类继承图的绘制方法为基础的:

继承

由导出类(Wind)转型成基类(Instrument),在继承图中是由下而上移动的,因此称为向上转型。向上转型总是安全的。

到底该用组合还是继承,一个最清晰的判断就是是否需要向上转型,如果需要则要使用继承。

三、代理语法

代理关系是介于继承和组合之间的中庸之道。代理指将一个成员对象置于所要构造的类中(就像组合),但与此同时,在新类中暴露了该成员对象的所有方法(就像继承)。例如,太空船需要一个控制模块:

public class SpaceShipControls {
    void up(int velocity) {}
    void down(int velocity) {}
}

制造太空船的一种方式是使用继承:

public class SpaceShip extends SpaceShipControls {
    private String name;
    public SpaceShip (String name) { this.name = name; }
}

但是这其实并不符合我们的预期,其实太空舱和控制模块是包含关系,而不是 is-a (继承)的关系,而且我们在 SpaceShipControls 类中并不希望把 SpaceShip 类中的所有方法都暴露出去。代理可以解决此难题:

public class SpaceShipDelegation {
    private String name;
    private SpaceShipControls controls = new SpaceShipControls();

    public SpaceShipDelegation(String name) {
        this.name = name;
    }
    public void up(int velocity) {
        controls.up(velocity);
    }
    public void down(int velocity) {
        controls.down(velocity);
    }
}

上面的代理方式实现了和继承一样的效果,SpaceShipDelegation 类拥有了 SpaceShipControls 类的所有方法。但是,代理更加灵活,比如我们不想把 down() 方法暴露出去,代理类就可以不定义 public void down(int velocity),但是继承做不到这一点。

四、final 关键字

final 关键字主要有三种使用情况:数据、方法和类。

4.1.final 数据

final 修饰基本类型和引用的区别

final 修饰某个成员时,这个成员如果是基本类型,可以实现下面两种效果:

  1. 一个永远不变的编译时常量;
  2. 一个在运行时被初始化的值,而你并不希望它被改变。

另外,一个既是 static 又是 final 的域,只占据一段不能改变的存储空间。

final 也可以修饰对象引用,final 会使引用恒定不变。一旦引用被初始化指向一个对象,就无法再把它指向另一个对象,但是对象本身的内容是可以被修改的。这一限制同样适用于数组,它也是对象。下面的示例展示了上述三种情况:

class Value {   // 包访问权限
    int i;
    public Value(int i) { this.i = i; }
}

public class FinalData {
    private final int valueOne = 1;
    private static final int VALUE_TWO = 2;
    public static final int VALUE_THREE = 3;

    private final Value v4 = new Value(4);
    private static final Value VAL_5 = new Value(5);

    private final int[] a = { 1, 2, 3 };

    public static void main(String[] args) {
        FinalData fd1 = new FinalData();
        // fd1.valueOne++;         -- 错误:编译器常量,不可修改
        // fd1.VALUE_TWO++;
        // fd1.VALUE_THREE++;

        // fd1.v4 = new Value(44); -- 错误:final引用,不可修改引用指向的对象
        // fd1.VAL_5 = new Value(55);
        // fd1.a = new int[5];

        for(int i=0; i<fd1.a.length; i++)
            fd1.a[i]++;          // -- 正确:final引用,不可修改引用指向的对象,但可以修改内容
    } 
}

空白 final

Java 允许生成空白 final,即被声明为 final 但又未给定初始值的域。但是必须在使用前进行初始化:

class class BlankFinal {
    private final int i = 0;
    private final int j;    // 空白 final

    public BlankFinal() {
        j = 1;              // 空白 final 在使用前要进行初始化
    }
}

所以,无论是否是空白 final,必须在域的定义处或者构造器中要对 final 进行赋值!

final 参数

Java 允许在参数列表中以声明的方式将参数指明为 final,在方法中该参数为只读参数,无法被修改:

public class FinalArguments {
    private Integer age;
    public Integer add(final Integer year) {
        // year += age;       -- 错误:year 为只读参数,不可修改
        return year + age;
    } 
}

4.2. final 方法

指明为 final 的方法在继承体系中,不会被导出类覆盖。

类中所有 private 方法都隐式指定为 final 的,由于导出类无法取用 private 方法,所以也就无法覆盖它。

给 private 方法添加 final 修饰词,不会增加任何额外意义。

导出类为什么不能覆盖基类的 private 方法?

class WithFinals {
    private final void f() { print("WithFinal.f()"); }
}

class OverridingPrivate extends WithFinals {
    private final void f() { print("OverridingPrivate.f()"); }
}

基类的 private 方法 f() 并不是基类接口的一部分,只是隐藏于内部的一块儿代码。导出类的 public/protected 方法 f() 是一个全新的方法,仅仅是和基类的 private 方法同名罢了,没有任何关系,也不会发生覆盖。

4.3. final 类

当类为 final 时,不允许继承该类。

final 类中的所有方法都隐式指定为 final 的,因为无法覆盖它们。在 final 类中可以给方法添加 fianl 修饰词,但不会添加任何意义。

final 类的域可以根据个人意愿设置是否为 final。

五、继承的初始化顺序

  1. 在其他任何事情发生之前,将分配给对象的存储空间初始化成二进制的零;
  2. 调用基类构造器;
  3. 按照声明顺序调用成员的初始化方法;
  4. 调用导出类构造器主体。

相关文章

  • Java编程思想学习笔记(7)

    Java编程思想学习笔记(7) 复用类 复用代码是Java的功能之一。 Java中对代码的复用是围绕着类展开的,可...

  • Java编程思想笔记四:复用类

    复用类就是指在不复制代码的前提下,通过某种手段创建新类来复用代码。作者本章介绍了两种手段:组合和继承,此外,还介绍...

  • Java编程思想 复用类

    继承语法 在创建子类对象的时候,会执行父类相关的构造方法的理解:子类可以使用父类的public,protected...

  • Java编程思想笔记7.复用类

    点击进入我的博客 复用代码是Java众多引人注目的功能之一,但要成为极具革命性的语言,仅仅能够复制代码并对之加以改...

  • Java编程思想(六) 复用类

    只需在新的类中产生现有类的对象。由于新的类是由现有的类的对象组成,被称为组合。 按照现有类的类型来创建新类。无需该...

  • 《Java编程思想》复用

    复用代码是java众多引人注目的功能之一。 Java 复用类有2中方式。第一种是组合,组合就是在新类中产生现有类的...

  • 《JAVA编程思想》学习笔记:第7章(复用类)

    第7章 复用类 7.1 名称屏蔽 在C++中,如果基类拥有一个已被多次重载的方法名称,那么在其派生类中重新定义该方...

  • 《 Java 编程思想》CH07 复用类

    复用代码是 Java 众多引人注目的功能之一。 Java 可以通过创建类来复用代码,要在使用类的时候不破坏现有代码...

  • 关于Java中基类构造器的调用问题

    在《Java编程思想》第7章复用类中有这样一段话,值得深思。当子类继承了父类时,就涉及到了基类和导出类(子类)这两...

  • 《java编程思想》第七章复用类

    Java实现代码的复用主要有三种方式,一,组合,在新类中产生现有类的对象,复用了现有代码的功能;二,继承,采用现有...

网友评论

    本文标题:Java编程思想笔记四:复用类

    本文链接:https://www.haomeiwen.com/subject/ccbyprtx.html