Java-Chapter 11 Inheritance and Polymorphism

问题 1:如何在Java中实现类继承?

答案
Java通过 extends 关键字实现类继承。

解释
继承允许子类从父类继承字段和方法。子类可以添加新的字段和方法,或者重写父类的方法来修改其行为。通过继承,子类可以复用父类的代码。

示例

1
2
3
4
5
6
7
8
9
10
11
12
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}

class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}

问题 2:子类是否继承父类的构造方法?

答案
子类不会继承父类的构造方法,但可以通过 super() 调用父类的构造方法。

解释
构造方法是专门用于初始化对象的,而子类不能继承父类的构造方法。但可以在子类构造方法中使用 super() 来调用父类的构造方法。如果不显式调用,编译器会自动插入 super(),这将调用父类的无参构造方法。

示例

1
2
3
4
5
6
7
8
9
10
11
12
class Animal {
Animal() {
System.out.println("Animal constructor");
}
}

class Dog extends Animal {
Dog() {
super(); // 调用父类的构造方法
System.out.println("Dog constructor");
}
}

问题 3:superthis 关键字的区别?

答案

  • super 用于访问父类的构造方法或父类的方法。
  • this 用于访问当前类的字段或调用当前类的构造方法。

解释

  • super() 用于调用父类的构造方法,它必须是构造方法中的第一条语句。
  • super.method() 用于调用父类的方法。
  • this.field 用于访问当前对象的字段。
  • this(args) 用于调用当前类的另一个构造方法。

示例

1
2
3
4
5
6
7
8
9
10
11
12
class Animal {
Animal(String name) {
System.out.println("Animal: " + name);
}
}

class Dog extends Animal {
Dog() {
super("Dog"); // 调用父类的构造方法
System.out.println("Dog constructor");
}
}

问题 4:如果子类没有显式调用父类构造方法,会发生什么?

答案
如果子类构造方法没有显式调用父类构造方法,编译器会自动插入 super(),调用父类的无参构造方法。

解释
在没有显式调用父类构造方法时,编译器会默认调用父类的无参构造方法。这意味着,如果父类没有无参构造方法并且子类没有显式调用其他父类构造方法,编译将会失败。

示例

1
2
3
4
5
6
7
8
9
10
11
12
class Animal {
Animal(String name) {
System.out.println("Animal: " + name);
}
}

class Dog extends Animal {
Dog() {
super("Dog"); // 必须显式调用父类构造方法
System.out.println("Dog constructor");
}
}

问题 5:如何重写父类的方法?

答案
重写方法时,子类的方法必须具有与父类方法相同的签名(方法名、参数类型、返回类型)。

解释
重写(Override)是子类重新定义继承自父类的方法,以改变方法的行为。重写的方法必须与父类的方法具有相同的方法签名(包括方法名、参数类型和数量)。此外,重写的方法可以有不同的访问修饰符,但它不能更严格地限制访问权限。

示例

1
2
3
4
5
6
7
8
9
10
11
12
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}

class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}

问题 6:每个Java类的父类是什么?

答案
每个Java类默认继承自 java.lang.Object 类,除非显式指定其他父类。

解释
java.lang.Object 是所有类的根类,所有类都直接或间接继承自它。即使我们没有显式地指定父类,Java编译器会默认将类继承自 Object 类。

示例

1
2
3
class Dog {
// Dog 类默认继承自 Object 类
}
  • 继承(extends)是Java的基本特性,使得子类能够复用父类的代码,并可修改或扩展其功能。
  • 子类可以通过 super 关键字访问父类的构造方法和方法,this 用于访问当前类的构造方法和字段。
  • 子类重写父类的方法时,必须确保方法签名相同。
  • 默认情况下,每个Java类都继承自 Object 类。

问题 7:什么是子类类型和父类类型?

答案

  • 子类定义的类型称为子类型。
  • 父类定义的类型称为超类型(或父类型)。

解释
子类继承父类并扩展或修改父类的行为,因此子类的类型可以视为父类类型的一个特殊化或扩展。一个类型可以同时是另一个类型的子类型和超类型,视具体情况而定。

示例

1
2
3
4
class Animal {}   // 父类(超类型)
class Dog extends Animal {} // 子类(子类型)

Animal a = new Dog(); // Dog 是 Animal 的子类型,Animal 是 Dog 的超类型

问题 8:什么是多态(Polymorphism)?

