梅庄

java语言程序设计-基础篇-进阶篇-读书笔记4

CH11继承和多态

继承

父类和子类

  • 继承是一种(is-a)关系
  • java中不允许多重继承,一个java类只可能直接继承自一个父类。(单一继承 single inheritance)
  • 多重继承可以通过接口来实现

使用super关键字

调用父类 的构造方法

关键字super指代父类,可用于调用父类中的普通方法和构造方法

1
2
super()
super(parameters)

要调用父类构造方法必须使用关键字super,而这个调用必须是构造方法的第一条语句

构造方法链

构造方法可以调用重载的构造方法或父类的构造方法。如果它们都没有被显式地调用,编译器就会自动地将super()作为构造方法的第一条语句

Constructor chaining
当构造一个子类的对象时,子类构造方法会在完成自己的任务之前,首先调用它的父类的构造方法。如果父类继承自其他类,那么父类构造又会在完成自己的任务之前,调用它自己的父类的构造方法。这个过程持续到沿着这个继承体系结构的最后一个构造方法被调用为止
例如下面这段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Faculty extends Employee {
public static void main(String[] args) {
new Faculty();
}
public Faculty()
{
System.out.println("(4)Performs Faculty's tasks");
}
}
class Employee extends Person {
public Employee() {
this(" (2)Invoke Employee's overloaded constructor");
System.out.println("3)Performs Employee's tasks ");
}
public Employee(String s) {
System.out.println(s);
}
class Person {
public Person()
{
System.out.println("l)Performs Person's tasks");
}
}

正确输出顺序为1,2,3,4

注意

如果设计一个可以被继承的类,最好提供一个无参构造方法以避免程序设计错误
一般情况下,最好能为每个类提供一个无参构造方法,以便对该类进行扩展,同时避免错误

比如:

1
2
3
4
5
class Fruit {
public Fruit(String name) {
System.out.println("Fruit's constructor is invoked");
}
}

由于在 Apple 中没有显式定义的构造方法,因此,Apple 的默认无参构造方法被隐式调用。
因为 Apple 是 Fruit 的子类,所以 Apple 的默认构造方法会自动调用 Fruit 的无参构造方 法。然而,Fruit 没有无参构造方法,因为 Fruit 显式地定义了构造方法。因此,程序不 能被成功编译。

调用父类的方法

super.方法名(参数)

方法重写

method overriding
注意

  • 仅当实例方法是可访问时,它才能被覆盖。因为私有方法在它的类本身以外是不能 访问的,所以它不能被覆盖。如果子类中定义的方法在父类中是私有的,那么这个方法完全没有关系。
  • 与实例方法一样,静态方法也能被继承。但是,静态方法不能被覆盖。如果父类中 定义的静态方法在子类中被重新定义,那么在父类中定义的静态方法将被隐藏。可 以使用语法:父类名 .静态方法名(SuperClassName.staticMethodName) 调用隐藏的 静态方法。

方法重写与重载

要点

  • 重载意味着使用同样的名字但是不同的签名来定义多个方法。
  • 重写意味着在子 类中提供一个对方法的新的实现。

注意
a)重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Main{
public static void main(String[] args){
A a = new A();
a.p(10);
a.p(10.0);
}
}
class B{
public void p(double i){
System.out.println(i*2);
}
}
class A extends B{
public void p(double i){
System.out.println(i);
}
}

输出
10.0
10.0

b)重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Main{
public static void main(String[] args){
A a = new A();
a.p(10);
a.p(10.0);
}
}
class B{
public void p(double i){
System.out.println(i*2);
}
}
class A extends B{
public void p(int i){
System.out.println(i);
}
}

输出
10
20.0

注意

-方法重写发生在通过继承而相关的不同类中;方法重载可以发生在同一个类中,也可以发生在由于继承而相关的不同类中

  • 方法重写具有同样的签名和返回值;方法重载具有同样的名字,但是不同参数列表

为避免错误,用重写标注(override annotation)
即在子类的方法签名放一个@Override
该标注表示被标注的方法必须重写父类的一个方法,否则编译器报错

Object类及其toString方法

要点

java中所有的类都继承自java.lang.Object类

