当前位置: 首页 > news >正文

java泛型

基本信息

Java泛型(Generics)是Java 5引入的一项重要特性,它允许在定义类、接口和方法时使用类型参数,从而提高代码的复用性、类型安全性和可读性。

  • 泛型的基本概念

泛型的核心思想是参数化类型,即在定义类、接口或方法时,使用一个或多个类型参数来代替具体的类型。这些类型参数在使用时可以被指定为具体的类型。

  •  泛型的特点

(1)类型安全

  • 泛型在编译时进行类型检查,避免了运行时的类型转换错误。

  • 例如,使用List<String>时,只能添加String类型的对象,如果尝试添加其他类型,编译器会报错。

(2)代码复用

  • 泛型允许编写通用的代码,可以适用于多种类型。

  • 例如,一个List<T>可以用于存储StringInteger等任意类型的对象。

(3)消除强制类型转换

  • 使用泛型后,从集合中获取元素时不需要显式地进行类型转换。

List<String> list = new ArrayList<>();
list.add("Java");
String s = list.get(0); // 无需强制类型转换

(4)通配符和边界

  • Java泛型支持通配符(?)和边界(extendssuper),用于增强泛型的灵活性。

    • <? extends T>:表示类型参数是T或其子类。

    • <? super T>:表示类型参数是T或其父类。

public void printList(List<? extends Number> list) {
    for (Number n : list) {
        System.out.println(n);
    }
}
 

(5)类型擦除

  • Java泛型在编译后会进行类型擦除,即泛型信息在运行时会被擦除,替换为限定类型或原始类型(通常是Object)。

  • 这是为了兼容Java 5之前的代码。

List<String> list = new ArrayList<>();
// 编译后,List<String>会被擦除为List<Object>

泛型的继承规则

泛型类或接口可以像普通类一样被继承或实现,但需要注意类型参数的处理。

1. 泛型类/接口的继承

(1)继承泛型类

  • 子类可以继承父类的泛型类型参数,也可以指定具体的类型。

class Box<T> {
    private T value;
    public void setValue(T value) { this.value = value; }
    public T getValue() { return value; }
}

// 子类继承泛型类型参数
class StringBox<T> extends Box<T> {
    // 可以使用父类的泛型类型参数
}

// 子类指定具体类型
class IntegerBox extends Box<Integer> {
    // 父类的T被固定为Integer
}

(2)接口类似

2. 泛型类型的继承关系

泛型类型的继承关系与普通类型的继承关系不同,主要体现在以下方面:

(1)泛型类型之间没有继承关系

  • 即使类型参数之间有继承关系,泛型类型本身也没有继承关系。

  • 示例:

    List<String> list1 = new ArrayList<>();
    List<Object> list2 = list1; // 错误!List<String>不是List<Object>的子类
    • 虽然StringObject的子类,但List<String>并不是List<Object>的子类。

(2)通配符的继承关系

  • 使用通配符(?)可以表示泛型类型的继承关系。

    • <? extends T>:表示类型参数是T或其子类。

    • <? super T>:表示类型参数是T或其父类。

  • 示例:

    List<? extends Number> numbers = new ArrayList<Integer>(); // 正确
    List<? super Integer> objects = new ArrayList<Number>();   // 正确

    ​​​​​​​​​​​​​​

3. 泛型继承规则总结

  1. 泛型类/接口可以继承或实现,子类可以指定具体类型或保留泛型类型参数。

  2. 泛型类型之间没有继承关系,即使类型参数之间有继承关系。

  3. 通配符(?)可以表示泛型类型的继承关系

    • <? extends T>:表示T或其子类。

    • <? super T>:表示T或其父类。

  4. 泛型方法可以继承或重写,但必须保持方法签名一致。

  5. 泛型数组的继承受限制,通常使用通配符或类型擦除来处理。


反射与泛型

在 Java 中,java.lang.reflect.Type 是一个表示类型(如类、泛型、数组等)的接口,它是 Java 反射机制中用于描述类型信息的核心接口之一。Type 接口及其子接口提供了对 Java 类型系统的全面支持,尤其是在处理泛型时非常有用。

Type 接口本身是一个标记接口(没有方法),它的具体实现由以下几个子接口和类完成:


1. Type 接口的子接口和实现类

Type 接口有四个直接子接口和一个实现类:

