一、Kotlin的内容
1.扩展函数和扩展属性
2.不可空类型和可空类型
使用Kotlin编程比Java更加安全,至少在空指针问题上写起来代码来会更加“开心”。Kotlin中引入了不可空类型与可空类型来明确声明一个变量是否可能为null,同时在编译期通过类型来明确声明一个变量是否可能为null,同时在编译期通过类型是否匹配来检查空指针异常,大大降低了空指针异常出现的概率。同时,Kotlin还提供了Evis操作符、安全调用符等极简的语法格式,使开发者从Java的null防御式编程中被释放出来。
var a = "abc"
a = null //就会报错
声明可空的String类型,可以这样写:
var b:String? = "abc" //todo 声明一个可空的string?类型
如果要调用可空对象的方法那就需要这样调用才行,如果对象为空,就返回null
b?.length //todo 使用安全调用符
3.一等函数支持
在Kotlin中函数是第一等类型:我们可以将函数像值一样传递,函数可以作为另一个函数的返回值。我们通常称之为“一等函数”支持。例如:
//todo 声明一个不可变的List
val list = listOf(1,2,3,4,5,6,7)
//todo 调用filter函数,传入一个Lambda表达式(it%3=0)作为参数[1,2,4,5,7]
list.filter{it%3!=0}
4.智能类型推断
在上面的诸多例子中,可以看到在声明变量的时候并没有显式指定它的类型。Kotlin编译器会自动推断出其类型。
二、编程哲学
Kotlin的定位是一种现代化工业语言:它专注于代码重用和可读性的弹性抽象,以及面向早期错误侦测和明确捕获维护与清理的意图这些问题的静态类型安全性。
Kotlin最重要的使用场景之一是对于一个庞大的Java代码库,其开发者需要一个更棒的语言:你能够将Java和Kotlin自由混合,迁移可以是渐进式的,不需要一下子对整个代码库进行改变。
三、Kotlin语法基础
1.变量和标识符
变量标识一个对象的地址,我们称之为标识符。而具体存放的数据占用内存的大小和存放的形式则由其类型来决定。
在Kotlin中,所有的变量类型都是引用类型。Kotlin的变量分为val(不可变)和var(可变的)。可以简单理解为:
val是可读的,仅能一次赋值,后面就不能被重复赋值
var是可写的,在它生命周期中可以被多次赋值。
只要可以,尽量在Kotlin中首选使用val不变值。因为在程序中大部分只需要使用不可变的变量,而使用val变量可以带来可预测的行为和线程安全等优点。
2.关键字与修饰符
通常情况下,编程语言中都有一些具有特殊意义的标识符是不能用作变量名,这些具有特殊意义的标识符叫做关键字,编译器需要针对这些关键字进行词法分析,这是编译器对源码进行编译的基础步骤之一。
Kotlin中的修饰符关键字主要分为:类修饰符、成员修饰符、访问权限修饰符、协变逆变修饰符、函数修饰符、属性修饰符、参数修饰符、具体化类型修饰符等。
abstract 抽象类
final 不可被继承final类
enum 枚举类
open 可继承open类
annotaion 注解类
scaled 密封类
data 数据类
成员修饰符
override 重写函数方法
open 声明函数可被重写
final 声明函数不可被重写
abstract 声明函数为抽象函数
3.流程控制语句
流程控制语句是编程语言中的核心之一,可分为:
分支语句
循环语句
跳转语句
3.1 if表达式
if...else语句是控制程序流程的最基本形式,其中else是可选的。在Kotlin中,if是一个表达式,即它会返回一个值。
fun main(args:Array<String>) {
println(max(1,2))
}
fun max(a: Int ,b : Int): Int {
//todo 表达式返回值
val max = if(a > b) a else b //todo直接使用if...else表达式来实现max逻辑
return max
}
fun max3(a: Int,b: Int,): Int{
val max = if(a > b){print("Max is a")} else {print("Max is b")}
return max
}
3.2 When表达式
When表达式类似于switch...case表达式。when会对所有的分支进行检查知道一个条件满足。但相比switch而言,when语句的功能要更加强大、灵活。
Kotlin的极简语法表达风格,使得我们对分支检查的代码写起来更加简单、直接
fun casesWhen(obj: Any?){
when(obj){
0,1,2,3,4,5,6,7,8,9 -> println("${obj} ===> 这是一个0-9之间的数字")
“hello” -> println("${obj} ===> 这是字符串hello")
}}
3.3 for循环
for循环可以对任何提供迭代器的对象进行变量
for(item in collection) {
print(item)
}
使用索引遍历一个数组,arraylist.indices表示数组的下标序列
for(i in arraylist.indices){
println(i)
}
使用withIndex方法来遍历下标和对应的元素
for((index,value) in array.withIndex()){
}
循环控制语句while、break、continue、return和java、c、c++是差不多的
标签label
在kotlin中任何表达式都可以用标签来标记
throw表达式
在kotlin中throw是表达式,它的类型是特殊类型Nothing。该类型没有值,与C、Java语言中的Void意思一样
2.4操作符和重载
Kotlin允许我们为自己的类型提供预定义的一组操作符的实现。这些操作符具有固定的符号表示和固定的优先级。这些操作符的符号定义如下:
操作符优先级:
最高:
(1)后缀:++,--,.,?,?
优先级依次递减:
(2)前缀:-,+,++,--,!,labelDefinithion@
(3)右手类型运算符:as,as?
(4)剩余取余:*,/,%
(5)加减:+,-
(6)区间范围:..
(7)Infix函数:
(8)命名检查符:in,!in,is,!is
(9)比较大小:<,>,<=,>=
(10)相等判断:===,!=,===,!==
(11)与
(12)或
(13)赋值
为实现这些操作符,Kotlin为二元操作符左侧的类型和一元操作符的参数提供了相应的函数或扩展函数。重载操作符的函数需要用operator修饰符标记。
2.4.2一元操作符
一元操作符有前缀操作符、递增和递减操作符等
1.前缀操作符
前缀操作符是放在操作数的前面
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()
重载一元运算符的示例:
data class Point(val x: Int,val y: Int)//声明数据类Point
operator fun Point.unaryMinus() = Point(-x,-y)
代码说明:
第一处:声明Point数据类
第二处:使用operator操作符关键字实现重载函数unaryMinus()
三、类型系统与可空类型
与Java、C和C++语言一样,Kotlin语言也是“静态类型的编程语言”。通常,编程语言中的类型系统中定义了:
(1)如何将数值和表达式归为不同的类型
(2)如何操作这些类型
(3)这些类型之间如何相互作用
我们在编程 语言中使用类型的目的是为了让编译器能够确定类型所关联的对象需要分配多少空间
3.1类型系统
定型的过程就是赋予一组比特以具体的意义。类型通常和存储器中的数值或对象相联系。
3.1.1类型系统的作用
使用类型系统,编译器可以检查无意义、无效的、类型不匹配等错误代码。这也是强类型语言能够提供更多的代码安全性保障的原因之一。
Java类型系统
3.1.3Kotlin的类型系统
Java是一个近乎“纯洁”的面向对象编程语言,但是为了编程方便还是引入了基本数据类型。为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型,int的包装类型就是Integer,从Java5开始引入自动装箱、拆箱机制,使得二者可以互相转换。Java为每个原始类型提供了相应的包装类型。
原始类型:boolean,char,byte,short,int,long,float,double
相应的包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
Kotlin系统类型分为可空类型和不可空类型。Kotlin中引入了可空类型,把有可能为null的值单独用可空类型来表示。这样就在可空引用与不可空引用之间划分出一条明确、显式的“界线”。
通过这样显式地使用可空类型,并在编译期作类型检查,大大降低了出现空指针异常的概率。
对于Kotlin中的数字类型而言,不可空类型与Java中原始的数字类型对应。Kotlin中对应的可空数字类型就相当于Java中的装箱
3.2可空类型
或许Java和Android开发者早已厌倦了空指针异常。
对于null异常这个问题,Groovy提供了一种安全的属性
user?.getUsername()?.toUpperCase()//安全调用符
Swift也有类似的语法,只作用在optional的类型上。
Kotlin也有类似的语法,只作用在Optional的类型上
Kotlin中使用了Groovy里面的安全调用符,并简化了Optional类型的使用,直接通过在类型T后面加“?”,就表达了Optional的意义
3.3.2非空断言“!!”
kotlin中提供了断言操作符“!!”,使得可空类型对象可以调用成员方法或者属性
3.3.3Elvis运算符“?:”
3.4特殊类型
本节我们介绍Kotlin中的特殊类型:Unit、Nothing、Any及其对应的可空类型Unit?,Nothing?,Any?。
3.4.1Unit类型
Kotlin也是面向表达式的语言。在Kotlin中所有控制流语句都是表达式
Kotlin中的Unit类型实现了与Java中的void一样的功能。
public object Unit{
override fun toString() = "Kotlin.Unit"
}
3.4.2Nothing与Nothing?类型
在Java中,void不能是变量的类型,也不能作为值打印输出。但是在Java中有个包装类Void是void的自动装箱类型。如果你想让一个方法的返回类型永远是null的话,可以把返回类型置为这个大写的Void类型。Nothing不能被实例化的原因是因为默认的构造函数是私有的。如果一个函数的返回值是nothing,就意味着这个函数不会有返回值。
3.5类型检测与类型转换
Kotlin在运行时通过使用is操作符或其否定形式!is来检查对象是否符合给定类型。
3.5.1 is运算符
is运算符可以检查对象A是否与特定的类型X兼容,还可以用来检查一个对象是否属于某数据类型。
3.5.2类型自动转换——类似于强制转换
当我们使用Java时,使用str instanceof string来判断其值为true的时候,我们想使用str变量,还需要显式地显式强制转换类型。
3.5.3 as运算符——转换符
as运算符用于执行引用类型的显式类型转换。如果要转换的类型与指定的类型兼容,转换就会成功进行;
3.6本章小结
Kotlin通过引入可空类型,在编译时就大量“请扫了”空指针异常。同时,Kotlin中还引入了安全调用符“.?”及Elvis操作符,使得我们的代码写起来更加简洁。
Kotlin的类型系统比Java更加简单、一致,Java中的原始类型与数组类型在Kotlin中都统一表现为引用类型。
Kotlin中还引入了Unit、Nothing等特殊类型,使得没有返回值的函数与永远不会返回的函数有了更加规范和一致的签名。
我们可以使用is操作符来判断对象实例的类型,使用as操作符进行类型的转换。
四、类与面向对象编程
4.1面向对象编程简史
20世纪50年代后期,在用Fortran语言编写大型程序时,由于没有封装机制,那个时候变量都是“全面变量”,因此会不可避免地经常出现变量名冲突问题。
在ALGOL60中采用以Begin End为标识符的程序块,使块内变量是局部,以避免它们与程序中块外的同名变量相冲突。在编程语言中首次提供了封装机制。
Java有5个基本特性:
万物皆对象:每一个对象都会存储数据,并且可以对自身执行操作。因此,每一个对象包含两个部分:成员变量和成员方法
程序是对象的集合:它们通过发送消息来告知彼此所要做的事情,也就是调用相应的成员函数。
每一个对象都有自己的由其他对象所构成的存储:也就是说在创建新对象的时候可以在成员变量中使用已存在的对象。
每个对象都有其类型,每个对象都是某个类的实例,每一个类区别于其他类的特性就是可以向它发送什么类型的消息,也就是它定义了哪些成员函数。
某一个特定类型的所有对象都可以接受同样的消息。另一种对对象的描述为:对象具有状态、行为、标识。
4.2声明类
本节介绍Kotlin中的类和构造函数的相关内容。主要包括类的声明和使用,以及构造函数和次级构造函数的编写。
4.2.1空类
使用class关键字声明类。我们可以声明一个什么都不干的类。
例如:
class AnEmptyClass
4.2.2声明类和构造函数
在Kotlin中,我们可以在声明类的时候同时构造函数,语法格式是在类的后面使用括号包含构造函数的参数列表。
使用这样简洁的语法,可以通过主构造器来定义属性并初始化属性值。在代码中可以这样使用Person类:
val person = Person("Jack",29,"M")
println(..)
也可以先声明属性,等构造实例对象的时候再去初始化属性值,那么Person类可以进行如下声明:
class Person1{
lateinit var name: String
var age: Int = 0
lateinit var sex: String
override fun toString(): String{
return "Person1(name='$name',age=$age...
}
4.3抽象类与接口
抽象类表示“is-a”的关系,而接口所代表的是has-a的关系
声明父类
abstract class Shape
class Rectangle:Shape()、、继承Shape父类
val s = Shape()//不能通过
val r = Rectangle()
println(r is Shape)
4.5.2 数据类自动创建的函数
编译器会根据主构造函数中声明的属性,自动创建以下3个函数
equals()/hashCode函数toString()格式
4.5.3数据类的语法限制
数据类有如下限制:
(1)主构造函数至少包含一个参数
(2)参数必须标识val或者var
(3)不能为abstract、open、sealed或者inner
(4)不能继承其他类(但可以实现接口)
4.5 Pair和Triple
Kotlin标准库提供了pair和Triple数据类,分别表示二元组和三元组对象。它们的定义如下:
//todo Pair数据类
public data class Pair<out A,out B>(
public val first: A,
public val second: B) : Serializable {
public override fun toString() : String = "(${first},${second})"
}
public infix fun <A,B> A.to(that: B): kotlin.Pair<A, B> =
kotlin.Pair(this,that)
public data class Triple<out A,out B,out C>(
public val first: A,
public val second: B,
public val third: C) : Serializable{
override fun toString(): String {
return "($first,$second,$third)"
}
}
我们可以使用Pair对象来初始化一个Map,代码示例如下:
val map = mapOf(1 to "A",2 to "B",3 to "C")
4.6 注解
注解是将元数据附加到代码中。元数据信息由注解kotlin.Metada定义。
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
internal annotation class Metadata
4.7枚举
Kotlin中使用enum class关键字来声明一个枚举类。
enum class Direction {
NORTH,SOUTH,WEST,EAST //todo 使用enum class声明一个Direction枚举类型
}
相比于字符常量,使用枚举能够实现类型安全。枚举类有两个内置的属性:
public final val name: String
public final val ordinal: Int
分别表示的是枚举对象的值与下标位置。例如上面的Direction枚举类,它的枚举对象的信息如下:
val north = Direction.NORTH //todo 访问枚举的NORTH对象
每一个枚举都是枚举类的实例,它们可以被初始化:
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
4.8内部类
本节我们介绍Kotlin的内部类,包括普通嵌套类、内部嵌套和匿名内部类
4.8.1普通嵌套类
Kotlin中,类可以嵌套。一个类可以嵌套在其他类中,而且可以嵌套多层。
class NestedClassesDemo{
class Outer {
private val zero: Int = 0
val one: Int = 1
class Nested {
fun getTwo() = 2
class Nested1{
val Nested = 3
fun getFour() = 4
}
}
}
}
val one = NestedClassesDemo.Outer().one
val two = NestedClassesDemo.Outer
...
可以看出,代码中NestedClassDemo.Outer.Nested().getTwo()访问
嵌套类的方式是直接使用类名,来访问,有多少层嵌套,就用多少层类名来访问
普通嵌套类没有持有外部类的引用,所以是无法访问外部类变量
class NestedClassesDemo{
class Outer {
private val zero: Int = 0
val one: Int = 1
class Nested{
fun getTwo() = 2
fun accessOuter() = {
println(zero)
println(one)
}
}
}
}
4.8.2嵌套内部类
如果一个类Inner想要访问外部类Outer中的成员,可以在这个类前面添加修饰符inner。内部类会带有一个外部类的对象引用。
class Outer {
private val zero: Int = 0
val one: Int = 1
class Nested {
fun getTwo() = 2
fun accessOuter() = {
//println(zero)
//println(one)
}
}
}
可以看到,当访问innerclassInner的时候,我们使用的是Outer().Inner(),这是持有了Outer的对象引用,与普通嵌套类直接使用类名访问的方式不同。
4.8.3匿名内部类
匿名内部类就是没有名字的内部类。匿名内部类也可以访问外部类的变量。
4.9本章小结
本章我们介绍了Kotlin面向对象编程的特性:类与构造函数、抽象类与接口、继承与组合等知识,同时介绍了Kotlin中的注解类、枚举类、数据类、嵌套类、内部类、匿名类、单例object对象等特性类。总的来说,在面向对象编程范式的支持,Kotlin相比于Java,增加了不少有趣的功能与特性支持,这使得写代码来更加、快捷了。
五、函数与函数式编程
5.1函数式编程简介
函数式编程是关于不变性和函数数组合的编程范式。函数式编程有如下特征:
一等函数支持:函数也是一种数据类型,可以作为参数传入另一个函数中,同时函数也可以返回一个函数。
纯函数和不变性:纯函数指的是没有副作用的函数。
函数组合:在面向对象编程中是通过对像之间发送消息来构建逻辑;而在函数式编程中是通过不同函数的组合来构建程序逻辑的。
5.2声明函数
Kotlin中使用fun关键字来声明函数,其语法实例如下:
fun multiply(x: Int,y: Int): Int {
return x*y
}
fun:表示声明函数的关键字
multiply:表示函数名
(x: Int,y: Int):表示参数列表
: Int:表示函数的返回值
为了更加直观地表现函数也可以当作变量来使用,声明一个函数类性的变量sum如下:
val sum = fun(x: Int,y: Int): Int{ return x + y}
为了更加直观地表现函数也可以当作变量来使用,声明一个函数类型的变量sum如下:
(kotlin.Int, Kotlin.Int) -> kotlin.Int
这个带箭头“->”的表达式就是一个函数类型,表示一个输入两个Int类型值、输出一个Int类型值的函数。可以直接使用这个函数字面值Sum
从上面这个典型的离职中可以看出,Kotlin也是一种面向表达式的语言。既然sum是一个表达函数类型的变量,稍后我们将看到一个函数可以当作参数传入另一个函数中
5.3 Lambda表达式
例如:
val list = listOf(1,2,3,4,5,6)
list.filter(it % 2 == 1)
这里filter()函数的入参{it % 2 == 1}就是一段Lambda表达式。
其中的filter()函数声明如下:
public inline fun <T> Interable<T>.filter(predecate: (T) -> B)
val isOdd = {it: Int -> it % 2 ==1}//直接使用Lambda表达式声明一个函数,这个函数判断输入的Int是不是奇数
5.4高阶函数
高阶函数的定义:如果一个函数使用另一个函数作为参数或者返回另一个参数,那么这个函数称为高阶函数
本节介绍Kotlin中的高阶函数
其实在上面的代码示例list.filter(isOdd)中,已经看到了高阶函数。现在再添加一层映射逻辑。我们有一个字符串列表:
val list = listOf("a","ab","abc","adcd","abcde")
typealias G = (String) -> Int
typealias F = (Int) -> Boolean
typealias H = (String) -> Boolean
那么我们的h函数可以写成下面这样了:
val h = fun(g: G,f: F): H{
return {f(g(it))}//需要注意的是,这里的{},是不能被省略的
5.5 Kotlin中的特殊函数
本节我们介绍Kotlin中的run()、apply()\、let()、also()、with()这5个特使的函数。
例如:
myfun(): String {
println("执行了myfun函数”)
return “这是myfun的返回值”
}
5.5.1 run函数
run函数的定义如下:
public inline fun <R> run(block: () -> R):R {
contract{
callsInPlace(block,InvocationKind.EXACTLY_ONCE)
}
return block()
}
七、集合类
集合类是在Java类库的基础上进行了改造和扩展,引入了不可变集合,同时扩展了大量方便使用的功能,这些功能的API都在Kotlin.collections包下面
另外,在Kotlin的集合类中不仅仅能持有普通对象,而且能够持有函数类型的变量。
val funlist:List<(Int)> -> Boolean >= listOf({it -> it % 2 == 0},{it -> it % 2 ==1})
7.1.1常用的3种集合类
集合类主要有3种:List、Set、Map
List容器中的元素以线性方式存储,集合中可以存放重复对象。列表中的元素是有序地排列
Set集容器的元素无序、不重复
Map映射中持有的“键值对”对象,每一个对象都包含一对键值对k-v对象。Map映射容器中存储的每一个对象都有一个相关的关键字key对象,关键字决定对象的存储位置。关键字是唯一的。其实关键字本身并不能决定对象的存储位置,它通过散列产生一个被称做散列码的数组。
Iterable:任何类继承这个接口就表示可以遍历序列的元素
MutableIterable:在迭代期间支持删除元素的迭代。
Collection:List和Set的父类接口。只读不可变。
MutableCollection:支持添加和删除元素的Collection。它提供写入的函数,如add、remove或clear等
List:最常用的集合,继承Collection接口,元素有序,只读不可变
MutableList:继承List,支持添加和删除元素,除了拥有List中读取数据的函数,还有add、remove或clear等写入数据的函数
Set:元素无重复、无序。继承Collection接口。只读不可变
MutableSet:继承Set,支持添加和删除元素的set
Map:存储K-V对的集合。在Map映射表中key是唯一的
MutableMap:支持添加和删除元素的map
7.2 不可变集合类
List列表分为只读不可变的List和可变MutableList
Set集合也分为不可变Set和可变MutableSet(可写入、删除数据)
Kotlin中的Map与List、Set一样,Map也分为只读Map和可变MutableMap
7.3创建集合类
Kotlin中分别使用listOf()、setOf()、mapOf()函数创建不可变的List列表容器、Set集容器、Map映射容器:使用mutableListOf()
val list = listOf(1,2,3,4,5,6,7,8) //创建不可变list
val mutableList = mutableListOf("a","b","c")//创建可变MutableList
val set = setOf(1,2,3,4,5,6,7)//创建不可变set
val mutableSet = mutableSetOf("a","b","c")//创建可变的Set
val map = mapOf(1 to "a",2 to "b" 3 to "c")//创建不可变Map
val mutableMap = mutableMapOf(1 to "x",2 to "Y",3 to "Z")//创建可变MutableMap
如果创建没有元素的空List,使用listOf()即可。不过这个时候,变量的类型不能省略。
7.4 变量集合中国的元素
List、Set类继承了Iterable接口,里面扩展了forEach函数来迭代变量元素:同样Map接口中也扩展了forEach函数来迭代变量元素。