1. Java包装类与String类深度解析
作为一名Java开发者,掌握包装类和String类的使用是基本功。今天我将结合多年开发经验,详细解析这两大核心类的原理和使用技巧,帮助大家避开常见的"坑"。
1.1 包装类概述与基本使用
Java的包装类(Wrapper Class)为8种基本数据类型提供了对象表示形式。这种设计主要解决两个问题:
- 让基本类型能参与面向对象的操作(如集合存储)
- 为基本类型提供丰富的操作方法
8种基本类型与包装类对应关系表:
| 基本类型 | 包装类 | 继承关系 |
|---|---|---|
| byte | Byte | Number子类 |
| short | Short | Number子类 |
| int | Integer | Number子类 |
| long | Long | Number子类 |
| float | Float | Number子类 |
| double | Double | Number子类 |
| char | Character | 直接继承Object |
| boolean | Boolean | 直接继承Object |
注意:Character和Boolean不是Number的子类,其他数值型包装类都继承自Number抽象类
基本类型与包装类转换的四种方式:
- 构造方法(JDK9已标记为过时)
java复制Integer i = new Integer(100); // 基本类型→包装类
Integer i2 = new Integer("100"); // 字符串→包装类
- valueOf()静态方法(推荐)
java复制Integer i = Integer.valueOf(100);
Integer i2 = Integer.valueOf("100");
- xxxValue()实例方法
java复制int num = i.intValue(); // 包装类→基本类型
- 自动装箱/拆箱(JDK5+)
java复制Integer i = 100; // 自动装箱
int num = i; // 自动拆箱
1.2 自动装箱的缓存机制与陷阱
Java对常用数值(-128~127)的包装类对象做了缓存优化:
java复制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类在加载时会预先创建256个对象(-128~127)并缓存。当自动装箱的值在这个范围内时,直接返回缓存对象。
开发建议:包装类比较永远使用equals()而不是==
空指针陷阱示例:
java复制Integer i = null;
int num = i; // 运行时抛出NullPointerException
这是因为自动拆箱实际调用的是intValue()方法,对null调用方法必然抛出异常。
1.3 String类的不可变性设计
String类的核心设计特点就是不可变(Immutable),这体现在:
- 存储数据的char[]数组被final修饰
- 类本身被final修饰,防止被继承修改
- 所有修改字符串的方法都返回新对象
String内存结构示例:
java复制String s1 = "hello"; // 字符串常量池
String s2 = new String("hello"); // 堆内存
两种创建方式的区别:
- 双引号创建:检查常量池,不存在则创建
- new创建:强制在堆中新建对象
1.4 String常用方法性能对比
字符串拼接性能测试:
java复制// 测试代码
long start = System.currentTimeMillis();
String result = "";
for(int i=0; i<10000; i++){
result += i; // 每次循环创建新对象
}
long end = System.currentTimeMillis();
System.out.println("+拼接耗时:"+(end-start)+"ms");
// 使用StringBuilder优化
start = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for(int i=0; i<10000; i++){
sb.append(i);
}
end = System.currentTimeMillis();
System.out.println("StringBuilder耗时:"+(end-start)+"ms");
典型输出结果:
code复制+拼接耗时:432ms
StringBuilder耗时:2ms
性能建议:在循环体内拼接字符串务必使用StringBuilder
1.5 字符串查找方法优化技巧
indexOf的常见使用误区:
java复制String str = "hello world";
// 低效写法 - 重复计算字符串长度
for(int i=0; i<str.length(); i++){
if(str.indexOf('o', i) == i){
System.out.println(i);
}
}
// 优化写法
int index = -1;
while((index = str.indexOf('o', index+1)) != -1){
System.out.println(index);
}
contains方法的实现原理:
java复制public boolean contains(CharSequence s) {
return indexOf(s.toString()) >= 0;
}
实际上就是调用indexOf方法,时间复杂度为O(n*m)
1.6 字符串分割的性能陷阱
split方法使用注意事项:
- 正则表达式越复杂,性能开销越大
- 简单分隔优先使用StringTokenizer
- 超长字符串考虑使用substring手动分割
性能对比示例:
java复制String data = "a,b,c,d,e,f,g";
// 方法1:split
String[] arr1 = data.split(",");
// 方法2:StringTokenizer
StringTokenizer st = new StringTokenizer(data, ",");
String[] arr2 = new String[st.countTokens()];
int i = 0;
while(st.hasMoreTokens()){
arr2[i++] = st.nextToken();
}
1.7 字符串编码问题深度解析
getBytes()的编码陷阱:
java复制String str = "中文";
byte[] bytes1 = str.getBytes(); // 使用平台默认编码
byte[] bytes2 = str.getBytes("UTF-8"); // 明确指定编码
System.out.println(bytes1.length); // 可能输出4(GBK)
System.out.println(bytes2.length); // 输出6
关键点:始终明确指定字符编码,避免跨平台问题
编码转换的正确姿势:
java复制String str = "中文";
// 错误示范:可能抛出UnsupportedEncodingException
try {
byte[] bytes = str.getBytes("UTF-8");
String newStr = new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// 正确示范:Java7+可以使用StandardCharsets
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
String newStr = new String(bytes, StandardCharsets.UTF_8);
1.8 字符串常量池的优化技巧
intern()方法的使用场景:
java复制String s1 = new String("hello").intern();
String s2 = "hello";
System.out.println(s1 == s2); // true
实际应用建议:
- 适合大量重复字符串场景
- 会增加方法区内存占用
- 需要权衡CPU和内存开销
字符串常量池位置变化:
- JDK7之前:永久代(方法区)
- JDK7+:移动到堆内存
- JDK8+:元空间(Metaspace)
1.9 实战经验总结
-
包装类使用原则:
- 集合中必须使用包装类
- 避免自动拆箱时的NPE
- 比较使用equals()而非==
-
字符串优化建议:
- 循环拼接用StringBuilder
- 静态字符串用字面量
- 大文本处理考虑字符数组
-
性能关键点:
- 减少不必要的字符串对象创建
- 注意substring的内存泄漏问题(JDK6)
- 合理使用字符串缓存
最后分享一个实际案例:在一次性能优化中,我们将XML解析中的字符串拼接从"+"改为StringBuilder,使处理时间从1200ms降低到150ms。这提醒我们,基础类的正确使用对性能影响巨大。