kotlin 学习第一步

2021-12-08  本文已影响0人  半称心x度

Kotlin 基础知识

一,Using Kotlin for Android Development

  1.1 使用Kotlin做Android开发

Kotlin 是非常适合用于开发Android应用的,它给Android平台带来了新语言的所有优点却不增加任何新的限制:

兼容性:Kotlin完全兼容JDK6,所以Kotlin可以在老旧的Android设备上运行不出问题。AndroidStudio完全支持Kotlin,编译系统也兼容Kotlin。

性能:因为非常相似的字节码结构,Kotlin应用运行速度和Java一样快。因为Kotlin支持内联函数,当时用lambdas时,通常比用Java代码运行更快。

互通性:Kotlin能100%与Java相互协作,能使用所有的Android库,包括注解处理库,所以像databinding,dagger这些库都能使用。

占用空间:Kotlin的运行时库非常简洁,当时用混淆的时候还会缩减到更小,只会增加几百个方法,只增加apk不到100k的体积。

编译时间:Kotlin支持高效的增量编译,所以当项目已经编译过,再进行额外的编译时,这种增量编译可以像Java一样快,甚至更快。

学习曲线:对于Java开发者来说,使用Kotlin非常简单。Java代码自动化转换成Kotllin代码的Kotlin插件帮我们迈出了第一步。 Kotlin Koans 通过一系列的包含Kotlin主要特点的交互性习题给我们提供了指南。

1.2 用Kotlin 做Android开发样例研究

Kotlin已经被一些大企业接受,其中部分企业分享了他们的经验:

Pinterest已经成功地在他们月用户量1.5亿的应用中引进了Kotlin。

Basecamp 的Android app 100%使用Kotlin代码,他们表示Kotlin给他们带来了编程的快乐,以及工作质量和效率的提升。

Keepsafe的app Lock app也已经转换成100%的Kotlin,这使得代码行数减少30%,方法数减少10%。

1.3 Android 开发工具

Kotlin团队一套工具用于开发,其中有超越了标准语言的特性:

Kotlin Android Extensions

是编译器的拓展,它使你免去在代码中调用findViewById()方法,而是通过编译生成。

Anko是一个库,它提供了一组Android APIs和DSL的包装,让你用Kotlin代码替换.xml文件。

1.4 下一步

下载安装 Android Studio 3.0,它提供了Kotlin支持,安装即可使用。

按照 Getting Started with Android and Kotlin指导创建你的第一个Kotlin应用。

想要深入了解,可查看reference documenKotlin Koans

另一个很好的资源是一本书《 Kotlin for Android Developers》,它一步一步地指导你创建一个Kotlin编写的Android应用。

看Google的样例 sample projects written in Kotlin

二,变量的声明和使用

2.1 变量声明

只读变量

可理解为Java中常量,使用 val 关键字修饰

val a: Int = 1  //在声明的时候直接赋值

val b = 2  // 类型是明确的,变量类型Int可省去

val c: Int  // 在声明时没有赋值,变量类型Int不可省去

c = 3      // 只可赋值一次,此后c值不能再变

易变变量

可理解为Java中的普通变量,用 var 关键字修饰,与 val 变量的区别是变量的值可变:

var a=0

a=1

a=3

成员变量

与Java一样,通过对象.变量名的方式调用:

fun main(args: Array<String>) {

    print(Test().property)

}

class Test{

    var property="property"

}

静态变量

用companion object{}包裹,与Java一样通过类名.变量名的方式调用,关于companion object后面会详细讲解:

fun main(args: Array<String>) {

    print(Test.com)

}

class Test{

    companion object{

        var com="com"

    }

}

顶级变量

在类的外部声明,可理解为Java中的静态成员变量。

通过包名.变量名的方式来调用

样例:

package a.b.c

var top = "top"

fun main(args: Array<String>) {

    print(a.b.c.top)

}

静态变量与顶级变量的区别:

通过反编译可以知道,其实他们不在一个类中。

当文件中有顶级变量,编译时会新生成一个[文件名+kt]的类,顶级变量就在其中。

2.2 Getters and Setters

我们先定义各种可见性的var类型成员变量:

    private var privateField = ""

    internal var internalField = ""

    protected var protectedField = ""

    var publicField = ""

