Java集合和数据结构-Java包装类与泛型深度解析

一、包装类基础
1.1 什么是包装类
Java的包装类(Wrapper Classes)是将8种基本数据类型封装成对象的类。由于Java是"一切皆对象"的语言,但基本类型不是对象,因此需要包装类来填补这一鸿沟。
|
基本类型 |
包装类 |
父类 |
|
byte |
Byte |
Number |
|
short |
Short |
Number |
|
int |
Integer |
Number |
|
long |
Long |
Number |
|
float |
Float |
Number |
|
double |
Double |
Number |
|
char |
Character |
Object |
|
boolean |
Boolean |
Object |
核心特性:
-
包装类是不可变的(一旦创建,值不能修改)
-
都是
final类(不能被继承)
二、自动装箱与拆箱原理
2.1 核心结论
|
结论 |
说明 |
|
结论1 |
自动装箱是编译器自动调用 |
|
结论2 |
自动拆箱是编译器自动调用 |
|
结论3 |
|
2.2 代码示例
public class WrapperDemo {
public static void main(String[] args) {
// 自动装箱:编译器转为 Integer.valueOf(100)
Integer a = 100;
// 自动拆箱:编译器转为 a.intValue()
int b = a;
// 缓存陷阱:-128~127内==返回true,超出返回false
Integer x = 127;
Integer y = 127;
System.out.println(x == y); // true(缓存内同一对象)
Integer m = 128;
Integer n = 128;
System.out.println(m == n); // false(不同对象)
System.out.println(m.equals(n)); // true(值相等)
}
}
2.3 空指针风险
Integer nullInt = null;
// 安全:只是声明引用为null
System.out.println(nullInt); // 输出"null"
// 危险:拆箱时触发NPE
// int c = nullInt; // NullPointerException!
// int result = nullInt + 10; // 同样NPE!
记忆口诀:包装类本身可以为 null(安全),但一旦试图把它当作基本类型使用(赋值、运算、比较),就会触发自动拆箱,此时如果为 null 就会 NPE。
三、缓存机制源码解析
3.1 享元模式应用
Integer.valueOf() 使用享元模式(Flyweight Pattern),通过缓存常用对象减少内存开销。
// Integer.valueOf源码逻辑(Java 8)
public static Integer valueOf(int i) {
// i在-128到127之间直接返回缓存数组中的对象
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
// 超出范围则新建对象
return new Integer(i);
}
// 缓存类(静态内部类)
private static class IntegerCache {
static final int low = -128;
static final int high = 127; // 可通过JVM参数调整
static final Integer cache[] = new Integer[(-low) + high + 1];
static {
// 类加载时预填充缓存数组
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(k - 128);
}
}
3.2 各包装类缓存范围
|
包装类 |
缓存范围 |
|
Integer |
-128 ~ 127(可通过 |
|
Byte、Short、Long |
-128 ~ 127 |
|
Character |
0 ~ 127 |
|
Boolean |
TRUE 和 FALSE 两个静态常量 |
四、包装类使用场景
|
场景 |
为什么需要包装类 |
代码示例 |
|
集合存储 |
集合只能存储对象,不能存基本类型 |
|
|
泛型约束 |
泛型参数必须是对象类型 |
|
|
可空值 |
基本类型不能为null,包装类可以 |
|
|
类型转换 |
提供字符串与数字互转的方法 |
|
|
反射操作 |
反射机制操作的是对象 |
|
public class WrapperUsage {
public static void main(String[] args) {
// 场景1:集合必须使用包装类
List<Integer> scores = new ArrayList<>();
scores.add(95); // 自动装箱
// 场景2:泛型必须使用对象类型
Map<String, Integer> ageMap = new HashMap<>();
// 场景3:允许null
Integer dbAge = null; // 数据库查询结果可能为空
// 场景4:字符串转换
int num = Integer.parseInt("100");
// 场景5:进制转换
String binary = Integer.toBinaryString(10); // "1010"
}
}
五、面试陷阱与最佳实践
5.1 四大陷阱
|
陷阱 |
说明 |
示例 |
|
== vs equals |
-128~127范围内 |
|
|
性能损耗 |
频繁装箱拆箱产生大量临时对象 |
循环中使用 |
|
空指针风险 |
拆箱时对象为null会抛出NPE |
|
|
三目运算符陷阱 |
混合使用基本类型和包装类时自动拆箱 |
|
5.2 三目运算符陷阱详解
Integer a = null;
Integer b = true ? a : 0; // 不会报错,结果还是Integer
int c = true ? a : 0; // 抛出NullPointerException!
// 原因:a被拆箱为int,null.intValue()导致NPE
六、泛型基础与优势
6.1 什么是泛型
Java泛型(Generics)是类型参数化的机制,让代码可以适用于多种类型,同时保持编译期类型检查。
6.2 有无泛型对比
集合框架对比
无泛型(JDK 1.4风格):
List list = new ArrayList();
list.add("Hello");
list.add(100); // 编译通过,隐患埋下
// 取值时必须强转
String s = (String) list.get(0);
String s2 = (String) list.get(1); // 运行时ClassCastException!
有泛型:
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(100); // 编译错误!直接阻止
String s = list.get(0); // 无需强转
6.3 泛型核心优势
|
维度 |
无泛型时代 |
有泛型时代 |
重要性 |
|
类型安全 |
运行时ClassCastException |
编译期类型检查 |
提前发现错误 |
|
代码复用 |
为每种类型复制粘贴代码 |
一套泛型代码适配所有类型 |
减少重复代码90% |
|
可读性 |
满屏(Type)强制转换 |
类型声明即约束 |
降低维护成本 |
|
性能 |
装箱拆箱频繁 |
编译期擦除后效率同原始类型 |
零运行时开销 |
|
API设计 |
返回Object |
返回T |
增强封装性 |
6.4 泛型方法示例
public class GenericUtil {
// 泛型方法:类型参数化,一次编写,到处使用
public static <T> void swap(T[] arr, int i, int j) {
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
String[] strs = {"A", "B"};
swap(strs, 0, 1); // T推断为String
Integer[] ints = {1, 2};
swap(ints, 0, 1); // T推断为Integer
}
}
七、泛型声明位置详解
7.1 泛型声明位置总览
|
声明目标 |
语法位置 |
示例 |
|
泛型类 |
类名后 |
|
|
泛型接口 |
接口名后 |
|
|
泛型方法 |
返回值前 |
|
|
泛型构造器 |
构造器名前 |
|
7.2 泛型类 vs 泛型方法
泛型类:
class Box<T> { // <T>在类名后,整个类都认识T
private T value;
public void set(T val) { // 直接用T,无需重复声明
this.value = val;
}
public T get() {
return value;
}
// 静态方法不能直接用类参数T!
// static void foo(T val) {} // 编译错误!
static <E> void foo(E val) {} // 必须自己声明
}
泛型方法:
class Normal {
// <T>在返回值前,作用域仅限本方法
public <T> T method(T val) {
return val;
}
// void返回值,<T>在void前
public static <T> void print(T obj) {
System.out.println(obj);
}
}
7.3 静态方法泛型规则
class MyClass<T> {
// ❌ 错误:静态方法不能直接访问类参数T
// static void bad(T val) {}
// ✅ 正确:静态方法必须自己声明类型参数
static <E> void good(E val) {}
// ⚠️ 合法但不推荐:用相同字母T会造成遮蔽
static <T> void confusing(T val) {}
}
最佳实践:静态方法必须自立门户自己声明 <>,但不要用和类相同的字母。
7.4 记忆口诀
|
口诀 |
解释 |
|
"类在后,方法在前" |
类声明 |
|
"没有返回值,T在void前" |
即使是void, |
|
"谁用谁声明" |
类用泛型→类声明;方法用泛型(类没声明或静态方法)→方法自己声明 |
八、总结
8.1 包装类核心要点
-
8种包装类:Byte、Short、Integer、Long、Float、Double、Character、Boolean
-
自动装箱/拆箱:编译器自动调用
valueOf()和xxxValue() -
缓存机制:-128~127范围内使用缓存对象,超出范围新建对象
-
== vs equals:比较值必须用
equals,==比较的是对象地址 -
NPE风险:拆箱时对象为null会抛出NullPointerException
8.2 泛型核心要点
-
类型参数化:让代码适用于多种类型,保持编译期类型检查
-
类型安全:编译期发现类型错误,避免运行时ClassCastException
-
代码复用:一套泛型代码适配所有类型,减少重复代码
-
零成本抽象:类型擦除机制,运行时不牺牲性能
-
声明位置:类在后(
class Box<T>),方法在前(<T> void method())
8.3 一句话总结
-
包装类:连接基本类型与对象世界的桥梁,掌握自动装箱拆箱和缓存机制是避免生产环境bug的基础。
-
泛型:让Java代码在保持灵活性的同时获得严格的类型约束,是连接"动态语言的便捷"与"静态语言的安全"的桥梁。
本文整理自Java包装类与泛型技术文档,去除重复内容,提炼核心知识点,适合作为面试复习和日常开发参考。
更多推荐



所有评论(0)