1. 使用 super 调用父类方法
当你创建子类时,有时需要访问父类的字段或方法,尤其在你已经对其进行了重写或在子类中“遮蔽”了同名成员时。为此,Java 提供了专门的关键字——super。
打个比方,super 就像“妈妈,帮帮忙!”——当你在子类中想要显式地访问父类中定义的内容时。
设想你有一个类 Animal,它有一个方法 eat(),仅仅打印 "动物在进食"。而在类 Cat 中,你希望猫先做点自己的事(例如喵两声),然后仍然执行标准的“动物在进食”。这时就可以用 super.eat()。
当你在子类中重写了一个方法,同时又希望在该方法内部调用父类的实现时,请使用 super.methodName()。
示例:扩展行为
class Animal {
void eat() {
System.out.println("动物在进食");
}
}
class Cat extends Animal {
@Override
void eat() {
System.out.println("猫在闻食物...");
super.eat(); // 调用 Animal 中的 eat() 方法
System.out.println("猫满足地咕噜咕噜叫");
}
}
这是如何工作的?
- 当对 Cat 类型的对象调用 eat() 时,首先执行的是 Cat.eat() 中的代码,即子类自身的逻辑。
- 在该方法内部我们显式调用 super.eat(),也就是来自父类 Animal 的实现。
- 这样就能在不丢失“父类逻辑”的前提下添加额外行为。
实践:在应用中使用
假设在我们的教学应用中,有一个父类 Animal 和子类 Dog、Cat。我们希望在喂食时,既执行通用行为(例如增加饱腹度),也执行每种动物特有的行为。
class Animal {
int satiety = 0;
void eat() {
satiety += 10;
System.out.println("动物在进食。饱腹度:" + satiety);
}
}
class Dog extends Animal {
@Override
void eat() {
System.out.println("狗在吃饭前摇尾巴");
super.eat();
}
}
现在,如果调用 dog.eat(),你会看到两条消息,并且 satiety 会正确增加。
2. 使用 super 访问父类字段
如果你在子类中声明了与父类同名的字段,它会“遮蔽”父类字段。有时需要访问父类中的原始字段——此时可以使用 super.fieldName。
示例:字段遮蔽
class Animal {
String name = "动物";
}
class Cat extends Animal {
String name = "猫";
void printNames() {
System.out.println("来自 Cat 的名称: " + name);
System.out.println("来自 Animal 的名称: " + super.name);
}
}
调用 new Cat().printNames(); 将输出:
来自 Cat 的名称: 猫
来自 Animal 的名称: 动物
在实际开发中,不建议在没有必要的情况下“遮蔽”字段,但了解这种可能性是有用的。
3. 通过 super(...) 调用父类构造函数
对象在继承层次中如何创建?
当你创建子类对象时,会先调用父类的构造函数,然后才调用子类的构造函数。这样做是为了正确初始化所有字段,因为子类“继承”了父类的一部分状态。
显式调用父类构造函数
如果父类有无参构造函数——很简单:Java 会在执行子类构造函数之前自动调用它。但如果父类没有无参构造函数,你必须通过 super(...) 显式调用所需的构造函数。
示例:
class Animal {
String name;
Animal(String name) {
this.name = name;
System.out.println("已创建动物:" + name);
}
}
class Cat extends Animal {
Cat(String name) {
super(name); // 必须!Animal 没有无参构造函数
System.out.println("已创建猫:" + name);
}
}
调用 new Cat("Daisy") 将打印:
已创建动物:Daisy
已创建猫:Daisy
重要:通过 super(...) 调用父类构造函数必须是子类构造函数的第一行。如果你尝试在该调用之前编写其他语句,编译器会报错并提醒你。
如果不显式调用怎么办?
如果父类只有带参数的构造函数,而你没有通过 super(...) 显式调用它,编译器会报错:"constructor Animal in class Animal cannot be applied to given types"。
4. 实用要点
什么时候使用 super?
用于扩展而不是完全替换行为。
有时你并不想完全替换方法的行为,只是想“扩展”它。例如,在父类逻辑之前或之后添加一点额外处理。在这种情况下,请在重写方法体内使用 super.methodName()。
用于初始化继承而来的字段。
如果父类有必须初始化的字段(例如动物的名称),务必通过 super(...) 调用带有相应参数的父类构造函数。
用于访问被隐藏的字段/方法。
如果你因某些原因“遮蔽”了父类的字段或方法,而又需要访问它,请使用 super.fieldName 或 super.methodName()。
super 的限制与特点
- 通过 super(...) 调用父类构造函数只能在构造函数中,并且必须是第一行。
- 不能在子类构造函数之外调用父类构造函数。
- 如果不显式调用 super(...),Java 会尝试调用父类的无参构造函数(如果存在)。
- 关键字 super 不能在静态方法中使用——只能在实例方法和构造函数中使用。
- 如果父类的方法或字段是 private,那么 super 也无能为力:无法访问私有成员。
5. 巩固示例
示例 1:使用 super 扩展方法
class Animal {
void makeSound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
@Override
void makeSound() {
super.makeSound(); // 先执行标准行为
System.out.println("狗在叫:汪汪!");
}
}
示例 2:调用父类构造函数
class Vehicle {
String brand;
Vehicle(String brand) {
this.brand = brand;
System.out.println("交通工具:" + brand);
}
}
class Car extends Vehicle {
int year;
Car(String brand, int year) {
super(brand); // 调用父类构造函数
this.year = year;
System.out.println("汽车 " + brand + ",年份:" + year);
}
}
Car car = new Car("Toyota", 2023);
// 输出:
// 交通工具:Toyota
// 汽车 Toyota,年份:2023
示例 3:典型错误——忘记调用 super(...)
class Animal {
String name;
Animal(String name) {
this.name = name;
}
}
class Cat extends Animal {
Cat() {
// super(); // 错误!没有无参的 Animal() 构造函数
// 需要显式调用 super(name)
super("无名的猫");
}
}
6. 使用 super 时的常见错误
错误 1:在构造函数中,super(...) 不是第一行。
Java 严格要求:通过 super(...) 调用父类构造函数必须是子类构造函数的第一行。如果你尝试在此调用之前做其他事(例如打印一条消息),编译器会报错。
错误 2:父类没有合适的构造函数。
如果父类没有无参构造函数,而你也没有通过 super(...) 调用其他构造函数,编译器无法生成默认调用并会报错。
错误 3:尝试通过 super 访问父类的私有成员。
关键字 super 并不会赋予你访问父类私有字段或方法的“魔法”。凡是被声明为 private 的,对子类依然不可见。
错误 4:在不了解影响的情况下遮蔽字段或方法。
如果你在子类中声明了与父类同名的字段或方法而忘记了这点,可能会产生意料之外的结果。务必记住,在这种情况下访问父类成员只能通过 super。
错误 5:在静态方法中使用 super。
静态方法不属于具体对象,因而不能使用 super。
GO TO FULL VERSION