类型转换
有继承关系,就有转型。
父类引用指向子类对象,是多态的起点。但有时候,你需要反过来——拿到具体的子类引用,才能调用子类独有的方法。这就是类型转换存在的理由。
两种转型:方向不同,安全性不同
| 转型方向 | 代码写法 | 安全性 | 需要检查 |
|---|---|---|---|
| 向上转型 | Animal a = new Dog() | 安全 | 不需要 |
| 向下转型 | Dog d = (Dog) a | 危险 | 必须先 instanceof |
向上转型是把范围扩大,就像把长颈鹿放进动物笼子里,当然放得下。向下转型是把范围缩小,就像从动物笼子里捞出一只长颈鹿——你不确定里面有没有,可能捞出来一只兔子。
向上转型:自动发生,不需要写
子类对象本来就是父类的一种,向上转型是天然成立的,编译器自动处理。
public class UpcastDemo {
static class Animal {
void eat() {
System.out.println("动物在吃东西");
}
}
static class Dog extends Animal {
@Override
void eat() {
System.out.println("狗在吃狗粮");
}
void bark() {
System.out.println("汪汪!");
}
}
public static void main(String[] args) {
Dog dog = new Dog();
// 向上转型:自动发生,可以不写类型
Animal animal = dog;
animal.eat(); // 输出: 狗在吃狗粮 ← 调用的是 Dog 的方法
// animal.bark(); // ❌ 编译错误:Animal 没有这个方法
// // 编译器只认类型,Dog 的 bark() 看不到
}
}这里有个有意思的点:虽然 animal 的静态类型是 Animal,但 eat() 调用的是 Dog 的版本——这是动态绑定的结果。转型没有改变对象的真实类型,它只是改变了你透过哪个「窗口」看这个对象。
向下转型:你确定吗?
有时候你真的需要那个子类特有的方法。比如:
public class DowncastDemo {
static class Animal {
void eat() {}
}
static class Dog extends Animal {
void bark() {
System.out.println("汪汪!");
}
}
static class Cat extends Animal {
void meow() {
System.out.println("喵~");
}
}
public static void main(String[] args) {
Animal animal = new Dog();
// animal.bark(); // ❌ 编译不过
// 向下转型:强制把 Animal 引用转成 Dog
// ❌ 危险:animal 实际上是什么,你确定吗?
// Dog dog = (Dog) animal; // 运行时报 ClassCastException
// ✅ 安全做法:先检查,再转型
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.bark();
}
// ❌ 如果转错了,直接崩给你看
// Cat cat = (Cat) animal; // 运行时报 ClassCastException
}
}你可能会问:为什么 animal instanceof Dog 为 true 还能转错? 因为 instanceof 只检查引用指向的对象类型。如果 animal 是另一个 Dog,那没问题。但如果代码里 animal 实际是 Cat:
Animal animal = new Cat();
if (animal instanceof Dog) { // false,不会进入
Dog dog = (Dog) animal;
}只要每次转型前都做 instanceof 检查,ClassCastException 就可以完全避免。
Pattern Matching:JDK 16+ 的简化写法
每次都要写 if (animal instanceof Dog) { Dog dog = (Dog) animal; },有点繁琐。JDK 16 引入的 Pattern Matching 把检查和转型合并成一步:
// JDK 16 之前
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.bark();
}
// JDK 16+:检查通过后,dog 直接可用,不需要手动转型
if (animal instanceof Dog dog) {
dog.bark();
}检查通过时,变量 dog 的作用域只在 if 块内,不会污染外部命名空间。这是 JDK 16+ 的语法糖,编译后还是 instanceof + 强制转型的字节码。
实际应用
最常见的使用场景:处理一个异构集合,但需要对特定类型做特殊处理。
public class CastPracticalDemo {
static class Shape {
void draw() {
System.out.println("绘制图形");
}
}
static class Circle extends Shape {
@Override
void draw() {
System.out.println("绘制圆形");
}
void calculateArea() {
System.out.println("计算圆面积");
}
}
static class Rectangle extends Shape {
@Override
void draw() {
System.out.println("绘制矩形");
}
void calculateArea() {
System.out.println("计算矩形面积");
}
}
public static void main(String[] args) {
Shape[] shapes = {new Circle(), new Rectangle()};
for (Shape shape : shapes) {
// 统一绘制:多态
shape.draw();
// 特殊处理:先检查再转型
if (shape instanceof Circle circle) {
circle.calculateArea();
}
if (shape instanceof Rectangle rect) {
rect.calculateArea();
}
}
}
}遍历数组时用多态统一处理 draw(),但如果需要对特定图形做额外操作,再用 instanceof 区分。两种手段配合使用,各司其职。
总结
向上转型 Animal a = new Dog() → 天然安全,自动发生
向下转型 Dog d = (Dog) a → 需要 instanceof 检查
Pattern if (a instanceof Dog d) → JDK 16+ 简化语法转型的本质是什么?改变你看对象的「窗口大小」。向上转型扩大视野,但看不清细节;向下转型缩小视野,能看到更多。安全的关键只有一个:向下转型前,永远先问 instanceof。
