学习目标
继承是面向对象的三大特性之一,它可以减少代码冗余,提高可复用性和可维护性。除此之外,本章还会介绍继承关系中构造方法的调用关系,从而掌握在继承中创建并初始化对象的过程。
2.1 初识继承
2.1.1 继承的基本概念
继承(Inheritance)是面向对象的三大特性之一,继承可以解决编程中代码冗余的问题,是实现代码重用的重要手段之一。继承是软件可重用性的一种表现,使子类具有和父类相同的特征和行为,从而缩短开发周期,此时新类被称为子类,现在的类被称为父类。Java语言通过extends关键字实现类之间的继承关系。继承最基本的作用就是使代码可重用,增加软件的可扩展性。
继承语法:
//继承语法格式
[访问修饰符] class <SubClass> extends <SuperClass>{
}
其中,SubClass被称为子类或派生类,SuperClass被称为父类或基类。继承的规则如下:
- 可以继承父类中public和protected修饰的属性和方法,不论子类和父类是否在同一个包中。
- 可以继承默认访问修饰符修饰的属性和方法,但是子类和父类必须在同一个包中。
- 无法继承private修饰的属性和方法。
- 无法继承父类的构造方法。
注意
在Java中只支持单继承,即每个类只能有一个直接父类。
2.1.2 继承的应用
我们来举一个例子吧:
//矩形类
public class Rectangle {
//私有类
private int length;//长
private int width;//宽
//无参的公共构造函数
public Rectangle() {
//构造函数将length和width都初始化为0
length = 0;
width = 0;
}
//有参的构造函数
public Rectangle(int length, int width) {
this.length = length;
this.width = width;
}
//定义get方法和set方法
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
// 添加这个方法来计算矩形的面积
public int calculateArea() {
return length * width;
}
}
//立方类
//定义Cube类,它继承了Rectangle类,并编写它的专有属性。
public class Cube extends Rectangle{
private int height;//立方属性
//无参构造函数
public Cube(){
super();
height = 0;
}
//有参构造函数
public Cube(int length,int width,int height){
//super 关键字用于调用父类的构造方法、方法或属性
super(length,width);
this.height = height;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int calculateVolume() {
return super.calculateArea()*height;
}
}
//测试类
public class Test {
public static void main(String[] args) {
//创建Rectangle对象并调用calculateArea()方法
Rectangle rectangle = new Rectangle(5,10);
int area = rectangle.calculateArea();
System.out.println("矩形的面积为:"+area);
//创建立方体对象,new个Cube方法
Cube cube = new Cube(5,10,3);
int volume = cube.calculateVolume();
System.out.println("立方体的体积为:"+volume);
}
}
2.1.3 Object类
事实上,所有的Java类都直接或间接地继承Object类,它位于java.lang包中。Object类是所有Java的“祖先”。在定义一个类时,没有使用extends关键字,也就没有显式地继承某个类,那么这个类直接继承Object类。所有对象都继承Object类的方法。
方法 | 说明 |
---|---|
toString | 返回当前对象本身的有关信息,返回字符串对象 |
boolean equals() | 比较两个对象是否是同一个对象,若是,则返回true |
Object clone() | 生成当前对象的一个副本,并返回 |
int hashCode() | 返回该对象的哈希代码值 |
getClass() | 获取当前对象所属的类信息,返回Class对象,即运行时类 |
Object类中的equals()方法来比较两个对象是否同一对象,若是,则返回true,而字符串对象的equals()方法用来比较两个字符串的值是否相等,java.lang.String类等重写了Object类中的equals()方法,那么,什么是方法重写嘞?
2.2 继承关系中的方法重写
子类通过继承可以拥有和父类相同的特征和行为,另外,子类也可以根据需要,定义自己特有的行为,即沿袭了父类的方法名称,又重新实现了父类方法,这就是方法重写,下面详细介绍这种用法。
2.2.1 方法重写
在Java中,方法重写(@Override)是面向对象编程中一个重要的概念。当子类继承了一个父类,并且子类想要改变或扩展父类中的某个方法的行为时,就可以在子类中重写这个方法。重写的方法必须与父类中被重写的方法具有相同的名称、参数列表和返回类型(或者子类的返回类型是父类返回类型的子类)。
下面是一个简单的例子,展示了如何在子类中重写父类的方法:
public class Animal {
// 父类中的方法
public void makeSound() {
System.out.println("动物发出声音:");
}
}
public class Dog extends Animal {
// 子类重写父类的方法
@Override // 使用@Override注解可以帮助编译器检查是否正确地重写了方法
public void makeSound() {
System.out.println("狗叫:汪汪");
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Animal();
animal.makeSound(); // 输出 "动物发出声音"
Dog dog = new Dog();
dog.makeSound(); // 输出 "狗叫:汪汪"
// 注意:如果尝试将Dog对象赋值给Animal引用并调用makeSound,也会调用Dog的方法
Animal anotherDog = new Dog(); // 多态性
anotherDog.makeSound(); // 输出 "狗叫:汪汪",因为实际运行的是Dog的makeSound方法
}
}
在这个例子中,Dog类继承了Animal类,并重写了makeSound()方法。当通过Dog对象调用makeSound()方法时,会执行Dog类中重写的方法。即使我们将Dog对象赋值给一个Animal类型的引用(如anotherDog),由于Java的多态性,调用makeSound()方法时仍然会执行Dog类中重写的方法(这就是动态方法分派)。
@Override注解是可选的,但它是一个好的编程习惯,因为它可以帮助编译器检查你确实重写了父类中的方法,而不是意外地创建了一个新方法。如果父类中没有与你尝试重写的方法签名匹配的方法,编译器会报错。
方法重写必须遵守以下规则:
- 重写方法和被重写方法必须具有相同的方法名。
- 重写方法和被重写方法必须具有相同的参数列表。
- 重写方法的返回值类型必须和被重写方法的返回值类型相同或是其子类。
- 重写方法不能缩小被重写方法的访问权限。
2.2.2 super关键字
如果想在子类中调用父类的被重写的方法,应该如何实现呢?
super关键字在编程中,特别是在Java等面向对象的编程语言中,扮演着重要角色。以下是关于super关键字的详细解释和用法:
一、定义与理解
super关键字用于引用当前对象的父类对象。在存在继承关系的类中,子类可以继承父类的属性和方法。但当子类需要直接访问或调用父类的特定属性、方法或构造方法时,就可以使用super关键字。
二、主要用途
1.调用父类的构造方法:
- 在子类的构造方法中,可以使用super()来显式调用父类的构造方法。
- 如果子类的构造方法中没有显式调用父类的构造方法,那么编译器会自动调用父类的无参构造方法(如果存在)。
- super()必须位于子类构造方法的第一行。
2.访问父类的成员:
- 当子类重写了父类的方法或变量时,可以使用super来访问父类中的定义。
- 使用super.父类属性名或super.父类方法名的方式可以调用父类中被重写的属性或方法。
3.解决歧义:
- 在某些情况下,子类可能会重写父类的方法或变量,而同时又需要调用父类中的定义,这时可以使用super来消除歧义。
三、用法示例
- 调用父类构造方法:
public class Animal {
public Animal() {
System.out.println("动物构造函数调用");
}
}
public class Dog extends Animal{
public Dog() {
super(); // 调用父类的构造方法
System.out.println("调用了Dog构造函数");
}
}
- 访问父类的成员:
public class Parent {
int x = 10;
}
public class Child extends Parent{
int x = 20;
void display() {
System.out.println(super.x); // 访问父类的 x 变量, 输出 10
System.out.println(this.x); // 访问子类的 x 变量, 输出 20
}
}
四、总结
super关键字是Java中提供的一种机制,它使得子类可以方便地访问和操作其父类的成员。它是面向对象编程中继承概念的一个重要组成部分,有助于实现代码的重用和多态性。在编程中,super关键字允许我们直接访问父类中的属性、方法或构造函数,确保我们访问的是父类中的成员而不是子类中的。在子类构建过程中,可以使用super()调用父类的初始化操作。
super关键字语法:
//super关键字语法格式
访问父类构造方法 : super(参数)
访问父类属性 / 方法 : super.< 父类属性 / 方法 >
使用super关键字时,需要注意以下几点:
- super关键字必须出现在子类(子类的方法和构造方法)中,而不允许在其他位置。
- 可以访问父类的成员,如父类的属性、方法、构造方法。
- 注意访问权限的限制,如无法通过super关键字访问private成员。
注意:
(1)在构造方法中如果有this语句或super语句,则只能是第一条语句。
(2)在一个构造方法中,不允许同时使用this关键字和super关键字调用构造方法(否则就有两条第一条语句)。
(3)在静态方法中不允许出现this关键字或super关键字。
(4)在实例方法中,this语句和super语句不要求是第一条语句,可以共存。
2.2.3 继承关系中的构造方法
在Java中,一个类的构造方法在如下两种情况下总会被执行。
创建该类的对象(实例化)。
创建该类的子类的对象(子类的实例化)。
在Java中,继承关系中的构造方法调用是一个重要的概念。下面是一个简单的示例,展示了如何在子类的构造方法中调用父类的构造方法:
public class Parent {
private int parentField;
// 父类的无参构造方法
public Parent() {
this.parentField = 0;
System.out.println("调用了父级的无参数构造函数");
// 父类的有参构造方法
public Parent(int value) {
this.parentField = value;
System.out.println("调用了带有参数的父级构造函数。值:" + value);
}
// 父类的一个方法(可选)
public void display() {
System.out.println("调用了父级的显示方法。字段值:" + parentField);
}
}
public class Child extends Parent {
private int childField;
// 子类的构造方法,显式调用父类的有参构造方法
public Child(int parentValue, int childValue) {
super(parentValue); // 调用父类的有参构造方法
this.childField = childValue;
System.out.println("调用了子的构造函数。子值: " + childValue);
}
// 子类重写父类的方法(可选)
@Override
public void display() {
super.display(); // 调用父类的display方法
System.out.println("调用了子项的显示方法。子值:" + childField);
}
}
public class TestInheritance {
public static void main(String[] args) {
Child child = new Child(10, 20); // 创建Child对象,会先调用Parent的有参构造方法
child.display(); // 调用Child的display方法,会先调用Parent的display方法
}
}
在这个示例中:
- Parent 类有两个构造方法: 一个无参的和一个有参的。
- Child 类继承自 Parent 类: 并有一个构造方法,该构造方法通过 super(parentValue) 显式调用 Parent 类的有参构造方法。
- 在 main 方法中,我们创建了一个 Child 对象 并传入了两个参数。这些参数被用于调用 Child 类的构造方法,进而调用 Parent 类的有参构造方法。
- Child 类还重写了 Parent 类的 display 方法并在自己的 display 方法中通过 super.display() 调用了父类的 display 方法。
子类继承父类时构造方法的调用规则如下:
- 如果在类的构造方法中没有通过super关键字显示调用父类的带参构造方法,也没有通过this关键字显式调用自身的其他构造方法,则系统会默认先调用父类的无参构造方法。在这种情况下,是否写"super();"语句,效果是一样的。
- 如果在子类的构造方法中通过super关键字显式地调用了父类的带参构造方法,那么将执行父类相应的构造方法,而不执行父类无参构造方法。
- 如果在子类的构造方法中通过this关键字显式地调用了自身的其他构造方法,那么在相应构造方法中遵循以上两条规则。
- 如果存在多级继承关系,则在创建一个子类对象时,以上规则会多次向更高一级父类应用,直到执行顶级父类Object类的无参构造方法为止。