JAVA泛型

Java中的泛型(Generics)是一种在编译时提供类型安全检查的机制,允许开发者编写灵活且可重用的代码。泛型的核心思想是参数化类型,即定义类、接口或方法时使用类型参数,使用时再指定具体类型。

目录

作用

使用方法

通配符

类型擦除


作用

  • 类型不安全
    //在 Java 5 之前,集合类(如 ArrayList、HashMap)只能存储 Object 类型的数据。
    //这意味着你可以将任何类型的对象放入集合中,但在取出时需要强制类型转换。
    //如果类型转换错误,会导致运行时异常(ClassCastException)。
    //在没有泛型的情况下,类型错误只能在运行时被发现,而不是在编译时提示错误。
    List list = new ArrayList();
    list.add("Hello");
    list.add(123); // 可以放入不同类型的对象
    
    String str = (String) list.get(0); // 需要强制类型转换
    Integer num = (Integer) list.get(1); // 需要强制类型转换
    
    String wrongStr = (String) list.get(1); // 运行时抛出 ClassCastException
    
    //----------------------------使用泛型---------------------------
    
    List<String> list = new ArrayList<>();
    list.add("Hello");
    // list.add(123); // 编译错误,类型不匹配
    
    String str = list.get(0); // 无需强制类型转换
    
  • 代码冗余
    //如果没有泛型,开发者需要为每种类型编写单独的类或方法。
    //例如,如果要实现一个存储 String 和 Integer 的容器,可能需要分别编写 StringBox 和 IntegerBox。
    class StringBox {
        private String content;
        public void setContent(String content) { this.content = content; }
        public String getContent() { return content; }
    }
    
    class IntegerBox {
        private Integer content;
        public void setContent(Integer content) { this.content = content; }
        public Integer getContent() { return content; }
    }
    
    //----------------------------使用泛型---------------------------
    
    class Box<T> {
        private T content;
        public void setContent(T content) { this.content = content; }
        public T getContent() { return content; }
    }
    
    Box<String> stringBox = new Box<>();
    stringBox.setContent("Hello");
    
    Box<Integer> intBox = new Box<>();
    intBox.setContent(123);

使用方法

  1. 泛型类
    //在类名后添加类型参数 <T>,T 是占位符,可替换为任何非基本类型。
    public class Box<T> {
        private T content;
    
        public void setContent(T content) {
            this.content = content;
        }
    
        public T getContent() {
            return content;
        }
    
        public static void main(String[] args) {
            Box<String> stringBox = new Box<>();
            stringBox.setContent("Hello");
            System.out.println(stringBox.getContent()); // 输出: Hello
    
            Box<Integer> intBox = new Box<>();
            intBox.setContent(123);
            System.out.println(intBox.getContent()); // 输出: 123
        }
    }
  2. 泛型方法
    //在返回类型前定义类型参数 <T>,方法可以独立于类的泛型参数。
    public class ArrayUtils {
        // 泛型方法:打印任意类型数组
        public static <T> void printArray(T[] array) {
            for (T element : array) {
                System.out.print(element + " ");
            }
            System.out.println();
        }
    
        public static void main(String[] args) {
            Integer[] intArr = {1, 2, 3};
            String[] strArr = {"A", "B", "C"};
            ArrayUtils.printArray(intArr); // 输出: 1 2 3 
            ArrayUtils.printArray(strArr); // 输出: A B C 
        }
    }
  3. 泛型接口
    //接口定义类型参数,实现类需指定具体类型。
    public interface Pair<K, V> {
        K getKey();
        V getValue();
    }
    
    public class SimplePair<K, V> implements Pair<K, V> {
        private K key;
        private V value;
    
        public SimplePair(K key, V value) {
            this.key = key;
            this.value = value;
        }
    
        @Override
        public K getKey() { return key; }
    
        @Override
        public V getValue() { return value; }
    
        public static void main(String[] args) {
            Pair<String, Integer> pair = new SimplePair<>("Age", 25);
            System.out.println(pair.getKey() + ": " + pair.getValue()); // 输出: Age: 25
        }
    }

通配符

