在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
可以取代switch
、if(){}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
文件中想要调用Kotlin
的companion 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
会使用默认值10
。msgType
如果不传则获取所有类型,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 } // 返回年龄最大的
我们来看一看常用的setOnClickListener
,kotlin
中可以怎么写呢?
// 使用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
开发的一些方式,如果有说的不对的地方,还希望大家指正,在此感谢!
网友评论