调用一个对象的 toStringO 会返回一个描述该对象的字符串。默认情况下,它返回一 个由该对象所属的类名、at 符号(@)以及该对象十六进制形式的内存地址组成的字符串。
比如Loan@15037e5的字符串,不过这个信息不是很有用,需要重写

可 以 使 用 System.out.println(loan)来 替 换 System.out.println(loan.toString())

多态

要点

形成多态条件:

  • 有继承
  • 有重写
  • 有父类引用指向子类对象

动态绑定

要点

方法可以在沿着继承链的多个类中实现。JVM 决定运行时调用哪个方法。
方法可以在父类中定义而在子类中重写

1
Object o = new CeometricObject();

o的declared type是Object,actual type是实际类型,o调用那个方法由o的actual tupe决定,这就是动态绑定

动态绑定的工作机制

动态绑定工作机制如下:假设对象 o是类 Cl, C2,…,Cn-1, Cn 的实例,其中 C1是 C2的子类 ,C2 是 C3 的子类,…,Cn-1是 Cn 的子类,如图 11-2所示。也就是说,Cn 是最通用的类,C1是最特殊的类。在 Java 中,Cn 是 Object 类。如果对象 o 调用一个方法 p, 那么 JVM 会依次在类 Cl, C2,…,Cn-1,Cri 中查找方法 p 的实现,直到找到为止。一旦找到一个实现 ,就停止査找,然后调用这个首先找到的实现。

引用变量的declare type决定了编译时匹配时匹配哪个方法
Java虚拟机在运行时动态绑定方法的实现,是由变量的actual type决定的。

以下代码的运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Main {
public static void main(String[] args) {
new A();
new B();
}
}
class A {
int i = 7;
public A() {
setI(20);
System.out.println("i from A is " + i);
}
public void setI(int i) {
this.i = 2 * i;
}
}
class B extends A {
public B() {
System.out.println("i from B is " + i);
}
public void setI(int i) {
this.i = 3 * i;
}
}

输出
i from A is 40
i from A is 60
i from B is 60

对象转换和instanceof运算符

要点:对象的引用可以转换为对另外一种对象的引用,这称为对象转换

隐式转换(implicit casting)Object o = new Student();
显式转换(Explicit casting)Student b = (Student)o;

声明类型决定了在编译时匹配哪个方法

1
Object myObject = new Circle()

这时候如果使用myObject.getDiameter()会引起一个编译错误,因为Object类没有getDiameter方法。编译器无法找到和myObject.getDiameter()匹配的方法。所以,有必要将myObject转换为Circle类型,来告诉编译器myObject也是Circle的一个实例。

instanceof
只有源对象是目标类的实例时才能进行类型转换。在执行转换前,程序使用instanceof运算符来确保源对象是否是目标类的实例
注意

对象成员访问运算符(.)优先于类型转换运算符。使用圆括号保证在点运算符( •) 之前进行转换,例如:

1
((Circle)object).getAreaO;

对基本类型值进行转换不同于对对象引用进行转换

  • 转换基本类型值返回一个新的值

    1
    2
    int age = 45;
    byte newAge = (byte)age; //A new value is assigned to newAge
  • 转换一个对象引用不会创建一个新的对象

    1
    2
    Object o = new Circle();
    Circle c = (Circle)o; // No new object is created

现在,引用变量o和c指向同一个对象

Object类的equals方法

Object类中equals方法的默认实现是:

1
2
3
public boolean equals(Object obj){
return (this == obj);
}

这个实现使用==检测两个引用变量是否指向同一个对象

equlas方法在很多类中被重写,如String类Date类,用于比较两个对象的内容是否相等。

注意
当覆盖equals方法时,常见的错误就是在子类中输错它的签名

1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
Object circle1 = new Circle();
Object circle2 = new Circle();
System.out.println(circle1.equals(circle2));
}
}

a输出
false
b输出
true

重写equals方法要使用equals(Object obj)而不是equals(SomeClasss obj)

ArrayList类

要点ArrayList存储不限定个数对象
记住,是对象,Object!!!
所以,下面代码报错

1
2
ArrayList<Double> list = new ArrayList<>();
list.add(1);

从JDK1.7开始,语句

1
ArrayList<AConcreteType> list = new ArrayList<AConcreteType>();

可以简化为

1
ArrayList<AConcreteType> list = new ArrayList<>();

数组和ArrayList之间的异同