然后反编译看看对应的java代码:

  @NotNull

  public final String getInternalField$app() {

      return this.internalField;

  }

  public final void setInternalField$app(@NotNull String var1) {

      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");

      this.internalField = var1;

  }

  @NotNull

  protected final String getProtectedField() {

      return this.protectedField;

  }

  protected final void setProtectedField(@NotNull String var1) {

      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");

      this.protectedField = var1;

  }

  @NotNull

  public final String getPublicField() {

      return this.publicField;

  }

  public final void setPublicField(@NotNull String var1) {

      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");

      this.publicField = var1;

  }

可知,protected和public会生成对应的setter和getter方法,internal也生成了特殊的对应方法。

所以对于protected和public的成员变量,我们不能自己创建对应的getter和setter方法,如:

var name:String?

fun setName(name:String){}

将会报错:

Platform declaration clash: The following declarations have the same JVM signature (setName(Ljava/lang/String;)V)

Visibility Modifiers

对于getter

getter的可见性和变量的可见性相同,无需重复添加修饰符。非要多此一举添加,如果和变量的修饰符不一样,将会报错,如:

var field: String = ""

private get  //报错: Getter visibility must be the same as property visibility

对于setter

setter的可见性必须小于等于变量自身的可见性,否则报错,如:

private var name=""

public set //error:Setter visibility must be the same or less permissive than property visibility

我们也可以自定义getter和setter方法

语法

<var <propertyName>[: <PropertyType>] [= <property_initializer>]

    [<getter>]

    [<setter>]

当给变量赋值时会调用setter方法,调用变量得到的是getter中的返回值

样例:

var name: String = "111"

    get() {

        return "{$field}"

    }

    set(value) {

        field = "[$value]"

    }

fun main(args: Array<String>) {

    println(name)

    name = "222"

    print(name)

}

结果:

{111}

{[222]}

三,方法的定义和使用

3.1 方法定义

定义语法:

fun  [方法名] ( [参数名] : [参数类型] ) : [返回类型]{

    ...

    return [返回值]

}

有返回值

fun multiply(x: Int, y:Int): Int {

    return x * y

}

也可以转换为:

fun multiply(x: Int, y: Int): Int = x * y

还可以这样:

var multiply = { x: Int, y: Int -> x * y }

无返回值

使用Unit代替返回类型:

fun log(msg: String): Unit {

    print(msg)

}

Unit也可以省去:

fun log(msg: String) {

    print(msg)

}

成员方法

与Java一样通过对象.方法名的方式调用:

fun main(args: Array<String>) {

    Test().method()

}

class Test{

    fun method(){

        print("hello")

    }

}

静态方法

和Java一样通过类名.方法名的方式调用:

fun main(args: Array<String>) {

    Test.com()

}

class Test{

    companion object{

        fun com(){

            print("com")

        }

    }

}

顶级方法

在类外部定义的方法,可理解为静态方法,通过包名.方法名的方式调用:

package a.b.c

fun top(){

    print("top")

}

fun main(args: Array<String>) {

    a.b.c.top()

}

顶级方法与静态方法区别

通过反编译可以知道,其实他们不在一个类中。

如果有顶级变量会新生成一个[文件名+kt]的类,顶级方法就在其中。

3.2 方法调用顺序可变

通过指明参数名称,可按任意顺序传参:

fun main(args: Array<String>) {

    log(name = "mao", age = 18)

}

fun log(age: Int, name: String) {

    print("age:$age,name:$name")

}

3.3 命名参数

方法样例:

fun logInfo(

        name: String,

        age: Int,

        married: Boolean = true,

        language: String = "Chinese"

) {

    println("[name:$name,age:$age,married:$married,language:$language");

}

必须传入未设置默认值的参数

在上述logInfo方法中,name和age未设置默认值,必须传参,其它参数可自由选择:

    logInfo("ma",18)

    logInfo("ma",18,false);

    logInfo("ma",18,false,"English");

3.4 可变参数

使用vararg 修饰参数:

fun logInfos(vararg infos: String) {

    for (info in infos) {

        print("$info  ")

    }

}

方法调用

logInfos("aaa","bbb","ccc")

四,null安全(“?”,“?:”,“!!”)

