购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

bt2-L 1.5 字符串

字符串是一个字符的序列。在Java中,字符串可以包含任意的Unicode字符。例如,字符串"Java™"或"Java\u2122"由5个字符构成,分别是J、a、v、a和 ™。其中最后一个字符是“U+2122,注册商标”。

1.5.1 拼接

使用 + 运算符可以拼接两个字符串。例如:

String location = "Java";
String greeting = "Hello " + location;

以上两条语句将greeting设置为字符串"Hello Java"。(注意第一个操作数末尾的空格。)

当你将一个字符串与另一个值拼接时,该值将会转换为字符串:

int age = 42;
String output = age + " years";

现在字符串output是"42 years"。

警告: 如果混合使用拼接和加法运算,那么可能会得到意想不到的结果。示例如下。

"Next year, you will be " + age + 1 // Error

首先,字符串拼接age,然后再拼接1,因此,最后得到的结果是"Next year, you will be 421"。在这种情况下,需要使用括号。

"Next year, you will be " + (age + 1) // OK

如果要组合多个字符串,并使用分隔符将他们分隔开,请使用join方法:

String names = String.join(", ", "Peter", "Paul", "Mary");
    // Sets names to "Peter, Paul, Mary"

join方法的第一个参数是分隔符字符串,后面是要拼接的字符串。它们的数量可以是任意多个,你也可以以字符串数组传递参数(数组在1.8节中有介绍)。如果需要连接大量的字符串,那么这种方法的效率会有些低。在这种情况下,请使用StringBuilder来代替join方法:

var builder = new StringBuilder(); 
while (more strings) {
     builder.append(next string);
}
String result = builder.toString();

1.5.2 子串

如果拆分字符串,可以使用substring方法。例如:

String greeting = "Hello, World!";
String location = greeting.substring(7, 12); // Sets location to "World"

substring方法的第一个参数是要提取子串的起始位置,位置从0开始表示。

第二个参数是不包含子串的第一个位置。在以上的示例中,greeting的第12个位置是 !,这个是我们不需要的字符的位置。该方法需要指定一个不需要的字符的位置,这可能看起来很奇怪,但这样做有一个优点:12 – 7将会是这个子串的长度。

有时,你可能希望从一个由分隔符分隔的字符串中提取所有子串。split方法将能够实现这个功能,并返回一个由子串组成的数组。

String names = "Peter, Paul, Mary"; 
String[] result = names.split(", ");
    // An array of three strings ["Peter", "Paul", "Mary"]

这里的分隔符可以是任何正则表达式(参见第9章)。例如,input.split("\\s+") 将在空白处拆分input字符串。

1.5.3 字符串比较

如果要比较两个字符串是否相等,请使用equals方法。例如:

location.equals("World")

当location字符串恰好为"World"时,表达式将返回true。

警告: 永远不要使用 == 运算符来比较字符串。在下面的比较中,仅当location和"World"在内存中是完全相同的对象时才能返回true。

location == "World" // Don’t do that!

即在虚拟机中,每个字符串字面量只用一个实例,因此只有"World" == "World" 才能为true。但如果location是被计算得到的,例如,

String location = greeting.substring(7, 12);

那么结果将会被放置到一个单独的String对象中,location == "World" 将返回false!

与其他任何对象一样,String变量可以是null。null表示这个变量不指向任何对象,甚至不指向一个空字符串。

String middleName = null;

如果要测试一个对象是否为null,可以使用 == 运算符:

if (middleName == null) ...

需要注意的是,null与空字符串""不同。空字符串是长度为零的字符串,而null表示根本不存在任何字符串。

警告: 针对null调用任何方法都会导致“空指针异常”。和所有异常一样,如果你不显式地处理它,该异常会中断程序的运行。

提示: 当将字符串与字符串字面量进行比较时,最好将字符串字面量放在前面,示例如下。

if ("World".equals(location)) ...

这样的优势在于,即使location为null,该测试也能正常工作。

如果在比较两个字符串时需要忽略字符的大小写,可以使用equalsIgnoreCase方法。例如:

"world".equalsIgnoreCase(location);

当location是"World"、"world"或者"WORLD"等情况时,都会返回true。

有时,你可能需要将字符串按顺序排列。调用compareTo方法可以判断两个字符串是否按字典顺序排列:

first.compareTo(second)

如果first在second之前,那么该方法返回一个负整数(不一定是−1);如果first在second之后,则返回正整数(不一定是1);如果两者相等,则返回0。

