[Thinking in Java]第7章-复用类

7.1 组合语法
7.2 继承语法
7.3 代理
7.4 重载和覆盖
7.5 初始化以及类的加载

目录


7.1 组合语法

组合就是把对象放在新类中,做新类的成员,比如

class A {
}

class B {
    private String value1, value2, value3;
    private A a = new A();
    private int integer;
    private double d;
}

如上所示,类A对象作为类B的一个成为,此外,类B还具有类String3个对象,又有基本数据类型integer和d。

如果想要初始化类成为,可以在代码中的4种位置进行

1.在定义对象的地方;

2.在类的构造器中;

3.使用实例初始化,也就是初始化块

4.正要使用这个对象之前,这种方式也称作惰性初始化,可以减少额外的负担。

class A{
}

class B {
    // 1.在定义对象的地方
    private String name = new String("小明");
    private String sex;//未初始化
    private int id;// 未初始化
    private A a;// 未初始化

    {//4.使用实例初始化
        id = 13;
    }

    public B() {//2.在构造器中初始化
        a = new A();
    }

    public void f() {
        //4.正要使用这个对象时初始化
        if (sex == null)
            sex = "boy";
        System.out.println("I am a " + sex);
    }
}

7.2 继承语法

当创建一个类时,总是在继承,因此,除非已明确指出要从其他类中继承,否则总是在隐式地从Java的标准根类Object进行继承。

class A {
    private int a;
    private double b;
    private String name;

    public void f1() {}
    public void f2() {}
}

class B extends A {
    private int c;

    public void f3() {}
}

如上代码所示,使用关键字extends,类B继承了类A,获得了父类所有的属性和方法,包括父类的private属性

class Person {
    public String toString() {
        return "I am a person";
    }
}

class Father extends Person {
    private String name = "小明";

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

    public String toString() {
        return "I am a father";
    }

    public String getName() {
        return name;
    }
}

class Son extends Father {
    public Son(String name) {
        super(name);
    }

    public String toString() {
        return "I am a son";
    }
}

public class PersonTest {
    public static void main(String[] args) {
        Father father = new Father("小天");
        Son son = new Son("小强");

        father.say("My name is" + father.getName());
        son.say("I am " + son.getName());
    }
}

如上代码所示,类Father定义了private修饰的属性name,被类Son继承并在构造器中初始化了;同时类Father定义了访问器getName(),因为是用public修饰的,所以类Son的对象可以直接使用;我们还可以注意到,类Person定义了公共方法toString(),实际上根类Object也定义了toString(),但类Person的toString()是重载了,因为方法签名不一样,类Father也定义了相同方法签名的toString(),这是覆盖类Person的toString(),同理类Son的toString()覆盖了类Father的toString()。

注意到类Son的构造器这一句 super(name); 如果注释掉这一句,或者改为super();编译器只能报错,这是因为继承机制要求要初始化父类:

实际上,继承并不只是复制父类的接口,当创建了一个子类的对象时,该对象包含了一个父类的子对象,这个子对象与你用父类直接创建对象是一样,只不过后者来自于外部,而父类的子对象被包装在子类对象的内部。对父类子对象的正确初始化也是至关重要的,而且也仅仅有一种方法,那就是早构造器中调用父类的构造器来执行初始化

class Art {
    Art() {System.out.print("Art 构造器");}
}

class Drawing extends Art {
    Drawing() {System.out.print("Drawing 构造器");}
}

class Cartoon extends Drawing {
    Cartoon() {System.out.print("Cartoon 构造器");}
}

如上代码所示,当初始化Cartoon时,发现有父类Drawing,于是编译器会默认在构造器的第一句添加一句super();再先初始化父类Drawing,又发现有父类Art,于是会默认在构造器的第一句添加一句super();再初始化父类Art,对于根类Object就不说了,就这样输出结果为

Art 构造器Drawing 构造器Cartoon 构造器

那构造器的第一行的super(初始化父类的super本来就必须是第一行)能否带有参数呢?当然可以,像上面的例子,类Son的构造器中super(name)就是带有参数的,但若去掉参数name,或者干脆不写,编译器就抱怨无法初始化了,因为父类Father不存在无参构造器。如果遇到下面这样的例子呢

class A {
    A() {
        System.out.println("A无参");
    }
}

class B extends A {
    B() {
        System.out.println("B无参");
    }

    B(int a) {
        this();
        System.out.println("B有参");
    }
}

class C extends B {
    C() {
        super(1);
        System.out.println("C无参");
    }
}

public class Inheritance {
    public static void main(String[] args) {
        new C();
    }
}

因为这个构造方法链是从初始化对象开始,一直到根类Object结束。所以,当执行this();程序跳转到B()这个构造器,但是此时整个构造方法链条早已经完成了,自然不会super到父类A了,因此程序输出为

A无参
B无参
B有参
C无参