操作 数组 ArrayList
创建数组/数组列表 String[] a = new String[10] ArrayList list = new ArrayList()
引用元素 a[index] list.get(index)
更新元素 a[index] = “London” list.set(index, “London”)
返回大小 a.length list.size()
添加一个新元素 list.add(“London”)
插入一个新元素 list.add(index, “London”)
删除一个元素 list.remove(index)
删除一个元素 list.remove(Object)
删除所有元素 list.clear()

当创建ArrayList后,它的大小为0。如果元素不在ArrayList中,就不能使用get(index)和set(index, element)方法。
排序

  • 对数组进行排序
1
java.util.Arrays.sort(arrays);
  • 对数组列表进行排序
1
java.util.Collections.sort(arrayList)

题目

1
2
3
4
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);

1
2
list.remove(1);
System.out.println(list);

输出
[1,3]

1
2
list.remove(Integer.valueOf(1));
System.out.println(list);

输出
[2,3]

list的一些API

array- > ArrayList

1
2
String[] array = {"red", "green", "blue"};
ArrayList<String> list = new ArrayList<>(Arrays.asList(array));

Arrays类中的静态方法asList返回一个list

ArrayList - > array

1
2
String[] array1 = new String[list.size()];
list.toArray(array1);

将list的内容复制到array1中

排序

1
java.util.Collections.sort(list);

找最大元素和最小元素

1
2
java.util.Collection.max(list);
java.util.Collection.min(list);

随机打乱list中元素

1
java.util.Collections.shuffle(list);

protected数据和方法

要点一个类中的protected(受保护成员)可以从子类中访问
private只能在类内访问
public被任意的其他类访问

public,default用于class
public,protected, private用于class的成员

注意

子类可以重写它的父类protected方法,并把它的可见性改为public但是,子类不能削弱父类中定义方法的可见性
例如:如果一个方法在父类中定义为public,子类中也必须定义为public

final防止扩展和重写

要点一个被final修饰的类和方法都不能被扩展,被final修饰的数据域是一个常数
有时候,希望防止类被扩展,可以用final修饰,表明是最终的类,不能作为父类
比如Math类,String类,StringBuilder类和StringBuffer类

  • final修饰的类不能被继承
  • final修饰的方法不能被重写

本章小结

  • 构造方法用来构造类的实例。不同于厲性和方法,子类不继承父类的构造方法。它们只能用关键字 super从子类的构造方法中调用。
  • 构造方法可以调用重载的构造方法或它的父类的构造方法。这种调用必须是构造方法的第一条语句。 如果没有显式地调用它们中的任何一个,编译器就会把 super()作为构造方法的第一条语句,它调 用的是父类的无参构造方法。
  • 为了重写一个方法,必须使用与它的父类中的方法相同的签名来定义子类中的方法。
  • 实例方法只有在可访问时才能重写。这样,私有方法是不能重写的,因为它是不能在类本身之外访 问的。如果子类中定义的方法在父类中是私有的,那么这两个方法是完全没有关系的。
  • 静态方法与实例方法一样可以继承。但是,静态方法不能重写,如果父类中定义的静态方法在子类 中重新定义,那么父类中定义的方法被隐藏。
  • Java中的每个类都继承自 java.lang.Object 类。如果一个类在定义时没有指定继承关系,那么它 的父类就是Object
  • 如果一个方法的参数类型是父类(例如:Object), 可以向该方法的参数传递任何子类(例如: Circle 类或String 类)的对象。这称为多态。
  • 因为子类的实例总是它的父类的实例,所以,总是可以将一个子类的实例转换成一个父类的变量。 当把父类实例转换成它的子类变量时,必须使用转换记号(子类名)进行显式转换,向编译器表明 你的意图。
  • 当从引用变置调用实例方法时,该变量的实际类型在运行时决定使用该方法的哪个实现。这称为动态绑定。
  • 可以使用表达式 obj instanceof AClass(对象名 instanceof 类名)测试一个对象是否是一个类 的实例。
  • 可以使用ArrayList 类来创建一个对象,用于存储一个对象列表。
  • 可以使用 protected 修饰符来防止方法和数据被不同包的非子类访问。
  • 可以用final 修饰符来表明一个类是最终类,是不能被继承的;也可以表明一个方法是最终的, 是不能重写的