前言,本文章并不是教学向的文章,本文意在总结和分享在实际使用kotlin进行开发时踩到的坑和好用的特性的分享,需要具备kotlin基础知识后在来阅读,如果你想找一篇教学类的文章,那可能中文官网更适合你。
让人关注的特性
官网最大宣传点有四个:
- *安全
- *互操作Java,kotlin互相调用
- 简洁
- 方便的使用技巧
- 值得注意的特性和功能
- 工具友好 as的支持
一,空检查,空判断
简单来说就是:声明变量的时候就必须声明变量是否可空,不可空的变量不能赋值为空
摘自官网
/*
彻底告别那些烦人的 NullPointerException——著名的十亿美金的错误
*/
var output: String //竟然可以这样写,但是在使用时会编译出错
output = null // 编译错误
// Kotlin 可以保护你避免对可空类型进行误操作
val name: String? = null // 可空类型
println(name.length()) // 编译错误
// 补充
println(name?.length()) // 当变量为空时不会调用length()方法
二,互操作,kotlin可以和java互相调用
1.我们是不是一定会遇到与java交互的情况?
- 在已经有项目的情况下很难一下把项目的java全转kotlin,因为工作量太大,风险太大,转变需要过程
- 就算项目是全kotlin的也避免不了合java的三方库调用
2.在与java互相调用的时候需要注意的一些问题
1.可以绕过kotin的空检查,在kotlin文件中调用java的方法返回对象时
kotlin从java获取对象有三种情况
- 不写类的类型,用kotlin的类型自动推导
![](https://tva1.sinaimg.cn/large/007S8ZIlly1geipehvhsrj30y20i2tb7.jpg)
可以看到调用java的方法,然后用自动推导出来的类型是强制转换成非空的,但是不管返回是否为空,编译是可以通过的
如果这个时候getTestModel()
的返回值是空的就会引发NullPointerException
- 写类的类型但是不可为空
首先这样写编译也是没问题的
但是如果getTestModel()
返回空值的话会爆IllegalStateException
如下图:
3.写类的类型可空
在与java互相调用时,从java来的返回值推荐直接这样写,比较安全
三.一切皆是对象
Kotlin 中没有基础数据类型,只有封装的数字类型
四.kotlin中函数是头等的(可以了解一下函数式编程)
这意味着它们可以存储在变量与数据结构中、作为参数传递给其他高阶函数(高阶函数是将函数用作参数或返回值的函数。)以及从其他高阶函数返回。可以像操作任何其他非函数值一样操作函数。
最直接的带来的好处就是java的回调写起来更容易了
以前java想写个回调
kotlin写一个
五.扩展函数和扩展属性
1.扩展函数
从面对对象编程的角度来说扩展函数是很有意义的,比如 将String 转为 int 理应String.toInt()更符合面对对象的思想而不是Integer.paseInt();
而且在我们自己写这种工具方法时也通常放在工具类里,但是这个工具类本身还需要我们去项目里找或者问一些资历比较老的同学,也有可能它根本不存在,这无形之中增加了开发和维护的成本,而使用扩展函数来代替Utils+静态方法的组合就很大程度的解决了这个问题。
不管怎么说把一个方法直接挂到一个类上这种操作都很magic那我们看一下它到底是什么东西
1.1kotlin的扩展函数示例,没错,直接这样写道kt的文件里,外面不需要也不可以包个什么class之类的东西,这个例子在一个叫Expand.kt
的文件里
fun String.toMyInt(): Int? {
return try {
Integer.parseInt(this)
} catch (e: NumberFormatException) {
null
}
}
//简单展示一下
fun show(){
val str = "test"
str.toMyInt()
}
1.2.编译为java之后
@Metadata(
mv = {1, 1, 16},
bv = {1, 0, 3},
k = 2,
d1 = {"\u0000\u000e\n\u0000\n\u0002\u0010\b\n\u0002\u0010\u000e\n\u0002\b\u0002\u001a\u0011\u0010\u0000\u001a\u0004\u0018\u00010\u0001*\u00020\u0002¢\u0006\u0002\u0010\u0003¨\u0006\u0004"},
d2 = {"toMyInt", "", "", "(Ljava/lang/String;)Ljava/lang/Integer;", "app"}
)
public final class ExpandKt {
@Nullable
public static final Integer toMyInt(@NotNull String $this$toMyInt) {
Intrinsics.checkParameterIsNotNull($this$toMyInt, "$this$toMyInt");
Integer var1;
try {
var1 = Integer.parseInt($this$toMyInt);
} catch (NumberFormatException var3) {
var1 = null;
}
return var1;
}
}
所以扩展方法是不能Override的,如果想在java中调用kotlin的扩展方法其实就是调用了一个静态方法而已,也是没问题的。
2.扩展属性
同样的kotlin的扩展属性也跟扩展函数的方式相似
2.1kotlin扩展属性
val String.lastChar: Char
get() = get(length - 1)
var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value) {
this.setCharAt(length - 1, value)
}
2.2编译为Java后
public static final char getLastChar(@NotNull String $this$lastChar) {
Intrinsics.checkParameterIsNotNull($this$lastChar, "$this$lastChar");
return $this$lastChar.charAt($this$lastChar.length() - 1);
}
public static final char getLastChar(@NotNull StringBuilder $this$lastChar) {
Intrinsics.checkParameterIsNotNull($this$lastChar, "$this$lastChar");
return $this$lastChar.charAt($this$lastChar.length() - 1);
}
public static final void setLastChar(@NotNull StringBuilder $this$lastChar, char value) {
Intrinsics.checkParameterIsNotNull($this$lastChar, "$this$lastChar");
$this$lastChar.setCharAt($this$lastChar.length() - 1, value);
}
同样也是编译为静态方法
五.密封类 sealed class,协程
类的继承只能写在相同的文件里
这个东西它有什么用?
在java中很难仅通过父类就获取到它有多少个自类的,而密封类限制了子类必须父类写在一个文件中,这种功能某种意义上说实际上是枚举的一种扩展,枚举的常量里不能写方法,密封类的子类还是类,所以可以像普通的类一样写方法写功能都不受到限制。
同时,ide也容易知道你的有哪些子类,反应到实际开发中就是这样:
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// 不再需要 else 子句,因为我们已经覆盖了所有的情况
}
感觉还是没啥用啊!!
好吧,实际上经典的应用场景是在使用协程做异步时,对返回的数据做处理时用的,比如网络请求:
在实际开发中的应用举例:
不使用协程的网络请求:
HttpUtils.getData({ data ->
//do something
}, { errCode ->
//error
})
如果使用协程还用这种写法来写的话,多少有点浪费了协程的一些特性(异步代码写法),所以我们大概率会写成这样:
1.返回的密封类
sealed class HttpResponse
class Succeed :HttpResponse(){
fun getResData():String{
//.....
return "succeed"
}
}
class Failed : HttpResponse(){
fun getHttpCode():Int{
return 500
}
fun showErrorMsgToast(){
//大概不会有这样的需求把,大概
}
}
2.网络请求的实现
suspend fun getData():HttpResponse{
//假装我做了网络请求
return Succeed()
}
3.实际的应用
suspend fun coroutinesGetApi() {
val response: HttpResponse = getData()
when (response) {
is Succeed -> {
response.getResData()
}
is Failed -> {
response.showErrorMsgToast()
}
}
}
哎···我也分不清这是精妙的设计还是被逼的了····