7.3 代理

类之间的关系有组合,继承,还有第三种关系成为代理,Java没有提供对它的直接支持,它是组合与继承的中庸之道。

因为我们将一个成员对象置于所要构造的类中(就像组合),但与此同时我们会在新类中暴露了此成员对象的所有方法(就像继承),代理解决了此难题

class A {
    f1(int i) {}
    f2(int i) {}
}

class B {
    A a = new A();

    f1(int i) {
        a.f1(i);
    }

    f2(int i) {
        a.f2(i);
    }
}

如上代码所示,在设计时,我们希望使用B的f1和f2方法,而不是直接使用A的f1和f2方法,打个比方,我们希望让一辆汽车向前开,而不是让汽车的控制引擎向前开

7.4 重载和覆盖

重载就是方法名相同,而参数列表不同,允许返回值类型不同,其中参数列表不同在于参数的个数和类型不同;

覆盖就是子类重写了父类的方法,子类的某个方法的方法名,返回值类型和参数列表必须和父类的一个方法一模一样

 1 class A {
 2     int method(int i) {
 3         return i;
 4     }
 5 }
 6
 7 public class B extends A {
 8     public int method(int i) {
 9         return ++i;
10     }
11
12     double method(double d) {
13         return d;
14     }
15
16     double method(int a, double b) {
17         return a + b;
18     }
19
20     public static void main(String[] args) {
21         B c = new B();
22         System.out.println(c.method(1));
23         System.out.println(c.method(5.6));24     System.out.println(c.method(5, 5.5));
25     }
26 }

需要注意的是,如果第8行代码的修饰符改成private,编译器就抱怨修饰符的可见性比父类的method低,因此覆盖时必须保证修饰符的可见性不低于父类的方法;第12到14行是重载了父类的method,其中返回值类型可以不同,第16到18行也是重载了method,其中参数列表的个数不同

7.5 初始化以及类的加载

每个类的编译代码都存在于它自己的独立的文件中,尽管一个java文件可以有多个类。该文件只在需要使用程序代码时才会被加载。一般来说,类的代码在初次使用时才加载。这通常是指加载发生于创建类的第一个对象之时,但是当访问static 域或static 方法时,也会发生加载。

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

下面是Java编程思想的一个例子

 1 import static java.lang.System.*;
 2
 3 class Insect {
 4     private int i = 9;
 5     protected int j;
 6     Insect() {
 7         out.println("i = " + i + ", j = " + j);
 8         j = 39;
 9     }
10
11     private static int x1 =
12         printInit("static Insect.x1 initialized");
13
14     static int printInit(String s) {
15         out.println(s);
16         return 47;
17     }
18 }
19
20 public class Beetle extends Insect {
21     private int k = printInit("Beetle.k initialized");
22     public Beetle() {
23         out.println("k = " + k);
24         out.println("j = " + j);
25     }
26
27     private static int x2 =
28         printInit("static Beetle.x2 initialized");
29
30     public static void main(String[] args) {
31         out.println("Beetle constructor");
32         Beetle b = new Beetle();
33     }
34 }

在Beetle上运行Java时,所发生的第一件事就是试图访问Beetle.main()(一个static方法),于是加载器开始启动并找出Beetle类的编译代码(在名为Beetle.class的文件中)。在对它进行加载的过程中,编译器注意到它有一个父类(由关键字extends得知),于是它继续加载,不管你是否打算创建一个该父类的对象,这都要发生。

如果该父类还有其自身的父类,那么第二个父类就会被加载,如此类推。接下来,根父类中的static初始化(在此例中为Insect)即会被执行,然后是下一个子类,以此类推。

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

正如这个例子,在试图访问Beetle.main()时,会先尝试加载Beetle类,但又发现Beetle的父类Insect,于是先加载Insect,Insect的第一个static语句是第11行,而第11行又调用第14行的printInit(String s),打印s并返回47,因此x1=47;然后再加载Beetle,第一个static语句是第27行,而第27行又调用父类的printInit(String s),打印s并返回47,因此x2=47,至此,所有的类已加载完毕;回到Beetle.main(),执行第31行的语句,然后执行第32行,创建Beetle对象,于是尝试执行第22行的构造器,但发现有父类Insect,因而先初始化父类,程序跳转到第6行,调用父类的构造器,当父类初始化完毕,再初始化子类,以此类推,才成功创建Beetle对象。

时间: 2024-10-26 15:36:44

[Thinking in Java]第7章-复用类的相关文章

[think in java]第7章 复用类

java中,复用代码有两种途径: 在新的类中产生现有类的对象.由于新的类是由现有类的对象组成的,所以这种方法称为组合. 采用继承的方式来复用. 继承 继承采用关键字extends实现. java用super关键字表述超类的意思. 初始化基类 当创建一个子类的对象时,该对象包含了一个基类的子对象.这个子对象与你用基类创建的对象是一样的.对基类子对象的正确初始化也至关重要,而且也只有一种方法来保证这一点:在构造器中调用基类的构造器执行初始化.看下面这个例子: public class Cartoon

