`super` 关键字在继承中的作用:访问父类成员与构造器

super 关键字:继承关系中的魔法钥匙

各位看官,今天咱们要聊聊 Java 继承中的一个关键角色——super 关键字。别被它严肃的名字吓到,其实 super 就像一把魔法钥匙,能打开通往父类宝藏的大门,让你在子类中自由地访问和使用父类的成员和构造器。

继承:家族企业的传承

在深入 super 之前,咱们先简单回顾一下继承的概念。你可以把继承想象成一个家族企业,父类是老一代创始人,子类是年轻一代继承者。子类可以继承父类的资产(属性)和经营方式(方法),并在其基础上进行创新和发展。

// 父类:动物
class Animal {
    protected String name;
    protected int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void makeSound() {
        System.out.println("动物发出叫声...");
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

// 子类:狗
class Dog extends Animal {
    private String breed;

    public Dog(String name, int age, String breed) {
        // 调用父类的构造器
        super(name, age);
        this.breed = breed;
    }

    @Override // 方法重写
    public void makeSound() {
        System.out.println("汪汪汪!");
    }

    public String getBreed() {
        return breed;
    }
}

public class Main {
    public static void main(String[] args) {
        Dog myDog = new Dog("旺财", 3, "金毛");
        System.out.println("名字: " + myDog.getName()); // 访问父类的属性
        System.out.println("年龄: " + myDog.getAge());   // 访问父类的属性
        System.out.println("品种: " + myDog.getBreed()); // 访问子类的属性
        myDog.makeSound(); // 调用子类重写的方法
    }
}

在这个例子中,Dog 类继承了 Animal 类。Dog 类拥有了 Animal 类的 nameage 属性,以及 getNamegetAge 方法。同时,Dog 类还拥有自己的 breed 属性和重写的 makeSound 方法。

super 的两种主要用途

super 关键字主要有两种用途:

  1. 调用父类的构造器 (super(...)):在子类的构造器中,调用父类的构造器来初始化从父类继承的属性。
  2. 访问父类的成员 (super.member):在子类中,访问父类被隐藏或重写的方法或属性。

1. 调用父类的构造器:初始化继承的遗产

当子类继承父类时,子类构造器需要负责初始化从父类继承的属性。为了确保父类属性的正确初始化,通常需要在子类的构造器中调用父类的构造器。 这就是 super(...) 的用武之地。

为什么需要调用父类的构造器?

  • 确保父类属性的正确初始化:父类的构造器可能包含一些重要的初始化逻辑,例如设置默认值或执行必要的计算。如果子类不调用父类的构造器,这些初始化逻辑可能不会被执行,导致父类属性处于未定义或错误的状态。
  • 维护父类的封装性:父类可能有一些私有属性,只能通过父类的构造器进行初始化。子类无法直接访问这些私有属性,因此必须通过调用父类的构造器来间接初始化它们。
  • 简化代码:如果子类需要手动初始化从父类继承的属性,代码会变得冗长且容易出错。调用父类的构造器可以避免重复的代码,并提高代码的可读性和可维护性。

super(...) 的语法

super(...) 必须是子类构造器中的第一条语句。括号中的参数列表必须与父类构造器的参数列表匹配。如果没有显式地调用父类的构造器,Java 编译器会自动插入一个对父类无参构造器的调用 (super())。 但是,如果父类没有无参构造器,那么子类必须显式地调用父类的某个有参构造器,否则编译器会报错。

// 父类:车辆
class Vehicle {
    protected String brand;
    protected String model;

    public Vehicle(String brand, String model) {
        this.brand = brand;
        this.model = model;
        System.out.println("Vehicle 构造器被调用");
    }
}

// 子类:汽车
class Car extends Vehicle {
    private int numberOfDoors;

    public Car(String brand, String model, int numberOfDoors) {
        // 调用父类的构造器
        super(brand, model);
        this.numberOfDoors = numberOfDoors;
        System.out.println("Car 构造器被调用");
    }

    public int getNumberOfDoors() {
        return numberOfDoors;
    }
}

public class Main {
    public static void main(String[] args) {
        Car myCar = new Car("丰田", "卡罗拉", 4);
        System.out.println("品牌: " + myCar.brand);
        System.out.println("型号: " + myCar.model);
        System.out.println("车门数量: " + myCar.getNumberOfDoors());
    }
}

在这个例子中,Car 类的构造器首先调用 super(brand, model) 来初始化从 Vehicle 类继承的 brandmodel 属性。 然后,Car 类的构造器初始化自己的 numberOfDoors 属性。

如果父类没有无参构造器会怎样?

如果父类没有无参构造器,子类必须显式地调用父类的某个有参构造器。否则,编译器会报错。

class Parent {
    private int value;

    public Parent(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

class Child extends Parent {
    private String name;

    public Child(int value, String name) {
        super(value); // 必须显式调用父类的有参构造器
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

2. 访问父类的成员:穿越重重迷雾

在子类中,有时需要访问父类的成员(属性或方法)。 但是,如果子类定义了与父类同名的成员(方法重写或属性隐藏),或者父类成员的访问权限限制了子类的访问,那么直接使用成员名可能会导致混淆或错误。 这时,super.member 就能派上用场,它可以明确地指定要访问的是父类的成员。

方法重写 (Overriding)

当子类定义了与父类同名、参数列表也相同的方法时,就发生了方法重写。 子类重写的方法会覆盖父类的方法。 如果需要在子类中调用父类被重写的方法,可以使用 super.methodName()

class Animal {
    public void makeSound() {
        System.out.println("动物发出叫声...");
    }
}

class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("喵喵喵!");
    }

    public void parentMakeSound() {
        super.makeSound(); // 调用父类被重写的方法
    }
}

public class Main {
    public static void main(String[] args) {
        Cat myCat = new Cat();
        myCat.makeSound(); // 调用子类重写的方法
        myCat.parentMakeSound(); // 调用父类被重写的方法
    }
}

在这个例子中,Cat 类重写了 Animal 类的 makeSound 方法。 myCat.makeSound() 调用的是 Cat 类重写的方法,而 myCat.parentMakeSound() 通过 super.makeSound() 调用的是 Animal 类被重写的方法。

属性隐藏 (Hiding)

当子类定义了与父类同名的属性时,就发生了属性隐藏。 子类的属性会隐藏父类的属性。 但是,父类的属性仍然存在,只是在子类中无法直接访问。 如果需要在子类中访问父类被隐藏的属性,可以使用 super.attributeName

class Parent {
    protected int age = 50;
}

class Child extends Parent {
    protected int age = 10;

    public void printAges() {
        System.out.println("子类的年龄: " + age);
        System.out.println("父类的年龄: " + super.age);
    }
}

public class Main {
    public static void main(String[] args) {
        Child myChild = new Child();
        myChild.printAges();
    }
}

在这个例子中,Child 类定义了与 Parent 类同名的 age 属性。 myChild.age 访问的是 Child 类的 age 属性,而 super.age 访问的是 Parent 类的 age 属性。

访问权限限制

super 关键字可以用于访问父类的 publicprotecteddefault (包访问权限) 成员。 但是,无法使用 super 关键字访问父类的 private 成员。 这是因为 private 成员只能在声明它们的类中访问。

class Parent {
    private int privateValue = 10;
    protected int protectedValue = 20;

    public int getPrivateValue() {
        return privateValue;
    }
}

class Child extends Parent {
    public void printValues() {
        // System.out.println("父类的私有值: " + super.privateValue); // 错误:无法访问父类的私有成员
        System.out.println("父类的保护值: " + super.protectedValue);
        System.out.println("父类的私有值 (通过 getter): " + super.getPrivateValue());
    }
}

在这个例子中,Child 类无法直接访问 Parent 类的 privateValue 属性,因为它是 private 的。 但是,Child 类可以通过调用 Parent 类的 getPrivateValue() 方法来间接访问 privateValue 属性。

super 的总结

super 关键字是 Java 继承中一个非常重要的工具,它可以帮助子类:

  • 初始化从父类继承的属性:通过调用父类的构造器。
  • 访问父类被隐藏或重写的方法或属性:通过 super.member
用途 语法 说明
调用父类的构造器 super(...) 必须是子类构造器中的第一条语句。括号中的参数列表必须与父类构造器的参数列表匹配。如果父类没有无参构造器,子类必须显式地调用父类的某个有参构造器。
访问父类被重写的方法 super.methodName() 用于在子类中调用父类被重写的方法。
访问父类被隐藏的属性 super.attributeName 用于在子类中访问父类被隐藏的属性。
访问父类的 publicprotecteddefault 成员 super.member super 关键字可以用于访问父类的 publicprotecteddefault (包访问权限) 成员。
无法访问父类的 private 成员 N/A 这是因为 private 成员只能在声明它们的类中访问。

掌握 super 关键字,你就能更好地理解和使用 Java 的继承机制,编写出更加清晰、简洁和可维护的代码。

super 的使用场景案例

  1. 模板方法模式:在模板方法模式中,父类定义算法的骨架,子类实现算法的某些步骤。 子类可以使用 super 关键字调用父类的方法,以确保算法的骨架得到正确执行。

    abstract class AbstractClass {
        public void templateMethod() {
            step1();
            step2();
            step3();
        }
    
        protected abstract void step1();
    
        protected void step2() {
            System.out.println("AbstractClass: Step 2");
        }
    
        protected abstract void step3();
    }
    
    class ConcreteClass extends AbstractClass {
        @Override
        protected void step1() {
            System.out.println("ConcreteClass: Step 1");
        }
    
        @Override
        protected void step3() {
            System.out.println("ConcreteClass: Step 3");
            super.step2(); // 调用父类的 step2 方法
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            AbstractClass myObject = new ConcreteClass();
            myObject.templateMethod();
        }
    }
  2. 事件处理:在事件处理中,子类可以重写父类的事件处理方法,并在子类的方法中使用 super 关键字调用父类的事件处理方法,以确保父类的事件处理逻辑得到执行。

    class ParentComponent {
        public void onClick() {
            System.out.println("ParentComponent: onClick");
        }
    }
    
    class ChildComponent extends ParentComponent {
        @Override
        public void onClick() {
            System.out.println("ChildComponent: onClick");
            super.onClick(); // 调用父类的 onClick 方法
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            ChildComponent myComponent = new ChildComponent();
            myComponent.onClick();
        }
    }
  3. 组合模式:在组合模式中,叶子节点和组合节点都继承自同一个抽象类。 组合节点可以使用 super 关键字调用父类的方法,以确保组合节点的行为与叶子节点保持一致。

    interface Component {
        void operation();
    }
    
    class Leaf implements Component {
        @Override
        public void operation() {
            System.out.println("Leaf: operation");
        }
    }
    
    class Composite implements Component {
        private List<Component> children = new ArrayList<>();
    
        public void add(Component child) {
            children.add(child);
        }
    
        public void remove(Component child) {
            children.remove(child);
        }
    
        @Override
        public void operation() {
            System.out.println("Composite: operation");
            for (Component child : children) {
                child.operation();
            }
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Composite composite = new Composite();
            composite.add(new Leaf());
            composite.add(new Leaf());
            composite.operation();
        }
    }

总而言之,super 关键字是理解和运用 Java 继承机制的关键。 掌握 super 关键字,你就能更好地利用继承的优势,编写出更加灵活、可扩展和可维护的代码。希望这篇文章能帮助你更好地理解 super 关键字,并在你的 Java 编程之旅中发挥它的魔力!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注