什么是 Java 泛型?

news2024/11/23 7:09:01

本文首发自「慕课网」,想了解更多IT干货内容,程序员圈内热闻,欢迎关注!

作者| 慕课网精英讲师 ColorfulC

通过本篇文章你将了解到什么是泛型为什么需要泛型,如何使用泛型,如何自定义泛型,类型通配符等知识。

1. 什么是泛型

泛型不只是 Java 语言所特有的特性,泛型是程序设计语言的一种特性。允许程序员在强类型的程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须做出声明。

Java 中的集合类是支持泛型的,它在代码中是这个样子的:

代码中的<Integer>就是泛型,我们把类型像参数一样传递,尖括号中间就是数据类型,我们可以称之为实际类型参数,这里实际类型参数的数据类型只能为引用数据类型。

那么为什么需要泛型呢?我们马上就见分晓。

2. 为什么需要泛型

我们在使用ArrayList实现类的时候,如果没有指定泛型,IDEA会给出警告,代码似乎也是可以顺利运行的。请看如下实例:

import java.util.ArrayList;

public class GenericsDemo1 {

    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        arrayList.add("Hello");
        String str = (String) arrayList.get(0);
        System.out.println("str=" + str);
    }

}
代码块123456789101112

运行结果:

str=Hello
代码块1

虽然运行时没有发生任何异常,但这样做有两个缺点:

  1. 需要强制类型转换: 由于ArrayList内部就是一个Object[]数组,在get()元素的时候,返回的是Object类型,所以在ArrayList外获取该对象,需要强制类型转换。其它的Collection、Map如果不使用泛型,也存在这个问题;
  2. 可向集合中添加任意类型的对象,存在类型不安全风险。例如如下代码中,我们向列表中既添加了Integer类型,又添加了String类型:
import java.util.ArrayList;

public class GenericsDemo2 {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        arrayList.add(123);
        arrayList.add("Hello");
        String str = (String) arrayList.get(0);
        System.out.println("element=" + str);
    }
}
代码块1234567891011

运行结果:

Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
	at GenericsDemo2.main(GenericsDemo2.java:8)
代码块12

由于我们的“疏忽”,列表第 1 个元素实际上是整型,但被我们强制转换为字符串类型,这是行不通的,因此会抛出ClassCastException异常。

使用泛型可以解决这些问题。泛型有如下优点:

  1. 可以减少类型转换的次数,代码更加简洁;
  2. 程序更加健壮:只要编译期没有警告,运行期就不会抛出ClassCastException异常;
  3. 提高了代码的可读性:编写集合的时候,就限定了集合中能存放的类型。

3. 如何使用泛型

3.1 泛型使用

在代码中,这样使用泛型:

List<String> list = new ArrayList<String>();
// Java 7 及以后的版本中,构造方法中可以省略泛型类型:
List<String> list = new ArrayList<>();
代码块123

要注意的是,变量声明的类型必须与传递给实际对象的类型保持一致,下面是错误的例子:

List<Object> list = new ArrayList<String>();
List<Number> numbers = new ArrayList(Integer);
代码块12

3.2 自定义泛型类

3.2.1 Java 源码中泛型的定义

在自定义泛型类之前,我们来看下java.util.ArrayList是如何定义的:

类名后面的<E>就是泛型的定义,E不是 Java 中的一个具体的类型,它是 Java 泛型的通配符(注意是大写的,实际上就是Element的含义),可将其理解为一个占位符,将其定义在类上,使用时才确定类型。此处的命名不受限制,但最好有一定含义,例如java.lang.HashMap的泛型定义为HashMap<K,V>,K表示Key,V表示Value。

3.2.2 自定义泛型类实例1

下面我们来自定义一个泛型类,自定义泛型按照约定俗成可以叫<T>,具有Type的含义,实例如下:

实例演示

public class NumberGeneric<T> { // 把泛型定义在类上

    private T number; // 定义在类上的泛型,在类内部可以使用

    public T getNumber() {
        return number;
    }

    public void setNumber(T number) {
        this.number = number;
    }

