美文网首页
[大白装逼]从java到kotlin的学习笔记

[大白装逼]从java到kotlin的学习笔记

作者: lewis_v | 来源:发表于2018-07-22 11:24 被阅读42次

    前言

    以前android的开发大部分使用java开发,而由于kotlin的推出,公司也开始转向了使用kotlin,所以在这里记录下学习kotlin的笔记.

    基本语法

    基本类型

    在kotlin中没有基本类型,其中一切都是对象,这也导致了其没有自动转换类型的功能,如folat不会自动上转为double之类的,而在java的基础类型之间转换需要调用其中的方法,虽然在本文中,多出地方使用了关键字,但其也是一个对象,只是作者沿用了java的说法.

    val i :Int = 7
    val d :Double = i.toDouble()//Int对象中的转换方法
    val s = "eeeee"//String类型的s
    val c = s[0]//kotlin的字符串类型也可以像数组那样字节访问,且可以当做数组来迭代
    

    定义包和导入包

    这个与java的使用时一样的

    package com.lewis.test
    
    import com.lewis.test.util
    

    定义函数

    kotlin中的函数使用fun作为关键字来定义,返回值在函数参数声明后使用":"冒号来定义返回值类型,当然也可以让其自动判断返回类型,返回参数也使用return返回

    fun sum(a :Int,b:Int) : Int{
    return a+b
    }
    
    fun sum(a:Int,b:Int) = a + b//自动推断返回的(a+b)的类型
    
    fun sum(a:Int,b:Int) = { a + b}//这里返回的是a+b的类型,如Int
    

    kt中无返回值(返回无意义的值),也就是java中的void,其使用关键字Unit,当然这个关键字一般是可以省略的,如下两个函数,其返回值是Unit(无返回值)

     fun test(){
    println("555")
    }
    fun test() :Unit{
    println("555")
    }
    

    kotlin的函数可以使用参数默认值,这是java所不支持的,其使用与C++的默认参数类似,但也有点不同

    fun defultTest(name :String ,age:Int,sex : String = "Man"){//此处的第三个参数有默认值Man
        print(name+sex+age)
    }
    defultTest("lewis",23)//第三个参数可以省略,其默认为Man,相当于defultTest("lewis",23,"Man")
    defultTest("lewis",23,"women")//这里就不使用默认参数,使用women值
    

    这里有个小扩展,在C++中的默认参数一定要放在函数参数列表的后面,否则会报错,但kotlin这样用却不会报错,个人认为将默认参数放在中间这是一个意义不大的事情

    fun defultTest(name :String ,sex : String = "Man",age:Int){}
    //像这种情况,在调用时因为第三个参数没有默认值,所以一定要传第三个值,所以这里也导致了第二个值一定要传,所以这里,个人认为在实际开发中尽量避免这样的设计
    

    可变长度的参数

    在java中可以使用...来表示函数传入的参数长度不固定,而kotlin中也有这功能,但不是...,而是vararg关键字

    fun argTest(vararg data:String){
    println(data.size)
        println(data.toList())//转换为list,并调用toString()输出,直接输出data是一个数组对象
    }
    

    局部函数

    kotlin中有局部函数,从名字看,就是函数中的函数

    fun save(str:String){
    fun isEmpty(str:String){//定义一个内部函数
    if (str == null){ throw NullpointException("null")}
    }
    isEmpty(str)//在函数内部直接调用,在外部不可调用
    }
    

    局部参数定义

    val定义的只能赋值一次,类似于java的final;var定义可变类型,类似java定义的普通变量;两种定义的使用方式基本类型,其不同之处只是在定以后val的参数不可以再次赋值,而var可以

    fun main(args : Array<String>){
    val a:Int = 1
    val b = 2//自动识别b为int类型
    val c: Int//定义c,其类型为int,此时的类型不可以省略
    c = 3
    }
    
    fun main(args:Array<String>){
    var d = 5//自动识别d为int
    d += 1//运算符和java一样用
    }
    

    其中可以使用as关键字类转换类型,父子类的转换或强制转换

    var a:Int = 3 as Any as Int//这里是将3转换成Any类型,在转换为Int类型(这里只是作为一个例子,没有任何实际意义)
    

    字符串模板

    kt可使用"",在字符串中引用参数或将"{xxx}"xxx的结果转换为字符串

    fun stringTest(){
        var a = 5
        val str = "a is $a"//此时str的字符串赋值为"a is 5"
        a = 1
        println(str.replace("is","was")+",but now is $a")
        println("${str.replace("is","was")},but now is $a")
    }
    

    if条件判断

    kt中的if与java的用法类似,可以说java怎么用,kt就可以怎么用,但kt有更多的用法,如kt的if是可以用返回值的,在if的代码块的最后一句,如果为一个值,那么他就是if的返回值,这里需要注意的是,如果需要其有返回值,那每一个if条件的代码块的最后一行都需要是有一个同类型的值,否则无返回值

    fun ifTest(): Int {
        var result =  if (3 == 4) 3 else 4
        return result
    }
    
    fun ifTest2():Int{
    return if(3==4)println(3)else 4//这样子if是没有返回值的,且编译器会报错
    }
    

    null可空值

    在kt中,要是某个值可为null时,需要再声明的类型后面添加?,来表示该应用可以为空;
    且在这个参数使用时加上?使用,可在其不为空时才调用,在为空时不调用;
    使用!!来保证不为空时才调用,为空时抛出异常;
    使用?:来给其在空时的默认值

    var a : Int? = null
    var a: Int = null//此处会报错
    fun getInt():Int?{//这里的返回可以为null
    ...
    var a:String? = null
    a?.length()//这里只有在a不为空时才会调用
    a!!.length()//这里a为空时会直接抛出异常
    a?.length() ?:0//这里在a为空的时候,使用默认值0
    a.length()//这里是不能通过编译的,因为a在这里有可能为空
    a as? String//这里是a不为空时将a转换为String类型,否者返回null
    }
    

    类型检测及自动类型转换

    kt中使用is关键字来检测类型,且检测后的分支中可以直接当该类型使用

    fun getStringLength(obj:Any):Int?{//Any为任何类型,类似于java的object
    if (obj is String){
    return obj.length//此分支中,obj的类型为String
    }
    //出了分支obj还是String
    return null
    }
    

    for循环

    kt中使用in关键字来循环遍历

    fun forTest(args:Array<String>){
    for(item in args){//遍历其数组中的元素
    println(item)//此处不可做删除操作,否则会抛异常
    }
    for(index in args.indices){//遍历其数组中的下标
    println(args[index])//此处也不可做删除操作,删除了数组的大小会变小,在最后就会出现数组越界,所以如果删除了元素就要做相应的保护措施
    }
    }
    

    区间

    kotlin中有使用..来表示区间

    if(x in 1..3){//x是否在1到3之间,包括1和3,如果为!in为如果不在1到3的区间
    ...
    }
    

    迭代区间

    for(x in 1..5){//包括1和5
    println(x)//1 2 3 4 5
    }
    for(x in 1..5 step 2){//包括1和5,step 2表示迭代时每次迭代的步长为2
    println(x)//1 3 5
    }
    for(x in 1 util 5){//1到5,此处不包括5,一般用于遍历列表
    println(x)//1 2 3 4
    }
    for(x in 100 downTo 1 step 3){//downTo是递减,这里是从100递减到1,步长为3,就是每次-3,包括1和100
    println(x)//100 97 94 91...
    }
    

    while循环

    这个与java 的一样使用

    when表达式

    类似于java 的swich,且when和if一样是有返回值的,返回值用法也和if的一样

    fun whenTest(obj : Any) :String =
    when(obj){
    1 ->"111"
    2,3->"2,3"//多个情况可使用逗号隔开
    4..7->"4-7"//使用区间也可以
    "asd" ->"aaaa"
    is Long -> "long"
    !is String -> "Not string"
    else -> "unKnown"
    }
    

    当然when中可以不传参数,再不传的情况下,其分支条件需要为布尔值

    when{
    true->"true"
    a ==0 || b == 0->"false"
    else "null"
    }
    

    异常

    kotlin的异常与java 的类似,也是使用try-catch-finally,但其和if一样是一个表达式

    var result = try{//这里无异常时会返回22,有异常时返回null,且一定会打印change
    Integer.parseInt("22")
    } catch(e : NumberFormatException){
    null
    }finally{
    println("change")
    }
    

    ==相等比较

    在kotlin中==是使用equals()比较的,是数值相等,而在java中,若对对象使用==,则是比较引用相等
    在kotlin中,如果需要比较其引用相等,需要使用===,三等号

    定义类

    class MainActivity{
    }
    class MainActivity2(name:String,surname:String)//此处的类内无任何操作,所以可以忽略大括号,且(name:String,surnameString)这个就是声明其的构造函数
    class MainActivity3(var name:String,var surname:String)//这个和上一个很像,但上一个的类中并没有声明有属性,但这个声明了name和surname两个参数
    class MainActivity4(var name:String,var surname:String){
    init{
    ...//这个方法块是在主构造函数的函数体
    }
    }
    

    属性

    构造函数中声明了属性或类中声明的属性,会默认的有setter和getter方法,虽在在使用的时候像是直接在操作属性其实其是调用了对应的get和set方法,当然也可以修改默认的set和get方法

    class Test(){
    var change0 = 0
    var change1 = 1
     get() {
                        return field+1//field是保留值,指向获取的这个参数,这里就是指向change1
                }
                set(value) {this.change = 2}
    //这里修改了change1的get和set的默认方法,对change0和change2没有影响
    var change = 2
    var change3 = 4
    private set//可以将访问其的权限设置为本类使用或其他的
    }
    

    构造方法

    class Test1(var name:String){//此处括号声名的就是此类的主构造函数,当存在主构造函数时,就不能有从构造函数
    }
    class Test2{//无主构造函数
    constructor(name:String){//从构造函数
    this(name,0)//和java一样,可以使用this来调用其他构造函数
    }
    constructor(name:String,age:Int){}
    }
    

    一般来说,要使用主构造函数来实现,然后其他参数提供相应的默认值,来达到类似重载的功能

    类继承

    kotlin中的类只能继承显示声明为open或abstract的类,其中open是可被重写的意思,abstract是抽象的意思

    open class Test1(name: String)
    abstract class Test2(surname: String)
    

    向上面两个类的声明才可以被继承,继承时使用":"来继承,而不是java中的extends关键字

    class Test3(name: String) : Test1(name){//这里在构建是会默认调用父类Test1(name:String)的构造方法
    }
    

    kotlin的方法默认是final,不可重写的,所以,如果需要可以重写就需要显示声名open,或设为abstract抽象方法,而继承后重写一定要使用override关键字,且override关键字默认为open,所以在重写后,若想此方法不可被重写就要显示的使用final,kotlin的final与java的类似

    abstract class abTest{
    abstract fun one()//子类必须重写
    open fun two(){}//子类可以重写,也可以不重写
    fun three(){}//子类不可重写
    }
    

    接口

    接口的实现与继承一样,都是使用":",接口内的方法默认都为open,且无实现体就默认为abstract
    kotlin的接口定义也是使用interface关键字,且支持在接口中提供默认实现
    无默认方法的,其子类一定要实现这个方法,如有默认实现的,子类可以不是实现,默认使用接口中的是实现

    interface Clickable{
    fun click()
    fun showOff() = println("default")//此方法有默认的实现
    }
    interface Clickable2{
    fun showOff() = println("default2")//此方法有默认的实现
    }
    class Button : Clickable,Clickable2{
    override fun click() = println("click")//override 是重写父类的编辑,在java中是一个注释且可有可无,但在kotlin中,如果是重写就一定要有override 
    override fun showOff() {//当实现的两个接口都有同一个方法,那子类一定要重写他,否则会报错
    super<Clickable>.showOff()//调用指定父类的实现
    super<Clickable2>.showOff()
     println("Button")
    }
    }
    

    这里可以看出继承和实现的不同,继承需要加上其构造方法,而实现不用
    除了方法之外,kotlin的接口还支持默认属性,且kotlin的接口不存储属性值,只有其子类才会存储

    interface ITest{
    val name :String//此处的name不可以赋值,且子类实现后必须重写此属性
    val age:Int//此处提供了get,其子类可以不重写age
    get() = 1
    }
    class Test4(override val name :String) :ITest
    

    可见性修饰符

    在kotlin中有public/internal/protected/private,四种,默认为public,而java中默认为包私有,这和kotlin的不太一样,且kotlin没有包私有修饰符.protected与java的不同,只能是在本类和子类中可见,而kotlin转java是,internal会被转为public

    修饰符 || 类成员 || 顶层声名
    public(默认) || 所有地方可见 || 所有地方可见
    internal || 模块中可见 || 模块中可见
    protected || 子类可见 || 无此用法
    private || 类中可见 || 文件中可见

    data数据类

    kotlin中提供了一个数据类,数据类中提供了equals/hashcode/toString方法的重写,还提供了copy方法来复制数据类,声名的关键字为data

    data class TestData(val name:String,var age:Int)
    

    enum枚举类

    enum class EnumTest(val a:Int,val b:String ){
        ONE(1,"1"),TWO(2,"2");//此处定义枚举类的值,若要定义方法,则要使用";"隔开,这里是kotlin中唯一要使用";"的地方
        fun printAll() = println("$a,$b")
    }
    

    扩展函数

    这个就厉害了,可以类以外的一个地方对类方法进行扩展,也可以说是给类添加一个函数,然后在其他地方使用到这个类时,可以直接调用这个方法使用,如对Context添加一个Toast的方法
    需要注意的是,扩展函数无法使用被扩展类的protected和private修饰的属性或方法

    //Test.kt
    fun Context.toast(message :CharSequence){
        Toast.makeText(this,message,Toast.LENGTH_SHORT).show()
    }
    

    //MainACtivity.kt

    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            toast("nfh")//因为Activity是继承了Context,所以可以直接调用Context中扩展的函数toast
        }
    }
    

    当然除了可以添加函数,也可以添加属性

    public var Context.age: Int//对Context添加属性age,这样所有Context的子类及本身都有这个属性了
        get() = age
        set(value) { age = value}
    

    需要注意的是,扩展函数不能被子类重写,因为扩展函数会被当做静态类型来处理,而且当扩展函数名和成员函数名一样是会优先使用成员函数名

    内部类(嵌套类)

    在java中的内部类,如不声名为静态的,其会持有外部类的引用,而声名了静态类型则不持有
    而在kotlin中默认是不持有的(相当于静态),如果需要持有外部类引用则需要使用inner关键字

    class Outer{
    inner class Inner{
    fun getOut() : Outer = this@Outer//要使用外部类时,要使用this@去引用
    }
    }
    

    密封类

    关键字sealed,使用sealed声名的类,默认为open,其子类只能在本类中继承在类外不可继承(在kotlin1.1版本改为在文件中)

    sealed class Expr{
    calss Num():Expr()
    class Sum():Expr()
    }
    

    kotlin基础之后的

    协程

    协程有个特点就是轻量级:
    协程与线程类似,但是他比线程更加的轻量级,如开启1000个线程和1000个协程去执行一个工作,其中的开启线程就是开了1000个线程,这里是非常消耗资源的,而1000个协程,其中只是开了几个或几十个线程去工作.

    协程的使用

    使用协程需要导入一些包

    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion"//版本用了0.20版本再高点低点问题不大
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutinesVersion"//这个是android使用的包,这里会多一个使用handle调度协程所工作的线程的方法
    

    kotlin的协程可使用async或launch来开启,两者功能类似,只是async默认是异步的,即不会立即运行,且两者的返回值不同,launch返回一个Job对象,async返回一个Deferred( -一个轻量级、非阻塞的 future),而Deferred内部是继承了Job所以两者之间的差异并不是很大
    使用协程时,需要给其指定协程所执行的上下文或环境(也可以说是在哪个线程),而0.20版本中提供了以下的默认环境

    //它里面定一个线程池,去处理你的代码块
            CommonPool
    //无限制,就是当前什么线程就是什么线程。
           Unconfined
    //安卓开发用的,用android的handler处理你的代码块,构造方法需要提供Handler
            HandlerContext
    

    正式使用协程

    val fu = async(CommonPool  ){
    
    }
    println(fu.isCancelled)//fu协程是否取消
    
    val obj = launch(Unconfined){
    
    }
    obj.cancel()//取消obj协程
    
    val UI = HandlerContext(Handler(Looper.getMainLooper()), "UI")
    launch(UI){//这里就是运行在UI线程中了
    }
    

    集合类

    kotlin中使用的集合类是java的集合类,虽然如此,kotlin还为其添加了很多便于开发者操作的方法/函数

    val list = arrayListOf(1,4,6,46)//这里的list其实就是java的ArrayList
    val map = hashMapOf(1 to "one",4 to "four")//这里的map是java的HashMap
    

    中缀调用

    这个有一点点像C++中对运算符的重载的感觉,虽然表象上差距挺大的
    像集合类中map中的to,这个就是个中缀调用,定义中缀调用函数的关键字是infix,他可以作用在普通函数(需要再一个类中的,顶层函数不可以)和扩展函数中且规定了其函数参数只能有一个,返回值可有可无

    infix fun Int.infixTest(other:Int):Int {//这里的定义了一个扩展函数,且支持中缀调用
        println(this+other)
        return this+other
    }
    
    c infixTest 3//中缀调用方式,其会返回c+3的和
    c.infixTest(3)//当然这样子也是可以的,效果是一样的
    

    而kotlin中对Any提供了一个扩展函数to,其实现为

    infix fun Any.to(other:Any) = Pair(this,other)//Pair是kotlin标准库中的类,其存储了一个键值对,其实也就是两个元素,所以map初始化时可以使用to来定义每一个键值对
    

    字符串处理

    kotlin的字符串处理也是使用了java的字符串处理,只不过kotlin还为其添加了很多易用的扩展函数,如获取字符串第一个或最后一个字符的函数

    "12.654-A".split(".-".toRegex)//字符串分隔,这里使用Regex类型来解决一些字符问题
    "12.654-A".split(".","-")//kotlin支持多个字符的切割
    """/\.asd\'"""//三重冒号也就是左右两边都是""",被三重冒号包围的字符串为纯字符串,无任何的转义序列
    

    类委托

    kotlin中提供了类委托的操作,关键字为by,这委托是在实现一个接口时,将实现部分委托给一个属性,使得本类成为一个中转的类

    class ListTest<T> (val list : List<T> = ArrayList()) : List<T> by list{这里的ListTest类实现了List,其实现部分委托给了list属性
    override fun add(value : T){//重写add方法
    println(value)
    list.add(value)
    }
    }
    

    单例

    在kotlin中实现单例非常的方便,使用object关键字就可以实现

    object Test5{//定义
    val data = "data"
    fun printAll(){
    println(data)
    }
    }
    Test5.printAll()//调用单例
    

    伴生对象

    伴生对象有点像java的静态方法,但kotlin中没有static这东西,而代替他的是使用顶层函数,但是顶层函数无法访问类的private成员,所以这是就需要伴生对象了,其关键字为companion,一般会配合object使用
    并且伴生对象也支持扩展函数,一般可以声名一个空的伴生对象,留给之后扩展

    class A{
    companion object{//定义伴生对象
    fun bar(){
    println("伴生")
    }
    }
    }
    A.bar()//调用时,可以直接通过类型名调用
    interface ITest{
    }
    class B{
    companion object My : ITest{//定义伴生对象,并给个名字My,且可以实现接口
    fun bar(){
    println("伴生")
    }
    }
    }
    B.My.bar()//调用时通过类名加伴生名字调用
    fun ITestTest(test:ITest){//此函数需要传入一个ITest
    }
    ITestTest(B)//或者B.My
    

    let函数

    let函数有点像groovy的闭包,使用let后,可在{}中通过it或自定义为其他参数来使用调用者

    var str :String? = "666"
    str?.let{//此处会在str不为空的情况下,才调用let中的内容,否者不调用
    println(it.length)//此时使用的it其实就是str对象
    it.indexOf(1)
    }
    
    

    lateinit 属性延时初始化

    在使用lateinit可以声名一个不为空的属性,且声名时不初始化,在之后再初始化,这里的属性需要为var,如果为val的话就必须在构造函数中初始化

    private lateinit var data:String
    
    fun setData(s:String){
    data = s
    }
    

    如果在初始化之前就调用了data,那么就会报错..

    by lazy惰性初始化

    这个与上面的latinit不太一样,by lazy()是在使用的时候在进行相关的初始化,且默认线程安全

    class User(val name:String){
    var age by lazy{
    loadAge()//此处为初始化的操作
    }
    }
    var user =User("lewis_v")
    user.age//在此处调用age时才会进行age的初始化
    

    Nothing类型

    Nothing类型作为函数返回值,表示这个函数不会正常结束,也表明这个函数不会返回任何东西

    fun fail(msg:String) : Nothing{
    throw IllegalStateException(msg)
    }
    fun fail(msg:String) : Nothing{
    return 0//这里报错,不给编译,一定要出错才可以
    }
    

    运算符重载

    kotlin的运算符重载与C++的类似,其关键字为operator,其可重载二元、一元、复合、比较运算符

    二元

    二元的有*/%+-运算
    其对应的重载方法为
    表达式 || 函数名

    • || times
      / || div
      % || mod
    • || plus
    • || minus
    data class User(var age:Int){
    operator fun plus(other:User):User{//重载+号,+号左边的为自身,右边的为other,其他符号与这个一样操作
    return Point(age + other.age)
    }
    }
    User(5) + User(5) //此处的结果为User(10)
    

    复合

    复合的有+= -= *= /= %=
    其重载的方法与其二元运算符差不多,在其基础上加上Assign即可,如+号为plusAssign,-号minusAssign

    data class User(var age:Int){
    operator fun plusAssign(other:User):User{//重载+=号,+=号左边的为自身,右边的为other,其他符号与这个一样操作
    return Point(age + other.age)
    }
    }
    var user = User(5)
    user += User(5) //此处的结果user变为User(10)
    

    一元

    一元的有++ -- + - !
    其对应的重载方法为
    表达式 || 函数名
    +a || unaryPlus
    -a || unaryMinus
    !a || not
    ++a,a++ || inc
    --a,a-- || dec

    data class User(var age:Int){
    operator fun inc():User{//重载++号,一元没有参数传入,只有对自己进行操作,其他符号与这个一样操作
    return Point(age + 1)
    }
    }
    var user = User(5)
    user ++  //此处的结果user变为User(6)
    

    比较运算符

    ==为重写equals
    其余的> < >= <=之类的比较需要实现Comparable,实现其中的方法compareTo,在比较时,会比较此方法返回数值的大小

    小结

    以上是前段时间学习kotlin的学习笔记,总的来说,kotlin使用上更加方便简洁,且内部已经提供了很多常用的操作如集合类的操作等等...

    相关文章

      网友评论

          本文标题:[大白装逼]从java到kotlin的学习笔记

          本文链接:https://www.haomeiwen.com/subject/hvslmftx.html