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编译期常量
满足三点要求的变量可以成为编译期常量
- 基本数据类型 有初始值
- static修饰 只有一份
- 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的。