
本教程深入探讨java中`super`关键字在子类中对父类属性赋值的行为。我们将解释实例变量的独立性、`super`关键字的作用范围,并通过代码示例阐明,`super`仅作用于当前子类实例中继承的父类属性,而不会影响其他独立的父类实例。
在Java的面向对象编程中,继承是实现代码复用和多态性的核心机制。子类可以继承父类的属性和方法。然而,对于实例变量的赋值和访问,尤其是结合super关键字使用时,常常会引起一些混淆。本教程将通过一个具体的案例,详细解析super关键字在子类中修改父类属性时的行为,帮助开发者理解实例变量的作用域和对象实例的独立性。
1. 理解Java中的实例变量与对象实例
在Java中,当一个类定义了实例变量(非静态变量)时,每创建一个该类的对象实例,该对象就会拥有这些实例变量的一份独立副本。这意味着,即使两个对象是同一个类的实例,它们各自的实例变量也是相互独立的,一个对象的变量值改变不会影响另一个对象的变量值。
当子类继承父类时,子类对象同样会拥有父类中定义的实例变量的副本。这些变量成为子类对象自身的一部分。例如,如果Fruit类有一个price实例变量,那么Apple类继承Fruit后,每个Apple对象也会包含一个price实例变量。
2. super关键字的作用机制
super关键字在Java中有两个主要用途:
立即学习“Java免费学习笔记(深入)”;
- 调用父类构造器: 在子类构造器中,使用super()可以调用父类的构造器来初始化父类部分。
- 访问父类成员: 当子类重写了父类的方法或隐藏了父类的实例变量时,可以使用super.method()或super.variable来显式地访问父类中定义的原始成员。
需要特别强调的是,无论是访问父类的方法还是实例变量,super关键字始终是在当前对象实例的上下文中操作的。super.variable并不意味着它会去操作一个独立的父类对象,而是指访问当前子类对象中继承自父类的那个实例变量。
3. 案例分析:子类中修改父类属性
我们通过以下代码示例来深入分析super关键字在子类中对父类属性赋值的行为。
package Practice.FruitConst;
public class App {
public static void main(String[] args) {
// 创建一个Fruit类的实例
Fruit fruit = new Fruit();
// 创建一个Apple类的实例
Apple apple = new Apple();
// 通过Apple实例的setter方法设置价格
apple.setPrice(100.0);
// 调用Apple实例的pp方法打印价格
apple.pp();
// 打印Fruit实例的价格
System.out.println("fruit " + fruit.price);
}
}
// Apple类继承自Fruit类
class Apple extends Fruit {
@Override
public void setPrice(Double price) {
// 使用super关键字为当前Apple实例的price属性赋值
super.price = price;
}
public void pp() {
// 打印当前Apple实例的price属性
System.out.println("apple " + this.price);
// 再次打印当前Apple实例中继承的price属性
System.out.println("fruit? " + super.price);
}
}
class Fruit {
String name;
String color;
double price; // 实例变量
// ... 省略其他方法,如toString, getName, setName, getColor, setColor ...
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
}代码执行流程及输出解析:
-
Fruit fruit = new Fruit();
- 在堆内存中创建了一个Fruit类的对象实例,并将其引用赋值给fruit变量。
- 这个fruit对象拥有自己的name、color和price实例变量。price的默认值是0.0(double类型的默认值)。
-
Apple apple = new Apple();
- 在堆内存中创建了一个Apple类的对象实例,并将其引用赋值给apple变量。
- 由于Apple继承自Fruit,这个apple对象也拥有自己的name、color和price实例变量(这些是继承自Fruit的)。price的默认值同样是0.0。
- 重要提示: fruit和apple是两个完全独立的、互不影响的对象实例。
-
apple.setPrice(100.0);
- 调用apple对象(Apple类型)中重写的setPrice方法。
- 进入Apple类的setPrice方法:
@Override public void setPrice(Double price) { super.price = price; // 关键行 } - super.price = price; 这行代码的含义是:将当前apple对象中继承自Fruit的那个price实例变量的值设置为100.0。
- 注意: 此时,只有apple对象的price变为了100.0。fruit对象的price仍然是0.0,因为它是一个独立的实例,其值并未被修改。
-
apple.pp();
- 调用apple对象(Apple类型)的pp方法。
- 进入Apple类的pp方法:
public void pp(){ System.out.println("apple " + this.price); System.out.println("fruit? " + super.price); } - System.out.println("apple " + this.price);
- this.price指向当前apple对象的price实例变量,其值在setPrice方法中被设置为100.0。
- 输出:apple 100.0
- System.out.println("fruit? " + super.price);
- super.price同样指向当前apple对象中继承自Fruit的那个price实例变量,其值也是100.0。
- 输出:fruit? 100.0
- 可以看到,在Apple对象内部,this.price和super.price都指向同一个存储位置,即apple实例中继承的price属性。
-
System.out.println("fruit " + fruit.price);
- 这行代码访问的是最开始创建的fruit对象的price实例变量。
- 由于fruit对象与apple对象是完全独立的,fruit对象的price从未被修改过,所以它仍然保持默认值0.0。
- 输出:fruit 0.0
最终输出:
apple 100.0 fruit? 100.0 fruit 0.0
这与原始问题中观察到的输出完全一致,也解释了为什么fruit对象的price没有被更新。
4. 总结与注意事项
通过上述分析,我们可以得出以下关键结论:
- 实例变量的独立性: 在Java中,每创建一个对象实例,它都会拥有自己独立的实例变量副本。修改一个对象的实例变量不会影响其他对象的同名实例变量。
- super关键字的作用域: super关键字用于访问当前对象实例中继承自父类的成员。它不会去操作或影响任何其他独立的父类对象实例。在子类方法中使用super.variable,实际上是在修改当前子类对象内部所包含的父类部分的该变量。
- this与super在实例变量上的行为: 在没有变量隐藏的情况下,this.variable和super.variable(以及直接使用variable)在子类内部通常都指向同一个实例变量,即当前子类对象中继承的那个变量。super的显式使用更多是为了消除歧义或在变量被子类隐藏时访问父类版本。
注意事项:
- 初学者常常混淆“类”和“对象实例”的概念。类是蓝图,对象是根据蓝图创建的具体实体。每个对象都是独立的。
- 如果想要让所有Fruit或Apple实例共享同一个price值,应该将price声明为static(静态变量)。静态变量属于类本身,而不是类的任何特定实例。
理解这些基本概念对于掌握Java的面向对象编程至关重要,尤其是在处理继承和多态性时,能够避免因误解super关键字和实例变量作用域而导致的逻辑错误。










