Java 基础教程

继承(Inheritance)

什么是继承?

继承(Inheritance)是面向对象编程中的一个重要概念和核心机制之一,它允许一个类(称为子类或派生类)继承另一个类(称为父类、基类或超类)的属性和方法。通过继承,子类可以获得父类的特性,并且可以在此基础上添加自己的特性或修改继承的属性和方法。

继承的主要目的是实现代码的重用和扩展。通过继承,我们可以定义一个通用的父类,其中包含多个子类共享的属性和方法。子类可以直接使用父类的属性和方法,而无需重新编写相同的代码。这样可以减少代码冗余,并提高代码的可维护性和可扩展性。

在继承中,父类通常具有通用的属性和方法,而子类可以通过继承来获取这些属性和方法,并可以添加自己的特定属性和方法。子类还可以重写继承的方法,以适应自己的需求。

继承关系可以形成一个类的层次结构,其中父类可以有多个子类,而子类也可以成为其他类的父类,从而形成更复杂的继承关系。这种层次结构可以提供更好的代码组织和管理,以及更高的代码复用性。

需要注意的是,继承并不适用于所有情况,过度的继承可能导致类的层次结构过于复杂,代码可读性和可维护性下降。因此,在设计和使用继承时,需要谨慎考虑,并遵循适当的设计原则和最佳实践。

继承语法

接口继承

在 Java 中,接口也可以进行继承,这被称为接口继承(Interface Inheritance)。接口继承允许一个接口(称为子接口)继承另一个或多个接口(称为父接口)的方法签名。

子接口继承了父接口的方法签名,但并不继承其实现。子接口可以添加自己的方法声明,以及重新声明父接口中的方法(在子接口中提供不同的实现)。

一个类可以实现一个或多个接口,这样它就需要实现这些接口中的所有方法。如果一个接口继承了其他接口,那么实现该接口的类也需要实现所有父接口中的方法。

接口继承的主要目的是为了实现接口的分层和组织。通过接口继承,我们可以将相关的方法签名组织到一个父接口中,然后在子接口中进行扩展和特殊化。这样可以提高代码的可读性、可维护性和可扩展性。

需要注意的是,接口继承可以形成多层继承关系,但是接口之间不存在多重继承的问题。一个接口可以继承多个父接口,但一个类只能继承一个类或抽象类。这是因为 Java 中的类只支持单一继承,而接口可以实现多个。

接口继承语法

接口继承使用关键字 extends,子接口通过 extends 关键字来继承一个或多个父接口。语法如下:

interface 子接口名 extends 父接口1, 父接口2, ... {
    // 子接口的方法声明
}

接口继承示例

创建一个 Person 接口代表人类,拥有吃饭和睡觉的动作,然后创 Student 学生和 Worker 工人接口,这两接口均继承 Person 接口,且分别自定义了自己的动作,如下:

public interface Person {
   /** 吃饭 */
   public void eat();

   /** 睡觉 */
   public void sleep();
}

public interface Student extends Person {
   /** 学习 */
   public void learn();
}

public interface Worker extends Person {
   /** 工作 */
   public void work();
}

类继承

类继承是面向对象编程中的一个重要概念,它允许一个类(称为子类或派生类)继承另一个类(称为父类、基类或超类)的属性和方法。通过类继承,子类可以获得父类的特性,并且可以在此基础上添加自己的特性或修改继承的属性和方法。

子类继承了父类的属性和方法,包括父类的公有和受保护成员(私有成员不可继承)。子类可以直接使用父类的属性和方法,而无需重新编写相同的代码。这样可以减少代码冗余,并提高代码的可维护性和可扩展性。

在类继承中,父类通常具有通用的属性和方法,而子类可以通过继承来获取这些属性和方法,并可以添加自己的特定属性和方法。子类还可以重写继承的方法,以适应自己的需求。

类继承可以形成一个类的层次结构,其中父类可以有多个子类,而子类也可以成为其他类的父类,从而形成更复杂的继承关系。这种层次结构可以提供更好的代码组织和管理,以及更高的代码复用性。

需要注意的是,Java 中的类只支持单一继承,即一个类只能继承一个父类。这是为了避免多重继承可能带来的复杂性和冲突。如果需要实现多个类的特性,可以使用接口来实现多重继承的效果。

类继承语法

类继承使用关键字 extends,子类通过 extends 关键字来继承一个父类。语法如下:

class 子类名 extends 父类名 {
   // 子类的属性和方法
}

类继承示例

下面示例创建一个普通人 Person(拥有姓名和年龄),然后通过继承 Person 类创建一个 Student 学生,该学生还新添加了所读学校和学号属性。如下:

Person.java 代码如下:

/**
* 通用人员信息
* @author hxstrive.com
*/
public class Person {
   /** 名称 */
   private String name;
   /** 年龄 */
   private int 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;
   }

   @Override
   public String toString() {
       return "Person{" +
               "name='" + name + '\'' +
               ", age=" + age +
               '}';
   }

}

Student.java 代码如下:

/**
* 学生,学生也是普通人中的一员,只是比较特殊,他们都有成绩属性
* @author hxstrive.com
*/
public class Student extends Person {
   /** 所读学校 */
   private String school;
   /** 学号 */
   private String studentNumber;

   public String getSchool() {
       return school;
   }

   public void setSchool(String school) {
       this.school = school;
   }

   public String getStudentNumber() {
       return studentNumber;
   }

   public void setStudentNumber(String studentNumber) {
       this.studentNumber = studentNumber;
   }

   @Override
   public String toString() {
       return "Student{" +
               "name='" + getName() + '\'' +
               ", age=" + getAge() +
               ", school='" + school + '\'' +
               ", studentNumber='" + studentNumber + '\'' +
               '}';
   }

}

Test.java 代码如下:

/**
* 测试类
* @author hxstrive.com
*/
public class Test {

   public static void main(String[] args) {
       Person person = new Person();
       person.setName("Tom");
       person.setAge(30);
       System.out.println("person = " + person);

       Student student = new Student();
       student.setName("Helen");
       student.setAge(35);
       student.setSchool("实验中学");
       student.setStudentNumber("S001");
       System.out.println("student = " + student);
   }

}

运行示例,输出如下:

person = Person{name='Tom', age=30}
student = Student{name='Helen', age=35, school='实验中学', studentNumber='S001'}

继承中的重写

在 Java中,子类可以重写(override)父类的方法。重写是指子类提供了与父类具有相同名称、参数列表和返回类型的方法,但是子类提供了自己的实现。

重写的语法规则如下:

(1)子类的方法必须与父类的方法具有相同的名称、参数列表和返回类型。

(2)子类的方法不能比父类的方法具有更低的访问权限。例如,如果父类的方法是 public,那么子类的方法也必须是 public 或 protected,不能是 private。

(3)子类的方法不能比父类的方法抛出更多的异常,或者抛出与父类方法不兼容的异常。子类可以不抛出异常,或者只抛出父类方法声明的异常或其子类异常。

当一个对象调用一个被重写的方法时,Java 会根据对象的实际类型来决定调用哪个方法。如果对象是子类的实例,那么将调用子类的方法;如果对象是父类的实例,那么将调用父类的方法。

重写的目的是在子类中提供对父类方法的特殊实现。子类可以通过重写来修改父类方法的行为,或者添加额外的功能。重写可以实现多态性,即通过父类引用调用子类的方法。

注意:重写只能发生在父类和子类之间。如果一个类没有继承关系,那么它们之间的方法称为重载(overload),而不是重写。重载是指在同一个类中定义了多个方法,它们具有相同的名称但不同的参数列表。重载允许使用不同的参数来调用相同的方法名,以提供更多的灵活性和便利性。

示例:

public class Person {

   public void show() {
       System.out.println("Person");
   }

}

