杰瑞科技汇

Java String构造函数有哪些陷阱?

String 类在 Java 中是一个非常重要的不可变类,它提供了多种构造函数来从不同的数据源创建字符串对象。

Java String构造函数有哪些陷阱?-图1
(图片来源网络,侵删)

Java String 的构造函数主要用于:

  1. 从字符数组 创建字符串。
  2. 从字节数组 创建字符串(需要指定字符编码)。
  3. 直接复制 另一个字符串的内容。
  4. 从部分字符/字节数组 创建字符串。
  5. 拼接其他对象 的字符串表示形式。

下面我们逐一详细介绍最常用和最重要的构造函数。


String() - 默认构造函数

这个构造函数创建一个空字符串对象。

String emptyStr = new String();
System.out.println("空字符串的长度: " + emptyStr.length()); // 输出: 0
System.out.println("空字符串的内容: '" + emptyStr + "'");  // 输出: ''

注意:虽然可以这样做,但在实际开发中,直接使用字符串字面量 来创建空字符串更为常见和高效,因为 Java 会对字符串字面量进行池化。

Java String构造函数有哪些陷阱?-图2
(图片来源网络,侵删)
String anotherEmptyStr = ""; // 更推荐的方式

String(char[] value) - 从字符数组构造

这是最常用的构造函数之一,它将一个字符数组中的所有元素按顺序组合成一个新字符串。

char[] chars = {'J', 'a', 'v', 'a'};
String str = new String(chars);
System.out.println(str); // 输出: Java

String(char[] value, int offset, int count) - 从字符数组部分构造

这是上一个构造函数的扩展版本,它允许你从字符数组的指定 offset(偏移量)位置开始,取 count 个字符来创建新字符串。

  • value: 源字符数组。
  • offset: 开始拷贝的索引位置(从0开始)。
  • count: 要拷贝的字符数量。
char[] chars = {'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'};
// 从索引 6 开始,取 5 个字符
String str = new String(chars, 6, 5);
System.out.println(str); // 输出: World

边界检查offsetcount 超出数组范围,会抛出 IndexOutOfBoundsException 异常。


String(byte[] bytes) - 从字节数组构造

这个构造函数非常特殊,它使用平台的默认字符集(Charset)将字节数组解码成字符串。由于不同平台的默认字符集可能不同(如 Windows 可能是 GBK,Linux/macOS 通常是 UTF-8),这可能导致程序在不同环境下产生不一致的结果,因此应谨慎使用。

Java String构造函数有哪些陷阱?-图3
(图片来源网络,侵删)
// 假设平台默认字符集是 UTF-8
byte[] utf8Bytes = {'H', 'e', 'l', 'l', 'o'}; // 这些字节在 UTF-8 中对应 'H', 'e', 'l', 'l', 'o'
String str1 = new String(utf8Bytes);
System.out.println(str1); // 输出: Hello
// 假设平台默认字符集是 GBK
byte[] gbkBytes = {-60, -29, -70, -61}; // 这两个字节在 GBK 编码中对应 '中'
String str2 = new String(gbkBytes);
System.out.println(str2); // 输出: 中 (在默认 GBK 平台上)

String(byte[] bytes, String charsetName) - 从字节数组构造(指定编码)

这是最安全、最推荐的从字节数组创建字符串的方式,它允许你明确指定字符编码(如 "UTF-8", "GBK", "ISO-8859-1"),确保程序在任何环境下都能得到一致的结果。

  • bytes: 源字节数组。
  • charsetName: 字符编码的名称。
byte[] utf8Bytes = {-28, -72, -83, -27, -101, -67}; // "你好" 的 UTF-8 编码
// 正确使用 UTF-8 编码
String strCorrect = new String(utf8Bytes, "UTF-8");
System.out.println(strCorrect); // 输出: 你好
// 错误使用 GBK 编码
String strWrong = new String(utf8Bytes, "GBK");
System.out.println(strWrong); // 输出: ä½ å¥½ (乱码)

String(byte[] bytes, int offset, int length, String charsetName) - 从字节数组部分构造

这是上一个构造函数的扩展版本,可以从字节数组的指定位置和长度来创建字符串,并指定字符编码。

