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

Posted 二木成林

tags:

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

第7章 复用类

什么是复用类:不必重复写以前写过的类功能,使代码可以重复利用,而不是每次从头开始写。

复用方法:

  • 组合:在新的类中产生现有类的对象,由于新的类是由现有类的对象所组成,所以称之为组合。该方法复用了现有程序代码的功能,而非它的形式。
  • 继承:按照现有类的类型来创建新类。无需改变现有类的形式,采用现有类的形式并在其中添加新代码。

7.1 组合语法

组合,即是在新类中实例化对象即可。

例如(本例目的在于演示组合语法的基本使用):

package 第7章_复用类.第1节_组合语法;

public class SprinklerSystem {
    private String value1, value2, value3, value4;
    private WaterSource source = new WaterSource();
    private int i;
    private float f;

    @Override
    public String toString() {
        return "value1 = " + value1 + " " + "value2 = " + value2 + " " + "value3 = " + value3 + " " + "value3 = " + value4 + "\\n" + "i = " + i + " " + "f = " + f + " " + "source = " + source;
    }

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

class WaterSource {
    private String s;

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

    @Override
    public String toString() {
        return s;
    }
}
/**
 * 打印结果:
 * WaterSource()
 * value1 = null value2 = null value3 = null value3 = null
 * i = 0 f = 0.0 source = Constructed
 */

代码解释:

  • toString()方法是每一个非基本类型的对象都有的方法,该方法的用处是当编译器需要一个String而只有一个对象时,该方法便会被调用。
  • 类中变量为基本类型时能够自动被初始化为零;对象引用(如String变量)被初始化为null。

可以在代码中的下列位置初始化这些引用:

  • 1.在定义对象的地方。这意味着它们总是能够在构造器被调用之前被初始化。
  • 2.在类的构造器中。
  • 3.就在正要使用这些对象之前,这种方式称为惰性初始化。在生成对象不值得及不必每次都生成对象的情况下,这种方式可以减少额外的负担。
  • 4.使用实例初始化。

本例目的在于演示可以在哪些位置初始化引用

package 第7章_复用类.第1节_组合语法;

import static net.mindview.util.Print.print;

public class Bath {
    private String s1 = "Happy", s2 = "Happy", s3, s4;// 在定义对象的地方就初始化
    private Soap castille;
    private int i;
    private float toy;

    // 在类的构造器中初始化引用
    public Bath() {
        print("Inside Bath()");
        s3 = "Joy";
        toy = 3.14f;
        castille = new Soap();
    }

    // 使用实例初始化
    {
        i = 47;
    }

    @Override
    public String toString() {
        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();
        print(b);
    }
}

class Soap {
    private String s;

    Soap() {
        print("Soap()");
        s = "Constructed";// 在类的构造器中初始化引用
    }

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

/**
 * 打印结果:
 * Inside Bath()
 * Soap()
 * s1 = Happy
 * s2 = Happy
 * s3 = Joy
 * s4 = Joy
 * i = 47
 * toy = 3.14
 * castille = Constructed
 */

7.2 继承语法

只要是创建一个类,那么就总是在继承,因为除非明确指出要从其他类中继承,那么就是隐式地从Java的标准根类Object进行继承。

继承类语法,通过extends关键字:

class Child extends Father {

}

当继承基类(也就是父类)后,那么会自动得到基类(父类)中的所有变量和方法。

本例演示继承的基本使用

package 第7章_复用类.第2节_继承语法;

import static net.mindview.util.Print.print;

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();
        print(x);
    }
}

public class Detergent extends Cleanser {
    // 重写scrub()方法
    public void scrub() {
        append(" Detergent.scrub()");
        super.scrub();// 调用基类的scrub()方法
    }

    // 在子类中添加独有的方法
    public void foam() {
        append(" foam()");
    }

    public static void main(String[] args) {
        Detergent x = new Detergent();
        x.dilute();
        x.apply();
        x.scrub();
        x.foam();
        print(x);
        print("Testing base class: ");
        Cleanser.main(args);// 调用基类中的main()方法
    }
}
/**
 * 打印结果:
 * Cleanser dilute() apply() Detergent.scrub() scrub() foam()
 * Testing base class:
 * Cleanser dilute() apply() scrub()
 */

