kotlin 语言特性
- kotlin中一切皆为对象
- 没有像java一般的基本数据类型,数值类型为:Int, Float, Double等
- 函数也是对象,可作为参数和返回值
- 自然就会有高阶函数和lambda
语法特性及规范
- 语句结尾不用分号
- 使用驼峰命名,尽量避免下划线
- public函数应当有说明文档
- lambda中花括号内前后都应该有空格
一、变量
1. 变量声明:
- 只读变量:
val a: Int = 0
- 可变变量:
var b: String = "Kotlin"
在kotlin中,变量类型是写在变量名之后的,用英文冒号 : 隔开,类型声明不是必须的,如果后面的值是一个明显可识别的类型,就不必添加类型声明。
例如,以上声明可直接这么写:val a = 0
val b = "Kotlin"
2. 变量使用的一些特性
-
空值安全检查
var s: String? = null
当一个变量允许为空值时,必须显示地使用 ? 来进行声明,? 置于类型名之后,否则,在编写时该变量出现空值就会直接报错。
例如:var s: String = null
这一语句会直接报错,不用等到编译时检查 - 此外,对于nullable values, 可以在调用的时候进行安全检查,例如,有一个User类,要获取类中的name属性
if( user != null)
name = user.getName()
else
name = null
在kotlin中:
var name: String? = user?.getName()// 当user不为空的时候执行getName()
当然,在kotlin中的数据类并不需要显式调用getter和setter方法,通常情况下都只需要直接使用属性名:var name: String? = user?.name
-
还可以进行链式调用:
user?.name?.toUpperCase() // 当user和name都为null的时候表达式值为null
- Elvis Operator ?:
var url = urlList.get(pos) ?: return null
当?:左边表达式不为空时,返回表达式的值,否则执行?:右边表达式的值
也可以使用if… else…语句val url = if(pos < urlList.size()) urlList.get(pos) else return null
- 安全类型转换:
var linearLayout = viewGroup as? LinearLayout
正常情况下无法转换失败将抛出异常,进行类型安全检查之后不会抛出异常,而是返回了null,所以此时的接收变量也应当为nullable
二、语句
- if, else等条件判断语句与java相同
-
没有switch语句,取而代之的是when语句:
when(obj){ // 这里是lambda表达式 0, 1 -> println("zero or one") // 只有同种数据类型才能够这么写,应该是用泛型列表实现的 1 -> println("one") "hello" -> println("hello") is String -> println(obj) !is Long -> println("not a long value") else -> println("other value") // 相当于switch中的default,可以省略 }
可以看出,when语句可以可以接收多种数据类型,不像switch只能够接收int型(jdk 1.7以后支持字符串,但实际上是通过比较hashCode来实现的,实际上还是int) ,但也有不足之处,即不能够像switch那样顺序执行,遇到break才中断
-
循环语句
-
for循环:java中的foreach,(跟python几乎一样)
for(news in newsList){} for(idx in 1..array.lastIndex){} // ..表示范围,即可以用数字,也可以使用英文字母,如:a..z for(i in 1 until 100){} // 左闭右开区间,这里即不包括100 for(i in 1..100 step 2){} // step定义步长,这里为:1, 3, 5, ... , 99 for(i in 10 downTo 1){} // downTo 很容易理解,作用相当于--,但也可以和step结合使用
-
while和do…while语句和java一样
-
-
跳转语句
- break 和 continue,首先可以像Java里的那么使用
- kotlin里的break, continue和 return还可以使用label来跳转(怎么看着像go语句了)
这里只举使用label的例子:loop@ for (i in 1..100) { // 这里的loop是label名,可以自定义 for (j in 1..100) { if (...) break@loop } }
需要注意的是,使用label时,break, continue只能用在循环中,return不能用在循环中
三、函数
定义
函数定义使用fun关键字
fun add(val x: Int, y: Int) : Int /* 这里是返回值类型 */{
// 默认返回类型为Unit(不是null),没有什么实际意义,可以不用写
// 函数参数可以不用写val,但不能使用var
return x + y
}
上述函数中只有一个表达式,可以简写为: fun add(x: Int, y: Int) : Int = x + y
lambda
lambda表达式的作用相当于一个函数,但可以大大地简化代码,特别是在android中设置各种listener的的时候。
这里先用上面的例子来说明,上面的例子用lambda可以写成: val add: (Int, Int) -> Int = {x , y/*这里是参数*/ -> x + y}
这尼玛不是忽悠人吗,代码明明增多了还说减少
且慢,先看一下android中无比常用的setOnClickListener:
使用java:
button.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v){
...
}
})
再看看kotlin:
button.setOnClickListener{ v -> if(v.isEnable) v.isEnable = false }
怎么样,是不是简单了好多
等等,不对啊,怎么setOnClickListener方法没有参数了,变成了{},这是什么鬼
那是因为在kotlin中,
如果函数的最后一个参数是函数,那么这个参数可以直接写在圆括号外面(要用花括号),
如果只有一个函数参数,可以直接省略圆括号!
还有更简单的呢:
button.setOnClickListener{ if(v.isEnable) v.isEnable = false }
// 再lambda中,如果只有一个参数,那么可以使用it来表示,而不用写参数声明
各种单个函数当接口,如android中许多listener,runnable等都可以使用lambda表达式来简化
默认参数值函数
即可以为某个参数设定默认值,如:
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size()) {}
专用类型函数(没有这说法,标题我自己起的。。)
-
内联函数:即把函数调用语句直接替换成函数体,省去了调用函数的时间开销,当增加了空间开销,故一般只对函数体较小的函数进行内联
关键字:inlineinline fun add(x: Int, y: Int): Int = x + y add(3, 5) // 调用语句被替换成 3 + 5
-
中缀函数
关键字:infixinfix fun Int.shl(bitCounts : Int): Int{} 1 shl 2 // 跟 1.shl(2) 相同
-
尾递归函数: 编译器会对尾递归函数进行优化以获得更好的性能
- 要求:递归调用语句必须是函数最后一句
关键字: tailrectailrec fun findFixPoint(x: Double = 1.0): Double = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
- 要求:递归调用语句必须是函数最后一句
四、类和接口
-
基础杂项
-
定义及初始化:使用class关键字定义
// 写在类名后的constructor()是主构造方法(primary constructor) class User constructor(name: String){ init { //由于primary constructor中不能初始化(不能写表达式),一般初始化工作都写在init代码块中,init 是一个关键字 logger.info("User initialized with name: ${name}") } } // 构造方法没有参数可以省略constructor(),类没有具体实现可以省略{} // 可以写成: class User
除了primary constructor 外,其余构造方法称为secondary constructors,
上面都User类,用secondary constructor可以改写为:class User{ constructor(name: String){ logger.info("User initialized with name: ${name}") } } // 如果一个类有primary constructor,那么所有的secondary constructor都应该实现(或者说委托, delegate)primary constructor. class User constructor(name: String){ var name: String = "" constructor(name: String, age: Int): this(name){ // 这里的name和primary constructor的name不是同一个,跟成员变量name也不是同一个 } // 也可以直接赋常量值,反正目的就是初始化 constructor(age: Int, address: String): this("dongdong"){ } }
-
类属性
kotlin类属性默认都会自动生成get()和set(value)方法的,我们仅在需要指定条件时才重写。class A{ var name: String get() = "Tom" // 注意:这里一旦写成定值,set方法将失效,因为每次调用name实际上都是调用其get()方法的 set(value){ // 这里可以根据需要添加各种条件判断 } }
-
继承
由于kotlin中所有类和方法默认都是final的,不能直接继承或重写,需要继承的类或类中要重写的方法都应当在定义时添加open关键字open class Parent(name: String){ var name: String init{ this.name = name // 如果没有初始化,就必须声明为abstract // 也可以声明的时候进行初始化:var name = "dongdong" } fun showChildren(){} // 没有加open关键字,默认为final方法,不能重写,但可以重载 open fun showIdentity(){} // 可以重写或重载 } class Child(name: String): Parent(name){ override fun showIdentity(){} // 使用override关键字,习惯上写成一行,不像java是使用注解分开写 }
注意这里使用父类名+() 来表示了primary constructor,同样也可以使用super关键字来实现,亲测两种实现方法可以互换,这里的super跟java的super相似:
class Child: Parent{ constructor(name: String): super(name){} }
kotlin中所有类都有一个共同的父类: Any(注意这不是java.lang.Object), 这个类只有equals(), hashCode(), toString()方法。
kotlin与java一样,只能单继承,但可以实现多个接口。 -
抽象
kotlin的抽象与java的抽象的规则基本一致:- 使用abstract关键字
- 抽象方法必须声明在抽象类中
- 有抽象属性(即抽象变量,像上面说的那个name属性,这一点与java不同)
- 可以把非抽象父类的方法重新声明为抽象方法(倒是没试过java行不行)
- 非抽象子类必须实现抽象父类的所有抽象方法
-
-
接口
同样使用 interface 关键字
很奇怪,kotlin的接口跟Java抽象类几乎一样,可以定义有方法体的方法!- 接口可以多继承
- 接口中的属性不能初始化
-
Object
对于object 关键字,刚开始接触可能不太好理解,其实就是类似于java的匿名内部类,因为kotlin没有匿名内部类,不能这样创建实例:User("hello"){}
,主要用处有:- 当需要对一个类进行轻微的改动时,避免重新创建子类而使用
-
实现单例。
首先最基本用法:(这里使用listener举例,但实际的listener并不用这么写)
这时叫companion expression, 是即时加载的button.setOnClickListener(object: OnClickListener { override fun onClick(v: View?){} })
object关键字的声明也可以进行继承:
interface A{} object: Base("name"), A{}
单例实现(这个时候叫companion declarations, 是惰性加载的):
kotlin单例实现很简单,只需要使用object 关键字后面加一个单例的名称即可:object DefaultListener : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { // ... } override fun mouseEntered(e: MouseEvent) { // ... } }
- companion object
companion关键字仅有这个用法,companion object只能在类中使用,相当于java中的静态内部类(kotlin没有static关键字),这里摘取kotlin文档的例子:class MyClass { companion object Factory { fun create(): MyClass = MyClass() } } // 使用 var instance = MyClass.create() // 也可以使用下面的方法来调用 // 由于Factory定义后不可修改,故使用val变量来存放 val factory = MyClass.Factory var instance = factory.create()
- companion object
companion object后的名字可以省略(反正暂时我还是觉得没什么卵用, 直接通过类名使用即可了),省略后会有一个默认的名字Companion
companion object是在类加载的时候初始化的-
sealed class
新东西,看下文档先:Sealed classes are used for representing restricted class hierarchies, when a value can have one of the types from a limited set, but cannot have any other type. They are, in a sense, an extension of enum classes: the set of values for an enum type is also restricted, but each enum constant exists only as a single instance, whereas a subclass of a sealed class can have multiple instances which can contain state.
说白了,就是一个内部元素为类的枚举,并且内部的类都是本身的子类。sealed class 只能有一个实例,但是其子类跟普通的类一样,可以有多个实例,也可以在任何地方被继承。
sealed class 的直接子类必须放在sealed class内,但sealed class内部可以有任意类(但一般只放置其子类)。sealed class Expr { class Const(val number: Double) : Expr() class Sum(val e1: Expr, val e2: Expr) : Expr() object NotANumber : Expr() // 也可以: class Sub{} }
sealed class有什么用呢,可以发现,sealed class 和 when 更配哦:
fun eval(expr: Expr): Double = when(expr) { is Expr.Const -> expr.number is Expr.Sum -> eval(expr.e1) + eval(expr.e2) Expr.NotANumber -> Double.NaN // the `else` clause is not required //because we've covered all the cases }
-
data class
kotlin中有专门的数据类来代替java中的JavaBean,再也不用写JavaBean的getter和setter了data class News(title: String, time: String)
简单粗暴,连{}都省了,而且编译器还会自动生成该类的equals(), hashCode(), copy(), toString() 方法。
-
Delegated Properties
三种基本用法:lazy properties: the value gets computed only upon first access,
observable properties: listeners get notified about changes to this property,
storing properties in a map, not in separate field each.
即:
1. 延迟加载,仅在第一次使用时加载
2. 可被观察属性
3. 存储
要求,必须提供getValue()(和setValue(value), 如果是mutable的,即var变量)方法
class Example {
var p: String by Delegate()
}
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name} in $thisRef.'")
}
}
// 使用
val e = Example()
println(e.p)
标准库的delegates有lazy, Delegates.observable(), map, 暂时不细说了,有兴趣看官方文档
扩展函数及扩展属性(Extension Functions and Extension Properties)
这个真的非常非常好用,特别拿出来说。
这里把扩展函数放在了函数这一块。
还在为了给类扩展一个方法而写多一个子类吗?好吧,再也不用这么做了,让kotlin 扩展函数来帮你解决。下面直接用一个例子说明。
// 扩展函数是用类名.函数名来定义的,
// 扩展后该类的所有实例都能够使用该函数
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
// 函数调用
var l = MutableList<String>()
l.swap(0, 10)
这里为什么叫函数而不是方法呢?
首先要知道方法和函数的区别:其实没有实质上的区别,只不过当一个函数放在类或类实例中时称为方法(也就是面向对象语言的说法),而函数与类不相关(面向过程)。
可以猜出,这里的扩展函数并不是真正地在类中增加一个方法,从kotlin文档中也可以知道:
Extensions are resolved statically
Extensions do not actually modify classes they extend. By defining an extension, you do not insert new members into a class, but merely make new functions callable with the dot-notation on instances of this class.
We would like to emphasize that extension functions are dispatched statically, i.e. they are not virtual by receiver type. This means that the extension function being called is determined by the type of the expression on which the function is invoked, not by the type of the result of evaluating that expression at runtime. For example:
也就是说,扩展函数是静态加载的,与类或实例无关。
这里摘kotlin文档上的一个例子:
open class C
class D: C()
fun C.foo() = "c"
fun D.foo() = "d"
fun printFoo(c: C) {
println(c.foo())
}
printFoo(D()) // 结果是 c
因为默认函数却决于调用时声明的类型,而不是运行时计算出来的结果。
-
成员函数优先于扩展函数
再来一个官方文档的例子:class C{ fun foo(){ println("member") } } fun C.foo(){ println("extension") } C().foo() // 结果是member