深入理解Jdk5引入的Java泛型:类型安全与灵活性并存

news2024/9/22 23:32:53

深入理解Jdk5引入的Java泛型:类型安全与灵活性并存

在这里插入图片描述

​ 在Java的中,有一个强大的工具,它可以让你在编写代码时既保持类型安全,又享受灵活性。**这个工具就是——泛型(Generics)。**本文将引导你深入了解Java泛型的奥秘,让你从此编写更强大、更安全的代码。

基本概念:

泛型是什么?

​ 泛型是Java中的一项强大功能,它允许你编写能够处理多种数据类型的类、接口和方法。通过使用泛型,你可以将数据类型作为参数传递给类或方法,从而实现更高层次的代码重用和类型安全。

Java 泛型(generics)本质是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

假定我们有这样一个需求:写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,该如何实现?

答案是可以使用 Java 泛型

使用 Java 泛型的概念,我们可以写一个泛型方法来对一个对象数组排序。然后,调用该泛型方法来对整型数组、浮点数数组、字符串数组等进行排序。


泛型类和泛型方法

泛型在Java中有两种主要形式:泛型类和泛型方法。泛型类是具有一个或多个类型参数的类,而泛型方法是在调用时可以接受不同类型参数的方法。


泛型类的基本概念

泛型类是一种允许在创建类的实例时指定数据类型的类。通过使用泛型,你可以将类的数据类型参数化,从而使得类可以适用于多种数据类型。让我们来看一个简单的例子:一个通用的容器类Box

public class Box<T> {
    private T content;

    public Box(T content) {
        this.content = content;
    }

    public T getContent() {
        return content;
    }

    public void setContent(T content) {
        this.content = content;
    }
}

在上面的示例中,我们定义了一个泛型类Box,它可以存储任意类型的数据。T是一个类型参数,它在实例化Box对象时确定。

使用泛型类

现在,让我们看看如何使用泛型类Box来存储不同类型的数据。

public class GenericClassExample {

    public static void main(String[] args) {
        Box<Integer> intBox = new Box<>(42);//存储整数
        int intValue = intBox.getContent();
        System.out.println("Integer value: " + intValue);

        Box<String> stringBox = new Box<>("Hello, Stevedash!");//存储字符串
        String stringValue = stringBox.getContent();
        System.out.println("String value: " + stringValue);
    }
}

在上述示例中,我们创建了两个Box对象,一个存储整数,另一个存储字符串。通过使用泛型类,我们可以在编译时就确保数据类型的正确性,避免了在运行时出现类型错误。


类型安全与重用性

泛型类不仅提供了类型安全性,还增加了代码的重用性。你可以轻松地创建适用于不同类型的通用类,减少了代码的冗余。这使得你的程序更加健壮、可维护。


泛型类的限制

虽然泛型类非常强大,但也有一些限制。例如,无法创建泛型数组,也不能使用基本数据类型作为泛型参数(需要使用对应的包装类)。

自定义泛型类

除了使用Java提供的泛型类,你还可以自定义泛型类来满足特定需求。通过实现自己的泛型类,你可以更好地理解泛型的原理,以及如何灵活地使用它。


泛型方法

你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。

下面是定义泛型方法的规则:

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的 )。

  • 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。

  • 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。

  • 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像 int、double、char 等)。


    java 中泛型标记符:

  • E - Element (在集合中使用,因为集合中存放的是元素)

  • T - Type(Java 类)

  • K - Key(键)

  • V - Value(值)

  • N - Number(数值类型)

  • ? - 表示不确定的 java 类型


下面的例子演示了如何使用泛型方法打印不同类型的数组元素:

实例

public class GenericMethodTest
{
   // 泛型方法 printArray                         
   public static < E > void printArray( E[] inputArray )
   {
      // 输出数组元素            
         for ( E element : inputArray ){        
            System.out.printf( "%s ", element );
         }
         System.out.println();
    }
 
    public static void main( String args[] )
    {
        // 创建不同类型数组: Integer, Double 和 Character
        Integer[] intArray = { 1, 2, 3, 4, 5 };
        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
        Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
 
        System.out.println( "整型数组元素为:" );
        printArray( intArray  ); // 传递一个整型数组
 
        System.out.println( "\n双精度型数组元素为:" );
        printArray( doubleArray ); // 传递一个双精度型数组
 
        System.out.println( "\n字符型数组元素为:" );
        printArray( charArray ); // 传递一个字符型数组
    } 
}

编译以上代码,运行结果如下所示:

整型数组元素为:
1 2 3 4 5 

双精度型数组元素为:
1.1 2.2 3.3 4.4 

字符型数组元素为:
H E L L O 

在这段代码中,虽然我们使用了泛型方法printArray来输出不同类型的数组元素,但实际上并没有创建一个泛型数组。我们只是在使用已有的数组来进行迭代遍历并输出元素,而不是通过泛型参数创建新的数组。


在Java中,直接创建一个泛型数组是不允许的。例如,以下代码是不合法的:

// 这是不合法的,无法直接创建泛型数组
T[] array = new T[10];

​ 泛型数组的限制是为了确保类型安全性。虽然在代码中使用了泛型方法,但我们并没有在代码中直接创建泛型数组,而是使用了已有的数组。因此,虽然代码中有泛型方法,但没有涉及创建泛型数组的情况。


通配符和边界

泛型还支持通配符和边界的概念,让你能够更精细地控制泛型类型的范围。通配符?表示未知类型,而边界则限制了允许传递的类型范围。

import java.util.List;

public class GenericWildcard {

    /**
     * 打印通配符类型的列表元素
     *
     * @param list 要打印的列表
     */
    public static void printList(List<?> list) {
        for (Object element : list) {
            System.out.print(element + " ");
        }
        System.out.println();
    }

    /**
     * 计算数字类型列表的总和
     *
     * @param list 要计算总和的列表
     * @return 列表元素的总和
     */
    public static <T extends Number> double sumOfList(List<T> list) {
        double sum = 0;
        for (T element : list) {
            sum += element.doubleValue();
        }
        return sum;
    }//<T extends Number>: 这是泛型类型参数的声明。在这里,<T> 表示泛型类型参数的名称,你可以在方法体内使用它来代表实际的数据类型。extends Number 是一个泛型类型的限定符。它的意思是,类型参数 T 必须是 Number 类或其子类。这样做的目的是为了确保列表中的元素都是数字类型,以便在方法中进行数字运算。

    public static void main(String[] args) {
        // 创建一个包含不同类型元素的列表
        List<?> wildcardList = List.of("Hello", 42, 3.14, 'A');

        // 打印通配符类型的列表元素
        System.out.print("通配符列表元素:: ");
        printList(wildcardList);

        // 创建一个数字类型的列表
        List<Integer> integerList = List.of(1, 2, 3, 4, 5);

        // 计算数字类型列表的总和
        double sum = sumOfList(integerList);

        // 输出计算结果
        System.out.println("整数列表总和: " + sum);
    }
}

​ 输出结果如下:

通配符列表元素: Hello 42 3.14 A 
整数列表总和: 15.0

​ 上面的示例演示了一个使用通配符的方法printList和一个使用边界的方法sumOfList。通配符使方法能够接受不同类型的列表,而边界则限制了方法只能接受继承自Number的类型列表。

泛型与集合框架

Java的集合框架(如List、Set、Map等)广泛使用了泛型,使得集合中的元素类型可以更加明确和安全。使用泛型可以在编译时捕获类型错误,避免在运行时出现类型转换异常。


最后总结:

​ Java泛型是一项强大的功能,它既保持了类型安全,又提高了代码的灵活性和重用性。通过学习泛型的使用,能够编写更强大、更安全的代码,让我们的程序更加健壮和可维护。

总结一下泛型类的优缺点:

泛型类的优点:

  1. 类型安全性: 泛型类在编译时可以检查数据类型,从而提前发现类型错误,避免了在运行时出现类型转换异常。
  2. 代码重用性: 泛型类允许你创建通用的数据结构,适用于多种数据类型,减少了代码的冗余,提高了代码的重用性。
  3. 灵活性: 泛型类使得你可以创建适用于不同数据类型的通用类,从而在不同场景下实现灵活的数据存储和处理。
  4. 编译时类型检查: 泛型类使得编译器能够检查和强制确保类型的正确性,从而提高了代码的可靠性和稳定性。
  5. 更好的可读性: 使用泛型类可以提供更清晰的代码结构和命名,使得代码更易于理解和维护。

