Skip to content

类型转换

有继承关系,就有转型。

父类引用指向子类对象,是多态的起点。但有时候,你需要反过来——拿到具体的子类引用,才能调用子类独有的方法。这就是类型转换存在的理由。

两种转型:方向不同,安全性不同

转型方向代码写法安全性需要检查
向上转型Animal a = new Dog()安全不需要
向下转型Dog d = (Dog) a危险必须先 instanceof

向上转型是把范围扩大,就像把长颈鹿放进动物笼子里,当然放得下。向下转型是把范围缩小,就像从动物笼子里捞出一只长颈鹿——你不确定里面有没有,可能捞出来一只兔子。

向上转型:自动发生,不需要写

子类对象本来就是父类的一种,向上转型是天然成立的,编译器自动处理。

java
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 的版本——这是动态绑定的结果。转型没有改变对象的真实类型,它只是改变了你透过哪个「窗口」看这个对象。

向下转型:你确定吗?

有时候你真的需要那个子类特有的方法。比如:

java
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:

java
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 把检查和转型合并成一步:

java
// 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 + 强制转型的字节码。

实际应用

最常见的使用场景:处理一个异构集合,但需要对特定类型做特殊处理。

java
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

基于 VitePress 构建