第7章-复用类

Think in java 读书笔记

pikzas

2019.03.06

第七章 复用类

知识点

1.JAVA中类复用的几种方式

  • 组合
  • 继承
  • 代理

1.1.组合 将类A作为类B的属性 表达的是B中有A这么一个概念

例如汽车Car有引擎Engine这样的关系

class Car{
    private Engine engine;
}
1.1.1.组合带来的初始化问题

前面提过初始化的几个步骤,这里引入了组合
基本数据类型会在加载完成之后置空(0或者0.0或者false等)
引用数据类型会在加载完成之后置为null

引用类型初始化的四种方式

  • 定义对象的地方 (此种方式能够在构造器被调用之前就执行完成)
class Demo{
    private int i = 21;
}

  • 类的构造器中
class Demo{
    private int i;
    public Demo(){
        i = 22;
    }
}
  • 正要使用这些对象之前(也成为惰性初始化)有些对象没必要在创建对象的时候就初始化,或者是消耗太多资源的情况下。
class Demo{
    private SomeThingBig someThingBig;
    public void method(){
        if(someThingBig==null){
            someThingBig = new SomeThingBig();
            //一些消耗资源的初始化操作
        }
    }
}

  • 实例初始化
class Demo{
    private int x;
    {
        x = 123;
    }
}

1.2.继承 将类A作为类B的子类 表达的是类A是B类的一个子集的概念 所有类都是默认继承自Object类,所以Object里的方法都能用。

例如红色Red是颜色Color的一种

class Red extends Color{
    
}
1.2.1.继承的一些说明 子类对于父类的属性和方法的访问权限遵循包类访问权限控制

所以推荐将父类的属性设置为private,仅仅是父类自己可以访问。
将方法设置为public,那样所有人都可以访问

1.2.2.protected关键字

前一章提到过,如果出现了一个属性或者方法,除了给自己基类用之外,还想给子类访问,那么就是设置成protected,注意protected同时兼具默认包访问权限。

1.2.3.父子类的初始化

子类对象中其实包含有父类的对象
编译器会自动在子类的构造器中调用父类的构造器(针对的是无参构造器)
如果想要调用有参构造器或者是父类仅仅有有参构造器,那么在子类构造器中必须手动调用父类的构造方法。

public class A {
}
public class B extends A {
    public B(){
        System.out.print("B");
    }
}

public class C extends B {
    public C(int i){
        System.out.print("C");
    }
}

public class D extends C {
    public D(int i) {
//        System.out.println("D"); // 打印语句不能放在这里,应为对象D还未初始化完成,会提示编译错误。
        super(i);
        System.out.print("D");
    }

    public static void main(String[] args) {
        A t = new D(1);
    }
}
// 输出 BCD

1.2.4.super关键字

子类中如果想要获取父类的引用,可用super来实现。

class Parent{
    public void f(){
        print("parent");
    }
}

class Son extends Parent{
    public void f(){
        print("son");
        super.f(); // 如果不用super来表明我要调用父类,该方法会调用自己,从而死循环
    }
    public static void main(String[] args){
        Son s = new Son();
        s.f();
    }
}
1.2.5.如果在父子类存在的情况下保证正确的清理顺序

执行类的清理顺序的时候,顺序要求与生成顺序相反

1.2.6.重载overload与重写(覆盖)override
  • overload指的是同一个类中方法名相同,但是方法的参数列表不同(参数的类型,个数,顺序),用来将同名方法区分开。
  • override指的是子类中方法名和参数列表与父类都相同,并且可以加上@override注解,表明子类对父类方法的修改。
组合与继承的选择,组合表达的是xxx has a yyy的意思,继承表达的是xxx is a yyy的意思
1.2.7.向上转型

由于子类与父类在继承图上,父类位于上部,子类位于下部。方法的入参或者返回值为一个基类,那么我们可以传入或者返回一个该基类的子类。

1.2.8.继承与初始化

类的代码在初次使用的时候才会加载,通常指创建类的第一个对象的时候或者是访问类中static的属性或者方法的时候(构造方法是隐式static的)

1.2.9.在继承结构中类初始化过程
package com.pikzas.thinkinjava.chapterseven;