    public static void main(String[] args) {
        // 实例化对象,指定元素类型为整型
        NumberGeneric<Integer> integerNumberGeneric = new NumberGeneric<>();
        // 分别调用set、get方法
        integerNumberGeneric.setNumber(123);
        System.out.println("integerNumber=" + integerNumberGeneric.getNumber());

        // 实例化对象,指定元素类型为长整型
        NumberGeneric<Long> longNumberGeneric = new NumberGeneric<>();
        // 分别调用set、get方法
        longNumberGeneric.setNumber(20L);
        System.out.println("longNumber=" + longNumberGeneric.getNumber());

        // 实例化对象,指定元素类型为双精度浮点型
        NumberGeneric<Double> doubleNumberGeneric = new NumberGeneric<>();
        // 分别调用set、get方法
        doubleNumberGeneric.setNumber(4000.0);
        System.out.println("doubleNumber=" + doubleNumberGeneric.getNumber());
    }

}
123456789101112131415161718192021222324252627282930313233

运行结果:

integerNumber=123
longNumber=20
doubleNumber=4000.0
代码块123

我们在类的定义处也定义了泛型:NumberGeneric<T>;在类内部定义了一个T类型的number变量,并且为其添加了setter和getter方法。

对于泛型类的使用也很简单,在主方法中,创建对象的时候指定T的类型分别为Integer、Long、Double,类就可以自动转换成对应的类型了。

3.2.3 自定义泛型类实例2

上面我们知道了如何定义含有单个泛型的类,那么对于含有多个泛型的类,如何定义呢?

我们可以看一下HashMap类是如何定义的。如下是 Java 源码的截图:

 参照HashMap<K,V>类的定义,下面我们来看看如何定义含有两个泛型的类,实例如下:

实例演示

public class KeyValueGeneric<K,V> { // 把两个泛型K、V定义在类上

    /**
     * 类型为K的key属性
     */
    private K key;

    /**
     * 类型为V的value属性
     */
    private V value;

    public K getKey() {
        return key;
    }

    public void setKey(K key) {
        this.key = key;
    }

    public V getValue() {
        return value;
    }

    public void setValue(V value) {
        this.value = value;
    }

    public static void main(String[] args) {
        // 实例化对象,分别指定元素类型为整型、长整型
        KeyValueGeneric<Integer, Long> integerLongKeyValueGeneric = new KeyValueGeneric<>();
        // 调用setter、getter方法
        integerLongKeyValueGeneric.setKey(200);
        integerLongKeyValueGeneric.setValue(300L);
        System.out.println("key=" + integerLongKeyValueGeneric.getKey());
        System.out.println("value=" + integerLongKeyValueGeneric.getValue());

        // 实例化对象,分别指定元素类型为浮点型、字符串类型
        KeyValueGeneric<Float, String> floatStringKeyValueGeneric = new KeyValueGeneric<>();
        // 调用setter、getter方法
        floatStringKeyValueGeneric.setKey(0.5f);
        floatStringKeyValueGeneric.setValue("零点五");
        System.out.println("key=" + floatStringKeyValueGeneric.getKey());
        System.out.println("value=" + floatStringKeyValueGeneric.getValue());
    }
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546

运行结果:

key=200
value=300
key=0.5
value=零点五
代码块1234

3.3 自定义泛型方法

前面我们知道了如何定义泛型类,在类上定义的泛型,在方法中也可以使用。下面我们来看一下如何自定义泛型方法。

泛型方法不一定写在泛型类当中。当类的调用者总是关心类中的某个泛型方法,不关心其他属性,这个时候就没必要再整个类上定义泛型了。

请查看如下实例:

实例演示

public class GenericMethod {

    /**
     * 泛型方法show
     * @param t 要打印的参数
     * @param <T> T
     */
    public <T> void show(T t) {
        System.out.println(t);
    }

    public static void main(String[] args) {
        // 实例化对象
        GenericMethod genericMethod = new GenericMethod();
        // 调用泛型方法show,传入不同类型的参数
        genericMethod.show("Java");
        genericMethod.show(222);
        genericMethod.show(222.0);
        genericMethod.show(222L);
    }
}
123456789101112131415161718192021

运行结果:

Java
222
222.0
222
代码块1234

实例中,使用<T>来定义show方法的泛型,它接收一个泛型的参数变量并在方法体打印;调用泛型方法也很简单,在主方法中实例化对象,调用对象下的泛型方法,可传入不同类型的参数。

4. 泛型类的子类

泛型类也是一个 Java 类,它也具有继承的特性。

泛型类的继承可分为两种情况:

  1. 子类明确泛型类的类型参数变量;
  2. 子类不明确泛型类的类型参数变量。

下面我们来分别看一下这两种情况。

4.1 明确类型参数变量

例如,有一个泛型接口:

public interface GenericInterface<T> { // 在接口上定义泛型
    void show(T t);
}
代码块123

泛型接口的实现类如下:

public class GenericInterfaceImpl implements GenericInterface<String> { // 明确泛型类型为String类型
    @Override
    public void show(String s) {
        System.out.println(s);
    }
}
代码块123456

子类实现明确了泛型的参数变量为String类型。因此方法show()的重写也将T替换为了String类型。

4.2 不明确类型参数变量

当实现类不确定泛型类的参数变量时,实现类需要定义类型参数变量,调用者使用子类时,也需要传递类型参数变量。

如下是GenericInterface接口的另一个实现类:

public class GenericInterfaceImpl1<T> implements GenericInterface<T> { // 实现类也需要定义泛型参数变量
    @Override
    public void show(T t) {
        System.out.println(t);
    }
}
代码块123456

在主方法中调用实现类的show()方法:

    public static void main(String[] args) {
        GenericInterfaceImpl1<Float> floatGenericInterfaceImpl1 = new GenericInterfaceImpl1<>();
        floatGenericInterfaceImpl1.show(100.1f);
    }
代码块1234

5. 类型通配符

我们先来看一个泛型作为方法参数的实例:

import java.util.ArrayList;
import java.util.List;

public class GenericDemo3 {
    /**
     * 遍历并打印集合中的每一个元素
     * @param list 要接收的集合
     */
    public void printListElement(List<Object> list) {
        for (Object o : list) {
            System.out.println(o);
        }
    }
}
代码块1234567891011121314

观察上面的代码,参数list的限定的泛型类型为Object, 也就是说,这个方法只能接收元素为Object类型的集合,如果我们想传递其他元素类型的集合,是行不通的。例如,如果传递装载Integer元素的集合,程序在编译阶段就会报错:

Tips: 泛型中的List<Object>并不是List<Integer>的父类,它们不满足继承关系。

5.1 无限定通配符

想要解决这个问题,使用类型通配符即可,修改方法参数处的代码,将<>中间的Object改为?即可:

public void printListElement(List<?> list) {
代码块1

此处的?就是类型通配符,表示可以匹配任意类型,因此调用方可以传递任意泛型类型的列表。

完整实例如下:

实例演示

import java.util.ArrayList;
import java.util.List;

public class GenericDemo3 {
    /**
     * 遍历并打印集合中的每一个元素
     * @param list 要接收的集合
     */
    public void printListElement(List<?> list) {
        for (Object o : list) {
            System.out.println(o);
        }
    }

    public static void main(String[] args) {
        // 实例化一个整型的列表
        List<Integer> integers = new ArrayList<>();
        // 添加元素
        integers.add(1);
        integers.add(2);
        integers.add(3);
        GenericDemo3 genericDemo3 = new GenericDemo3();
        // 调用printListElement()方法
        genericDemo3.printListElement(integers);

        // 实例化一个字符串类型的列表
        List<String> strings = new ArrayList<>();
        // 添加元素
        strings.add("Hello");
        strings.add("慕课网");
        // 调用printListElement()方法
        genericDemo3.printListElement(strings);
    }
}
12345678910111213141516171819202122232425262728293031323334

运行结果:

1
2
3
Hello
慕课网
代码块12345

5.2 extends 通配符

extends通配符用来限定泛型的上限。什么意思呢?依旧以上面的实例为例,我们来看一个新的需求,我们希望方法接收的List 集合限定在数值类型内(float、integer、double、byte 等),不希望其他类型可以传入(比如字符串)。此时,可以改写上面的方法定义,设定上界通配符:

public void printListElement(List<? extends Number> list) {
代码块1

这样的写法的含义为:List集合装载的元素只能是Number自身或其子类(Number类型是所有数值类型的父类),完整实例如下:

实例演示

import java.util.ArrayList;
import java.util.List;

public class GenericDemo4 {
    /**
     * 遍历并打印集合中的每一个元素
     * @param list 要接收的集合
     */
    public void printListElement(List<? extends Number> list) {
        for (Object o : list) {
            System.out.println(o);
        }
    }

    public static void main(String[] args) {
        // 实例化一个整型的列表
        List<Integer> integers = new ArrayList<>();
        // 添加元素
        integers.add(1);
        integers.add(2);
        integers.add(3);
        GenericDemo4 genericDemo3 = new GenericDemo4();
        // 调用printListElement()方法
        genericDemo3.printListElement(integers);

    }
}
123456789101112131415161718192021222324252627

运行结果:

1
2
3
代码块123

5.3 super 通配符

既然已经了解了如何设定通配符上界,也就不难理解通配符的下界了,可以限定传递的参数只能是某个类型的父类。

语法如下:

<? super Type>
代码块1

6. 小结

在本篇文章中,我们知道了使用泛型可以避免强制类型转换,也可以避免运行期就抛出的ClassCastException异常。在使用泛型时,要注意变量声明的泛型类型要匹配传递给实际对象的类型, Java 7 及以后的版本中,构造方法中可以省略泛型类型,推荐直接省略。
我们也学习了如何自定义泛型类和泛型方法,在实际的开发中,我们想要编写比较通用的代码就避免不了使用泛型,大家可以在以后的开发中慢慢体悟。
另外,泛型也是可以继承的。
最后,我们还讲解了类型通配符的概念和使用场景。

欢迎关注「慕课网」,发现更多IT圈优质内容,分享干货知识,帮助你成为更好的程序员!

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

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

相关文章

记一次以小勃大,紧张刺激的渗透测试(2017年老文)

零、前言 有技术交流或渗透测试培训需求的朋友欢迎联系QQ/VX-547006660&#xff0c;需要代码审计、渗透测试、红蓝对抗网络安全相关业务可以看置顶博文 2000人网络安全交流群&#xff0c;欢迎大佬们来玩 群号820783253 一、起因 emmm&#xff0c;炎炎夏日到来&#xff0c;这…

libxlsxwriter数据验证

今天要分享的这个问题其实也属于excel的高级用法。我们想通过libxlsxwriter来实现一个能对用户的输入进行数据验证的表格功能。 内心os&#xff1a;小白最近是不是有点走火入魔了……我们真的会遇上这样的场景吗&#xff1a;需要通过代码生成一个表格&#xff0c;这个表格中还有…

智慧机场,或将成为航空领域数字孪生技术得完美应用

在《智慧民航建设路线图》文件中&#xff0c;民航局明确指出&#xff0c;智慧机场是实现智慧民航的四个核心抓手之一。这一战略性举措旨在推进数字化技术与航空产业的深度融合&#xff0c;为旅客提供更加智能化、便捷化、安全化的出行服务&#xff0c;进一步提升我国民航发展的…

IO流进阶

&#x1f3e1;个人主页 &#xff1a; 守夜人st &#x1f680;系列专栏&#xff1a;Java …持续更新中敬请关注… &#x1f649;博主简介&#xff1a;软件工程专业&#xff0c;在校学生&#xff0c;写博客是为了总结回顾一些所学知识点 目录IO流进阶缓冲流缓冲流概述字节缓冲流(…

【LeetCode】剑指 Offer(1)

目录 写在前面&#xff1a; 题目1&#xff1a;剑指 Offer 03. 数组中重复的数字 - 力扣&#xff08;Leetcode&#xff09; 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 题目2&#xff1a;剑指 Offer 06. 从…

JavaEE初阶---TCP/IP协议

一:概述 本文是网络编程的理论基础,也是网络部分的重点和难点,在笔试,面试中,这部分内容也多有考察.二:详解 TCP/IP五层协议 应用层传输层网络层数据链路层物理层 2.1应用层 我们自己编写的代码,就是在应用层,这也是在实际开发中接触最多的层.应用层里有许多现成的协议 , 而在…

springboot(5)静态资源访问前缀+欢迎页+REST风格的传递参数

Springboot的访问规则是 先访问controller的请求&#xff0c;如果没有的话&#xff0c;我们在访问静态资源。 但是静态资源过多的情况下这样不好处理&#xff0c;所以我们可以加一个前缀&#xff0c;这样可以拦截掉非静态资源的请求&#xff0c;每次都可以准确快速无风险的访…

镀镍废水处理,企业污水处理,离子交换树脂在镀镍废水中的应用

传统沉淀法不能满足日益提的环保要求(如电镀表三镍含量要求0.1mg/l以下)。针对特定重金属离子的特点&#xff0c;利用螯合树脂的特种功能基团与重金属离子形成络合物的特性&#xff0c;实现重金属离子的回收利用及深度去除。 CH-90Na对除铜镍铅锌钴锰等具有特定的选择性&#x…

API的应用

API&#xff08;Application Programming Interface,应用程序编程接口&#xff09;是一些预先定义的函数&#xff0c;目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力&#xff0c;而又无需访问源码&#xff0c;或理解内部工作机制的细节 应用程序接口 想…

安卓|aar和jar打包引入流程演示

安卓|aar和jar打包引入流程演示aar和jar存在相关意义打包环境工具aar和jar|Module项目目录aar打包|引入总览步骤|aar配置打包创建java or kotlin类文件及资源|截图展示步骤|aar包引入jar打包|引入总览步骤|jar配置打包任务创建java类文件|截图展示步骤|jar包引入aar和jar运行在…

OpenTracing协议规范链接

一、官网链接 OpenTracing specificationhttps://opentracing.io/specification/不过目前OpenTracing项目已归档&#xff0c;不再维护。需要参考OpenTelemetry官网链接 Migrating from OpenTracing | OpenTelemetryBackward compatibility with OpenTracing has been a prior…

Promise 的理解

promise 是什么&#xff1f;promise 是JS 中进行异步编程的最新的解决方案从语法上来说&#xff1a;Promise 是一个构造函数从功能上来说&#xff1a;promise 对象时用来封装一个异步操作并可以获取其结果其三种状态&#xff0c;reasolve 、pendding、 rejectedPromise 的基本运…

计算机网络-ip数据报

在图中&#xff0c;网络层包含了四种协议&#xff1a;ARP、IP、ICMP、IGMP&#xff0c;由上下关系表明&#xff0c;ARP为IP协议服务&#xff0c;IP为ICMP和IGMP服务。 IP数据报格式 此处不区分数据报和分组的概念&#xff1a;当数据部分过长时&#xff0c;将数据部分拆分&…

2022年全国职业院校技能大赛A(1)

目录 模块A 基础设施设置与安全加固 一、项目和任务描述&#xff1a; 二、服务器环境说明 三、具体任务&#xff08;每个任务得分以电子答题卡为准&#xff09; A-1任务一 登录安全加固 1.密码策略&#xff08;Windows&#xff0c;Linux&#xff09; a.设置最短密码长度为…

Python 之 NumPy 随机函数和常用函数

文章目录一、随机函数1. numpy.random.rand(d0,d1,…,dn)2. numpy.random.randn(d0,d1,…,dn)3. numpy.random.normal()4. numpy.random.randint()5. numpy.random.sample6. 随机种子np.random.seed()7. 正态分布 numpy.random.normal二、数组的其他函数1. numpy.resize()2. nu…

芯片是怎样“炼”成的?

在芯片设计完成&#xff0c;交由芯片制造厂&#xff08;Fab&#xff09;进行试产&#xff08;Tape out&#xff09;之后&#xff0c;就可以进行量产了。但Tape out是个漫长的过程&#xff0c;在此过程中&#xff0c;Fab里的工艺及设备工程师们需要不断调试&#xff0c;使得芯片…

Python优化算法—遗传算法

Python优化算法—遗传算法一、前言二、安装三、遗传算法3.1 自定义函数3.2 遗传算法进行整数规划3.3 遗传算法用于旅行商问题3.4 使用遗传算法进行曲线拟合一、前言 优化算法&#xff0c;尤其是启发式的仿生智能算法在最近很火&#xff0c;它适用于解决管理学&#xff0c;运筹…

全球认可的PMI认证体系

全球认可的PMI认证体系我们生活在一个“项目化”的世界里&#xff0c;而且这一趋势在不断加强。PMI的专业认证将使您做好准备&#xff0c;满足全球范围内各种项目、不同企业的需求。PMI的认证源于项目管理从业者&#xff0c;并服务于项目管理从业者。所有认证基于严格的标准和持…

HTML5之CSS基础学习笔记

基础认知 CSS初始 CSS的介绍 CSS&#xff1a;层叠样式表&#xff08;Cascading style sheets&#xff09; CSS作用是什么&#xff1f;&#xff1f; 给页面中的HTML标签设置样式 CSS就是用来做美化的&#xff0c;我之前写的网页就是黑白&#xff0c;要不带点彩色的图片&#x…

MYSQL-主从复制

文章目录1 概述2 原理3 搭建3.1 主库配置3.2 从库配置1 概述 主从复制是指将主数据库的DDL和 DML操作通过二进制日志传到从库服务器中&#xff0c;然后在从库上对这些日志重新执行(也叫重做)&#xff0c;从而使得从库和主库的数据保持同步。 MySQL支持一台主库同时向多台从库进…