(1) Class<T>
  • 作用:表示一个具体的类或接口类型。

  • 特点

    • 是 Type 接口的唯一直接实现类。

    • 可以表示普通类、接口、数组类型和基本类型。

    • 通过 Class<T> 可以获取类的元信息(如类名、方法、字段等)。

  • 示例

    Class<String> stringClass = String.class;
    System.out.println(stringClass.getName()); // 输出 "java.lang.String"
(2) ParameterizedType
  • 作用:表示参数化类型(泛型类型),例如 List<String>

  • 方法

    • Type[] getActualTypeArguments():返回泛型参数的实际类型(如 String)。

    • Type getRawType():返回原始类型(如 List)。

    • Type getOwnerType():返回外部类的类型(如果是内部类)。

  • 示例

    List<String> list = new ArrayList<>();
    Type type = list.getClass().getGenericSuperclass(); // 获取 ArrayList 的父类类型
    if (type instanceof ParameterizedType) {
        ParameterizedType pType = (ParameterizedType) type;
        System.out.println(pType.getRawType()); // 输出 "java.util.AbstractList"
        for (Type arg : pType.getActualTypeArguments()) {
            System.out.println(arg); // 输出 "E"(泛型参数)
        }
    }
     
(3) TypeVariable<D>
  • 作用:表示泛型类型变量(如 TE 等)。

  • 方法

    • Type[] getBounds():返回类型变量的上界(如 T extends Number)。

    • D getGenericDeclaration():返回声明该类型变量的泛型声明(如类或方法)。

    • String getName():返回类型变量的名称(如 T)。

  • 示例

    class MyClass<T extends Number> {
        T value;
    }
    TypeVariable<?>[] typeVariables = MyClass.class.getTypeParameters();
    for (TypeVariable<?> tv : typeVariables) {
        System.out.println(tv.getName()); // 输出 "T"
        for (Type bound : tv.getBounds()) {
            System.out.println(bound); // 输出 "java.lang.Number"
        }
    }
     
(4) GenericArrayType
  • 作用:表示泛型数组类型(如 T[])。

  • 方法

    • Type getGenericComponentType():返回数组的组件类型(如 T)。

  • 示例

    List<String>[] array;
    Type type = array.getClass().getGenericSuperclass();
    if (type instanceof GenericArrayType) {
        GenericArrayType gaType = (GenericArrayType) type;
        System.out.println(gaType.getGenericComponentType()); // 输出 "java.util.List<java.lang.String>"
    }
     
(5) WildcardType
  • 作用:表示通配符类型(如 ?? extends Number? super Integer)。

  • 方法

    • Type[] getUpperBounds():返回通配符的上界(如 Number)。

    • Type[] getLowerBounds():返回通配符的下界(如 Integer)。

  • 示例

    Type type = MyClass.class.getGenericSuperclass();
    if (type instanceof ParameterizedType) {
        ParameterizedType pType = (ParameterizedType) type;
        for (Type arg : pType.getActualTypeArguments()) {
            if (arg instanceof WildcardType) {
                WildcardType wType = (WildcardType) arg;
                for (Type upperBound : wType.getUpperBounds()) {
                    System.out.println(upperBound); // 输出上界
                }
                for (Type lowerBound : wType.getLowerBounds()) {
                    System.out.println(lowerBound); // 输出下界
                }
            }
        }
    }
     

2. Type 与 Class<T> 的关系

  • Class<T> 是 Type 的实现类

    • Class<T> 是 Type 接口的唯一直接实现类。

    • Class<T> 主要用于表示具体的类或接口类型,而 Type 接口及其子接口用于表示更复杂的类型(如泛型、数组、通配符等)。

  • Class<T> 的局限性

    • Class<T> 只能表示具体的类型,无法直接表示泛型类型(如 List<String>)。

    • 如果需要处理泛型类型,必须使用 Type 接口及其子接口(如 ParameterizedType)。

  • Type 的扩展性

    • Type 接口及其子接口提供了对 Java 类型系统的完整描述,包括泛型、数组、通配符等。

    • 通过 Type 接口,可以在运行时获取泛型类型的具体信息。


3. 使用场景

  • 反射:通过 Type 接口及其子接口,可以在运行时获取泛型类型的具体信息。

  • 序列化/反序列化:在处理泛型对象时,需要获取泛型类型信息。

  • 框架开发:许多框架(如 Spring、Gson)使用 Type 接口来处理泛型类型。


