[读书笔记]Java编程思想第7章之复用类

Posted Spring-_-Bear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[读书笔记]Java编程思想第7章之复用类相关的知识,希望对你有一定的参考价值。

  1. 只需在新的类中产生现有类的对象。由于新的类是由现有类的对象所组成,所以这种方法称为组合。
  2. 初始化对象引用有以下几种方法:
    1)在定义对象的地方初始化。这意味着它们总是能够在构造器被调用之前被初始化。
    2)在类的构造器中进行初始化。
    3)在要开始使用这些引用之前进行初始化,这种方式叫做惰性初始化。在生成对象不值得及不必每次都生成对象的情况下,这种方式可以减少额外的负担。
    4)使用实例进行初始化。
package thinkinjava.charpenter7;

/**
 * @author Spring-_-Bear
 * @version 2021/9/25 22:48
 */
public class Bath {
    /**
     * Initializing at point of definition:
     */
    private String s1 = "Happy";
    private String s2 = "Happy";
    private String s3, s4;
    private Soap castille;
    private int i;
    private float toy;

    /**
     * Constructor initialization
     */
    public Bath() {
        System.out.println("Inside Bath()");
        s3 = "Joy";
        toy = 3.14F;
        castille = new Soap();
    }

    /**
     * Instance initialization:
     */ {
        i = 47;
    }

    @Override
    public String toString() {
        /**
         * Delayed initialization
         */
        if (s4 == null) {
            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);
    }
}

class Soap {
    private String s;

    Soap() {
        System.out.println("Soap()");
        s = "Constructed";
    }

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

每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个String而你只有一个对象(引用)时,该方法便会被(自动)调用。

  1. 为了继承,一般的规则是将所有的数据成员都指定为private,将所有的方法都指定为public。Java用super关键字表示超类的意思,当前类就是从超类继承来的。
  2. 当创建了一个导出类(子类)的对象时,该对象包含了一个基类(父类)的子对象。这个子对象与你用基类直接创建的对象是一样的。二者区别在于,后者来自于外部,而基类的子对象被包装在导出类对象内部。Java会自动在导出类的构造器中插入对基类构造器的调用。
package thinkinjava.charpenter7;

/**
 * @author Spring-_-Bear
 * @version 2021/9/26 9:07
 */

class Art{
    Art() {
        System.out.println("Art() Constructor.");
    }
}

class Drawing extends Art{
    Drawing() {
        System.out.println("Drawing() Constructor.");
    }
}

public class Cartoon extends Drawing {
    public Cartoon() {
        System.out.println("Cartoon() Constructor.");
    }

    public static void main(String[] args) {
        Cartoon cartoon = new Cartoon();
    }
}

构建过程时从基类“向外”扩散的,所以基类在导出类构造器可以访问它之前,就已经完成了初始化。即使你不为Cartoon()创建构造器,编译器也会为你创建一个默认的构造器,该构造器将调用基类的构造器。

  1. Java语言不直接支持代理,但很多开发工具却支持代理。
  2. 虽然编译器强制你去初始化基类,并且要求你要在构造器起始处就要这么做,但是它并不监督你必须将成员对象也初始化。
  3. 最好的办法是除了内存以外,不能依赖垃圾回收器去做任何事。如果需要进行清理,最好是编写自己的清理方法,但不要使用finalize()。
  4. 如果Java的基类拥有某个已被多次重载的方法名称,那么在导出类中重新定义该方法并不会屏蔽在基类中的任何版本。
package thinkinginjava.charpenter7;

/**
 * @author Spring-_-Bear
 * @version 2021/9/26 10:14
 */
class Homer{
    char doh(char c) {
        System.out.println("doh(char)");
        return 'd';
    }

    float doh(float f) {
        System.out.println("doh(float)");
        return 1.0f;
    }
}

class Milhouse{}

class Bart extends  Homer{
    void doh(Milhouse milhouse) {
        System.out.println("doh(Milhouse)");
    }
}

public class Hide {
    public static void main(String[] args) {
        Bart bart = new Bart();
        bart.doh(1);
        bart.doh('x');
        bart.doh(1.0F);
        bart.doh(new Milhouse());
    }
}
  1. 组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形。
  2. “向上类型转换”:“为新的类提供方法”并不是继承技术中最重要的方面,其最重要的方面是用来表现新类和基类之间的关系。这种关系可以用“新类是现有类的一种类型”这句话加以概括。
package thinkinginjava.charpenter7;

/**
 * @author Spring-_-Bear
 * @version 2021/9/26 11:19
 */
class Instrument{
    public void play() {
    }