在Java开发中,null一直是个大问题,哪怕我们再小心,也难免有疏忽的时候,Kotlin针对这个问题做了一些措施。

Kotlin将变量分为可以为Nullable类型 Non-Null类型,变量在声明时就要确定属于哪个阵营。

变量默认Non-Null类型,如果想声明Nullable的变量,需要用“?”修饰:

声明Non-Null变量

    var a: String = "hello" 

声明Nullable变量

    var b: String? = "world"

声明变量时若直接赋值,变量类型由所赋值的类型决定

如在声明b时,将a赋值给b,b的类型(Nullable或Non-Null)与a相同:

    var a: String? = "hello"  //Nullable

    var b = a  //与a相同,也是Nullable

4.1 Non-Null变量赋值

Non-Null变量不能赋值为null

var a: String=null //报错:Null can not be a value of a non-null type String

Nullable变量无法直接赋值给Non-Null变量

    var a: String? = "hello"

    var b = "world"

    b = a //报错:Type mismatch: inferred type is String? but String was expected

想要将Nullable变量赋值给Non-Null变量有以下方法:

先处理后赋值

    var a: String? = "hello"

    var b = "world"

    if (a != null) {

        b == a

    }

使用“!!”

    var a: String? = "hello"

    var b = "world"

    b = a!!

使用“!!”方法要注意,当a为null时会抛出KotlinNullPointerException异常。

4.2 Nullable变量的使用

“?”符号的使用

Nullable变量进行操作时要带“?”,当变量为null时,不会出现异常,而是返回结果null:

    var name: String? = null

    var len = name?.length

    print(len == null)  //输出:true

“?:”符号的使用

这个符号的作用是当它左边的结果为null时,进行右边的操作。

左边结果不为null:

    var a: String? = "hello"

    var b = a?.length ?: 100  //很明显左边不为null

    println(b)  //输出: 5

左边结果为null:

    var a: String? = null

    var b = a?.length ?: 100  //左边为null,返回右边的100

    println(b)  //输出: 100

五,类与继承

5.1 类的创建

与Java一样,Kotlin也是用class关键字声明类。

class User{}

Kotlin中一个类可以有一个主构造方法(primary constructor)和一个或多个次构造方法( secondary constructors)。

5.2 主构造方法

主构造方法通过在类名后面添加constructor和参数实现:

class User private constructor(name: String) {}

如果没有注解和可见的修饰符,constructor关键字可以省略:

class User(name: String) {}

初始化顺序

类内部的init模块和变量的初始化顺序按照他们出现的顺序进行

fun main(args: Array<String>) {

    User("mao")

}

class User(name: String) {

    val firstProperty = "First property".also(::println)

    init {

        println("First initializer")

    }

    val secondProperty = "Second property".also(::println)

    init {

        println("Second initializer")

    }

}

输出:

First property

First initializer

Second property

Second initializer

成员变量和init模块在初始化时可直接使用主构造方法中的参数

class User(name: String) {

    var mName = name

    init {

        var mName = name

    }

}

5.3 次构造方法

次构造方法也使用constructor实现

class User {

    var name: String = ""

    constructor(name: String) {

        this.name = name

    }

}

当类声明了主构造方法,所有次构造方法必须直接或间接调用主构造方法

class User() {

    constructor(name: String) : this() {

        print("conconstructor")

    }

    constructor(name: String, age: Int) : this(name) {}

}

类中的变量初始化和init模块初始化都是主构造方法的一部分,所以都在次构造方法之前执行

fun main(args: Array<String>) {

    User("mao")

}

class User() {

    constructor(name: String) : this() {

        print("conconstructor")

    }

    var name = "property".also(::println)

    init{

        println("init")

    }

}

输出:

property

init

conconstructor

当一个类没有任何构造方法时,默认生成一个public类型的无参主构造方法,如果不希望这个默认构造方法存在,可以主动声明一个主构造方法

class User private constructor() {}

5.4 继承

Kotlin中的类默认是final类型的,想要被继承,得用“open”关键字修饰。

open class Shape {}

class Rectangle : Shape {}

子类的所有构造构造方法必须直接或间接调用一个父类的构造方法

open class Shape {

    constructor(name: String) {

        print(name)

    }

}

