CS61B 课程笔记(DISC 04 Inheritance)

主要的Java概念:

  1. 继承
    • 子类Salmon)继承自父类Fish)。
    • 使用 super() 调用父类构造函数,允许子类重用父类的构造逻辑。
  2. 静态类型 vs 动态类型
    • 静态类型是编译时定义的类型(例如:Fish fish2 = new Salmon();)。
    • 动态类型是在运行时决定的类型(例如:fish2在运行时实际指向Salmon对象)。
  3. 方法重写 vs 方法重载
    • 方法重写:子类方法与父类方法签名相同,会覆盖继承的版本。
    • 方法重载:方法的名称相同但参数不同
  4. 动态方法选择
    • 编译时,根据静态类型来确定方法调用,但在运行时,会根据动态类型来选择合适的方法。
  5. 类型转换
    • 向下转换允许将父类引用转换为子类类型(例如,将Fish类型转换为Salmon)。
    • 如果转换的对象与预期类型不符,可能会抛出转换错误(例如:ClassCastException)。
  6. 接口
    • 接口声明方法而不提供具体实现。实现该接口的类必须提供具体的实现。
    • 示例:Plant接口由Rose实现。

代码示例:

  1. Fish 和 Salmon 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class Fish {
    int weight;
    public Fish(int w) { weight = w; }
    public void swim() { System.out.println("splash"); }
    }

    public class Salmon extends Fish {
    String home;
    public Salmon(int w, String h) {
    super(w); // 调用Fish构造函数
    home = h;
    }
    public void migrate() { System.out.println("Migrating to " + home); }
    }
  2. 动态方法选择

    1
    2
    3
    4
    5
    6
    7
    Fish fish = new Fish();
    Salmon salmon = new Salmon();
    Fish bob = new Salmon();

    fish.swim(); // 调用Fish的swim方法
    salmon.swim(); // 调用Salmon中重写的swim方法
    bob.swim(); // 由于动态类型为Salmon,调用Salmon的swim方法
  3. 类型转换

    1
    2
    3
    4
    5
    Fish blueFish = new Salmon();
    ((Salmon) blueFish).swim(5); // 在将blueFish转换为Salmon后可以运行

    Fish redFish = new Fish();
    ((Salmon) redFish).swim(5); // 运行时错误:ClassCastException

继承中的练习:

  • 理解方法行为在不同的继承层次结构中的表现。例如,A b0 = new B(); 在运行时使用B类的方法(根据动态类型),但编译时使用静态类型规则。

  • 错误级联:如果对象的初始化由于类型不匹配而失败,后续的方法调用也会失败。

以下是关于继承和Java类的中文笔记,整理自您提供的内容:

CS 61B 继承练习

1. 创建猫类

1.1 猫类定义
根据Animal类定义Cat类,使得当调用greet()方法时,打印出“Cat”(而不是“Animal”)。假设如果猫的年龄大于或等于5岁,发出“Meow!”的声音;如果小于5岁,则发出“MEOW!”的声音。

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
30
31
32
33
34
public class Animal {
protected String name, noise;
protected int age;

public Animal(String name, int age) {
this.name = name;
this.age = age;
this.noise = "Huh?";
}

public String makeNoise() {
if (age < 5) {
return noise.toUpperCase();
} else {
return noise;
}
}

public void greet() {
System.out.println("Animal " + name + " says: " + makeNoise());
}
}

public class Cat extends Animal {
public Cat(String name, int age) {
super(name, age); // 调用父类构造函数
this.noise = "Meow!"; // 设置猫的声音
}

@Override
public void greet() {
System.out.println("Cat " + name + " says: " + makeNoise());
}
}

2. 猫和狗的测试

2.1 输出分析
假设AnimalCat已按上面的定义,Java在以下行中将打印什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestAnimals {
public static void main(String[] args) {
Animal a = new Animal("Pluto", 10);
Cat c = new Cat("Garfield", 6);
Dog d = new Dog("Fido", 4);

a.greet(); // (A) Animal Pluto says: Huh?
c.greet(); // (B) Cat Garfield says: Meow!
d.greet(); // (C) Dog Fido says: Woof!
a = c;
((Cat) a).greet(); // (D) Cat Garfield says: Meow!
a.greet(); // (E) Cat Garfield says: Meow!
}
}

考虑下列代码的添加:

1
2
a = new Dog("Spot", 10);
d = a;

这段代码会产生编译错误,原因在于d的静态类型是Dog,而a的静态类型是Animal。解决此错误的方法是强制转换:

1
d = (Dog) a;

详细解释:

这段代码会产生编译错误的原因是因为静态类型不匹配。

在以下代码中:

1
2
3
4
Animal a = new Dog("Spot", 10);
Dog d = new Dog("Fido", 4);
a = new Dog("Spot", 10); // a的类型是Animal
d = a; // 编译错误
  • a的静态类型是Animal,虽然它的动态类型是Dog,但在赋值给d时,编译器只检查静态类型。
  • d的静态类型是Dog,而a的静态类型是Animal,这意味着你不能将一个Animal类型的引用直接赋值给Dog类型的变量。

如何修复这个错误?
可以使用强制类型转换来修复这个错误:

1
d = (Dog) a; // 强制转换为Dog类型

在这种情况下,如果a确实是Dog的实例,转换将成功;如果不是,则会抛出ClassCastException

3. 继承中的练习

3.1 查找编译时和运行时错误
交叉删除导致编译时错误或级联错误的行,并在运行时错误的行上画个X。注意,错误可能在类ABC中。

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class A {
public int x = 5;
public void m1() { System.out.println("Am1-> " + x); }
public void m2() { System.out.println("Am2-> " + this.x); }
public void update() { x = 99; }
}

class B extends A {
public void m2() { System.out.println("Bm2-> " + x); }
public void m2(int y) { System.out.println("Bm2y-> " + y); }
public void m3() { System.out.println("Bm3-> called"); }
}

class C extends B {
public int y = x + 1;
public void m2() { System.out.println("Cm2-> " + super.x); }
public void m4() { System.out.println("Cm4-> " + super.super.x); } // X: 运行时错误
public void m5() { System.out.println("Cm5-> " + y); }
}

class D {
public static void main(String[] args) {
B a0 = new A(); // X: 编译时错误
a0.m1();
a0.m2(16); // X: 编译时错误
A b0 = new B();
System.out.println(b0.x);
b0.m1();
b0.m2();
b0.m2(61); // X: 编译时错误
B b1 = new B();
b1.m2(61);
b1.m3();
A c0 = new C();
c0.m2();
C c1 = (A) new C(); // X: 编译时错误
A a1 = (A) c0;
C c2 = (C) a1;
c2.m3();
c2.m4(); // X: 运行时错误
c2.m5();
((C) c0).m3();
(C) c0.m3(); // X: 编译时错误
b0.update();
b0.m1();
}
}