news 2026/10/8 7:54:00

Java 多态与对象数组全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java 多态与对象数组全解析

前言

这一篇进入面向对象最核心、也最容易懵的部分:多态,顺便把对象数组一起讲了,因为多态和数组经常配合出现。期待和你的一起进步

一、对象数组的声明和初始化

1.1 什么是对象数组?

对象数组就是"装对象的数组"。普通数组装的是int、double这种基本类型,对象数组装的是对象的引用(地址)。

1.2 声明方式

java

// 方式一:先声明,再分配空间 Student[] students = new Student[3]; // 方式二:声明 + 初始化 Student[] students = new Student[]{s1, s2, s3}; // 方式三:简写 Student[] students = {s1, s2, s3};

1.3 代码示例

java

public class Student { private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } public void show() { System.out.println("姓名:" + name + ",年龄:" + age); } }

java

public class Test { public static void main(String[] args) { // 声明一个长度为 3 的对象数组 Student[] students = new Student[3]; // 分别给每个位置赋值 students[0] = new Student("张三", 18); students[1] = new Student("李四", 19); students[2] = new Student("王五", 20); // 使用 students[0].show(); // 姓名:张三,年龄:18 students[1].show(); // 姓名:李四,年龄:19 students[2].show(); // 姓名:王五,年龄:20 } }

1.4 我的理解

对象数组就像一排快递柜。new Student[3]就是造了 3 个空柜子,柜子里暂时啥都没有(null)。然后students[0] = new Student(...)就是往第一个柜子里放一个包裹。

注意:造柜子 ≠ 放包裹。new Student[3]只是造了 3 个空柜子,里面的包裹还得你自己一个个放。

核心一句话:对象数组装的是对象的地址,不是对象本身。


二、对象数组的遍历和基本使用

2.1 普通 for 循环

java

for (int i = 0; i < students.length; i++) { students[i].show(); }

2.2 增强 for 循环

java

for (Student stu : students) { stu.show(); }

2.3 两种遍历的区别

对比项普通 for增强 for
能否拿到下标✅ 能❌ 不能
能否修改数组元素✅ 能❌ 不能(只能读)
代码简洁度一般简洁
适用场景需要下标时只读遍历时

2.4 代码示例

java

public class Test { public static void main(String[] args) { Student[] students = { new Student("张三", 18), new Student("李四", 19), new Student("王五", 20) }; // 普通 for for (int i = 0; i < students.length; i++) { System.out.println("第 " + (i + 1) + " 个学生:"); students[i].show(); } // 增强 for System.out.println("--- 增强 for ---"); for (Student stu : students) { stu.show(); } } }

2.5 我的理解

遍历对象数组就是把每个柜子打开,拿出里面的包裹用一用。

增强 for 写起来简洁,但拿不到下标。如果你想"把第 2 个学生换成李四",那就得用普通 for,因为你需要students[1] = ...这种下标操作。

核心一句话:要下标用普通 for,只读遍历用增强 for。


三、对象数组的内存结构

3.1 内存图

java

Student[] students = new Student[3]; students[0] = new Student("张三", 18); students[1] = new Student("李四", 19); students[2] = new Student("王五", 20);

内存结构:

text

栈内存 堆内存 +-----------+ +---------------------+ | students | ---> | Student[] 数组 | +-----------+ +---------------------+ | [0] | 地址A | ----+ | [1] | 地址B | --+ | | [2] | 地址C | + | | +---------------------+ | | | | +------------------------+ | | Student 对象(张三) | | name = "张三" | | age = 18 | +-------------------------+ +------------------------+ | Student 对象(李四) | | name = "李四" | | age = 19 | +------------------------+

3.2 关键点

  1. students是栈里的引用,指向堆里的数组

  2. 数组本身在堆里,每个元素存的都是对象的地址

  3. 对象实体也在堆里,和数组是分开的

  4. 数组里存的不是对象,是对象的地址

3.3 我的理解

这个内存图画几遍就记住了。可以这么想:

  • students是你手里的遥控器(栈)

  • Student[]数组是一排快递柜(堆)

  • 柜子里放的不是包裹,是包裹的取件码(地址)