代码解释:

  • 父类(基类)中的所有方法必须是public的,如果没有添加任何权限修饰词,那么成员默认的访问权限是包访问权限,它仅允许包内的成员访问。因此在包中,如果没有访问权限修饰词,那么任何人都可以使用这些方法。
  • 所以,一般情况下,将所有的变量指定为private,而将所有的方法指定为public。
  • 可以在子类中对继承的方法进行修改。
  • 如果想要调用父类(基类)中的方法,一定要使用super关键字,否则会造成递归的。
  • 也可以在子类中添加父类中没有的新方法。

7.2.1 初始化基类

对父类(基类)的初始化是在子类(导出类)中执行的,即是在子类构造器中调用基类构造器来执行初始化。

本例目的在于演示如何在子类中初始化父类

package 第7章_复用类.第2节_继承语法.第1目_初始化基类;

import static net.mindview.util.Print.print;

class Art {
    Art() {
        print("Art constructor");
    }
}

class Drawing extends Art {
    Drawing() {
        print("Drawing constructor");
    }
}

public class Cartoon extends Drawing {
    public Cartoon() {
        print("Cartoon constructor");
    }

    public static void main(String[] args) {
        Cartoon x = new Cartoon();
    }
}
/**
 * 打印结果:
 * Art constructor
 * Drawing constructor
 * Cartoon constructor
 */

代码解释:

  • 构建过程是从父类“向外”扩散的,所以父类在子类构造器可以访问它之前,就已经完成初始化了。即从父类逐步到继承它的子类。
  • 如果不为子类创建构造器,那么编译器也会合成一个默认的构造器,该默认构造器也会调用父类的构造器。

本例目的在于演示带参数的构造器

package 第7章_复用类.第2节_继承语法.第1目_初始化基类;

import static net.mindview.util.Print.print;

class Game {
    Game(int i) {
        print("Game constructor");
    }
}

class BoardGame extends Game {
    BoardGame(int i) {
        super(i);// 调用父类Game的构造器方法,并传入参数i
        print("BoardGame constructor");
    }
}

public class Chess extends BoardGame {
    Chess() {
        super(11);// 调用父类BoardGame的构造器方法,并传入参数i
        print("Chess constructor");
    }

    public static void main(String[] args) {
        Chess x = new Chess();
    }
}
/**
 * 打印结果:
 * Game constructor
 * BoardGame constructor
 * Chess constructor
 */

代码解释:

  • 在子类中必须调用父类的构造器并传入参数,否则编译器会报错提示。

7.3 代理

代理,是继承与组合的中庸之道,Java中没有提供直接支持。即是将一个成员对象置于所要构造的类中(就像组合),但与此同时在新类中暴露该成员对象的所有方法(就像继承)。

例如,太空船需要一个控制模块,里面有些控制太空船的方法:

package 第7章_复用类.第3节_代理;

public class SpaceShipControls {
    void up(int velocity){}
    void down(int velocity){}
    void left(int velocity){}
    void right(int velocity){}
    void forward(int velocity){}
    void back(int velocity){}
    void turboBoost(){}
}

那么构造太空船的其中一种方式就是继承:

package 第7章_复用类.第3节_代理;

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

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

    public static void main(String[] args) {
        SpaceShip protector=new SpaceShip("NSEA Protector");
        protector.forward(100);
    }
}

但是,SpaceShip虽然包含了SpaceShipControls,而SpaceShipControls的所有方法(如up、down、left、right、forward等)都会暴露出来。

代理的作用就是解决这个难题,解决类中方法会被暴露出来的问题。

本例目的在于演示代理的使用

package 第7章_复用类.第3节_代理;

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

    public SpaceShipDelegation(String name) {
        this.name = name;
    }

    public void back(int velocity) {
        controls.back(velocity);
    }

    public void down(int velocity) {
        controls.down(velocity);
    }

    public void forward(int velocity) {
        controls.forward(velocity);
    }

    public void left(int velocity) {
        controls.left(velocity);
    }

    public void right(int velocity) {
        controls.right(velocity);
    }

    public void turboBoost() {
        controls.turboBoost();
    }

    public void up(int velocity) {
        controls.up(velocity);
    }

    public static void main(String[] args) {
        SpaceShipDelegation protector = new SpaceShipDelegation("NSEA Protector");
        protector.forward(100);
    }
}

