CodeGym /课程 /JAVA 25 SELF /使用 super:调用父类的构造函数和方法

使用 super:调用父类的构造函数和方法

JAVA 25 SELF
第 17 级 , 课程 2
可用

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 和子类 DogCat。我们希望在喂食时,既执行通用行为(例如增加饱腹度),也执行每种动物特有的行为。

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.fieldNamesuper.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

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION