Android Kotlin 基础(一)

Android Kotlin 基础

这里讲一个 Java 和 Kotlin 命名由来的小插曲。

我们知道 Java 就是著名的爪哇岛,爪哇岛盛产咖啡,据说就是一群研究出 Java 语言的牛人们在为它命名时由于闻到香浓的咖啡味,遂决定采用此名称。

Kotlin 来源于芬兰湾中的 Kotlin 岛。

因此,我们在代码段的开头以「☕️」来表示 Java 代码段,「🏝️」来表示 Kotlin 代码段。

本文章转载于:扔物线朱凯老大处 【https://rengwuxian.com/】
本文首发于:https://rengwuxian.com/mian-shi-hei-dong-android-de-jian-zhi-dui-cun-chu-you-mei-you-zui-you-jie/
微信公众号:扔物线

一 初级

0-基本类型

在 Kotlin 中,所有东西都是对象,Kotlin 中使用的基本类型有:数字、字符、布尔值、数组与字符串。

🏝️
var number: Int = 1 // 👈还有 Double Float Long Short Byte 都类似
var c: Char = 'c'
var b: Boolean = true
var array: IntArray = intArrayOf(1, 2) // 👈类似的还有 FloatArray DoubleArray CharArray 等,intArrayOf 是 Kotlin 的 built-in 函数
var str: String = "string"

这里有两个地方和 Java 不太一样:

  • Kotlin 里的 Int 和 Java 里的 int 以及 Integer 不同,主要是在装箱方面不同。

    Java 里的 int 是 unbox 的,而 Integer 是 box 的:

    ☕️
    int a = 1;
    Integer b = 2; // 👈会被自动装箱 autoboxing
    

    Kotlin 里,Int 是否装箱根据场合来定:

    🏝️
    var a: Int = 1 // unbox
    var b: Int? = 2 // box
    var list: List<Int> = listOf(1, 2) // box
    

    Kotlin 在语言层面简化了 Java 中的 int 和 Integer,但是我们对是否装箱的场景还是要有一个概念,因为这个牵涉到程序运行时的性能开销。

    因此在日常的使用中,对于 Int 这样的基本类型,尽量用不可空变量。

    Java 中的数组和 Kotlin 中的数组的写法也有区别:

    ☕️
    int[] array = new int[] {1, 2};
    

    而在 Kotlin 里,上面的写法是这样的:

    🏝️
    var array: IntArray = intArrayOf(1, 2)
    // 👆这种也是 unbox 的
    

简单来说,原先在 Java 里的基本类型,类比到 Kotlin 里面,条件满足如下之一就不装箱:

  • 不可空类型。
  • 使用 IntArray、FloatArray 等。

1-变量的声明和赋值

首先回忆一下 Java里面声明一个变量View的写法:

private View v;

而在Kotlin里声明一个变量的写法:

var v: View

对比着看上去,只是语法的格式有点不同,但如果真的这么写,IDE就会报错:

var v: View 
// 👆这样写 IDE 会报如下错误
// Property must be initialized or be abstract

这个提示是在说,属性需要在声明的同时进行初始化或者延迟初始化lateinit,除非你把它声明为抽象的;这句提示给出了两个问题点:

  • 那什么是属性呢?这里我们可以简单的类比为Java里面的成员变量或者字段来Kotlin里面的Property,虽然他们其实有些不一样,但Kotlin里面的Property功能会多一些。
  • 变量还可以声明为抽象的?嗯,是的,Kotlin里面的功能,归属于Kotlin的特性,后面会讲。

属性为什么要求初始化呢?

因为Kotlin的变量是没有默认值的,这点不像Java,在Java里面成员变量都是有默认值的:

String name; // 👈默认值是 null
int age; // 👈默认值是 0

当然Java里面也就只有成员变量是有默认值的,局部变量也是没有默认值的,如果不给它初始化也会报错:

void run() {
    int count;
    count++; 
    // 👆IDE 报错,Variable 'count' might not have been initialized
}

回到Kotlin,既然声明的时候需要初始化,那就给它一个默认值吧:

var v: View = null
// 👆这样写 IDE 仍然会报错,Null can not be a value of a non-null type View

很遗憾,你会发现仍然会报错~

又不行,IDE 告诉我需要赋一个非空的值给它才行,怎么办?Java 的那套不管用了。

其实这都是 Kotlin 的空安全设计相关的内容。很多人尝试上手 Kotlin 之后快速放弃,就是因为搞不明白它的空安全设计,导致代码各种拒绝编译,最终只能放弃。所以咱先别急,我先来给你讲一下 Kotlin 的空安全设计。


2-Kotlin 的空安全设计

简单来说就是通过 IDE 的提示来避免调用 null 对象,从而避免 NullPointerException。其实在 androidx 里就有支持的,用一个注解就可以标记变量是否可能为空,然后 IDE 会帮助检测和提示,我们来看下面这段 Java 代码:

@NonNull
View view = null;
// 👆IDE 会提示警告,'null' is assigned to a variable that is annotated with @NotNull

而到了 Kotlin 这里,就有了语言级别的默认支持,而且提示的级别从 warning 变成了 error(拒绝编译):

var view: View = null
// 👆IDE 会提示错误,Null can not be a value of a non-null type View

在 Kotlin 里面,所有的变量默认都是不允许为空的,如果你给它赋值 null,就会报错,像上面那样。

这种有点强硬的要求,其实是很合理的:既然你声明了一个变量,就是要使用它对吧?那你把它赋值为 null 干嘛?要尽量让它有可用的值啊。Java 在这方面很宽松,我们成了习惯,但 Kotlin 更强的限制其实在你熟悉了之后,是会减少很多运行时的问题的。

不过,还是有些场景,变量的值真的无法保证空与否,比如你要从服务器取一个 JSON 数据,并把它解析成一个 User 对象:

🏝️
class User {
    var name: String = null // 👈这样写会报错,但该变量无法保证空与否
}

这个时候,空值就是有意义的。对于这些可以为空值的变量,你可以在类型右边加一个 ? 号,解除它的非空限制:

🏝️
class User {
    var name: String? = null
}

加了问号之后,一个 Kotlin 变量就像 Java 变量一样没有非空的限制,自由自在了。

你除了在初始化的时候可以给它赋值为空值,在代码里的任何地方也都可以:

🏝️
var name: String? = "Mike"
...
name = null // 👈原来不是空值,赋值为空值

这种类型之后加 ? 的写法,在 Kotlin 里叫可空类型

不过,当我们使用了可空类型的变量后,会有新的问题:

由于对空引用的调用会导致空指针异常,所以 Kotlin 在可空变量直接调用的时候 IDE 会报错:

🏝️
var view: View = Java.fun (编译器认为返回值一定是不为空,但fun返回值有可能为空)
view.setBackgroundColor(Color.RED) must null
// 👆这样写会报错,Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type View?

「可能为空」的变量,Kotlin 不允许用。那怎么办?我们尝试用之前检查一下,但似乎 IDE 不接受这种做法:

🏝️
if (view != null) {
    view.setBackgroundColor(Color.RED)
    // 👆这样写会报错,Smart cast to 'View' is impossible, because 'view' is a mutable property that could have been changed by this time
} 

这个报错的意思是即使你检查了非空也不能保证下面调用的时候就是非空,因为在多线程情况下,其他线程可能把它再改成空的。


那Kotlin怎么处理呢?

那么 Kotlin 里是这么解决这个问题的呢?它用的不是 . 而是 ?.

🏝️
view?.setBackgroundColor(Color.RED)

这个写法同样会对变量做一次非空确认之后再调用方法,这是 Kotlin 的写法,并且它可以做到线程安全,因此这种写法叫做「safe call」。

另外还有一种双感叹号的用法:

🏝️
view!!.setBackgroundColor(Color.RED)

意思是告诉编译器,我保证这里的 view 一定是非空的,编译器你不要帮我做检查了,有什么后果我自己承担。这种「肯定不会为空」的断言式的调用叫做 「non-null asserted call」。一旦用了非空断言,实际上和 Java 就没什么两样了,但也就享受不到 Kotlin 的空安全设计带来的好处(在编译时做检查,而不是运行时抛异常)了。