class Rectangle : Shape {

    constructor(name: String) : super(name) {}

    constructor(name: String, age: Int) : this(name) {}

}

方法重写

继承过程中,只有open修饰的方法才能被重写,重写时要用override修饰。

open特性也能被继承,想要断了open特性,只需用final修饰即可。

open class Shape {

    open fun method() {}

}

open class Rectangle : Shape() {

    override fun method() {}

}

class Square : Rectangle() {

    final override fun method() {}

}

成员变量重写

与方法重写相同,只有open修饰的变量才能被重写,open同样可以继承,也可以用final中断。

重写过程中,变量可由val类型变为var类型,反之则不行。

open class Shape {

    open val name: String = "Shape"

}

open class Rectangle : Shape() {

    override var name: String = "Rectangle"

}

class Square : Rectangle() {

    final override var name = "Square"

}

调用父类方法和成员变量

可通过“super”关键字调用父类的方法和成员变量

open class Shape {

    open val name: String = "Shape"

    open fun draw() {}

}

open class Rectangle : Shape() {

    override var name: String = super.name

    override fun draw() {

        super.draw()

    }

}

内部类调用外部类父类的方法

使用“super@Outer”方式:

open class Sup {

    open fun method() { println("Sup.method") }

}

class Sub:Sup(){

    inner class Inner{

        fun test(){

            super@Sub.method()

        }

    }

}

当继承的类和接口当中出现相同的方法(方法名和参数都相同),通过类似泛型的方法明确调用哪个方法

interface Action {

    fun eat() {

        println("Action")

    }

}

open class Animal {

    open fun eat() {

        println("Animal")

    }

}

class Human() : Animal(), Action {

    override fun eat() {

        super<Action>.eat()

        super<Animal>.eat()

    }

}

六,内联方法

使用高阶方法会造成一些强制性的开销:内存分配和调用都是开销。不过,在许多情况下有些开销是可以避免的。

例子:

现在把吃饭分为三个步骤:1.拿起筷子 2.吃 一口3.放下筷子

1.情况一:每吃一口都要放下筷子,即拿、吃、放、拿、吃、放...

操作顺序:

1、2、3;  1、2、3; 1、2、3...

这种情况不是饭菜不合胃口、就是吃撑了。

2.情况二:每吃若干口才放下筷子。

操作顺序:

1、2、2...2、3; 1、2、2...2、3;... 1、2、2...2、3;

明显这才是正确操作,能省不少力气。

在JVM中也有类似的情况,JVM中每个线程都有一个虚拟机栈,每个方法的从调用到完成,对应着入栈、出栈的过程,如果将一方法分为多个方法时,就会有更多的入栈、出栈的开销,使用内联方法能有效减少这部分开销。

例一

现在我们要打印一个人,分三部分打印:头、身体和脚,其中身体构造复杂,又要分三部分

1-1

fun main(args: Array<String>) {

    //printHead

    println("head")

    //printbody

    println("body1")

    println("body2")

    println("body3")

    //printFoot

    println("foot")

}

现在我们想把打印身体这部放到一个单独的方法中,使代码更清晰:

1-2

fun main(args: Array<String>) {

    //printHead

    println("head")

    //printbody

    printBody()

    //printFoot

    println("foot")

}

fun printBody() {

    println("body1")

    println("body2")

    println("body3")

}

不过这样一来多了部分开销,这时内联方法可以帮我们避免这部分开销。

6.1 inline

定义内联方法需使用“inline”关键字修饰

1-3

fun main(args: Array<String>) {

    //printHead

    println("head")

    //printbody

    printBody()

    //printFoot

    println("foot")

}

inline fun printBody() {

    println("body1")

    println("body2")

    println("body3")

}

编译过程会帮我们把1-3转换为1-1的样子,提高了性能的同事又能保证结构清晰。

例二

如何才能将代码

lock(l) { foo() }

实现这样的效果:

l.lock()

try {

    foo()

}

finally {

    l.unlock()

}

可以通过内联方法实现:

inline fun <T> check(lock: Lock, body: () -> T): T {

    lock.lock()

    try {

        return body()

    } finally {

        lock.unlock()

    }

}

调用:

    var lock = ReentrantLock()

    check(lock) {print("hello")}