compareTo方法会依次比较每一个字符,直到其中一个字符串到达末尾,或者两个字符串不匹配。例如,当比较" wor d"和" wor ld"时,前3个字符是匹配的,第4个字符d的Unicode值小于l。因此,"word"字符串在前。所以"word".compareTo("world") 返回−8,该值是d和l的Unicode值之间的差。

这种比较方式对很多人来说可能不是很直观,因为它取决于字符的Unicode值的大小。例如,"blue/green"在"bluegreen"之前,因为字符 / 的Unicode值恰好小于g的Unicode值。

提示: 在对相对比较容易阅读的字符串进行排序时,可以使用支持特定语言排序规则的Collator对象。有关更多信息参见第13章。

1.5.4 数值和字符串的相互转换

要将整数转换为字符串,可以调用静态Integer.toString方法:

int n = 42;
String str = Integer.toString(n); // Sets str to "42"

这个方法也可以有第二个参数,即一个基数(范围为2~36):

String str2 = Integer.toString(n, 2); // Sets str2 to "101010"

注意: 更简单地将整数转换为字符串的方法是用空字符串和整数拼接,例如:"" + n。但是有些人认为这样的代码很不美观,且效率稍低。

相反地,如果要将包含整数的字符串转换成为数值,那么可以使用Integer.parseInt方法:

String str = "101010";
int n = Integer.parseInt(str); // Sets n to 101010

同样地,该方法也可以指定转换基数:

int n2 = Integer.parseInt(str, 2); // Sets n2 to 42

对于浮点数和字符串之间的相互转换,可以使用Double.toString和Double.parseDouble方法:

String str = Double.toString(3.14); // Sets str to "3.14" 
double x = Double.parseDouble(str); // Sets x to 3.14

1.5.5 字符串API

就像你期望的那样,String类定义了大量的方法。表1-4列出了一些经常使用的方法及其功能。

表1-4 String类常用方法

需要注意的是,在Java中,String类是 不可变 (immutable)的。也就是说,String的众多方法中没有一个方法能够修改字符串本身内容。例如,

greeting.toUpperCase()

将返回一个新字符串"HELLO,WORLD!",但并不会改变greeting。同样需要注意的是,有些方法具有CharSequence类型的参数。这是String、StringBuilder和其他字符序列的通用超类。如果需要查询每个String方法的详细描述,请参阅在线Java API文档。在搜索框中输入类名并选择匹配的类型即可得到如图1-4所示的信息(在本例中为java.lang.String)。

图1-4 检索API文档

随后,你将会获得一个记录每个方法的页面,如图1-5所示。当然,如果你碰巧知道某个方法的名称,可以直接在搜索框中输入方法的名称进行检索。

图1-5 API文档中的String方法

本书没有详细介绍API的具体细节,因为很多时候直接浏览API文档会更快捷。如果你不能保证总是可以连接到互联网,那么你可以下载并解压离线文档,进行脱机浏览。

1.5.6 码点和代码单元

Java第一次发布时,就非常自豪地采纳了同样是新生事物的Unicode标准。Unicode标准旨在解决字符编码这个非常棘手的问题。在Unicode之前,有许多互相不兼容的字符编码。以英语为例,有几乎可以作为通用标准的7位ASCII编码标准,该标准为所有英文字母、十进制数字和许多符号分配了介于0~127的编码。在西欧,ASCII还被扩展为8位代码,用来容纳类似ä和é等重音字符。在俄罗斯,ASCII也同样被扩展,俄罗斯使用128~255的位置表示一些斯拉夫字符。在日本,通常使用可变长度编码对英语和日语字符进行编码。此外,还有多种不兼容的中文字符编码也在被广泛使用。总之,在使用不同编码的情况下交换文件是一个很困难的问题。

Unicode通过介于0~65535的唯一的16位编码,来对所有书写系统的每个字符分配唯一的编码,来解决困扰大家已久的字符编码问题。1991年,Unicode 1.0发布,该标准使用了略少于半数的有效65536编码。Java从一开始就被设计成使用16位Unicode字符的系统,这一点对比其他使用传统8位字符编码的编程语言,是一个重大进步。但随后又发生了一些尴尬的事情,即汉字的数量远远超过了之前的预估值,这就迫使Unicode必须使用超过16位的编码方案。

如今,Unicode需要21位进行编码。每个有效的Unicode值称为 码点 (code point),其基本形式为U+与其后的4个或多个十六进制的数字。例如,字符A的码点是U+0041,而表示八元数集合的数学符号 pg27a 的码点是U+1D546。

