【Java】泛型 之 什么是泛型

2023-09-21 17:11:19

什么是泛型

泛型是一种“代码模板”,可以用一套代码套用各种类型。

在讲解什么是泛型之前,我们先观察Java标准库提供的ArrayList,它可以看作“可变长度”的数组,因为用起来比数组更方便。

实际上ArrayList内部就是一个Object[]数组,配合存储一个当前分配的长度,就可以充当“可变数组”:

public class ArrayList {
    private Object[] array;
    private int size;
    public void add(Object e) {...}
    public void remove(int index) {...}
    public Object get(int index) {...}
}

如果用上述ArrayList存储String类型,会有这么几个缺点:

  • 需要强制转型;
  • 不方便,易出错。

例如,代码必须这么写:

ArrayList list = new ArrayList();
list.add("Hello");
// 获取到Object,必须强制转型为String:
String first = (String) list.get(0);

很容易出现ClassCastException,因为容易“误转型”:

list.add(new Integer(123));
// ERROR: ClassCastException:
String second = (String) list.get(1);

要解决上述问题,我们可以为String单独编写一种ArrayList:

public class StringArrayList {
    private String[] array;
    private int size;
    public void add(String e) {...}
    public void remove(int index) {...}
    public String get(int index) {...}
}

这样一来,存入的必须是String,取出的也一定是String,不需要强制转型,因为编译器会强制检查放入的类型:

StringArrayList list = new StringArrayList();
list.add("Hello");
String first = list.get(0);
// 编译错误: 不允许放入非String类型:
list.add(new Integer(123));

问题暂时解决。

然而,新的问题是,如果要存储Integer,还需要为Integer单独编写一种ArrayList

public class IntegerArrayList {
    private Integer[] array;
    private int size;
    public void add(Integer e) {...}
    public void remove(int index) {...}
    public Integer get(int index) {...}
}

实际上,还需要为其他所有class单独编写一种ArrayList

LongArrayList
DoubleArrayList
PersonArrayList
...

这是不可能的,JDKclass就有上千个,而且它还不知道其他人编写的class

为了解决新的问题,我们必须把ArrayList变成一种模板:ArrayList<T>,代码如下:

public class ArrayList<T> {
    private T[] array;
    private int size;
    public void add(T e) {...}
    public void remove(int index) {...}
    public T get(int index) {...}
}

T可以是任何class。这样一来,我们就实现了:编写一次模版,可以创建任意类型的ArrayList

// 创建可以存储String的ArrayList:
ArrayList<String> strList = new ArrayList<String>();
// 创建可以存储Float的ArrayList:
ArrayList<Float> floatList = new ArrayList<Float>();
// 创建可以存储Person的ArrayList:
ArrayList<Person> personList = new ArrayList<Person>();

因此,泛型就是定义一种模板,例如ArrayList,然后在代码中为用到的类创建对应的ArrayList<类型>:

ArrayList<String> strList = new ArrayList<String>();

由编译器针对类型作检查:

strList.add("hello"); // OK
String s = strList.get(0); // OK
strList.add(new Integer(123)); // compile error!
Integer n = strList.get(0); // compile error!

这样一来,既实现了编写一次,万能匹配,又通过编译器保证了类型安全:这就是泛型。

向上转型

在Java标准库中的ArrayList<T>实现了List<T>接口,它可以向上转型为List<T>

public class ArrayList<T> implements List<T> {
    ...
}


List<String> list = new ArrayList<String>();

即类型ArrayList<T>可以向上转型为List<T>

要特别注意:不能把ArrayList<Integer>向上转型为ArrayList<Number>List<Number>

这是为什么呢?假设ArrayList<Integer>可以向上转型为ArrayList<Number>,观察一下代码:

// 创建ArrayList<Integer>类型:
ArrayList<Integer> integerList = new ArrayList<Integer>();
// 添加一个Integer:
integerList.add(new Integer(123));
// “向上转型”为ArrayList<Number>:
ArrayList<Number> numberList = integerList;
// 添加一个Float,因为Float也是Number:
numberList.add(new Float(12.34));
// 从ArrayList<Integer>获取索引为1的元素(即添加的Float):
Integer n = integerList.get(1); // ClassCastException!

我们把一个ArrayList<Integer>转型为ArrayList<Number>类型后,这个ArrayList<Number>就可以接受Float类型,因为FloatNumber的子类。但是,ArrayList<Number>实际上和ArrayList<Integer>是同一个对象,也就是ArrayList<Integer>类型,它不可能接受Float类型, 所以在获取Integer的时候将产生ClassCastException

实际上,编译器为了避免这种错误,根本就不允许把ArrayList<Integer>转型为ArrayList<Number>

ArrayList<Integer>ArrayList<Number>两者完全没有继承关系。

小结

泛型就是编写模板代码来适应任意类型;

泛型的好处是使用时不必对类型进行强制转换,它通过编译器对类型进行检查;

