在面向对象编程(OOP)中,继承是一个核心概念,它允许我们创建一个新类(子类)来继承另一个类(父类)的属性和方法。这样做的好处是可以避免代码的重复,同时还可以扩展或修改继承来的行为和属性。
在前面的章节中,我们已经定义了Person
类:
class Person {
private String name;
private int age;
public String getName() {...}
public void setName(String name) {...}
public int getAge() {...}
public void setAge(int age) {...}
}
现在,假设需要定义一个Student
类。通过使用继承,我们可以在 Student
类中直接继承 Person
类的所有字段和方法,而无需再次定义 name
和 age
这两个字段及其方法。这样,Student
类的实例将自动拥有 Person
类的所有功能,并且还具备了 Student
类特有的 score
属性。
下面是使用继承后的 Student
类的代码示例:
class Person {
private String name;
private int age;
// 构造器、getter和setter方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class Student extends Person {
// 继承了Person类,不需要再次定义name和age字段及其方法
private int score;
// 构造器
public Student(String name, int age, int score) {
super(name, age); // 调用父类的构造器
this.score = score;
}
// 新增score字段的getter和setter方法
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
}
注意:子类自动获得了父类的所有字段,严禁定义与父类重名的字段!
在OOP的术语中,我们把Person
称为超类(super class),父类(parent class),基类(base class),把Student
称为子类(subclass),扩展类(extended class)。
继承树
在Java中,每个类都有一个继承体系,这使得对象可以共享和扩展公共的属性和方法。当我们在定义一个类时,如果没有明确指定继承自哪个类,Java编译器会自动将其继承自Object
类。这是因为Object
是Java中所有类的根类,它位于继承树的最顶端。因此,除了Object
类本身,所有的类都会继承自某个父类。
当我们创建Student
类时,我们使用extends
关键字明确指出它继承自Person
类。这样,Student
类不仅继承了Person
类的属性和方法,还可以添加或修改自己的特定行为。Student
类的实例将拥有Person
类的所有功能,并且增加了自己的score
属性。
同样的,如果我们定义一个Teacher
类,它也可以继承自Person
类。这样,Teacher
类将继承Person
类的所有属性和方法,并可以添加自己的特定属性和方法。这种继承关系形成了一个层次结构,其中Object
位于顶端,Person
是中间的父类,而Student
和Teacher
是子类。
protected
在Java中,继承有一些访问控制的限制。特别是,子类不能直接访问父类的private
成员。这是因为private
访问修饰符限制了成员的访问范围仅在定义它们的类内部。这种设计是为了保护封装性,防止外部对类内部实现的直接访问和修改。
为了解决这个问题,我们可以将Person
类的name
和age
字段的访问修饰符从private
改为protected
。这样做之后,这些字段就可以被子类Student
访问了,因为protected
访问级别允许子类及其子类访问这些成员。
class Person {
protected String name; // 从private改为protected
protected int age; // 从private改为protected
}
class Student extends Person {
public String hello() {
return "Hello, " + name; // 现在可以访问name字段
}
}
super
在Java编程语言中,super
是一个特殊的关键字,它被用来表示当前对象的直接父类。这个关键字在子类中特别有用,因为它允许子类访问父类的属性、方法和构造方法。当子类需要引用父类的成员时,可以使用super.fieldName
的形式来明确指出要访问的是父类的字段,尽管在很多情况下,直接使用字段名也能达到同样的效果,因为编译器会自动解析到父类的字段。
例如,下面的Student
类继承自Person
类,并在hello
方法中通过super.name
访问父类的name
字段:
class Student extends Person {
public String hello() {
return "Hello, " + super.name; // 访问父类的name字段
}
}
然而,有时候必须显式地使用super
关键字,尤其是在构造方法中。构造方法的第一个语句必须是对父类构造方法的调用,这是为了确保父类的成员变量能够被正确初始化。如果子类构造方法没有显式地调用父类构造方法,编译器会自动插入super();
,这时候会默认调用父类的无参数构造方法。
来看这个例子:
public class Main {
public static void main(String[] args) {
Student s = new Student("Xiao Ming", 12, 89);
}
}
class Person {
protected String name;
protected int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
this.score = score;
}
}
代码示例中,Student
类的构造方法没有显式调用Person
类的构造方法,这将导致编译错误,因为Person
类没有无参数的构造方法。编译器试图插入super();
,但是找不到匹配的Person
构造方法,所以编译失败。
为了解决这个问题,Student
类的构造方法必须显式地调用Person
类中存在的一个构造方法,并提供相应的参数:
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
super(name, age); // 正确调用Person类的构造方法
this.score = score;
}
}
super
关键字在子类构造方法中的使用是必要的,尤其是当父类没有默认构造方法时。此外,需要注意的是,子类不会继承父类的构造方法,子类默认的构造方法是编译器自动生成的,而不是从父类继承的。因此,子类必须显式地调用父类的构造方法来确保正确的初始化。