泛型类的缺点:

  1. 类型参数限制: 泛型类无法直接使用基本数据类型作为类型参数,需要使用对应的包装类。同时,无法创建泛型数组。
  2. 复杂性: 对于初学者来说,泛型的概念可能会有一定的复杂性,需要一些时间来理解和掌握。
  3. 编译时错误信息: 有时,编译器产生的泛型相关的错误信息可能不够直观,需要花时间来解读和修复。
  4. 擦除和类型擦除: 泛型类在编译时会进行类型擦除,这可能会导致在某些情况下失去一些类型信息。
  5. 部分场景不适用: 在某些特定的场景下,泛型类可能不适用,需要根据实际需求来选择是否使用泛型。

​ 尽管泛型类存在一些缺点,但它的优点远远超过了缺点,可以帮助我们写出更安全、更灵活、更高效的代码。

作者:Stevedash

发表于:2023年8月8日15点19分

来源:Java 泛型 | 菜鸟教程 (runoob.com)

注:本文内容基于个人学习理解,如有错误或疏漏,欢迎指正。感谢阅读!如果觉得有帮助,请点赞和分享。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/855324.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小白到运维工程师自学之路 第七十集 (Kubernetes集群部署)

一、概述 Kubernetes&#xff08;简称K8S&#xff09;是一个开源的容器编排和管理平台&#xff0c;是由Google发起并捐赠给Cloud Native Computing Foundation&#xff08;CNCF&#xff09;管理的项目。它的目标是简化容器化应用的部署、扩展、管理和自动化操作。 以下是Kube…

【D3S】集成smart-doc并同步配置到Torna

目录 一、引言二、maven插件三、smart-doc.json配置四、smart-doc-maven-plugin相关命令五、推送文档到Torna六、通过Maven Profile简化构建 一、引言 D3S&#xff08;DDD with SpringBoot&#xff09;为本作者使用DDD过程中开发的框架&#xff0c;目前已可公开查看源码&#…

leetcode26-删除有序数组中的重复项

双指针—快慢指针 慢指针 slow 走在后面&#xff0c;快指针 fast 走在前面探路&#xff0c;找到一个不重复的元素的时候就让slow前进一步并赋值给它。 流程&#xff1a; 代码 class Solution { public:int removeDuplicates(vector<int>& nums) {int slow 0, fas…

【2012年专利】基于中继节点的互联网通信系统和通信路径选择方法

基于中继节点的互联网通信系统和通信路径选择方法 CN102594703A地址基于中继节点的互联网通信系统和通信路径选择方法 ,解决:服务器间直接传输时丢包率高及延时长的缺点包括:服务器之间转发数据包的中继节点计算服务器间最优传输路径的选路决策节点, 本发明涉及一种晶于中继…

【Spring专题】Spring之底层架构核心概念解析

目录 前言前置知识课程内容一、BeanDefinition&#xff1a;图纸二、BeanDefinitionReader&#xff1a;图纸读取器——Spring工厂基础设施之一2.1 AnnotatedBeanDefinitionReader2.2 XmlBeanDefinitionReader2.3 ClassPathBeanDefinitionScanner 三、BeanFactory&#xff1a;生产…

银行电子密码器也远程管理吗?操作步骤如下

企业需要在不同办公地点管理多个资金账户&#xff0c;能不能远程点按读取银行电子密码器呢&#xff1f; 三个字&#xff0c;很简单&#xff01; 可以用密码点按器&#xff01; 有了它就可以自动输入密码和读取验证码&#xff01; 第一步 用双面胶把密码器固定在点按器上&…

【UE4 RTS】04-Camera Pan

前言 本篇实现了CameraPawn的旋转功能。 效果 步骤 1. 打开项目设置&#xff0c;添加两个操作映射 2. 打开玩家控制器“RTS_PlayerController_BP”&#xff0c;新建一个浮点型变量&#xff0c;命名为“PanSpeed” 在事件图表中添加如下节点 此时运行游戏可以发现当鼠标移动…

【学习日记】【FreeRTOS】调度器函数实现详解

写在前面 本文主要是对于 FreeRTOS 中调度器函数实现的详细解释&#xff0c;代码大部分参考了野火 FreeRTOS 教程配套源码&#xff0c;作了一小部分修改。 一、MSP 和 PSP Cortex-M有两种栈空间&#xff0c;主堆栈和进程堆栈。 MSP 用于系统级别和中断处理的堆栈 MSP 用于保…