代理使得我们可以有更多的控制力,可以选择只提供在成员对象中的方法的某个子集。

7.4 结合使用组合和继承

同时使用组合和继承。

package 第7章_复用类.第4节_结合使用组合和继承;

class Plate {
    Plate(int i) {
        System.out.println("Plate constructor");
    }
}

class DinnerPlate extends Plate {
    DinnerPlate(int i) {
        super(i);
        System.out.println("DinnerPlate constructor");
    }
}

class Utensil {
    Utensil(int i) {
        System.out.println("Utensil constructor");
    }
}

class Spoon extends Utensil {
    Spoon(int i) {
        super(i);
        System.out.println("Spoon constructor");
    }
}

class Fork extends Utensil {
    Fork(int i) {
        super(i);
        System.out.println("Fork constructor");
    }
}

class Knife extends Utensil {
    Knife(int i) {
        super(i);
        System.out.println("Knife constructor");
    }
}

class Custom {
    Custom(int i) {
        System.out.println("Custom constructor");
    }
}

public class PlaceSetting extends Custom {
    private Spoon sp;
    private Fork frk;
    private Knife kn;
    private DinnerPlate pl;

    PlaceSetting(int i) {
        super(i + 1);
        sp = new Spoon(i + 2);
        frk = new Fork(i + 3);
        kn = new Knife(i + 4);
        pl = new DinnerPlate(i + 5);
        System.out.println("PlaceSetting constructor");
    }

    public static void main(String[] args) {
        PlaceSetting x = new PlaceSetting(9);
    }
}
/**
 * 打印结果:
 * Custom constructor
 * Utensil constructor
 * Spoon constructor
 * Utensil constructor
 * Fork constructor
 * Utensil constructor
 * Knife constructor
 * Plate constructor
 * DinnerPlate constructor
 * PlaceSetting constructor
 */

编译器强制要求你去初始化基类,并且要求在构造器起始处就要这么做。但并不强制你必须将成员对象也初始化。

7.4.1 确保正确清理

 

7.4.2

 

7.5 在组合与继承之间选择

组合和继承都允许在新的类中放置子对象,组合是显式使用,而继承是隐式使用。

  • 组合技术:用于想在新类中使用现有类的功能而非它的接口。即在新类中嵌入某个对象,让其实现所需要的功能。需要在新类中嵌入一个现有类的private对象。如果允许类的用户直接访问新类中的组合成分,即将成员对象声明为public。

本例目的在于演示组合的使用

package 第7章_复用类.第5节_在组合与继承之间选择;

class Engine {
    public void start() {
    }

    public void rev() {
    }

    public void stop() {
    }
}

class Wheel {
    public void inflate(int psi) {
    }
}

class Window {
    public void rollup() {
    }

    public void rolldown() {
    }
}

class Door {
    public Window window = new Window();

    public void open() {
    }

    public void close() {
    }
}

public class Car {
    public Engine engine = new Engine();
    public Wheel[] wheel = new Wheel[4];
    public Door left = new Door();
    public Door right = new Door();

    public Car() {
        for (int i = 0; i < 4; i++) {
            wheel[i] = new Wheel();
        }
    }

    public static void main(String[] args) {
        Car car = new Car();
        car.left.window.rollup();
        car.wheel[0].inflate(72);
    }
}
  • 继承技术:继承中,使用某个现有类,并开发一个它的特殊版本。例如一个形状Shape类,这是一个通用类,但可以进行特殊化,如正方形Square类、圆Circle类。

7.6 protected关键字

protected关键字对类用户是private的,但对于任何继承于此类的导出类或其他任何处于同一个包内的类来说,它是可以访问的。

对于变量最好的处理还是private,通过protected来控制类的继承者的访问权限。

package 第7章_复用类.第6节_protected关键字;

class Villain {
    private String name;

    protected void set(String nm) {
        name = nm;
    }

