杰瑞科技汇

Java字符串构造函数有哪些陷阱?

String 类在 Java 中是一个非常重要的类,它提供了多种构造函数来从不同的数据源创建字符串对象,理解这些构造函数有助于我们更高效、更安全地处理字符串。

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

核心要点

  1. 不可变性:最重要的一点是,String 对象在 Java 中是不可变的,一旦创建,其内容就不能被改变,任何看起来像修改字符串的操作(如 substring(), concat(), replace())实际上都会返回一个新的 String 对象。
  2. 构造函数 vs. 字面量:创建字符串最常见的方式是使用字面量,String str = "hello";,这种方式会尝试将字符串放入字符串常量池中,以提高性能和内存效率,而使用构造函数(如 new String("hello"))则总是会在堆上创建一个新的对象,即使内容与池中已有的字符串相同。

常用的 String 构造函数

以下是 String 类中最常用的一些构造函数,并附有示例说明。

String()

  • 描述:创建一个空的字符串对象。
  • 参数:无
  • 示例
    String emptyStr = new String();
    System.out.println("空字符串的长度: " + emptyStr.length()); // 输出: 0
    System.out.println("空字符串的内容: '" + emptyStr + "'"); // 输出: ''

String(String original)

  • 描述:创建一个新字符串,它是参数字符串 original 的副本,这是最常用的构造函数之一。

  • 参数original - 一个 String 对象。

  • 示例

    Java字符串构造函数有哪些陷阱?-图2
    (图片来源网络,侵删)
    String original = "hello world";
    String copy = new String(original); // 创建 original 的副本
    System.out.println(copy); // 输出: hello world
    System.out.println(original == copy); // 输出: false,因为它们是两个不同的对象

String(char[] value)

  • 描述:通过一个字符数组创建一个新的字符串对象。

  • 参数value - 字符数组。

  • 示例

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

String(char[] value, int offset, int count)

  • 描述:通过字符数组的指定部分(从 offset 开始,长度为 count)创建一个新的字符串。

    Java字符串构造函数有哪些陷阱?-图3
    (图片来源网络,侵删)
  • 参数

    • value: 字符数组。
    • offset: 开始复制的索引位置。
    • count: 要复制的字符数量。
  • 示例

    char[] chars = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'};
    String subString = new String(chars, 6, 5); // 从索引6开始,取5个字符
    System.out.println(subString); // 输出: World

String(byte[] bytes)

  • 描述:使用平台的默认字符集(通常是 UTF-8)将指定的字节数组解码为字符串。

  • 参数bytes - 字节数组。

  • 示例

    byte[] utf8Bytes = "你好".getBytes(StandardCharsets.UTF_8); // 获取UTF-8编码的字节数组
    String fromUtf8Bytes = new String(utf8Bytes);
    System.out.println(fromUtf8Bytes); // 输出: 你好

String(byte[] bytes, int offset, int length)

  • 描述:使用平台的默认字符集,将字节数组的指定部分解码为字符串。

  • 参数

    • bytes: 字节数组。
    • offset: 开始解码的索引位置。
    • length: 要解码的字节数量。
  • 示例

    byte[] allBytes = "Java Programming".getBytes();
    String subBytesString = new String(allBytes, 5, 11); // 从索引5开始,取11个字节
    System.out.println(subBytesString); // 输出: Programm

String(byte[] bytes, String charsetName)

  • 描述:使用指定的字符集将整个字节数组解码为字符串,这在处理不同编码的文本时非常重要。

  • 参数

    • bytes: 字节数组。
    • charsetName: 字符集的名称(如 "UTF-8", "ISO-8859-1")。
  • 示例

    byte[] gbkBytes = "中文".getBytes("GBK"); // 使用GBK编码获取字节数组
    // 如果用错误的字符集解码,会出现乱码
    String wrongDecode = new String(gbkBytes, "UTF-8");
    System.out.println("错误解码: " + wrongDecode); // 可能输出乱码
    // 使用正确的字符集解码
    String correctDecode = new String(gbkBytes, "GBK");
    System.out.println("正确解码: " + correctDecode); // 输出: 中文

String(StringBuffer buffer)

  • 描述:通过一个 StringBuffer 对象创建字符串。

  • 参数buffer - StringBuffer 对象。

  • 示例

    StringBuffer sb = new StringBuffer("Hello, ");
    String fromSb = new String(sb);
    fromSb += "World!";
    System.out.println(fromSb); // 输出: Hello, World!

String(StringBuilder builder)

  • 描述:通过一个 StringBuilder 对象创建字符串。

  • 参数builder - StringBuilder 对象。

  • 示例

    StringBuilder sb = new StringBuilder("Building a ");
    String fromSb = new String(sb);
    fromSb.append("String!");
    System.out.println(fromSb); // 输出: Building a String!

构造函数 vs. 字面量:一个重要的区别

理解构造函数和字面量在创建对象时的区别至关重要。

public class StringExample {
    public static void main(String[] args) {
        // 使用字面量创建
        String s1 = "hello";
        String s2 = "hello";
        // 使用构造函数创建
        String s3 = new String("hello");
        String s4 = new String("hello");
        // == 比较的是对象的内存地址(引用)
        // equals() 比较的是对象的内容
        System.out.println("s1 == s2: " + (s1 == s2)); // true: 字面量会放入常量池,s1和s2指向同一个池中的对象
        System.out.println("s1.equals(s2): " + s1.equals(s2)); // true: 内容相同
        System.out.println("s3 == s4: " + (s3 == s4)); // false: new 总是创建新对象,s3和s4是两个不同的对象
        System.out.println("s3.equals(s4): " + s3.equals(s4)); // true: 内容相同
        System.out.println("s1 == s3: " + (s1 == s3)); // false: s1指向池中的对象,s3指向堆中的新对象
        System.out.println("s1.equals(s3): " + s1.equals(s3)); // true: 内容相同
    }
}

输出:

s1 == s2: true
s1.equals(s2): true
s3 == s4: false
s3.equals(s4): true
s1 == s3: false
s1.equals(s3): true
  • 字面量 ():JVM 会在字符串常量池中查找是否存在相同内容的字符串,如果存在,则直接引用该对象;如果不存在,则创建一个新对象并存入池中,这有助于节省内存。
  • 构造函数 (new String(...))总是在堆内存中创建一个新的 String 对象,无论内容是否已经存在于常量池中。
构造函数 描述 常见使用场景
String() 创建空字符串 初始化一个空的字符串变量。
String(String original) 复制一个现有字符串 需要一个字符串的副本,以确保原始字符串不被意外修改(虽然String本身不可变,但有时是为了逻辑上的隔离)。
String(char[] value) 从字符数组创建 将字符数组内容转换为字符串。
String(char[] value, int offset, int count) 从字符数组子集创建 只需要字符数组的一部分内容。
String(byte[] bytes) 从字节数组(默认编码)创建 处理从文件或网络读取的字节流,并使用系统默认编码。
String(byte[] bytes, String charsetName) 从字节数组(指定编码)创建 处理多语言文本,必须明确指定正确的字符集以避免乱码。
String(StringBuffer/Builder) 从缓冲区创建 StringBufferStringBuilder 的内容转换为不可变的 String

在实际开发中,推荐优先使用字面量来创建字符串,因为它更简洁且性能更好(得益于字符串常量池),只有在需要从其他数据类型(如字节数组、字符数组)显式构造字符串,或者需要强制创建一个新对象时,才应使用 new String() 构造函数。

分享:
扫描分享到社交APP
上一篇
下一篇