public class Insect {
    private int i = 9;
    protected int j;
    Insect(){
        System.out.println("i = " + i +" , j = " + j);
        j = 47;
    }
    private static int x1 = printInit("static Insect x1 init");
    static int printInit(String s){
        System.out.println(s);
        return 99;
    }
}



public class Bettle extends Insect {
    private int k = printInit("None Static field init");
    public Bettle(){
        System.out.println("k = " + k );
        System.out.println("j = " + j );
    }
    private static int x2 = printInit("static Bettle x2 init");

    public static void main(String[] args) {
        System.out.println("Bettle main method");
        Bettle b = new Bettle();
    }
}

运行bettle 中main的结果是:

static Insect x1 init
static Bettele x2 init
Bettle main method
i = 9 , j = 0
None Static field init
k = 99
j = 47

分析加载过程

  • 第一步,要调用Bettle中main方法,导致Bettle.class被加载。
  • 第二步,发现Bettle继承自父类Insect.class被加载。
  • 第三步,先将最上层的类Insect中的static属性和方法在内存中初始化,非static的属性会置为默认值(打印static Insect x1 init)
  • 第四步,然后由上而下将类中的static属性和方法初始化非static的属性会置为默认值(打印static Bettele x2 init)
  • 第五步,调用Bettle的main方法(打印Bettle main method)
  • 第六步,发现调用Bettle的构造方法,此时会触发默认的父类Insert的构造方法
  • 第七步,想要调用Insect的构造方法,就需要将Insect类中所有成员初始化完成,然后才会调用构造方法(打印i = 9 , j = 0)
  • 第八步,由上往下调用构造器,初始化所有成员变量(打印None Static field init)
  • 第九步,调用最外层的构造器Bettle()(打印k = 99 j = 47)

1.3.代理 调用类A的方法时,B作为A的一个属性,将其委托给B去执行

例如发动汽车Car,实际执行者是Engine

class Car{
    private Engine engine;
    
    public void start(){
        engine.start();
    }
}

2.final关键字 想表达的意识是无法改变的

  • 作用在属性上
  • 作用在方法上
  • 作用在class上

2.1.加在属性上

如果是基本数据类型,如果在编译的时候就给定了值,并且是static的,那么他就是编译期常量,如果对象初始化才开始给定值,一旦初始化就不会再变化。
如果是引用数据类型(包括数组),指的是该变量所指向的内存地址不会再变动,但是该对象内部属性还是可变的。

2.1.1编译期常量

满足三点要求的变量可以成为编译期常量

  1. 基本数据类型 有初始值
  2. static修饰 只有一份
  3. final修饰 是个常量
    常用大写字母加下划线特别表明
2.1.2.空白final

java允许我们在声明一个变量为final的前提下但是不给定初始值,但是在使用前,必须要指定值,这样增加了灵活性,也就必然要使用到构造器初始化。

class Demo{
    private int i;
    public Demo(){
        i = 1;
    }
    public Demo(int x){
        i = x;
    }
}

2.2.加在方法参数上

准确的说是加在方法上的参数列表的修饰符上,表明这个对象不能在方法中变动。否则会报错。

class Demo{
    public void method(final int i){
        i = 123; // 这里尝试修改i的值,会报错。
    }
}

2.3.加在方法

加在方法上的表明该方法不能被重写

class Demo{
    public final void method(int i){
        i = 123;
    }
}

class Son extends Demo{
    // 会提示编译器错误 因为demo中method为final 这里不能再重写
    
    public void method(int i){

    }
}

看另一个例子

class Parent{
    // private 隐式包含了final的意思 但是也是可以同时写出来的
    private final void f(int x){}
}

class Son extends Parent{
    //这时候如果写了Override,此时会报编译错误,因为此时并不是子类对父类的f()做了重写,因为Parent中f方法为private的,重写指的是对父类接口的重新实现,而父类方法是一个private的私有方法,不是一个对外的接口,外部调用不到,更不存在需要复写的理由了。
    @Override
    private final void f(int y){}
}
2.3.1.final和private关键字

private修饰的方法隐式的包含了final的意思

2.4.加在类上

表示该类不能被继承,其中的方法也不能被覆盖(都不能继承,咋覆盖),其中的属性依据需要,决定是否是final的。

posted @ 2019-07-25 23:15  pikzas  阅读(175)  评论(0编辑  收藏  举报