您的位置:首页 > 移动开发 > Android开发

Android知识体系梳理笔记五:Kotlin学习笔记二:空安全,操作符,Lambad表达式...

2017-09-14 15:48 633 查看

前言

已经用kotlin完成了项目中的一个模块,现在总结一下在项目中学到的知识(但想学的很好,要书籍和代码结合到一起才行)

空安全

NullPointerException这异常相信大家都见到过,虽然NPE(NullPointerException)解决起来很简单,但是一出现异常就让人很不爽的对不对,而Kotlin 的类型系统旨在从我们的代码中消除 NullPointerException。zaikotlin中出现NPE 的唯一可能的原因可能是下面几条:

显式调用 throw NullPointerException()

使用了 !! 操作符

外部 Java 代码导致的

对于初始化,有一些数据不一致(如一个未初始化的 this 用于构造函数的某个地方)

PS: 这里有一条可以在JAVA中很快的确定NPE的小技巧:再出现NPE的语句里,绝大部分情况下都是调用者为空。例如:xxx.hhh.getyyy().get() 都是 “.” 前面的语句为空

避免NPE和空安全检查

Kotlin 的类型系统旨在从我们的代码中消除 NullPointerException,那么他是怎么消除的呢

空判断

可以和java中一样的进行null条件判断

if (b != null&&b.length > 0 ) {
return b.length
} else {
return null
}


?操作符

//如果 b 非空,就返回 b.length,否则返回 null,这个表达式的类型是 Int?
b?.length


像上面PS中的xxx.hhh.getyyy().get()我们可以写成xxx?.hhh?.getyyy()?.get(),只要其中一个为null就会返回null,而不出现NPE

let操作符

如果一个数据中有空和非空,而只要对非空值执行某个操作,可以使用let操作符

val listWithNulls: List<String?> = listOf("A", null)
for (item in listWithNulls) {
item?.let { println(it) } // 输出 A 并忽略 null
}


Elvis 操作符

这个操作符实际上和Java中的三目操作符很像,只不过一个做的是空判断,一个做的是Boolean判断

val l = b?.length ?: -1


如果 “?:”左侧的表达式不为空,Elvis 操作符就会返回该表达式的值,如果为空则使用其右边的值

!!操作符

如果需要的值一定不为空,可以用!!标识它

val l = b!!.length


如果b为空,则会抛出一个NPE

修饰符和操作符

这里只提一下在项目中用到的修饰符,修饰符,操作符大全

vararg 允许一个参数传入可变数量的参数

in 指定在 for 循环中迭代的对象;

用作中缀操作符以检查一个值属于一个区间、 一个集合或者其他定义“contains”方法的实体;

在 when 表达式中用于上述目的;将一个类型参数标记为逆变

//vararg 表示参数个数可变,多参数
fun gone(vararg views:View){
for(view in views){
view?.visibility=View.GONE
}
}


val 声明一个只读属性或局部变量

var 声明一个可变属性或局部变量

when 开始一个 when 表达式(执行其中一个给定分支)相当于Java中的switch语句,但比switch要强大很多

override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
when(ev?.action){
MotionEvent.ACTION_DOWN->
doSomeing()
MotionEvent.ACTION_MOVE->
doSomeing()
MotionEvent.ACTION_UP,MotionEvent.ACTION_CANCLE->
doSomeing()

}
return super.dispatchTouchEvent(ev)
}


特定类型转换检查

when(view){
is TextView->
doSomeing()
is Button->
doSomeing()
is listView->
doSomeing()
}


检测一个值在(in)或者不在(!in)一个区间或者集合中

when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}


when 也可以用来取代 if-else if链。 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:

when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}


object 同时声明一个类及其实例

要创建一个继承自某个(或某些)类型的匿名类的对象(可以是类也可以是接口)

photoSetAdapter.setTapListener(object :PhotoSetAdapter.OnTapListener{
override fun onPhotoClick() {
...
}
})


还可以创建单例:这称为对象声明。并且它总是在 object 关键字后跟一个名称。 就像变量声明一样,对象声明不是一个表达式,不能用在赋值语句的右边。

object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ……
}

val allDataProviders: Collection<DataProvider>
get() = // ……
}

...
DataProviderManager.registerDataProvider(……)


类内部的对象声明可以用 companion 关键字标记:这使得其标记的内容看起来像其他语言的静态成员(就是在其里面声明的方法和成员变量可以用类名调用),在运行时他们仍然是真实对象的实例成员,而且,例如还可以实现接口

companion object {
lateinit var instance: AppApplication
}

override fun onCreate() {
super.onCreate()
instance = this
mContext = applicationContext
init()
}

...
//在其他类里可以使用
AppApplication.instance.getXXX()


如果想要把方法和成员变量变成真正的静态成员,在 JVM 平台,可以使用 @JvmStatic 注解

companion object {
//@JvmField 标注这样的属性使其成为与属性本身具有相同可见性的静态字段。
@JvmField
val COMPARATOR: Comparator<Key> = compareBy<Key> { it.value }
}
companion object {
//将这些函数标注为 @JvmStatic 的话。 编译器既会在相应对象的类中生成静态方法,也会在对象自身中生成实例方法。:
@JvmStatic fun foo() {}
fun bar() {}
}


lambda 表达式

lambda 表达式总是被大括号括着

其参数(如果有的话)在 -> 之前声明(参数类型可以省略)

函数体(如果存在的话)在 -> 后面

要说清楚lambda 表达式,不得不说一下Kotlin的高阶函数和匿名函数

