前言
想学Kotlin,一直没认真去实现,看郭霖大神又出了第一行代码的第三版,感受了下,终于入门,深感Kotlin其妙
写下这篇梳理文字,纯手打,主要是梳理和关键点记忆,用作自己笔记,不会特别详细,有错之处还望指出并海涵,ths!
PS: 学习Kotlin的朋友,其实前期可以用java代码直接转换成Kotlin掌握基本语法
环境配置
自行google或者百度,不做详细,随便找了个
变量
有且只有2种
- val(value)相当于java中的final修饰的变量
- var(variable)相当于java中非final修饰的变量
ps:vararg用作函数中的可变参数列表
java中变量的声明:
A a;
A a = new A ();//初始化
kotlin中变量的声明:
val a : A
val a = A ()//初始化
如果不是自定义的对象都不需要说明,拥有出色的类型推导机制,例如
var a = 1
val b = "我是个String"
还有我并不是漏写";“kotlin里你再也不需要写”;"
变量可以通过lateinit关键字延迟初始化
lateinit val a:A
但是请注意,请确保在使用时已经完成相关初始化,可以通过::a.isInitialized方法来判断是否已经初始化
Kotlin预设类型
这个叫法是我斗胆这么说的,与java不同,Kotlin不再存在所谓的基础数据类型,全是对象数据类型,java中8大基类变成了首字母大写的对象数据类型
Int,Long,Float,Double,Byte,Char,Short,Boolean
空指针检查
变量后面加?代表可以为空
变量后面加!!代表为不为空都是程序员自行担责任,Kotlin你不要怕
?.是判空操作符,比如a?.b()代表a对象不为空的时候再执行b函数
内嵌表达式
Kotlin支持如下输出
val person = Person("felix")//随便臆想一个人名构造函数的Person类
println("my name is ${person.name}")
运算符重载
本来1+1=2,可以通过重载的方式让他等于3,当然一般用于对象的运算,我们通过operator这个关键字来完成替代,并且可以是多重重载,比如a+b实际调用的就是a.plus(b),其他运算符可以类推,不再细说
class Person(val name:String,val age:Int){
operator fun plus(p:Person):Person{
val s="新人"
val age=25
return Person(s,25)
}
operator fun plus(n:String):String{
val s=name+n
return s
}
operator fun plus(a:Int):Int{
val sum=age+a
return sum
}
}
val p1=Person("felix",25)
val p2=Person("lily",20)
val p3 =p1 + p2
val string =p1+p2.name
val age =p1+p2.age
泛型
泛型应该不是个新概念了,Kotlin中的用法和java大差不差
设置泛型上界
fun <T:Any?> method(t:T){//Any相当于java中的Object
}
可以用*表示不知道或不关心的泛型类型
如果需要实化泛型,需要在声明泛型的地方加上reified的关键字前置,Kotlin就会通过内联函数对其实化,使得a is T,T::class.java可以使用,也就是说使得很多函数不因为传入泛型参数而无法使用
泛型协变/逆变
定义:
B是A的子类,如果C< B >是C< A >的子类,那么我们称C < T >在T这个泛型上协变
反之如果C< A >是C< B >的子类,那么就是C < T >在T这个泛型上逆变
首先统一一个定义出现在参数位上为in,返回值位上为out
如果想协变,那么势必让数据只读才能没有类型转换隐患,设置泛型为代表只能输出,不能输入,即不能使用set方法,只能使用get,注意通过构造函数初次赋值是允许的。
如果想逆变,设置泛型为代表只能输入作为参数,不能输出,即使用set方法,不能使用get
可以使用@UnsafeVariance注解来取消Kotlin对于这一语法的控制,但是要确保,我们确实按照这个规矩去做,比如说协变的时候我们需要set来完成逻辑,但是不会对数据本身产生影响,否则肯定会有额外的风险
控制逻辑
条件语句:
- if
- when
与java不同点,两种语句均存在返回值,并且在代码量小的情况下,有优美的书写形式。
循环语句:
- while
- for
for-in循环中常用
i in 0 … 5 代表0到5的闭区间
i in 0 until 5 代表0到5的左闭右开区间也就是0-4
i in 0 until 5 step 2 step关键字代表递增每次加2,也就是0,2,4
i in 5 downTo 1关键字代表降序,5,4,3,2,1
函数
关键字很简单就是fun(Function)
fun Method (arg1:T,arg2:X):T{
//do something
return t//返回一个T类型变量
}
fun 方法名(参数:类型,…)返回类型{ }
这部分过渡应该很容易,和java很容易对应起来
类
与java不同,Kotlin类默认不能被继承,除非用open修饰,继承和实现接口均在":"后写明
open class A(){
init{
}
}
interface B{
}
class C :A(),B{
}
主构造函数是在类名后会显示的,并且继承的父类名后面也要跟上相对于的构造体。
上面代码中A()代表的就是A是无参构造函数。如果不书写构造体,可以在init中完成初始逻辑
构造函数分为主构造函数和次构造函数
父类 open class A(val arg1:T,val arg2:X){
}
子类 class B(val arg3:T,arg1:T,arg2:X):A(arg1,arg2){
constructor(arg1:T):this("","","",""){
}
constructor():this("",""){
}
}
父类定义过修饰的参数子类就不需要在定义了,如果不仅需要一个构造函数,使用关键字constructor来定义次构造函数,最起码有一个次构造函数引用主构造函数
PS:特殊情况可以不存在主构造函数只存在次构造函数
class B : A {
constructor(): super ("","") {
}
}
那子类中的次构造函数只能引用来自父类的构造体使用super关键字,因为没有主构造函数,类名后的构造体都省略了
函数修饰符
public(所有类可见),private(当前类可见),protected(当前类,子类可见),internal(同一模块下的类可见)
密封类
sealed关键字,配合条件语句when执行很棒,Kotlin对于密封类需要强制完成所有条件的处理,并且不用使用else(毕竟已经实现了所有条件)
sealed class A
class B:A()
class C:A()
fun method(a:A) = when(a){
is B -> 代码块
is C -> 代码块
}
函数参数默认值
函数参数可以有默认值
fun method(val a:Int,val b:String ="aaa",c:String)
//我们可以通过如下调用
method( a=5,c="ccc")//b已经存在默认值,不赋值不会报错
静态函数
- 使用单例类中方法可以实现类似静态方法的调用
- 使用companion object关键字,这个关键字会在该类内部创建有且只有一个的伴生类,相当于是静态方法
- 在companion object{}中静态方法前加上@JvmStatic的注释
- 创建顶层函数,kotlin会把顶层函数全部编译成静态方法
接口
刚刚上面已经显示了接口的写法
interface A {
fun a ()
fun b () {
println ("可以实现方法,这样实现A接口的类可以不实现,默认走接口中的实现")
}
}
单例
把class关键字改成object关键字
匿名函数
同样是用object关键字
例如线程
Thread(object: Runnable(){
override fun run(){
}
}).start();
扩展函数
顾名思义为某个类做扩展,一般定义为顶层方法,让扩展函数拥有全局的访问域
fun String.likeSomeString():Boolean{
return true
}
这就是String的函数,当我们用到String对象的时候就可以用这个函数,是不是很便捷呢,不需要额外开销。
委托模式/委托属性
委托模式说白了,就是自己想做的一套别的都已经做了,而且你觉得做的很不错,那为啥不直接用别人的呢。
只需要在接口声明的后面使用by关键字加上委托的对象就可以了
class MyMap<T>(val hashmap:HaspMap<T>):Map<T> by hashmap{
fun println()=println("success")
//contains,isempty,iterator等等方法只要hashmap实现了的都可以直接用,不用再写
}
委托属性同理就是一个变量委托给一个类去实现需要的方法
委托属性很明显的实现是by lazy{…}的形式实现懒加载,只有by是关键字
常见函数式API
这一块内容相对重要,Kotlin的简洁美妙很好的体现
集合中使用:
.map{} 映射成其他,比如变成大写
.filter{} 过滤,{}中写条件
.any{} 判断集合中是否存在一个元素
.all{} 判断所有元素是不是符合标准
参数一般有且仅list,map,set的本身所以可以用it代替
标准函数:
标准函数是指Standard.kt中定义的函数,说实话真的好用
- .let{} 代表{}内都是对该对象的使用配合?.判空超级好用
a?.let{
it.b()
it.c()
it.d()
}
//配合?.再也不用单独判空,并且不同于java的判空
//let函数不存在多线程导致的空指针危险
- with(对象){}在{}中我们可以直接用对象的方法,最后一行作为返回值
- .run{}和with没什么区别只是对象从括号中到了引用
- .apply{}和run和with的区别在于不是最后一行指定返回值,而是直接返回对象本身
高阶函数
如果一个函数接受另一个函数作为参数,或者返回值的类型是另外一个函数,那么就是高阶函数,通过::表示函数的引用
//第一种通过::引用函数
fun A (arg1:Int,method:(String,Int)->Int):Int{
return method("Hi",arg1)
}
val integer = A(1,::method)
//第二种通过扩展函数的写法获取到对象上下文,直接使用对象的函数
fun String.returnSelf(block: String.(String) -> Int): String {
block(this)
println("success")
return this
}
val str ="1".returnself{
compareTo("1")
}
高阶函数背后是通过lambda表达式实现function接口的匿名类实现
内联函数
只要在高阶函数前面加上inline,就可以变成内联函数
内联函数直接替换相对模块代码,去除高阶函数的运行开销,不做详细说明,自行了解
ps:内联函数可以进行函数返回,而非内联函数只能局部返回(很好理解,内联函数都直接替换了对应外包函数代码了,return就在外层生效了)
noinline关键字用于把内联函数的函数参数非内联
inline fun a(block1:()->Unit,noinline block2:()->Unit){
}
如果我们在高阶函数中创建了其他的lambda或者匿名内部类,将无法声明为内联函数,需要通过crossinline来解决
inline fun runRunnable(crossinline block:()->Unit){
val runnable=Runnable{
block()
}
runnable.run()
}
infix函数
之所以把infix函数单独讲,是因为他真的惊艳到我,可以实现语言式的代码
infix函数有2个限制,不能定义成顶层函数必须是某个类函数(可以是扩展函数),只有1个参数输入
infix fun Int.magicCa(a:Int):Int{
val v = this
return v+a-5
}
5 magicCa 5//答案应该是5
数据
集合
listof和mapof方法对应不可变设参
mutableListof和mutableMapof对应可变设参
val array = listof(1,2,3)//那么array这个变量不能再set
val map = mapof(1 to "1",2 to "2")//可以通过to关键字进行map一对一映射,to关键字其实也是通过infix关键字实现的,后面会说到
线程与协程
Kotlin中可以用如下来快速使用线程
thread{
}
//直接等同于java的
new Thread(new Runnable(){
@override
public void run(){
}
}).start();
Kotlin引入了协程这个概念,个人认为其实是在单线程下模拟多线程的感觉,其实线程严格来说也都是并发在做毕竟CPU就这几个核,没有到真正意义下并行,所以协程更像是轻量级的线程。
协程和线程都有个很关键的点,作用域,就像局部变量作用域为所在的函数
协程的使用
- GlobalScope.launch{…}:创建一个协程作用域,单纯这样的协程不会影响其他协程和所在的线程,也就是说可能线程走完,协程没走完,但是协程依附于线程,所以后面也就不执行了。可以通过delay(time)方法来挂起协程(非阻塞)
- runBlocking{…}:通过runBlocking{…}函数,该函数可以保证该作用域内所有协程不做完,线程会被阻塞不被结束,换句话说使得协程的生命周期会影响线程的生命周期。所以还有个关键点就是要小心使用
- launch{…}:刚刚说了runBlocking{…}保证所有作用域下的协程,这时候它来了,launch{…}函数可以在协程域(并且只能在协程域)中创建一个新协程,如果外层协程结束了,那么里面子协程也会结束,和第一点GlobalScope.launch{…}在线程的概念有点像
- suspend关键字:我们不可能所有代码全部放在协程域中直接写,这样的话太冗长,通过suspend关键字我们可以实现类似delay{time}挂起函数的效果,在协程作用域中使用,并且可以互相调用挂起函数,但是该关键字声明的fun不具备协程作用域。
- coroutineScope{…}:同样是个挂起函数,会继承外部的协程作用域并且创建一个子作用域,并且会确保函数体执行结束通runBlocking{…}类似,但是是阻塞协程
- job.cancel():取消协程,GlobalScope.launch{…}或者launch{…}都是会返回一个job对象
- async{…}和deferred.await():这个相当于异步的内容实现和接口监听,async{…}的代码执行完,可以通过await获得其返回结果deferred对象,如果没执行外,awiait()会阻塞该协程,所以如果你不想阻塞协程的话,尽可能在需要结果的时候调用该deferred对象的await函数获取结果。
- withContext(线程对象){…}:该函数就是async{…}.await()的组装版,但是请注意需要指定线程,协程说到底还是依附于线程,而android很多东西不能在主线程中做,所以碰到处理这种逻辑例如网络请求,我们如果想用Kotlin的协程来完成就需要指定到一个子线程中实现.(Dispatcher.Default,Dispatcher.IO,Dispatcher.Main)
- suspendCoroutine{…}:激动人心,可用作简化协程的回调函数,带一个Continuation参数的Lambda表达式,主要是挂起协程,调用其Continuation.resume()或者Continuation.resumeWithException()来恢复协程,通过这个方法可以十分方便的发起网络请求
接下来通过代码我们认真看下这9点
//正常不推荐使用GlobalScope.launch{..}只是创建协程域,不做说明
//runBlocking{..}会阻塞线程用作测试环境没啥问题
runBlocking{
launch{
delay(1000)
}
launch{
}
}
//使用正常的协程方式
val job=job()
val scope=CoroutineScope(job)
scope.launch{
val result=async{
A()
1+1
}
println(result.await())//打印为2
}
scope.launch{
val result =withContext(Dispatchers.default){
//如果需要等待输出结果,可以使用withcontext函数
//否则请使用async和await搭配
}
}
suspend fun A(){
//可以使用其他挂起函数
}
suspend fun B() =coroutineScope{
//拥有协程域
launch{
}
}
//X类的扩展函数,X.method需要一个接口,实现一个匿名接口回调,注意必须在协程域或者挂起函数中调用
suspend fun X.netRequest(arg1:T):T{
return suspendCoroutine{ c:continuation ->
method(object:Callback<T> {
override fun onResponse(){
c.resume()
}
override fun onFailure(){
c.resumeWithException()
}
}
}
}
//真正请求时,不需要一次次写回调
fun A(){
try{
val request =X.netRequest("xxxxxx")
}catch(e){
}
}
建议用Kotlin处理异步,多线程编程,我们都可以通过协程去完成
lambda编程
这块其实jdk1.8以后应该都有所涉猎,不具体讲
精髓所在,就是往死里简单,当然我们还是要知道过程
小小规则:
- lambda表达式如果非唯一参数但是在最后可放在括号外
- 若唯一参数可以省略括号
- 表达式内参数其实在kotlin中不需要申明参数,因为可以类型推导
- 如果只有1个参数可以用it代替
如下
lambda表达式我们称之为LAM
{ 参数名1:类型,参数名2:类型 -> 函数体 }
fun method(a:T,{LAM}) -> fun method(a:T) {LAM}
fun method({LAM}) -> fun method {LAM}
//如果只有一个参数甚至可以忽略参数块,直接用it写函数体
{ it.method }
其他关键词
- is 常见于when循环,相当于instance of关键字
- as 类型转换 A as String,as?为可空转换,转换失败会返回null