    static void tune(Instrument instrument) {
        instrument.play();
    }
}

public class Wind extends Instrument {
    public static void main(String[] args) {
        Wind flute = new Wind();
        /* 子类对象的引用可以传给父类对象的引用 */
        Instrument.tune(flute);
    }
}
  1. 在面向对象编程中,生成和使用程序代码最有可能采用的方法就是直接将数据和方法包装进一个类中,并使用该类的对象。也可以运用组合技术使用现有类来开发新的类;而继承技术其实是不太常用的。因此,尽管在教授OPP的过程中我们多次强调继承,但这并不意味着要尽可能使用它。相反,应当慎用这一技术,其使用场合仅限于你确信使用该技术确实有效的情况。到底是改用组合还是继承,一个最清晰的办法就是问一问自己是否需要从新类向基类进行向上转型。如果必须向上转型,则继承是必要的;但如果不需要,则应该好好考虑自己是否需要继承。
  2. 对于编译期常量这种情况,编译器可以将该常量值代入任何可能用到它的计算式中,也就是说,可以在编译时执行计算式,这减轻了一些运行时的负担。在Java中,这类常量必须是基本数据类型,并且以关键字final表示。在对这个变量进行定义的时候,必须对其进行赋值。一个既是static又是final的域只占用一段不能改变的存储空间。对用final修饰的对象引用,final使引用恒定不变。
package thinkinginjava.charpenter7;

import java.util.Random;

/**
 * @author Spring-_-Bear
 * @version 2021/9/26 11:31
 */
class Value {
    int i;

    public Value(int i) {
        this.i = i;
    }
}

public class FinalData {
    private static Random random = new Random(47);
    private String id;

    public FinalData(String id) {
        this.id = id;
    }

    /**
     * Can be  compile-time constants:
     */
    private final int valueOne = 9;
    private static final int VALUE_TWO = 99;
    /**
     * Typical public constantS
     */
    public static final int VALUE_THREE = 39;
    /**
     * Cannot be compile-time constants:
     */
    private final int i4 = random.nextInt(20);
    static final int INT_5 = random.nextInt(20);
    private Value v1 = new Value(11);
    private final Value v2 = new Value(22);
    private static final Value VAL_3 = new Value(33);
    /**
     * Arrays:
     */
    private final int[] a = {1, 2, 3, 4, 5, 6};

    @Override
    public String toString() {
        return id + ": " + "i4 = " + i4 + ", INT_5 = " + INT_5;
    }

    public static void main(String[] args) {
        FinalData finalData1 = new FinalData("finalData1");

        finalData1.v2.i++;
        finalData1.v1 = new Value(9);

        for (int i = 0; i < finalData1.a.length; i++) {
            finalData1.a[i]++;
        }

        System.out.println(finalData1);
        System.out.println("Creating new FileData");
        FinalData finalData2 = new FinalData("finalData2");
        System.out.println(finalData1);
        System.out.println(finalData2);
    }
}

我们不能因为某数据是final的就认为在编译时可以知道它的值。

  1. Java允许生成“空白final”,所谓空白final是指被声明为final但又为给定初值的域。无论什么情况,编译器都确保空白final在使用前必须被初始化。但是,空白final在关键字final的使用上提供了更大的灵活性。为此,一个类中的final域就可以做到根据对象不同而有所不同,却又保持其恒定不变的特性。
package thinkinginjava.charpenter7;

/**
 * @author Spring-_-Bear
 * @version 2021/9/26 11:51
 */
class Poppet {
    private int i;

    Poppet(int ii) {
        i = ii;
    }
}

public class BlankFinal {
    private final int i = 0;
    private final int j;
    private final Poppet p;

    /**
     * Blank finals must be initialed in the constructor.
     */

    public BlankFinal() {
        j = 1;
        p = new Poppet(1);
    }

    public BlankFinal(int x) {
        j = x;
        p = new Poppet(x);
    }

    public static void main(String[] args) {
        new BlankFinal();
        new BlankFinal(47);
    }
}

必须在域的定义处或者每个构造器中使用表达式对final进行赋值,这正是final域在使用前总是被初始化的原因。

  1. 使用final方法的原因有两个,第一个原因是把方法锁定,以访任何继承类修改它的含义。这是出于设计的考虑:想要确保在继承中是方法行为保持不变,并且不会被覆盖。过去建议使用final方法的第二个原因是效率。类中所有的private方法都隐式地指定是final的。
package thinkinginjava.charpenter7;

/**
 * @author Spring-_-Bear
 * @version 2021/9/26 14:39
 */
class WithFinals{
    private final void f(){
        System.out.println("WithFinals.f()");
    }

