泛型擦除案例

wen java案例 2

本文目录导读:

泛型擦除案例

  1. 什么是泛型擦除
  2. 典型擦除案例
  3. 额外发现和解决方案

我来为你详细讲解泛型擦除(Type Erasure)的典型案例,包括原理、示例和注意事项。

什么是泛型擦除

Java 的泛型是编译期实现的,编译器会擦除所有的泛型类型信息,这意味着:

  • 编译时:有完整的泛型类型检查
  • 运行时:泛型信息完全消失

典型擦除案例

案例1:基本类型擦除

import java.util.ArrayList;
import java.util.List;
public class BasicErasure {
    public static void main(String[] args) throws Exception {
        // 编译时:泛型类型检查
        List<String> strList = new ArrayList<>();
        List<Integer> intList = new ArrayList<>();
        // 运行时:两个 List 是同一个类型
        System.out.println("strList 类型: " + strList.getClass());
        System.out.println("intList 类型: " + intList.getClass());
        System.out.println("是否相同类型: " + 
            (strList.getClass() == intList.getClass()));  // true!
        // 通过反射添加错误类型的数据
        List<String> list = new ArrayList<>();
        list.add("Hello");
        // 利用反射绕过泛型检查
        list.getClass().getMethod("add", Object.class)
            .invoke(list, 123);  // 可以添加 Integer!
        System.out.println("List 内容: " + list);
        // 输出: List 内容: [Hello, 123]
    }
}

案例2:方法签名擦除

public class MethodErasure {
    // 编译错误:方法签名冲突
    // public void process(List<String> list) { }
    // public void process(List<Integer> list) { }
    // 无法共存!因为擦除后都是 List
    // 解决方式:使用不同的方法名
    public void processString(List<String> list) {
        System.out.println("处理字符串列表");
        for (String s : list) {
            System.out.println(s.toUpperCase());
        }
    }
    public void processInteger(List<Integer> list) {
        System.out.println("处理整数列表");
        for (Integer i : list) {
            System.out.println(i * 2);
        }
    }
    public static void main(String[] args) {
        MethodErasure demo = new MethodErasure();
        List<String> strings = List.of("java", "python");
        List<Integer> integers = List.of(1, 2, 3);
        demo.processString(strings);
        demo.processInteger(integers);
    }
}

案例3:继承中的泛型擦除

import java.util.ArrayList;
import java.util.List;
class Parent<T> {
    private T value;
    public void set(T value) {
        this.value = value;
    }
    public T get() {
        return value;
    }
}
class Child extends Parent<String> {
    // 编译器会自动生成桥接方法
    @Override
    public String get() {
        System.out.println("调用了 Child.get()");
        return super.get();
    }
    // 桥接方法(编译器自动生成):
    // public Object get() { return this.get(); }
}
public class InheritanceErasure {
    public static void main(String[] args) throws Exception {
        Child child = new Child();
        child.set("Hello");
        // 查看桥接方法
        for (var method : Child.class.getDeclaredMethods()) {
            System.out.println("方法: " + method.getName() 
                + ", 参数: " + java.util.Arrays.toString(method.getParameterTypes())
                + ", 返回: " + method.getReturnType());
        }
        // 通过父类类型调用
        Parent<String> parent = child;
        System.out.println("获取值: " + parent.get());
    }
}

案例4:泛型数组问题

import java.util.List;
public class ArrayErasure {
    public static void main(String[] args) {
        // 编译错误:不能直接创建泛型数组
        // List<String>[] array = new List<String>[10];  // 错误!
        // 正确方式:使用 ArrayList 代替
        List<String>[] array = new List[10]; // 使用原始类型创建
        // 或者使用 List<List<String>>
        List<List<String>> nestedList = new java.util.ArrayList<>();
        // 示例:泛型数组的实际问题
        List<Integer> intList = List.of(1, 2, 3);
        List<String> strList = List.of("a", "b", "c");
        Object[] objects = new List[2];
        objects[0] = intList;
        objects[1] = strList;
        // 运行时可能会抛出 ClassCastException
        try {
            List<Integer>[] intArrays = (List<Integer>[]) new List[2];
            intArrays[0] = List.of(1, 2, 3);
            // intArrays[1] = List.of("string");  // 运行时错误!
            System.out.println("数组第一个元素: " + intArrays[0]);
        } catch (Exception e) {
            System.out.println("捕获异常: " + e);
        }
    }
}

