数据结构:3.包装类和泛型
【目标】
1.了解包装类
2. 以 能阅读java集合源码 为目标学习泛型
3.了解泛型
1.包装类(Wrapper Class)
1.1 引出包装类
1.1.1 什么是包装类?
一句话:包装类就是把 Java 的 8 种基本数据类型(int,double,char等)“包装”成对应的类。以下是对应关系,期终 int 和 char 的包装类需要特别注意。
| 基本类型 | 包装类 |
|---|---|
int | Integer |
long | Long |
double | Double |
char | Character |
boolean | Boolean |
byte | Byte |
short | Short |
float | Float |
形象理解:
基本类型= 一张写了数字“10”的纸片,轻便但功能单一。
包装类= 把那张纸片放进一个信封里。信封上可以贴邮票、写字、盖章——对象能做很多事,但多了一层包装。
1.1.2 包装类存在的意义?(建议学完 “1.2 包装类的基础语法” 再看)
意义1:让基本类型也能“变成对象”,放进集合里
这是最直接、最重要的原因。
Java 的集合(ArrayList,HashMap等)只能存储对象,不能存基本类型。
// ❌ 这样写会报错!ArrayList<int> 不允许 ArrayList<int> list = new ArrayList<>(); // ✅ 必须用包装类 ArrayList<Integer> list = new ArrayList<>(); list.add(10); // 自动装箱:int → Integer int first = list.get(0); // 自动拆箱:Integer → int
意义2:提供实用的工具方法
基本类型什么方法都没有,而包装类却提供了很多静态方法,这也正是包装类存在的意义。
// 字符串转数字 int num = Integer.parseInt("123"); // 比较两个 int(可以避免 null 问题) int result = Integer.compare(10, 20); // -1 // 最大值/最小值 int max = Integer.MAX_VALUE; // 2147483647 int min = Integer.MIN_VALUE; // -2147483648意义3:支持泛型
泛型只能用类,不能用基本类型。
// ❌ 报错 List<int> list1 = new ArrayList<>(); // ✅ 正确 List<Integer> list2 = new ArrayList<>(); // 泛型方法也只能用包装类 public <T> T getValue(T value) { return value; } // 调用:getValue(100) → 自动装箱成 Integer意义4:可以表示“空值”(null)
基本类型不能是null,包装类可以。这在数据库操作、JSON解析等场景很重要。
int a = null; // ❌ 编译错误 Integer b = null; // ✅ 可以 // 实际应用:从数据库读取年龄字段,可能是空值 Integer age = getAgeFromDB(); // 可能是 null if (age == null) { System.out.println("年龄未知"); }意义5:实现对象级特性
可继承/多态:包装类是被final修饰的
,不能被继承。但可以统一当作Number运用多态思想进行处理。
// 多态:Number 是 Integer、Double 等的父类
public void printNumber(Number n) {
System.out.println(n);
}
printNumber(100); // Integer
printNumber(3.14); // Double
可序列化:包装类实现了
Serializable,可以跨网络传输。(此处只是简单进行理解)
Serializable接口是一个空接口,表明这个类的对象可以被序列化(对象 → 字节流)和反序列化(字节流 → 对象)。
字节流就是一串按顺序排列的字节(byte),每个字节是 0~255 之间的整数,它是计算机中所有数据在“传输/存储”时的底层通用形态。【字节流就是机器语言(二进制语言)和编程语言(java、python等)进行转化之间的根本中间变量】
你写的所有东西 (Java代码、图片、文字、视频...)
↓
【字节流】 (底层二进制数据)
↓
┌─────────┬─────────┼──────────┬──────────┐
↓ ↓ ↓ ↓ ↓
字节码 序列化流 图片格式 文本编码 网络传输
↓ ↓ ↓ ↓ ↓
JVM 对象恢复 图片查看器 文本编辑器 网卡/路由器
1.2 包装类相关语法
1.2.1 装箱\装包
1.2.2 拆箱\拆包
1.3 补充知识
1.3.1 关于缓存池![]()
问:为什么会出现这一种情况???
答:与缓存池(针对于包装类,只是为了方便交流而取的一个名字,实际上就是一块普通的堆空间)有关,具体解释如下:
首先我们要明白缓存池是什么:缓存池说白了就是普通的堆空间。
具体解析:对于包装类而言,他是一个类,那么必然会要创造对象(即使我们看到 显示和隐式 都没有创建对象,实际上是底层创建了对象,感兴趣的可以去看一下源码),我们把常用的包装类对象拿出来,用一个静态数组来接收(静态数组表明:数组引用存放在方法区;而具体的数组对象还是在堆上)(这个数组存的是包装类对象,而不是数字)
| 包装类 | 是否有缓存池 | 缓存范围 | 说明 |
|---|---|---|---|
Integer | ✅ 有 | -128 ~ 127 | 最典型、最常被讨论 |
Long | ✅ 有 | -128 ~ 127 | 和 Integer 一样 |
Short | ✅ 有 | -128 ~ 127 | 和 Integer 一样 |
Byte | ✅ 有 | -128 ~ 127(全部) | Byte 总共就 256 个值,全缓存 |
Character | ✅ 有 | 0 ~ 127(ASCII) | 只缓存 ASCII 字符 |
Boolean | ❌ 没有数组 | 就两个常量 | 不是数组,是两个静态常量TRUE/FALSE |
Double | ❌ 没有 | - | 浮点数范围太大,不缓存 |
Float | ❌ 没有 | - | 浮点数范围太大,不缓存 |
也就是说这里 aaa 和 bbb 指向了同一个对象,而 aaa 和 bbb 都是引用,存储的是地址(在这里多说一点,我们 直接打印引用 得到的并不是地址,而是 hashCode() 的返回值)
而 ccc 和 ddd 分别指向的是不同的对象,他们存储的地址并不相同,所以出现的结果不同
1.3.2 关于常量池
问:为什么会出现这一种情况???
答:与常量池有关
首先我们要明白常量池是什么:常量池是堆上的一块特殊空间,独立于堆上的其他空间。
具体解析:
1. 在程序刚开始运行的时候,常量池中是什么都没有的
2. 我们把第一次通过 "" 的形式创建的对象放入常量池中
3. 等到以后再通过 "" 的形式创建相同内容的对象的时候,直接让这个引用指向常量池中已被创建的对象
所以说: a 和 b 指向的是同一个对象,那么通过 == 比较的是他们的实际地址,实际地址相同,所以说返回值为 true
而通过 new 来创建对象的形式就是普通创建一个对象,和常量池无关,所以 a 和 c 进行比较返回的却是 false
2.泛型
2.1 引出泛型
2.1.1什么是泛型?
一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。----- 来源《Java编程思想》对泛型的介绍。
泛型是在JDK1.5引入的新的语法,通俗讲,泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化(把 类型 当作参数)
2.2.2 泛型所能解决的问题示例
需求:实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以通过成员方法根据下标找到对应的值。
根据以上代码显示,我们实现了需求,但不难发现有两个问题:
1. 代码混乱,类型混乱
2. 需要我们进行手动强转
在这种情况下,我们数组中的任何数据都可以存取,但是在更多情况下,我们还是希望每一个数组有固定的类型,而非一个 Object 就可以全部接受的,这样不利于代码的维护。
所以我们就引出了泛型:把数据类型也当作一种“参数”进行传递。泛型指定当前的容器要持有什么类型的对象,让编译器去做检查。
2.2 语法
2.2.1 泛型类
1. class 泛型类名称<类型参数列表>extends 接口/类{......} ——> 定义
2. 泛型类名称<类型参数列表> 引用 = new 泛型类名称<类型参数列表>();——> 运用
//标红部分可不写,如果不写那么就是运用了类型推导
需求:实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以通过成员方法根据下标找到对应的值。接下来我们用泛型类来实现该需求:
2.2.2 泛型方法
类型修饰符 <类型参数列表> 返回值类型 泛型方法名称(参数列表) {......} ——> 定义
泛型方法名称(参数列表)这里运用了类型推导或者
类名.<类型参数列表>泛型方法名称(参数列表) ——> 运用
我们拿以下例子来讲述泛型方法:
2.2.3 注意点
1.类名后的<>代表占位符,表示当前类是一个泛型类。
【规范】类型形参一般使用一个大写字母表示,常用的名称有:
E 表示 Element
K 表示 Key
V 表示 Value
N 表示 Number
T 表示 Type
S, U, V 等等 - 第二、第三、第四个类型
2.不能new泛型类型的数组 意味着:
T[] arr = new T[]; ——> 错误
T[] array = (T[])new Object[10]; ——> 正确
2.3 泛型是如何编译的
2.3.1 类型检查
我们会进行类型检查,检查参数 能否 赋值给e(检查 参数类型 是否和 <>中的类型 一致)
2.3.2 类型擦除
核心思想:Java 泛型只在编译时存在,运行时会被擦除成原始类型。泛型信息在编译时被擦除,运行时统一替换为原始类型(如Object或边界类型),并自动插入必要的类型转换。
在编译完成之后,泛型便被擦除,这时候我们就会从上面的情况变成下面的情况
2.3.3 总结
泛型的核心思想是「类型参数化」—— 将类型作为参数,在编译时确定,既保证了类型安全,又实现了代码复用。
2.4 泛型上界 和 泛型下界
2.4.1泛型边界
所谓泛型边界,就是对传入的类型变量进行约束。接下来我们具体举例说明:
2.4.2 泛型上界
泛型上界规定了作为参数传过来的类型参数必须是指定类型参数的本类或子类
2.4.3 泛型下界
泛型下界规定了作为参数传过来的类型参数必须是指定类型参数的本类或父类
在这里,下界我们不进行讲解,等到集合框架学完再进行学习补充
