
前言这一篇进入面向对象最核心、也最容易懵的部分多态顺便把对象数组一起讲了因为多态和数组经常配合出现。期待和你的一起进步一、对象数组的声明和初始化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 代码示例javapublic 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); } }javapublic 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 循环javafor (int i 0; i students.length; i) { students[i].show(); }2.2 增强 for 循环javafor (Student stu : students) { stu.show(); }2.3 两种遍历的区别对比项普通 for增强 for能否拿到下标✅ 能❌ 不能能否修改数组元素✅ 能❌ 不能只能读代码简洁度一般简洁适用场景需要下标时只读遍历时2.4 代码示例javapublic 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 内存图javaStudent[] 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 关键点students是栈里的引用指向堆里的数组数组本身在堆里每个元素存的都是对象的地址对象实体也在堆里和数组是分开的数组里存的不是对象是对象的地址3.3 我的理解这个内存图画几遍就记住了。可以这么想students是你手里的遥控器栈Student[]数组是一排快递柜堆柜子里放的不是包裹是包裹的取件码地址真正的包裹对象在另一个地方堆里的另一块区域所以你通过students[0]拿到取件码再根据取件码找到真正的包裹。核心一句话数组在堆里对象也在堆里数组元素存的是对象的地址。四、对象数组中的空指针异常陷阱4.1 什么是空指针异常NullPointerExceptionNPE是 Java 最常见的异常之一。当你用一个null去调用方法或访问属性时就会抛出它。4.2 陷阱示例javaStudent[] students new Student[3]; // 此时 students[0]、students[1]、students[2] 都是 null students[0].show(); // ❌ NullPointerException原因new Student[3]只造了 3 个空柜子柜子里没有包裹。你直接对null调用show()当然报错。4.3 正确写法javaStudent[] students new Student[3]; students[0] new Student(张三, 18); // 先放包裹 students[0].show(); // ✅ 再使用4.4 遍历时的判空javafor (Student stu : students) { if (stu ! null) { // 判空防止 NPE stu.show(); } }4.5 我的理解这个坑很容易踩。new Student[3]之后直接遍历调用方法程序直接崩了还纳闷我不是创建数组了吗。后来才明白创建数组 ≠ 创建对象。数组只是柜子柜子里是空的。你得先往柜子里放包裹才能用包裹。核心一句话对象数组创建后元素默认是 null使用前必须判空。五、多态——向上转型5.1 什么是向上转型父类引用指向子类对象叫向上转型。java父类 变量名 new 子类();5.2 代码示例javapublic 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(狗在汪汪叫); } }javaAnimal animal new Dog(); // 向上转型 animal.eat(); // 输出狗在啃骨头 // animal.bark(); // ❌ 编译报错父类引用不能调用子类特有方法5.3 向上转型的特点自动发生不需要强转父类引用只能调用父类中定义的方法如果子类重写了该方法调用的是子类重写后的版本子类特有方法不能调用5.4 我的理解向上转型就是把狗当动物看。你牵一条狗跟别人说这是我的动物因为狗本来就是动物。但你说这是我的动物它会汪汪叫别人就懵了——因为动物这个身份里没有汪汪叫这个技能。你得说这是我的狗才能让它汪汪叫。核心一句话向上转型 父类引用指向子类对象只能调用父类定义的方法。六、多态——动态绑定6.1 什么是动态绑定运行时才确定调用哪个方法叫动态绑定也叫运行时多态。6.2 代码示例javapublic 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 动态绑定的前提有继承关系子类重写了父类方法父类引用指向子类对象三个条件缺一不可。6.4 我的理解动态绑定就是看实际对象不看声明类型。你写Animal animal new Dog()编译器看左边Animal知道你有eat()方法放行。但真正执行时JVM 看右边Dog发现是条狗就调用狗版本的eat()。核心一句话编译看左边运行看右边。七、多态——向下转型7.1 什么是向下转型把父类引用转回子类引用叫向下转型。需要强制类型转换。java子类 变量名 (子类) 父类引用;7.2 代码示例javaAnimal animal new Dog(); // 向上转型 Dog dog (Dog) animal; // 向下转型 dog.bark(); // ✅ 现在可以调用子类特有方法了7.3 向下转型的风险如果转错了类型会抛ClassCastExceptionjavaAnimal animal new Dog(); Cat cat (Cat) animal; // ❌ ClassCastException原因animal实际是Dog对象你硬要把它当CatJVM 不干。7.4 为什么要向下转型因为向上转型后子类特有方法不能调用。如果你确实需要调用子类特有方法就得转回去。javaAnimal animal new Dog(); // animal.bark(); // ❌ 报错 Dog dog (Dog) animal; dog.bark(); // ✅ 可以了7.5 我的理解向上转型是把狗当动物看向下转型是把动物当狗看。问题是你手里这个动物它到底是不是狗如果是狗转回狗没问题如果其实是猫你硬转成狗那就出事了。所以向下转型之前必须先判断类型这就引出了instanceof。核心一句话向下转型 父类引用转回子类必须强制转换转错会抛异常。八、instanceof 关键字8.1 什么是 instanceofinstanceof用来判断一个对象是不是某个类的实例返回boolean。java对象 instanceof 类名8.2 代码示例javaAnimal 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 配合向下转型使用javapublic 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()方法