图3.4  Kotlin字符串跟Java字符串是完全一样的东西。你可以把Kotlin代码中创建的一个字符串传递给任意的Java方法,同时你也可以对你从Java代码中接收的字符串使用Kotlin标准库方法。(这)并不涉及任何的转换,同时也没有创建额外的包装对象。  Kotlin通过提供一大堆有用的扩展函数来使得用Java标准库变得更为享受。(同时它)也隐藏了一些令人疑惑的方法,添加更加清晰的扩展。作为我们API差异的第一个例子,让我们来看看Kotlin如何处理拆分的字符串的。

3.5.1 拆分字符串

你可以对String类中的split方法非常熟悉。每个人都是使用它,但是仍然有人在Stack Overflow网站上抱怨它:“Java中的split方法对点号不起作用”。这是一个很常见的陷阱:写成"12.345-6.A".split(".")同时却期待得到[12, 345-6, A]数组这样一个结果。但是Java的split方法返回了空数组!这会发生,因为它把正则表达式当做一个参数,同时它依据(传入的)正则表达式将一个字符串查分成多个字符串。点号.在此处是一个指代任意字符的正则表达式。  Kotlin隐藏了令人困惑的方法并提供了作为替代的多个有着不同参数的叫做split的扩展。把正则表达式作为参数的(split重载扩展)把Regex类型的值当做参数而不是String类型。这确保了传递给函数的一个字符串无论是被解析成普通字符还是正则表达式,它始终是清晰的。

以下是你如何使用点号或者下划线来拆分字符串的:


>>> println("12.345-6.A".split("\\.|-".toRegex())) // 1 显式的创建一个正则表达式
[12, 345, 6, A]

Kotlin使用跟Java完全一样的正则表达式语法。此处的模式匹配了一个点号或者一个破折号(我们对它进行转义来表明我们想要的是一个字面量,而不是通配符)。使用正则表达式的API也跟Java标准库API相似,但是它们更加好用。举个例子,在Kotlin中,你使用一个扩展函数toRegex来把一个字符串转换成一个正则表达式。  但是对于这样一个简单的例子,你不需要使用正则表达式。Kotlin中其他重载split的扩展函数接收任意数量的分隔符作为普通的字符串:


>>> println("12.345-6.A".split(".", "-")) // 1 指定多个分隔符
[12, 345, 6, A]

注意,你可以指定字符参数,写成:"12.345-6.A".split('.', '-'),这样得到的结果也是一样的。这个方法隐藏了Java中可以接受一个字符作为分隔符的相似的方法。

3.5.2 正则表达式和三引号字符串(triple-quoted strings)

让我们来看看另一个有两种不同实现的例子:第一个将使用扩展,第二个将会使用正则表达式。你的任务将会是吧一个文件的完整路径名解析成它的组件:一个路径、一个文件名、以及一个扩展名。Kotlin标准库包含了获取给定分隔符前后的子字符串的函数。以下是你可以使用它们来解决这个任务的方式(同时看图3.4):


fun parsePath(path: String) {
    val directory = path.substringBeforeLast("/")
    val fullName = path.substringAfterLast("/")
    val fileName = fullName.substringBeforeLast(".")
    val extension = fullName.substringAfterLast(".")
    println("Dir: $directory, name: $fileName, ext: $extension")
}
>>> parsePath("/Users/yole/kotlin-book/chapter.adoc")
Dir: /Users/yole/kotlin-book, name: chapter, ext: adoc

图3.4

图3.4 通过使用substringBeforeLastsubstringAfterLast函数把一个路径拆分成一个路径、一个文件名和一个文件扩展名。  在文件路径的最后一个斜杠符之前的子字符串是一个闭合的目录路径。在最后一个点号之后的子字符串是一个文件扩展,同时,文件名在两者之间。  如你所见,Kotlin使得不适用正则表达式重新排序的情况下解析字符串变得更加方便了。这是非常有用的。但是有时候写下的代码也非常难以理解。如果你想使用正则表达式,Kotlin标准库可以提供帮助。下面演示同样的任务如何使用正则表达式来完成的:


fun parsePathRegexp(path: String) {
    val regex = """(.+)/(.+)\.(.+)""".toRegex()
    val matchResult = regex.matchEntire(path)
    if (matchResult != null) {
        val (directory, filename, extension) = matchResult.destructured
        println("Dir: $directory, name: $filename, ext: $extension")
    }
}

在这个例子中,正则表达式被写在一个三引号字符串。在这样一个字符串中,你不需要转义任何字符,包括反斜杠。因此,你可以用\. 来编码点号而不是你在普通字符串中写的\\.(见图3.5)。

图3.5

图 3.5 用来将路径分割成一个目录、一个文件名和一个文件扩展名的正则表达式

正则表达式通过斜杠和点号把一个路径分割成三组。.模式匹配开头的任意字符,所以第一组(.+)包含了最后一个斜杠前的子字符串。这个子字符串包含了之前所有的斜杠,因为他们匹配了“任意字符”模式。相似的,第二组包含了最后一个点号之前的子字符串。第三组包含了其余所有的字符串。  现在让我们来讨论前一个例子的parsePathRegexp函数的实现。你创建一个正则表达式并用它来匹配一个输入的路径。如果匹配结果为成功(不是null),你分配把它的destructured属性值分配给对应的变量。它使用的语法跟你分配一个元组给两个变量时一样:7.4节将会涵盖(一些)细节。

3.5.3 多行三引号字符串

三引号字符串的目的不仅仅是避免转义字符。这样的一个字符串字面量恶意包含任意的字符,包括换行符。这给了你一种便捷的方式在你的程序文本中嵌入包含换行符(的字符串)。正如这个例子,然我们来画一些艺术的ASCII字符:


val kotlinLogo = """| //
                   .|//
                   .|/ \"""
>>> println(kotlinLogo.trimMargin("."))
| //
|//
|/ \

多行字符串包含了三重引号之间所有的字符,包括用来格式化代码的缩进。如果你想要这种一个字符串的一个更好的表现形式,你可以裁剪缩进(换言之,就是左对齐)。为了达到这个效果,你添加一个前缀到字符串内容,来标记对齐的结束为止。然后调用trimMargin()来删除每一行前缀之前的文字。之前的例子使用点号作为前缀。  尽管三重引号字符串包含了换行符,但是你不能使用像\n这样的特殊字符另一方面,你不需要转义\,所以,windows风格的路径"C:\\Users\\yole\\kotlin-book"可以被写成"""C:\Users\yole\kotlin-book"""。  你也可以在多行字符串中使用字符串模板。因为多行字符串并不支持转义(字符)序列,如果你需要在你的字符串中使用一个美元符号的字面量,你必须使用一个嵌套的表达式。比如:val price = """${'$'}99.9"""。  在你的(除了使用ASCII艺术字的游戏以外)程序中,多行字符串非常有用的一个方面是测试。在测试中,处理多行文本并比较结果和预期输出是非常常见的操作。多行字符串给了你一个非常完美的解决方案来包含作为你的测试的一部分的预期输出。不需要笨拙的从外部文本进行加载或者转义--只需要添加一些引号标记并在引号之间放置需要的HTML文本或者其他输出。为了得到更好的格式,使用前面提到的作为又一个扩展函数的例子trimMargin函数。

NOTE   注意  你现在可以看到,扩展函数式扩展现有函数库的API并将它们融合到你的新语言--Pimp my Library模式的东西的语法习惯中去的一个很有力的方式。事实上,Kotlin标准库的很大一部分是由标准Java类库的扩展函数组成的。Anko库,也是由JetBrains开发的,提供扩展函数来让Android API更加Kotlin友好(Kotln-friendly)的库。你也可以找到许多社区开发的,用来为主要的第三方库的库,比如Spring,提供Kotlin友好的包装的库。

现在你可以看到Kotlin如何为你使用的库提供更好的API,让我们的注意力回到你的代码中去。你将会看到扩展函数的一些新的用法。我们也将会讨论一个新的概念:本地函数(local functions)。

results matching ""

    No results matching ""