从概念上讲,Java 字符串就是 Unicode 字符序列。Java 没有内置的字符串类型,而是在标准 Java 类库中提供了一个预定义类,很自然地叫做 String 。每个用双引号括起来的字符串都是 String 类的一个实例:

  1. String e = ""; // an empty string
  2. String greeting = "Hello";

子串

String 类的 substring 方法可以从一个较大的字符串提取出一个子串。例如:

  1. String greeting = "Hello";
  2. String s = greeting.substring(0, 3); // Hel

substring 方法的第二个参数是不想复制的第一个位置。上面的例子中,在 substring 中从 0 开始计数,直到 3 为止,但不包含 3 。
另外 substring 非常容易计算子串。s.substring(a,b)的长度为 b-a 。

拼接

Java 允许使用 + 号连接(拼接)两个字符串。当将一个字符串与一个非字符串的值进行拼接时,后者被转换成字符串。

  1. int age = 13;
  2. String rating = "PG" + age; // PG13

如果需要把多个字符串放在一起,用一个定界符分隔,可以使用静态 join 方法:

  1. String all = String.join(" / ", "S", "M", "L", "XL") // S / M / L / XL

不可变字符串

String 类没有提供用于修改字符串的方法。所以你能做的只能是赋值:

  1. greeting = greeting.substring(0, 3) + "P!"; // Help!

现在 greeting 的值修改为 Help!
由于不能修改 Java 字符串中的字符,所以在 Java 文档中将 String 类对象称为不可变字符串,如同数字 3 永远是数字 3 一样,字符串 "Hello" 永远包含字符 H、e、l、l 和 o 的代码单元序列,而不能修改其中的任何一个字符。当然,可以修改字符串变量 greeting ,让它引用另外一个字符串,这就如同可以将存放 3 的数值变量改成存放 4 一样。
不可变字符串有一个优点:编译器可以让字符串共享。将各种字符串存放在公共的存储池中。字符串变量指向存储池中相应的位置。如果复制一个字符串变量,原始字符串与复制的字符串共享相同的字符。
总而言之,Java 的设计者认为共享带来的高效率远远胜过于提取、拼接字符串所带来的低效率。查看一下程序会发现:很少需要修改字符串,而是往往需要对字符串进行比较。

检测字符串是否相等

使用 s.equals() 方法检测两个字符串是否相等。

  1. "Hello".equals(greeting) // false

如果想要检测两个字符串是否相等,并且不区分大小写,可以使用 s.qeualsIgnoreCase 方法
不要使用 == 来检测两个字符串是否相等!== 比较的是两个字符串是否位于同一内存,而不是比较字符串的内容。

  1. String greeting = "Hello"; //initialize greeting to a string
  2. if (greeting == "Hello")...
  3. // probably true
  4. if (greeting.substring(0, 3) == "Hel")//
  5. // probably false

如果虚拟机始终将相同的字符串共享,就可以使用 == 运算符检测是否相等。但实际上只有字符串常量是共享的,+substring 等操作产生的结果并不是共享的。因此,千万不要使用 == 运算符测试字符串的相等性,以免在程序中出现糟糕的 bug 。从表面上看,这种 bug 很像随机产生的间歇性错误。

C++ 中的 string 之所以可以使用 == 进行相等性检测,是因为,string 类重载了 == 运算符,而 Java ,没有采用这种方式。

空串和 null 串

空串是一个 Java 对象,有自己的串长度(0)和内容(空)。

  1. // 判断字符串是否为空
  2. if (str.leng() == 0)
  3. if (str.equals(""))

String 变量还可以存放一个特殊的值为 null ,表明目前没有任何对象与该变量关联。

  1. // 判断字符串是否为 null
  2. if (str == null)

检查一个字符串既不是 null 也不为空串。

  1. if (str != null && str.length() != 0)

需要注意的是,要先检查 str 不为 null,因为如果在一个 null 值上调用方法,会出现错误。

码点与代码单元

Java 字符串由 char 值序列组成。char 数据类型是一个采用 UTF-16 编码表示 Unicode 码点的代码单元。常用的 Unicode 字符使用一个代码单元就可以表示,而辅助字符需要两个代码单元表示。也就是一个码点可能是一个代码单元,可以能是两个代码单元。
通常我们使用 length() 查看的是 UTF-16 编码表示的代码单元数量。
如果要查看实际长度,即码点数量,可以使用 codePointCount(0, s.length()) 来查看。
还有更多的码点操作,可以去 AIP 文档里面关注 codePoint 前缀的方法

构建字符串

有些时候,需要由较短的字符串构建字符串,例如,按键或来自文件中的单词。采用字符串连接的方式达到此目的效率比较低。每次连接字符串,都会构建一个新的 String 对象,既耗时,又浪费空间。使用 StringBuilder 类就可以避免这个问题的发生。

  1. // 构建一个空的字符串构建器
  2. StringBuilder builder = new StringBuilder();
  3. // 往构建器里面添加内容
  4. builder.append('x');
  5. builder.append("yikang");
  6. // 调用 toString 方法来得到 String 对象
  7. String completedString = builder.toString();

在 JDK5.0 中引入 StringBuilder 类。这个类的前身是 StringBuffer ,其效率稍有些低,但允许采用多线程的方式执行添加或删除字符的操作。如果所有字符串在一个单线程中编辑(通常都是这样),则应该用 StringBuilder 替代它。这两个类的 API 是相同的。