Java 泛型

2023-09-15 20:48:40

一、泛型简介

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

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

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

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

C++ 模板编程

template<typename T>  // 这里的 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 的泛型标记符都是约定俗成的,没有说强制某一种泛型标记符表示什么含义,只是按照约定俗成的来可以让其他人更容易理解你的代码。

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

标记符约定全称描述
EElement在集合中使用,因为集合中的都是元素(element)
TType表示任意 Java 的引用类型
KKey在字典中使用,表示键(key)
VValue在字典中使用,表示值(value)
NNumber表示数字(number)类型
UUnbounded无限制类型通配符,常用于泛型方法和泛型类的定义中
??无限制类型通配符,常用于泛型方法的返回类型声明和方法参数中

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

下面是一个简单的示例:

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

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

2.2 限定泛型的范围

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

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

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

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

import java.util.ArrayList;

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

    public static void main(String[] args) {
        ArrayList<Integer> integerArrayList = new ArrayList<>();
        ArrayList<String> 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 extends Comparable> T maxNumber (T a, T b) { // 泛型方法
    return a.compareTo(b) > 0 ? a : b; // 返回较大值
}

四、泛型类(接口)

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

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

下面是一个具体示例:

class TypeBox<T> { // 泛型类
    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<Integer> 将被替换为 ArrayList<Object>,即 ArrayList。

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

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

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

下面是一个示例:

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

C#网站代码防止漏洞和攻击 增强网站安全性方法

输入验证:永远不要信任用户提供的输入数据。始终对用户提交的数据进行验证和过滤,以防止恶意输入。使用正则表达式、白名单过滤或内置的.NET验证来验证输入。防止SQL注入:使用参数化查询或存储过程来执行数据库查询,而不是将用户输入直接嵌入SQL语句中。这可以有效防止SQL注入攻击。防止跨站脚本(XSS)攻击:始终对用户提交

SpringMVC之JSR303和拦截器

目录一.JSR3031.1.介绍1.2.为什么要使用JSR-3031.3.常用注解1.4.快速入门1.4.1.导入依赖1.4.2.配置校验规则1.4.3.编写方法校验1.4.4.测试二.拦截器2.1.什么是拦截器?2.2.拦截器与过滤器的区别2.3.拦截器的应用场景2.4.基础使用2.5.用户登录权限控制最后实战Spr

指针进阶笔试题

今天分享的是指针的笔试题,相信看完这篇文章对指针又会有深入的了解,让我们来学习吧。首先分享的是指针和数组的关系,我们都知道数组名是首元素的地址,那就让我们来看一下一维数组和指针的关系吧//一维数组inta[]={1,2,3,4};printf("%d\n",sizeof(a));printf("%d\n",sizeof

Java手写桶排序和算法案例拓展

Java手写桶排序和算法案例拓展1.算法思维导图解释实现思路原理#mermaid-svg-OFCWrsU78anzeD09{font-family:"trebuchetms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-OFCWrsU7

【Python】python -m pip install 和 pip install 的区别

文章目录一、pipinstall二、python-mpipinstall三、两者的总结一、pipinstall当你使用pipinstall命令时,你正在使用Python包管理器pip来安装Python包或模块。以下是关于pipinstall的详细理解:安装包:pipinstall<package>命令用于安装指定的Py

【Python】保姆级万字讲解:Python中的 pip 和 conda 的理解

文章目录一、pip的理解1.1安装1.2如何使用1.3升级1.4安装某个版本的包1.5卸载或者是更新包1.6查看某个包的信息1.7查看需要被升级的包1.8查看兼容问题1.9指定国内源来安装1.10下载包但是不安装1.11批量安装软件包二、conda的理解2.1下载源channel详解2.1.1国内部分好用conda下载

Vue学习笔记

初识Vue需要创建Vue实例root容器代码需要符合html规范,但是混入了一些特殊的Vue语法root容器代码被称为Vue模板容器和Vue实力之间一一对应注意区分js表达式、js代码:一个表达式会生成一个值,可以放在任何一个需要值的地方{{}}需要写js表达式,可以读取data中的所有属性data中属性的值发生改变,

回归与聚类算法系列⑤:逻辑回归

目录1、介绍2、原理输入激活函数3、损失及其优化损失函数优化4、API5、案例:乳腺癌肿瘤预测数据集代码🍃作者介绍:双非本科大三网络工程专业在读,阿里云专家博主,专注于Java领域学习,擅长web应用开发、数据结构和算法,初步涉猎Python人工智能开发。🦅主页:@逐梦苍穹📕回归与聚类算法系列⭐①:概念简述⭐②:

Spring Bean&生命周期图&扩展接口介绍&spring的简化配置

目录1.生命周期简图2.扩展接口介绍2.1Aware接口2.2BeanPostProcessor接口2.3InitializingBean2.4DisposableBean2.5BeanFactoryPostProcessor接口3.spring的简化配置3.1项目搭建3.2Bean的配置和值注入3.3AOP的示例1.

学习Bootstrap 5的第十四天

目录Toast如何创建Toast实例打开Toast实例滚动监听(Scrollspy)如何创建滚动监听实例侧边栏导航(Offcanvas)如何创建Offcanvas侧边栏实例侧边栏的方向实例设置背景及背景是否可滚动实例侧边栏案例实例ToastToast组件类似警告框,当发生某些事情时(例如当用户单击按钮、提交表单等)时,

7.从句学习

目录一、从句。(1)从句总结。(2)从句类型。(3)引导词(常见的引导词)。(3.1)名词性从句:(3.2)形容词性从句:(3.3)副词性从句:(4)从句举例。(4.1)名词性从句举例。(4.2)形容词性从句举例。(4.3)副词性从句举例。一、从句。(1)从句总结。1.名词性从句:从句(引导词+句子/单词)直接充当句子

热文推荐