神泱 发表于 7 天前

Java泛型:代码世界的“万能钥匙”与“类型契约”

一、关于泛型

1.1 简介

Java泛型自J2SE 5.0引入,彻底改变了开发者处理数据类型的方式,将类型安全与代码复用推向新高度。
Java 泛型(Generics)是 Java 5 引入的一个特性,允许在类、接口和方法中使用类型参数,使得代码更加通用、灵活、可重用,并且能提供更强的类型安全性。

‍在 Java 中,泛型允许在定义类、接口或方法时使用类型参数,具体的类型在使用时再指定。泛型使得同一类或方法能够处理不同类型的数据,而无需编写多个版本的代码。
例如,使用泛型的集合类 List​ 可以存储不同类型的元素,T​ 代表类型参数。
1.2 发展

Java 泛型(Generics)的发展历程可以追溯到 Java 语言的早期版本。下面是 Java 泛型的主要发展过程:
1. Java 语言初期(1.0 - 1.4)

在 Java 的早期版本中,Java 并没有提供泛型机制。开发者只能使用原始类型(如 Object​)来实现代码的通用性,这导致了以下两个主要问题:

[*]类型安全问题:由于 Java 不支持泛型,开发者必须使用 Object​ 来表示任何类型,这导致了大量的类型转换错误,增加了出错的机会。
[*]代码冗余:开发者不得不编写多个版本的代码来处理不同类型的数据,这使得代码冗长且不易维护。
例如,早期集合类如 Vector​ 和 ArrayList​ 只能存储 Object​ 类型的元素,需要在取出元素时进行强制类型转换:
Vector vector = new Vector();
vector.add("Hello");
String str = (String) vector.get(0);// 需要类型转换2. Java 5(2004年发布) - 引入泛型

Java 5(J2SE 5.0,也叫做 "Tiger")是引入泛型的关键版本。泛型成为 Java 的一项重要特性,它改变了 Java 语言的编程方式,使得类型安全性和代码可重用性得到了极大的提升。主要特性如下:

[*]引入泛型语法:使用 ​ 表示类型参数,例如 List​ 表示一个只能存储 String​ 类型的集合。
[*]类型擦除机制:泛型在编译时会被擦除(type erasure),即类型参数在编译后的字节码中不再保留具体类型信息。
[*]通配符(Wildcard)与边界:支持通配符(如 ?​)和上界(extends​)与下界(super​)限定符,用于进一步限制泛型的类型范围。
// 引入泛型的例子
List<String> list = new ArrayList<>();
list.add("Hello");
String str = list.get(0);// 无需强制类型转换3. Java 6 和 Java 7(2006年和2011年发布)

Java 6 和 Java 7 主要对泛型进行了一些优化和增强,但没有引入重大变化。

[*]Java 6(Java 5 后的修复与优化):在 Java 6 中,泛型得到了进一步的优化,减少了类型擦除带来的性能损失。
[*]Java 7(引入钻石操作符​​ ​ ):Java 7 引入了钻石操作符(​),它使得代码更加简洁,开发者不再需要显式地指定泛型类型,在编译时由编译器自动推断。
// Java 7 引入的钻石操作符
List<String> list = new ArrayList<>();// 不再需要显式指定泛型类型4. Java 8(2014年发布)

Java 8 在泛型方面没有引入新特性,但它引入了与泛型紧密相关的其他功能,特别是 Lambda 表达式 和 Stream API。这两个特性使得开发者能够更简洁地处理集合和泛型数据,尤其是在函数式编程风格的代码中。

[*]Stream API:通过泛型支持,Stream API 可以对集合进行更复杂的操作,而无需显式地指定具体类型。
List<String> list = Arrays.asList("A", "B", "C");
list.stream().filter(s -> s.startsWith("A")).forEach(System.out::println);5. Java 9 及以后(2017年发布,后续版本)

Java 9 引入了模块化(Project Jigsaw),但并未对泛型进行重大更新。此后,Java 10、Java 11 等版本也未对泛型进行大的改动。
然而,Java 10 引入了 局部变量类型推断(​​var​​ ) ,可以让编译器自动推断局部变量的类型,包括泛型类型。这使得在某些场景下,泛型的使用更加灵活和简洁。
// Java 10 引入的局部变量类型推断
var list = new ArrayList<String>();// 编译器推断类型6. 泛型的局限性与挑战

尽管泛型在 Java 中带来了很多好处,但也存在一些局限性和挑战:

[*]类型擦除:泛型信息在编译时会被擦除,导致 Java 在运行时无法获取泛型的具体类型。这限制了某些操作,如无法通过反射获得泛型类型。
List<String> list = new ArrayList<>();
if (list instanceof List<String>) {// 编译时错误,因为泛型信息被擦除
    // 代码无法运行
}
[*]无法创建泛型数组:由于类型擦除的机制,不能直接创建带有泛型类型的数组。例如,new T[]​ 是不允许的。
// 编译错误
T[] array = new T;// 无法创建泛型数组

1.3 功能特点

Java 泛型(Generics)是一种允许编写可以与多种数据类型一起工作的类、接口和方法的特性。它是在 Java 5 中引入的,旨在提高代码的类型安全性、可读性和重用性。以下是 Java 泛型的一些主要特点:

