Java 包装类简单认识泛型
学习目标:
1,以 能阅读Java集合源码 为目标 学习泛型
2,了解包装类
3,了解泛型
一,包装类
在java中,由于基本类型不是继承于Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型
1,基本数据类型和对应的包装类
举个例子来说明两者的关系
比如说基本数据类型 byte 可以定义变量 a = 10, 而它的包装类 Byte 不仅可以定义变量 a = 10,在Byte里还有许多方法可以使用,因为他是一个类
除了 Integer 和 Character, 其余基本类型的包装类都是首字母大写。
2,装箱和拆箱:
装箱/装包: 自动装箱 显示装箱
拆箱/拆包: 自动拆箱 显示拆箱
什么是装箱:
把基本数据类型 变为 包装类类型 的过程 叫做装箱
什么是拆箱:
把包装类类型 变为 基本数据类型 的过程 叫做拆箱
1) 装箱
这个叫做自动装箱(隐式装箱)
这个叫做显示装箱
注意:无论是隐式还是显示装箱,底层都调用了valueOf方法
2) 拆箱
这个叫做自动拆箱(隐式拆箱)
这个叫做显示拆箱
3)小测试
这里有一段代码,猜一下输出的值是什么
是不是和你想的不太一样,为什么会这样?
我们想一下,这个过程是 装箱 对吧,那我们查看 装箱的源码 就得看 valueOf 的源码
这是valueOf的源码,可以看到,当变量的值在一个 范围 里面的时候,他返回的值 是直接从数组里拿的,当不在这个范围里的时候,直接返回一个 新的值,把新的值和旧值比较,结果必然不一样
那么,这个范围是什么呢?
从源码中看到,最小值是-128 最大值是127
因此在[-128 ~ 127]内的 256个数字 在进行 == 比较的时候都会输出 true
刚才说了在数组里取值,那这个数组是什么?
返回的这个数组,它的下标逻辑是
也就是说,当 i = -128 的时候,这个数组的下标是 -128 + ( - ( - 128 ) ) = 0 当 i=127 的时候,数组的下标是127+128 = 255
这个数组我们称为 缓存数组
明白了吧,那我们就不能在超出这个范围的时候 用我们正常的算术逻辑 去比较值了吗?当然不是,可以使用equals方法
二,泛型
1,什么是泛型
2,泛型的引出
int[] array = new int[10]; String[] strs = new String[10];
这样数组里就可以存放你想要的任何类型了
但是
3,泛型的语法
class 泛型类名称<类型形参列表> {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> {
}
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
// 可以只使用部分类型参数
}
代码1示例:
分析代码1:
类定义:
首先我们放置了一个<E>,这个其实可以看做一个占位符,目的就是告诉编译器这是一个泛型,E
是类型参数,代表该类可以操作任意类型的对象(后续使用时指定具体类型,如 Integer
)
成员变量:
public Object[] arr = new Object[10];
声明了一个长度为 10 的 Object
数组 arr
,用于存储数据。因为是 Object
类型数组,理论上可以存储任何类型的对象,但实际使用时会根据泛型参数做类型转换。
方法setValue:
public void SetValue(int pos, E val)
方法用于向数组 arr
的指定位置 pos
存储值 val
。参数 pos
是数组索引,val
是要存储的、类型为 E
的值,直接将 val
赋值给 arr[pos]
。
方法getValue:
public E getValue(int pos)
方法用于从数组 arr
的指定位置 pos
获取值,并将其转换为泛型类型 E
后返回。通过 (E)arr[pos]
做强制类型转换,把 Object
类型的数组元素转换为 E
类型。
主方法main:
MyArray<Integer> myArray = new MyArray();
创建 MyArray
类的对象 myArray
,指定泛型类型为 Integer
,但这里使用了原始类型创建(没有完整指定泛型参数,编译器会有警告 ),更规范的写法是 new MyArray<Integer>()
。当然不写也不报错
myArray.SetValue(1, 10);
调用 SetValue
方法,向 myArray
数组的索引 1 的位置存储值 10(因为泛型类型是 Integer
,所以实际存储的是 Integer
类型的对象 )。
那我们想要安放别的数据类型的时候怎么办,看代码:
代码2示例:
4,泛型是如何进行编译的
也就是说泛型是编译时期的一种机制,在运行的时候 没有泛型的概念
在编译完成后, E 会被擦除为 Object
擦除机制是一个非常晦涩的机制,我们来简单认识一下就好
由于这里我没有重写 toString方法,因此这里会输出test类地址
(shujujiegou是我的包名,不用管)
发现了吗,无论是Integer类型还是String类型,都被擦掉了.
通俗讲,此时<>里的内容不参与类型的组成,类型就是MyArray这就是 擦除机制
5,泛型的上界
观察这个代码,和之前的代码有什么不同
这两处就是不同的地方
此时规定传入的参数是Number或者Number的子类,但是String不是Number的子类,所以报错
此时就指定了传入参数E的一个界限 而不是想传什么就传什么
语法:
像之前那种没有上界的,可以认为E extends Object
这是什么意思?
即 E必须是实现了Comparable接口的