【java编程思想第五版笔记】第八章复用(更新中)

更新自2020.04.22

代码复⽤是⾯向对象编程(OOP)最具魅⼒的原因之⼀。

面向过程和面向对象复用的区别:

对于像 C 语⾔等⾯向过程语⾔来说,“复⽤”通常指的就是“复制代码”

Java 围绕“类”(Class)来解决问题。我们可以直接使⽤别⼈构建或

调试过的代码,⽽⾮创建新类、重新开始。

前提:
在不污染源代码的前提下使⽤现存代码

两种方法:组合和继承

他们都是基于现有类型构建新的类型

组合的语法

概念:把对象的引⽤(object references)放置在⼀个新的类⾥,这就使⽤了组合。

扫描二维码关注公众号,回复: 11065609 查看本文章

举例如下:假设你需要⼀个对象,其中内置了⼏个 String 对象,两个基本类

型(primitives)的属性字段,⼀个其他类的对象。

注:

对于⾮基本类型对象,

将引⽤直接放置在新类中,对于基本类型属性字段则仅进⾏声明。

代码如下:

class WaterSource {
  private String s;
  WaterSource() {
    System.out.println("WaterSource()");
    s = "Constructed";
  }
  @Override
  public String toString() { return s; }
}

public class SprinklerSystem {
  private String valve1, valve2, valve3, valve4;
  private WaterSource source = new WaterSource();
  private int i;
  private float f;
  @Override
  public String toString() {
    return
      "valve1 = " + valve1 + " " +
      "valve2 = " + valve2 + " " +
      "valve3 = " + valve3 + " " +
      "valve4 = " + valve4 + "\n" +
      "i = " + i + " " + "f = " + f + " " +
      "source = " + source; // [1]
  }
  public static void main(String[] args) {
    SprinklerSystem sprinklers = new SprinklerSystem();
    System.out.println(sprinklers);
  }
}
/*输出结果:
WaterSource()
valve1 = null valve2 = null valve3 = null valve4 = null
i = 0 f = 0.0 source = Constructed
*/

关于上面的程序的解析

仅限于个人理解

如果没有设置初始值,那么,引用的初始化结果为null,基础类型为int 的初始化结果是0,float类型的初始化结果是0.0

其中最重要的是source = Constructed的运行逻辑是new SprinklerSystem(),又因为在这个初始化块中声明了s的值是"Constructed",所以最后输出source = Constructed

编译器不会为每个引用创建一个默认对象,这是有意义的,因为在许多情况下,这会导致不必要的开销。

初始化引用有四种方法:

  1. 当对象被定义时。这意味着它们总是在调用构造函数之前初始化。
  2. 在该类的构造函数中。
  3. 在实际使用对象之前。这通常称为延迟初始化。在对象创建开销大且不需要每次都创建对象的情况下,它可以减少开销。
  4. 使用实例初始化。
    代码如下:
class Soap {
  private String s;
  Soap() {
    System.out.println("Soap()");
    s = "Constructed";
  }
  @Override
  public String toString() { return s; }
}

public class Bath {
  private String // Initializing at point of definition:
    s1 = "Happy",
    s2 = "Happy",
    s3, s4;
  private Soap castille;
  private int i;
  private float toy;
  public Bath() {
    System.out.println("Inside Bath()");//1
    s3 = "Joy";
    toy = 3.14f;
    castille = new Soap();
  }
  // Instance initialization:
  { i = 47; }
  @Override
  public String toString() {
    if(s4 == null) // Delayed initialization:
      s4 = "Joy";
    return
      "s1 = " + s1 + "\n" +
      "s2 = " + s2 + "\n" +
      "s3 = " + s3 + "\n" +
      "s4 = " + s4 + "\n" +
      "i = " + i + "\n" +
      "toy = " + toy + "\n" +
      "castille = " + castille;
  }
  public static void main(String[] args) {
    Bath b = new Bath();
    System.out.println(b);
  }
}
/* 输出结果:
Inside Bath()
Soap()
s1 = Happy
s2 = Happy
s3 = Joy
s4 = Joy
i = 47
toy = 3.14
castille = Constructed
*/