注意泛型的继承关系:可以把ArrayList<Integer>向上转型为List<Integer>(T不能变!),但不能把ArrayList<Integer>向上转型为ArrayList<Number>(T不能变成父类)。

更多推荐

伦敦银一手是多少?

伦敦银是以国际现货白银价格为跟踪对象的电子合约交易,无论投资者通过什么地方的平台进入市场,执行的都是统一国际的标准,一手标准的合约所代表的就是5000盎司的白银,如果以国内投资者比较熟悉的单位计算,那约相当于15.5公斤的白银。至于一手伦敦银合约的总价值是多少,具体的数值会根据国际银价的波动而变化。如果以近期每盎司25

python经典百题之求数字位数及逆序打印

题目:给一个不多于5位的正整数,要求:一、求它是几位数,二、逆序打印出各位数字程序分析我们需要编写一个程序,能够接受不多于5位的正整数,然后分析其位数,并逆序打印出各位数字。可以利用取模和除法运算来实现逆序打印数字,同时通过不断除以10的方式确定位数。方法1:使用取模和除法defreverse_print(num):p

dvwa命令执行漏洞分析

dvwa靶场命令执⾏漏洞high难度的源码:$target=trim($_REQUEST[‘ip’]);是一个接收id值的变量array_keys()函数功能是返回包含原数组中所有键名的一个新数组。str_replace()函数如下,把字符串“Helloworld!”中的字符“world”替换为“Shanghai”:s

自然语言处理(一):基于统计的方法表示单词

文章目录1.共现矩阵2.点互信息3.降维(奇异值分解)1.共现矩阵将一句话的上下文大小窗口设置为1,用向量来表示单词频数,如:将每个单词的频数向量求出,得到如下表格,即共现矩阵:我们可以用余弦相似度(cosinesimilarity)来计算单词向量的相似性:similarity⁡(x,y)=x⋅y∥x∥∥y∥=x1y1

大模型训练为什么用A100不用4090

这是一个好问题。先说结论,大模型的训练用4090是不行的,但推理(inference/serving)用4090不仅可行,在性价比上还能跟H100打个平手。事实上,H100/A100和4090最大的区别就在通信和内存上,算力差距不大。H100A1004090TensorFP16算力1979Tflops312Tflops

React 全栈体系(七)

第四章Reactajax一、理解1.前置说明React本身只关注于界面,并不包含发送ajax请求的代码前端应用需要通过ajax请求与后台进行交互(json数据)react应用中需要集成第三方ajax库(或自己封装)2.常用的ajax请求库jQuery:比较重,如果需要另外引入不建议使用axios:轻量级,建议使用封装X

Kotlin语言基础(二)-变量和数据类型

Kotlin语言基础-变量和数据类型一、Kotlin的变量Kotlin变量有两种形式var(variable)和val(value,取值)val定义只读量,一旦创建,其值不会发生变化例:vala=23那么对于a对应的值就只能是23,不会发生变化。如何试图对a重新赋值都会导致编译错误。var定义可变的变量,可以多次赋值修

【数据结构】堆的顺序结构及实现

目录一,堆的顺序结构二,堆的概念及结构三,堆的实现3.1堆的结构3.2堆的初始化3.3堆的插入数据3.3堆的删除数据3.4堆的取顶数据,返回堆数据大小,判断非空3.5堆的销毁四,总代码一,堆的顺序结构普通的二叉树是不适合用数组来存储的,因为可能存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们把堆(一种

Mybatis学习笔记8 查询返回专题

Mybatis学习笔记7参数处理专题_biubiubiu0706的博客-CSDN博客1.返回实体类2.返回List<实体类>3.返回Map4.返回List<Map>5.返回Map<String,Map>6.resultMap结果集映射7.返回总记录条数新建模块依赖目录结构1.返回实体类如果返回多条,用单个实体接收会出异

RHEL7 配置 LVM Mirror 创建共享卷

文章目录前言1.配置LVM镜像卷1.1.创建PV1.2.创建VG1.3.创建LV1.4.查看LV1.5.格式化文件系统1.6.创建挂载点并挂载1.7.写入测试数据2.切换LVM共享卷2.1.计划内切换2.1.1.检查I/O流量2.1.2.节点一卸载挂载点2.1.3.节点一禁用LV2.1.4.节点一禁用VG2.1.5.节

AI视频剪辑:批量智剪技巧大揭秘

对于许多内容创作者来说,视频剪辑是一项必不可少的技能。然而,传统的视频剪辑方法需要耗费大量的时间和精力。如今,有一种全新的剪辑方式正在改变这一现状,那就是批量AI智剪。这种智能化的剪辑方式能够让你在短时间内轻松剪辑大量视频,省时又省力。首先,要在浏览器中搜索并下载"固乔智剪软件"。这款软件基于人工智能技术,能够自动化地

热文推荐