4. 示例:获取泛型类型信息

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

class MyGenericClass<T> {
    private List<T> list;

    public MyGenericClass() {
        Type type = getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            ParameterizedType pType = (ParameterizedType) type;
            Type[] actualTypes = pType.getActualTypeArguments();
            for (Type actualType : actualTypes) {
                System.out.println(actualType); // 输出泛型参数类型
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        new MyGenericClass<String>() {}; // 输出 "class java.lang.String"
    }
}
 

假设有一个类层次结构如下:

class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}

通配符的超类型(上限)限定

语法<? extends T>,

含义:表示类型是 T 或其子类型。
特点

  • 只能从中读取数据,且读取的数据类型是 T

  • 不能向其中写入数据(除了 null),因为具体类型未知。

你需要一个方法,能够处理 Animal 及其子类型的列表,并读取其中的数据:

public void printAnimals(List<? extends Animal> animals) {
    for (Animal animal : animals) {
        System.out.println(animal);
    }
}
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
dogs.add(new Dog());

List<Cat> cats = new ArrayList<>();
cats.add(new Cat());
cats.add(new Cat());

printAnimals(dogs); // 可以处理 Dog 列表
printAnimals(cats); // 可以处理 Cat 列表

为什么用 <? extends T>

  • 方法可以接受 Animal 及其子类型的列表(如 List<Dog> 或 List<Cat>)。

  • 从列表中读取的数据可以安全地视为 Animal 类型。

不允许写入元素

// Cat 是 Animal子类
List<? extends Animal> pets = new ArrayList<>();
pets.add(new Cat()); // 编译错误
pets.add(new Dog()); // 编译错误
pets.add(new Animal()); // 编译错误
pets.add(null); // 允许,因为 null 是所有引用类型的有效值

原因在于 List<? extends Animal> 使用了 通配符的超类型限定(Upper Bounded Wildcard),这种限定方式在泛型中限制了集合的写入操作。List<? extends Animal> 表示这是一个可以存储 Animal 或其子类型(如 CatDog)的列表,但具体是哪种子类型是未知的。编译器无法确定 pets 的实际类型是 List<Cat>List<Dog> 还是 List<Animal>,因此为了保证类型安全,编译器会禁止向这种列表中添加任何非 null 的元素。

具体原因:

  1. 类型不确定性

    • List<? extends Animal> 可能是 List<Cat>List<Dog> 或 List<Animal>

    • 如果你尝试添加一个 Cat,而 pets 实际上是 List<Dog>,就会导致类型不匹配,破坏类型安全。

  2. 编译器的保护机制

    • 为了避免运行时出现 ClassCastException,编译器直接禁止向 List<? extends Animal> 中添加任何元素(除了 null)。

如果你需要向列表中添加元素,应该使用 确定的具体类型 或者 通配符的下限限定(Lower Bounded Wildcard)

1. 使用具体类型:

如果你知道列表的具体类型是 Cat,可以直接使用 List<Cat>

List<Cat> cats = new ArrayList<>();
cats.add(new Cat()); // 允许

2. 使用下限限定(<? super T>):

如果你希望列表可以接受 Animal 及其父类型(如 Object),可以使用 List<? super Cat>

List<? super Cat> pets = new ArrayList<>();
pets.add(new Cat()); // 允许,因为 Cat 是 Animal 的子类型
pets.add(new Animal()); // 编译错误,因为 Animal 是 Cat 的父类型

通配符的子类型(下限)限定

语法<? super T>

含义:表示类型是 T 或其父类型。
特点

  • 可以向其中写入 T 类型的数据

  • 只能从中读取 Object 类型的数据,因为具体类型未知。

使用场景
当你需要向一个集合中写入数据,并且希望代码能够处理 T 及其父类型的集合时。

public void addDog(List<? super Dog> dogs) {
    dogs.add(new Dog()); // 可以安全地添加 Dog 对象
}
List<Animal> animals = new ArrayList<>();
List<Dog> dogs = new ArrayList<>();
List<Object> objects = new ArrayList<>();

addDog(animals); // 可以处理 Animal 列表
addDog(dogs);    // 可以处理 Dog 列表
addDog(objects); // 可以处理 Object 列表

为什么用 <? super T>

  • 方法可以接受 Dog 及其父类型的列表(如 List<Animal> 或 List<Object>)。

  • 可以向列表中添加 Dog 类型的对象。

