一文通俗理解为什么需要泛型以及泛型的使用

news2025/2/22 10:46:33

为什么需要泛型?

public static void main(String[] args) {
    ArrayList list = new ArrayList();
    // 由于集合没有做任何限定,任何类型都可以给其中存放
    list.add("abc");
    list.add("def");
    list.add(5);
    Iterator it = list.iterator();
    while (it.hasNext()) {
        // 需要打印每个字符串的长度,就要把迭代出来的对象转成String类型
        String str = (String) it.next();
        System.out.println(str.length());
    }
}

思考:上面这段代码有什么问题吗?如果有,那么出现问题的原因是什么?

编译不报错,但运行发生了报错。

因为没使用泛型,导致我们想使用的时候,一旦强转就会出现类型转换异常,而在我们工作中,其实很多时候,我们都只需要一个集合容器里面放一个类型,如果有多个类型,我们就分多个容器来存放,那么进行约束存放类型的功能,就叫泛型(Generic)语法,让你在设计API时可以指定类或方法支持泛型,这样我们使用API的时候也变得更为简洁,并得到了编译时期的语法检查

泛型的使用

打开ArrayList的源码, 会发现在定义类的时候, 在类名后面加了个,然后这个E, 在多个方法参数里面都进行了使用, 这个E到底是什么?

class ArrayList<E> {
    public boolean add(E e) { }
    public E get(int index) { }
    ...
}

class ArrayList<String> {
    public boolean add(String e) { }
    public String get(int index) { }
    ...
}

class ArrayList<Integer> {
    public boolean add(Integer e) { }
    public Integer get(int index) { }
    ...
}

我 们 发 现 , 只 要 我 们 在 newArrayList<>()的时候,** 在<>括号里面放什么类型, 那么这个E就是什么类型**,比如我们构建的集合是String类型, 那么 E 就 是 String , 我 们 构 建 的 集 合 是Integer类型, 那么E就是Integer;

当然,这个E是可换成自定义的名字,比如说MyType。

使用泛型定义类

新建一个类,然后在<>里面写一个不存在的类型,这个类型甚至不需要我们创建类,然后为了展示,书写了该属性对应的get和set方法。

public class MyGenericClass<MyType> {
//	private E e;
//
//	public E getE() {
//		return e;
//	}
//
//	public void setE(E e) {
//		this.e = e;
//	}
	
	private MyType myType;

	public MyType getMyType() {
		return myType;
	}

	public void setMyType(MyType myType) {
		this.myType = myType;
	}
	
}

在测试类中,我们创建了2次之前所构建的类对象,两次所传递的泛型类型是不一样的,但是它都能进行接收,并且接收完成之后,也可以通过调用set方法来进行赋值,也能通过调用get方法来进行输出该数据。

public class TestMyGenericClass {
	public static void main(String[] args) {
		//模拟构建ArrayList对象的形式来构建一个MyGenericClass类对象
		MyGenericClass<String> my = new MyGenericClass<String>();
		my.setMyType("张三");
		System.out.println(my.getMyType());
		//再来创建一次其他类型的泛型
		MyGenericClass<Integer> my1 = new MyGenericClass<Integer>();
		my1.setMyType(1001);
		System.out.println(my1.getMyType());
	}
}

使用泛型定义接口

public interface MyGenericInterface<E> {
	void add(E e);
	E getE();
}

定义了一个带泛型的接口之后,这个接口被使用的时候,可以直接在写implements的时候,就把泛型给写死了,代表定义类的时候就确定好了泛型的类型了。

public class MyImp2 implements MyGenericInterface<String>{

