第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类的一个子集的概念

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

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

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

1.2.2.protected关键字

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

1.2.3.父子类的初始化

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

1.2.4.super关键字

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

1.2.5.如果在父子类存在的情况下保证正确的清理顺序

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

1.2.6.重载overload与重写(覆盖)override
  • overload指的是类中方法名相同,但是方法的参数列表不同(参数的类型,个数,顺序),用来将同名方法区分开。
  • override指的是类中方法名和参数列表都相同,并且可以加上@override注解,表明子类对父类方法的修改。

overload 如果发生在子类想要对父类的方法上

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.加在属性上

如果是基本数据类型,那么该变量的值就不会再变动了
如果是引用数据类型(包括数组),指的是该变量所指向的内存地址不会再变动,但是该对象内部属性还是可变的。

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.2.1.final和private关键字

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

2.3.加在类上

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

你可能感兴趣的:(第7章-复用类)