Kotlin入门(需要Java基础)

前言

想学Kotlin,一直没认真去实现,看郭霖大神又出了第一行代码的第三版,感受了下,终于入门,深感Kotlin其妙

写下这篇梳理文字,纯手打,主要是梳理和关键点记忆,用作自己笔记,不会特别详细,有错之处还望指出并海涵,ths!

PS: 学习Kotlin的朋友,其实前期可以用java代码直接转换成Kotlin掌握基本语法

环境配置

自行google或者百度,不做详细,随便找了个

Android Studio Kotlin环境配置

变量

有且只有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已经存在默认值,不赋值不会报错

静态函数
  1. 使用单例类中方法可以实现类似静态方法的调用
  2. 使用companion object关键字,这个关键字会在该类内部创建有且只有一个的伴生类,相当于是静态方法
  3. 在companion object{}中静态方法前加上@JvmStatic的注释
  4. 创建顶层函数,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就这几个核,没有到真正意义下并行,所以协程更像是轻量级的线程。
协程和线程都有个很关键的点,作用域,就像局部变量作用域为所在的函数

协程的使用
  1. GlobalScope.launch{…}:创建一个协程作用域,单纯这样的协程不会影响其他协程和所在的线程,也就是说可能线程走完,协程没走完,但是协程依附于线程,所以后面也就不执行了。可以通过delay(time)方法来挂起协程(非阻塞)
  2. runBlocking{…}:通过runBlocking{…}函数,该函数可以保证该作用域内所有协程不做完,线程会被阻塞不被结束,换句话说使得协程的生命周期会影响线程的生命周期。所以还有个关键点就是要小心使用
  3. launch{…}:刚刚说了runBlocking{…}保证所有作用域下的协程,这时候它来了,launch{…}函数可以在协程域(并且只能在协程域)中创建一个新协程,如果外层协程结束了,那么里面子协程也会结束,和第一点GlobalScope.launch{…}在线程的概念有点像
  4. suspend关键字:我们不可能所有代码全部放在协程域中直接写,这样的话太冗长,通过suspend关键字我们可以实现类似delay{time}挂起函数的效果,在协程作用域中使用,并且可以互相调用挂起函数,但是该关键字声明的fun不具备协程作用域
  5. coroutineScope{…}:同样是个挂起函数,会继承外部的协程作用域并且创建一个子作用域,并且会确保函数体执行结束通runBlocking{…}类似,但是是阻塞协程
  6. job.cancel():取消协程,GlobalScope.launch{…}或者launch{…}都是会返回一个job对象
  7. async{…}和deferred.await():这个相当于异步的内容实现和接口监听,async{…}的代码执行完,可以通过await获得其返回结果deferred对象,如果没执行外,awiait()会阻塞该协程,所以如果你不想阻塞协程的话,尽可能在需要结果的时候调用该deferred对象的await函数获取结果。
  8. withContext(线程对象){…}:该函数就是async{…}.await()的组装版,但是请注意需要指定线程,协程说到底还是依附于线程,而android很多东西不能在主线程中做,所以碰到处理这种逻辑例如网络请求,我们如果想用Kotlin的协程来完成就需要指定到一个子线程中实现.(Dispatcher.Default,Dispatcher.IO,Dispatcher.Main)
  9. 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以后应该都有所涉猎,不具体讲
精髓所在,就是往死里简单,当然我们还是要知道过程
小小规则:

  1. lambda表达式如果非唯一参数但是在最后可放在括号外
  2. 若唯一参数可以省略括号
  3. 表达式内参数其实在kotlin中不需要申明参数,因为可以类型推导
  4. 如果只有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
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值