val a: Int = 0
var b: String = "Kotlin"
val a = 0
val b = "Kotlin"
空值安全检查
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
var url = urlList.get(pos) ?: return null
val url = if(pos < urlList.size()) urlList.get(pos) else return null
var linearLayout = viewGroup as? LinearLayout
没有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一样
跳转语句
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表达式的作用相当于一个函数,但可以大大地简化代码,特别是在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, off: Int = 0, len: Int = b.size()) {}
内联函数:即把函数调用语句直接替换成函数体,省去了调用函数的时间开销,当增加了空间开销,故一般只对函数体较小的函数进行内联
关键字:inline
inline fun add(x: Int, y: Int): Int = x + y
add(3, 5) // 调用语句被替换成 3 + 5
中缀函数
关键字:infix
infix fun Int.shl(bitCounts : Int): Int{}
1 shl 2 // 跟 1.shl(2) 相同
尾递归函数: 编译器会对尾递归函数进行优化以获得更好的性能
tailrec 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的抽象的规则基本一致:
接口
同样使用 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) {
// ...
}
}
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
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, 暂时不细说了,有兴趣看官方文档
这个真的非常非常好用,特别拿出来说。
这里把扩展函数放在了函数这一块。
还在为了给类扩展一个方法而写多一个子类吗?好吧,再也不用这么做了,让kotlin 扩展函数来帮你解决。下面直接用一个例子说明。
// 扩展函数是用类名.函数名来定义的,
// 扩展后该类的所有实例都能够使用该函数
fun MutableList.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
扩展属性和扩展函数定义,使用和性质都基本一样,就不细说了
参考kotlin官方文档