案例5:工具类中的类型转换

import java.util.ArrayList;
import java.util.List;
public class TypeConversionErasure {
    // 辅助方法:安全的类型转换
    @SuppressWarnings("unchecked")
    public static <T> List<T> castList(Object obj) {
        if (obj instanceof List) {
            return (List<T>) obj;  // 运行时只是转成 List
        }
        return null;
    }
    // 显示运行时类型信息
    public static void printRuntimeInfo() {
        List<String> list = new ArrayList<>();
        list.add("Hello");
        System.out.println("=== 运行时泛型信息 ===");
        System.out.println("List 运行时类型: " + list.getClass());
        System.out.println("List 的父类: " + list.getClass().getGenericSuperclass());
        // 查看类型参数(运行时已经擦除)
        System.out.println("类型参数个数: " + 
            list.getClass().getTypeParameters().length);
    }
    public static void main(String[] args) {
        printRuntimeInfo();
        Object obj = new ArrayList<String>();
        obj = List.of("a", "b", "c");
        List<String> result = castList(obj);
        if (result != null) {
            System.out.println("转换成功: " + result);
        }
    }
}

案例6:泛型与重载

import java.util.List;
public class OverloadErasure {
    // 以下两个方法不能共存!
    // public void print(List<String> list) { }
    // public void print(List<Integer> list) { }
    // 正确方式:使用不同的方法名
    public void printStringList(List<String> list) {
        System.out.println("字符串列表: " + list);
    }
    public void printIntegerList(List<Integer> list) {
        System.out.println("整数列表: " + list);
    }
    // 或者使用不同的参数个数
    public void print(List<String> list, String... extra) {
        System.out.println("带额外参数的字符串列表: " + list);
    }
    public static void main(String[] args) {
        OverloadErasure demo = new OverloadErasure();
        demo.printStringList(List.of("a", "b"));
        demo.printIntegerList(List.of(1, 2));
        demo.print(List.of("x"), "extra");
    }
}

额外发现和解决方案

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;
public class ErasureSolutions {
    // 1. 通过 TypeToken 获取泛型类型
    public static class TypeToken<T> {
        private final Type type;
        protected TypeToken() {
            Type superclass = getClass().getGenericSuperclass();
            if (superclass instanceof ParameterizedType) {
                type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
            } else {
                throw new RuntimeException("无法获取泛型类型");
            }
        }
        public Type getType() {
            return type;
        }
    }
    // 2. 类型安全的容器类
    public static class SafeMap {
        private final java.util.Map<String, Object> map = new java.util.HashMap<>();
        @SuppressWarnings("unchecked")
        public <T> T get(String key) {
            return (T) map.get(key);
        }
        public <T> void put(String key, T value) {
            map.put(key, value);
        }
    }
    public static void main(String[] args) {
        // 使用 TypeToken 获取泛型类型
        TypeToken<List<String>> token = new TypeToken<List<String>>() {};
        System.out.println("泛型类型: " + token.getType());
        // 使用类型安全容器
        SafeMap safeMap = new SafeMap();
        safeMap.put("name", "张三");
        safeMap.put("age", 25);
        String name = safeMap.get("name");
        Integer age = safeMap.get("age");
        System.out.println("姓名: " + name + ", 年龄: " + age);
    }
}

泛型擦除的核心要点:

  1. 编译期检查,运行期擦除:类型安全在编译时保证
  2. 不能创建泛型数组:运行时类型信息不足
  3. 不能重载擦除后相同的方法:方法签名冲突
  4. 桥接方法:JVM 自动生成以维持多态
  5. 运行时需手动转型:泛型信息丢失后需要显式转换

最佳实践

  • 使用 List 而不是数组来保存泛型元素
  • 使用明确的类型边界(<? extends T> 等)
  • 避免混合原始类型和泛型类型
  • 使用类型令牌(TypeToken)获取运行时类型信息

抱歉,评论功能暂时关闭!