字符串是什么
String 保存的是一串字符,也就是文本,比如一个名字、一句话,或从文件中读到的一行内容。上一页你已经在引用类型的列表里见过 String,这一页我们来真正使用它。字符串字面量用双引号书写(单引号只用于单个 char):
name.length() 返回字符的个数,"Ada" 的结果是 3。这是一次方法调用,所以括号不能省略。
拼接字符串
+ 运算符可以把字符串连在一起。只要 + 的任意一边是字符串,另一边就会自动转换成文本,所以你可以直接混入数字和布尔值,不用做任何额外处理:
有一个坑:+ 是从左往右计算的,所以 "" + 1 + 2 得到 "12"(字符串加 int,再加 int),而 1 + 2 + "" 得到 "3"(两个 int 先相加,再变成文本)。拿不准的时候,就把算术部分用括号括起来。
字符串是不可变的
这是理解 Java 字符串最重要的一点:String 一旦创建就永远不会改变。每个看起来在“修改”字符串的方法,实际上都返回了一个全新的字符串,原字符串保持不变。所以如果你忘了接收返回值,什么都不会发生:
第一次调用算出了 "HELLO",然后马上把它丢掉了。只有第二行把结果重新赋值给 s,才产生了看得见的效果。几乎每个人都会在这里栽一次跟头。
比较字符串
比较两个字符串的内容要用 .equals()。用 == 是新手的经典错误:== 问的是“它们是内存中的同一个对象吗?”,而不是“它们的文本相同吗?”:
这里 a == b 是 false,因为 new String(...) 强制创建了一个单独的对象。比较字符串的值时,一律使用 .equals()(忽略大小写时用 .equalsIgnoreCase())。当某一边可能是 null 时,有个避免程序崩溃的小技巧:在字面量上调用 .equals。即使 input 是 null,"yes".equals(input) 也不会抛出异常。
日常 String 方法
因为字符串不可变,下面这些方法都会返回一个新字符串(或一个值),而不会改变原字符串。这些是你会反复用到的方法:
注意这里的链式调用:s.trim().toUpperCase() 之所以可行,是因为 trim() 返回一个字符串,你可以接着在它上面调用 toUpperCase()。substring 是另一个常用方法,它接收一个起始索引(包含)和一个可选的结束索引(不包含):
索引从零开始,所以 substring(0, 4) 取的是第 0 到第 3 个字符。如果索引超出字符串末尾,就会抛出 StringIndexOutOfBoundsException,这是一种可以提前防范的运行时异常。另外注意,上面的 split(",") 返回的是一个数组,数组的 length 是不带括号的字段,这和字符串的 .length() 方法不同。
在循环中构建字符串
拼接几段文本时用 + 没问题,但在大型循环里这样做会很慢,因为每次 + 都会新建一个字符串并复制全部内容。在循环中组装文本时,请使用 StringBuilder,它直接在缓冲区上修改,最后只生成一次最终的字符串:
append 返回的是同一个 builder,所以可以链式调用。最后调用 .toString() 得到一个普通的 String。偶尔拼接一次时继续用 +;需要累积大量片段时,StringBuilder 才是合适的工具。
下一页:String Format
一旦需要对齐的列、固定的小数位数或补齐位数的数字,拼接就会变得混乱。Java 的 String.format 和 printf 使用带 % 占位符的模板,可以把这些都处理得干净利落,这就是下一页的内容。
常见问题
Java 中如何比较两个字符串?
比较内容用 .equals():字符相同时,a.equals(b) 返回 true。不要用 == 比较字符串的值,因为 == 比较的是对象引用,两个内容相同的字符串也可能返回 false。不区分大小写时用 .equalsIgnoreCase()。
为什么 Java 字符串是不可变的?
String 对象一旦创建就不能再改变。replace、toUpperCase 这类方法返回的是一个新字符串,不会修改原字符串。不可变性让字符串可以安全地共享和缓存,但也意味着你必须接收返回结果:s = s.toUpperCase();。如果调用方法后忽略了返回值,什么都不会发生。
Java 中如何获取字符串的长度?
调用 .length() 方法:"hello".length() 返回 5。注意,在 String 上它是带括号的方法;而数组的 length 是字段,不带括号(arr.length)。