继承是面向对象编程中的一种重要概念,它允许一个类(称为子类或派生类)从另一个类(称为父类或基类)继承属性和方法。通过继承,子类可以获得父类的特性,并且可以添加自己特定的特性,形成类之间的层次关系。
2.1.4 Java继承语法
在Java语言中,继承是通过使用关键字"extends"实现的。继承的语法结构为:子类名 extends 父类名。通过继承,子类可以继承父类的成员变量和成员方法,并且可以添加自己特定的成员变量和成员方法。
需要注意的是,Java语言不支持多重继承,一个类只能继承一个父类,但一个父类可以有多个子类。
继承的特性包括:
- 单继承:Java不支持类的多继承,这意味着一个类只能有一个超类(父类),但一个超类可以有多个子类。这种单一继承的特性保证了类之间的层次结构清晰明确。
- 传递性:子类不仅可以继承直接父类的属性和方法,还可以继承父类所继承的其他父类的属性和方法。这种传递性的特性使得类的继承关系形成了一个层次结构,子类可以逐级继承父类的特性,使代码的复用更加灵活和高效。
2.2 继承中的构造器
2.2.1 重用构造器
在上述案例中,我们使用继承将共享的属性和方法抽取到了父类Person中。然而,我们也注意到子类的构造器中出现了重复的冗余代码,即对父类属性的初始化。
Java中的构造器是不能被继承的,子类不能直接继承父类的构造器。但是,Java提供了一种机制让子类能够调用父类的构造器,从而解决这个问题。
子类可以使用关键字super调用父类的构造器。通过调用父类的构造器,可以将公共属性的初始化代码抽取到父类中,然后子类只需调用父类的构造器,而不需要再重复初始化父类的属性。
2.2.2【案例】重用构造器示例
下面是一个修改后的案例示例:
- class Person{
- String name;
- int age;
- char gender;
- public Person(String name, int age,
- char gender){
- this.name = name;
- this.age = age;
- this.gender = gender;
- }
- }
- class Student extends Person {
- double score;
- public Student(String name, int age, char gender, double score) {
- super(name, age, gender); // 调用父类的构造器
- this.score = score;
- }
- }
- class Teacher extends Person {
- double salary;
- public Teacher(String name, int age, char gender, double salary) {
- super(name, age, gender); // 调用父类的构造器
- this.salary = salary;
- }
- }
在修改后的代码中,我们通过在子类的构造器中使用super关键字调用父类的构造器。通过这种方式,子类可以继承父类的属性,并且将属性的初始化工作交给父类的构造器来完成。
通过使用super调用父类的构造器,我们避免了重复编写相似的属性初始化代码,提高了代码的复用性和可维护性。
总结起来,通过在子类的构造器中使用super关键字调用父类的构造器,可以将公共属性的初始化代码抽取到父类中,实现代码的重用和简化。这样,子类只需关注自己特定的属性和逻辑,提高了代码的可读性和可维护性。
2.2.3 关于 super() 方法
在继承关系中,super() 方法用于子类调用父类的构造器。
下面是关于 super() 方法的一些要点:
- super() 方法在子类的构造器中使用。
- 如果在子类的构造器中没有显式地写上 super(),编译器会自动添加一个默认的 super() 调用,用于调用父类的无参数构造器。但是,如果父类没有无参构造器,会导致编译错误。
- 通过 super(参数) 的形式,可以调用父类中的有参数构造器。
- super() 方法必须写在子类构造器的第一行,即在子类构造器执行之前调用父类的构造器。
使用 super() 方法可以在子类构造器中显式地调用父类的构造器,以完成父类的属性的初始化工作。
总结起来,super() 方法用于子类调用父类的构造器,可以在子类构造器中使用,用于完成父类属性的初始化。它可以自动调用父类的无参构造器,也可以通过指定参数来调用父类的有参数构造器。注意,super() 方法必须写在子类构造器的第一行。
2.2.4【案例】调用父类构造器
在继承关系中,如果父类没有无参数构造器,而子类又需要调用父类的构造器进行属性的初始化,可以使用 super(参数) 的形式来显式地调用父类的有参数构造器。
下面是一个示例,展示了父类没有无参数构造器时,如何使用 super(参数) 调用父类的有参数构造器:
- class Person {
- String name;
- public Person(String name) {
- this.name = name;
- }
- }
- class Student extends Person {
- int age;
- public Student(String name, int age) {
- super(name); // 调用父类的有参数构造器
- this.age = age;
- }
- }
在这个示例中,父类 Person 没有无参数构造器,而子类 Student 需要使用父类的构造器进行属性的初始化。通过使用 super(name),子类的构造器调用了父类的有参数构造器来初始化父类的属性。这样就实现了子类对父类属性的初始化。
2.3.2 方法的重写
方法的重写(Override)是指子类在继承父类的方法后,对该方法进行修改或重新实现,以满足子类的特定需求。
在继承中,如果子类需要对父类的方法进行修改或定制化,可以使用方法重写。子类可以重写(覆盖)父类的方法,使得子类在调用该方法时执行子类自定义的逻辑。方法的重写保持了方法的名称、参数列表和返回类型相同,但方法体可以被子类重新定义。
方法重写的规则如下:
- 子类的重写方法必须和父类的被重写方法具有相同的方法名、参数列表和返回类型。
- 子类的重写方法的访问修饰符不能比父类的被重写方法的访问修饰符更严格(即不能降低访问权限)。
- 子类的重写方法不能抛出比父类的被重写方法更宽泛的异常(可以抛出相同的异常或子类异常)。
2.3.3【案例】方法的重写
以下是一个示例,展示了方法的重写:
- class Person {
- public void eat() {
- System.out.println("人在吃饭");
- }
- }
- class Student extends Person {
- @Override
- public void eat() {
- System.out.println("学生在食堂吃饭");
- }
- }
- public class Demo7 {
- public static void main(String[] args) {
- Person p1 = new Person();
- p1.eat(); // 输出:人在吃饭
- Student s1 = new Student();
- s1.eat(); // 输出:学生在食堂吃饭
- }
- }
在这个示例中,父类 Person 定义了一个 eat() 方法,子类 Student 继承了父类的方法并进行了重写。在 Demo7 类的 main 方法中,我们分别创建了一个 Person 对象和一个 Student 对象,并调用它们的 eat() 方法。由于子类重写了父类的方法,所以在调用子类对象的 eat() 方法时,执行的是子类自定义的逻辑。
通过方法的重写,子类可以修改或定制化从父类继承的方法,使得方法更适应子类的特定需求。这样可以增加代码的灵活性和可扩展性,同时保持了继承的代码复用的优势。
2.3.4使用super关键字
在继承关系中,子类可以使用super关键字来访问父类中的属性和方法。super关键字提供了一种明确引用父类成员的方式,尤其在父类和子类存在相同属性或方法名的情况下特别有用。
一般情况下,子类可以直接访问继承自父类的属性和方法。但是当子类中存在与父类同名的属性或方法时,就会发生命名冲突,编译器无法区分使用哪一个。这时候可以使用super关键字来明确指定访问父类中的成员。
super关键字的一般形式为super.属性名和super.方法名(),其中super.属性名用于访问父类的属性,super.方法名()用于调用父类的方法。
2.3.5【案例】使用super关键字示例
以下是一个示例,展示了super关键字的使用:
- class Person {
- String name;
- int age;
- char gender;
- public Person(String name, int age, char gender) {
- this.name = name;
- this.age = age;
- this.gender = gender;
- }
- public void printInfo() {
- System.out.println("姓名:" + name);
- System.out.println("年龄:" + age);
- System.out.println("性别:" + gender);
- }
- }
- class Student extends Person {
- double score;
- public Student(String name, int age, char gender, double score) {
- super(name, age, gender); // 调用父类构造方法
- this.score = score;
- }
- @Override
- public void printInfo() {
- super.printInfo(); // 调用父类的printInfo方法
- System.out.println("成绩:" + score);
- }
- }
- public class Demo8 {
- public static void main(String[] args) {
- Student s1 = new Student("Tom", 12, '男', 99.5);
- // 测试继承的方法
- s1.printInfo();
- }
- }
在这个示例中,父类Person定义了属性name、age和gender,以及方法printInfo()。子类Student继承了父类的属性和方法,并在子类中重写了printInfo()方法。在子类的构造方法中,使用super关键字调用父类的构造方法来初始化继承的属性。在子类的printInfo()方法中,使用super关键字调用父类的printInfo()方法,以显示父类的信息。然后子类再输出自己特有的属性。
通过使用super关键字,子类可以明确引用父类的属性和方法,解决了父子类型中可能出现的属性和方法冲突问题,确保了正确的访问和调用。这样可以使代码更加清晰和可维护,提高了代码的可读性和可扩展性。
2.3.6 经典面试题目:重写与重载的区别
重写(Override)和重载(Overload)是面向对象编程中的两个重要概念,常常在面试中被问及。它们有以下几个区别:
- 定义:重写是指在子类中重新定义父类的方法,方法名、参数列表和返回类型都相同。重载是在同一个类中定义多个方法,方法名相同但参数列表不同。
- 关联:重写涉及继承关系,即子类继承父类的方法并对其进行重新定义。重载是同一个类中的方法之间的关系,通过方法的参数列表的差异来区分。
- 方法签名:重写要求子类方法与父类方法具有相同的方法签名,包括方法名、参数列表和返回类型。重载要求方法名相同,但参数列表必须不同(个数、类型、顺序)。
- 功能:重写用于在子类中重新定义父类的方法,可以根据子类的需要实现不同的功能。重载用于处理同一个类中不同的输入情况,通过参数的差异来选择不同的方法。
- 编译时决定:重写是在运行时动态绑定的,即根据对象的实际类型来确定调用的方法。重载是在编译时静态绑定的,根据传入的参数类型和个数来选择合适的方法。
总结起来,重写和重载的区别在于定义位置、关联性、方法签名、功能和编译时决定。重写用于子类对父类方法的重新定义,重载用于同一个类中多个方法的差异处理。理解它们的区别对于理解面向对象编程的核心概念和方法的灵活应用非常重要。