答案
多态是指当一个方法的参数类型是父类时,可以将该方法传入任何子类对象。

解释
多态允许对象以父类类型传递,并在运行时根据对象的实际类型调用适当的类方法。多态的实现通常基于继承关系和方法重写(Override)。通过这种方式,可以编写更加通用和可扩展的代码。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}

class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}

class Test {
public static void makeSound(Animal a) {
a.sound(); // 动态绑定,实际调用的将是 Dog 类的 sound 方法
}

public static void main(String[] args) {
Dog dog = new Dog();
makeSound(dog); // 输出 "Dog barks"
}
}

问题 9:什么是动态绑定(Dynamic Binding)?

答案
动态绑定是指方法的实际实现(例如,toString 方法)在运行时根据对象的实际类型决定。

解释
在多态中,当一个方法被调用时,编译器无法在编译时确定调用哪个类的实现。它会等到运行时,根据对象的实际类型来决定调用哪个方法,这就是动态绑定。例如,当我们用一个父类类型的引用变量调用一个重写的方法时,具体调用的是哪个类的方法是在运行时决定的。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}

class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}

public class Test {
public static void main(String[] args) {
Animal animal = new Dog(); // 动态绑定,animal 是 Animal 类型的引用,但实际是 Dog 对象
animal.sound(); // 输出 "Dog barks" 由于动态绑定,调用 Dog 类的 sound 方法
}
}

问题 10:什么是方法匹配(Method Matching)?

答案
方法匹配是编译器在编译时根据方法签名(方法名、参数类型等)来查找匹配的方法。

解释
编译器会根据方法签名来匹配调用的函数。在编译时,编译器根据传递给方法的参数类型、数量、顺序来确定哪个方法应该被调用。对于重载的方法,编译器使用方法签名来匹配方法。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Test {
void method(int a) {
System.out.println("Method with one integer");
}

void method(String b) {
System.out.println("Method with one String");
}

public static void main(String[] args) {
Test t = new Test();
t.method(10); // 调用 method(int a)
t.method("Hello"); // 调用 method(String b)
}
}

问题 11:当通过引用变量调用实例方法时,实际调用哪个方法是如何决定的?

答案
当通过引用变量调用实例方法时,实际调用的方法是根据引用变量实际指向的对象的类型来决定的,而不是引用变量的声明类型。

解释
如果通过父类类型的引用调用一个实例方法,而该方法在子类中被重写(Override),则在运行时调用子类的方法,而不是父类的方法。此行为是由动态绑定决定的。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}

class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}

public class Test {
public static void main(String[] args) {
Animal animal = new Dog(); // 父类引用指向子类对象
animal.sound(); // 动态绑定,实际调用的是 Dog 类的 sound 方法
}
}

问题 12:如何确定访问字段或静态方法时,实际使用哪个方法?

答案
访问字段或静态方法时,实际使用的方法由引用变量的声明类型来决定,而不是引用变量实际指向的对象类型。

解释
对于字段访问和静态方法调用,Java 编译器在编译时根据引用变量的声明类型决定使用哪个字段或静态方法。这与实例方法调用不同,实例方法调用是在运行时根据对象的实际类型决定的。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Animal {
static void staticMethod() {
System.out.println("Animal static method");
}
}

class Dog extends Animal {
static void staticMethod() {
System.out.println("Dog static method");
}
}

public class Test {
public static void main(String[] args) {
Animal animal = new Dog();
animal.staticMethod(); // 编译时由引用变量类型决定,输出 "Animal static method"
}
}
  • 子类和父类的关系:子类是父类的子类型,父类是子类的超类型。
  • 多态:允许将父类类型的引用指向子类对象,并在运行时调用适当的方法。
  • 动态绑定:运行时决定调用哪个类的方法,特别适用于实例方法。
  • 方法匹配:编译时根据方法签名确定调用哪个方法。
  • 字段和静态方法访问:由引用变量的声明类型决定,而不是实际对象类型。

问题 13:如何使用 instanceof 操作符判断一个对象是否是某个类的实例?

答案
可以使用 obj instanceof ClassName 来判断对象 obj 是否是 ClassName 类或其子类的实例。

解释
instanceof 操作符用于检查对象是否属于某个特定类型的类或其子类。它在运行时进行类型检查,返回 truefalse。这种方式常用于确保在执行特定操作之前对象具有正确的类型。