byte[] allBytes = {-28, -72, -83, -27, -101, -67, -28, -72, -83}; // "你好你好" 的 UTF-8 编码
// 从索引 3 开始,取 3 个字节
String str = new String(allBytes, 3, 3, "UTF-8");
System.out.println(str); // 输出: 你 (因为 "你" 的UTF-8编码正好是3个字节)

String(String original) - 复制构造函数

这个构造函数会创建一个新字符串,其内容与传入的 original 字符串完全相同。

重要概念:字符串不可变性与字符串池

  • 不可变性:一旦 String 对象被创建,它的内容就不能被修改。
  • 字符串池:为了提高性能和节省内存,JVM 维护了一个特殊的内存区域,叫做“字符串常量池”,当你使用双引号 创建字符串时,JVM 会先检查池中是否已存在相同内容的字符串,如果存在,就直接返回池中的引用;如果不存在,就在池中创建一个新的并返回引用。
String s1 = "hello"; // "hello" 被放入字符串池
String s2 = new String("hello"); // "hello" 已经在池中,但 new 操作符会**在堆内存**中创建一个新的 String 对象
System.out.println(s1 == s2); // false
// s1 指向池中的 "hello"
// s2 指向堆中 newly created 的 "hello"
System.out.println(s1.equals(s2)); // true
// equals() 比较的是内容,内容相同,所以返回 true

new String("original") 的主要作用是强制在堆内存中创建一个新的字符串对象,即使池中已经存在相同内容的字符串。


String(StringBuffer buffer)String(StringBuilder builder) - 从缓冲区构造

这两个构造函数分别从 StringBufferStringBuilder 对象创建字符串,它们会复制缓冲区中的内容来创建一个新的、不可变的 String 对象。

StringBuffer sb = new StringBuffer("Java");
String strFromSb = new String(sb);
System.out.println(strFromSb); // 输出: Java
StringBuilder sbl = new StringBuilder("Python");
String strFromSbl = new String(sbl);
System.out.println(strFromSbl); // 输出: Python

为什么需要它们? StringBufferStringBuilder 是可变的,适用于频繁修改字符串的场景,当你完成了所有的拼接和修改操作,并最终需要一个不可变的字符串时,就可以使用这两个构造函数来生成最终的 String 对象。


String(StringBuilder builder) - 从 StringBuilder 构造

这是 JDK 9 中引入的,与 StringBuffer 的构造函数类似,用于从 StringBuilder 创建字符串。

// JDK 9+
StringBuilder sb = new StringBuilder("Hello");
String str = new String(sb);
System.out.println(str); // 输出: Hello

总结与最佳实践

构造函数 描述 示例 注意事项
String() 创建一个空字符串 new String() 推荐使用
String(char[] value) 从整个字符数组创建字符串 new String(new char[]{'a','b'}) 常用
String(char[] value, int offset, int count) 从字符数组的指定部分创建字符串 new String(chars, 1, 3) 注意 offsetcount 的边界
String(byte[] bytes) 使用默认字符集从字节数组创建字符串 new String(byteArray) 不推荐,因平台依赖可能导致乱码
String(byte[] bytes, String charsetName) 使用指定字符集从字节数组创建字符串 new String(byteArray, "UTF-8") 强烈推荐,保证跨平台一致性
String(String original) 复制一个已存在的字符串,在堆上创建新对象 new String("hello") 区分 (地址) 和 equals() (内容)
String(StringBuffer/StringBuilder) 从可变的字符串缓冲区创建不可变的字符串 new String(new StringBuffer("abc")) 用于在完成字符串拼接后生成最终结果

核心建议

  1. 优先使用字符串字面量:如 String s = "hello";,因为它利用了字符串池,效率更高。
  2. 处理字节数据时,务必指定编码:始终使用 new String(byte[], "charsetName") 的形式,避免使用依赖平台默认编码的 new String(byte[])
  3. 理解 new String() 的作用:它总是创建一个新的对象,即使内容相同,这可以用来绕过字符串池的引用。
分享:
扫描分享到社交APP
上一篇
下一篇