Bath 构造函数中,有一个代码块在所有初始化发生前就已经执行了。当你不在定义处初始化时,仍然不能保证在向对象引用发送消息之前执行任何初始化——如果你试图对未初始化的引用调用方法,则未初始化的引用将产生运行时异常。

当调用 toString() 时,它将赋值 s4,以便在使用字段的时候所有的属性都已被初始化。

继承语法

继承是面向对象的三大特性之一,每个类都会继承,如果它们不显式继承其他类,那么肯定它们隐式继承(Object)类,也是所有java类的祖类。

组合和继承的区别就是继承使用了一种特殊的语法——使用关键字 extends 后跟基类的名称。

class 子类 extends 父类

之后子类就会拥有父类的所有字段和方法,无需再次声明。

注:java不允许菱形继承

代码示例如下

class Cleanser {
  private String s = "Cleanser";

  public void append(String a) {
    s += a;
  }

  public void dilute() {
    append(" dilute()");
  }

  public void apply() {
    append(" apply()");
  }

  public void scrub() {
    append(" scrub()");
  }

  @Override
  public String toString() {
    return s;
  }

  public static void main(String[] args) {
    Cleanser x = new Cleanser();
    x.dilute();
    x.apply();
    x.scrub();
    System.out.println(x);
  }
}

public class Detergent extends Cleanser {
  // Change a method:
  @Override
  public void scrub() {
    append(" Detergent.scrub()");
    super.scrub(); // Call base-class version
  }

  // Add methods to the interface:
  public void foam() {
    append(" foam()");
  }

  // Test the new class:
  public static void main(String[] args) {
    Detergent x = new Detergent();
    x.dilute();
    x.apply();
    x.scrub();
    x.foam();
    System.out.println(x);
    System.out.println("Testing base class:");
    Cleanser.main(args);
  }
}
/* Output:
Cleanser dilute() apply() Detergent.scrub() scrub()
foam()
Testing base class:
Cleanser dilute() apply() scrub()
*/

这演示了一些特性。首先,在 Cleanserappend() 方法中,使用 += 操作符将字符串连接到 s,这是 Java 设计人员“重载”来处理字符串的操作符之一 ,专门为了处理字符串而进行重载的。

在这里,Detergent.main() 显式地调用 Cleanser.main(),从命令行传递相同的参数(当然,你可以传递任何字符串数组)。

Cleanser 的接口中有一组方法: append()dilute()apply()scrub()toString()。因为 Detergent 是从 Cleanser 派生的(通过 extends 关键字),即使你没有在 Detergent 中看到所有这些方法的显式定义,它也会在其接口中自动获取所有这些方法。

但是如果从父类里面的继承过来的方法不满意的话,可以覆写这个方法,只需要定义一个同名方法即可,不过你最好加上 @Override注解,如 scrub() 方法就进行了重写。

那么,可以把继承看作是复用类。如在 scrub() 中所见,可以使用基类中定义的方法并修改它。在这里,你可以在新类中调用基类的该方法。但是在 scrub() 内部,不能简单地调用 scrub(),因为这会产生递归调用。为了解决这个问题,Java的 super 关键字引用了当前类继承的“超类”(基类)。因此表达式 super.scrub() 调用方法 scrub() 的基类版本。

继承时,你不受限于使用基类的方法。你还可以像向类添加任何方法一样向派生类添加新方法:只需定义它。方法 foam() 就是一个例子。Detergent.main() 中可以看到,对于 Detergent 对象,你可以调用 CleanserDetergent 中可用的所有方法 (如 foam() )。

本笔记大部分基于《on java 8》整理, 另外初学者一枚,大家多多关照,有错误可以在下面说出来 谢谢大家

发布了19 篇原创文章 · 获赞 9 · 访问量 855

猜你喜欢

转载自blog.csdn.net/qq_43761222/article/details/105688148
今日推荐