完整样例:

开启两个线程,通过加同一把锁实现同步:

fun main(args: Array<String>) {

    var lock = ReentrantLock()

    Thread() {

        check(lock) {

            for (i in 1..5) {

                TimeUnit.SECONDS.sleep(1)

                println("111")

            }

        }

    }.start()

    Thread() {

        check(lock) {

            for (i in 1..5) {

                TimeUnit.SECONDS.sleep(1)

                println("222")

            }

        }

    }.start()

}

inline fun <T> check(lock: Lock, body: () -> T): T {

    lock.lock()

    try {

        return body()

    } finally {

        lock.unlock()

    }

}

输出:

111

111

111

111

111

222

222

222

222

222

6.2 noinline

inline 方法中的方法参数默认是inline类型的,如果想过滤掉inline特性,可用noinline修饰。

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {

    // ...

}

6.3 lambda与return

1.普通方法的lambda模块中禁止使用return

fun foo() {

    ordinaryFunction {

        return // ERROR: can not make `foo` return here

    }

}

2.inline方法的lambda模块可用使用return

fun foo() {

    inlineFunction {

        return // OK: the lambda is inlined

    }

}

fun hasZeros(ints: List<Int>): Boolean {

    ints.forEach {

        if (it == 0) return true // returns from hasZeros

    }

    return false

}

这么设计很好理解,普通方法的lambda模块其实是另一个方法的方法体,你在本方法中调用另一方法中的return算是怎么回事,稍不注意容易理解错误。

而inline方法可认为是本方法的一部分,外面那层大括号包装可当它不存在,使用return就显得和自然了。

3.那么想要在普通方法lambda return怎么办?只需在return后加“@方法名”即可:

fun f() {

    test {

        return@test

    }

}

fun test(action: () -> Unit) {}

4.inline 类型的方法参数不能直接用于赋值,要想用于赋值得用crossinline 修饰

inline fun f(crossinline body: () -> Unit) {

    val f = object: Runnable {

        override fun run() = body()

    }

}

6.4 reified

泛型只在编译时起作用,在运行时已经被擦除,所以泛型标记是没法当做对象使用的。

不过在Kotlin中,reified可修饰inline方法中的泛型,对其进行反射操作。

inline fun <reified T> membersOf() = T::class.members

fun main(s: Array<String>) {

    println(membersOf<User>())

}

class User() {

    var name = "mao"

}

七,enum,data,sealed,object

7.1 enum类

7.1.1与java不同,Kotlin中enum当做class的修饰符使用

1.常见用法:

enum class Direction {

    NORTH, SOUTH, WEST, EAST

}

2.有成员属性时:

enum class Color(val rgb: Int) {

    RED(0xFF0000),

    GREEN(0x00FF00),

    BLUE(0x0000FF)

}

打印成员的值

print(Color.RED.rgb)

3.匿名方法

假如有一个机器,他有三种状态start,run,stop,并且不断循环我们可以这样实现:

enum class State {

    Start {

        override fun nextState() = Run

    },

    Run {

        override fun nextState() = Stop

    },

    Stop {

        override fun nextState() = Start

    };

    abstract fun nextState(): State

}

简单调用:

fun main(args: Array<String>) {

    var state = State.Stop

    println(state)

    for (i in 1..3) {

        state = state.nextState()

        println(state)

    }

}

输出:

Stop

Start

Run

Stop

7.2 data类

在Java中,我们在信息类创建信息类时总是伴随大量getter/setter方法,虽然可以用工具自动生成,但也影响美观。Kotlin中使用data类型帮助我们解决了这个问题。

data class User(

        var name: String,

        var age: Int

)

在编译时,根据主构造器中的参数会自动生成getter/setter,hashcode(),toString(),equals(),copy()等方法。

我们无法直接调用getter/setter 方法,但我们对它的操作上本质上都是通过调用getter/setter方法实现的。

data class User(

        var name: String,

        var age: Int

)

设置和修改变量的值

    var user = User("mao", 18)

    user.name = "zhang"

    user.age = 3

copy()

当我们想将对象复制一份时,可用copy()方法:

    var user = User("mao", 18)

    var user2 = user.copy()

如果想改变某个变量:

    var user=User("mao",18)

    var user2=user.copy(age=100)

