前言
具体 Kotlin 是什么?我相信你已经在网络上其他地方看到过解释,这里不再赘述,简单一句话:“Kotlin 是一种与 Java、C++ 平级的函数式编程语言”。在上一篇文章中,我们应该看到了,Kotlin 有很多的基础特殊的语法,让代码变得简单清晰 (可能在开始的时候你反而觉得变得复杂了),但 Kotlin 真的就是语法的改变吗?花两个小时看看 Kotlin 的语法就算学完了 Kotlin 吗?非也!Kotlin 与 Java 最大的区别是 Kotlin 的函数式编程,这一点以后我们会经常提到,Kotlin 的核心与 Java 就有本质的不同。Kotlin Koans 是学习 Kotlin 极好的学习资料,我们先把 Kotlin Koans 做一遍,相信你会对 Kotlin 有完全不同的认识。
Kotlin Koans 项目详解
Kotlin Koans 是一个 Kotlin 学习的教程,采用的方式是给你未完成的代码,给你一点提示,你去补全代码,运行单元测试,通过后进入下一题,可以非常好的学习 Kotlin。
Kotlin Koans 的安装,可以在 GitHub 上下载源代码项目,但是我不建议这样做,我建议的方式是安装 EduTools 插件,安装方式是直接在插件中心中搜索 EduTools 就可以了:
然后重启 Android Studio。可以看到第一项 Browse Courses:
点击选择 Kotlin Koans 项目:
大约需要一分钟后,会创建项目:
感觉提示,完成第一个任务:
这个任务就是上一篇中函数的定义相关的内容,我们可以看到得到如下一些信息:
- Kotlin 文件后缀为
.kt
- Kotlin 文件不需要定义类,可以直接定义方法
- 简单的方法可以省略大括号,直接在
=
后写返回值
每一个 Task 详解
Task 2:Java to Kotlin conversion
将 Java 代码转换成 Kotlin 写法。我们打开 JavaCode.java
文件,选中 toJSON 方法,复制,到 Task.kt
中粘贴(粘贴需要选中代码),粘贴后可以发现 Java 代码自动转换成了 Kotlin 代码,很方便。
除了上面这种方法,还可以用 Android Studio 中代码装换工具,将 Java 文件转换成 Kotlin:
我们在这里注意几个问题:
- 函数的定义,用 fun 关键字
- 参数先写参数名,再写参数类型,中间用冒号
:
分割 - 方法参数后和大括号中间需要写函数返回值类型,用
:
说明 - 创建对象没有
new
关键字,直接类名后跟括号创建新对象 - 属性定义用 val(不可更改) 或 var(可以更改)
Task 3:Named arguments
Kotlin 中参数是可以有默认值的,并且在调用的时候,可以显示声明哪些变量用哪些值,而不一定必须要按顺序赋值,其他的值使用默认值,很方便灵活,这样就可以减少构造函数、重载函数的数量。Java 中两个重载函数参数的类型如果都一样的话,是不允许的,但是 Kotlin 使用默认值的方式就可以很好的避免这个问题(声明一个三个参数的方法,并加上默认值,调用的时候对不同的参数进行声明赋值,就可以达到 Java 需要多个不同类型从在才能达到的目的)。
/**
* Creates a string from all the elements separated using [separator] and using the given [prefix] and [postfix] if supplied.
*
* If the collection could be huge, you can specify a non-negative value of [limit], in which case only the first [limit]
* elements will be appended, followed by the [truncated] string (which defaults to "...").
*/
public fun <T> Iterable<T>.joinToString(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = -1, truncated: CharSequence = "...", transform: ((T) -> CharSequence)? = null): String {
return joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString()
}
Task 4:Default arguments
看题目可以看出,有些方法在调用的时候报错,提示有些值没有别赋值,那我们的任务就是在函数定义的时候加上合适的默认值,分析提示给出的 Java 代码,我们可以看到,最后一个如果 number 没有被赋值,会用默认值 42 调用第二个方法,而第二个方法 toUpperCase 没有被赋值,使用默认值 false 调用第一个方法,则可以写出答案:
在这里我们可以看到合理的使用默认值,可以极大的简化代码。
Task 5:Lambdas
找偶数很简单,每一个都分别除 2 看余数是否为 0,但是刚刚看到这道问题可能会摸不着头脑,怎么写,这什么意思呀?题目名称叫 Lambdas,Java 8 中支持 Lambda 表达式,Kotlin 也同样支持,怎么写呢。如果想解决这个问题,我们最好解决几个概念:
- 函数在 Kotlin 中是一等公民(First-class function)
- 函数可以像变量一样传递给其他函数作为参数(某一个函数有参数,这个参数是一个变量)
- 函数可以作为其他函数的返回值
- 函数可以用于给变量赋值可以存储在数据结构中
- 高阶函数(Higher-Order Functions)
- 函数作为函数的参数
- 函数作为函数的返回值
- 函数式编程(Functional programming)
- 函数式编程是和面向对象、面向过程同一级别的编程方法或编程模式
- 函数是一等公民是函数式编程的必要条件,经常用在高阶函数中
- 后面我们会有单独的文章介绍函数式编程
我们可以看到 any 函数的定义,predicate 参数是一个函数,这个函数的返回值是一个 Boolean 类型的值:
简化到最后,就很简单了。有没有觉得函数式编程特别神奇呢?
Task 6:Strings
Kotlin 字符串可以像 Java 一样,也可以用三个双引号声明,三个双引号声明的字符串其中可以包含多行。字符串模板可以很方便在字符串中使用变量,则这个题的答案就是:
这里我们用了 trimIndent()
方法对缩进进行格式化,还可以使用 trimMargin()
,如下图,其中的竖线 |
是trimMaring 默认格式化特殊字符:
Task 7:Data classes
Date Class 是什么东西?我们先来了解 Kotlin 中集中数据结构:
- Class
- 与 Java 类类似,是一种数据结构
- Kotlin 中的 Class 继承自 Any,而不是 Object
- 用关键字 constructor 声明构造函数,够着函数可以有默认值
- 构造函数分为主构造函数和二级构造函数,主构造函数跟在类名后由关键字 constructor 和参数表共同构成,二级构造函数在类中,用 constructor 和参数列表构成,注意如果有主构造函数,二级函数必须调用主构造函数。如
constructor(name: String, parent: Person) : this(name)
。 - 主构造函数无法执行代码,如果需要执行一些代码时,可以用
init{}
在类中执行,可以声明多个init{}
,执行顺序与声明顺序一致。 - 默认类不可以被集成,方法不可以被重写,如果希望类被继承或方法可以被重写,可以在类或方法前加
open
关键字。 - 子类在重写父类方法时,需要在方法前加
override
关键字 -
super
关键字调用父类方法 -
abstract
关键字定义抽象类,interface
关键字定义接口。 - 普通情况下,类没有静态方法,如果需要静态发发或所有静态属性,需要用伴随对象
companion object
,相当于独立于类开辟了一块空间,所有对象共有。
- Properties and Fields
- val 修改不可更改的属性
- var 修饰可以更改的属性
- lateinit 修饰延迟加载的属性,在需要用的时候才初始化。
- Data Class
- 有些时候,创建类就是为了让其可以有几个属性,Java 中的 Bean 对象,而在 Kotlin 中,将这种数据结构单独定义为
data class
- 其中会自动创建一些方法,如
copy()
,equals()
等 - 我们在后面会有主题讲解
data class
- 有些时候,创建类就是为了让其可以有几个属性,Java 中的 Bean 对象,而在 Kotlin 中,将这种数据结构单独定义为
用 data class
可以很简单的定义数据结构,并且其中自动包含了 get/set 方法,易读且方便。
Task 8:Nullable type
Java 中经常需要判空,如果不为空,获取其中某些属性值等,写起来不优雅,且不安全,Kotlin 中采用了更简单的方式。
注意,这并不意味着 Kotlin 中就完全不需要关心变量为空引起的错误,我们后面会具体详细说明一些 Java 代码转成 Kotlin 或直接 写 Kotlin 代码引起的一些错误,是一个需要填的坑。
Task 9:Smart casts
自动类型转换是一个很聪明的特性。Kotlin 中没有 switch-case,代替它的是 when,这里的例子是判断 expr 具体是哪一种类型,这里可以展现出 Kotlin 强大的是在判断一个对象是哪一种类型后,后面这个变量直接转换成该类型,而不像 Java 中需要强转。
Task 10:Extension functions
扩展函数。有些时候,某些类功能不够强大,你希望扩展这个类,而你又不能修改该类的源代码,如果是 Java,你可能需要继承自功能不够强大的类,添加新方法,然后使用新的类,这比较麻烦,也可能会因为需求的变更,这个类越来越不好维护。Kotlin 的方式是在不修改原来的类的代码的情况下,扩展方法出新的方法,使用起来就像是在原来的类中添加了新的方法。
在没有修改 Int 和 Pair 对象的情况下,他们都有了转成有理数的方法了。
Task 11:Object expressions
对象表达式。Kotlin 的对象表达式与 Java 中的匿名内部类差不多。有些情况只用一次的对象,没必要写成一个类。
简化代码改为 Lambda 表达式:
再简化代码,使用 Kotlin stdlib:
了解越多 Kotlin stdlib 中的方法,写代码越快,效率越高。
Task 12:Extension functions on collections
Kotlin 在集合类中做了大量的优化,提供了大量的有用的方法,这里排序可以直接调用。
/**
* Returns a list of all elements sorted descending according to their natural sort order.
*/
public fun <T : Comparable<T>> Iterable<T>.sortedDescending(): List<T> {
return sortedWith(reverseOrder())
}
Kotlin 提供了自然序的升序降序排序方法:sortedDescending
Task 13:Comparison
我们定义了一个类 MyDate,我们现在直接用 <
对这个类的对象比较大小。我们需要对这个对象定义比较大小操作符,则只需重写 compareTo 方法就可以了。
Task 14:Task In range
有了上面的经验,这里只需了解 in 操作符是调用的 contains 方法就可以了:
简写成(其中 ..
就是执行 rangeTo
方法):
Task 15:Range to
我们在上一个任务中,有编译器自动提示使用了 in - ..
操作符,我们点进去看看,发现其实就是执行了 rangeTo
方法,这一个任务中,我们就需要实现 rangeTo
方法就可以,rangeTo
方法返回值是 ClosedRange
,而 ClosedRange
需要对象实现比较大小 compareTo
方法,恰好我们上一节中已经在 MyData 中实现了 compareTo,恰好 DateRange 实现了 ClosedRange,因此我们可以直接使用,结果如下:
这个任务比较复杂,但是逻辑是很清晰的。
Task 16:For loop
For 循环需要迭代器,DateRange 需要实现 Iterable,则问题就简单了:
当然,可以优化代码:
For 循环就是迭代器的遍历。
Task 17:Operators overloading
+
操作符,其实就是实现 plus
方法。*
操作符就是实现 times
方法
Task 18:Destructuring declarations
可以将对象属性值赋值给其他对象,要求这个对象必须由 data 修饰。
Task 19:Invoke
[图片上传失败...(image-63c82c-1523208961981)]
小结
本文主要了解了一些 Kotlin 的具体实现方法,对真正写 Kotlin 程序极有好处。
到这里,我们完成了一半的任务,为避免篇幅过长,下面的任务我们在下一篇文章中继续讨论。
如果有一天你觉得过的舒服了,你就要小心了!欢迎关注我的公众号:我是任玉琢