1.Kotlin简介以及创建kotlin工程
在 Google I/O 2019 上,Google 宣布 Kotlin 成为 Android 的第一开发语言。这对于开发者来讲意味着,将来所有的官方示例会首选 Kotlin,并且 Google 对 Kotlin 在开发、构建等各个方面的支持也会更优先。
在这个大环境下,Kotlin是必须的趋势,因此,学会并掌握 Kotlin 成了 Android 开发者的当务之急。
android studio 集成了kotlin,创建一个新的kotlin工程,只需要在在创建project的时候选定语言为kotlin即可。
2.kotlin中的变量、函数和类别
为方便理解,之后的内容将结合建议的代码进行解释。
```
package org.kotlinmaster
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
```
以上的内容,便是以kotlin为开发语言,创建了一个Empty Activity之后,MainActivity中的代码。
从上可以看出,导入、包、以及类的关键字和java一样,当然也有一些不同,为了理解简单,我们可以再度简化一下。
在AS上新建一个kotlin文件,不继承任何类,命名为Example,代码如下:
```
package com.example.mediaplayer
class Example {
}
```
2.1变量
2.1.1 变量的声明:
我们声明一个View类型的变量,使用java语言:
```
View v;
```
使用kotlin语言:
```
var v : View
```
对比可以发现,在kotlin里的变量声明,需要使用var关键字,变量名在类名的前面,而且两者中间有冒号隔开。(不管是局部变量,成员变量,甚至是方法中的参数,都是使用 ==变量名 :变量类型== 的方式来书写)
ps:kotlin中语句结尾不需要加分号。
### 2.1.2 空安全机制
以上是kotlin中一般变量的声明,但是这么写IDE会爆出错误:
Property must be initialized or be abstract(属性必须初始化或者为抽象的)
```
var v : View
//Property must be initialized or be abstract
```
关于抽象部分,后面再说,此处先讲解必须初始化,这里引出kotlin语言的一个比较实用的地方————空安全机制。
在Java中,变量都有默认值,基本类型、引用类型都是如此。但在kotlin中,变量是没有默认值的,要求变量在声明的时候必须初始化。
```
class Sample {
var v: View = null
// 这样写 IDE 仍然会报错,Null can not be a value of a non-null type View
}
```
non-null type,非空类型,在kotlin中声明的所有变量,默认皆为非空类型,不能赋予空值。对于不能确认非空与否的变量(例如model中的变量),则添加?表明其可空,所以:
```
class Sample {
var v: View? = null
}
```
上述变量v,你可以在代码中的任意地方将null值赋予它。
当声明可空变量后,使用它又会产生新的问题:
```
var view: View? = null
view.setBackgroundColor(Color.RED)
// 这样写会报错,Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type View?
```
这就是见得非常多的NullPointerException,用常规做法判断一下:
```
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
}
```
IDE不接受这样的做法,因为即使判断非空,也可能在调用之前被重新置为空(多线程操作)。
遇到这样的问题,其实IDE已经提示过了两种解决办法,一种是安全调用(safe call),使用方式为?.:
```
var view: View? = null
view?.setBackgroundColor(Color.RED)
```
这种方法会在调用方法之前再做一次非空判断,并且保证线程安全,所以叫做安全调用(safe call).
另一种方法则有些风险,叫做「non-null asserted call」,非空断言调用,使用方式为!!.:
```
...
view!!.setBackgroundColor(Color.RED)
```
这表示你保证view为非空类型,并且自己承担后果,不需要编译器检查。当编译器运行到此的时候,便会跳过非空检查这一步,直接调用方法。
以上,便是kotlin的空安全机制,归纳起来有几点:
1.变量必须手动初始化,否则会报错。
2.变量默认为非空类型,非空类型在任何地方均不能赋予空值。
3.可空类型在使用时,会因为“可能为空”报错,需要注意。
其实在java中也有类似的安全机制设计,那就是名为@NotNull 和 @Nullable 注解,会将变量转换为可空和非空类型。
2.1.3延迟初始化
回到主界面的代码中:
```
package com.example.mediaplayer
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
open class MainActivity : AppCompatActivity() {
private var mBtn: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mBtn = findViewById(R.id.btn_click)
}
}
```
在了解空安全机制过后,便知道如上代码绝对会报错,mBtn为非空类型,必须初始化。但是我们一般的习惯,是先声明,在铺设布局之后,从布局中获取相应控件,赋值给变量。在不使用可空声明的情况下,我们可以使用延迟初始化的方式,来使之符合我们的编写习惯。这里要用到一个新的关键字————lateinit,望词知义,就是延迟初始化的意思。
```
...
open class MainActivity : AppCompatActivity() {
private lateinit var mBtn: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mBtn = findViewById(R.id.btn_click)
}
}
```
使用lateinit关键字之后,编译器便不会检查变量的初始化和非空声明,只要注意如java一样在使用前初始化变量就行。
2.1.4类型推断
在kotlin中有一个方便之处,那便是在类型声明的时候,若是直接声明初始值,则不用写出变量类型。
```
var name: String = "Mike"
```
```
var name = "Mike"
```
以上的两种方式都可以。
2.1.5 val 与 var
val 是 Kotlin 在 Java 的「变量」类型之外,又增加的一种变量类型:只读变量。它只能赋值一次,不能修改。而 var 是一种可读可写变量。
var 是 variable 的缩写,val 是 value 的缩写。
val 和 Java 中的 final 类似:
```
final int size = 18;
```
```
val size = 18
```
final与val是不完全等同的,但有个共同点,直接进行重新赋值是不行的。
2.1.6 可见性
可见性见3.3。
2.2 函数
在Java中的方法(method),在kotlin中称之为函数(function)。
2.2.1 函数的声明
我们先来看看 Java 里的方法是怎么写的:
```
Food cook(String name) {
...
}
```
而到了 Kotlin,函数的声明是这样:
```
fun cook(name: String): Food {
...
}
```
以 fun 关键字开头,返回值写在了函数和参数后面。
和在java中不一样,java中没有返回值的时候,也必须以void表示,并写出来,而kotlin中返回值没有的话,可以不写,也可以写成Unit。
```
void cook(String name) {
...
}
```
```
fun cook(name: String): Unit {
...
}
或者
fun cook(name: String){
...
}
```
函数的参数也可以是可空类型的,结合上面讲到的空安全机制,使用时要注意:
```
// 可空变量传给不可空参数,报错
var myName : String? = "jidou"
fun cook(name: String) : Food {}
cook(myName)
//可空变量传给可空参数,正常运行
var myName : String? = "jidou"
fun cook(name: String?) : Food {}
cook(myName)
// 不可空变量传给不可空参数,正常运行
var myName : String = "jidou"
fun cook(name: String) : Food {}
cook(myName)
```
**可见性:**
函数的默认可见性是public(以override修饰的方法除外其可见性继承自父类),一般不写出来,除此之外的可见性修饰要写出来。
2.2.2 属性的 getter/setter 函数
在 Java 里面的 field 经常会带有 getter/setter 函数:
```
public class User {
String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
```
这两个函数的作用就是除了保护数据的可见性之外,还可以自定义函数内部来达到对数据做一定处理的效果,比如下面这种:
```
public class User {
String name;
public String getName() {
return this.name + " hello";
}
public void setName(String name) {
this.name = "wow " + name;
}
}
```
在 Kotlin 里,这种 getter / setter 是怎么运作的呢?
```
class User {
var name = "Mike"
}
```
```
class User1 {
fun run() {
var user = User()
user.name = "JACK"
// 上面的写法实际上是这么调用的
// user.setName("JACK")
// 建议自己试试,IDE 的代码补全功能会在你打出 setn 的时候直接提示 name 而不是 setName
println(user.name)
// 的写法实际上是这么调用的
// print(user.getName())
// IDE 的代码补全功能会在你打出 getn 的时候直接提示 name 而不是 getName
}
}
```
那么我们如何来操作前面提到的数据处理呢?看下面这段代码:
```
class User {
var name = "Mike"
get() {
return field + " nb"
}
set(value) {
field = "Cute " + value
}
}
```
格式上和 Java 有一些区别:
**getter / setter 函数有了专门的关键字 get 和 set
getter / setter 函数位于 var 所声明的变量下面
setter 函数参数是 value**
注意此为默认情况,若是属性用private修饰,则可以写成java中的一般用法:
```
class User {
private var name = "Mike"
fun getName():String{
return name;
}
fun setName(bb:String){
name = bb
}
}
```
```
class User1 {
fun run() {
var user = User()
user.setName("JACK")
print(user.getName())
}
}
```
除此之外还多了一个叫 field 的东西。这个东西叫做「Backing Field」,中文翻译是幕后字段或后备字段。具体来说,你的这个代码:
```
class Kotlin {
var name = "jidouauto"
}
```
在编译后的字节码大致等价于这样的 Java 代码:
```
public final class Kotlin {
@NotNull
private String name = "jidouauto";
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String name) {
this.name = name;
}
}
```
上面的那个 String name 就是 Kotlin 帮我们自动创建的一个 Java field。这个 field 对编码的人不可见,但会自动应用于 getter 和 setter,因此它被命名为「Backing Field」(backing 的意思是在背后进行支持)。
所以,虽然 Kotlin 的这个 field 本质上确实是一个 Java 中的 field,但对于 Kotlin 的语法来讲,它和 Java 里面的 field 完全不是一个概念。在 Kotlin 里,它相当于每一个 var 内部的一个变量。
我们前面讲过 val 是只读变量,只读的意思就是说 val 声明的变量不能进行重新赋值,也就是说不能调用 setter 函数,因此,val 声明的变量是不能重写 setter 函数的,但它可以重写 getter 函数:
```
val name = "Mike"
get() {
return field + " hello"
}
```
val 所声明的只读变量,在取值的时候仍然可能被修改,这也是和 Java 里的 final 的不同之处。
关于这两个函数的作用,除了修改取值和赋值,也可以加一些自己的逻辑,就像我们在 Activity 的生命周期函数里做的事情一样。
2.3 Kotlin的类型
2.3.1 基本类型
在 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"
```
PS:和Java不同的是,数字类型、布尔类型、字符类型的首字母需要大写。
在java中,int 和Integer与kotlin中不同,主要表现在装箱方面。
```
int a =1;
Integer b = 1;//自动装箱
```
在kotlin中只有可空变量声明会自动装箱,一切非空变量都是不装箱的。
数组的写法也和java的常规写法不太一样。
2.3.2 类和对象
2.3.2.1 类
现在可以来看看我们的老朋友 MainActivity 了,重新认识下它:
```
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
}
}
```
```
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
}
}
```
对比 Java 的代码来看有哪些不同:
首先是类的可见性,Java 中的 public 在 Kotlin 中可以省略,Kotlin 的类默认是 public 的(包括方法、以及未写明可见性修饰符的变量等)。
类的继承的写法,Java 里用的是 extends,而在 Kotlin 里使用 :,但其实 : 不仅可以表示继承,还可以表示 Java 中的 implement,以及声明变量时候的类型指定。
如果实现点击监听器,如上代码则可以写成:
```
class MainActivity : AppCompatActivity() ,View.OnClickListener{
override fun onCreate(savedInstanceState: Bundle?) {
...
}
}
```
```
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
}
}
```
kotlin中不需要使用implements关键字。
其次便是方法重载时候的不同,Java中需要在方法前加@Override,在kotlin中,override变成了关键字,直接写在fun的前面,表示方法重载。protected修饰符也不见了,在kotlin中,可见性直接继承自父类。
**类的继承:**
kotlin中类以及的默认可见性是public(可以直接省略不写),按照java的习惯,是可以直接继承的。
但是,kotlin中直接继承可见性为public的类,会报错:This type is final, so it cannot be inherited from。
在kotlin中,类除了是public的,还是默认final的,无法被继承的。那要想继承一个类,该怎么办? 答案便是在class之前添加一个open关键字,解除final的限制,让类可以被其他类继承。
```
open class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
}
}
```
2.3.2.2 对象
对象作为类的实例,java和kotlin中获取的方式又有了很大的不同,以我们最开始创建的Example类为例:
```
void test() {
Example examJava = new Example();
}
```
```
fun test() {
var examJava : Example = Example();
}
```
对于类型,我们要注意:
类的可见性和开放性
构造方法
继承
override 函数
2.3.3 类型判断以及强转
Java中的多态,在kotlin中也有体现。如果父类使用子类的方法,需要进行类型判断,使用instanceof关键字判断,然后转换类型,在调用方法。
```
class NewActivity : MainActivity() {
fun action() {}
}
```
那么接下来这么写是无法调用该函数的:
```
fun main() {
var activity: Activity = NewActivity()
// activity 是无法调用 NewActivity 的 action 方法的
}
```
在 Java 里,需要先使用 instanceof 关键字判断类型,再通过强转来调用:
```
void main() {
Activity activity = new NewActivity();
if (activity instanceof NewActivity) {
((NewActivity) activity).action();
}
}
```
Kotlin 里同样有类似解决方案,使用 is 关键字进行「类型判断」,并且因为编译器能够进行类型推断,可以帮助我们省略强转的写法:
```
fun main() {
var activity: Activity = NewActivity()
if (activity is NewActivity) {
// 强转由于类型推断被省略了
activity.action()
}
}
```
那么能不能不进行类型判断,直接进行强转调用呢?可以使用 as 关键字:
```
fun main() {
var activity: Activity = NewActivity()
(activity as NewActivity).action()
}
```
这种写法如果强转类型操作是正确的当然没问题,但如果强转成一个错误的类型,程序就会抛出一个异常。
我们更希望能进行安全的强转,可以更优雅地处理强转出错的情况。
这一点,Kotlin 在设计上自然也考虑到了,我们可以使用 ==as?== 来解决:
```
fun main() {
var activity: Activity = NewActivity()
// '(activity as? NewActivity)' 之后是一个可空类型的对象,所以,需要使用 '?.' 来调用
(activity as? NewActivity)?.action()
}
```