泛型通配符(?)用于增加泛型的灵活性,常用于通用API编写,减少代码冗余。

  1. 上界通配符<? extends T>
    //表示参数可以是 T 或其子类的集合
    public static double sumOfList(List<? extends Number> list) {
        double sum = 0.0;
        for (Number num : list) {
            sum += num.doubleValue();
        }
        return sum;
    }
    
    public static void main(String[] args) {
        List<Integer> integers = List.of(1, 2, 3);
        System.out.println(sumOfList(integers)); // 输出: 6.0
    }
  2. 下界通配符<? super T>
    //表示参数可以是 T 或其父类的集合。
    public static void addNumbers(List<? super Integer> list) {
        for (int i = 1; i <= 5; i++) {
            list.add(i);
        }
    }
    
    public static void main(String[] args) {
        List<Number> numbers = new ArrayList<>();
        addNumbers(numbers);
        System.out.println(numbers); // 输出: [1, 2, 3, 4, 5]
    }
  3. PECS原则

        PECS 原则(Producer Extends, Consumer Super)用于指导在使用泛型集合时如何正确选择通配符(<? extends T> 和 <? super T>)。核心思想是:

  • Producer Extends:如果集合是生产者(提供数据,即可读取),使用 <? extends T>

    //当集合是生产者(即从集合中读取数据)时,使用 <? extends T>。
    //这表示集合中的元素是 T 或其子类型,因此可以安全地从集合中读取数据。
    public void processNumbers(List<? extends Number> numbers) {
        for (Number number : numbers) {
            System.out.println(number);
        }
    }
    
    //不能向 <? extends T> 集合中添加元素(除了 null)
    //当你尝试向 List<? extends Double> 中添加元素时,例如:
    List<? extends Double> numbers = new ArrayList<>();
    numbers.add(new Double(1));
    //List<? extends Double> 可能是 List<Double>,也可能是 List<MyDouble>(假设 MyDouble 是 Double 的子类)
    //如果 numbers 实际上是 List<MyDouble>,那么添加 Double 类型的元素就会破坏类型安全,因为 Double 不是 MyDouble 的子类。
    //为了避免这种潜在的类型安全问题,Java 禁止向 List<? extends Double> 中添加任何元素(除了 null,因为 null 是所有引用类型的默认值)。
  • Consumer Super:如果集合是消费者(消费数据,即可添加),使用 <? super T>

    //当集合是消费者(即向集合中写入数据)时,使用 <? super T>。
    //这表示集合中的元素是 T 或其父类型,因此可以安全地向集合中添加 T 类型的元素。
    public void addIntegers(List<? super Integer> integers) {
        integers.add(1);
        integers.add(2);
    }
    
    //不能从 <? super T> 集合中读取具体类型的元素(只能读取 Object 类型),因为编译器无法确定具体的父类型。
  • PECS的应用案例

    //java.util.Collections 中的 copy 方法就使用了 PECS 原则:
    //dest 是消费者(接受数据),因此使用 <? super T>。
    //src 是生产者(提供数据),因此使用 <? extends T>。
    //该方法将src中的内容拷贝到dest中
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        for (int i = 0; i < src.size(); i++) {
            dest.set(i, src.get(i));
        }
    }

类型擦除

  • 含义

         类型擦除(Type Erasure)是 Java 泛型实现的一种机制。在编译时,泛型类型参数会被替换为它们的上界(通常是 Object),并且在使用泛型的地方会插入强制类型转换,并在字节码中移除所有泛型类型信息。这意味着泛型类型信息在运行时是不可用的。

  1. 泛型类:泛型类中的类型参数会被替换为 Object 或指定的上界。

  2. 泛型方法:泛型方法的类型参数会被替换为 Object 或指定的上界。

  3. 泛型类型检查:类型检查仅在编译时进行,运行时无法获取泛型类型信息。

    // 编译前
    public class Box<T> {
        private T content;
    
        public void setContent(T content) {
            this.content = content;
        }
    
        public T getContent() {
            return content;
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Box<Integer> box = new Box<>();
            box.setContent(123);
            Integer boxValue = box.getContent();
            System.out.println(boxValue);
        }
    }
    
    // 编译后(类型擦除)
    public class Box {
        private Object content; // T 被替换为 Object
    
        public void setContent(Object content) {
            this.content = content;
        }
    
        public Object getContent() {
            return content;
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Box box = new Box(); // 泛型类型信息被擦除
            box.setContent(123); // 自动装箱,int -> Integer
    
            // 编译器插入强制类型转换
            Integer boxValue = (Integer) box.getContent(); // Object -> Integer
            System.out.println(boxValue);
        }
    }
  • 原因

        Java 泛型是在 Java 5 中引入的,而在此之前,Java 集合类(如 ArrayList、HashMap)都是基于 Object 实现的。为了确保旧版本的代码能够在新版本的 JVM 上运行,Java 采用了类型擦除机制,使得泛型代码在编译后与旧版本的非泛型代码兼容。
        同时,类型擦除避免了在运行时维护泛型类型信息,从而减少了 JVM 的负担。

  • 局限性
    • 无法获取泛型类型信息
      //由于类型信息在运行时被擦除,无法直接获取泛型类型参数的具体类型。
      public class Box<T> {
          private T content;
      
          public void setContent(T content) {
              this.content = content;
          }
      
          public T getContent() {
              return content;
          }
      }
      
      Box<String> box = new Box<>();
      System.out.println(box.getClass().getTypeParameters()[0]); // 输出: T,而不是 String
    • 无法实例化泛型类型
      //由于类型擦除,无法直接实例化泛型类型参数。
      public class Box<T> {
          private T content;
      
          public Box() {
              // this.content = new T(); // 编译错误,无法实例化泛型类型
          }
      }
    • 无法重载泛型方法
      //由于类型擦除,以下代码会导致编译错误,因为擦除后的方法签名相同。
      public class Utils {
          public void print(List<String> list) {}
          public void print(List<Integer> list) {} // 编译错误,方法签名冲突
      }
    • 无法使用基本类型作为泛型参数
      //Java 泛型是通过 类型擦除(Type Erasure) 实现的。在编译时,泛型类型参数会被替换为它们的上界(通常是 Object)。由于基本类型不是对象,它们不能作为 Object 的子类,因此无法直接用于泛型。
      List<int> list = new ArrayList<>(); // 编译错误
      List<Integer> list = new ArrayList<>(); // 正确
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值