高阶函数:高阶函数是将函数用作参数或返回值的函数。使用形式:

fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}


上面的的body为函数参数名,“()”里面的内容就是body这个函数需要的参数,什么都没有表示lock函数里面的这个body是一个无参函数,”->”右边的T表示body函数返回值的类型。而这个body函数还可以继续简化,这就是下面说的匿名函数。

匿名函数:匿名函数看起来非常像一个常规函数声明,除了其名称省略了。其函数体可以是表达式或代码块:

fun(x: Int, y: Int): Int {
return x + y
}


考虑这样一个高阶函数:max(…)

fun compare(x: String, y: String): Boolean {
return x.length < y.length
}
max(strings, compare:(x,y)->Boolean)


可以使用匿名函数简化一下

max(strings, (x,y)-> {x.length < y.length})


这个匿名函数的参数类型和返回值类型虽然都没有写明,但是kotlin的特性就是类型可推断;

然后这个函数还可以再简化一下,那就是lambda 表达式了

lambda 表达式(简单化的函数体):根据上面的3条特性

//上面一条代码可以变为
max(strings, {x,y->x.length<y.length})


如果一个函数接受另一个函数作为最后一个参数,lambda 表达式参数可以在圆括号参数列表之外传递。 so:

//上面一条代码可以变为
max(strings) {x,y->x.length<y.length}


如果高阶函数只有一个函数最为参数值,则这个高阶函数的括号部分可以省略:

//max函数只有一个函数参数
max{x,y->x.length<y.length}


如果 lambda 表达式的参数未使用,那么可以用下划线取代其名称:

//max函数只有一个函数参数
max{_,y->println("$y!")}


it操作符,单个参数的隐式名称:如果lambda 表达式只有一个参数, 那么它的声明可以省略(连同 ->),其名称是 it:

//max函数只有一个函数参数,而这个函数参数只有一个参数
max{x->println("$x!")}


则可以这样

max{println("$it!")}


带接收者的lambda 表达式:Kotlin 提供了使用指定的 接收者对象 调用函数字面值的功能。 在函数字面值的函数体中,可以调用该接收者对象上的方法而无需任何额外的限定符。 这类似于扩展函数,它允许你在函数体内访问接收者对象的成员。

sum : Int.(other: Int) -> Int


可以这样使用

1.sum(2)


SAM(单抽象方法) 转换:Kotlin 支持 SAM 转换,这意味着 Kotlin lambad表达式可以被自动的转换成 只有一个非默认方法的 Java 接口的实现,只要这个方法的参数类型 能够跟这个 Kotlin 函数的参数类型匹配的上。

此功能仅限于Java互操作,只适用与 Kotlin 中对 java 的调用,而不支持对 Kotlin 的调用

只支持Java接口,接口只有一个抽象方法,不支持抽象类

//未使用lambad表达式
Observable.just(...)
.map (object :Function<NewsInfo,NewsMultiItem>{
override fun apply(t: NewsInfo): NewsMultiItem { //在这里不能在skipType使用'!!'符号,因为请求到的skipType字段可能为空,所以'?',?,!!区别
if (NewsUtils.isNewsPhotoSet(t.skipType)){
return NewsMultiItem(t,NewsMultiItem.NEWS_INFO_PHOTO_SET)
}
return NewsMultiItem(t,NewsMultiItem.NEWS_INFO_NORMAL)
}
})


//使用lambad表达式
Observable.just(...)
.map (Function<NewsInfo, NewsMultiItem> {
//t->是可以省略的,下面可以改成it
t ->
//在这里不能在skipType使用'!!'符号,因为请求到的skipType字段可能为空,所以'?',?,!!区别
if (NewsUtils.isNewsPhotoSet(t.skipType)){
return@Function NewsMultiItem(t,NewsMultiItem.NEWS_INFO_PHOTO_SET)
}
NewsMultiItem(t,NewsMultiItem.NEWS_INFO_NORMAL)
})


集合

Kotlin 区分可变集合和不可变集合(lists、sets、maps 等)。精确控制什么时候集合可编辑有助于消除 bug 和设计良好的 API。

不可变集合:Kotlin 的 List 类型是一个提供只读操作如 size、get等的接口。和 Java 类似,它继承自 Collection 进而继承自 Iterable。

可变集合:改变 list 的方法是由 MutableList 加入的。这一模式同样适用于 Set/MutableSet 及 Map

一些框架在Kotlin中遇到的坑

dagger2 依赖注入框架

在使用注解类框架中,要使用Kotlin注解类插件:apply plugin: ‘kotlin-kapt’

依赖:

//dagger
kapt 'com.google.dagger:dagger-compiler:2.11'
compile 'com.google.dagger:dagger:2.11'


在module注解的类中,提供对象的函数的返回类型必须是相对应的类名,在Java中,这个函数的返回类型可以使其实现的父类或接口,而在Kotling中不可以

//java
@PerActivity
@Provides
public BaseQuickAdapter provideManageAdapter() {
return new ManageAdapter(mView);
}


//kotlin
@PerActivity
@Provides
fun providesAdapter():ManageAdapter{
return ManageAdapter(mView)
}


如果还有错误,可以看AndroidStudio的EventLog窗口,具体问题具体分析

结语

最近开始有新项目,恰好这个学习项目的一个模块写完了,就先总结一波学到的知识。Kotlin确实简洁方便,缺点也有,但是多掌握一点技术也是不错的,毕竟人生(矮油,不错哦)。。。

Github

拼搏在技术道路上的一只小白And成长之路

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android kotlin