	@Override
	public void add(String e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public String getE() {
		// TODO Auto-generated method stub
		return null;
	}

}

定义了一个带泛型的接口之后,这个接口被使用的时候,也可以在该类上仍然不把泛型的类型给确定,让它在构建该类对象的时候去确定该类型。

public class MyImp1<E> implements MyGenericInterface<E>{
	@Override
	public void add(E e) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public E getE() {
		// TODO Auto-generated method stub
		return null;
	}
}

MyImp2类在使用时无需给出泛型,但MyImp1类在使用时必须指明泛型。

public class TestGenericInterface {
	public static void main(String[] args) {
		MyImp1<Integer> my1 = new MyImp1<Integer>();
		//随着你指定的泛型类型不一样,那么它里面的两个方法参数和返回类型也发生了变化
		my1.add(1001);
		//这个类型的创建,不需要你使用泛型了,因为我们在该类构建的时候,就已经确定了父接口的
		//泛型的类型,所以它的方法都跟着变了,以后就不能发生改变了!
		MyImp2 my2 = new MyImp2();
		my2.add("你好");
	}
}

使用泛型定义方法

在方法的返回值前面如果加上泛型的话,那么参数里面的MyType将会报错,所以,通过泛型在方法上的运用,可以让我们在书写方法重载的时候,变的更加的方便

public class MyGenericMethod {
	//在修饰词的位置写一个泛型的尖括号,里面随便写个类型
	//这样的效果同等于在类或者是接口名后加<>,只不过这样
	//的范围就只局限于当前的方法
	public <E> void show1(E e) {
		System.out.println(e.getClass());
	}
	
	public <E> E show2(E e) {
		return e;
	}
	
	public static void main(String[] args) {
		MyGenericMethod mm = new MyGenericMethod();
		mm.show1(123);
		mm.show1("abc");
		mm.show1(3.14);
		
		System.out.println(mm.show2(123));
	}
}

通配符

然而,如果设 Foo 是 Bar 的一个子类型(子类或者子接口),而 G 是具有泛型声明的类或接口,G并不是 G 的子类型!这一点非常值得注意,因为它与大部分人的习惯认为是不同的。

造成这个原因是因为泛型不存在继承关系

类型通配符的上下限

如果你想限制一个方法,限制其参数的泛型类型时,你有两种方式:

  1. 使用<? extends 类>来约束泛型必须要是该类或者是该类的子类

