学习《Java核心技术》——第5章:继承

137 阅读10分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

文章目录

学习《Java核心技术》——第5章:继承

Java核心技术.卷I(第11版)

第4章:对象与类

1.继承

使用extends关键字

public class SubClazz extends ParentClazz{
    ...
}

覆盖(override)或重写(overwrite)

继承时期发生

子类不能直接访问父类的私有字段,因此要使用getset方法

调用父类的非私有方法,super.xxx()

子类构造器

public SubClazz(String s, int i){
    super(s,i);//调用父类同参数的构造器,对父类私有字段初始化
    c = "haha";
}

没有显式调用,则自动调用父类的无参构造器,否则报编译错误。

归纳:要么默认调用无参构造器,要么就显式调用构造器

多态(polymorphism) :一个对象变量可以指示多种实际类型的现象

比如,循环遍历一个父类对象数组,运行时虚拟机知道它的实际对象,并调用相对应的方法。

动态绑定(dynamic binding) :运行时自动选择对应合适的方法(不同重写过的方法或者说不同子类的相同方法)

好处就是,对代码扩展来讲,非常好,虚拟机会自己确定具体的方法,并且不用修改其他部分代码。

阻止继承(final类和方法):

final关键字修饰的类和方法

  • 类不可以被继承
  • 子类不能覆盖(重写)这个final方法

对于final字段,构造对象后,值不能被改变。

final类的方法自动成为final,但是字段不会。

强制类型转换:使用()

double x = 3.14;
int nx = (int)x;

子类可以转成父类,没问题;父类转子类之前应该先instanceof判断一下

a instanceof Clazz:判断a是否是Clazz类的实例

对于a是null的情况,一定返回false,因为null不引用任何对象

受保护访问,private

父类的方法只被子类访问

子类的方法访问父类的某个字段

受保护字段只能被同一个包的类访问,一般这个不归规范,不利于OOP,不用

受保护方法,被子类使用,不被其他类使用。(待测试加深印象,如Object的clone方法)

2.this和super总结

this

  • 调用该类的其他构造器,按照参数类型匹配
  • 指示隐式参数的引用,即调用此类的方法和字段

super

  • 调用父类的方法

  • 调用父类的构造器

    此时,必须是构造器的第一句

3.方法调用过程

  • 编译器查看对象的声明类型和方法名

    编译器会找出该类和其父类的所有可能被调用的方法

  • 编译器确定方法调用中的参数类型,找到对应匹配的方法

    这就是**重载解析**

  • 如果是private, static, final方法或者构造器,那么编译器就可以准确知道应该调用哪个方法。

    这就是静态绑定,就是只要在该类寻找就行,也不用涉及重载的问题。对应的是动态绑定

  • 程序运行并采用动态绑定调用方法时

    虚拟机为每个类准备了方法表(method table),调用方法时,虚拟机查表就好了。

4.fianl总结

final关键字修饰的类和方法

  • 类不可以被继承
  • 子类不能覆盖(重写)这个final方法

对于final字段,构造对象后,值不能被改变。

final类的方法自动成为final,但是字段不会。

final的用意:确保被final修饰的方法和类不会在子类中改变语义,就是被覆盖重写,保证状态唯一。

5.抽象类总结

越顶层的类往往越具有抽象性,使用abstract声明该类;

子类使用extends继承抽象类;

即使类不含抽象方法,类也可以是抽象类;

抽象类不可以被实例化;

但可以定义一个抽象类的对象变量,用这个变量去引用非抽象子类的对象,此时去调用方法就会定位到具体实现的对应方法

public abstract class Person{
    private String name;
    
    public Person(String name){
        this.name = name;
    }
    public abstract String getdescription();
}

抽象方法类似占位符,具体使用时再具体实现。抽象方法在具体子类中都要被实现。

扩展抽象类

  1. 子类也是抽象类。只实现了一部分的抽象方法
  2. 子类不是抽象类,这样就要定义抽象父类的全部方法

6.访问控制修饰符总结

  • public

    公开,对外部可见

  • protected

    对本包和所有子类可见,一般就用在方法上,字段上不提倡

  • private

    仅对本类可见

  • default

    对本包可见

那么就是三个层级:本类子类

7. Object基类

Object类型的变量可以引用任何类型的对象

在Java中,只有基本类型(primitive type)不是对象,比如数值、字符和布尔类型的值都不是对象

数组类型是对象,扩展了Object类

equals方法:(不详细,待补充)

检测两个对象是否相等,Object类实现的equal方法是比较两个对象的引用。

Object.equals(a,b)//a,b都为null时,返回ture

equals方法要具有以下特性:

  • 自反性:对于任何非空引用x,x.equals(x)应该返回ture
  • 对称性:对于任何引用x和y,x.equals(y)和y.euqals(x)返回一致
  • 传递性:对于任何引用x,y,z,如果x=y,y=z,则x=z
  • 一致性:如果x和y引用的对象没有发生变化,则x.equals(y)的结果不变
  • 对任意非空引用x,x.equals(null)返回false

hashCode方法

hash code(散列码):传入对象,返回整型值

每个对象都继承Object类,因此,每个对象默认有一个散列码,而且一般不相同

两个相等的对象的散列码也要一致。因此,如果重新定义了equals方法,hashCode方法可能也要重新定义。

null的散列码是0

toString方法

返回表示对象值的一个字符串

对象与一个字符串通过操作符“+”连接,Java编译器自动调用toString方法获得这个对象的字符串描述。即,

/**
 * 自动调用toString方法
 */
