1 在Kotlin中创建集合
val set = setOf(1,2,3,4)//这个地方返回的是一个数组
val set2 = hashSetOf(1,2,3,4)
可以用类似的方法创建list或者map:
val list = arrayListOf(1,2,3,4,5)
val map = hashMapOf(1 to "one",2 to "two",3 to "three",4 to "four",5 to "five")
注意,to并不是一个特殊的结构,而是一个普通的结构。在稍候会探讨它
println(set.javaClass)
println(list.javaClass)
println(map.javaClass)
打印结果为:
Kotlin中并没有采用它自己的集合类,而是采用的标准Java集合类。这样对Java开发者是一个好消息,现在所掌握的Java集合知识在Kotlin中一样可以使用。为什么Kotlin没有自己专门的集合类呢?那是因为使用标准的Java集合类,Kotlin可以更容易与Java代码交换。当从Kotlin调用Java方法的时候,不用转换它的集合类来匹配Java的类,反之亦然。
2 让函数更好用
我们知道怎么创建集合了,让我们打印它的内容
val list = arrayListOf(1,2,3,4,5)
println(list)
打印结果:
如果需要用分号分给每个元素,然后用括号括起来,而不是采用方括号:(1;2;3;4;5)。解决这个问题,Java项目会使用第三方的类库,而在Kotlin中,它的标准库中有专门的函数处理这种情况。
/**
* 扩展函数
* 使用JVMOverload注解指示编译器生成java重载函数,从最后一个开始省略每个参数
*
*/
@JvmOverloads
fun Iterable.joinToString(separator:String = ": ",prefix:String = "{",postfix: String = "}"): String {
val result = StringBuilder()
result.append(prefix)
for((index, element) in this.withIndex()){
if(index > 0)
result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
其实这个函数在Kotlin中是已经实现的一个扩展函数。这个函数式一个参数带默认值的扩展函数,这样可以避免创建重载函数。如果不传参数,参数后面的值就会被使用;如果只是想传入部分参数,需要使用“参数名=值“进行传入,跟下面第二个打印一样。
println(list.joinToString())
打印结果就是:
println(list.joinToString(separator = " "))
2.1消除静态工具类:顶层函数和属性
我们都知道,在Java作为一门面向对象的语言,需要所有的代码都写作类的类的函数。大多数情况下,这种方式还能行得通。但事实上,几乎所有的大型项目,最终都有很多的代码并不能属于任何一个类中。有时一个操作对应两个不同的类的对象,而且重要性相差无几。有时存在一个基本的对象,但你不想通过实例来添加操作,让它的API继续膨胀。结果就是,最终这些类将不包含任何的状态或者实例函数,而是作为一堆静态函数的容器。在JDK中,最适合的例子应该就是Collections了。
在Kotlin中,根本就不需要去创建这些无意义的类。相反,可以把这些函数直接放到代码文件的顶层,不用从属于任何的类。这些放在文件顶层的函数依然是包内的成员,如果你需要从包外访问它,则血药import,但不再需要额外包一层。
package strings
fun joinToString(...):String{...}这个文件编译完生成的Java类
package strings;
public class JoinKt{
public static String joinToString(...){...}
}
可以看到Kotlin编译生存的类的名称,对应于包含函数的文件的名称。这个文件中的所有顶层函数编译为这个类的静态函数。
2.2顶层属性
和函数一样,属性也可以放到文件的顶层。在一个类的外面保存单独的数据片段虽然不常用,但是还是有他的价值。举个例子,可以用var属性来计算一些函数被执行的次数:
var opCount = 0
fun performOperation(){
opCount ++
}
fun reportOperationCount(){
println("Operation performed $opCount times")
}
像这个值就会被存储到一个静态的字段中。
也可以在代码中用顶层属性来定义常量:
val UNIX_LINE_SEPARATOR = "\n"
默认情况下,顶层属性和其他任意的属性一样,是通过访问器暴露给Java使用的(如果是val就只用一个getter,如果是var就对应一对getter和setter)。为了方便使用,如果你想要把一个常量以public static final 的属性暴露给Java,可以用const来修饰(这个适用于所用的基本数据类型的属性,以及String类型)。
const val UNIX_LINE_SEPARATOR = "\n"
这个等同于下面的Java代码
public static final String UNIX_LINE_SEPARATOR = "\n";
2.3给别人的类添加方法:扩展函数和属性
Kotlin的一大特色,就是可以平滑的与现有代码集成。甚至,纯Kotlin的项目都可以基于Java库构建,如JDK、Android框架,以及其他的第三方框架。当你在一个现有的Java项目中集成Kotlin的时候,依然需要面临现有代码目前不能转换成Kotlin,甚至将来也不会转成Kotlin的局面。当使用这些API的时候,如果不用重写,就能使用到Kotlin为它带来的方便,岂不是更好?这里,可以用扩展函数来实现。
理论上来说,扩展函数非常简单,他就是一个类的成员函数,不过定义在类的外面。为了方便阐释,让我们添加一个方法,来计算一个字符串的最后一个字符
/**
* 扩展函数
*/
fun String.lastChar():Char {
return this.get(this.length-1)
}
使用的时候
println("Kotlin".lastChar())
在这个例子中,String就是接受类型,而"Kotlin"就是接受者对象。
从某种意义上说,你已经为String类添加了自己的方法。即使字符串不是代码的一部分,也没有类的源代码,你仍然可以在自己的项目中根据需要去扩展方法。不管String类是用Java、Kotlin或者想Groovy的其他JVM语言编写的,只要是它会编译为Java类,你就可以为这个类添加自己的扩展。
咋这个扩展函数中,可以像其他成员函数一样用this。而且也可以像普通的成员函数一样省略它。
fun String.lastChar() = get(length-1)
在扩展函数中,可以直接访问被扩展的类的其他方法和属性,就好像是在这个类自己的方法中访问它们一样。注意,扩展函数并不允许你打破它的
封装性。和在类的内部定义的方法不同的是,扩展函数不能访问私有的或者受保护的成员。
2.3.1导入扩展函数
对于你定义的一个扩展函数,它不会自动地在整个项目范围内生效。相反,如果你要使用它,需要进行导入,就像其他任何的类或者函数一样。这是为了避免偶然性的命名冲突。Kotlin允许用和导入类一样的语法来导入单个的函数:
import com.houde.second.lastChar
"Kotlin".lastChar()
当然也可以用*来导入:
import com.houde.*
可以使用关键字as来修改导入的类或者函数的名称:
import com.houde.second.lastChar as last
"Kotlin".last()
当在不同的包中,有一些重名函数时,在导入时给它重新命名就显得很有必要了,这样可以在同一个文件中去使用它们在这种情况下,对于一般的类和函数,还有另外一个选择:可以选择用全名来指出这个类或者函数。对于扩展函数,Kotlin的语法要求你用简短的名称,所以,在导入声明的时候,关键字as就是解决命名冲突的唯一方式。
2.3.2从Java中调用扩展函数
实质上,扩展函数式静态函数,它把调用对象作为了它的第一个参数。调用扩展函数,不会常见适配的对象或者任何运行时的额外消耗。这使得从Java中调用Kotlin的扩展函数变得非常简单:调用这个静态函数,然后把结束这对象作为第一个参数传进去即可,可其他的顶层函数一样,包含这个函数的Java类的名称,是由这个函数声明的文件名称决定的。假设它声明在一个叫做HelloKotlin5.kt的文件中:
char c = HelloKotlin5Kt.lastChar("Java");
System.out.println(c);
这个扩展函数被声明为顶层函数,所以,它将会被编译为一个静态函数。在Java中静态导入lastChar函数,就可以直接使用它了,如lastChar("Java")。
2.3.3 作为扩展函数的工具函数
现在可以写一个joinToString函数的终极版本了:
/**
* 扩展函数
* 使用JVMOverload注解指示编译器生成java重载函数,从最后一个开始省略每个参数
*
*/
@JvmOverloads
fun Iterable.joinToString(separator:String = ": ",prefix:String = "{",postfix: String = "}"): String {
val result = StringBuilder()
result.append(prefix)
for((index, element) in this.withIndex()){
if(index > 0)
result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
println(arrayListOf(1,2,3,4,5).joinToString(separator = "; ",prefix = "{",postfix = "}"))
可以给元素的集合类添加一个扩展函数,然后给所有的参数添加一个默认值。这样,就可以像使用一个类的成员函数一样,去调用joinToString了。
val list = arrayListOf(1,2,3,4,5)
println(list.joinToString())
因为扩展函数无非就是静态函数的一个高效语法糖,可以使用更具体的类型来作为接受者类型,而不是一个类。假设你想要一个join函数,你能有字符串的集合来触发。
fun Collection.join(
separator:String = ", ",
prefix:String = "",
postfix: String = ""
) = joinToString(separator,prefix,postfix)
如果是用其他类型的对象列表来调用,将会报错:
val list = arrayListOf(1,2,3,4,5)
list.join()
Error:(11, 5) Kotlin: Type mismatch: inferred type is kotlin.collections.ArrayList but Collection was expected。
扩展函数的静态性质也就决定了扩展函数不能被子类重写。
2.3.4扩展属性
扩展属性提供了一种方法,用来扩展类的API,可以用来访问属性,用的是属性语法而不是函数语法。尽管它们被称为属性,但它们可以没有任何状态,因为没有合适的地方来存储它,不可能给现有的Java对象的实例添加额外的字段。但有时短语法仍然是便于使用的。在上面我们定义了一个lastChar的函数,现在把它转换成一个属性试试。
/**
* 扩展属性
* String类的不变的扩展属性
*/
val String.lastChar: Char
get() = get(length - 1)
可以看到,和扩展函数一样,扩展属性也像接受者的一个普通的成员属性一样。这里,必须定义getter函数,因为没有支持字段,因此没有默认的getter实现。同理初始化也不可以:因为没有地方存储初始值。
如果在StringBulider上定义一个相同的属性,可以置为var,因为StringBuilder内容是可变的。
/**
* StringBuilder类的可变扩展属性
* 可变与不可变主要在于关键字val还是var上
*/
var StringBuilder.lastChar: Char
set(value: Char) = set(length-1,value)
get() = get(length-1)
注意:当需要Java中访问扩展属性的时候,应该显示地调用它的getter函数:文件名kt.getLastChar("Java");
4处理集合:可变参数、中缀调用和库支持
几个相关语言特性:
1.可变参数关键字:vararg,可以用来声明一个函数将可能有任意数量的参数
2.一个中缀表示法,当你在调用一些只有一个参数的函数时,使用它会让代码更加简练
3.解构声明,用来把单独的组合值展开到多个变量中
4.1扩展Java集合的API
开始的前提是基于Kotlin中的集合与java的类相同,但对API做了扩展
val list = arrayListOf(1,2,3,4,5)
println(list.last())
println(list.max())
我们感兴趣的是它是怎么工作的:尽管它们是Java库类的实例,为什么Kotlin中能对集合有这么多丰富的操作。现在答案很明显:因为函数last()和max()都被声明成了扩展函数。
last函数不会比String的lastChar更复杂,它是List类的一个扩展函数。
/**
* Returns the last element.
* @throws [NoSuchElementException] if the list is empty.
*/
public fun List.last(): T {
if (isEmpty())
throw NoSuchElementException("List is empty.")
return this[lastIndex]
}
4.2可变参数:让函数支持任意数量的参数
在调用一个函数来创建列表的时候,可以传递任意个数的参数给它:
val list = listOf(2,3,4,5,6,7)
这个函数的声明是:
/**
* Returns a new read-only list of given elements. The returned list is serializable (JVM).
* @sample samples.collections.Collections.Lists.readOnlyList
*/
public fun listOf(vararg elements: T): List = if (elements.size > 0) elements.asList() else emptyList()
Kotlin的可变参数与Java类似,但语法略有不同:Kotlin在该类型之后不会再使用三个点,而是在参数上使用vararg修饰符。
Kotlin和Java之间的另一个区别是,当需要传递的参数已经包装在数组中时,调用该函数的语法。在Java中,可以按原样传递数组,
而Kotlin则要求你显示地解包数组,以便每个数组元素在函数中能作为单独的参数来调用。从技术的角度来讲这个功能被称为展开运算符,
而使用的时候,不过是在对应的参数前面放一个 * :
val args2 = arrayOf(1,2,3,4,5)
val list3 = listOf(*args2)
println(list3)
打印结果为:
[1, 2, 3, 4, 5]
这个实例展示了,通过展开运算符,可以在单个调用中组合来自数组的值和某些固定值。这在Java中并不支持。
4.3键值对的处理:中缀调用和结构声明
可以使用mapOf函数来创建Map:
val map = hashMapOf(1 to "one",2 to "two",3 to "three",4 to "four",5 to "five")
这行代码中的单词to不是内置的结构,而是一种特殊的函数调用被称为中缀调用。在中缀调用中,没有添加额外的分隔符,函数名称是直接放在
目标对象名称和参数之间的。以下两个调用方式是等价的:
1 to "one" 和 1.to("one")
中缀调用可以与只有一个参数的函数一起使用,无论是普通的函数还是扩展函数。要允许使用中缀符号调用函数,需要使用infix修饰符来标记
它。下面是一个简单的to函数声明:
/**
* to 函数声明
*/
infix fun Any.to(other :Any) = Pair(this,other)
to函数会返回一个Pair类型的对象,Pair是Kotlin标准库中的类,不出所料,他会用来表示一对元素。Pair和to的声明都用到了泛型,简单起
见,这里我们省略了泛型。注意,可以直接用Pair的内容来初始化两个变量:
val (number,name) = 1 to "one"
解构声明特征不止用于pair。例如,还可以使用map的Key和value内容来初始化两个变量。
这也适用于循环,正如你在使用withIndex函数的joinToString实现中看到的:
val list = arrayListOf(1,2,3,4,5)
for((index,element) in list.withIndex()){
println("index is $index value is $element")
}
输出结果:
to函数式一个扩展函数,可以创建一对任何元素,这意味着它是泛型接受者的扩展:可以使用1 to "one" 、 "one" to 1 、list to list.size()等写法。
5 字符串和正则表达式的处理
Kotlin字符串与Java字符串完全相同。可以将在Kotlin代码中创建的字符串传递给任何Java函数,也可以吧任何Kotlin标准库函数
应用到从Java代码接收的字符串上,而不用转换,也不用创建附加对象。
Kotlin通过提供一系列有用扩展函数,使标准Java字符串使用起来更加方便。此外,它还隐藏了一些令人费解的函数,添加了一些
更清晰易用的扩展。作为体现API差异化的第一个例子。
5.1 分割字符串
我们可能对String的split方法很熟悉了。每个人使用它,但有时候会抱怨Java的split方法不适用于一个点号。