    public Villain(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "I'm a Villain and my name is " + name;
    }
}

public class Orc extends Villain {
    private int orcNumber;

    public Orc(String name, int orcNumber) {
        super(name);
        this.orcNumber = orcNumber;
    }

    public void change(String name, int orcNumber) {
        set(name);// 可以访问set()方法,因为它的权限是protected
        this.orcNumber = orcNumber;
    }

    @Override
    public String toString() {
        return "Orc " + orcNumber + ": " + super.toString();
    }

    public static void main(String[] args) {
        Orc orc = new Orc("Limburger", 12);
        System.out.println(orc);
        orc.change("Bob", 19);
        System.out.println(orc);
    }
}
/**
 * 打印结果:
 * Orc 12: I'm a Villain and my name is Limburger
 * Orc 19: I'm a Villain and my name is Bob
 */

代码解释:

  • change()方法中是可以访问set()的,因为它是protected的。

7.7 向上转型

本例目的在于演示向上转型

package 第7章_复用类.第7节_向上转型;

class Instrument {
    public void play() {
    }

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

public class Wind extends Instrument {
    public static void main(String[] args) {
        Wind fulte = new Wind();
        Instrument.tune(fulte);
    }
}

代码解释:

  • 可以看到在Instrument对象的tune方法中竟然可以传入Wind对象作为参数。

向上转型就是将子类引用转换为父类引用的动作。例如上面的Wind转换为Instrument引用。

到底是该用组合还是用继承,一个最清晰的判断办法就是问一问自己是否需要从新类向基类进行向上转型。如果必须向上转型,则继承是必要的,但如果不需要,则应当好好考虑自己是否需要继承。

7.8 final关键字

final关键字通常指“这是无法改变的”。

下面讨论了final使用的三种情况:数据、方法和类。

7.8.1 final数据

一般final数据用于一个永不改变或在运行时被初始化的常量。

在Java中这种常量是基本类型,final使其数值不变。在对这个常量定义的时候,必须对其进行赋值。一个既是static又是final的常量占据一段不能改变的存储空间。

当对对象引用而不是基本类型运用final时。对于基本类型,final使数值恒定不变,而用于对象引用,final使引用恒定不变。一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象。然而,对象其自身却是可以被修改的,Java并未提供使任何对象恒定不变的途径(但可以自己编写类以取得使对象恒定不变的效果)。这一限制同样适用数组,它也是对象。

注意:根据惯例,既是static又是final的常量将用大写字母表示,并使用下划线分隔各个单词。

本例目的在于演示常用final数据的使用

package 第7章_复用类.第8节_final关键字.第1目_final数据;

import java.util.Random;

class Value {
    int i;

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

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

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

    private final int valueOne = 9;
    private static final int VALUE_TWO = 99;

    public static final int VALUE_THREE = 39;

    private final int i4 = rand.nextInt(20);
    static final int INT_5 = rand.nextInt(20);
    private Value v1 = new Value(11);
    private final Value v2 = new Value(22);
    private static final Value VAL_3 = new Value(33);

    // 数组
    private final int[] a = {1, 2, 3, 4, 5, 6};

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

