Java 泛型

一、泛型简介

Java 泛型(generics)是 JDK 5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。下面给一个 Java 泛型的简单例子:

public static  void printArray(E[] inputArray){ // 这里的 E 可代表任意类型
    for (E element: inputArray){ // 输出数组中的元素,无论是什么类型
        System.out.println(element);
    }
}

不仅仅在 Java 中有泛型的概念,在 C++ 中也有类似的概念,叫做模板。当然,一般来说只有静态语言才说有类似于泛型的语法,其实不然,对于动态语言也有类似的语法,但用途不太相同,如 Python3.12 中的新类型提示语法,类型形参语法,就有点像仿 Java 泛型语法而成的类型提示语法。

下面给几个例子以进行对比学习:

C++ 模板编程

template  // 这里的 T 与 Java 泛型中的 T 类似  
void swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

// C++ 的“泛型”有其独特性,语法与 Java 相比不太一样,毕竟 C++ 中这称之为模板

Python 类型形参语法

def print_array[T](array: list[T]) -> None:  # 此处的 T 只是为了类型提示,无实际功能,可认为是特殊的注释
    """ Type Parameter Syntax """
    print(*array)

# Java 用的是尖括号("<>")表示,而 Python 则是用方括号("[]")表示

二、泛型语法

所有的泛型声明都是一对尖括号("<>")加上泛型标记符以及范围限定的关键字,多个标记符之间用逗号隔开。

2.1 泛型标记符

Java 的泛型标记符都是约定俗成的,没有说强制某一种泛型标记符表示什么含义,只是按照约定俗成的来可以让其他人更容易理解你的代码。

下面是一些常见的泛型标记符:

标记符 约定全称 描述
E Element 在集合中使用,因为集合中的都是元素(element)
T Type 表示任意 Java 的引用类型
K Key 在字典中使用,表示键(key)
V Value 在字典中使用,表示值(value)
N Number 表示数字(number)类型
U Unbounded 无限制类型通配符,常用于泛型方法和泛型类的定义中
? ? 无限制类型通配符,常用于泛型方法的返回类型声明和方法参数中

另外,这里强调一点,上面的常用标记符只是约定俗成的,标记符并没有强制只能是一个字符,多个字符也是可以的,比如 abc,不过标记符还是要满足类型的写法,毕竟其本质也只是表示类型而已,因此一般使用大写开头。

下面是一个简单的示例:

public static  void print(K key, V value){ // 这里举的例子是:泛型方法参数
    System.out.println(key + " : " + value);
}

当然,System.out.println 本身就可以打印很多类型的对象,这里只是举个例子,来介绍泛型的用法。

2.2 限定泛型的范围

在泛型的声明中可以使用 extends 和 super 关键字来限定泛型的范围,使其更符合我们预期的要求,含义与平时使用时略有差异, 下面列出一个表格以体现具体的差异:

关键字(限定词) 使用方法 描述
extends 限定泛型 T 的上界,即 T 必须是类 someType 的子类
super 限定泛型 T 的下界,即 T 必须是类 someType 的父类

特别说明,上表中 someType 可以是类(class),也可以是接口(interface)。

下面给出一些具体的示例来详细地说明它们的用法:

import java.util.ArrayList;

public class Test {
    // extends Number 限定数组里面只能是数字
    public static  void printNumberArray(ArrayList arrayList) {
        for (N n: arrayList) System.out.println(n);
    }

    public static void main(String[] args) {
        ArrayList integerArrayList = new ArrayList<>();
        ArrayList stringArrayList = new ArrayList<>();
        integerArrayList.add(1);
        integerArrayList.add(2);
        printNumberArray(integerArrayList); // 正常运行
        stringArrayList.add("1");
        stringArrayList.add("2");
        printNumberArray(stringArrayList); // 类型不符合泛型范围,报错!
    }
}

三、泛型方法

泛型用在方法中,可以表示此方法的参数是泛型的,也可以是此方法的返回类型是泛型的。

3.1 泛型参数类型

泛型的参数类型很好写,泛型的声明放在方法的返回类型之前,修饰符之后,声明之后就可以在参数中使用泛型了。

语法:修饰符 泛型声明 返回类型 方法名 参数列表

3.2 泛型返回类型

泛型返回类型和泛型参数类型类似,泛型声明在方法的返回类型之前,修饰符之后,声明之后就可以在返回类型中直接使用泛型了。语法和上述一致。

下面是一个简单的示例:

public static  T maxNumber (T a, T b) { // 泛型方法
    return a.compareTo(b) > 0 ? a : b; // 返回较大值
}

四、泛型类(接口)

泛型类中泛型声明与泛型方法非常类似,但又略有不同。其泛型声明在类名的后面。

语法:修饰符 关键字 类(接口)名 泛型声明

下面是一个具体示例:

class TypeBox { // 泛型类
    public T type; // 泛型属性

    TypeBox (T type){
        this.type = type;
    }

    public void set(T value){
        type = value;
    }

    public T get(){
        return type;
    }
}

public class Test {
    public static void main(String[] args) {
        TypeBox typeBox_1 = new TypeBox(666); // 整数:OK
        TypeBox typeBox_2 = new TypeBox("Java"); // 字符串:OK
        System.out.println(typeBox_1.get()); // Output: 666
        System.out.println(typeBox_2.get()); // Output: Java
    }
}

五、类型擦除

类型擦除是 Java 泛型中的一类特殊的机制,它出现的目的是为了兼容 JDK 5 之前的代码。

类型擦除是指,在 Java 运行时,Java 会把所有的泛型都替换为它们最顶级的上界,也就是 Object 对象。比如 ArrayList 将被替换为 ArrayList,即 ArrayList。

在 JDK 5 之前,是没有泛型的,当时的程序员们为了实现类似泛型的功能,是用 Object 对象代替完成的,但 Object 对象实现的“泛型”并不完善,无法真正地做到和 JDK 5 出现的泛型一样,但由于这种做法已经非常普遍了,为了兼容这种早期做法,Java 泛型就有了类型擦除这种机制,为的就是兼容旧代码。这种机制也是 Java 泛型和其他编程语言泛型的区别之一。

顺便提一下,用 Object 对象实现类似泛型的功能:类型都用 Object 来定义,然后在运行时进行检查。

这里有个细节需要注意一下:既然有类型擦除机制,会在运行时将泛型擦除掉,全部用 Object 替换,那为什么泛型机制还可以现在泛型类型变量的值?比如,ArrayList 为什么不能 add 一个 String 类型的数据?其实,并非不行,只是只能在运行时进行 add!运行时怎么 add 呢?这就要涉及到反射的知识了!反射知识详见:Java 反射_小康2022的博客-CSDN博客

下面是一个示例:

ArrayList arrayList = new ArrayList();
arrayList.add(666);
arrayList.add("Java"); // 此处无法直接 add,会报错
Class arrayListClass = arrayList.getClass();
Method add = arrayListClass.getDeclaredMethod("add", Object.class);
add.invoke(arrayList, "Java"); // 通过反射机制可以在类型擦除机制之后成功 add

你可能感兴趣的:(全面详细的,Java,Java,泛型)