示例

1
2
3
4
5
6
7
8
9
10
class Animal {}
class Dog extends Animal {}

public class Test {
public static void main(String[] args) {
Animal animal = new Dog();
System.out.println(animal instanceof Dog); // 输出 true
System.out.println(animal instanceof Animal); // 输出 true
}
}

问题 14:如何使用 protected 修饰符?

答案
protected 修饰符可以用来限制数据和方法的访问,仅允许同一个包内的类以及不同包中的子类访问。

解释
protected 修饰符比 private 更宽松,允许子类继承父类的字段和方法,并且在同一包内也可以访问。但如果没有继承关系,不同包中的类无法访问 protected 成员。protected 一般用于需要在继承关系中共享的数据和方法。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Parent {
protected int x = 10;
}

class Child extends Parent {
void printX() {
System.out.println(x); // 继承父类的 protected 成员
}
}

public class Test {
public static void main(String[] args) {
Child child = new Child();
child.printX(); // 输出 10
}
}

问题 15:如何使用 final 修饰符?

答案

  • final 修饰符可以用于类,表示该类不能被继承。
  • final 修饰符可以用于方法,表示该方法不能被重写。
  • final 修饰符可以用于变量,表示该变量的值一旦赋值后不能再改变。

解释

  • 当一个类被 final 修饰时,不能有子类继承它。
  • 当一个方法被 final 修饰时,不能被子类重写(Override)。
  • 当一个变量被 final 修饰时,必须在声明时或构造器中初始化,并且一旦赋值后不能修改。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
final class Parent {
// 这个类不能被继承
}

class Child extends Parent { // 编译错误,不能继承 final 类
}

class Example {
final void display() {
System.out.println("This is a final method");
}
}

class ChildExample extends Example {
// 编译错误,不能重写 final 方法
void display() {
System.out.println("Trying to override");
}
}

问题 16:static 方法是否可以被重写?

答案
static 方法不能被重写,但可以被重新声明(即方法隐藏)。

解释
static 方法是属于类而不是实例的,因此它的绑定是在编译时进行的,而不是运行时。子类可以定义一个与父类中同名的 static 方法,这种行为称为方法隐藏(method hiding),而不是重写(overriding)。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Parent {
static void staticMethod() {
System.out.println("Parent static method");
}
}

class Child extends Parent {
static void staticMethod() {
System.out.println("Child static method");
}
}

public class Test {
public static void main(String[] args) {
Parent.staticMethod(); // 输出 "Parent static method"
Child.staticMethod(); // 输出 "Child static method"
}
}

问题 17:如果一个方法不能被继承,它能否被重写?

答案
如果一个方法不能被继承(如 privatefinal 方法),它就不能被重写。

解释

  • private 方法对子类不可见,因此不能被继承或重写。
  • final 方法已经被确定为不可修改,因此不能被重写。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Parent {
private void privateMethod() {
System.out.println("Private method in Parent");
}

final void finalMethod() {
System.out.println("Final method in Parent");
}
}

class Child extends Parent {
// 编译错误,private 方法不能被继承或重写
void privateMethod() {
System.out.println("Trying to override");
}

// 编译错误,final 方法不能被重写
void finalMethod() {
System.out.println("Trying to override");
}
}

问题 18:java.util.ArrayList 类的作用是什么?

答案
java.util.ArrayList 类是一个可调整大小的数组,用于存储对象的动态集合。它提供了灵活的增、删、查功能,并且自动处理数组大小的调整。

解释
ArrayList 是一个实现了 List 接口的集合类,通常用于存储一系列对象。它在内部使用数组来存储数据,当元素增加时,ArrayList 会动态扩展其容量。它允许重复元素,并且可以通过索引来访问元素。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.ArrayList;

public class Test {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Orange");

System.out.println(list.get(0)); // 输出 "Apple"
System.out.println(list.size()); // 输出 3
}
}
  • instanceof 操作符 用于判断对象是否是某个类的实例。
  • protected 修饰符 限制数据和方法的访问,仅允许子类和同包类访问。
  • final 修饰符 用于限制类、方法或变量的继承或重写。
  • static 方法 不能被重写,只能被重新声明(方法隐藏)。
  • privatefinal 方法 不能被继承和重写。
  • java.util.ArrayList 用于存储动态大小的对象集合,并提供便捷的增删查操作。