从概念上讲,Java 字符串就是 Unicode 字符序列。Java 没有内置的字符串类型,而是在标准 Java 类库中提供了一个预定义类,很自然地叫做 String 。每个用双引号括起来的字符串都是 String 类的一个实例:
String e = ""; // an empty string
String greeting = "Hello";
子串
String 类的 substring 方法可以从一个较大的字符串提取出一个子串。例如:
String greeting = "Hello";
String s = greeting.substring(0, 3); // Hel
substring 方法的第二个参数是不想复制的第一个位置。上面的例子中,在 substring 中从 0 开始计数,直到 3 为止,但不包含 3 。
另外 substring 非常容易计算子串。s.substring(a,b)的长度为 b-a 。
拼接
Java 允许使用 + 号连接(拼接)两个字符串。当将一个字符串与一个非字符串的值进行拼接时,后者被转换成字符串。
int age = 13;
String rating = "PG" + age; // PG13
如果需要把多个字符串放在一起,用一个定界符分隔,可以使用静态 join 方法:
String all = String.join(" / ", "S", "M", "L", "XL") // S / M / L / XL
不可变字符串
String 类没有提供用于修改字符串的方法。所以你能做的只能是赋值:
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()
方法检测两个字符串是否相等。
"Hello".equals(greeting) // false
如果想要检测两个字符串是否相等,并且不区分大小写,可以使用 s.qeualsIgnoreCase
方法
不要使用 ==
来检测两个字符串是否相等!==
比较的是两个字符串是否位于同一内存,而不是比较字符串的内容。
String greeting = "Hello"; //initialize greeting to a string
if (greeting == "Hello")...
// probably true
if (greeting.substring(0, 3) == "Hel")//
// probably false
如果虚拟机始终将相同的字符串共享,就可以使用 ==
运算符检测是否相等。但实际上只有字符串常量是共享的,而 +
或 substring
等操作产生的结果并不是共享的。因此,千万不要使用 ==
运算符测试字符串的相等性,以免在程序中出现糟糕的 bug 。从表面上看,这种 bug 很像随机产生的间歇性错误。
C++ 中的 string 之所以可以使用 == 进行相等性检测,是因为,string 类重载了 == 运算符,而 Java ,没有采用这种方式。
空串和 null 串
空串是一个 Java 对象,有自己的串长度(0)和内容(空)。
// 判断字符串是否为空
if (str.leng() == 0)
if (str.equals(""))
String 变量还可以存放一个特殊的值为 null ,表明目前没有任何对象与该变量关联。
// 判断字符串是否为 null
if (str == null)
检查一个字符串既不是 null 也不为空串。
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 类就可以避免这个问题的发生。
// 构建一个空的字符串构建器
StringBuilder builder = new StringBuilder();
// 往构建器里面添加内容
builder.append('x');
builder.append("yikang");
// 调用 toString 方法来得到 String 对象
String completedString = builder.toString();
在 JDK5.0 中引入 StringBuilder 类。这个类的前身是 StringBuffer ,其效率稍有些低,但允许采用多线程的方式执行添加或删除字符的操作。如果所有字符串在一个单线程中编辑(通常都是这样),则应该用 StringBuilder 替代它。这两个类的 API 是相同的。