一文看懂Apipost接口自动化使用方法

随着项目研发进程的不断推进&#xff0c;软件功能不断增多&#xff0c;对于软件测试的要求也越来越高。为了提高测试效率和减少测试成本&#xff0c;许多软件测试团队借助于自动化测试工具来优化测试流程。Apipost也提供了自动化测试工具&#xff0c;在本文中&#xff0c;我们将…

Android Studio System.out.println()中文乱码

第一步&#xff1a; 打开studio64.exe.vmoptions加入-Dfile.encodingUTF-8 第二步&#xff1a; File-Settings-Editor-File Encodings 把所有的编码格式改为UTF-8 尝试跑一下代码&#xff0c;如果还不行&#xff0c;重启IDE 再试试。

利用DNS隧道构建隐蔽CC信道

背景介绍 无论是高级持续性威胁(APT&#xff09;、僵尸网络(Botnet)&#xff0c;还是勒索软件、后门等&#xff0c;命令与控制信道(C &C)都是其重要组成部分&#xff0c;尤其是APT和僵尸网络中的C&C信道决定了其威胁程度。学术界和工业界就C&C方面的研究已逐渐深入…

vue3:新特性

一、react和vue的主要区别 &#xff08;1&#xff09;数据更新上&#xff1a; 1、 react 采用 fiber架构 &#xff0c;使用 链表 表示 DOM 结构可以在 diff 时随时中断和继续&#xff0c;利用requestIdleCallback 在空闲时 diff &#xff0c;防止数据量大 diff 时间长导致卡顿…

Python 之禅

Python 社区的理念都包含在 Tim Peters 撰写的 “Python 之禅” 中 在 Windows 平台的 cmd 命令中打开 python&#xff0c;输入 import this&#xff0c;就能看到 Python 之禅: 翻译&#xff1a; Tim Peters 的 python 之禅Beautiful is better than ugly. # 优美胜于丑陋&am…

生成树协议

文章目录 STP冗余交换网络为什么存在广播风暴&#xff1f;广播的危害&#xff1f;交换环路的危害&#xff1f; 工作机制BPDU什么是最好的bpduBPDU触发机制 STP选举步骤配置协议分析字段分析开销模式端口状态 故障类型根桥故障直连故障间接故障 &#xff08;链路中间可能有HUB&a…

vuejs 设计与实现 - 渲染器的设计

渲染器与响应式系统的结合 本节&#xff0c;我们暂时将渲染器限定在 DOM 平台。既然渲染器用来渲染真实 DOM 元素&#xff0c;那么严格来说&#xff0c;下面的函数就是一个合格的渲染器: // 渲染器&#xff1a; function renderer(domString, container) {container.innerHTM…

中级课程-SSRF(CSRF进阶)

文章目录 成因危害挖掘 成因 危害 挖掘

JUC并发编程之线程锁(一)

目录 1.ReentrantLock(互斥锁) 2.ReentRantReaderWriterLock&#xff08;互斥读写锁&#xff09; 3.StampedLock&#xff08;无障碍锁&#xff09; 4.Condition&#xff08;自定义锁&#xff09; 5.LockSupport 问题引出&#xff1a; 由于传统的线程控制需要用到同步机制Sy…

LeetCode--HOT100题(23)

目录 题目描述&#xff1a;206. 反转链表&#xff08;简单&#xff09;题目接口解题思路代码 PS: 题目描述&#xff1a;206. 反转链表&#xff08;简单&#xff09; 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 LeetCode做题链接&…

剑指offer56-II.数组中数字出现的次数II

第一种方法非常简单&#xff0c;就是用一个HashMap&#xff0c;key是数组中元素的值&#xff0c;value是出现的次数&#xff0c;所以遍历一遍数组&#xff0c;如果map中没有这个元素就把它put进去value设为1&#xff0c;否则value加1&#xff0c;然后遍历一遍map&#xff0c;如…

tensorflow 1.14 的 demo 02 —— tensorboard 远程访问

tensorflow 1.14.0&#xff0c; 提供远程访问 tensorboard 服务的方法 第一步生成 events 文件&#xff1a; 在上一篇demo的基础上加了一句&#xff0c;如下&#xff0c; tf.summary.FileWriter("./tmp/summary", graphsess1.graph) hello_tensorboard_remote.py …