美文网首页
那些我在Android开发中所喜爱的Kotlin特性

那些我在Android开发中所喜爱的Kotlin特性

作者: AndrLin | 来源:发表于2017-09-21 19:51 被阅读0次

    在2017年5月谷歌I/O大会上,官方宣布Kotlin为Android开发的新语言,当然有人看好也有人不看好,距离现在我已经在两个线上项目中使用Kotlin开发。对于Android开发人员来说,学习Kotlin不需要花费太多时间,只需要看看官方文档,就可以直接上项目了。当然了,遇到问题肯定是会有的,就像使用第三方开源库一样存在风险。所以不用考虑太多,Just do it!

    下面就来聊聊我在开发过程中常用到的和喜欢的Kotlin特性。

    val和var

    我相信写代码写得最多的,应该就是定义各种变量了。Kotlin中使用val(不可变)和var(可变)关键字来定义变量。值得注意的是val定义的变量,最终会被编译成final类型的。在确定一个变量在使用过程中是不可变的,应该尽量使用val来定义。

    val s = "test string" // String
    var i = 1 // Int 可变
    i = 2
    val c = activity // Activity
    
    // 指定变量类型
    val user: User = User()
    val a: Any = 23 // Any 代表a可以为任意对象,类似java中的Object。最终会被编译成Object a = Integer.valueOf(1);
    
    

    字符串拼接

    Kotlin中支持在字符串中直接使用变量,相对于Java会更加直观易懂。

    // 定义一个User对象
    class User constructor(var name: String, var age: Int)
    
    // 字符串中使用User对象的属性
    var user: User = User("张三", 23)
    val toastStr: String = "这位是${user.name},今年${user.age}岁"
    
    // 字符串中直接使用变量
    val name = "赵四"
    val str: String = "你好,$name"
    

    实际上在Android中不建议使用字符串直接拼接,而是使用StringBuilder。值得注意的是Kotlin在使用变量直接进行拼接的时候,最终编译会转换成StringBuilder,而使用对象的属性进行拼接,并不会转换成StringBuilder。

    变量初始化

    null初始化

    kotlin初始化变量时,允许将变量设置为null,但是必须使用?修饰其变量类型。

    var str: String? = null
    

    kotlin提供了一种方式,强制任务这个变量时不为null的,即!!

    val srtLength = str!!.length
    

    当然这样是不安全的,只有在你非常确定变量肯定不为空的时候,才能使用它。kotlin提供一种判断变量不为空的方法?.let{},如果非要使用!!,建议这样写:

    str?.let { 
         val strLengh = str!!.length
    }
    

    每次都要做这样的判断空,及使用!!,那么有没有其他方式初始化呢?当然有,再接着往下瞧。

    lateinit延迟初始化

    没错延迟初始化,使用关键字lateinit修饰变量即可,当然在使用变量之前别忘了保证变量被初始化了。

    lateinit var str: String
    

    懒加载

    kotlin提供了非常实用的懒加载功能,在变量第一次使用的时候去初始化变量。

    // 华妃
    var huaFei: String by lazy {
        println("哎呀,我终于要被宠幸啦!")
        "华妃" // 后面的Lambda表达式中会有说明
    }
    

    相信Android开发中,懒加载会非常使用,大大提高应用相应速度。

    超级厉害的when表达式

    when可以取代switchif(){}else if(){}else{}使用。

    fun check(obj: Any) {
            when(obj) {
                0 -> print("obj == 0")
                1,2,3 -> print("obj 是 1或2或3")
                in 3..10 -> print("obj在3~10之间")
                is User -> print("obj是User对象")
                else -> {
                    print("不认识这个对象")
                    throw Exception(obj.toString())
                }
            }
        }
    

    类与方法的继承

    在Android开发中我们经常会写一些基类,那我们来尝试一下:

    class TestA {
        fun testFunc() {
        }
    }
    
    class TestB : TestA() {
    }
    

    看到这里大家肯定会有疑问:咦,你这TestB类没写完呀?没错是没写完,不是我不想写,而是当你写完继承TestA的时候编译器就报错了,编译器提示TestA需要关键字open来修饰。这是为什么呢?我们来看看TestA编译后的代码:

    public final class TestA {
       public final void testFunc() {
       }
    }
    

    没错,Kotlin在默认情况下类和方法都是final类型的。

    不可变的类比可变类更加易于设计、实现和使用。它们不容易出错,且更加安全。—— 《Effective Java》

    Kotlin中如果想要类或方法可以被继承或重写,只需要添加上open关键字即可:

    open class ClassA {
        open fun testFunc() {
        }
    }
    

    类的伴生对象——companion object

    在Android开发中,通常大家都为在Activity中提供一个静态方法,方便他人调用。在kotlin中我们该如何实现呢?用的童鞋就纳闷,为啥kotlin中没有提供static关键字来创建静态方法?实际上它提供了,就是companion object,我们可以在里面定义常量、属性及函数。

    class TestActivity : AppCompatActivity() {
    
        companion object {
            private val APP_ID = "15646a06818f61f7b8d7823ca833e1ce"
            fun start(context: Context, title: String) {
                val intent = Intent(context, TestActivity::class.java)
                intent.putExtra("title", title)
                context.startActivity(intent)
            }
        }
        ...
    }
    

    编译后,会在TestActivity中生成静态内部类Companion。那么我们在Java文件中想要调用Kotlincompanion object就可以如下使用:

    // java
    TestActivity.Companion.start(context, "测试");
    
    // kotlin
    TestActivity.start(context, "测试");
    

    看到这里想必大家都知道在kotlin中应该如何实现单例了。

    函数的参数居然可以设置默认值

    这个特性也是用用得比较多的,在程序中经常会一个函数,很多地方调用,当然就需要传很多参数,每次都需要输入很是麻烦。还有相信大家也遇到过Retrofit请求接口,有些参数有时候是不传的,写起来比较麻烦,甚至会写多个函数。那么接下来看看kotlin我们可以怎么处理:

    interface MsgApi {
        @GET("http://........")
        fun getList(@Query("offset") offset: Int, 
                    @Query("limit") limit: Int = 10,
                    @Query("msgType") msgType: Int?: null): Observable<BaseProtocol<MsgEntity>>
    }
    
    // 使用
    RetrofitHelper.getMsgApi()
        .getList(1)
        ....
    

    没错,使用的时候我们只需要传offset,也就是没有设置默认值的参数即可,limit会使用默认值10msgType如果不传则获取所有类型,Retrofit中如果一个参数不传可以直接赋值null,这样请求过程就不会传递此参数。是不是相当方便?

    这只是一个简单的例子,如果说参数非常多的情况,使用默认值会更加方便:

    fun reformat(str: String,
                 normalizeCase: Boolean = true,
                 upperCaseFirstLetter: Boolean = true,
                 divideByCamelHumps: Boolean = false,
                 wordSeparator: Char = ' ') {
     ...... 
     }
     
     // 直接使用默认参数访问,只需要传第一个参数的值
     reformat("str")
    

    Lambda表达式

    Java8中开始支持Lambda表达式,使用起来确实是更方便,刚开始使用获取会比较困难,不知道怎么写,不过多写写自然就会了,也能看懂了(呵呵哒)。kotlin中的Lambda表达式可以简单的概述为:

    • lambda表达式总是被大括号括着
    • 其参数(如果有的话)在->之前声明(参数类型可以省略,如果参数没有使用可以用_代替)
    • 函数体(如果存在的话)在->后面

    Lambda表达式的完整语法形式如下:

    val sum = {x: Int, y: Int -> x + y}
    

    如果推断出的该lambda的返回类型不是Unit,那么该lambda主体中的最后一个(或可能是单个)表达式会视为返回值。(前面懒加载中的huaFei最后一个表达式为华妃,即最终huaFei的赋值为华妃

    val sum: (Int, Int) -> Int = {x, y -> x + y }
    

    如果lambda只有一个参数,我们可以不用声明这个参数,kotlin会隐含地为我们声明其名称为 it

    private val userList = listOf(User("张三", 21), User("李四", 28), User("王五", 10))
    val maxAgeUser = userList.maxBy{ it.age } // 返回年龄最大的
    

    我们来看一看常用的setOnClickListenerkotlin中可以怎么写呢?

    // 使用object实现匿名内部类
    helloTv.setOnClickListener(object : View.OnClickListener {
        override fun onClick(v: View) {
            println("Hello Kotlin")
        }
    
    })
    // Kotlin允许Java库的一些优化,Interface中包含单个函数可以被替代为一个函数
    helloTv.setOnClickListener({ view ->
        println("Hello Kotlin")
    })
    // 如果函数(setOnClickListener)的最后一个参数是一个函数,那么我们可以把这个函数移到括弧的外面
    helloTv.setOnClickListener() { view ->
        println("Hello Kotlin")
    }
    // 如果这个函数有且只有一个参数,那么我们可以把括弧去掉
    helloTv.setOnClickListener { view ->
        println("Hello Kotlin")
    }
    // 如果`->`左边的参数没有使用到,可以直接省略
    helloTv.setOnClickListener {
        println("Hello Kotlin")
    }
    // 或者使用`it`
    helloTv.setOnClickListener { 
        println("Hello Kotlin ${it.id}")
    }
    
    

    看起来强大的扩展函数,实际也很强大 -_-!!!

    kotlin提供了一种方法,可以为一个类增加新的行为方法。先来个简单的例子,新建一个extention.kt文件:

    fun Any.log(message: String, tr: Throwable? = null) {
        if (tr == null) Log.d(this.javaClass.simpleName, message) else Log.d(this.javaClass.simpleName, message, tr)
    }
    
    fun Context.toast(msg: String, duration: Int = Toast.LENGTH_SHORT) {
        Toast.makeText(this,msg,duration).show()
    }
    

    简单的扩展了两个方法,一个可以在任何地方使用的log输出方法,一个toast方法。我们来看看编译后的代码是怎样的:

    public final class ExtentionKt {
       public static final void log(@NotNull Object $receiver, @NotNull String message, @Nullable Throwable tr) {
          Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
          Intrinsics.checkParameterIsNotNull(message, "message");
          if(tr == null) {
             Log.d($receiver.getClass().getSimpleName(), message);
          } else {
             Log.d($receiver.getClass().getSimpleName(), message, tr);
          }
    
       }
    
       // $FF: synthetic method
       // $FF: bridge method
       public static void log$default(Object var0, String var1, Throwable var2, int var3, Object var4) {
          if((var3 & 2) != 0) {
             var2 = (Throwable)null;
          }
    
          log(var0, var1, var2);
       }
    
       public static final void toast(@NotNull Context $receiver, @NotNull String msg, int duration) {
          Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
          Intrinsics.checkParameterIsNotNull(msg, "msg");
          Toast.makeText($receiver, (CharSequence)msg, duration).show();
       }
    
       // $FF: synthetic method
       // $FF: bridge method
       public static void toast$default(Context var0, String var1, int var2, int var3, Object var4) {
          if((var3 & 2) != 0) {
             var2 = 0;
          }
    
          toast(var0, var1, var2);
       }
    }
    
    

    没错,生成了一个ExtentionKt类,里面都是静态方法。设置了默认值的,会生成一个方法名$default的函数,在我们使用默认参数时,访问的就是类似的相关函数了。实际上有点类似于我们写Java的工具类。

    在满足情况的条件下,我们可以这样使用:

    // kotlin
    log("这是一条日志")
    toast("Hello Kotlin")
    toast("Hello Kotlin", Toast.LENGTH_LONG)
    
    // java
    ExtentionKt.toast(context, "Hello Kotlin", Toast.LENGTH_LONG)
    

    除了这些我们还能在开发过程中,扩展哪些有用的函数呢?当然kotlin给了我们无限的可能性,什么都可以扩展。

    现在Android开发基本上都会使用RxJava和Retrofit,通常情况下我们会使用RxJava的compose(Transformer)来进行数据变换等操作,那么我们是否可以通过扩展函数,给Observable扩展一些常用的操作呢?比如说:

    • 将Retrofit返回的BaseProtocol,只返回BaseProtocol中的T data
    • 扩展一个网络请求通用错误检测方法

    接下来我们就来尝试一下:

    先定义一个错误检测Transformer

    class CheckError<T> : ObservableTransformer<T, T> {
        override fun apply(upstream: Observable<T>): ObservableSource<T> {
            return upstream
                    .flatMap(Function<T, ObservableSource<T>> { baseProtocol ->
                        if (baseProtocol is BaseProtocol<*>) {
                            if (!baseProtocol.success) {
                                when(baseProtocol.code) {
                                    //可以在此处分别处理服务器端定义的code码,并返回相应的异常信息
                                    else -> {
                                        return@Function Observable.error(ApiException(baseProtocol.code, baseProtocol.message))
                                    }
                                }
                            }
                        }
    
                        return@Function Observable.just(baseProtocol)
                    })
        }
    }
    

    再定义检测错误的扩展函数

    fun <T> Observable<T>.checkError(): Observable<T> {
        return this.compose(CheckError())
    }
    
    fun <T> Observable<T>.ioToMain(): Observable<T> {
        return this.observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
    }
    

    我们再来看看是如何使用的呢?

    // 使用上面消息列表的例子
    RetrofitHelper.getMsgApi()
        .getList(1)
        .checkError()
        .ioToMain()
        .subscribe({
            // 在这里处理返回的BaseProtocol
            toast(it.message)            
         },{ 
            // 在这里处理异常
            if (it is ApiException) {
                    // 处理相应异常
            }          
         })
    

    是不是很简单,看起来就像是RxJava自带的函数一样。在AndroidStudio中扩展函数会被标记为黄色的,一眼就能看出来。

    上面只是简单的使用,你也可以自定义一个Observer类,在其onError方法中统一处理异常。

    我们再来扩展一个数据转换的,获取到我们真正想要的实体类。

    fun <T> Observable<BaseProtocol<T>>.transformProtocol(): Observable<T> {
        return flatMap { protocol ->
            // 在这里我就不做相应的判断了,直接结合checkError()使用即可
            Observable.just(protocol.data)
        }
    }
    

    再来看看使用:

    RetrofitHelper.getMsgApi()
        .getList(1)
        .checkError()
        .transformProtocol()
        .subscribe({
            // 在这里拿到的就不会是BaseProtocol了,而是BaseProtocol中的data
            toast(it.msgContent)           
         },{ 
            // 在这里处理异常
            if (it is ApiException) {
                    // 处理相应异常
            }          
         })
    

    总结

    Kotlin始终也算是一门新的语言,而且还再不断的改进,相信会越来越好。当然了,以上内容只是我个人使用Kotlin开发的一些方式,如果有说的不对的地方,还希望大家指正,在此感谢!

    相关文章

      网友评论

          本文标题:那些我在Android开发中所喜爱的Kotlin特性

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