Thinking In Java笔记(第七章 复用类)

第七章 复用类 复用代码是Java众多引人注目的功能之一,但想要成为极具革命性的语言,仅仅能够复制代码并对之加以改变是不够的,它还必须能够做更多的事情. Java中所有事物都是围绕着类来展开的.通过创建新类来复用代码,不必重新开头编写.此方法的窍门在于使用类而不破坏现有程序代码.本章中有两种代码重用机制来达到这一目的: 只需要在新的类中生成现有类的对象.由于新的类是由现有类的对象所组成的,这种方法通常成为组合.该方法只是复用了现有程序代码功能,而非形式上和之前的类有相似之处. 第二种方法更加细致

《JAVA编程思想》学习笔记——第七章 复用类

复用类的主要方式有两种:组合,继承 组合 例: class A {} class B {A a;} 继承 继承是所有OOP语言和Java语言不可缺少的组成部分.当创建一个类时,总是在继承,因此,除非已明确指出要从其它类中继承,否则就是在隐式地从Java的标准根类Object进行继承. 继承适用关键词:extends,继承会自动得到基类中所有的域和方法 super super关键字表示超类的意思,当前类就是从超类继承来的.调用超类方法:super.funcName(); 初始化基类 无参构造器,J

Java编程思想:第7章 复用类

复用代码是Java众多引人注目的功能之一.但仅仅能复制代码并对之加以改变是不够的,还需要做更多的事情. 复用代码的两种形式: 1.组合,新类中产生现有类对象 2.继承,用现有类型创建新类型 7.1组合语法 7.2继承语法 7.2.1初始化基类 当创建了一个导出类的对象时,该对象包含了一个基类的子对象.这个子对象与你用基类直接创建的对象是一样的(Java会自动在导出类构造器里插入对基类构造器的调用,基类只含带参构造需要自己用super调用,不可省略),二者区别在于继承时基类的子对象包装在导出类对象

Java编程思想(四) —— 复用类

看了老罗罗升阳的专访,情不自禁地佩服,很年轻,我之前以为和罗永浩一个级别的年龄,也是见过的不是初高中编程的一位大牛之一,专访之后,发现老罗也是一步一个脚印的人.别说什么难做,做不了,你根本就没去尝试,也没有去坚持. If you can't fly then run,if you can't run then walk, if you can't walk then crawl,but whatever you do,you have to keep moving forward--Martin

Java编程思想之七复用类

复用代码是Java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对之加以改变是不够的,它还必须做更多的事情. 使用类而不破坏程序代码: 在新类中产生现有对象.由于新的类是由现有类的对象组成,所有这种方法称为组合. 按照现有类的类型来创建新类.无需改变现有类的形式,采用现有类的形式并在其中添加新代码.这种方法是继承. 7.1 组合语法 组合只需要将对象引用置于新类中就可以了. 但编译器并不是简单的为每一个引用都创建默认对象.如果想初始化这些引用,可以在代码中下列位置进行:

《Java编程思想》笔记 第七章 复用类

1.组合 将其他类的对象引用置于新的类中. 3.继承 关键词extends  一个类继承基类后自动获得 基类的所有域(包括字段 引用 内部类 )和方法,当然不包括private,子类中调用继承下来的方法也不需要基类对象引用.继承相当于对基类的一个扩展,因为基类有的它都有,再额外添加了一些域和方法(或覆写方法)而已. 4.super 4.1 super.f() 调用基类的f()方法. 4.2 构造器中的super(args) 调用基类构造器,且只能在第一行. 4.3 由于构造器被重载后默认构造器不

JAVA代码重用机制复用类之继承语法(附源码)

前言 继承是所有OOP语言和Java语言不可缺少的组成部分.当创建一个类时,总是在继承,因此,除非已明确指出要从其他类中继承,否则就是在隐式地从Java的标准根类Object进行继承. 组合的语法比较平实,但是继承使用的是一种特殊语法.在继承过程中,需要先声明"新类与旧类相似".这种声明是通过在类主体的左边花括号之前,书写后面紧随基类名称的关键字extends而实现的.当这么做时,会自动得到基类中所有的域和方法.例如: 示例源码 基类 package com.mufeng.thesev

第七章-复用类-继承语法-2初始化基类-带参构造器

书上代码示例: 1 package com.learnJava.test; 2 3 /** 4 * @Author zhuchangli 5 * @Date 2019/9/14 6 **/ 7 class Game{ 8 Game(int i){ 9 System.out.println("Game constructor"); 10 } 11 } 12 13 class BoardGame extends Game{ 14 BoardGame(int i){ 15 super(i);