    private void g(){
        System.out.println("WithFinals.g()");
    }
}

class OverridingPrivate extends WithFinals{
    private final void f() {
        System.out.println("OverridingPrivate.f()");
    }

    private void g() {
        System.out.println("OverridingPrivate.g()");
    }
}

class OverridingPrivate2 extends OverridingPrivate{
    public final void f() {
        System.out.println("OverridingPrivate2.f()");
    }

    public void g() {
        System.out.println("OverridingPrivate.g()");
    }
}

public class FinalOverridingIllusion {
    public static void main(String[] args) {
        OverridingPrivate2 op2 = new OverridingPrivate2();
        op2.f();
        op2.g();
        /**
         * You can upcast:
         * 子类对象的引用可以当作是父类对象的引用
         * 父类的方法是private的,所以子类的对象不可调用
         */
        OverridingPrivate op = op2;
        WithFinals withFinals = op2;
    }
}

“覆盖”只有在某方法是基类的接口的一部分时才会出现。即,必须能够将一个对象进行向上转型为它的基本类型并调用相同的方法。

  1. final类的域可以根据个人的意愿选择是或不是final。不论类是否被定义为final,相同的规则都适用于定义为final的域。然而,由于final类禁止继承,所以final类中的所有方法都隐式指定为final,因为无法覆盖它们。在final中可以给方法添加final修饰词,但这不会增添任何意义。
  2. 类的代码在初次使用时才会加载。这通常是指加载发生于创建类的第一个对象时,但是当访问static域或static方法时,也会发生加载。所有的static对象和static代码都会在加载时依照程序中书写顺序而依次初始化。
package thinkinginjava.charpenter7;

/**
 * @author Spring-_-Bear
 * @version 2021/9/26 14:53
 */
class Insect {
    private int i = 9;
    protected int j;

    Insect() {
        System.out.println("i = " + i + ", j = " + j);
        j = 39;
    }

    private static int x1 = printInit("Static Insect.x1 initialized");

    static int printInit(String s) {
        System.out.println(s);
        return 47;
    }
}

public class Beetle extends Insect {
    private int k = printInit("Beetle.k initialized");

    public Beetle() {
        System.out.println("k = " + k + ", j = " + j);
    }

    private static int x2 = printInit("static beetle.x2 initialized");

    public static void main(String[] args) {
        System.out.println("Beetle constructor.");
        Beetle b = new Beetle();
    }
}
  1. 组合一般是将现有类型作为新类型底层实现的一部分来加以复用,而继承复用的是接口。在使用继承时,由于导出类具有基类接口,因此它可以向上转型至基类。尽管面向对象编程极力强调继承,但在一开始设计时,一般应优秀选择组合(或者可能是代理),只在确实必要时才使用继承。

练习1: 创建一个简单的类。在第二个类中,将一个引用定义为第一个类的对象。运用惰性初始化来实例化这个对象。

package thinkinginjava.charpenter7;

/**
 * @author Spring-_-Bear
 * @version 2021/9/26 15:06
 */
class Animal {
    private String s;

    Animal() {
        System.out.println("Inside Animal()");
        s = "In Animal() Constructed ";
    }

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

public class SevenTest1 {
    private String s;
    private Animal animal;

    SevenTest1() {
        System.out.println("Inside SevenTest1()");
        s = "In SevenTest1() Constructed ";
    }

    @Override
    public String toString() {
        /**
         * delayed initialization:
         */
        if (animal == null) {
            animal = new Animal();
        }

        return s + animal;
    }

    public static void main(String[] args) {
        SevenTest1 sevenTest1 = new SevenTest1();
        System.out.println(sevenTest1);
    }
}

练习2: 从Detergent中继承产生一个新的类。覆盖scrub()并添加一个名为sterilize()的新方法。

package thinkinginjava.charpenter7;

/**
 * @author Spring-_-Bear
 * @version 2021/9/26 15:20
 */
class Cleanser1 {
    private String s = "Cleanser";

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

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

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

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

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

    public static void main(String[] args) {
        Cleanser1 x = new Cleanser1(以上是关于[读书笔记]Java编程思想第7章之复用类的主要内容,如果未能解决你的问题,请参考以下文章

[读书笔记]Java编程思想第6章之访问权限控制

[读书笔记]Java编程思想第8章之多态

《Java编程思想》读书笔记之第7章-复用类

[读书笔记]Java编程思想第4章之控制执行流程

[读书笔记]Java编程思想第3章之操作符

[读书笔记]Java编程思想第5章之初始化与清理