public class Student extends Person {

   // 重载方法,注:@Override 是一个注解,后续章节介绍
   @Override
   public void show() {
       System.out.println("Student");
   }

   // 重载方法,和 show() 方法同名但是参数列表不一样
   public void show(String msg) {
       System.out.println(msg);
   }

   public static void main(String[] args) {
       Person p1 = new Person();
       p1.show();

       Person p2 = new Student();
       p2.show();
       //p2.show("重载方法"); // 调用失败,Person 类型中没有该方法

       // 强制类型转换
       ((Student)p2).show("调用重载方法");
   }

}

使用 super 关键字

在 Java 中,super 是一个关键字,主要用来引用父类的成员变量、方法和构造方法。我们可以通过 super 关键字调用父类的构造方法、成员变量和成员方法。

注意:super 关键字只能在子类中使用,并且只能用于引用父类的成员。在父类中使用 super 关键字是非法的,因为父类没有父类。另外,super 关键字必须在子类的构造方法的第一行使用,用于调用父类的构造方法。如果没有显式调用父类的构造方法,Java 会自动调用父类的默认构造方法。

调用父类构造方法

在子类的构造方法中,可以使用super关键字来调用父类的构造方法。通过 super 关键字,可以在子类的构造方法中调用父类的构造方法,从而完成对父类的初始化。这对于子类需要继承父类的属性和行为非常有用。例如:

public class Person {
   private int id;
   private String name;

   public Person(int id, String name) {
       this.id = id;
       this.name = name;
   }
}

public class Student extends Person {
   private float score;

   public Student(int id, String name, float score) {
       // 调用父类 Person 的构造方法 Person(int id, String name)
       super(id, name);
       this.score = score;
   }
}

操作被隐藏的成员变量

在子类中,可以使用 super 关键字来引用父类的成员变量。这在子类中存在与父类同名的成员变量时特别有用。通过 super 关键字,可以访问父类的成员变量,而不是子类的成员变量。例如:

public class Person {
   protected String name = "Person";
}

public class Student extends Person {
   // 和父类成员同名
   private String name = "Student";

   public void show(boolean flag) {
       if(flag) {
           // 引用父类 name 成员
           System.out.println(super.name); // 输出:Person
       } else {
           // 引用当前类的 name 成员
           System.out.println(name); // 输出:Student
       }
   }

   public static void main(String[] args) {
       new Student().show(true);
       new Student().show(false);
   }

}

操作被重写的成员方法

在子类中,可以使用 super 关键字来调用父类的方法。这在子类中存在与父类同名的方法时特别有用。通过 super 关键字,可以调用父类的方法,而不是子类的方法。这对于在子类中扩展父类的方法非常有用。例如:

public class Person {
   public String getName() {
       return "Person";
   }
}

public class Student extends Person {
   public String getName() {
       return "Student";
   }

   public Student() {
       // 调用本类的 getName() 方法
       System.out.println(getName()); // 输出:Student
       // 调用父类的 getName() 方法
       System.out.println(super.getName()); // 输出:Person
   }

   public static void main(String[] args) {
       new Student();
   }

}

单重继承和多重继承

Java 语言中只支持单重继承,而不允许多重继承。所谓单重继承是指一个 Java 类只能有一个直接父类,即 extends 关键字后面只跟写一个类。而多重继承是指一个类可以同时继承多个直接父类,这可能导致子类成员在追溯上的不确定性。

如果允许多重继承,则可能出现如下情况:

public class A {
   public void show() {
       System.out.println("In class A!");
   }
}

public class B {
   public void show() {
       System.out.println("In class B!");
   }
}

public class C extends A,B {
   public static void main(String[] args) {
       C c = new C();
       c.show();
   }
}

上述代码中,C 类的对象 c 在调用 show() 方法时会出现问题,系统无法确定到底该执行其父类 A 类中定义的 show() 方法,还是该执行 B 类中的 show() 方法呢。

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号