  • 真正的包裹(对象)在另一个地方(堆里的另一块区域)

所以你通过students[0]拿到取件码,再根据取件码找到真正的包裹。

核心一句话:数组在堆里,对象也在堆里,数组元素存的是对象的地址。


四、对象数组中的空指针异常陷阱

4.1 什么是空指针异常?

NullPointerException(NPE)是 Java 最常见的异常之一。当你用一个null去调用方法或访问属性时,就会抛出它。

4.2 陷阱示例

java

Student[] students = new Student[3]; // 此时 students[0]、students[1]、students[2] 都是 null students[0].show(); // ❌ NullPointerException!

原因:new Student[3]只造了 3 个空柜子,柜子里没有包裹。你直接对null调用show(),当然报错。

4.3 正确写法

java

Student[] students = new Student[3]; students[0] = new Student("张三", 18); // 先放包裹 students[0].show(); // ✅ 再使用

4.4 遍历时的判空

java

for (Student stu : students) { if (stu != null) { // 判空,防止 NPE stu.show(); } }

4.5 我的理解

这个坑很容易踩。new Student[3]之后直接遍历调用方法,程序直接崩了,还纳闷"我不是创建数组了吗"。

后来才明白:创建数组 ≠ 创建对象。数组只是柜子,柜子里是空的。你得先往柜子里放包裹,才能用包裹。

核心一句话:对象数组创建后元素默认是 null,使用前必须判空。


五、多态——向上转型

5.1 什么是向上转型?

父类引用指向子类对象,叫向上转型。

java

父类 变量名 = new 子类();

5.2 代码示例

java

public class Animal { public void eat() { System.out.println("动物在吃东西"); } } public class Dog extends Animal { @Override public void eat() { System.out.println("狗在啃骨头"); } public void bark() { System.out.println("狗在汪汪叫"); } }

java

Animal animal = new Dog(); // 向上转型 animal.eat(); // 输出:狗在啃骨头 // animal.bark(); // ❌ 编译报错!父类引用不能调用子类特有方法

5.3 向上转型的特点

  1. 自动发生,不需要强转

  2. 父类引用只能调用父类中定义的方法

  3. 如果子类重写了该方法,调用的是子类重写后的版本

  4. 子类特有方法不能调用

5.4 我的理解

向上转型就是"把狗当动物看"。你牵一条狗,跟别人说"这是我的动物",因为狗本来就是动物。

但你说"这是我的动物,它会汪汪叫",别人就懵了——因为"动物"这个身份里没有"汪汪叫"这个技能。你得说"这是我的狗",才能让它汪汪叫。

核心一句话:向上转型 = 父类引用指向子类对象,只能调用父类定义的方法。


六、多态——动态绑定

6.1 什么是动态绑定?

运行时才确定调用哪个方法,叫动态绑定(也叫运行时多态)。

6.2 代码示例

java

public class Test { public static void main(String[] args) { Animal animal1 = new Dog(); Animal animal2 = new Cat(); animal1.eat(); // 输出:狗在啃骨头 animal2.eat(); // 输出:猫在吃鱼 } }

编译时:编译器只知道animal1是Animal类型,检查Animal里有没有eat(),有,通过。

运行时:JVM 发现animal1实际指向的是Dog对象,于是调用Dog重写的eat()。

6.3 动态绑定的前提

  1. 有继承关系

  2. 子类重写了父类方法

  3. 父类引用指向子类对象

三个条件缺一不可。

6.4 我的理解

动态绑定就是"看实际对象,不看声明类型"。

你写Animal animal = new Dog(),编译器看左边(Animal),知道你有eat()方法,放行。但真正执行时,JVM 看右边(Dog),发现是条狗,就调用狗版本的eat()。

核心一句话:编译看左边,运行看右边。


七、多态——向下转型

7.1 什么是向下转型?

把父类引用转回子类引用,叫向下转型。需要强制类型转换。

java

子类 变量名 = (子类) 父类引用;

7.2 代码示例

java

Animal animal = new Dog(); // 向上转型 Dog dog = (Dog) animal; // 向下转型 dog.bark(); // ✅ 现在可以调用子类特有方法了

7.3 向下转型的风险

如果转错了类型,会抛ClassCastException:

java

Animal animal = new Dog(); Cat cat = (Cat) animal; // ❌ ClassCastException!

原因:animal实际是Dog对象,你硬要把它当Cat,JVM 不干。

7.4 为什么要向下转型?

因为向上转型后,子类特有方法不能调用。如果你确实需要调用子类特有方法,就得转回去。

java

Animal animal = new Dog(); // animal.bark(); // ❌ 报错 Dog dog = (Dog) animal; dog.bark(); // ✅ 可以了

7.5 我的理解

向上转型是"把狗当动物看",向下转型是"把动物当狗看"。

问题是:你手里这个"动物",它到底是不是狗?如果是狗,转回狗没问题;如果其实是猫,你硬转成狗,那就出事了。

所以向下转型之前,必须先判断类型,这就引出了instanceof。

核心一句话:向下转型 = 父类引用转回子类,必须强制转换,转错会抛异常。


八、instanceof 关键字

8.1 什么是 instanceof?

instanceof用来判断一个对象是不是某个类的实例,返回boolean。

java

对象 instanceof 类名

8.2 代码示例

java

Animal animal = new Dog(); System.out.println(animal instanceof Dog); // true System.out.println(animal instanceof Cat); // false System.out.println(animal instanceof Animal); // true(狗也是动物) System.out.println(animal instanceof Object); // true(所有类都是 Object 子类)

8.3 配合向下转型使用

java

public void doSomething(Animal animal) { if (animal instanceof Dog) { Dog dog = (Dog) animal; dog.bark(); } else if (animal instanceof Cat) { Cat cat = (Cat) animal; cat.meow(); } }

8.4 我的理解

instanceof就是"你是不是这个东西?"。

向下转型前先问一句:"你是不是狗?"是的话再转,不是就别转,这样就不会出ClassCastException。

核心一句话:instanceof 判断类型,向下转型前先用它防翻车。


九、核心口诀

知识点口诀
对象数组造柜子 ≠ 放包裹,元素默认 null
对象数组内存数组在堆,对象在堆,数组存地址
空指针陷阱创建数组 ≠ 创建对象,用前必判空
向上转型父类引用指向子类对象,只能调父类方法
动态绑定编译看左边,运行看右边
向下转型父转回子,必须强转,转错抛异常
instanceof判断类型,向下转型前先用它防翻车

十、常见易错点

易错点说明
对象数组元素忘赋值new Student[3]后元素都是 null,直接用会 NPE
向上转型后调子类方法编译报错,父类引用不能调子类特有方法
向下转型不判断类型直接强转可能抛 ClassCastException,先用 instanceof
混淆编译和运行编译看左边类型,运行看右边对象
静态方法当多态静态方法看左边类型,不参与动态绑定
数组 length 忘写数组用length属性,不是length()方法
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 7:53:50

基于深度学习的目标检测App实战:从YOLO训练到部署全流程指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/8 7:51:23

Superpowers开发工具链:本地AI编程增强体系实战指南

1. 项目概述&#xff1a;Superpowers 不是超能力&#xff0c;而是开发者效率革命的代号 “Superpowers”这个词最近在开发者圈子里炸开了锅——它不是漫威电影里的变种人设定&#xff0c;也不是什么玄学概念&#xff0c;而是一套正在真实改变日常编码方式的智能开发工具链统称…

作者头像 李华
网站建设 2026/10/8 7:50:54

yfinance 取数:3 行代码拿数据,4 类异常一次修完的实用速查

yfinance 取数&#xff1a;3 行代码拿数据&#xff0c;4 类异常一次修完的实用速查 【免费下载链接】yfinance Download market data from Yahoo! Finances API 项目地址: https://gitcode.com/GitHub_Trending/yf/yfinance 写回测脚本&#xff0c;第一道坎往往是数据&a…

作者头像 李华
网站建设 2026/10/8 7:50:43

OpenShell 实战:浏览器交互式 Shell 的会话保持与部署避坑指南

1. 从一个空输入框说起&#xff1a;OpenShell 到底在解决什么问题第一次看到“OpenShell”这个词&#xff0c;是在一个终端工具讨论帖里。有人丢出一句话&#xff1a;“有没有那种能让我在浏览器里直接开一个像样的 shell&#xff0c;又不用装一堆东西的方案&#xff1f;”底下…

作者头像 李华
网站建设 2026/10/8 7:49:08

第116篇 Kotlin 代码评审清单:团队规范与静态检查

前面几节讲的是"怎么写",这一节讲"怎么保证团队写出来的代码跟你一样好"。这题的面试形态很特别——它不考语法也不考 API,考的是你有没有一套能落地的质量保障机制。答得浅的人说"团队 review 很严格",答得深的人会讲清"哪些交给机器、…

作者头像 李华