虽然允许写入元素,但不允许写入T的父类元素

public void addDog(List<? super Dog> dogs) {
    dogs.add(new Dog()); // 安全,因为 Dog 是 ? super Dog 的子类型
    dogs.add(new Animal()); // 编译错误,因为 Animal 是 Dog 的父类型
}

在Java泛型中,List<? super Dog> 表示一个可以接受 Dog 类型或其任何父类型的列表。虽然这种通配符限定允许你向列表中添加 Dog 类型的对象,但它并不允许添加 Dog 的父类型对象(例如 Animal)。这就是为什么在 dogs.add(new Animal()); 这行代码会报错。

原因分析

  1. 类型安全性

    • List<? super Dog> 表示列表的元素类型是 Dog 或其父类型(例如 Animal 或 Object)。

    • 你可以向这样的列表中添加 Dog 类型的对象,因为 Dog 是 ? super Dog 的子类型,符合类型安全。

    • 但是,你不能添加 Animal 类型的对象,因为 Animal 是 Dog 的父类型,而 ? super Dog 并不保证列表的具体类型是 Animal。列表的实际类型可能是 Dog 或 Object,因此添加 Animal 可能会导致类型不安全。

  2. 编译器限制

    • 编译器无法确定 List<? super Dog> 的具体类型是什么。它只知道这个列表可以接受 Dog 或其父类型的对象。

    • 由于 Animal 是 Dog 的父类型,编译器无法保证 Animal 类型的对象可以安全地添加到列表中。因此,编译器会报错以避免潜在的类型安全问题。

对比超类型限定和子类型限定

特性超类型限定 (<? extends T>)子类型限定 (<? super T>)
类型范围T 或其子类型T 或其父类型
读取数据安全,可以视为 T 类型不安全,只能视为 Object
写入数据不安全,只能添加 null安全,可以添加 T 类型
适用场景需要从集合中读取数据需要向集合中写入数据

<T extends Comparable<? super T>> 和 <T extends Comparable>比较

在大多数情况下,推荐使用 <T extends Comparable<? super T>>,因为它提供了更大的灵活性和类型安全性。

  • <T extends Comparable<? super T>>:更灵活,允许 T 与其父类进行比较。

  • <T extends Comparable>:限制更严格,要求 T 直接实现 Comparable

相同点

  1. 类型约束:两者都要求 T 实现 Comparable 接口。

  2. 泛型使用:都用于限制泛型类型参数 T 的范围。

不同点

  1. 灵活性

    • <T extends Comparable<? super T>>:允许 T 实现 Comparable 接口,且 Comparable 的类型参数可以是 T 或其父类。这意味着 T 可以与自身或其父类进行比较,提供了更大的灵活性。

    • <T extends Comparable>:要求 T 直接实现 Comparable 接口,且类型参数必须是 T 自身,限制更严格。

  2. 类型安全性

    • <T extends Comparable<? super T>>:更灵活且类型安全,允许 T 与其父类进行比较,减少了类型转换的需要。

    • <T extends Comparable>:类型安全性较低,可能需要进行类型转换,增加了运行时错误的风险。

例如以下场景,假设两个类

class Animal implements Comparable<Animal> {
    public int compareTo(Animal other) {
        // 实现比较逻辑
        return 0;
    }
}

class Dog extends Animal {
    // Dog 继承了 Animal 的 compareTo 方法
}
  • 对于<T extends Comparable<? super T>>
public static <T extends Comparable<? super T>> void sort(List<T> list) {
    // 可以接受 List<Animal> 或 List<Dog>
}

这里 T 可以是 Animal 或 Dog,因为 Dog 继承了 Animal 的 compareTo 方法。

  • 对于<T extends Comparable>
public static <T extends Comparable> void sort(List<T> list) {
    // 只能接受 List<Animal>,不能接受 List<Dog>
}

这里 T 必须是直接实现 Comparable 的类型,如 Animal,而不能是 Dog,因为 Dog 没有直接实现 Comparable

无界通配符

通配符捕获(Wildcard Capture)

Java 泛型中的一个概念,用于处理泛型类型中通配符(?)的具体类型推断问题。通配符本身表示未知类型,但在某些情况下,编译器需要推断出通配符的具体类型以便进行类型安全的操作。这种推断过程称为“通配符捕获”。

  • 背景