[*]类型安全:

[*]泛型提供编译时的类型检查,这意味着如果尝试将不兼容类型的对象插入到泛型集合中,编译器会报错,而不是在运行时抛出 ClassCastException​ 。
[*]这种特性使得开发者能够在开发阶段捕获错误,而不需要等到程序运行时才发现问题。

[*]消除强制类型转换:

[*]使用泛型可以减少或完全避免显式的类型转换。例如,在没有使用泛型的情况下,从集合中获取元素通常需要进行强制类型转换;而在使用了泛型之后,这些转换是自动完成的,并且是隐式的 。

[*]向后兼容:

[*]泛型被设计为向后兼容的,因此现有的非泛型代码可以在新的支持泛型的环境中继续工作。这种兼容性是通过类型擦除实现的,即在编译期移除所有泛型类型信息 。

[*]通配符:

[*]泛型支持通配符 ?​ 来表示未知类型。这允许编写更加灵活的方法签名,比如接受任意类型的参数列表而不必指定具体的类型 。

[*]有界类型参数:

[*]泛型允许定义有界的类型参数,这意味着你可以限制传入的类型必须是某个特定类型或其子类。例如,你可以定义一个泛型方法只接受实现了 Comparable​ 接口的类型作为参数 。

[*]类型擦除:

[*]在Java中,泛型信息在编译时被移除,这个过程被称为类型擦除。这意味着泛型类型参数在运行时并不存在,所有的泛型类型都会被替换为其上限类型(通常是 Object​),除非指定了下限 。

[*]泛型不能用于基本数据类型:

[*]泛型只能用于引用类型,如 Integer​, String​, 自定义类等,而不能直接用于基本数据类型如 int​, char​, boolean​ 等。不过,由于自动装箱/拆箱机制的存在,可以直接使用包装类来间接地处理基本数据类型 。

[*]泛型类和泛型方法:

[*]泛型不仅可以应用于类,也可以应用于单独的方法。这意味着你可以在不改变整个类的前提下,使单个方法具有泛型能力 。

[*]性能提升:

[*]使用泛型减少了不必要的装箱和拆箱操作,特别是在处理集合时,这可以带来一定的性能增益 。

[*]复用性和灵活性:

[*]泛型提高了代码的复用性,因为相同的逻辑可以适用于多种不同的数据类型,同时保持了类型的安全性 。



二、泛型语法


[*]泛型类:指定类的类型参数。
[*]泛型方法:指定方法的类型参数。
[*]通配符(Wildcard) :用于表示不确定的类型。

2.1 泛型类

使用泛型定义类或接口时,需要在类名后面加上尖括号 ​< >​​,并在其中指定类型参数。
类型参数通常使用大写字母表示(如:T, E, K, V等)。常用T表示,是Type的缩写。

示例

// 定义一个泛型类
public class Box<T> {
    private T value;

    public T getValue() {
      return value;
    }

    public void setValue(T value) {
      this.value = value;
    }
}使用泛型类时,指定实际类型:
Box<Integer> intBox = new Box<>();
intBox.setValue(100);
System.out.println(intBox.getValue()); // 输出100

Box<String> strBox = new Box<>();
strBox.setValue("Hello");
System.out.println(strBox.getValue()); // 输出Hello‍



2.2 泛型接口

接口定义类型参数,实现类需指定具体类型。

示例

public interface List<T> {
    void add(T element);
    T get(int index);
}

// 实现类
public class StringList implements List<String> {
    private ArrayList<String> elements = new ArrayList<>();

    @Override
    public void add(String element) {
      elements.add(element);
    }

    @Override
    public String get(int index) {
      return elements.get(index);
    }
}​








2.3 泛型方法

泛型方法是指在方法中使用类型参数。它允许在方法中灵活地指定类型。

示例

泛型方法是指在方法中使用类型参数。它允许在方法中灵活地指定类型。
public <T> void printArray(T[] array) {
    for (T element : array) {
      System.out.println(element);
    }
}调用时可以传入不同类型的数组:
Integer[] intArray = {1, 2, 3};
String[] strArray = {"A", "B", "C"};

printArray(intArray); // 输出 1 2 3
printArray(strArray); // 输出 A B C‍
package org.example.Generics;

public class MethodTest {

    public static void main(String[] args) {
      Integer[] intArray = {1, 2, 3};
      String[] strArray = {"A", "B", "C"};

      MethodTest test = new MethodTest();
      test.printArray(intArray);
      test.printArray(strArray);
    }


    public <T> void printArray(T[] array) {
      for (T element : array) {
            System.out.println(element);
      }
    }

}‍


2.4 通配符(Wildcard)

泛型的通配符 ?​ 用于增强灵活性,常见于方法参数或返回值。

[*]? extends T​:表示类型是 T 或 T 的子类型。
[*]? super T​:表示类型是 T 或 T 的父类型。
[*]?​:表示未知类型。

1. 无界通配符 ​

匹配任意类型,但只能读取数据,无法写入。
public void printList(List<?> list) {
    for (Object elem : list) {
      System.out.println(elem);
    }
}‍
2. 上界通配符
页: [1]
查看完整版本: Java泛型:代码世界的“万能钥匙”与“类型契约”