    public static void main(String[] args) {
        FinalData fd1 = new FinalData("fd1");
        // fd1.valueOne++;// 不能改变值,因为是一个常量
        fd1.v2.i++;
        fd1.v1 = new Value(9);// 不是final
        for (int i = 0; i < fd1.a.length; i++) {
            fd1.a[i]++; // Object isn't constant!
        }

        // fd1.v2=new Value(0);// 不能
        // fd1.VAL_3=new Value(1);//
        // fd1.a=new int[3];

        System.out.println(fd1);
        System.out.println("Creating new FinalData");
        FinalData fd2 = new FinalData("fd2");
        System.out.println(fd1);
        System.out.println(fd2);
    }
}
/**
 * 打印结果:
 * fd1: i4 = 15, INT_5 = 18
 * Creating new FinalData
 * fd1: i4 = 15, INT_5 = 18
 * fd2: i4 = 13, INT_5 = 18
 */

代码解释:

由于valuOne和VAL_TWO都是带有编译时数值的final基本类型,所以它们二者均可以用作编译期常量,并且没有重大区别。VAL_THREE是一种更加典型的对常量进行定义的方式:定义为public,则可以被用于包之外;定义为static,则强调只有一份﹔定义为final,则说明它是一个常量。请注意,带有恒定初始值(即,编译期常量)的final static基本类型全用大写字母命名,并且字与字之间用下划线隔开(这就像C常量一样,C常量是这一命名传统的发源地)。
我们不能因为某数据是final的就认为在编译时可以知道它的值。在运行时使用随机生成的数值来初始化4和INT_5就说明了这一点。示例部分也展示了将final数值定义为静态和非静态的区别。此区别只有当数值在运行时内被初始化时才会显现,这是因为编译器对编译时数值一视同仁〈并且它们可能因优化而消失)。当运行程序时就会看到这个区别。请注意,在fd1和fd2中,i4的值是唯一的,但INT_5的值是不可以通过创建第二个FinalData对象而加以改变的。这是因为它是static的,在装载时已被初始化,而不是每次创建新对象时都初始化。
v1到VAL_3这些变量说明了final引用的意义。正如在main()中所看到的,不能因为v2是final的,就认为无法改变它的值。由于它是一个引用,final意味着无法将v2再次指向另一个新的对象。这对数组具有同样的意义,数组只不过是另一种引用(我还不知道有什么办法能使数组引用本身成为final)。看起来,使引用成为final没有使基本类型成为final的用处大。

空白final

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

本例目的在于演示final数据必须在定义处或构造器中初始化

package 第7章_复用类.第8节_final关键字.第1目_final数据;

class Poppet {
    private int i;

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

public class BlankFinal {
    private final int i = 0;// 初始化的final数据
    private final int j;// 空final
    private final Poppet p;// 空final对象

    // 空final必须被初始化在构造器中
    public BlankFinal() {
        j = 1;// 初始化空final
        p = new Poppet(1);// 初始化空final对象
    }

    public BlankFinal(int x) {
        j = x;// 初始化空final
        p = new Poppet(x);// 初始化空final对象
    }

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

注意:必须在变量的定义处或每个构造器中用表达式对final进行赋值,这也是final变量在使用前总是被初始化的原因所在。

final参数

Java允许在参数列表中以声明的方式将参数指明为final。这意味着你无法在方法中更改参数引用所指向的对象:

本例目的在于演示final参数只能被读而不允许被修改

package 第7章_复用类.第8节_final关键字.第1目_final数据;

class Gizmo {
    public void spin() {
    }
}

public class FinalArguments {
    void with(final Gizmo g) {
        // 编译器显式:Cannot assign a value to final variable 'g'
        // g=new Gizmo();// 违法的,因为g是final的,不能重新初始化
    }

    void without(Gizmo g) {
        g = new Gizmo();// 合法的,因为g不是final的
        g.spin();
    }

    void f(final int i) {
        // 编译器显式:Cannot assign a value to final variable 'i'
        // i++;// 不能改变值
    }

    // 因此你只能读一个final参数,而不是修改或者初始化它
    int g(final int i) {
        return i + 1;
    }

    public static void main(String[] args) {
        FinalArguments bf = new FinalArguments();
        bf.without(null);
        bf.without(null);
    }
}

代码解释:

  • 这个final参数只能读不能修改的特性,主要用来向匿名内部类传递数据。

7.8.2 final方法

使用final方法的原因有两个:第一个是把方法锁定,以防任何继承类修改它的含义。第二个是效率。

类中所有的private方法都隐式地指定为是final的。由于无法取用private方法,所以也就无法覆盖它。可以对private方法添加final修饰词,但这并不能给该方法增加任何额外的意义。

这一问题会造成混淆。因为,如果你试图覆盖一个private方法(隐含是final的),似乎是奏效的,而且编译器也不会给出错误信息:

本例目的在于演示final方法不能被修改,private隐式指定为final,所以也无法覆盖

package 第7章_复用类.第8节_final关键字.第2目_final方法;

class WithFinals {
    // 单独标志该方法final
    private final void f() {
        System.out.println("WithFinals.f()");
    }