还有一种更加清楚的方式来表示码点,例如使用int值,但这显然是非常浪费的。Java使用一种变长的编码形式,称为UTF-16,它将所有“经典”的Unicode字符表示为单个16位的值,此外对于所有超过U+FFFF的字符编码,都需要通过一个16位的值组合配对表示,这个16位的值表示一个特殊的代码区域,通常被称为“代理字符”。在UTF-16编码中,字符A可以通过一个char值来表示,记作\u0041;而 pg27a 会被记作一对char值\ud835\udd46。

换句话说,char并不是Unicode字符或码点。它只是一个 代码单元 (code unit),是UTF-16编码中所使用的一个16位的量。

如果你并不使用中国的汉字,并且愿意把 pg27a 等特殊字符抛在脑后的话,那么字符串是一个Unicode字符序列的事情就对你没有太大影响,你当它是一个神话传说就行。在这种情况下,可以这样获得第i个字符:

char ch = str.charAt(i);

也可以这样获取字符串的长度:

int length = str.length();

但是如果你想正确地处理字符串,那么必须工作得更加辛苦一些。例如,要获取Unicode的第i个码点,需要调用:

int codePoint = str.codePointAt(str.offsetByCodePoints(0, i));

码点总数为:

int length = str.codePointCount(0, str.length());

循环提取每一个码点:

int i = 0;
while (i < s.length()) {
     int cp = sentence.codePointAt(i);
     i += Character.charCount(cp);
     ... // Do something with cp
}

或者,也可以使用codePoints方法来生成一个int值的 (stream),这样每个int值都对应一个码点。我们将在第8章中讨论流。你也可以将流转换为一个数组,如:

int[] codePoints = str.codePoints().toArray();

注意: 过去,字符串总是在内部采用UTF-16编码表示,以char值数组的形式来表示。现在,String对象会尽可能地以ISO-8859-1字符的byte数组的形式来表示。未来版本的Java内部可能会改用UTF-8。

1.5.7 文本块

使用文本块语法可以更加方便地提供跨行形式的字符串文本。文本块以"""开头,后面可以直接使用换行符,结尾则使用另一个"""来标记:

String greeting = """ 
Hello
World
""";

greeting字符串包含两个换行符:一个在Hello之后,另一个在World之后。字符串文本中不包含起始的"""标记后的换行符。

如果你不希望在最后一行使用换行符,那么可以将终止标记符"""放在最后一个字符之后:

String prompt = """ 
Hello, my name is Hal. \ 
Please enter your name:""";

在任何一行的末尾,你都可以在行末添加反斜杠作为禁止换行的标志:

String prompt = """
Hello, my name is Hal. \
Please enter your name:""";

这样,字符串内就不包含任何换行符了。

文本块特别适用于一些包含其他语言代码的情况,例如SQL或HTML。只须将其粘贴在一对三重引号之内:

String html = """
<div class="Warning">
    Beware of those who say "Hello" to the world 
</div>
""";

需要注意的是,使用文本块时,你可以不用转义引号。但是,文本块中还是有两种特殊情况需要转义引号:

文本块以引号 结尾

文本块包含3个或更多引号。

遗憾的是,文本块中你仍然需要转义所有反斜杠。

常规字符串中的所有转义序列在文本块中的使用方式都相同。

可以通过删除末尾的空格,或者将Windows系统的换行符(\r\n)更改为更加简单的换行符(\n)的方式来规范文本的换行符。如果你仍旧需要保留末尾的空格,请将最后一个空格转换为\s转义符。以下字符串就以两个空格结尾:

String prompt = """
Hello, my name is Hal. 
Please enter your name: \s""";

对于前导空格来说,事情就更加复杂了。考虑一个典型的变量声明,需要从左边距进行缩进。可以缩进文本块:

String html = """
     <div class="Warning">
         Beware of those who say "Hello" to the world
     </div>
     """;

这样就会去除文本块中所有行共有的最长前导空格序列。实际字符串是:

"<div class=\"Warning\">\n   Beware of those who say \"Hello\" to the world\n</div>\n"

注意,第一行和第三行中没有缩进。

文本块终止标记符"""之前的空格非常重要。但是,在删除缩进的过程中,整行的空格并不会被压缩。

警告: 作为前缀的空格必须与文本块中的所有行 完全 匹配。如果混合使用制表符和空格,你可能会发现删减的空格会比预期的少。 WIWjRZXY5HQ48l0L9DWThdq0YhG+m+D15InSX+oxwAmRASluVHyGOfeiEVGlX4WD

点击中间区域
呼出菜单
上一章
目录
下一章
×