Java包装类详解
一、包装类的作用
包装类(Wrapper Classes)是Java中一种特殊的类,它们的主要作用是:
1. 基本数据类型与对象的桥梁
- 将基本数据类型(如int, double等)包装成对象
- 使得基本数据类型可以像对象一样操作
2. 集合框架的必要性
- Java集合框架(如ArrayList, HashMap等)只能存储对象
- 包装类让基本数据类型可以存储在集合中
3. 提供实用方法
- 提供类型转换、比较等实用方法
- 如字符串与基本类型的相互转换
4. 支持null值
- 基本数据类型不能为null,包装类可以表示"无值"状态
二、八种基本类型的包装类
基本类型 | 包装类 | 继承关系 |
---|---|---|
byte | Byte | extends Number |
short | Short | extends Number |
int | Integer | extends Number |
long | Long | extends Number |
float | Float | extends Number |
double | Double | extends Number |
char | Character | 直接继承Object |
boolean | Boolean | 直接继承Object |
三、包装类的核心特性
1. 自动装箱与拆箱(Auto-boxing & Unboxing)
// JDK 1.5之前的手动装箱拆箱
Integer i1 = Integer.valueOf(100); // 手动装箱
int x = i1.intValue(); // 手动拆箱
// JDK 1.5之后的自动装箱拆箱
Integer i2 = 100; // 自动装箱:编译器转换为 Integer.valueOf(100)
int y = i2; // 自动拆箱:编译器转换为 i2.intValue()
2. 常量池与缓存机制
public class WrapperExample {
public static void main(String[] args) {
// Integer缓存范围:-128 ~ 127
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true,使用缓存
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false,超出缓存范围,新建对象
// 手动创建对象,不使用缓存
Integer e = new Integer(100);
Integer f = new Integer(100);
System.out.println(e == f); // false,不同对象
}
}
四、常用方法详解
Integer类常用方法
public class IntegerMethods {
public static void main(String[] args) {
// 构造方法
Integer i1 = new Integer(100);
Integer i2 = new Integer("200");
// 静态方法:字符串转int
int num1 = Integer.parseInt("123");
int num2 = Integer.parseInt("FF", 16); // 按16进制解析
// 静态方法:int转字符串
String s1 = Integer.toString(456);
String s2 = Integer.toBinaryString(10); // 二进制字符串
String s3 = Integer.toHexString(255); // 十六进制字符串
// 比较方法
int compare = Integer.compare(10, 20); // -1 (10 < 20)
// 数值提取
int value = i1.intValue();
double dValue = i1.doubleValue();
// 常量
System.out.println("MAX_VALUE: " + Integer.MAX_VALUE);
System.out.println("MIN_VALUE: " + Integer.MIN_VALUE);
System.out.println("SIZE: " + Integer.SIZE); // 32位
}
}
Character类常用方法
public class CharacterMethods {
public static void main(String[] args) {
char ch = 'A';
// 判断字符类型
System.out.println(Character.isLetter(ch)); // true
System.out.println(Character.isDigit(ch)); // false
System.out.println(Character.isWhitespace(' '));// true
System.out.println(Character.isUpperCase(ch)); // true
System.out.println(Character.isLowerCase(ch)); // false
// 字符转换
System.out.println(Character.toLowerCase(ch)); // 'a'
System.out.println(Character.toUpperCase('b')); // 'B'
// 获取数值
System.out.println(Character.getNumericValue('9')); // 9
}
}
五、包装类的实际应用
1. 在集合中的使用
import java.util.*;
public class CollectionExample {
public static void main(String[] args) {
// List只能存储对象,不能存储基本类型
List<Integer> list = new ArrayList<>();
list.add(1); // 自动装箱
list.add(2);
list.add(3);
int first = list.get(0); // 自动拆箱
// Map中使用包装类
Map<String, Integer> map = new HashMap<>();
map.put("age", 25); // 自动装箱
map.put("score", 95);
int age = map.get("age"); // 自动拆箱
}
}
2. 处理可能为null的数值
public class NullHandling {
public static void processScore(Integer score) {
if (score == null) {
System.out.println("成绩未录入");
} else {
System.out.println("成绩: " + score);
}
}
public static void main(String[] args) {
processScore(95); // 成绩: 95
processScore(null); // 成绩未录入
}
}
3. 类型转换工具
public class TypeConversion {
public static void main(String[] args) {
// 字符串与基本类型的转换
String numberStr = "123.45";
double number = Double.parseDouble(numberStr);
String boolStr = "true";
boolean flag = Boolean.parseBoolean(boolStr);
// 基本类型转字符串
String intStr = Integer.toString(100);
String doubleStr = Double.toString(3.14);
// 更简洁的方式(推荐)
String intStr2 = String.valueOf(100);
String doubleStr2 = String.valueOf(3.14);
}
}
六、注意事项和最佳实践
1. 空指针异常
public class NullPointerExample {
public static void main(String[] args) {
Integer num = null;
// 自动拆箱时会抛出NullPointerException
try {
int value = num; // NullPointerException
} catch (NullPointerException e) {
System.out.println("捕获到空指针异常");
}
// 安全的处理方式
if (num != null) {
int safeValue = num;
}
}
}
2. 比较时的注意事项
public class ComparisonExample {
public static void main(String[] args) {
Integer a = 100;
Integer b = 100;
Integer c = 200;
Integer d = 200;
System.out.println(a == b); // true (在缓存范围内)
System.out.println(c == d); // false (超出缓存范围)
// 正确的比较方式:使用equals()或intValue()
System.out.println(a.equals(b)); // true
System.out.println(c.equals(d)); // true
System.out.println(a.intValue() == b.intValue()); // true
}
}
3. 性能考虑
public class PerformanceExample {
public static void main(String[] args) {
// 不推荐的写法:频繁创建包装对象
long startTime = System.currentTimeMillis();
Long sum = 0L;
for (int i = 0; i < 1000000; i++) {
sum += i; // 每次循环都创建新的Long对象
}
long endTime = System.currentTimeMillis();
System.out.println("包装类耗时: " + (endTime - startTime) + "ms");
// 推荐的写法:使用基本类型
startTime = System.currentTimeMillis();
long sum2 = 0L;
for (int i = 0; i < 1000000; i++) {
sum2 += i; // 直接操作基本类型
}
endTime = System.currentTimeMillis();
System.out.println("基本类型耗时: " + (endTime - startTime) + "ms");
}
}
七、总结
包装类在Java中扮演着重要的角色:
- 桥梁作用:连接基本类型和对象世界
- 功能扩展:为基本类型提供丰富的操作方法
- 集合支持:使得基本类型可以存储在集合框架中
- null支持:可以表示"无值"状态
在使用包装类时要注意:
- 合理使用自动装箱拆箱
- 注意缓存机制的范围
- 警惕空指针异常
- 在性能敏感的场景考虑使用基本类型
包装类是Java语言设计中一个优雅的解决方案,它既保留了基本类型的高效性,又提供了面向对象的灵活性。
Java包装类缓存机制详解
一、缓存范围概述
Java为部分包装类提供了缓存机制,主要是为了提高性能和减少内存占用。
各包装类的缓存范围
包装类 | 缓存范围 | 备注 |
---|---|---|
Byte | -128 ~ 127 (全部) | 所有byte值都在缓存范围内 |
Short | -128 ~ 127 | |
Integer | -128 ~ 127 | 最常用的缓存 |
Long | -128 ~ 127 | |
Character | 0 ~ 127 (ASCII字符) | |
Boolean | TRUE, FALSE (两个值都缓存) | |
Float | 无缓存 | |
Double | 无缓存 |
二、缓存机制源码分析
Integer缓存源码
public final class Integer extends Number implements Comparable<Integer> {
// 内部缓存类
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// 默认上限是127
int h = 127;
// 可以通过JVM参数调整上限
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// 最大不能超过Integer.MAX_VALUE - (-low) -1
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch(NumberFormatException nfe) {
// 忽略配置错误
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {}
}
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
}
三、缓存范围验证示例
1. Integer缓存验证
public class IntegerCacheDemo {
public static void main(String[] args) {
// 在缓存范围内 (-128 ~ 127)
Integer a = 127;
Integer b = 127;
Integer c = -128;
Integer d = -128;
System.out.println("=== 缓存范围内 ===");
System.out.println("127 == 127: " + (a == b)); // true
System.out.println("-128 == -128: " + (c == d)); // true
// 超出缓存范围
Integer e = 128;
Integer f = 128;
Integer g = -129;
Integer h = -129;
System.out.println("=== 超出缓存范围 ===");
System.out.println("128 == 128: " + (e == f)); // false
System.out.println("-129 == -129: " + (g == h)); // false
// 使用valueOf方法(自动装箱调用此方法)
Integer i = Integer.valueOf(100);
Integer j = Integer.valueOf(100);
Integer k = Integer.valueOf(200);
Integer l = Integer.valueOf(200);
System.out.println("=== 使用valueOf方法 ===");
System.out.println("100 == 100: " + (i == j)); // true
System.out.println("200 == 200: " + (k == l)); // false
}
}
2. 各包装类缓存对比
public class AllWrapperCacheDemo {
public static void main(String[] args) {
System.out.println("=== Byte缓存测试 ===");
Byte b1 = 127;
Byte b2 = 127;
Byte b3 = -128;
Byte b4 = -128;
System.out.println("Byte 127 == 127: " + (b1 == b2)); // true
System.out.println("Byte -128 == -128: " + (b3 == b4)); // true
System.out.println("\n=== Short缓存测试 ===");
Short s1 = 127;
Short s2 = 127;
Short s3 = 128;
Short s4 = 128;
System.out.println("Short 127 == 127: " + (s1 == s2)); // true
System.out.println("Short 128 == 128: " + (s3 == s4)); // false
System.out.println("\n=== Long缓存测试 ===");
Long l1 = 127L;
Long l2 = 127L;
Long l3 = 128L;
Long l4 = 128L;
System.out.println("Long 127 == 127: " + (l1 == l2)); // true
System.out.println("Long 128 == 128: " + (l3 == l4)); // false
System.out.println("\n=== Character缓存测试 ===");
Character c1 = 127;
Character c2 = 127;
Character c3 = 128;
Character c4 = 128;
System.out.println("Character 127 == 127: " + (c1 == c2)); // true
System.out.println("Character 128 == 128: " + (c3 == c4)); // false
System.out.println("\n=== Boolean缓存测试 ===");
Boolean bool1 = true;
Boolean bool2 = true;
Boolean bool3 = false;
Boolean bool4 = false;
System.out.println("Boolean true == true: " + (bool1 == bool2)); // true
System.out.println("Boolean false == false: " + (bool3 == bool4)); // true
}
}
四、自定义缓存上限
通过JVM参数调整Integer缓存上限
public class CustomIntegerCache {
public static void main(String[] args) {
// 运行前添加JVM参数:-Djava.lang.Integer.IntegerCache.high=500
System.out.println("测试自定义缓存上限");
// 原本超出缓存的范围,现在在缓存内
Integer a = 300;
Integer b = 300;
Integer c = 500;
Integer d = 500;
Integer e = 501;
Integer f = 501;
System.out.println("300 == 300: " + (a == b)); // 如果设置了high=500,则为true
System.out.println("500 == 500: " + (c == d)); // 如果设置了high=500,则为true
System.out.println("501 == 501: " + (e == f)); // false,超出缓存上限
}
}
运行命令:
java -Djava.lang.Integer.IntegerCache.high=500 CustomIntegerCache
五、缓存机制的实际影响
1. 性能测试
public class CachePerformanceTest {
public static void main(String[] args) {
int iterations = 10_000_000;
// 测试缓存范围内的性能
long startTime = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
Integer value = i % 256 - 128; // 保持在缓存范围内
}
long cacheTime = System.currentTimeMillis() - startTime;
// 测试超出缓存范围的性能
startTime = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
Integer value = i + 1000; // 超出缓存范围
}
long noCacheTime = System.currentTimeMillis() - startTime;
System.out.println("缓存范围内耗时: " + cacheTime + "ms");
System.out.println("超出缓存范围耗时: " + noCacheTime + "ms");
System.out.println("性能差异: " + (noCacheTime - cacheTime) + "ms");
}
}
2. 内存占用分析
public class MemoryUsageAnalysis {
public static void main(String[] args) {
// 创建大量Integer对象
int size = 10000;
Integer[] cachedIntegers = new Integer[size];
Integer[] nonCachedIntegers = new Integer[size];
// 使用缓存范围内的值
for (int i = 0; i < size; i++) {
cachedIntegers[i] = i % 256 - 128; // 缓存范围内
}
// 使用超出缓存范围的值
for (int i = 0; i < size; i++) {
nonCachedIntegers[i] = i + 1000; // 超出缓存范围
}
// 检查对象重用情况
int cachedReuse = 0;
for (int i = 1; i < size; i++) {
if (cachedIntegers[i] == cachedIntegers[i-1]) {
cachedReuse++;
}
}
int nonCachedReuse = 0;
for (int i = 1; i < size; i++) {
if (nonCachedIntegers[i] == nonCachedIntegers[i-1]) {
nonCachedReuse++;
}
}
System.out.println("缓存范围内对象重用次数: " + cachedReuse);
System.out.println("超出缓存范围对象重用次数: " + nonCachedReuse);
}
}
六、最佳实践和注意事项
1. 正确的比较方式
public class CorrectComparison {
public static void main(String[] args) {
Integer a = 100;
Integer b = 100;
Integer c = 200;
Integer d = 200;
// 错误的比较方式(依赖缓存)
System.out.println("a == b: " + (a == b)); // true,但依赖缓存
System.out.println("c == d: " + (c == d)); // false,超出缓存
// 正确的比较方式
System.out.println("a.equals(b): " + a.equals(b)); // true
System.out.println("c.equals(d): " + c.equals(d)); // true
// 或者使用intValue()
System.out.println("a.intValue() == b.intValue(): " + (a.intValue() == b.intValue())); // true
// 最佳实践:使用Objects.equals()
System.out.println("Objects.equals(a, b): " + java.util.Objects.equals(a, b)); // true
System.out.println("Objects.equals(c, d): " + java.util.Objects.equals(c, d)); // true
}
}
2. 在集合中的使用
import java.util.*;
public class CollectionWithCache {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
// 添加大量数据,观察内存使用
for (int i = 0; i < 1000; i++) {
// 使用缓存范围内的值可以节省内存
list.add(i % 256 - 128);
}
// 检查列表中对象的唯一性
Set<Integer> uniqueObjects = new HashSet<>();
for (Integer num : list) {
uniqueObjects.add(num);
}
System.out.println("列表大小: " + list.size());
System.out.println("唯一对象数量: " + uniqueObjects.size());
System.out.println("对象重用率: " + (1 - (double)uniqueObjects.size() / list.size()));
}
}
七、总结
缓存机制的关键点:
- 范围固定:默认-128到127,Integer可通过JVM参数调整上限
- 性能优化:避免重复创建常用数值的包装对象
- 内存节省:相同数值共享对象引用
- 比较陷阱:
==
比较在缓存范围内外表现不同
开发建议:
- 始终使用
equals()
方法比较包装类对象 - 了解缓存范围,在性能敏感场景合理利用
- 避免依赖缓存机制进行对象比较
- 在大量使用包装类时考虑缓存范围对内存的影响
缓存机制是Java为了平衡性能和面向对象特性而设计的巧妙方案,理解其原理有助于编写更高效、健壮的代码。