通配符类型(如 List<?>)本身是只读的,不能直接修改。如果需要修改通配符类型的对象,必须通过通配符捕获来推断出具体类型,从而保证类型安全。

  • 示例
public void swap(List<?> list, int i, int j) {
    // 通配符捕获:编译器推断出通配符的具体类型
    swapHelper(list, i, j);
}

// 辅助方法:用于捕获通配符的具体类型
private <T> void swapHelper(List<T> list, int i, int j) {
    T temp = list.get(i);
    list.set(i, list.get(j));
    list.set(j, temp);
}

解释:

  1. swap 方法

    • 参数 List<?> 是一个通配符类型,表示未知类型的列表。

    • 由于 List<?> 不能直接修改(只能读取为 Object),我们需要一个辅助方法来处理具体类型。

  2. swapHelper 方法

    • 这是一个泛型方法,类型参数 T 被用来捕获 List<?> 中的具体类型。

    • 编译器会推断出 T 是 List<?> 中实际的元素类型,从而允许安全地操作列表。

  3. 通配符捕获

    • 在调用 swapHelper 时,编译器会“捕获” List<?> 中的具体类型,并将其作为 T 传递给 swapHelper

    • 这样,swapHelper 就可以安全地操作列表元素。

List 和 List<?>的区别

1. List(原始类型,Raw Type)

  • 定义List 是一个没有指定类型参数的泛型类,称为原始类型(Raw Type)。

  • 特点

    • 可以存储任意类型的对象(类似于 List<Object>)。

    • 失去了泛型的类型检查,编译器不会对元素的类型进行约束。

    • 使用时需要手动进行类型转换,容易引发运行时异常(如 ClassCastException)。

  • 如下,类型不安全,容易引发运行时错误

    List list = new ArrayList();
    list.add("Hello");
    list.add(123); // 可以存储任意类型
    String str = (String) list.get(0); // 需要强制类型转换
    Integer num = (Integer) list.get(1); // 需要强制类型转换
     

2. List<?>(无界通配符类型,Unbounded Wildcard)

  • 定义List<?> 表示一个未知类型的泛型列表,称为无界通配符类型。

  • 特点

    • 可以接受任何类型的 List,例如 List<String>List<Integer> 等。

    • 不能向其中添加任何元素(除了 null),因为类型未知。

    • 只能从中读取元素,且读取的元素类型是 Object

  • 如下, 通常用于编写通用方法,可以接受任意类型的 List
List<?> list = new ArrayList<String>();
list.add(null); // 只能添加 null
Object obj = list.get(0); // 读取的元素类型是 Object

相关文章:

  • 交通物联网:概念、历史、现状与展望
  • Redis 字符串(String)
  • 【分布式理论11】分布式协同之分布式事务(一个应用操作多个资源):从刚性事务到柔性事务的演进
  • Vue 监听属性(watch)
  • MySQL数据库入门到大蛇尚硅谷宋红康老师笔记 高级篇 part 2
  • 嵌入式工业级显示器在环保垃圾柜设备中发挥着至关重要的作用
  • Linux 实操篇 组管理和权限管理、定时任务调度、Linux磁盘分区和挂载
  • Linux-C/C++《C/9、信号:基础》(基本概念、信号分类、信号传递等)
  • 2025年archlinux tigervnc分辨率设置不生效的问题
  • Deepseek 与 ChatGPT:AI 浪潮中的双子星较量
  • YOLOv11-ultralytics-8.3.67部分代码阅读笔记-loaders.py
  • 机器视觉--图像的运算(减法)
  • matplotlib无法显示中文的问题
  • HTML/CSS中子代选择器
  • AI赋能前端开发:告别加班,提升抗压能力
  • 【Python爬虫(29)】爬虫数据生命线:质量评估与监控全解
  • 软件测试与软件开发之间的关系
  • spring注解开发(配置bean的作用范围与生命周期)(3)
  • 游戏引擎学习第113天
  • DeepSeek 引领AI 大模型时代,服务器产业如何破局进化?
  • 阿坝县建设局网站/游戏推广员如何推广引流
  • 品牌营销策划网站/网站建设平台
  • 如何提高网站的收录/学会计哪个培训机构比较正规
  • 简单小网站/怎么做app推广代理
  • asp.net+mvc+网站开发/营销型网站建设解决方案
  • 刷QQ砖的网站咋做/app开发工具哪个好