Kotlin 没有本身的集合类库而是彻底使用标准的 Java 集合类库。java
val hashSet = hashSetOf(1, 2, 3, 4, 5)
println(hashSet.javaClass) // class java.util.HashSet
val linkedHashSet = linkedSetOf(1, 2, 3)
println(linkedHashSet.javaClass) // class java.util.LinkedHashSet
val arrayList = arrayListOf(1, 2, 3, 4, 5)
println(arrayList.javaClass) // class java.util.ArrayList
val list = listOf(1, 2, 3, 4, 5)
println(list.javaClass) // class java.util.Arrays$ArrayList
val hashMap = hashMapOf(1 to "a", 2 to "b", 3 to "c")
println(hashMap.javaClass) // class java.util.HashMap
复制代码
经过上面这些函数就能够建立集合,经过本身 new 集合对象的方式也是能够的 , Kotlin 中在建立对象时省略了 new 关键字。虽然 Kotlin 采用的是 Java 集合类库,可是 Kotlin 提供了一些额外的扩展。正则表达式
val list = listOf("小明", "丹尼", "李华")
println("获取第一个元素 : ${list.first()}") // 获取第一个元素 : 小明
println("获取最后一个元素: ${list.last()}") // 获取最后一个元素: 李华
println("获取指定下标的元素: ${list[1]}") // 获取指定下标的元素: 丹尼
println("获取当中最大的一个元素: ${list.max()}") // 获取当中最大的一个元素: 李华
println("翻转这个集合 :${list.asReversed()}") // 翻转这个集合 :[李华, 丹尼, 小明]
println("根据条件在集合中查找知足条件的元素 : ${list.find { it.startsWith("小") }}")
// 根据条件在集合中查找知足条件的元素 : 小明
复制代码
在后面的部分会仔细探究他们的工做原来,以及这些在 Java 类中新增长的函数是从何而来。数据库
这一节咱们从一个例子开始,需求是获得一个集合的字符串展现形式,能够指定元素之间的分隔符号,前缀和后缀。先写一个最基本的函数。编程
fun <T> joinToString(collection: Collection<T> , separator: String ,
prefix: String , postfix: String): String {
val result = StringBuilder(prefix)
for ((index , element) in collection.withIndex()) {
if (index > 0) {
result.append(separator)
}
result.append(element)
}
result.append(postfix)
return result.toString()
}
复制代码
val list = listOf("小明", "丹尼", "李华")
println(joinToString(list , "|" , "<" , ">"))
// <小明|丹尼|李华>
复制代码
对 joinToString 函数的测试结果获得了咱们的预期。接下来咱们会用 Kotlin 支持的特性来改写这个函数,力求让它变得更简洁和实用。数组
命名参数 是 Kotlin 的特性之一 ,能够解决可读性的问题 , 由于当你在调用这样一个 API : joinToString(Collection , "" , "" , "") 的时候。你极可能会搞不清楚每一个位置的String类型的参数究竟意味着什么,只要参数的顺序传错了你就会获得一些奇怪的结果。为了不这个问题你须要去看一下它的函数声明,来肯定每一个位置上的须要的是什么参数。bash
在 Kotlin 中能够经过命名参数来解决这个问题, 就是在调用一个函数传入参数的时候,能够显示的写上参数的名称,而且指定要传入的值赋值给那个参数。可是若是在调用一个函数时,指明了一个参数的名称时,为了不混淆,这个参数以后的全部参数都须要标明名称了。 例如我对 prefix 参数标明了名称,那么必须在对以后的 postfix 和 separator 参数都标明名称。架构
这个特性是无法在调用 Java 函数时使用的。由于把参数名称保存到.class 文件中是 Java 8及其更高版本的一个可选功能,Kotlin 须要保持对 Java 6 的兼容性。因此编译器不能识别出调用函数的参数名称。app
val list = listOf("小明", "丹尼", "李华")
println(joinToString(list , prefix = "<" , separator = "|" , postfix = ">"))
// <小明|丹尼|李华>
复制代码
Java 的另外一个广泛存在的问题是一些类的重载函数太多。这些重载,本来是为了向后兼容,方便这些API的使用者,又或者是出于别的缘由,但致使的最终结果是同样的:重复。框架
在 Kotlin 中能够在声明函数的时候指定参数的默认值,这样能够避免建立重载函数。使用默认参数值对 joinToString 函数进行改写。函数
fun <T> joinToString(collection: Collection<T> , separator: String = ", " ,
prefix: String = "[" , postfix: String = "]"): String {
val result = StringBuilder(prefix)
for ((index , element) in collection.withIndex()) {
if (index > 0) {
result.append(separator)
}
result.append(element)
}
result.append(postfix)
return result.toString()
}
复制代码
val list = listOf("小明", "丹尼", "李华")
println(joinToString(list)) // [小明, 丹尼, 李华]
复制代码
在对 joinToString 函数进行调用的时候咱们只传入了一个 list 参数值。其余参数都使用了咱们在声明函数时所指定的默认值。 注意!参数的默认值是被编码到被调用的函数中,而不是调用的地方。若是你改变了参数的默认值并从新编译这个函数,没有给参数从新赋值的调用者,将会开始使用新的默认值
Java中是没有默认值概念的,因此当从 Java 代码中调用 Kotlin 函数的时候,调用者必须显示的指定全部参数的值。同时 Kotlin 也给出了符合 Java 习惯的解决方法 ,在函数上加上 @JvmOverloads 注解,编译器就会生成 Java 的重载函数,从最后一个开始省略每一个参数,被省略的参数使用的是函数声明时指定的默认值。
@JvmOverloads
fun <T> joinToString(collection: Collection<T> , separator: String = ", " ,
prefix: String = "[" , postfix: String = "]"): String {
val result = StringBuilder(prefix)
for ((index , element) in collection.withIndex()) {
if (index > 0) {
result.append(separator)
}
result.append(element)
}
result.append(postfix)
return result.toString()
}
复制代码
List<String> list = new ArrayList<>();
list.add("小明");
list.add("丹尼");
list.add("李华");
System.out.println(new KTDemo().joinToString(list)); // [小明, 丹尼, 李华]
复制代码
我相信绝大多数 Java 开发者都会在本身的,公司的,开源框架项目,或者是 JDK 中看到很多名称为 XXXUtils 或者 XXXs 的类。这些类存在的意义就是工做在一些不须要对象的地方。这样的类仅仅做为一堆静态函数的容器存在。看吧事实就是这样,并非全部人都须要对象(object) (注意这里的对象指的是编程世界中的对象,而不是中文口语的那个对象,事实上在现实世界中人人都须要对象,否则人该有多孤单啊) 。
在 Kotlin 中根本酒不须要去建立这些无心义的类。相反,能够把这些函数直接放在代码文件的顶层 ,不用从属于任何类。这些放在文件顶层的函数任然是包内的成员,若是你须要从包外访问它,则须要 import 。
这里咱们写了一个 join.kt 文件,直接将 joinToString 函数放在了文件内。在 Java 代码中调用这个函数 。
仔细观察能够发现 import static kt.demo.JoinKt.joinToString 这行代码,这说明了 join.kt 文件被编译成了一个类名为 JoinKt , joinToString 是其中的一个静态函数。固然这里你也能够这样写。
import kt.demo.JoinKt
public class JavaClassDemo {
@Test
public void test1() {
List<String> list = new ArrayList<>();
list.add("小明");
list.add("丹尼");
list.add("李华");
System.out.println(JoinKt.joinToString(list));
}
}
复制代码
想要改变包含 Kotlin 顶层函数的编译生成的类名称,须要给这个 Kotlin 文件添加 @JvmName 的注解,将其放到这个文件的开头,为于包名的前面:
使用时就可使用 JoinFunctions 这个名称。
和函数同样属性也能够被放到文件顶层。放在顶层的属性会被编译成一个静态字段。默认状况下顶层属性和其余任意属性是同样的,是经过访问器暴漏给使用者。为了方便使用,若是你想要把一个常量以 public static final 的属性暴漏给 Java 可使用 const 来修饰它。
const val UNIX_LINE_SEPARATOR = "\n"
public static final String UNIX_LINE_SEPARATOR = "\n";
// 这两行代码等同
复制代码
理论上来讲扩展函数很是简单,就是一个类的成员函数,不过这个成员函数定义在了类的外面。以下图咱们就为 String 定义了一个扩展函数用来获取字符串的最后一个字符。
fun String.lastChar(): Char = this.last()
复制代码
能够像调用类的普通成员去调用这个函数:
println("Kotlin".lastChar()) // n
复制代码
在上面这个例子中 ,String 就是接收者类型 。 "Kotlin" 字符串就是接收者对象。如今咱们不须要修改 String 类的源码就为它增长了新的行为。无论 String 类是用 Java 、Kotlin,或者像 Groovy 的其余 JVM 语言编写的,只要他会编译为 Java 类,就能够为这个类添加本身的扩展。
一个扩展函数不会自动在整个项目范围内生效。若是你须要使用它须要进行导入。若是导入后发现了命名冲突可使用 as 关键字来另外定义一个名称,这样对导入的类或者函数都是有效的。
import javax.persistence.Entity
import org.hepeng.cornerstone.entity.Entity as E
复制代码
由于是静态函数,这样调用扩展函数就不会建立适配的对象或者任何运行时的额外开销。知道了这一点如何从 Java 中调用扩展对象就很简单了,无非就是调用这个静态函数罢了。
import kt.demo.StringsKt;
public class JavaClassDemo {
@Test
public void test2() {
String s = "kotlin";
System.out.println(StringsKt.lastChar(s)); // n
}
}
复制代码
在学习了以上这些知识后咱们能够进一步改写 joinToString 函数了 :
@JvmOverloads
fun <T> Collection<T>.joinToString(collection: Collection<T> , separator: String = ", " ,
prefix: String = "[" , postfix: String = "]"): String {
val result = StringBuilder(prefix)
for ((index , element) in collection.withIndex()) {
if (index > 0) {
result.append(separator)
}
result.append(element)
}
result.append(postfix)
return result.toString()
}
复制代码
在 Kotlin 中调用扩展函数 :
val list = listOf("小明", "丹尼", "李华")
println(list.joinToString(separator = " @ ")) // [小明 @ 丹尼 @ 李华]
复制代码
扩展属性提供了一种方法,用来扩展类的 API ,能够用来访问属性,用的是属性语法而不是函数语法。尽管他们被称为属性,可是他们能够没有任何状态,由于没有合适的地方来存储它,不可能给现有的 Java 对象实例添加额外的字段。但有时短语法仍然是便于使用的。
声明一个扩展属性,这里必须显示的定义 getter 函数,由于没有对应的字段因此也不会存在默认的 getter 实现。同理初始化也是不能够的,由于没有地方存储值。
在 Java 中调用扩展属性的时候,是显示的调用它的 getter 函数。
val String.lastChar: Char
get() = this.last()
复制代码
这节内容会涉及到的语言特性:
使用函数来建立集合的时候能够传入任意个数的参数。
val list = listOf(1 , 2 , 3 , 4 , 5)
复制代码
在 Java 中的可变参数是经过 ... 声明的, 能够把任意个数的参数值打包到数组中传给函数。 Kotlin 的可变参数使用 vararg 声明。Kotlin 和 Java 之间另外一给区别是,当须要传递的参数已经包装在数组中时,调用该函数的语法。在 Java 中能够按原样传递数组 ,而 Kotlin 则要求你显示的解包数组,以便每一个数组元素在函数中能做为单独的参数来调用。从技术角度来说这个功能被称为展开运算符,而使用的时候,不过是在参数前面放一个 * 。
fun main(args: Array<String>) {
val list = listOf("args: " , *args)
println(list)
}
复制代码
在以前的内容中我写过一些这样的代码来建立一个 map 集合。在这行代码中 to 不是内置的结构,而是一种特殊的函数调用,被称为中缀调用。
在中缀调用中没有添加额外的分隔符,函数名称是直接放在目标对象名称和参数之间的。 第二行代码和第一行代码调用方式是等价的。
val hashMap = hashMapOf(1 to "a", 2 to "b", 3 to "c")
复制代码
val hashMap = hashMapOf(1.to("a"), 2.to("b"), 3.to("c"))
复制代码
infix fun String.join(s: String) = this.plus(" $s")
复制代码
println("hello" join "world") // hello world
复制代码
解构声明能够把一个对象解构成不少变量,这样会带来一些便利性。
val map = mapOf(1 to "One", 2 to "Two", 3 to "three")
for ((key , value) in map) {
println("key = $key , value = $value")
}
复制代码
例如这里 (key , value) in map 就是一个解构声明
data class Cat(var name: String? , var color: String?)
复制代码
val cat = Cat(name = "小将" , color = "白色")
val (name , color) = cat
复制代码
这里对 cat 也是一个解构声明
Kotlin 字符串和 Java 字符串彻底相同。Kotlin 提供了一些有用的扩展函数,使得字符串使用起来更加方便。
Kotlin 中使用与 Java 彻底相同的正则表达式语法。
val text = """ >Tell me and I forget. >Teach me and I remember. >Involve me and I learn. >(Benjamin Franklin) """
复制代码
三重引号字符串中的内容不会被转义,它能够包含任何字符,将会保持原样。上面的字符串打印后会按照原样输出。
若是为了更好的表示这样的字符串,能够去掉缩进(左边距)。为此能够向字符串内容添加前缀,标记边距的结尾,而后调用 trimMargin 来删除每行中的前缀和前面的空格。
val text = """ >Tell me and I forget. >Teach me and I remember. >Involve me and I learn. >(Benjamin Franklin) """.trimMargin(">")
复制代码
三重引号字符串中也是可使用字符串模板的
许多开发人员认为,好代码的重要标准之一是减小重复代码,甚至还给这个原则起了个名字:不要重复你本身(DRY)。可是当你写 Java 代码的时候,有时候作到这点就不那么容易了。许多状况下能够抽取出多个方法,把长的函数分解成许多小的函数而后重用他们。可是这样可能会让代码更费解,由于你以一个包含许多小方法的类了结,并且他们之间没有明确的关系。能够更进一步将提取的函数组合成一个内部类,这样就能够保持结构,可是这种函数须要用到大量的样板代码。
Kotlin 提供了一个更整洁的方案: 能够在函数中嵌套这些提取的函数。这样既能够得到所须要得结构,也无需额外得语法开销。
data class User(var id:Int , var name: String , var address: String)
fun saveUser(user: User) {
if (user.name.isEmpty()) {
throw IllegalArgumentException("Can't save user ${user.id}: empty Name")
}
if (user.address.isEmpty()) {
throw IllegalArgumentException("Can't save user ${user.id}: empty Address")
}
// 保存到数据库
}
复制代码
data class User(var id:Int , var name: String , var address: String)
fun saveUser(user: User) {
fun validate(value: String , fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException("Can't save user ${user.id}: empty $fieldName")
}
}
validate(user.name , "Name")
validate(user.address , "Address")
// 保存到数据库
}
复制代码
data class User(var id:Int , var name: String , var address: String)
fun saveUser(user: User) {
user.validateBeforeSave()
// 保存到数据库
}
fun User.validateBeforeSave() {
fun validate(value: String , fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException("Can't save user $id: empty $fieldName")
}
}
validate(name , "Name")
validate(address , "Address")
}
复制代码