人生苦短,要用Kotlin
这是一种对程序猿更为友好的语言,可以减少开发者的工作量,原本由开发者干的事情,其实很多都可以由编译器实现了,这是一种更为高级的语言。Java虽然严谨,但却过于繁琐,太啰嗦了,一个小事情却要写大量的代码,而且有些代码又是非常机械式的,在实际编码过程中都是用IDE来自动生成。Java,C,C++,Object C这些都是上世纪的编程语言。
现在到了新时代了,编程也发展了很多,像lambda表达式,函数式编程,等等一些新的概念和范式在涌现。所以就有了新时代的编程语言,像水果的Swift,Groovy,Scala,以及Java阵营的Kotlin。Kotlin是新一代的编程语言,与Java完美融合,简洁,方便,可以大大提高程序可读性,特别是对于Android开发者来说。水果推出了Swift以解放水果平台的开发者,而Kotlin就是来解放Android开发者的。
虽然说Kotlin可以用在任何可以用Java的地方,但目前主要就是两大领域服务端,以及Android应用开发,特别是有了Google官方的支持,所以Kotlin对于Android开发者的意义更为重大,身为一个Android猿,是一定要学习一下这门现代的编程语言的,因为当你学过了之后 ,你会发现,之前写的代码都是在浪费生命。
Development environment setup
有三种方式
命令行
其实,这是最好的方式,因为配置起来非常的方便。到官网去下载编译器,解压,然后把kotlinc/bin/放到PATH环境变量里面,就可以了。如果要配置Vim,还需要安装一下插件,大神们早就把插件准备好了,只需要下载,然后按照官方方法安装即可,其实就是把解压后的东西拷贝到相应的目录里面就好了。
Idea IntellJ
这个看官方文档就可以了,孤未亲测,如遇困难请自行Google。
Android Studio
因为Kotlin官已支持了Android Studio,而Google也支持了,总而言之就是在Android Studio中可以直接使用Kotlin。所以, Android Stuido 3.0以后的版本无需特殊配置,就可以用例Kotlin了。
对于刚开始学习Kotlin而言呢,孤推荐使用命令行的方式,而不要使用Android Studio,特别是直接创建一个基于Kotlin的Android项目,因为此时对语言还不够熟悉,直接上项目,会迷失在项目配置,frameworks以及语言基础之中。刚学习一门语言的时候要先学习基本的语法以及语言本身的特性,这最好先绕开框架和项目,会更容易上手一些。
Hello world
这是所有编程语言的入门必学课程,目的是让学习者快速的体验一下一门语言,我们也不用多想,照着一个字母,一个字母的把示例敲进去就好了:
- 选择喜欢的文本编辑器,如Vim hello.kt,Kotlin的文件扩展名是*.kt,我们遵循就好。
- 一字不差的敲进去:
package hello
fun main(args: Array) {
println("Hello, world")
}
然后,保存文件
- 回到命令行,编译源码,如果一切顺利会得到一个叫hello.jar的文件,这就是kotlin的最终输出,也就是它的目标文件.
kotlinc hello.kt -include-runtime -d hello.jar
- 运行,这里跟Kotlin其实已经没啥关系了,因为经过编译得到的是一个标准的Jar文件,像运行其他jar一样运行就好了:
java -jar hello.jar
就会得到输出Hello, world到此,第一个Kotlin程序已经完成,是不是很酷,已经迫不及待的想深入学习了!往下看吧。
The basics
语句结构
一行一个语句(先不纠结语句与表达式的区别),不用加分号,不用打分号,光这个就可以节省多少时间呢?是不是感觉人生都浪费在了分号上面。如果想在一行写多个语句,前面的要加上分号。
缩进规则与Java一致,用四个空格,也可以用tab,或者不加缩进,只要没人打你。
语句块需要加上花括号{}。总之,语句结构与Java很类似。
变量
用var来声明变量,用val来声明常量,因为Kotlin是静态强类型语言(也就是说每个变量在编译的时候必须知道类型)声明时需要带上类型,方法是在变量名的后面加冒号,空格跟上类型名字,与Pascal差不多。如果声明时直接定义,则可以不用指定类型,编译器会根据定义表达式来推测它的类型。示例:
var str: String
val i: Int
var str = "Hello, world"
语句和表达式
主要想说一下语句和表达式的区别,简单来说就是表达式是有值的,可以放在变量赋值的右边,而语句是没有值的,不能放在赋值的右边
基本运算
不多说了,跟Java一样
注释
这个跟Java也一样:
// 单行注释
/* / 多行注释
/* */ documentation
函数
以fun关键字来定义一个函数格式为:fun 函数名(参数): 返回类型 {函数体},如:
fun foo(name: String): Int {
return name.length()
}
命名参数和默认值,调用函数时可以把参数的名字带上,以增加可读性。声明函数时可以用默认值 ,以更好的支持函数的重载。如:
fun foo(name: String, number: Int = 42, toUpper: Boolean = false): String {}
使用时,可以指定参数的名字:
foo("a)
foo("b", number = 1)
foo("c", toUpper = true)
foo(name = "d", number = 2, toUpper = false)
表达式体如果一个函数体内只有一个表达式,且有返回值时,那么,可以直接把返回值放在函数 的后面,如:
fun foo(name: String): String = name.toUpperCase()
甚至还可以把返回类型的声明给省略掉,如:
fun foo(name: String) = name.toUpperCase()
跟Java不一样的是,Kotlin的函数可以声明为toplevel也就是跟class一个级别,也就是说不必非放在类里面,也就是说跟C和C++是类似的。此外,还可以函数赋值给一个变量,这个变量就像其他变量一样。
类与对象
类的声明与对象创建
用class来声明一个类型,用:来继承父类或者实现接口,不需要使用new来创建对象:
class Person {
var name: String
var age: Int
}
假如,一个类,是空的,没有内容,那么花括号{}是可以省略的:
class Person
创建对象:
var someone = Person()
Primary constructor
构造方法,有所谓的primary constructor,可以直接写在类名的后面:
class Person constructor(name: String)
一般情况下,constructor 可以省略掉:
class Person(name: String)
初始化块因为primary constructor不能包含代码,所以,想要做些初始化工作就可以放在初始化块里面(initializer block),也可以在定义属性时直接使用:
class Person(name: String) {
var firstName: String = name
init {
println("First initializer block that prints ${name}")
}
}
一般情况下,如果声明的属性变量在primary constructor中都有赋值(通过initializer block)的话,可以有更简洁的表达方式:
class Person(var name: String, var age: Int)
这相当于:
class Person(theName: String, theAge: Int) {
var name: String = theName var age: Int = theAge
}
如果primary construct前面要声明属性,或者有annotation的话,关键字constructor不能省略:
class Person public @Inect constructor(var name: String)
Secondary constructor
如果primary constructor不能满足需求怎么办呢?还可以声明其他constructor,所谓的secondary constructor:
class Person {
var name: String constructor(name: String){
this.name = name
}
}
是不是看起来舒服一些,因为跟Java一样了,可以把primary constfuctor和second constructor联合起来一起用:
class Person(var name: String) {
constructor(name: String, parrent: Person) : this(name) {
parrent.addChild(this)
}
}
这里要把secondary construct尽可能delegate到primary constructor,这里的delegate的意思就是primary constructor会在second constructor之前 执行,还有就是initiailzer block都是在primary construct中执行的,这就能保证initiliazer block在second constructor之前执行。即使没有显示的声明primary constructor,编译器还是会生成一个默认的primary constructor以及把secondary constructor默认的delegate到primary constrcutor上面。也就是说,会保证primary constructor以及initializer block执行在second constructor前面:
class Constructors {
init {
println("Initializer block")
}
constructor(i: Int) {
println("second constructor")
}
}
fun main(args: Array) {
val c = Constructors(3)
}
输出:
Initializer block
second constructor
属性和访问方法
Kotlin会为声明的属性生成默认的setter和getter:
class Person(var name: Strring, var age: Int)
val p = Person("Kevin", 24)
p.getName() // 返回"Kevin"
p.setAge(32) // age变成了32
如果想自定义setter和getter,也是可以的:
class Person {
var name: String
set(n: String) {
if (n == null || n == "") {
name = "Unkown"
} else {
name = n
}
}
get() {
if (name == "Unkwon") {
return "Nobody"
}
return name
}
}
定义类的方法
跟声明普通函数一样,只不过是放在了类里面:
class Person(val name: String, val age: Int) {
fun report() = "My name is $name, and I'm $age"
}
如果,要覆写父类的方法,需要使用在方法声明时加上override关键字。
class Doggy(val name: String) : Animal {
override fun yell() = "Barking from $name"
}
访问权限
访问权限也跟Java类似分为public,protected,private以及internal,前三个意义也都一样,只不过默认值不一样,在Java里,如果对成员没有指明,则是package scope,也就是同一个package可以访问,但是Kotlin默认是public的。
internal是module内部可见,有点类似于Java中的package,但是module定义跟package不一样,module是一组编译在一起的Kotlin文件,它跟编译打包有关系,简单的理解它的范围要比package要大。
还有就是类,默认是不可被继承的,相当于final class。如果想要允许继承就要在声明类的时候加上open。
字串
概念就不说了,大部分与Java一模一样的,像支持的方法等。唯一需要说的就是字串模板,就是说把其他类型转化为字串时,有较Java更为方便的方式:直接用$来把变量嵌入到字串之中,如:
val msg = "Error 1"
val count = 32
print("We got message $msg") //等同于"We got message " + msg
print("Total is $count") // Total is 32
lambda表达式
首先要介绍一个概念,高阶函数,其实就是把另外函数当作参数的函数,或者说产生一个函数,也即把函数作为返回值 的函数。前面说过,函数是一级对象,可以像常规变量一样来使用,所以,就能把函数作为参数或者返回值来使用高阶函数。lambda表达式就是为高阶函数更方便使用而生的。
lambda 表达式
作为新时代的编程语言,都会支持函数式编程,而lambda表达 式又是函数式编程里面必不可少的一份子。其实啥是lambda表达式呢?说的简单点就是没有名字的函数,非常简短的,通常都是一两句话的没有名字的函数。就是长这个样子{A, B -> C},这里面A,B是参数,C是表达式,如:
val sum = { x: Int, y: Int -> x + y }
其中,参数的类型是可以省略的,因为编译器能从上下文中推测出来:
max(strings, { a, b -> a.length < b.length }
表达式部分,可以不止一个,最后一个表达式作为返回值。
当把一个lambda表达作为最后一参数,传给某个函数时,可以直接把lambda表达式写在参数的外面,比如:
val product = items.fold(1) { acc, e -> acc * e }
而当lambda是唯一的参数时,也可以把参数的括号省略掉:
run { println("Hello, world") }
还有就是,如果lambda表达中只有一个参数,那么参数也可以省略,直接写表达式:
eval{ x * x }
函数类型
前面提到了函数是可以像普通变量一样使用的一级类,也就是说它是一个类型。它的具体形式是: (A, B)->C,其中括号内的是参数,C是返回类型,如:
val sum: (Int, Int)->Int = { x, y -> x + y }
val square: (Int)->Int = { x -> x * x }
为啥要提一下函数类型呢,因为有时需要声明高阶函数:
fun walk(f: (Int)->Int)
fun run(f: ()->Unit)
Unit是一个特殊的返回值,相当于void,意思就是此函数没有返回值。
集合
其实大部分跟Java是一样的。只不过有一些函数式的操作,要多注意使用,从而让代码更简洁,如:
- 遍历
- 过滤
- 映射
- 排序
- 折叠
- 分组
- 归类
这些操作,对于大家应该都不难理解,就不一一解释了,来断代码就知道了:
fun collectionTests() {
val list = listOf("Apple", "Google", "Microsoft", "Facebook", "Twitter", "Intel", "QualComm", "Tesla")
// 遍历,以进行某种操作
list.forEach{ println(it) }
//按条件进行过滤,返回条件为true的
val short = list.filter { it.length < 6 }
println(short) // [Apple, Intel, Tesla]
// 把列表元素映射成为另外一种元素
val lenList = list.map{ it.length }
println("Length of each item $lenList") //Length of each item [5, 6, 9, 8, 7, 5, 8, 5]
// 按某种条件进行排序
val ordered = list.sortedBy { it.length }
println("Sorted by length $ordered") // Sorted by length [Apple, Intel, Tesla, Google, Twitter, Facebook, QualComm, Microsoft]
// 折叠,用累积的结果继续遍历
val joint = list.fold("", {partial, item -> if (partial != "") "$partial, $item" else item })
println("Joint list with comma $joint") // Joint list with comma Apple, Google, Microsoft, Facebook, Twitter, Intel, QualComm, Tesla
//分组,用某种条件 把列表分成两组
val (first, second) = list.partition { it.length < 6 }
println("Length shorter than 6 $first") // Length shorter than 6 [Apple, Intel, Tesla]
println("Longer than 6 $second") // Longer than 6 [Google, Microsoft, Facebook, Twitter, QualComm]
// 归类,按某种方法把元素归类,之后变成了一个Map
val bucket = list.groupBy { it.length }
println("$bucket is a map now") //{5=[Apple, Intel, Tesla], 6=[Google], 9=[Microsoft], 8=[Facebook, QualComm], 7=[Twitter]} is a map now
}
null处理
为了有效的减少空指针异常,Kotlin加入了Nullable类型,核心的原理是这样的:声明类型的时候要明确的告诉编译器,这个变量是否可能为null,如果可能为null,那么可以赋null给这个变量,并且在使用此变量时必须检查是否为null;假如这个变量不可能为null,那么是不可以赋null给此变量的。也就是说,编译器会帮忙做一些检查,以减少NullPointerException的发生。
Nullable变量
默认的变量声明都是不可为null的,如:
var safe: String
safe = null // 会有compile error
要想允许变量为null,要在类型后面加一个问号,以告诉编译器这是一个nullable类型:
var danger: String?
danger = null // OKay
使用时,nullable不能直接使用,必须检查是否为null:
safe.length // okay
danger.length // compile error, danger could be null
检查Nullable的真伪
可以用传统方式:
val len = if (danger != null) danger.length else -1
Safe call
既然有Nullable类型,自然就有配套的方式来更方便的使用它:
val len = danger?.length
如果danger是null就返回null,否则返回长度,注意它的返回值是一个Int?(又是一个Nullable类型)。这个还能链起来:
bob?.department?.head?.name
如果任何一环为null,则直接返回null。是不是感觉省了好多if (a == null)判断。
Elvis operator
假如不能接受safe call返回的null,咋办呢?想提供默认值的呢?也有方式:
val len = danger?.length
println(len ?: -1)
稍有点绕哈,首先,danger?.length返回一个Int?吧,那么?:的作用就是如果len是null,那么就返回-1,否则返回它的值。
强制取值符!!
它的作用是如果Nullable变量为null就抛出NullPointerException,如果正常的话就取其值,返回的类型是一个non-null类型:
val len = danger!!.length // get length or NullPointerException
尽管,编译器可以帮助我们做一些事情,但是现实的项目中的大量的NPE并不是直接来源于,可以方便追踪的赋值为null,而多是发生在多线程环境中,以及非常复杂的逻辑之中,编译器能否追踪到并警示,还有待考察。另外,就是虽有利器,但是要运用恰当,何时用允许null,何时不允许,还是要靠工程师的设计能力,比如尽可能返回空列表,空Map,或者空字串,而不是直接简单的返回null,这就能减少一定的NPE。
Exercises
光是看书或者看教程是比较乏味的,学习编程最重要的是要上手去练习,这样能加深印象,更好的理解书中或者教程中所讲的概念和知识点。官方也准备了一个非常好的练习项目叫Kotlin-koans,非常适配初学习者来练手。
下面说一下如何使用这个练习项目:
- 到官网去下载后,解压
- 用Android Studio打开此项目,一切提示都回答yes
- 要想运行测试前需要先编译一下项目,否则会提示找不到基础的测试类,找到Gradle窗口,一般在右侧,点开找到kotlin-koans->Tasks->build->build,运行它
- 现在就可以用先进的TDD方式来学习Kotlin了,在Project视图下面,可以看到kotlin-koans项目,里面有两个,一个是java,一个是tests,这两个目录里面的子目录都是一一对应的,先运行tests下面的,会失败,然后编辑java/下面的对应的代码,直到测试通过。
Essence of Kotlin
致此,我们可以看出Kotlin这门语言的设计的核心理念:简洁,这是Kotlin的核心理念,所以我们看到,一些机械的,重复的,可以从上下文中推测 出来的都 可以省略,以增加可读性。我们在使用Kotlin的时候要践行此理念,把语言的特性发挥到最大。
当然,简洁,不是牺牲可读性的方式来缩短代码,而是要使用语言中的标准的简洁的表达方式,比如lambda表达式,省略参数等。
要注意参考Kotlin conventions以及Android Kotlin conventions以写出更加简洁和容易理解的代码。
Android dev setup
我们来新建一个项目,用纯Kotlin实现一个Hello, world Android应用,来展示一下如何在Android中使用Kotlin:
注意: 这里使用的是Android Studio 3.1.2版本,默认就支持Kotlin,如果使用小于3.0的版本需要安装Kotlin插件,可自行Google,孤还是建议先升级AS吧。
-
新建一个项目,其实流程跟新建一个普通Android Studio项目是一样一样的,从Android Studio3.0起,新建项目时就会有一个Checkbox,问你要不要添加Kotlin。这里把它选上。
-
就直接下一步就好
-
Next,创建一个empty activity
- Finish
-
布局跟其他新建的Android项目无差别
-
代码已经是Kotlin的了
- 直接显示"Hello, world"略显无聊,所以加一下点击事件:
class HelloActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hello)
val colorTable = listOf("#ff0000", "#00ff00", "#0000ff", "#ffff00", "#00ffff", "#ff00ff")
val label = findViewById(R.id.label)
label.setOnClickListener { view ->
val randomIndex = (Math.random() * colorTable.size).toInt()
view.setBackgroundColor(Color.parseColor(colorTable[randomIndex]))
}
}
}
其实,整体来看,布局和项目的结构还是按照Android的方式来,唯一的不同是代码可以用Kotlin来写了。
Good to go
至此,Kotlin就算入门了,可以使用Kotlin来构建应用程序了,或者在你的项目中应用Kotlin了。
参考资料和有用的资料分享
- 官方文档
- Awesome Kotlin Resources
- Kotlin and Android
- Resources to Learn Kotlin
- Learn Kotlin in Y minutes
原文链接:http://toughcoder.net/blog/2018/05/17/introduction-to-kotlin-programming-language/