java泛型

基本信息

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

  • 泛型的基本概念

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

  • 泛型的特点

(1)类型安全

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

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

(2)代码复用

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

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

(3)消除强制类型转换

  • 使用泛型后,从集合中获取元素时不需要显式地进行类型转换。
cpp 复制代码
List<String> list = new ArrayList<>();
list.add("Java");
String s = list.get(0); // 无需强制类型转换

(4)通配符和边界

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

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

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

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

(5)类型擦除

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

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

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

泛型的继承规则

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

1. 泛型类/接口的继承

(1)继承泛型类

  • 子类可以继承父类的泛型类型参数,也可以指定具体的类型。
java 复制代码
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)泛型类型之间没有继承关系

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

  • 示例:

    java 复制代码
    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或其父类。

  • 示例:

    java 复制代码
    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> 可以获取类的元信息(如类名、方法、字段等)。

  • 示例

    java 复制代码
    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():返回外部类的类型(如果是内部类)。

  • 示例

    java 复制代码
    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)。

  • 示例

    java 复制代码
    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)。
  • 示例

    java 复制代码
    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)。

  • 示例

    java 复制代码
    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. TypeClass<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. 示例:获取泛型类型信息

java 复制代码
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"
    }
}
复制代码

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

java 复制代码
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}

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

语法**<? extends T>,**

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

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

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

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

java 复制代码
public void printAnimals(List<? extends Animal> animals) {
    for (Animal animal : animals) {
        System.out.println(animal);
    }
}
java 复制代码
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 类型。

不允许写入元素

java 复制代码
// 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>

java 复制代码
List<Cat> cats = new ArrayList<>();
cats.add(new Cat()); // 允许
  1. 使用下限限定(<? super T>):

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

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

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

语法<? super T>

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

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

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

使用场景

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

java 复制代码
public void addDog(List<? super Dog> dogs) {
    dogs.add(new Dog()); // 可以安全地添加 Dog 对象
}
java 复制代码
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的父类元素

java 复制代码
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 或其父类型(例如 AnimalObject)。

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

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

  2. 编译器限制

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

    • 由于 AnimalDog 的父类型,编译器无法保证 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>:类型安全性较低,可能需要进行类型转换,增加了运行时错误的风险。

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

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

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

这里 T 可以是 AnimalDog,因为 Dog 继承了 AnimalcompareTo 方法。

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

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

无界通配符

通配符捕获(Wildcard Capture)

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

  • 背景

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

  • 示例
java 复制代码
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<?> 中的具体类型。

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

  3. 通配符捕获

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

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

ListList<?>的区别

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

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

  • 特点

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

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

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

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

    java 复制代码
    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

java 复制代码
List<?> list = new ArrayList<String>();
list.add(null); // 只能添加 null
Object obj = list.get(0); // 读取的元素类型是 Object
相关推荐
信徒_1 分钟前
Go 语言中的协程
开发语言·后端·golang
begei6 分钟前
飞牛os使用ddns-go配合华为云实现内网穿透
开发语言·golang·华为云
我就是我35234 分钟前
记录一次SpringMVC的406错误
java·后端·springmvc
向哆哆37 分钟前
Java应用程序的跨平台性能优化研究
java·开发语言·性能优化
free-elcmacom1 小时前
C语言番外篇(3)------------>break、continue
c语言·开发语言
ekkcole1 小时前
windows使用命令解压jar包,替换里面的文件。并重新打包成jar包,解决Failed to get nested archive for entry
java·windows·jar
卑微的小鬼1 小时前
Go 语言结合 Redis 实现固定窗口、滑动窗口、令牌桶和漏桶限流算法的示例代码
开发语言·redis·golang
ylfhpy2 小时前
Python常见面试题的详解16
开发语言·python·面试
handsomestWei2 小时前
java实现多图合成mp4和视频附件下载
java·开发语言·音视频·wutool·图片合成视频·视频附件下载
宝哥的菜鸟之路2 小时前
Python 数据分析概述 ①
开发语言·python·数据分析