  2. 使用<? super 类>来约束泛型必须要是该类或者是该类的父类

为什么指定通配符上限的集合不能添加元素?

// 定义一个抽象类 Shape
public abstract class Shape {
    public abstract void draw(Canvas c);
}
// 定义 Shape 的子类 Circle
public class Circle extends Shape {
    // 实现画图方法,以打印字符串来模拟画图方法实现
    public void draw(Canvas c) {
        System.out.println("在画布" + c + "上画一个圆");
    }
}
// 定义 Shape 的子类 Rectangle
public class Rectangle extends Shape {
    // 实现画图方法,以打印字符串来模拟画图方法实现
    public void draw(Canvas c) {
        System.out.println("把一个矩形画在画布" + c + "上");
    }
}
public void addRectangle(List<? extends Shape> shapes) {
    // 下面代码引起编译错误
    shapes.add(0, new Rectangle());
}

《java疯狂讲义》给出的答案是:

与使用普通通配符相似的是,shapes.add() 的第二个参数类型是? extends Shape,它表示 Shape 未知的子类,程序无法确定这个类型是什么,所以无法将任何对象添加到这种集合中。

简而言之,这种指定通配符上限的集合,只能从集合中取元素(取出的元素总是上限的类型或其子类),不能向集合中添加元素(因为编译器没法确定集合元素实际是哪种子类型)

我的更通俗理解:List<? extends Shape> shapes传进来的是一个类别Shape本身或者子类(这里我们假设有子类A和B,且A与B之间不存在继承关系)的集合,如果shapes添加子类A的对象,那万一shapes运行时传进来的是子类B的集合,那么"shapes.add(0, new Rectangle());"这行代码是存在问题的;同理shapes添加Shape的对象也是如此。

因此通配符上限的集合,即<? extends Shape>是无法添加的元素,而通配符下限的集合,即<? super Shape>确可以。因为<? super Shape>传进来的都是Shape的父类,而我们添加的元素只要是Shape类别或者Shape的子类就是合法的。

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

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

相关文章

Sam Altman 揭秘 OpenAI 未来蓝图:GPT-4.5、GPT-5 与模型规范重大更新

OpenAI CEO Sam Altman 近日在 X 平台&#xff08;原 Twitter&#xff09;上分享了关于 GPT-4.5 (代号 “Orion”) 和 GPT-5 的最新进展&#xff0c;同时公布了 OpenAI 模型规范&#xff08;Model Spec&#xff09;的重大更新&#xff0c;强调知识自由与模型行为准则。 核心亮…

老牌系统工具箱,现在还能打!

今天给大家分享一款超实用的电脑软硬件检测工具&#xff0c;虽然它是一款比较“资深”的软件&#xff0c;但依然非常好用&#xff0c;完全能满足我们的日常需求。 电脑软硬件维护检测工具 功能强大易用 这款软件非常贴心&#xff0c;完全不需要安装&#xff0c;直接打开就能用…

在vivado中对数据进行延时,时序对齐问题上的理清

在verilog的ISP处理流程中&#xff0c;在完成第一个模块的过程中&#xff0c;我经常感到困惑&#xff0c;到底是延时了多少个时钟&#xff1f;今日对这几个进行分类理解。 目录 1.输入信号激励源描述 1.1将数据延时[9]个clk 1.2将vtdc与hzdc延时[9]个clk(等价于单bit的数据…

链表 —— 常用技巧与操作总结详解

引言 链表作为一种动态数据结构&#xff0c;以其灵活的内存管理和高效的插入删除操作&#xff0c;在算法与工程实践中占据重要地位。然而&#xff0c;链表的指针操作复杂&#xff0c;容易引发内存泄漏和野指针问题。本文博主将从基础操作到高阶技巧&#xff0c;系统化解析链表的…

Linux下学【MySQL】常用函数助你成为数据库大师~(配sql+实操图+案例巩固 通俗易懂版~)

绪论​ 每日激励&#xff1a;“唯有努力&#xff0c;才能进步” 绪论​&#xff1a; 本章是MySQL中常见的函数&#xff0c;利用好函数能很大的帮助我们提高MySQL使用效率&#xff0c;也能很好处理一些情况&#xff0c;如字符串的拼接&#xff0c;字符串的获取&#xff0c;进制…

[c语言日寄]在不完全递增序中查找特定要素

【作者主页】siy2333 【专栏介绍】⌈c语言日寄⌋&#xff1a;这是一个专注于C语言刷题的专栏&#xff0c;精选题目&#xff0c;搭配详细题解、拓展算法。从基础语法到复杂算法&#xff0c;题目涉及的知识点全面覆盖&#xff0c;助力你系统提升。无论你是初学者&#xff0c;还是…

HtmlRAG:RAG系统中,HTML比纯文本效果更好

HtmlRAG 方法通过使用 HTML 而不是纯文本来增强 RAG 系统中的知识表示能力。通过 HTML 清洗和两步块树修剪方法&#xff0c;在保持关键信息的同时缩短了 HTML 文档的长度。这种方法优于现有基于纯文本的RAG的性能。 方法 其实主要看下围绕html提纯思路&#xff0c;将提纯后的…

在WPS中通过JavaScript宏(JSA)调用本地DeepSeek API优化文档教程

既然我们已经在本地部署了DeepSeek,肯定希望能够利用本地的模型对自己软件开发、办公文档进行优化使用,接下来就先在WPS中通过JavaScript宏(JSA)调用本地DeepSeek API优化文档的教程奉上。 前提: (1)已经部署好了DeepSeek,可以看我的文章:个人windows电脑上安装DeepSe…

2023-arXiv-CoT Prompt 思维链提示提升大型语言模型的推理能力

arXiv | https://arxiv.org/abs/2201.11903 摘要&#xff1a; 我们探讨了如何生成思维链&#xff08;一系列中间推理步骤&#xff09;显著提高大型语言模型执行复杂推理的能力。在三个大型语言模型上的实验表明&#xff0c;思维链提示提高了一系列算术、常识和符号推理任务的性…

程序诗篇里的灵动笔触:指针绘就数据的梦幻蓝图<10>

大家好啊&#xff0c;我是小象٩(๑ω๑)۶ 我的博客&#xff1a;Xiao Xiangζั͡ޓއއ 很高兴见到大家&#xff0c;希望能够和大家一起交流学习&#xff0c;共同进步。 今天我们继续来复习指针… 目录 一、看一段代码二、 一维数组传参的本质三、冒泡排序3.1 基本思想四、二…

CNN|ResNet-50

导入数据 import matplotlib.pyplot as plt # 支持中文 plt.rcParams[font.sans-serif] [SimHei] # 用来正常显示中文标签 plt.rcParams[axes.unicode_minus] False # 用来正常显示负号import os,PIL,pathlib import numpy as npfrom tensorflow import keras from tensor…

吉祥汽车泰国首发,用 Unity 实现行业首创全 3D 座舱虚拟世界

11 月 19 日&#xff0c;均瑶集团吉祥智驱&#xff08;以下简称“吉祥汽车”&#xff09;首款纯电动汽车 JY AIR 在泰国首发。延续吉祥航空在飞行体验上的优势&#xff0c;吉祥汽车对 JY AIR 赋予了将航空级服务标准延伸至地面的使命&#xff0c;为用户提供一站式大出行体验。此…

【OpenCV】双目相机计算深度图和点云

双目相机计算深度图的基本原理是通过两台相机从不同角度拍摄同一场景&#xff0c;然后利用视差来计算物体的距离。本文的Python实现示例&#xff0c;使用OpenCV库来处理图像和计算深度图。 1、数据集介绍 Mobile stereo datasets由Pan Guanghan、Sun Tiansheng、Toby Weed和D…

Uniapp 原生组件层级过高问题及解决方案

文章目录 一、引言&#x1f3c5;二、问题描述&#x1f4cc;三、问题原因❓四、解决方案&#x1f4af;4.1 使用 cover-view 和 cover-image4.2 使用 subNVue 子窗体4.3 动态隐藏原生组件4.4 使用 v-if 或 v-show 控制组件显示4.5 使用 position: fixed 布局 五、总结&#x1f38…

【数据结构初阶第十节】队列(详解+附源码)

好久不见。。。别不开心了&#xff0c;听听喜欢的歌吧 必须有为成功付出代价的决心&#xff0c;然后想办法付出这个代价。云边有个稻草人-CSDN博客 目录 一、概念和结构 二、队列的实现 Queue.h Queue.c test.c Relaxing Time&#xff01; ————————————《有没…

250213-RHEL8.8-外接SSD固态硬盘

It seems that the exfat-utils package is still unavailable, even after enabling the RPM Fusion repository. This could happen if the repository metadata hasn’t been updated or if the package isn’t directly available in the RPM Fusion repository for RHEL 8…

游戏引擎学习第99天

仓库:https://gitee.com/mrxiao_com/2d_game_2 黑板&#xff1a;制作一些光场(Light Field) 当前的目标是为游戏添加光照系统&#xff0c;并已完成了法线映射&#xff08;normal maps&#xff09;的管道&#xff0c;但还没有创建可以供这些正常映射采样的光场。为了继续推进&…

Linux初始化 配置yum源

问题出现&#xff1a;&#xff08;报错&#xff09; 1 切换路径 2 备份需要操作的文件夹 3 更改 CentOS 的 YUM 仓库配置文件&#xff0c;以便使用阿里云的镜像源。 4 清除旧的yum缓存 5 关闭防火墙 6 生成新的yum缓存 7 更新系统软件包 8 安装软件包

【笛卡尔树】

笛卡尔树 笛卡尔树定义构建性质 习题P6453 [COCI 2008/2009 #4] PERIODNICF1913D Array CollapseP4755 Beautiful Pair[ARC186B] Typical Permutation Descriptor 笛卡尔树 定义 笛卡尔树是一种二叉树&#xff0c;每一个节点由一个键值二元组 ( k , w ) (k,w) (k,w) 构成。要…

Java String 类深度解析:内存模型、常量池与核心机制

目录 一、String初识 1. 字符串字面量的处理流程 (1) 编译阶段 (2) 类加载阶段 (3) 运行时阶段 2. 示例验证 示例 1&#xff1a;字面量直接赋值 示例 2&#xff1a;使用 new 创建字符串 示例 3&#xff1a;显式调用 intern() 注意点1&#xff1a; ⑴. String s1 &q…