当前位置: 首页 > ops >正文

【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 父类成员访问

在继承体系中,子类可以访问父类的非私有成员(属性和方法)。访问规则如下:

  1. 子类和父类不存在同名成员变量:子类可以直接访问父类的成员变量
  2. 子类和父类成员变量同名:遵循"就近原则",优先访问子类自己的成员变量
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 子类构造方法

子类构造时必须先调用父类构造方法,确保父类成员先初始化

  1. 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用父类构造方法
  2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
  3. 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句。
  4. 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支持以下几种继承方式:

  1. 单继承:一个子类只能有一个直接父类
  2. 多层继承:例如 A → B → C
  3. 不同类继承同一父类:例如 B → AC → A
  4. 不支持多继承(如 C extends A, B

1.10 final关键字

final关键字用于限制继承:

  1. final修饰变量:常量,不可修改
  2. final修饰方法:不可重写
  3. 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中实现多态需要满足以下条件:

  1. 必须在继承体系下
  2. 子类必须重写父类方法
  3. 通过父类的引用调用重写的方法
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)

方法重写是实现多态的基础,必须满足以下规则:

  • 方法名、参数列表(顺序,类型,个数)相同
  • 返回值类型相同(或为父类返回值的子类)
  • 访问权限不能比父类更严格(可放宽)
  • 不能重写staticprivatefinal方法

使用@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 多态的优缺点

优点

  1. 提高代码的可扩展性和可维护性
  2. 减少代码的圈复杂度(减少if-else语句)
  3. 实现接口与实现的分离

缺点

  1. 性能略有下降(动态绑定需要运行时确定方法)
  2. 无法直接访问子类特有的成员

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());}}
}

总结

  1. 继承允许我们创建层次化的类结构,实现代码复用和扩展
  2. 多态允许我们使用统一的接口处理不同类型的对象,提高代码的灵活性和可扩展性
  3. 使用super关键字可以访问父类的成员,特别是在存在同名成员时
  4. 子类构造时必须先调用父类构造方法,确保正确的初始化顺序
  5. 向上转型是安全的,向下转型需要谨慎并使用instanceof进行检查
  6. 避免在构造方法中调用可重写的方法,以防止未初始化的状态

在实际开发中,我们应该遵循"优先使用组合而非继承"的原则,合理使用继承和多态,设计出高内聚、低耦合的系统结构。同时,要注意多态可能带来的性能影响,在性能敏感的场景中谨慎使用。

http://www.xdnf.cn/news/18466.html

相关文章:

  • openstack的novnc兼容问题
  • GitCode 疑难问题诊疗:全面指南与解决方案
  • 94. 城市间货物运输 I, Bellman_ford 算法, Bellman_ford 队列优化算法
  • 智慧工厂烟雾检测:全场景覆盖与精准防控
  • Java基础 8.22
  • 2-3.Python 编码基础 - 类型检测与类型转换
  • 集成电路学习:什么是SVM支持向量机
  • AI 大模型 “进化史”:从参数竞赛到场景落地,技术突破藏着哪些逻辑?
  • Unreal Engine UFloatingPawnMovement
  • 【ECharts】2. ECharts 性能优化
  • kafka的rebalance机制是什么
  • CentOS 10安装Ollama
  • 12-Linux系统用户管理及基础权限
  • 机试备考笔记 18/31
  • Nginx(一)认识Nginx
  • Eino 开源框架全景解析 - 以“大模型应用的搭积木指南”方式理解(一)
  • Azure TTS Importer:一键导入,将微软TTS语音接入你的阅读软件!
  • LeetCode 3195.包含所有 1 的最小矩形面积 I:简单题-求长方形四个范围
  • 【ElasticSearch】IK分词器安装,配置修改,支持新增词组,中文常用mapping使用案例
  • 微前端qiankun框架,子页面图标样式错乱问题,显示为X
  • 人脸识别驱动的工厂人体属性检测与预警机制
  • Conmi的正确答案——Ubuntu24.04禁用任何休眠
  • huggingface离线下载模型使用方法
  • CAN总线工具学习:DBC解析、设备扫描与报文监控
  • Logstash——性能、可靠性与扩展性架构
  • JAVA后端开发——API状态字段设计规范与实践
  • Claude Code接入Serena mcp
  • Elasticsearch Rails 集成(elasticsearch-model / ActiveRecord)
  • [激光原理与应用-317]:光学设计 - Solidworks - 零件、装配体、工程图
  • 浅拷贝,深拷贝