以上就是 Kotlin 的空安全设计。


理解了它之后再来看变量声明,跟 Java 虽然完全不一样,只是写法上不同而已。

很多人在上手的时候都被变量声明搞懵,原因就是 Kotlin 的空安全设计所导致的这些报错:

  • 变量需要手动初始化,如果不初始化的话会报错;
  • 变量默认非空,所以初始化赋值 null 的话报错,之后再次赋值为 null 也会报错;
  • 变量用 ? 设置为可空的时候,使用的时候因为「可能为空」又报错。

明白了空安全设计的原理后,就很容易能够解决上面的问题了。

关于空安全,最重要的是记住一点:所谓「可空不可空」,关注的全都是使用的时候,即「这个变量在使用时是否可能为空」。

另外,Kotlin 的这种空安全设计在与 Java 的互相调用上是完全兼容的,这里的兼容指:

  • Java 里面的@Nullable 注解,在 Kotlin 里调用时同样需要使用 ?.
☕️
@Nullable
String name;
🏝️
name?.length

Java 里面的 @Nullable 和 @NonNull 注解,在转换成 Kotlin 后对应的就是可空变量和不可空变量,至于怎么将 Java 代码转换为 Kotlin,Android Studio 给我们提供了很方便的工具(但并不完美),后面会讲。

☕️
@Nullable
String name;
@NonNull
String value = "hello";
🏝️
var name: String? = null
var value: String = "hello"

空安全我们讲了这么多,但是有些时候我们声明一个变量是不会让它为空的,比如 view,其实在实际场景中我们希望它一直是非空的,可空并没有业务上的实际意义,使用 ?. 影响代码可读性。

但如果你在 MainActivity 里这么写:

🏝️
class MainActivity : AppCompatActivity() {
    👇
    var view: View = findViewById(R.id.tvContent)
}

虽然编译器不会报错,但程序一旦运行起来就 crash 了,原因是 findViewById() 是在 onCreate 之后才能调用。

那怎么办呢?其实我们很想告诉编译器「我很确定我用的时候绝对不为空,但第一时间我没法给它赋值」。

Kotlin 给我们提供了一个选项:延迟初始化


3-延迟初始化

具体是这么写的:

🏝️
lateinit var view: View

var view : View? = null

这个 lateinit 的意思是:告诉编译器我没法第一时间就初始化,但我肯定会在使用它之前完成初始化的。

它的作用就是让 IDE 不要对这个变量检查初始化和报错。换句话说,加了这个 lateinit 关键字,这个变量的初始化就全靠你自己了,编译器不帮你检查了。

然后我们就可以在 onCreate 中进行初始化了:

🏝️
👇
lateinit var view: View
override fun onCreate(...) {
    ...
    👇
    view = findViewById(R.id.tvContent)
}

哦对了,延迟初始化对变量的赋值次数没有限制,你仍然可以在初始化之后再赋其他的值给 view


4-类型推断

Kotlin 有个很方便的地方是,如果你在声明的时候就赋值,那不写变量类型也行:

🏝️
var name: String = "Mike"
👇
var name = "Mike"

这个特性叫做「类型推断」,它跟动态类型是不一样的,我们不能像使用 Groovy 或者 JavaScript 那样使用在 Kotlin 里这么写:

🏝️
var name = "Mike"
name = 1
// 👆会报错,The integer literal does not conform to the expected type String

但:

// Groovy
def a = "haha"
a = 1
// 👆这种先赋值字符串再赋值数字的方式在 JavaScript 里是可以的

「动态类型」是指变量的类型在运行时可以改变;而「类型推断」是你在代码里不用写变量类型,编译器在编译的时候会帮你补上。因此,Kotlin 是一门静态语言。

除了变量赋值这个场景,类型推断的其他场景我们之后也会遇到。


老规矩:贴一张高清壁纸作为结尾~
喜欢给个赞~
最好就是给个关注和收藏~
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值