接触Kotlin是因为想实现一个手机APP,又不想去碰Java,于是通过tutorial尝试入门看看。
其实关于手机APP的想法已经暂时放弃了,对Kotlin的尝试也停止了。把当时记录的笔记记在这里,方便以后再捡起来的时候快速回忆起来。
hello world
fun main() {
println("Hello, world!")
}
变量
// 声明常量用 val
val procorn = 5
// 声明变量用 var
var customers = 10
字符串模板
在字符串中引用变量与shell脚本类似
val customers = 10
println("There are $customers customers")
println("There are ${customers + 1} customers")
基本的数据类
Byte,Short,Int,LongUByte,UShort,UInt,ULongFloat,DoubleStringCharIntRangeBoolean
复合数据类型
列表(List)
集合(Set)
映射(Map)
// 声明只读的列表使用 listOf
val readOnlyShape = listOf("triangle", "square", "circle")
// 声明可变的列表使用 mutableListOf
val shapes: MutableList<String> = mutableListOf("triangle, "square", "circle)
println(shapes)
// [triangle, square, circle]
列表支持的方法
.first(), .last(), .count(), .add(anObject), .remove(anObject)
检测某个对象是不是在列表中
val readOnlyShapes = listOf("triangle", "square", "circle")
println("circle" in readOnlyShapes)
集合是无序的,只能包含非重复对象,同样分为只读和可变的集合
val readOnlyFruit = setOf("apple", "banana", "cherry", "cherry")
val fruit: MutableSet<String> = mutableSetOf("apple", "banana", "cherry", "cherry")
println(fruit)
// [apple, banana, cherry]
映射相当于字典,或者是哈希表, 同样分为只读和可变的
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
var juiceMenu: MutableMap<String, Int> = mutableMapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println(juiceMenu)
// {apple=100, kiwi=190, orange=100}
访问映射成员的语法和Python的字典一样,使用[aKey]操作符。当键不存在时,得到null值。
从映射中删除某个键可以调用 .remove(aKey)方法
.count()方法返回计数
访问 .keys 和 .values 属性可以得到键和值的列表,注意,是属性而不是方法。
containsKey(aKey) 进行成员测试,返回 Boolean 值。
containsKey方法只能测试键在不在映射中,使用in配合keys, values可以测试键和值是否在映射中
aKey in aMap.keys
anObject in aMap.values
控制流
if
语法上,Kotlin的if与C系列的语言相同。语义上,Kotlin的if是表达式而不是语句。
if (aBoolean) {
...
} else {
...
}
val a = 1
val b = 2
println(if (a > b) a else b)
// 2
when
when类似于switch
val obj = "Hello"
when (obj) {
"1" -> println("One")
"Hello" -> println("Greeting")
else -> println("Unknown")
}
//-> Greeting
和if一样,when也是表达式而不是语句。
when的分支条件不仅仅是简单的对象比对,它似乎支持完整的布尔运算(或模式匹配?)。
区间
使用..运算符可以生成一个区间:1..4,得到的区间包括1,2,3,4
如果要得到一个不包括结束值的区间,使用..<代替,1..<4得到的区间是1,2,3。这个操作和Python的range()函数基本等价了.
降序的区间使用downTo: 4 downTo 1得到区间4, 3, 2, 1
区间支持.random()方法,随机选择一个元素
循环
for in
for (i in 1..5) {
print(i)
}
//-> 12345
for (cake in listOf("carrot", "cheese", "chocolate")) {
println("Yummy, it's a $cake cake!")
}
// Yummy, it's a carrot cake!
// Yummy, it's a cheese cake!
// Yummy, it's a chocolate cake!
while && do-while
函数
fun functionName(arg1: type1, arg2: type2) : returnValueType {
...
return anObject
}
关键字参数
调用函数时可以使用关键字传递参数, 使用关键字传递参数可以打乱参数顺序。
f(arg1 = anObject, arg2 = anObject)
默认值
在定义函数时可以定义默认值
fun funcName(arg1: type1, arg2: type2 = defaultValue) {
...
}
没有return的函数会返回一个特殊的值Unit
单行函数
fun sum(x: Int, y: Int) = x + y
所谓单行函数其实是个语法糖,语法糖过多,会提高代码阅读时的认知成本。既然有了lambda表达式,就没必要再提供单行函数了吧,只是少打了几个字而以。
val sum = {x: Int, y: int -> x + y}
lambda
lambda表达式相当于javascript的箭头函数,区别是参数不需要括号,函数体不需要花括号,整个表达式都包在花括号中。
lambda以最后一个表达式的值作为返回值,不需要的return
val upperCaseString = { text: String -> text.uppercase() }
lambda 表达式是典型的函数式编程特征。lambda表达式可以赋值给变量,可以作为参数进行传递,也可以作为函数的返回值。调用lambda的方法和调用函数一样,使用()进行调用和传递参数.
面向对象编程
类的实例变量可以在两个地方定义:
// 定义在类名称后面的参数表中
class Contact(val id: Int, var email: String)
// 定义在类后面的花括号中
class Contact(val id: Int, var email: String) {
val category: String = ""
}
在上面的 Contact(val id: Int, var email: String)隐含地定义了一个与类同名的构造器函数,同时定义了两个实例变量以及两个初始化参数id和email。在构造器的参数表中,加了val或var前缀的参数自动成为实例属性,交者是常量,而后者是变量。并且在调用构造器时从参数中得到初值。至于定义在类体中的实例变量,则只能通过其它方式初始化。
和函数类似,类的实例变量也可以有默认值,通过默认值可以实现可选参数。通过可选参数,又可以有效避免使用复杂的构造器函数重载。
val contact = Contact(1, "mary@gmail.com")
除了默认的构造器,Kotlin还可以定义其它的构造器, 不同的构造器主要表现在参数的不同上,其实可以通过可选参数在单一构造器中达到相同的目的。
在类体中{},可以定义独立的初始化逻辑init { ... },还可以定义其它的实例变量,也可以定义方法。实例变量默认是public的,并且提供了访问器(val属性只有getter, 没有setter)。
实例变量默认是public的,可以从外部直接访问。
定义成员函数(方法)与定义成员变量是一样的,直接在花括号里定义 fun 就行了。
继承
在Kotlin中,要让一个类可以被继承,必须显式地声明为open,可继承的类里面,也只有声明的open的实例方法才能被继承。继承的语法是在主构造器的参数表后面加冒号,接着调用父类的构造器(如果父类需要初始化参数的话要传递正确的参数),接下来才是类体。Kotlin的类定义对灵活性做了很多考量,但是带来的结果是复杂度上升。
一个完整的类定义可以写成下面的形式:
// 定义一个类作为超类,该类需要两个初始化参数 arg1, arg2
open class SuperClass(var arg1: Type1, var arg2: Type2)
// arg1 和 arg2 是超类的参数,不能加 var 或 val 前辍, 加了就冲突
// 在子类的构造器参数表后面立即调用超类的构造器,并且把接收到的 arg1 和 arg2传递给它
class ChildClass(arg1 Type1, arg2: Type1, var arg3: Type3, var arg4: Type4) : SuperClass(arg1, arg2) {
// 初始化逻辑写在下面
init {
....
}
// 附加的构造器,必须先调用主构造器并传递必要的参数
constructor(args) : this(args) {
...
}
// 方法定义
fun aMehtod(args) {
....
}
}
事实上,上面的写法也不完整,而是加了糖的。class ClassName(args)形式的主构造器,其实是class ClassName constructor(args)的简写. Kotlin真正的类定义语法形式是:
open class ClassName constructor(args) : SuperClass(args) {
init { ... }
constructor(args) : this(args) { ... }
...
fun aMethod(args) { ... }
...
}
data类
data类自动拥有某些成员函数,比如.toString(), .equals(), .copy()
data class SomeClass(...)
Null值安全
在kotlin中有专门保护Null值安全的机制,该机制是在编译时,而不是运行时检测潜在的问题。
该机制允许你:
在有可能出现
null的地方需要明确地声明检查
null值对可能产生
null值的属性或函数使用安全调用声明在遇到
null值时要采取的动作
声明允许为null的值
在类型后面加上一个问号
var neverNull: String = "This can't be null"
var nullable: String? = "You can keep a null here"
nullable = null
// 下面的代码在编译时会报错
var x = null
// 下面的函数不接受 null 值参数
fun strLength(notNull: String): Int {
return notNull.length
}
println(strLength(neverNull)) // 18
println(strLength(nullable)) // 抛出编译错误
安全访问
要安全地访问可能包含null值的对象属性,可以使用?.操作符,如果对象或被访问的属性为空,则返回null,而不是报错。
fun stringLength(s: String?): Int? = s?.length
安全访问可以级联,只要某一级遇到null就提前返回
person.company?.address?.country
Elvis操作符
当遇到null值时,可以用Elvis操作符?:返回一个替代值
nullString?.length ?: 0
如果nullString为null,则返回0
Kotlin 的 Null 安全把原本依赖单元测去发现的问题移到了编译期,通过编译器的静态分析来检测。但是静态分析并不是完全可靠的。在某些情况下,你已经从逻辑上处理了所有可能出现空值的地方,但是编译器仍然会抱怨有空值没有处理。在这种情况下,可以在变量名后面使用非空断言!!来让编译器闭嘴。
评论