该系列的博文是自己学习Kotlin的学习笔记整理,以备忘与回顾。整理来源 - 疯狂Koltin讲义
Kotlin预备知识
1、kotlin 是一门强类型语言;
2、kotlin-stdlib.jar是Kotlin运行时环境
Kotlin可以自由调用Java的各种库,因此使用kotlin调用Android应用程序的Framework层开发应用程序甚至无序额外的学习,可以无缝的过渡到使用kotlin开发。
kotlinc (kotlin-jvm) -> Xxxx.class
kotlin-js -> js代码(前端或node.js代码都可以)
3、Kotlin程序与Java程序不同,Kotlin支持函数式编程,且在Kotlin语言中,函数也是一等公民。因此函数可以独立存在,即Kotlin程序只需要一个main函数作为程序入口,不需要将该main函数放在任何类中;
所谓"第一等公民"(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。
举例来说,下面代码中的print变量就是一个函数,可以作为另一个函数的参数。
var print = function(i){ console.log(i);};
[1,2,3].forEach(print);
4、kotlinc(或kotlin-jvm)->XxxKt.class 或 Xxx.lass
kotlinxxxtlin文件
基础类型
1、与Java不同,Kotlin并不强制要求每条语句必须以分号结尾(前提是一行只有一条语句),如果一行有多条语句,则前面的语句需要使用分号表示结束。
2、kotlin中的[]不只可以访问数组,还可以访问List和Map。
3、var 变量 val 常量;
var | val 变量名[:类型] [=初始值]
由于存在类型推断,程序要么通过":类型"的形式显式指定该变量的类型,要么为该变量指定初始值 - kotlin编译器将会通过该初始值确定变量的类型,即var a:Int或var a=5,不能声明变量时既不指定变量的类型,也不指定变量初始值。
使用val声明的不可变变量其实相当于常量,意味着一旦初始化就不能再重新赋值。kotlin常量分为两种:
-
局部范围的常量:允许在声明时不指定初始值,只要第一次使用之前指定初始值即可。
-
类的常量属性:既可以在声明时指定初始值,也可以在类或结构体的构造器中指定初始值。
4、如果直接在Kotlin程序中定义变量、函数,kotlinc将会自动生成一个名为“文件名首字母大写+Kt”的类,并将变量转换为该类的静态的getter和setter方法,函数则转换为该类的静态方法。
5、四种整型:Byte/Short/Int/Long 不支持八进制
Kotlin的整型与Java的不同,Kotlin的整型是引用类型【大致相当于Java中的包装类】,都继承自Number类型。
Int -> int
Int? -> Integer 接收null值
比如:
var pm1 :Int = 200//相当于Java基本类型int
var pm2:Int = 200
pm1===pm2 true//基本类型比较,输出true
var pm3:Int? =200//相当于Java包装类型Integer
var pm4:Int? = 200
pm3===pm4 false//引用类型比较输出false
两种浮点型:Float和Double 十进制和科学计数形式
如果声明的变量初始值只是简单的赋值为浮点值,则kotlin自动判断该变量的类型为Double;
浮点型除以0.0会得到正无穷[正数/0.0]或负无穷大[负数/0.0]或非数[0.0/0.0 或 负数开方],整型除以0.0报错。
注意:正无穷大都相等,负无穷大都相等,非数不与任意值相等;
6、Kotlin中的Char型就是简简单单的字符型,不能直接当成整型使用;即Char型值不能赋值给整型变量,整型值也不能赋值给Char型变量;
7、与Java程序不同,Kotlin不支持取值范围小的数据类型隐式转换为取值范围大的类型
kotlin要求不同的整型的变量或值、Float与Double、浮点型与整型之间必须进行显式转换(toXXX),即缺乏隐式转换
当进行类型转换时,尽量向表数比较大的数据类型转换,避免出现溢出。
8、虽然Kotlin缺乏隐式转换,但是Kotlin在表达式中可以自动转换,这是基于上下文推断出来的,即当一个算术表达式中包含多个数值型值得时候,整个算术表达式得数据类型就会发生自动提升,其规则如下:
-
所有得byte和short类型将被自动提升到Int类型;
-
整个算术表达式得数据类型自动提升到与表达式中最高等级操作数同样得类型,即以最高级操作数得数据类型为准
Byte->Short-> Int -> Long -> Float ->Double
9、Char型值不能被当成整数进行算术运算,但Kotlin为char类型提供了加、减运算支持:
Char型加减一个整型值:用Char型值对应的字符编码进行加减,运算结束在转换为char;
两个Char进行相减:用Char型值对应的字符编码进行减运算,最后返回Int类型的值;
注意:两个Char型值不能相加。
10、Any类型时Kotlin所有类型的父类,Any中只有三个方法:equals、hashcode、toString
11、Boolean -> boolean,Boolean? - >Boolean接收null值
12、null安全
kotlin语言对Java的重大改进之一,避免了空指针异常
var str = "fkstr"
var c: Int = str.toIntOrNull()//编译不通过
var d: Int? = str.toIntOrNull()//编译通过,d的值是null,因为转换失败了,如果str = "123",则会转换成功。
kotlin对可空类型进行了限制:如果不加任何处理,可空类型不允许直接调用方法、访问属性。因此,通过可空类型与非可空类型的区分,Kotlin即可在程序中避免空指针异常。
var aStr: String = "a"
var bStr: String? = "b"
aStr = null//错误
bStr = null//正确
println(aStr.length)//正确
println(bStr.length)//错误
虽然,可空类型不允许直接调用方法、访问属性。但是可以先判断该变量不为null,再调用该变量的方法和属性。
var b:String? ="fk"
var len = if(b!=null) b.length else -1
13、安全调用(?.)
user?.dog?.name
如果user不为null,则返回user的dog属性,如果dog不为null,则返回dog的name属性值,反过来,如果user或user.dog为null。则表达式也不会报空指针,整个表达式会返回null。
var str1: String? = "fkStr"
println(str1?.length)//5
str1 = null
println(str1?.length)//null
-->可为空的变量名 + ?. 就是安全调用
14、Elvis运算(?:)
如果?:左边的表达式不为null,则返回左边表达式的值,否则,返回?:右边的表达式的值
var len = if(b!=null) b.length else -1
var len = b?.length ?: -1;
由此可见,?:其实是if else分支的简写形式
15、强制调用(!!.)
不管是否是null,直接调用该变量的方法或属性,这样的调用可能会引发空指针异常
var b:String?="ss"
println(b!!.length)//2
b=null
println(b!!.length)//空指针异常
16、字符串(String)/字符串模板
可以通过str[i]这样的方式访问指定索引处的字符;通过for循环遍历字符串中的每一个字符
Kotlin的字符串有两种字面值:
-
转义字符串:可以理解为Java中的字符串
-
原始字符串:用三个引号引起来的部分都是字符串内容,包括换行符等各种特殊字符,比如 """ |hahah |heheheh |aaaaa """,可以通过trimMargin去除空格,默认使用|作为边界符。也可以使用其他的作为边界符,比如^,trimMargin("^"),""" ^hahah ^heheheh ^aaaaa """
注意:Kotlin中的String和Java中的String不是同一个类,因此提供的方法略有不同,但基本一致。可以说kotlin提供了更多的方法,比如toXxxx将字符串转换为数值:var a:String ="2.13" a.toDouble()等。
字符串模板是允许在字符串中嵌入变量和表达式,只需将变量和表达式放入到${}中即可。
println("${str}的长度是${str.length}")
17、typealias类型别名,在kotlin集合中使用的比较多;
typealias 类型别名 = 现有的类型
比如:typealias kotlin.ArrayList<E> = java.util.ArrayList<E>
运算符和表达式
kotlin基本支持Java的全部运算符,但不支持三目运算符(if可以代替);
kotlin的运算符都是以方法的形式来实现的,这些运算符都具有特定符号(如+ 或 * )和固定的优先级。每种运算符对应的方法名都是固定的,因此我们可以通过运算符重载(operator)来实现任意两个对象之间的运算。
1、== 相当于 java 的 equals,比较内容 , === 相当于 java的==,比较引用地址。比较运算符>,<,>=,<=基于CompareTo实现。
2、kotlin提供了与Java功能完全相同的位运算符,但这些位运算符都不是以特殊符给出的,而是以infix函数的形式给出的。kotlin的位运算符只能对Int和Long两种数据类型起作用。
3、区间运算符
==>左边界均不能大于右边界,否则报错
闭区间运算符:a..b,包含左右边界值的区间,2..5 -> 2 3 4 5,如果a,b的值相等,则产生一个值只包含一个值的区间
半开区间运算符:a until b,用于定义一个从a~b(包括a边界值,但不包括b边界值),如果a,b的值相等,则是一个空区间
反向区间:a downTo b,此时要求b不能大于a,6 downTo 2
区间步长:step,比如for(num in 7 downTo 1 step 2)则输出的值为1 3 5 7
4、运算符重载
kotlin的运算符都是靠特定名称的方法支撑的,因此只要重载这些名称的方法,我们就可以为任意类添加这些运算符。重载运算符的方法需要用operator修饰符进行标记。
data class Data(var v1: Int, var v2: Int) {
两种方式:
a、普通方式
operator fun unaryPlus(): Data {
return Data(-v1, -v2)
}}
b、扩展函数方式
operator fun Data.unaryPlus():Data{
}
fun main(args: Array<String>) {
val d = Data(1,2)
println(-d)
}
流程控制
1、分支:if、when(替代Java的switch,功能更强大)
循环:while、do-while、for-in,抛弃了Java原有的普通的for循环。
if表达式:if分支还可以作为表达式使用,整个if表达式最终会返回一个值,因此if表达式可以取代Java的三目运算符
var age = 20
var ageStr = if (age > 20) "大于20岁" else if (age < 20) "小于20岁" else "等于20岁"
when分支语句:
var score = 20
when (score) {
10 -> {
println("10")
}
20 -> {
println("20")
}
else -> {
println("默认")
}}
相比较switch,when有三个小改进:
- 可以匹配多个值;10,20->{......}
- 分支后的值不再要求是常量,可以是任意表达式;str[0]-4,str[1]-4 ->{......}
- 分支对条件表达式的类型没有任何 要求;Date()->{......}
when表达式
when分支也可以作为表达式,符合条件的分支的代码块的值就是整个表达式的值。
当when语句作为表达式使用时,when表达式也需要有一个返回值。
var score = 20
var str3 = when (score) {
10 -> {
println("10")
"10"
}
20 -> {
println("20")
"20"
}
else -> {
println("30")
"30"
}}
最终str3的值就是“20”
==>以下均为与表达式
when与in
var str = when(age){
in 10..25->{"花儿"}
...
else -> {
.....
}}
when与is
fun realPrice(price:Any)=when(price){
is String ->{}
...
else->{}
}
when与条件分支:用于取代if...else if,此时不需要为when分支提供任何条件表达式,每个分支条件都是一个布尔表达式,当指定分支的布尔表达式为true,则执行该分支。
var in = readLine()
if(in!=null){
when{
in.matches(Regex("\\d+"))->{}//每个分支都是一个布尔表达式
...
else->{}
}}
2、for-in
for(常量名 in 字符串|范围|集合)
说明:
a)for-in循环中的常量无需声明。循环中的常量会在每次循环开始时自动被赋值。
b)可遍历任何可迭代对象。
c)for-in循环的循环计数器相当于用val声明的常量,因此不允许被重新赋值,即常量名再循环里面是不允许开发者主动为其赋值
3、break与@标签
除了具备常见的break用法之外,break与@标签集合可以结束外层循环
//外层循环
outter@ for (item in 0 until 20) {
//内层循环
for (item2 in 0..6) {
if (item2 == 2)
break@outter
}}
当item2等于2的时候,整个循环就完全结束了
return结束一个方法:
fun test(){
for(i in 0 until 10){
if(i==2) return//当i==2的时候,整个方法就完全结束了
}}
数组与集合
1、数组Array<T>与集合【Kotlin为集合和数组提供了大量的扩展方法,具体参照数组,用法差不多】
Kotlin自己提供了一套集合体系,抛弃了Java集合体系中的Queue集合,但增加了可变集合和不可变集合的概念。Kotlin为数组增加了一个Array类,为元素为基本数据类型的数组增加了XxxxArray类。
Kotlin的集合由三种集合组成:
List、Set、Map
2、数组
引用类型,Array<T>代表数组,创建方式:
-
arrayOf()、arrayOfNulls()、emptyArray()等工具函数
-
Array(size:Int,init:(Int)->T)
arrayOfNulls<Double>(2)//创建指定长度、元素为null的数组【元素均被初始化为null】,相当于Java中的动态数组,由于无法推断元素类型,因此需要使用泛型指定元素类型;
emptyArray()//创建长度为0的空数组;
Array(5,{(it*26).toChar()})
此外,由于Array<T>要求它的元素必须是引用类型,因此,如果将基本类型的值存入Array<T>中,Kotlin将会完成自动装箱操作。
for-in循环遍历数组或集合元素,不允许对循环变量进行赋值。对数组使用for-in循环依然会被编译成使用基于索引的循环,并不会创建迭代器进行遍历,因此使用for-in循环数组依然具有很好的性能。(Kotlin底层将其编译成根据内存地址来访问数组元素)
更多知识点,参阅书籍
3、集合
Kotlin的集合类同样由两个接口派生:Collection和Map。Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。
与Java集合不同,Java集合都是可变集合,但Kotlin的集合被分为:可变集合和不可变集合。只有可变集合才能进行增删改操作,不可变集合只能进行读操作。
Kotlin为Collection接口派生了一个子接口:MutableCollection,该接口又包含了两个子接口:MutableSet和MutableList,代表了Kotlin的可变集合[可以理解为原有的Java集合]。而Collection直接派生的Set和List代表了不可变集合。
Set和MutableSet都有一个iterator()方法,但是普通的Set返回的iterator对象只包含hasNext和next两个方法;而MutableSet的iterator方法除了包含hasNext和next之外,还包含了remove方法,用于遍历的时候删除元素。
注意:Kotlin只提供了HashSet、HashMap、LinkedHashMap、LinkedHashSet、ArrayList这5个集合,而且他们都是可变集合。如果开发者想使用其他集合,则依然可以使用Java集合框架所提供的类。
note:Kotlin在JVM平台上并未真正实现HashSet、HashMap、LinkedHashMap、LinkedHashSet、ArrayList,只是为Java的HashSet、HashMap、LinkedHashMap、LinkedHashSet、ArrayList指定了一个类型别名而已,这样就可以借用Java的这些集合类。
由于Kotlin并未真正实现任何集合类,它只是借用了Java集合框架原有的集合类,因此想要了解底层实现分析性能,直接看Java的就可以了。
Kotlin的不可变集合类并没有暴露,我们只能通过函数来创建不可变集合。
Set集合都是无序的?错误!HashSet、HashMap是无序的,而LinkedHashMap、LinkedHashSet、TreeXxx都是有序的。
Kotlin规定以infix修饰的方法,能以运算符的方式进行调用。Set、Map等集合支持求并集、交集等方法均由infix修饰。
Set:
var data = setOf(1, 2, 3)//不可变,有序集合
var mutableData = mutableSetOf(1, 2, 3, 4)//可变,有序集合
var hashSetData = hashSetOf(1, 2, 4)//可变,无序集合
var linkedHashData = linkedSetOf(1, 2, 3)//可变,有序集合
var treeSetData = sortedSetOf(1, 2, 3)//可变,有序集合
Map:
var mapData = mutableMapOf("Java" to 90, "kotlin" to 88)//Kotlin使用to来指定key-value对,其中to之前的是key,之后的是value
println(mapData)//{Java = 90,Kotlin = 88}
for({key,value} in map)
for(en in map.entries)
for(keys in map.keys)
map.forEach({println("$(it.key)->${it.value}")})