7.3 sealed类

sealed类可看做时enum类的一种拓展,相比于enum的常量以单一实例存在,sealed类的子类可以有多种确定的类型。

sealed类自身是抽象类,它的子类不能是抽象类,子类和它必须在同一个文件中。

sealed class Expr

data class Const(val number: Double) : Expr()

data class Sum(val e1: Expr, val e2: Expr) : Expr()

object NotANumber : Expr()

与when搭配使用非常方便

fun eval(expr: Expr): Double = when(expr) {

    is Const -> expr.number

    is Sum -> eval(expr.e1) + eval(expr.e2)

    NotANumber -> Double.NaN

}

栗子:现在将人根据颜值和智商分为4类:

1.有颜值有智商

2.有颜值没智商

3.没智商有颜值

4.没智商没颜值

sealed class Feature

data class IQ(var score: Int) : Feature()

data class FaceScore(var Sore: Int) : Feature()

data class Sum(var f1: Feature, var f2: Feature) : Feature()

object None : Feature()

然后设计评分机制:颜值(faceScore)和智商(IQ)基础分0-10分

sum(总分)= faceScore * 8 + IQ * 2

计算方法实现:

fun cal(f: Feature): Int = when (f) {

    is IQ -> {

        f.score * 2

    }

    is FaceScore -> f.Sore * 8

    is Sum -> cal(f.f1) + cal(f.f2)

    None -> 0

}

计算:

fun main(args: Array<String>) {

    var f1 = IQ(10)

    var f2 = FaceScore(10)

    var f3 = Sum(f1, f2)

    var f4=None

    cal(f1).also(::println)

    cal(f2).also(::println)

    cal(f3).also(::println)

    cal(f4).also(::println)

}

7.4 object

object到底有什么作用呢?先创建一个最简单的object类

用来实现单例模式

object O

反编译获得Java代码:

public final class O {

    public static final O INSTANCE;

    static {

        O localo = new O();

        INSTANCE = localo;

    }

}

这是一种单例模式的实现方法,如此看来object 可以用来实现单例模式。

简单用法:

fun main(args: Array<String>) {

    O.test()

    O.name = "hello"

}

object O {

    var name = "mao"

    fun test() {

        print("test")

    }

}

object还可以用来实现匿名内部类

fun main(args: Array<String>) {

    var btn = Btn()

    btn.onClickLsn = object : Btn.OnClickLsn {

        override fun click() {

            print("click")

        }

    }

    btn.callClick()

}

class Btn() {

    var onClickLsn: OnClickLsn? = null

    fun callClick() {

        onClickLsn?.click()

    }

    interface OnClickLsn {

        fun click()

    }

}

object类可以继承一个类和多个接口。

当父类有构造方法时,应传入对应的参数。

interface A

open class B(age: Int) {

    var mAge = age

}

var c: A = object : B(18), A {}

object可以做private方法和成员变量的返回值,不能做public方法和成员变量的返回值。

作为private方法和变量的返回值时,返回类型是匿名对象类型,可以访问内部成员。

而最为public方法和变量返回值时,返回类型为Any,不能访问内部成员。

class C {

    // private方法,返回类型是匿名对象类型

    private fun foo() = object {

        val x: String = "x"

    }

    // public方法,返回类型是 Any

    fun publicFoo() = object {

        val x: String = "x"

    }

    fun bar() {

        val x1 = foo().x        // 没问题

        val x2 = publicFoo().x  //报错,无法引用x

    }

}

object和companion

在类内部,object和companion可实现静态成员和静态方法的效果。

class Outer {

    companion object Inner {

        var property = "property"

        fun method() {

            print("method")

        }

    }

}

调用:

Outer.Inner.property.also(::println)

Outer.Inner.method()

通过反编译可知此处的 Inner并不是类,而是一个静态常量实例。

正常使用过程中Inner可以省去:

Outer.property.also(::println)

Outer.method()

这两种方法本质上是一样的。

类中的companion object修饰的Inner也可去掉:

class Outer {

    companion object {

        var property = "property"

        fun method() {

            print("method")

        }

    }

}

当没有Inner的情况下,Inner默认为Companion

上一篇下一篇

猜你喜欢

热点阅读