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

在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开发的一些方式,如果有说的不对的地方,还希望大家指正,在此感谢!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值