Java 包装类基础
在 Java 中,基本数据类型(如 int、char、double)无法直接作为对象处理。为了解决这个问题,Java 提供了对应的包装类(Wrapper Classes),例如 Integer、Character、Double 等。将基本类型转换为对象的过程称为装箱(boxing),反之则为拆箱(unboxing)。
从 JDK 1.5 开始,Java 引入了自动装箱和拆箱机制。编译器会在需要时自动调用 valueOf() 或 xxxValue() 方法,这大大简化了代码编写,但开发者仍需了解其背后的原理以避免陷阱。
包装类的定义与作用
包装类使得基本数据类型可以作为对象参与泛型操作、集合存储以及方法参数传递。除了 Integer 和 Character 外,其余包装类名称均为对应基本类型的首字母大写形式。
性能与潜在风险
虽然自动转换很方便,但在高性能场景下需留意以下问题:
- 性能开销:频繁装箱会创建大量对象,增加 GC 压力;拆箱涉及方法调用。在循环或高频计算中应尽量避免。
- 空指针异常:包装类是引用类型,可以为 null。若对 null 进行拆箱(如
int value = nullInt;),会抛出 NullPointerException。 - 缓存机制:Integer、Boolean 等类对特定范围的值进行了缓存(如 Integer 为 -128 到 127)。在此范围内使用
==比较可能返回 true,但这依赖于实现细节,不可靠。 - 比较操作:比较两个包装类对象的值时,务必使用
equals()方法,而非==。后者比较的是内存地址。
public class BoxingUnboxingExample {
public static void main(String[] args) {
// 自动装箱:编译器隐式调用 Integer.valueOf(int)
int primitiveInt = 10;
Integer boxedInt = primitiveInt;
// 自动拆箱:编译器隐式调用 boxedInt.intValue()
int unboxedInt = boxedInt;
// 算术运算中的自动拆箱
Integer anotherBoxedInt = 20;
int result = boxedInt + anotherBoxedInt;
// 显式操作(通常不需要,但有助于理解)
Integer explicitlyBoxedInt = Integer.valueOf(primitiveInt);
int explicitlyUnboxedInt = explicitlyBoxedInt.intValue();
System.out.println("Primitive: " + primitiveInt);
System.out.println("Boxed: " + boxedInt);
System.out.println("Result: " + result);
}
}
Java 泛型基础
在引入泛型之前,集合类(如 ArrayList)只能存储 Object 类型。这意味着取出数据时需要强制类型转换,不仅繁琐,还容易引发 ClassCastException。泛型机制允许在编译期检查类型安全性,实现了代码的通用性与复用性。
核心优势
- 类型安全:编译期即可发现类型不匹配错误。
- 消除冗余:无需手动进行强制类型转换。
- 可读性提升:代码意图更明确,维护成本降低。
泛型类与方法
泛型类在类名后指定类型参数(如 <T>),泛型方法则在返回值前指定。编译器会根据上下文自动推断类型参数,减少代码冗余。
// 泛型类定义
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
// 使用泛型类
Box<Integer> integerBox = new Box<>();
integerBox.setContent(100);
Integer content = integerBox.getContent();
泛型通配符
通配符 ? 用于表示未知类型,分为无界、上界(? extends T)和下界(? super T)。
// 上界通配符:可以读取 Number 及其子类,但不能写入(除 null 外)
List<? extends Number> numberList = new ArrayList<>();
// 下界通配符:可以写入 Integer 及其子类,读取结果为 Object
List<? super Integer> integerSuperList = new ArrayList<>();
包装类与泛型的结合
在实际开发中,集合容器要求元素必须是对象类型,因此基本数据类型必须通过包装类才能存入泛型集合。这种结合既保证了类型安全,又支持了丰富的集合操作。
import java.util.ArrayList;
import java.util.List;
public class WrapperGenericsExample {
public static void main(String[] args) {
// 创建泛型集合,自动装箱
List<Integer> integerList = new ArrayList<>();
integerList.add(10);
integerList.add(20);
// 遍历时自动拆箱
for (Integer integer : integerList) {
System.out.println(integer);
}
// 注意:累加时需小心 null 值导致的 NPE
int sum = 0;
for (Integer integer : integerList) {
if (integer != null) {
sum += integer.intValue();
}
}
}
}
泛型进阶:擦除与推断
Java 泛型采用类型擦除策略,即泛型信息仅在编译期存在,运行时会被替换为原始类型(Raw Type)。这意味着在运行时无法直接获取具体的泛型类型参数。
类型擦除示例
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
// 运行时类型已被擦除,无法直接获取 T 的具体类型
// 如需获取,通常需要通过子类化并反射父类的泛型签名
}
}
类型推断
JDK 7 引入的菱形运算符 <> 允许右侧省略类型参数,由编译器根据左侧变量声明推断。JDK 10 的 var 关键字进一步简化了局部变量类型的声明。
import java.util.ArrayList;
import java.util.List;
public class TypeInferenceExample {
public static void main(String[] args) {
// JDK 7+ 菱形语法
List<String> list1 = new ArrayList<>();
// JDK 10+ var 关键字
var list2 = new ArrayList<String>();
// 泛型方法调用时的类型推断
printList(list1);
}
public static <T> void printList(List<T> list) {
for (T element : list) {
System.out.println(element);
}
}
}
实战建议
在工程实践中,包装类与泛型的组合非常常见。以下是几个关键注意点:
- 避免不必要的装箱:在大数据量处理或性能敏感路径上,尽量使用基本类型数组或专用库。
- 防御性编程:处理集合元素时,先判断是否为 null 再进行拆箱操作。
- 统一规范:团队内部应约定好何时使用包装类,何时使用基本类型,保持代码风格一致。
通过合理使用泛型和包装类,我们可以构建出既安全又高效的 Java 应用程序。