x.toString();
// ==
"" + x;

System.out.println(x);
// ==
system。呕吐。println(x.tostring());

//字符串输出
String s = Arrary.tostring(arrNum);

Class类:

Class是一个类,

Class getClass()方法,返回包含对象信息的类对象

String getName() , 返回类名

Class getSuperclass() , 以Class对象形式返回这个类的父类

8.泛型数组

Java允许运行时确定数组大小

ArrayList,数组列表,类似数组,可以自动调整数组容量。但是不能通过索引访问对象元素

ArrayList,是一个有类型参数(type parameter)的泛型类(generic class)

ArrayList<Person> p = new ArrayList<>(); //泛型为Person
p.add(new person());

add()方法的内部机制:如果调用add而内部数组已经满了,那么数组列表会创建一个更大的数组,并把所有的对象都拷贝过去。

ensureCapacity(100) ,表示可以add100次,超过后就创建一个更大的数组列表

add和set方法接受任意类型的对象,所以需要注意,在强制转换的时候就可能出现问题。

数组列表的元素拷贝到数组中:

var list = new ArrayList<X>();
//todo: add element
var a = new X[list.size()];
list.toArray(a);//拷贝成数组,便于索引访问

ArrayList插入(add)和删除(remove) ,插入和删除,都要整体移动后面的所有元素,使用链表插入和删除的效率高。

9.对象包装器和自动装箱

所有的基本类型都有一个与之对应的类,便于将基本类型转换成对象。

这些类就是包装器(wrapper) ,这些类的父类是Number类。包装器类都是final类,因此不能派生出子类。

在泛型中,<>只能使用包装器类,不能使用基本类型。

var list = new ArrayList<Integer>();

自动装箱(autoboxing) :基本类型的值封装成对应对象

list.add(3);
//自动变换成
list.add(Integer.valueOf(3));

与之对应的,自动拆箱:对象自动转换成基本类型的值

// list是一个整型数组列表
int n = list.get(i);
//自动转换成
int n = list.get(i).intValue()

装箱和拆箱都是编译器干的活,虚拟机只是负责执行这些字节码。

类型转换

String s = "3";
int x = Integer.paraseInt(s);//静态方法paraseInt()

10.参数数量可变的方法

type... var

public static double max(double... values){
    double largest = Double.NEGATIVE_INFINITY;
    for (double v:values) if (v > largest) largest = v;
    return largest;
}
double m = max(3.1, 40.1, -5);

分析:编译器将new double[]{3.1, 40.1, -5}传给max方法。

11.枚举类 enum关键字

声明的是一个类,枚举类。

经典模式:

public enum Size{SMALL, MEDIUM, LARGE, EXTRA_LARGE}

比较全的:

public enum Size{
    SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");//枚举常量
    private String abbreviation;
    
    private Size(String abbreviation){ this.abbreviation = abbreviation;}
    public String getAbbreviation(){return abbreviation;}
}

enum构造器一定是私有的,否则语法错误。

所有的枚举类都是Enum类。

Size.SMALL.toString(); //"SMALL"
Size s = Enum.valueOf(Size.class, "SMALL");//s=Size.SMALL
Size[] values = Size.values();//返回全部枚举值的数组
int ordinal();//返回枚举常量的索引(下标)

12.反射

能够分析类能力的程序成为反射(reflective) ,用来编写动态操纵Java代码的程序。

  • 在运行时分析类的能力
  • 在运行时检查对象
  • 实现泛型数组操纵对象
  • 利用Method对象

比如:支持 用户界面生成器;对象关系映射器;其他需要动态查询类能力的开发工具。

Class类

程序运行期间,Java运行时系统会对每个对象维护一个运行时类型标识,跟踪每个对象所属的类,以便执行正确的方法。

虚拟机为每个类型(包括基础类型)管理一个唯一的Class对象,因此可以使用“===”作比较。

与instanceof不同的是,他不涵括子类,只与自身类比较。

static Class forName(String className) : 返回一个Class对象,表示名为className的类。

利用反射分析类的能力:

最重要:检查类的结构

java.lang.reflect包中Field、Method和Constructor三个类分别用来描述类的字段、方法和构造器;使用这三个类的getName方法来返回。具体看API。

使用反射在运行时分析对象

也就是动态获取某个实例的某个字段的具体值,诸如此类。

步骤:

  • 获取该对象的类,返回Class类的对象
  • 获取字段
  • 通过字段,传入具体对象,获取该对象的某个字段的值。也可以设置值。

使用反射编写泛型数组

。。。跳过

调用任意方法和构造器

。。。跳过

13.声明异常入门

  • 非检查型异常

    比如越界错误和访问null引用,就是运行时候报错

  • 检查型异常

    编译器会检查,就是编译报错

在方法上使用throws XXXException,调用该方法的任何方法也要一样声明,包括main方法。

这样会捕获异常而不会因为异常终止程序。

14.继承的设计技巧

  • 公共操作和字段放在父类中

  • 不使用受保护的字段

    protected,对本包和所有子类可见,因此子类或同一包的其他类可以直接访问父类的字段,破坏了封装性

  • 使用继承实现“is-a”关系

  • 除非所有继承的方法都有意义,否则就不要使用继承

  • 在覆盖方法时,不要改变预期的行为

  • 使用多态,而不要使用类型信息

    也就是如果多个具体行为都具有同一个概念,就是用多态来完成,不要死板的使用类型类判断在做具体的行为。

    可以将这个放在父类或者接口中,再具体实现。

  • 不要滥用反射