1. Java包装类的前世今生
Java作为一门面向对象的编程语言,其设计理念是一切皆对象。但基本数据类型(如int、double等)却并非对象,这在实际开发中带来了诸多不便。为了解决这个问题,Java引入了包装类(Wrapper Class)的概念。
1.1 八大基本类型与对应包装类
Java为每种基本数据类型都提供了对应的包装类:
- byte → Byte
- short → Short
- int → Integer
- long → Long
- float → Float
- double → Double
- char → Character
- boolean → Boolean
这些包装类都位于java.lang包中,使用时无需额外导入。以Integer为例,它不仅仅是对int的简单封装,还提供了许多实用的方法:
java复制Integer num = Integer.valueOf("123"); // 字符串转Integer
int value = num.intValue(); // Integer转int
String binary = Integer.toBinaryString(255); // 十进制转二进制
1.2 自动装箱与拆箱机制
JDK1.5引入了自动装箱(Autoboxing)和拆箱(Unboxing)特性,大大简化了包装类的使用:
java复制// 自动装箱
Integer a = 100; // 编译器自动转换为Integer.valueOf(100)
// 自动拆箱
int b = a; // 编译器自动转换为a.intValue()
但这里有个隐藏的坑:自动装箱使用的是Integer.valueOf()方法,而该方法对-128~127之间的数字做了缓存:
java复制Integer x = 127;
Integer y = 127;
System.out.println(x == y); // true
Integer m = 128;
Integer n = 128;
System.out.println(m == n); // false
提示:包装类比较应该使用equals()方法而非==运算符,除非你明确知道自己在做什么。
1.3 包装类的典型应用场景
- 集合框架:Java集合类如ArrayList只能存储对象,不能存储基本类型
- 泛型编程:泛型类型参数必须是类类型
- 数据库操作:数据库字段可能为NULL,需要用包装类表示
- 方法参数:需要区分"未设置"和"设置为0"的情况
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 泛型深度解析
泛型是Java5引入的最重要特性之一,它让类型也能作为参数传递,大大增强了代码的类型安全性和重用性。
2.1 泛型的基本语法
java复制// 泛型类
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
// 使用
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
String value = stringBox.getContent(); // 无需强制类型转换
2.2 泛型擦除机制
Java的泛型是通过类型擦除(Type Erasure)实现的,这意味着泛型信息只存在于编译期,运行时会被擦除。这是为了保持与老版本Java的兼容性。
java复制// 编译前
List<String> list = new ArrayList<>();
list.add("test");
String s = list.get(0);
// 编译后(经过类型擦除)
List list = new ArrayList();
list.add("test");
String s = (String) list.get(0); // 强制类型转换
这种设计带来了一个限制:不能创建泛型数组。因为数组在运行时需要知道确切的类型信息。
2.3 泛型通配符
Java泛型提供了三种通配符:
<?>无界通配符<? extends T>上界通配符<? super T>下界通配符
java复制// 上界通配符示例
public static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number num : list) {
sum += num.doubleValue();
}
return sum;
}
// 可以传入List<Integer>, List<Double>等
注意:PECS原则(Producer Extends, Consumer Super)是使用通配符的重要指南:
- 当需要从数据结构获取数据(生产者)时,使用extends
- 当需要向数据结构存入数据(消费者)时,使用super
3. 集合框架中的泛型应用
Java集合框架是泛型应用最广泛的领域之一。理解集合与泛型的结合使用,对编写类型安全的代码至关重要。
3.1 常见集合类的泛型声明
java复制List<String> stringList = new ArrayList<>();
Map<Integer, String> idToNameMap = new HashMap<>();
Set<Double> uniqueValues = new HashSet<>();
3.2 集合排序与泛型
Collections.sort()方法很好地展示了泛型的威力:
java复制public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
这个声明表示:
- T必须实现Comparable接口
- Comparable的类型参数可以是T或其父类
- 方法参数是List
3.3 类型安全的集合操作
泛型集合可以避免许多运行时错误:
java复制// 非泛型集合(Java5之前)
List list = new ArrayList();
list.add("string");
list.add(123); // 编译通过,但可能导致运行时错误
// 泛型集合
List<String> stringList = new ArrayList<>();
stringList.add("string");
// stringList.add(123); // 编译错误
4. 高级泛型技巧与陷阱
4.1 泛型方法
不仅类可以泛型化,方法也可以:
java复制public static <T> T getFirst(List<T> list) {
if (list == null || list.isEmpty()) {
return null;
}
return list.get(0);
}
4.2 泛型与反射
由于类型擦除,运行时获取泛型信息需要特殊技巧:
java复制public class GenericType<T> {
private Class<T> type;
public GenericType(Class<T> type) {
this.type = type;
}
public Class<T> getType() {
return type;
}
}
// 使用
GenericType<String> gt = new GenericType<>(String.class);
Class<?> type = gt.getType(); // 获取String.class
4.3 泛型数组的替代方案
由于不能直接创建泛型数组,可以使用以下替代方案:
java复制// 方案1:使用Object数组然后强制转换
@SuppressWarnings("unchecked")
T[] array = (T[]) new Object[size];
// 方案2:使用ArrayList代替数组
List<T> list = new ArrayList<>();
// 方案3:传入数组工厂
public static <T> T[] toArray(Collection<T> c, IntFunction<T[]> generator) {
return c.stream().toArray(generator);
}
// 使用
String[] strings = toArray(list, String[]::new);
4.4 泛型与可变参数
可变参数方法中的泛型需要特别注意:
java复制@SafeVarargs
public static <T> List<T> asList(T... elements) {
List<T> list = new ArrayList<>();
for (T element : elements) {
list.add(element);
}
return list;
}
@SafeVarargs注解告诉编译器这个可变参数方法的使用是类型安全的,避免产生"unchecked"警告。
在实际项目中,我经常遇到需要处理泛型边界的情况。比如设计一个缓存系统时,我们可能希望缓存键可以是任意类型,但值必须是特定类型或其子类。这时就可以使用泛型边界:
java复制public class Cache<V extends Cacheable> {
private Map<String, V> cacheMap = new HashMap<>();
public void put(String key, V value) {
cacheMap.put(key, value);
}
public V get(String key) {
return cacheMap.get(key);
}
}
其中Cacheable是一个标记接口,确保只有实现了该接口的类才能被缓存。这种设计既保证了灵活性,又确保了类型安全。
