Java 入门指南:Java 泛型(generics)

news2024/9/23 23:30:17

Java 泛型概要

Java 泛型(generics) 是 JDK 5 中引入的一个新特性。泛型的本质是参数化类型,也就是所操作的数据类型被指定为一个参数(可以称之为类型形参,然后在使用/调用时传入具体的类型。)

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

Java中的泛型,只在编译阶段有效

在编译之后程序会采取去泛型化的措施。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段

因此,泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型

泛型有三种使用方式:泛型类泛型接口泛型方法

泛型的优势

  1. 类型安全:泛型可以在编译时检查类型错误,减少运行时异常。

  2. 消除强制类型转换:使用泛型后,可以自动获得正确的类型,无需进行显式的类型转换,提高了代码的可读性和安全性。

  3. 提高代码重用性:泛型使得可以编写更加通用的代码,如泛型集合可以存储任何类型的对象,而无需为每种类型编写特定的集合类。

  4. 性能优化:通过消除运行时的类型检查和转换,泛型可以提高程序的性能。

泛型类

泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:ListSetMap

泛型类的声明

class ClassName<T>{
	// 类成员的和方法定义
	private T item;
	
	public void setItem(T item){
		this.item = item;
	}
	public T getItem(){
		return item;
	}

	// 此方法有错误,因为类的声明中未声明泛型E,在使用E做形参和返回值类型时,编译器会无法识别。
	public E setKey(E key){
             this.key = key;
    }
}
  • T 是类型参数,它代表着一个占位符,表示在实例化泛型类时将传入的具体类型

  • 在泛型类的声明中,T可以被替换为任何合法的Java标识符,通常使用如下常见的命名约定:

    • T - 代表任意类型
    • E - 代表元素(通常在集合类中使用)
    • K - 代表键(通常在关联数据结构中使用)
    • V - 代表值(通常在关联数据结构中使用)
  • 类型参数声明部分可以包含一个或多个类型参数,参数间用逗号隔开。

  • 泛型的类型参数只能是类类型StringDoubleInteger),不能是简单类型(intfloatchardouble

  • 不能对确切的泛型类型使用 instanceof 操作。如下面的操作是非法的,编译时会出错:

if(ex_num instanceof Generic<Number>){...}

实例化是否需要传入实参

在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。

如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型

泛型接口

泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,它允许在接口的方法、成员变量和常量等位置使用一种或多种类型参数来增加灵活性和重用性。

public interface InterFaceName<T>{
	public T next();
}

未传入泛型实参

未传入泛型实参时,与泛型类的定义相同。在声明类的时候,需将接口泛型的声明也一起加到类中

如果不声明泛型,如:

class FruitGenerator implements Generator<T>

编译器会报错:“Unknown class

public interface Pair<K,V>{
	K getKey();
	V getValue();
}

public class OrderPair <K,V> implements Pair <K,V>{
	private K key;
	private V value;

	public OrderedPair(K key, V value) {
	        this.key = key;
	        this.value = value;
	    }
	@Override
	public K getKey() {
		return key;
	}
	@Override
	public V getValue() {
		return value;
	}
}

传入泛型实参时

定义一个类(生产器)实现这个接口,虽然我们只创建了一个泛型接口
但是我们可以为 T 传入无数个实参,形成无数种类型的 Implement 接口。

在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型,所有的 T 都将换成传入的实参

public class OrdersPair implements Pair <Integer,String>{
	private Integer key;
	private String value;

	public OrderedPair(Integer key, String value) {
	        this.key = key;
	        this.value = value;
	    }
	@Override
	public Integer getKey() {
		return key;
	}
	@Override
	public String getValue() {
		return value;
	}
}

类型擦除

虚拟机是没有泛型的,把泛型类的字节码进行反编译,用反编译工具(如 jad)将 class 文件反编译后,类型变量 <E> 消失了,取而代之的是 Object。

如果泛型类使用了限定符 extends,例如 <E extends TestClass>
类型变量 <E extends TestClass> 不见了,E 被替换成了 TestClass

Java 虚拟机会将泛型的类型变量擦除,并替换为限定类型(没有限定的话,为 Object

类型擦除会遇到的问题

![[Pasted image 20231103230114.png]]

在浅层的意识上,我们会想当然地认为 Arraylist<String> listArraylist<Date> list 是两种不同的类型,因为 StringDate 是不同的类。

但由于类型擦除的原因,以上代码是不会通过编译的——编译器会提示一个错误(这正是类型擦除引发的那些“问题”)
![[Pasted image 20231103230142.png]]

这两个方法的参数类型在擦除后是相同的。也就是说,method(Arraylist<String> list)method(Arraylist<Date> list) 是同一种参数类型的方法,不能同时存在。类型变量 StringDate 在擦除后会自动消失,method 方法的实际参数是 Arraylist list

泛型通配符

若传入的实参类型具有父子类关系,如 NumberInteger 类,能否在泛型中视为具有父子关系的泛型关系?

即在 class<Number>"作为形参的方法中,能否传入 class<Interger>的实例? 由于类型擦除的原因,编译器会报错

同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的

需要一个在逻辑上可以表示同时class<Integer>class<Number>父类的引用类型。由此类型通配符应运而生

在Java中,泛型通配符(Wildcard) 是一种特殊的类型参数,用于表示不确定的类型。通配符可以被用作泛型类、泛型接口和泛型方法中的类型参数,以增加代码的灵活性和重用性。

例如 List<?> 表示一个可以存储任何类型对象的 List,但是不能对其中的元素进行添加操作。通配符可以用来解决类型不确定的情况,例如在方法参数或返回值中使用。

泛型通配符有三种形式

通配符

问号 ? :表示未知类型,可以匹配任何类型。此处的“ ? ”是泛型实参,而不是泛型形参!!!即 NumberIntegerString… 都是同一种实际的类型

上限通配符

上限通配符 ? extends T表示类型参数是 T 或 T 的子类型。类型实参只准传入某种类型及其子类的对象。

public void processList(List<? extends Number> list) {
    for (Number element : list) {
        // 处理元素
    }
}

List<? extends Number> 表示接受的元素类型是 Number 或 Number 的子类型的List

下限通配符

下限通配符(Lower Bounded Wildcards) 用 super 关键字来声明,其语法形式为 <? super T> ,其中 T 表示类型参数。它表示的是该类型参数必须是某个指定类的超类(包括该类本身)。

当我们需要往一个泛型集合中添加元素时,如果使用的是上限通配符,集合中的元素类型可能会被限制,从而无法添加某些类型的元素。但是,如果我们使用下限通配符,可以将指定类型的子类型添加到集合中,保证了元素的完整性。

假设有一个类 Animal,以及两个子类 Dog 和 Cat。现在我们有一个 List<? super Dog> 集合,它的类型参数必须是 Dog 或其父类类型。可以向该集合中添加 Dog 类型的元素,也可以添加它的子类。但是,不能向其中添加 Cat 类型的元素,因为 Cat 不是 Dog 的子类。

虽然使用下限通配符可以添加某些子类型元素,但是在读取元素时,我们只能确保其是 Object 类型的,无法确保其是指定类型或其父类型。因此,在读取元素时需要进行类型转换

泛型方法

泛型方法是一种具有泛型类型参数的方法。通过在方法的声明中使用类型参数,可以在方法内部使用不特定的类型

![[Pasted image 20231103224842.png]]

方法返回类型和方法参数类型至少需要一个

public <T> returnType methodName(T parameter){
	// method code block
	for(T element : parameter){
	}
}
  • public 与 返回值中间 <T> 非常重要,表示类型参数的声明,可以是一个或多个类型参数。用于声明此方法为泛型方法。

  • 只有声明了 <T> 的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。

  • <T> 表明该方法将使用泛型类型 T,此时才可以在方法内部使用泛型类型T

  • 与泛型类的定义一样,此处 T 可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。

    • T - 代表任意类型
    • E - 代表元素(通常在集合类中使用)
    • K - 代表键(通常在关联数据结构中使用)
    • V - 代表值(通常在关联数据结构中使用)

泛型类,是在实例化类的时候指明泛型的具体类型;而泛型方法,是在调用方法的时候指明泛型的具体类型

如果使用泛型方法将整个类泛型化,那么就应该使用泛型方法。

泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的<T>上添加上下边界:

public <T extends Number> T showKeyName (Generic<T> container)

类中的泛型方法

public class ClassName<T>{
	public void test1(T t){
		System.out.println(t.toString);
	}
	
	public <E> void test2(E e){
	}
	
	public <T> void test3(T t){
	}
}
  • test2 中的 泛型 E 可以为任意类型。可以类型与 T 相同,也可以不同。由于泛型方法在声明的时候会声明泛型 <E>,即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。

  • test3 中的 泛型 T 是一个全新的类型,可以与类中使用的泛型 T 不同

泛型方法与可变参数

可变参数是一种特殊的参数形式,它允许方法接受可变数量的参数

使用可变参数和泛型方法的组合,可以更方便地处理具有不确定数量和类型的参数列表,并在方法内部使用泛型类型参数,从而实现更灵活和通用的代码。

class ClassName{
	public <T> void test(T...args){
		for(T arg : args){
		System.out.println(arg);
		}
	}
}

ClassName cn = new ClassName();
cn.test("111",222,"aaaa","2323.4",55.55)
cn.test(true,false);

静态方法与泛型

在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型

如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。即如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法

泛型数组

java中是 不能创建一个确切的泛型类型的数组 的。因为Java中的数组是具体类型的集合,而泛型是在编译时进行类型擦除的。由于类型擦除的存在,无法在运行时创建具体类型的泛型数组。

可以创建一个泛型类型的数组引用,然后将其转换为指定类型的数组。这个转换被称为类型强制转换或类型安全的转换。

// 创建泛型类型的数组引用
Object[] genericArray = new Object[5];

// 转换为指定类型的数组
String[] stringArray = (String[]) genericArray;

也可以使用通配符创建泛型数组:

List<?>[] lists = new ArrayList<?>[10];
// 转换为指定类型的数组
List<String>[] stringArray = (List<String>[]) genericArray;

最后取出数据是要做显式的类型转换的

或者直接指定类型:

List<String> lists = new ArrayList<?>[10];

在类型强制转换时,应确保转换是安全的,即转换后的类型与实际存储在数组中的对象类型相符。如果转换不是安全的,会在运行时抛出ClassCastException 异常。

尽管可以使用通配符创建泛型数组引用并进行类型转换,但在实际编程中,最好使用集合类型(如ArrayList)来代替泛型数组,以获得更好的类型安全和灵活性。

总结

泛型是Java中一种强大的特性,它允许我们编写类型安全且可重用的代码。通过使用泛型,我们可以创建灵活的组件,这些组件可以处理不同的数据类型,同时保持代码的简洁性和可读性。了解泛型的基本概念和高级用法对于编写高质量的Java程序至关重要。

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

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

相关文章

【机器学习】特征工程的基本概念以及LASSO回归和主成分分析优化方法

引言 特征工程是机器学习中的一个关键步骤&#xff0c;它涉及到从原始数据中提取和构造新的特征&#xff0c;以提高模型的性能和预测能力LASSO&#xff08;Least Absolute Shrinkage and Selection Operator&#xff09;回归是一种用于回归分析的线性模型&#xff0c;它通过引入…

字节跳动-生活服务-java后端-一面

基础题 计算机网络 1.tcp三次握手和四次挥手&#xff1f;tcp的第三次握手可以传输应用层数据嘛&#xff1f; 4.1 TCP 三次握手与四次挥手面试题 | 小林coding (xiaolincoding.com) 2.描述一下打开百度首页后发生的网络过程&#xff1f; 计算机网络面试题 | 小林coding (xi…

linux-基础知识1

简单命令 init 0 关机 int 6 重启 pwd 查看当前所在目录&#xff0c; cd切换目录 ls 列出目录下的内容 clear 清屏 date 查看时间 路径 linux表示硬件设备的文件在dev目录 /tmp是临时目录&#xff0c;可以创建目录和文件&#xff0c;但不能保证安全 df查看文件系统…

数据仓库系列 1:什么是数据仓库,它与传统数据库有什么不同?

想象一下,你正站在一座巨大的仓库前。这座仓库不是用来存放普通商品的,而是存储着海量的数据 - 这就是数据仓库。在大数据时代,数据仓库已经成为企业数据管理的核心。但它究竟是什么?又为什么如此重要?让我们一起揭开数据仓库的神秘面纱,探索它与我们熟知的传统数据库有何不同…

IDEA2023的激活与安装

前言 开始了java的学习之旅&#xff0c;当然少不了IDEA这个得力的开发工具软件。但是IDEA是付费的&#xff0c;免费版功能有太少&#xff0c;怎么使用上正式版呢&#xff01;当然还是激活啦 第一步&#xff1a;官网下载安装包 安装步骤就不展现了&#xff0c;无脑下一步就可以…

【学习笔记】技术分析-华为智驾控制器MDC Pro 610分析

华为的智能驾驶控制器一直在迭代&#xff0c;和网络上广泛披露的早期MDC 610相比&#xff0c;华为 MDC Pro 610 智能驾驶控制器&#xff0c;现在的样品设计采用了海思的双系统级芯片 (SoC) 提高了处理能力&#xff0c;三星的存储模块为无缝数据处理提供了充足的内存&#xff0c…

高并发业务下的无损技术方案设计

0 前言 秒杀&#xff0c;既有需求真实且迫切的用户&#xff0c;也有试图牟利的黄牛。系统挑战&#xff0c;就是相较于以往千倍万倍的用户规模&#xff0c;可能是真人可能是机器人&#xff0c;在同一瞬间对系统发起冲击&#xff0c;需要海量的计算资源才能支撑。 秒杀系统的设计…

Long Short-Term Memory

这篇论文总结的太抽象了&#xff0c;只是翻译了一遍。 &#xff08;我太笨了&#xff0c;如果把这个当我的入门读物&#xff0c;我觉着会把我折磨坏&#xff09; 递归神经网络的一个重要优点是它们在映射输入和输出序列时使用上下文信息的能力。不幸的是&#xff0c;对于标准的…

51单片机——按键控制

1、按键介绍 轻触按键&#xff1a;相当于是一种电子开关&#xff0c;按下时开关接通&#xff0c;松开时开关断开&#xff0c;实现原理是通过轻触按键内部的金属弹片受力弹动来实现接通和断开。 2、按键的抖动 对于机械开关&#xff0c;当机械触点断开、闭合时&#xff0c;由于…

基于SpringBoot+Vue的家教管理系统

文章目录 前言1.项目类型2.技术栈介绍1.客户端技术栈介绍2.服务端技术栈介绍 3.功能介绍1.客户端功能2.服务单功能 4.项目亮点5.适用场景6.项目展示1.客户端展示2.服务端展示 7.诚邀参与 前言 大家好&#xff0c;我是执手天涯&#xff0c;今天非常荣幸地向大家介绍一款基于Spr…

关于ssrf的实现

ssrf漏洞形成 SSRF(Server-Side Request Forgery:服务器端请求伪造)漏洞形成的原因主要是服务器端所提供的接口中包含了所要请求的内容的URL参数&#xff0c;并且未对客户端所传输过来的URL参数进行过滤 ssrf实现 本次ssrf于Pikachu靶场上实现 我们可以先拉取镜像 docker …

计算机毕业设计Spark+Tensorflow股票推荐系统 股票预测系统 股票可视化 股票数据分析 量化交易系统 股票爬虫 股票K线图 大数据毕业设计 AI

1. 需求分析 基于Spark的股票大数据分析及可视化系统是一个利用Spark分布式计算框架进行股票市场数据处理、分析和可视化的系统。它能够处理大规模的实时股票数据&#xff0c;包括股票价格、交易量、市场指标等&#xff0c;提供实时数据处理、数据可视化与展示和并提供相应决策…

【图像增强】使用 Albumentations Python 库(02)

一、说明 在本博客的第 1 部分中&#xff0c;我介绍了使用 Albumentations Python 库进行图像增广的基础知识。本部分介绍高级详细信息。 二、使用 Albumentations 进行语义分割任务 我们将使用来自 TGS 盐鉴定挑战赛的图像和数据。TGS Salt Identification Challenge | Kaggl…

基于carsim的线控转向仿真(2)--齿条力观测

观测器更详细的介绍文章可以关注博主以下两篇文章 从小车倒立摆系统看系统建模控制LQRLQE仿真_lqr平衡小车仿真模型-CSDN博客 好玩的直流电机调速实验、PID、极点配置、LQR、观测器&#xff1b;不讲大道理_观测器极点配置-CSDN博客 三个实例迅速掌握经典卡尔曼滤波用法_卡尔…

VIM的简单用法

vim三种模式的切换 Set nu&#xff1a;显示行号 Set nonu&#xff1a;不显示行号 Set mousea显示鼠标光标 Set cursorline:显示行线 为什么这些设定默认不能永久存在&#xff1a; 进程结束后&#xff0c;所占的内存空间会被系统回收&#xff0c;资源被释放&#xff0c;这些资源…

国内号码验证注册谷歌邮箱【亲测有效】

前言: 谷歌邮箱可以无需注册直接登录很多软件&#xff0c;但是直接很多人直接注册都会表示国内号码注册不了&#xff0c;所以需求还是有的&#xff0c;这里我尝试一下&#xff0c;顺便记录一下​。 ​环境前提&#xff1a;魔法 ​正文&#xff1a; 打开魔法&#xff0c;开启…

react笔记(React18)

以下笔记可能毫无章法&#xff0c;仅供个人学习记录使用。 关于状态提升&#xff1a; 状态提升适用于兄弟组件之间传递数据&#xff0c;共享状态&#xff0c;其实就是把两个兄弟组件要共同使用的数据存放到共同的父组件中&#xff0c;称状态提升。 关于context跨层级组件通信…

5步掌握“花开富贵”花园管理系统开发——基于Python Django+Vue

&#x1f34a;作者&#xff1a;计算机毕设匠心工作室 &#x1f34a;简介&#xff1a;毕业后就一直专业从事计算机软件程序开发&#xff0c;至今也有8年工作经验。擅长Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等。 擅长&#xff1a;按照需求定制化开发项目…

Java超市收银系统(十、爬虫)

引言 爬虫功能实现&#xff0c;要求爬取页面数据至少100条&#xff0c;这里以豆瓣音乐为示例编写代码豆瓣音乐标签: 民谣 (douban.com)。 功能实现 除了爬虫功能增加&#xff0c;代码其他内容原理和之前博客发布是一致的&#xff0c;只不过这里为了区分&#xff0c;我们重新创…

IDM是海外加速器吗 IDM在国内好用吗

IDM是一款出色的下载加速器&#xff0c;它可以将下载任务分割成多个部分&#xff0c;利用多线程技术加速下载速度&#xff0c;支持断点续传功能&#xff0c;能够从上次下载中断的地方继续下载&#xff0c;提高了下载效率和稳定性&#xff0c;所以深受年轻人的欢迎。 一、IDM是…