【Java SE】深入理解继承与多态
文章目录
- 一、 继承(Inheritance)
- 1.1 为什么需要继承?
- 1.2 继承的概念
- 1.3 继承的语法
- 1.4 父类成员访问
- 1.5 super关键字
- 1.6 子类构造方法
- 1.7 初始化顺序
- 1.8 访问权限与继承
- 1.9 继承方式
- 1.10 final关键字
- 1.11 继承与组合
- 二、 多态(Polymorphism)
- 2.1 多态的概念
- 2.2 多态的实现条件
- 2.3 方法重写(Override)
- 2.4 向上转型与向下转型
- 2.5 多态的优缺点
- 2.6 避免在构造方法中调用重写方法
- 三、实际应用示例
- 3.1 图形绘制系统
- 3.2 员工管理系统
- 总结
一、 继承(Inheritance)
1.1 为什么需要继承?
在现实世界中,许多对象之间存在共性。例如,狗和猫都是动物,它们共享一些基本特征如名字、年龄和体重,以及行为如吃饭和睡觉。如果在代码中为每个类单独定义这些共性的属性和方法,会导致大量重复代码,增加维护成本。
继承机制允许我们抽取这些共性,形成父类(基类),让子类(派生类)继承父类的特征,并添加自己特有的属性和方法。这样不仅减少了代码冗余,也提高了代码的可维护性和可读性。
继承主要解决的问题是:共性的抽取,实现代码复用。
1.2 继承的概念
继承是面向对象程序设计使代码可以复用的最重要手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能。这样产生的新类称为派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。
例如,我们可以创建一个Animal类作为父类,然后让Dog和Cat类继承它:
class Animal {String name;int age;public void eat() {System.out.println(name + "正在吃饭");}public void sleep() {System.out.println(name + "正在睡觉");}
}class Dog extends Animal {void bark() {System.out.println(name + "汪汪汪");}
}class Cat extends Animal {void mew() {System.out.println(name + "喵喵喵");}
}
1.3 继承的语法
在Java中,使用extends
关键字实现继承:
class SubClass extends SuperClass {// 子类特有的属性和方法
}
1.4 父类成员访问
在继承体系中,子类可以访问父类的非私有成员(属性和方法)。访问规则如下:
- 子类和父类不存在同名成员变量:子类可以直接访问父类的成员变量
- 子类和父类成员变量同名:遵循"就近原则",优先访问子类自己的成员变量
class Base {int a = 10;int b = 20;
}class Derived extends Base {int a = 30; // 与父类成员变量同名void display() {System.out.println(a); // 输出30(子类的a)System.out.println(super.a); // 输出10(父类的a)System.out.println(b); // 输出20(父类的b)}
}
1.5 super关键字
super
关键字用于在子类中访问父类的成员,特别是在存在同名成员时:
class Derived extends Base {int a;void method() {super.a = 100; // 访问父类的aa = 200; // 访问子类的asuper.method(); // 调用父类的方法}
}
1.6 子类构造方法
子类构造时必须先调用父类构造方法,确保父类成员先初始化:
- 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用父类构造方法
- 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
- 在子类构造方法中,
super(...)
调用父类构造时,必须是子类构造函数中第一条语句。 super(...)
只能在子类构造方法中出现一次,并且不能和this
同时出现
class Base {public Base() {System.out.println("Base构造函数");}
}class Derived extends Base {public Derived() {super(); //可省略System.out.println("Derived构造函数");}
}
1.7 初始化顺序
静态代码块只执行一次
class Parent {static { System.out.println("父类静态代码块"); }{ System.out.println("父类实例代码块"); }public Parent() { System.out.println("父类构造方法"); }
}class Child extends Parent {static { System.out.println("子类静态代码块"); }{ System.out.println("子类实例代码块"); }public Child() { System.out.println("子类构造方法"); }
}// 测试代码
public class Test {public static void main(String[] args) {Child c1 = new Child();System.out.println("==========");Child c2 = new Child();}
}
输出结果:
题目: 以下程序的输出结果是
class X{Y y=new Y();//1public X(){//2System.out.print("X");}
}
class Y{public Y(){//3System.out.print("Y");}
}
public class Z extends X{Y y=new Y();//4public Z(){//5System.out.print("Z");}public static void main(String[] args) {new Z();}
}
- 首先我们要明确执行顺序是
父类静态代码->子类静态代码->父类实例代码->子类实例代码->父类构造函数->子类构造函数
- 这段代码中没有静态代码,先找父类的实例代码(1和2),这里1输出
Y
,2输出x
- 接着执行子类的实例代码(4和5),4输出
Y
,5输出Z
- 因此输出结果为
YXYZ
1.8 访问权限与继承
Java中的访问修饰符影响继承关系中成员的可见性:
修饰符 | 同一包中子类 | 不同包中子类 | 不同包非子类 |
---|---|---|---|
private | ❌ | ❌ | ❌ |
default | ✅ | ❌ | ❌ |
protected | ✅ | ✅ | ❌ |
public | ✅ | ✅ | ✅ |
需要注意的是,父类的private
成员虽然不能被直接访问,但仍然被继承到子类中。
1.9 继承方式
Java支持以下几种继承方式:
- 单继承:一个子类只能有一个直接父类
- 多层继承:例如
A → B → C
- 不同类继承同一父类:例如
B → A
,C → A
- ❌ 不支持多继承(如
C extends A, B
)
1.10 final关键字
final
关键字用于限制继承:
final
修饰变量:常量,不可修改final
修饰方法:不可重写final
修饰类:不可继承(如String
类)
final class FinalClass {// 这个类不能被继承
}class Child extends FinalClass { // 编译错误
}
1.11 继承与组合
- 继承表示"is a"关系(如狗是动物)
- 组合表示"a part of"关系(如汽车有发动机):
示例:
- 组合
// 轮胎类
class Tire{// ...
}
// 发动机类
class Engine{// ...
}
// 车载系统类
class VehicleSystem{// ...
}class Car{
private Tire tire; // 可以复用轮胎中的属性和方法
private Engine engine; // 可以复用发动机中的属性和方法
private VehicleSystem vs; // 可以复用车载系统中的属性和方法
// ...
}
- 继承
class Benz extend Car{
// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来
}
在实际开发中,应优先使用组合而非继承,因为组合提供了更好的灵活性和更低的耦合度。
二、 多态(Polymorphism)
2.1 多态的概念
多态是指同一行为在不同对象上表现出不同状态。它是面向对象编程的核心特性之一,允许我们使用统一的接口处理不同类型的对象。
现实生活中的多态例子:
- 打印机:彩色打印机 vs 黑白打印机 → 打印效果不同
- 动物:猫吃鱼 vs 狗吃骨头 → 吃的行为不同
2.2 多态的实现条件
在Java中实现多态需要满足以下条件:
- 必须在继承体系下
- 子类必须重写父类方法
- 通过父类的引用调用重写的方法
class Animal {public void makeSound() {System.out.println("动物发出声音");}
}class Dog extends Animal {@Overridepublic void makeSound() {System.out.println("汪汪汪");}
}class Cat extends Animal {@Overridepublic void makeSound() {System.out.println("喵喵喵");}
}public class Test{public static void main(String[] args) {Animal myAnimal = new Animal();Animal myDog = new Dog();Animal myCat = new Cat();myAnimal.makeSound(); // 输出: 动物发出声音myDog.makeSound(); // 输出: 汪汪汪myCat.makeSound(); // 输出: 喵喵喵}
}
2.3 方法重写(Override)
方法重写是实现多态的基础,必须满足以下规则:
- 方法名、参数列表(顺序,类型,个数)相同
- 返回值类型相同(或为父类返回值的子类)
- 访问权限不能比父类更严格(可放宽)
- 不能重写
static
、private
、final
方法
使用@Override
注解可以帮助编译器检查重写是否正确:
class Parent {protected void show() {System.out.println("Parent的show方法");}
}class Child extends Parent {@Override // 确保这是重写而不是重载public void show() { // 访问权限从protected变为public(放宽)System.out.println("Child的show方法");}
}
与重载的区别:
区别点 | 重写(override) | 重载(override) |
---|---|---|
参数列表 | 不能修改 | 必须修改 |
返回类型 | 不能修改 | 【除非可以构成父子类关系】 可以修改 |
访问限定符 | 不能做更严格的限制 | (可以降低限制) 可以修改 |
2.4 向上转型与向下转型
- 向上转型(Upcasting)是子类对象赋给父类引用,这是安全的且会自动进行:
Animal animal = new Dog(); // 向上转型
注意: 父类引用不可以直接调用子类特有的方法(这也是向上转型的缺点)
animal.bark()//错误
- 向下转型(Downcasting)是父类引用转回子类类型,需要显式转换且可能失败:
Animal animal = new Dog();
Dog dog = (Dog) animal; // 向下转型
向下转型可能抛出ClassCastException
,因此应该先使用instanceof
进行检查:
if (animal instanceof Dog) {Dog dog = (Dog) animal;dog.bark();
}
2.5 多态的优缺点
优点:
- 提高代码的可扩展性和可维护性
- 减少代码的圈复杂度(减少if-else语句)
- 实现接口与实现的分离
缺点:
- 性能略有下降(动态绑定需要运行时确定方法)
- 无法直接访问子类特有的成员
2.6 避免在构造方法中调用重写方法
在构造方法中调用可重写的方法可能导致意想不到的行为,因为子类可能还没有完全初始化:
class Parent {public Parent() {show(); // 危险!可能调用子类重写的方法}public void show() {System.out.println("Parent的show方法");}
}class Child extends Parent {private int value = 10;@Overridepublic void show() {System.out.println("Child的show方法,value = " + value);}
}public class Test {public static void main(String[] args) {Child child = new Child(); // 输出: Child的show方法,value = 0}
}
在上面的例子中,value
的值为0而不是10,因为父类构造函数调用show()
时,子类的初始化还没有完成。
三、实际应用示例
3.1 图形绘制系统
下面是一个使用多态的图形绘制系统示例:
class Shape {public void draw() {System.out.println("绘制图形");}public double getArea() {return 0;}
}class Circle extends Shape {private double radius;public Circle(double radius) {this.radius = radius;}@Overridepublic void draw() {System.out.println("绘制圆形,半径: " + radius);}@Overridepublic double getArea() {return Math.PI * radius * radius;}
}class Rectangle extends Shape {private double width;private double height;public Rectangle(double width, double height) {this.width = width;this.height = height;}@Overridepublic void draw() {System.out.println("绘制矩形,宽度: " + width + ", 高度: " + height);}@Overridepublic double getArea() {return width * height;}
}public class DrawingSystem {public static void main(String[] args) {Shape[] shapes = new Shape[3];shapes[0] = new Circle(5);shapes[1] = new Rectangle(4, 6);shapes[2] = new Circle(3);for (Shape shape : shapes) {shape.draw();System.out.println("面积: " + shape.getArea());System.out.println("----------");}}
}
3.2 员工管理系统
另一个实际应用是员工管理系统:
class Employee {private String name;private double baseSalary;public Employee(String name, double baseSalary) {this.name = name;this.baseSalary = baseSalary;}public double calculateSalary() {return baseSalary;}public String getName() {return name;}
}class Manager extends Employee {private double bonus;public Manager(String name, double baseSalary, double bonus) {super(name, baseSalary);this.bonus = bonus;}@Overridepublic double calculateSalary() {return super.calculateSalary() + bonus;}
}class Developer extends Employee {private int overtimeHours;private double overtimeRate;public Developer(String name, double baseSalary, int overtimeHours, double overtimeRate) {super(name, baseSalary);this.overtimeHours = overtimeHours;this.overtimeRate = overtimeRate;}@Overridepublic double calculateSalary() {return super.calculateSalary() + (overtimeHours * overtimeRate);}
}public class HRSystem {public static void main(String[] args) {Employee[] employees = new Employee[3];employees[0] = new Employee("张三", 5000);employees[1] = new Manager("李四", 8000, 2000);employees[2] = new Developer("王五", 6000, 10, 100);for (Employee emp : employees) {System.out.println(emp.getName() + "的工资: " + emp.calculateSalary());}}
}
总结
- 继承允许我们创建层次化的类结构,实现代码复用和扩展
- 多态允许我们使用统一的接口处理不同类型的对象,提高代码的灵活性和可扩展性
- 使用
super
关键字可以访问父类的成员,特别是在存在同名成员时 - 子类构造时必须先调用父类构造方法,确保正确的初始化顺序
- 向上转型是安全的,向下转型需要谨慎并使用
instanceof
进行检查 - 避免在构造方法中调用可重写的方法,以防止未初始化的状态
在实际开发中,我们应该遵循"优先使用组合而非继承"的原则,合理使用继承和多态,设计出高内聚、低耦合的系统结构。同时,要注意多态可能带来的性能影响,在性能敏感的场景中谨慎使用。