    // 自动隐式指定为final
    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("OverridingPrivate2.g()");
    }
}

public class FinalOverridingIllusion {
    public static void main(String[] args) {
        OverridingPrivate2 op2 = new OverridingPrivate2();
        op2.f();
        op2.g();

        // 能够向上转型
        OverridingPrivate op = op2;
        // 但不能调用方法
        // op.f();
        // op.g();

        // 一样的情况,能够向上转型
        WithFinals wf = op2;
        // 但不能调用方法
        // wf.f();
        // wf.g();
    }
}
/**
 * 打印结果:
 * OverridingPrivate2.f()
 * OverridingPrivate2.g()
 */

7.8.3 final类

当将类定义设置为final时,那么其他类无法继承它,该类也不需要任何变动。

本例目的在于演示final类无法被继承

package 第7章_复用类.第8节_final关键字.第3目_final类;

class SmallBrain {
}

final class Dinosaur {
    int i = 7;
    int j = 1;
    SmallBrain x = new SmallBrain();

    void f() {
    }
}

// 无法继承final类
// 编译器显式:annot inherit from final 'Dinosaur'
// class Further extends Dinosaur{}

public class Jurassic {
    public static void main(String[] args) {
        Dinosaur n = new Dinosaur();
        n.f();
        n.i = 40;// 可以修改final类中的没有被final修饰的变量
        n.j++;
    }
}

代码解释:

  • final类无法被继承,但可以被使用。
  • final类中定义的变量可以根据程序员个人意愿选择是否final修饰。无论类是否被定义为final,类中的变量都可以自由选择是否final修饰,如果final修饰就无法修改,如果无final修饰,则可以修改变量的值。
  • 由于final类禁止继承,所以final类中所有方法都是隐式指定为final,无法覆盖。
  • 可以给final类中的方法添加final修饰,但没有任何意义。

7.9 初始化及类的加载

初次使用之处也是static初始化发生之处。所有的static对象和static代码段都会在加载时依程序中的顺序(即,定义类时的书写顺序)而依次初始化。当然,定义为static的东西只会被初始化一次。

7.9.1 继承与初始化

本例目的在于演示包括继承在内的初始化过程

package 第7章_复用类.第9节_初始化及类的加载.第1目_继承与初始化;

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);
        System.out.println("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();
    }
}
/**
 * 打印结果:
 * static Insect.x1 initialized
 * static Beetle.x2 initialized
 * Beetle constructor
 * i = 9, j = 0
 * Beetle.k initialized
 * k = 47
 * j = 39
 */

在Beetle上运行Java时,所发生的第一件事情就是试图访问Beetle.main( (一个static方法),

于是加载器开始启动并找出Beetle类的编译代码(在名为Beetle.class的文件之中)。在对它进行加载的过程中,编译器注意到它有一个基类(这是由关键字extends得知的),于是它继续进行加载。不管你是否打算产生一个该基类的对象,这都要发生(请尝试将对象创建代码注释掉,以证明这一点)。

如果该基类还有其自身的基类,那么第二个基类就会被加载,如此类推。接下来,根基类中的static初始化(在此例中为Insect)即会被执行,然后是下一个导出类,以此类推。这种方式很重要,因为导出类的static初始化可能会依赖于基类成员能否被正确初始化。

至此为止,必要的类都已加载完毕,对象就可以被创建了。首先,对象中所有的基本类型都会被设为默认值,对象引用被设为null─这是通过将对象内存设为二进制零值而一举生成的。然后,基类的构造器会被调用。在本例中,它是被自动调用的。但也可以用super来指定对基类构造器的调用(正如在Beetle()构造器中的第-一-步操作)。基类构造器和导出类的构造器一样,以相同的顺序来经历相同的过程。在基类构造器完成之后,实例变量按其次序被初始化。最后,构造器的其余部分被执行。

以上是关于《Java编程思想》读书笔记之第7章-复用类的主要内容,如果未能解决你的问题,请参考以下文章

《Java编程思想》读书笔记之第8章-多态

[读书笔记]Java编程思想

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

<java编程思想>第一章读书笔记二

《Java编程思想》阅读笔记之第4章-控制执行流程

《Java编程思想》阅读笔记之第11章-持有对象