第一章 kotlin基础入门

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()

}

```

©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,924评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,781评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,813评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,264评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,273评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,383评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,800评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,482评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,673评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,497评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,545评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,240评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,802评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,866评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,101评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,673评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,245评论 2 341

推荐阅读更多精彩内容