前端视角 Java Web 入门手册 2.2:Java Core ——String

244 阅读7分钟

Java 的 String 和 JavaScript 类非常相似,两者都具备不可变性,并且提供了丰富的方法用于常见的操作

1. 什么是 String 类

在 Java 中String 类用于表示和操作字符串。字符串是由字符序列组成的不可变对象,广泛应用于程序中的各种场景,如用户输入、文件处理、网络通信等。

String greeting = "Hello, World!";

在上面的例子中greeting 就是一个 String 对象,存储了文本 "Hello, World!"

2. String 的创建

在 Java 中,创建 String 对象有多种方式,主要包括使用字面量new 关键字。使用字面量创建的字符串会被存储在字符串常量池中,而使用 new 关键字创建的字符串则是在堆内存中创建新的对象实例

2.1 使用字面量创建

最常见的创建 String 对象的方式是直接使用双引号包围字符序列,这称为字符串字面量

String str1 = "Hello";
String str2 = "Hello"; // 与 str1 指向同一个对象

2.2 使用 new 关键字创建

也可以使用 new 关键字显式创建 String 对象。

String str3 = new String("Hello");
String str4 = new String("Hello"); // str3 和 str4 是不同的对象

2.3 文本块

Java 13 引入了文本块(Text Blocks)功能,这使得编写多行字符串变得更加简单。可以用三个双引号 """ 来定义多行字符串,这样可以避免在每一行末尾添加 + 号,并且可以更好地维护和阅读长字符串。

String json = """
            {
              "store": {
                "book": [
                  { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 },
                  { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 },
                  { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "price": 8.99 }
                ],
                "bicycle": { "color": "red", "price": 19.95 }
              }
            }
        """;

3.

3. String 的特性

3.1. 不可变性

String 对象在创建后其内容不可更改。这意味着一旦一个 String 实例被创建,它的值就不能被修改。如果对字符串进行任何修改操作,都会生成一个新的 String 对象。

String original = "Hello";
String modified = original.concat(", World!"); // 创建了一个新的 String 对象
System.out.println(original);  // 输出: Hello
System.out.println(modified);  // 输出: Hello, World!

3.2. 字符串常量池

Java 引入了字符串常量池(String Pool),用于优化内存管理和提高性能。当使用字面量创建字符串时,Java 会先检查常量池中是否已经存在相同内容的字符串。如果存在就直接引用该对象;否则才在池中创建一个新的字符串对象。

String a = "Java";
String b = "Java";
System.out.println(a == b); // 输出: true

然而使用 new 关键字创建的字符串不会存在于常量池中,而是创建新的对象实例。

String c = new String("Java");
String d = new String("Java");
System.out.println(c == d); // 输出: false

这种机制不仅节省了内存,还提高了字符串比较的效率,因为在常量池中相同内容的字符串是共享的。

4. 常用的 String 方法

String 类提供了丰富的方法,用于操作和处理字符串

4.1. 长度与字符访问

  • length() :返回字符串的长度。
  • charAt(int index) :返回指定索引处的字符。
  • indexOf(String str) :返回子字符串在字符串中首次出现的位置。
String str = "Hello, World!";
System.out.println("长度: " + str.length()); // 输出: 13
System.out.println("第7个字符: " + str.charAt(6)); // 输出: W
System.out.println("'World' 的位置: " + str.indexOf("World")); // 输出: 7

4.2. 字符串连接

  • concat(String str) :连接两个字符串。
  • + ****运算符:连接字符串。
String str1 = "Hello";
String str2 = ", World!";
String str3 = str1.concat(str2);
String str4 = str1 + str2;
System.out.println(str3); // 输出: Hello, World!
System.out.println(str4); // 输出: Hello, World!

4.3. 子字符串

  • substring(int beginIndex) :返回从指定索引开始到末尾的子字符串。
  • substring(int beginIndex, int endIndex) :返回从指定起始索引到结束索引之间的子字符串。
String str = "Hello, World!";
System.out.println(str.substring(7)); // 输出: World!
System.out.println(str.substring(0, 5)); // 输出: Hello

4.4. 查找与替换

  • contains(CharSequence s) :检查字符串是否包含指定的子字符串。
  • replace(CharSequence target, CharSequence replacement) :替换指定的子字符串。
String str = "Hello, World!";
System.out.println(str.contains("World")); // 输出: true
System.out.println(str.replace("World", "Java")); // 输出: Hello, Java!

4.5. 比较与匹配

  • equals(Object anObject) :比较内容是否相等。
  • equalsIgnoreCase(String anotherString) :忽略大小写比较内容。
  • matches(String regex) :匹配正则表达式。
String str1 = "Java";
String str2 = "java";
System.out.println(str1.equals(str2)); // 输出: false
System.out.println(str1.equalsIgnoreCase(str2)); // 输出: true
System.out.println(str1.matches("[A-Z][a-z]{3}")); // 输出: true

在 Java 中,判断字符串是否相等常用两种方法:== 运算符和 equals() 方法

  • == 运算符****比较的是引用:判断两个 String 对象是否指向同一个内存地址,用于检查是否为同一个对象实例
String a = "Test";
String b = "Test";
String c = new String("Test");
System.out.println(a == b); // 输出: true(同一常量池对象)
System.out.println(a == c); // 输出: false(不同对象)
  • equals() 方法 比较的是内容:判断两个 String 对象的字符序列是否相同,用来检查字符串内容是否一致。
String a = "Test";
String b = "Test";
String c = new String("Test");
System.out.println(a.equals(b)); // 输出: true
System.out.println(a.equals(c)); // 输出: true
  • 当需要比较字符串的内容是否相等时,应使用 equals() 方法,而不是 == 运算符。
  • 仅在需要确认两个引用是否指向同一个对象时,使用 == 运算符。

4.6. 转换方法

  • toUpperCase() :将字符串转换为大写。
  • toLowerCase() :将字符串转换为小写。
  • trim() :去除字符串两端的空白字符。
  • split(String regex) :根据正则表达式分割字符串。
String str = "   Hello, World!   ";
System.out.println(str.toUpperCase()); // 输出:    HELLO, WORLD!   
System.out.println(str.toLowerCase()); // 输出:    hello, world!   
System.out.println(str.trim()); // 输出: Hello, World!
String[] parts = str.trim().split(", ");
for (String part : parts) {
    System.out.println(part);
}
// 输出:
// Hello
// World!

StringBuilder 与 StringBuffer

String 类虽然功能强大,但由于其不可变性,在进行频繁的字符串修改操作时会带来性能问题。为了解决这个问题,Java 提供了 StringBuilderStringBuffer 类,这两个类是可变的字符串类,适用于需要频繁修改字符串内容的场景

StringBuilder 和 StringBuffer 都有一个内部字符数组和一个容量值。当添加新的字符超过当前容量时,内部数组会自动增长,默认初始容量通常为16个字符,可以通过构造方法指定

// 使用 StringBuilder
StringBuilder sb = new StringBuilder("Hello");
sb.append(", World!");
sb.insert(0, "Start: ");
sb.replace(6, 11, "Java");
System.out.println(sb.toString()); // 输出: Start: Hello, Java!

// 使用 StringBuffer
StringBuffer sbf = new StringBuffer("Hello");
sbf.append(", World!");
sbf.insert(0, "Start: ");
sbf.replace(6, 11, "Java");
System.out.println(sbf.toString()); // 输出: Start: Hello, Java!

StringBuilder 和 StringBuffer 都是用来处理字符串的可变对象,但它们之间有以下区别:

  1. 线程安全性:StringBuilder 是非线程安全的,而 StringBuffer 是线程安全的。在多线程环境下,如果有多个线程需要同时操作同一个字符串,应该使用 StringBuffer,以避免线程安全问题
  2. 性能:由于 StringBuffe r需要考虑线程安全问题,所以它的性能通常比 StringBuilder 差。在单线程环境下,建议使用 StringBuilder,能够获得更好的性能
  3. API:StringBuilder 的 API 与 StringBuffer 基本一致,但 StringBuilder 比 StringBuffer 多了一些API,如 indexOf、lastIndexOf、trim、strip 等

5. 最佳实践

5.1. 优先使用字符串字面量

在可能的情况下,使用字符串字面量创建 String 对象,充分利用字符串常量池,减少内存开销。

// 推荐
String str1 = "Hello";
String str2 = "Hello"; // str1 和 str2 指向同一个对象

// 不推荐
String str3 = new String("Hello");
String str4 = new String("Hello"); // str3 和 str4 是不同的对象

5.2. 避免不必要的字符串连接

在需要频繁修改字符串的场景中,避免使用 String 进行连接操作,改用 StringBuilderStringBuffer

// 不推荐
String str = "";
for (int i = 0; i < 1000; i++) {
    str += "a";
}

// 推荐
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append("a");
}
String str = sb.toString();

5.3. 使用 StringBuilder/StringBuffer 进行大量修改

当需要进行大量的字符串修改操作时,使用 StringBuilder(单线程)或 StringBuffer(多线程)来提高性能。

// 使用 StringBuilder
StringBuilder sb = new StringBuilder("Initial");
sb.append(" Modification");
sb.insert(0, "Start: ");
sb.replace(6, 11, "Java");
String result = sb.toString();
System.out.println(result); // 输出: Start: Java Modification

// 使用 StringBuffer
StringBuffer sbf = new StringBuffer("Initial");
sbf.append(" Modification");
sbf.insert(0, "Start: ");
sbf.replace(6, 11, "Java");
String resultBuffer = sbf.toString();
System.out.println(resultBuffer); // 输出: Start: Java Modification

5.4. 字符串不可变性的利用

由于 String 类的不可变性,可以将其用作安全的常量、键(Key)或在多线程环境中安全共享。

// 用作常量
public class Constants {
    public static final String APP_NAME = "MyApplication";
}

// 用作哈希表的键
Map<String, Integer> userMap = new HashMap<>();
userMap.put("Alice", 30);
userMap.put("Bob", 25);