kotlin 7章、类型进阶

177 阅读7分钟

7章、类型进阶

7.1、类的构造器

主构造器

/**
 * 1、constructor可以省略
 * 2、var修饰的是属性,类内部可见
 * 3、name属性,由于没有var或者val修饰,所以在构造器内可见 + init块内可见
 * 4、init块里面可以访问构造器的参数
 */
class Person constructor(var age: Int = 0, name: String = "") {

    // init块类似于主构造器的方法体
    init {
        Log.e("cdx",name)
    }
}

init块

class Person constructor(var age: Int = 0, name: String = "") {

    private var firstName: String;

    // init块类似于主构造器的方法体
    init {
        Log.e("cdx", name)
        firstName = name
    }

    init {
        age += 1
    }
}

1、init块可以有多个,最终会被合并执行。

2、构造器会将firstName,多个init一块合并,完成初始化。

主构造器 + 副构造器

// 括号内部的是主构造器
class Person(var age: Int, var name: String) {
    
    // 1、类内部的构造器是副构造器
    // 2、副构造器必须要调用主构造器
    constructor(age: Int) : this(age, "") {
        
    }
    
}

不定义主构造器,只有副构造器(不推荐)

class Person {
    // 可以省略super()
    constructor(age: Int) : super() {
    }
}

主构造器的默认参数

第一步:Person的构造器中age属性有默认参数。

class MainActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

    }

}

class Person constructor(val name: String, val age: Int = 0) {

}

第二步:我们定义Java类,然后去调用Person类,然后构造Person。

出现了编译错误,为了能让Java也能有调用它的重载构造方法,我们可以修改下kotlin的代码,增加@JvmOverloads

class Person @JvmOverloads constructor(val name: String, val age: Int = 0) {

}

构造同名的工厂函数

package com.example.kotlindemo

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class MainActivity2 : AppCompatActivity() {

    val persons = HashMap<String, Person>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

    }

    /**
     * 构造同名的工厂函数
     * 和Person类名一样
     */
    fun Person(name: String): Person {
        return persons[name] ?: Person("", 0).also { persons[name] = it }
    }
}


class Person @JvmOverloads constructor(val name: String, val age: Int = 0) {

}

在Android中的例子是:

        // 构造函数
        var str = String()
        // 同名的工厂函数
        var str2 = String(charArrayOf('1', '2'))

7.2、类与成员的可见性

可见性对比

记忆:模子

修饰范围

只有protected只能修饰成员,其它的什么都能修饰。

模块概念

模块可以认为是一个jar包、一个aar

internal的作用

一般由SDK 或者 公共组件 开发者用于隐藏模块内部的实现细节。

跨模块访问kotlin中的internal修饰的类

1、首先在mylibrary中定义Test类

package com.example.mylibrary

internal class Test {

    fun test() {
        
    }

}

2、然后在app module中的kotlin类中访问Test类

3、然后在app module中的java类中访问Test类

Java类中是可以访问kotlin中的Internal修饰的类的。

package com.example.kotlindemo;

import com.example.mylibrary.Test;

public class JavaTest {

    void test() {
        Test test = new Test();
        test.test();
    }
}

如何才能在java中也访问不了kotlin中internal修饰的类,做到kotlin对java的隔离呢?

我们给方法增加一个别名 @JvmName("%abcd")

package com.example.mylibrary

internal class Test {
    
    @JvmName("%abcd")
    fun test() {
    }

}

构造器的可见性

在构造器上增加了private的修饰,这样外界就没有办法通过构造来生成对象。

class Person private constructor(val name: String, val age: Int = 0) {}

属性的可见性

// 私有化属性name,外界没有办法访问到
class Person constructor(private val name: String, val age: Int = 0) {

}

属性可见性的两个结论

1、getter的可见性必须与属性的可见性保持一致。

2、setter的可见性不得大于属性的可见性。

顶级声明

1、顶级声明指文件内直接定义的属性、函数、类等。

2、顶级声明不支持protected

3、顶级声明被private修饰表示文件内部可见。

7.3、延迟初始化

第一种方式:初始化为null

class MainActivity2 : AppCompatActivity() {

    private var tv: TextView? = null;

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)
        tv = findViewById(R.id.tv)
        tv?.text = ""
    }

}

不推荐,因为后续调用的时候,都会通过?.来调用

第二种方式:使用lateinit

class MainActivity2 : AppCompatActivity() {

    private lateinit var tv: TextView;

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)
        tv = findViewById(R.id.tv)
        tv?.text = ""

        // 是否被初始化了
        if (::tv.isInitialized) {
            
        }
    }

}

不推荐,因为TextView要设置为var,但是它一旦初始化之后就不会变化了。

lateinit注意事项:

1、lateinit会让编译器忽略变量的初始化,不 支持Int 等基本类型。

2、开发者必须能够在完全确定变量值的生命周期下使用lateinit。

3、不要在复杂的逻辑中使用lateinit,它只会让你的代码更加脆弱。

4、Kotlin 1.2加入的判断lateinit属性是否初始化(isInitialized)的API最好不要用。 

第三种方式:lazy方式

class MainActivity2 : AppCompatActivity() {


    // 只有在tv首次被访问的时候执行
    private val tv: TextView by lazy {
        findViewById(R.id.tv)
    };

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

    }

}

7.4、代理

代理是什么

接口代理:对象代替当前类A实现接口B的方法 我代替你处理它(我替小孩写作业)。

属性代理:对象代替属性a实现getter/setter方法。

比如lazy:lazy代替属性tv实现getter方法,获得tv的初始化。

接口代理的例子

class MainActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

    }

}

interface Api {
    fun a()
    fun b()
    fun c()
}

class ApiImpl : Api {
    override fun a() {

    }

    override fun b() {

    }

    override fun c() {

    }

}

/**
 * 包装Api,做一些功能的增强,比如在c里面增加一些日志,但是我们还是得去实现a,b两个方法
 */
class ApiWrapper(val api: Api) : Api {
    override fun a() {
        api.a()
    }

    override fun b() {
        api.b()
    }

    override fun c() {
        Log.e("cdx", "ccccccc")
        api.c()
    }

}

// 对象api来代替ApiWrapper实现接口Api
// 这个地方相当于Api by api是一个类,然后我们重写这个类的方法
class ApiWrapper2(val api: Api) : Api by api {

    override fun c() {
        Log.e("cdx", "ccccccc")
        api.c()
    }
}

接口代理在代码中的使用场景?

属性代理-lazy

    // 只有在tv首次被访问的时候执行
    private val tv: TextView by lazy {
        findViewById(R.id.tv)
    };

lazy代理了tv的getter,然后将findViewById的值赋值给了tv

属性代理-observable

class MainActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        val stateManager = StateManager()
        stateManager.state = 3
        stateManager.state = 4
    }

}

class StateManager {
    // observable代理了state的setter方法,当设置完以后,就可以获取到旧方法,新方法了
    var state: Int by Delegates.observable(0) { property, oldValue, newValue ->
        Log.e("cdx", "$oldValue-$newValue")
    }
}

observable代理了state的setter,当state设置完setter以后,会获取到老数据和新数据。

自定义代理

X()对象代理了x属性的getter和setter属性

class MainActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)
        val foo = Foo()
        Log.e("cdx", foo.x.toString())
        foo.x = 3
        Log.e("cdx", foo.x.toString())
    }

}

class Foo {
    // X()对象代理了属性x的getter和setter
    var x: Int by X()
}

class X {
    operator fun getValue(foo: Any?, property: kotlin.reflect.KProperty<*>): kotlin.Int {
        return 2
    }

    operator fun setValue(foo: Any?, property: KProperty<*>, i: Int) {
        Log.e("cdx","i:$i")
    }
}

7.6、单例

如何实现

kotlin中如何访问

class MainActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        Log.e("cdx", "" + Singleton.x)
        Log.e("cdx", "" + Singleton.y())
    }

}

// 饿汉式单例
object Singleton {
    var x: Int = 2
    fun y() {

    }
}

java中如何访问

java中访问需要有INSTANCE关键字

public class JavaTest {

    void test() {
        int x = Singleton.INSTANCE.getX();
        Singleton.INSTANCE.y();
    }
}

如何让kotlin的成员有静态的概念

1、这个特性也是用于Java在调用的时候,更像是静态方法的调用,kotlin的调用没有什么影响

2、增加@JvmStatic

首先定义kotlin的单例,然后在kotlin文件中调用它

class MainActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        Log.e("cdx", "" + Singleton.x)
        Log.e("cdx", "" + Singleton.y())
    }

}

// 饿汉式单例
object Singleton {
    @JvmStatic var x: Int = 2
    @JvmStatic fun y() {

    }
}

然后是在java的文件中调用它,我们可以看到它少了INSTANCE关键字

public class JavaTest {

    void test() {
        int x = Singleton.getX();
        Singleton.y();
    }
}

不生成getter/setter @JvmField

class MainActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        Log.e("cdx", "" + Singleton.x)
        Log.e("cdx", "" + Singleton.y())
    }

}

// 饿汉式单例
object Singleton {
    @JvmField var x: Int = 2
    @JvmStatic fun y() {

    }
}

在java中调用如下:

public class JavaTest {

    void test() {
        // 由于在定义kotlin单例中类的成员的时候,使用了@JvmField,
        // 所以在Java中调用的时候,不再使用getX和setX,而是直接使用了x
        int x = Singleton.x;
        Singleton.y();
    }
}

普通类的静态成员

普通类在用@JvmStatic修饰是不可以的,但是我们可以借助于伴生对象

这两种情况是等价的,伴生对象 + @JvmStatic相当于是一个静态方法

首先定义普通类,内部有非静态Field和静态Field

class Foo {

    // 生成非静态Field
    @JvmField var x: Int = 2

    // 伴生对象,相当于另外的一半
    companion object {
        @JvmStatic
        fun y() {
        }

        // 生成静态的Field
        @JvmField var y:Int = 2
    }
}

然后在java文件中去调用它

public class JavaTest {

    void test() {
        // 对象调用的方式
        Foo foo = new Foo();
        int x = foo.x;
        // 静态调用的方式
        Foo.y();
        int y = Foo.y;
    }
}

object的构造器

1、object不能有构造器。

2、object可以有若干个init块。

object类的继承

// 单例类的继承和普通类是一样的
object Singleton:Runnable {
    override fun run() {

    }

}

7.7、内部类

内部类定义

1、和Java的定义相反,在kotlin中,加上inner表示非静态,什么都不加,表示静态。

内部类实例化

class MainActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        // 非静态内部类的调用
        val inner = Outer().Inner()
        // 静态内部类的调用
        val staticInner = Outer.StaticInner()

    }

}

class Outer {
    // 非静态内部类
    inner class Inner
    // 静态内部类
    class StaticInner
}

内部object

// 由于object内部成员默认是static的,
// 所以不能再内部的object前面增加Inner
object OuterObject {
    object StaticInnerObject
}

匿名内部类

java和kotlin匿名内部类的表示

匿名内部类实现多个接口

java并不支持,但是kotlin确支持

7.8、数据类

数据类component属性

因为是数据类,所以才有这个特性。

class MainActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        var data = TestData("张三", 11)
        val component1 = data.component1()
        val component2 = data.component2();
        Log.e("cdx","$component1-$component2")
    }

}

data class TestData(val name: String, val age: Int)

data类的特性

1、有component方法。

2、编译器基于component自动生成了equals/hashCode/toString/copy。

数据类解构

class MainActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        var data = TestData("张三", 11)

        // 解构赋值
        var (name, age) = data

        Log.e("cdx", "$name-$age")


    }

}

java和kotlin的区别

7.9、枚举类

Java和kotlin枚举的区别

枚举的属性

class MainActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        Log.e("cdx",State.Idle.name)
        Log.e("cdx",State.Busy.name)

    }

}

enum class State {
    Idle, Busy
}

结果如下:

枚举的构造器

枚举的接口

枚举的接口-各自实现

枚举进行扩展(和Java不同的地方)

class MainActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        Log.e("cdx", State.Idle.next().name)

    }

    // 获取下一个美剧值
    fun State.next(): State {
        return State.values().let {
            val nextOrdinal = (ordinal + 1) % it.size
            it[nextOrdinal]
        }
    }

}

枚举用来比大小

class MainActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        var state = State.Idle
        if (state < State.Idle){
            Log.e("cdx","11111")
        } else if (state < State.Busy){
            Log.e("cdx","22222")
        }

    }

}

enum class State {
    Idle, Busy
}

枚举的区间

class MainActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        var colors = Color.Yello..Color.Green
        var color = Color.Black
        var result = color in colors

    }

}

enum class Color {
    Yello, Black, Red, Green, White, Gray
}

7.10、密封类

定义

1、密封类是一种特殊的抽象类。

2、密封类的子类定义在与自身相同的文件中。

3、密封类的子类的个数是有限的。

class MainActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        val state: PlayerState = Playing()
        val result = when (state) {
            Idle -> {
                1
            }
            is Playing -> {
                2
            }
            is Error -> {
                3
            }
            else -> {
                4
            }
        }

    }

}

//1、首先是一个抽象类
//2、其次是一个密封类
sealed class PlayerState {
    constructor()
    constructor(int: Int)
}

//单例
object Idle : PlayerState() {}

class Playing(val song: String = "") : PlayerState() {}

class ErrorInfo(val error: Int = 0) : PlayerState() {}

密封类VS枚举类

7.11、内联类

定义

1、内联类是对某一个类型的包装。 

2、内联类是类似于Java装箱类型的一种类型。 

3、编译器会尽可能使用被包装的类型进行优化。

/**
 * 1、对Int类型的包装
 * 2、必须是val。
 */
inline class BoxInt(val value:Int){
    operator fun inc():BoxInt{
        return BoxInt(value + 1)
    }
}

 

内联类的属性

内联类是对其它类型的包装,所以不应该有属性。

backing fields

内联类的继承结构

1、可以实现接口,但是不能继承父类也不能被父类继承。

内联类的编译优化

内联类的方法都会被编译成静态方法

内联类的使用场景

内联类可以用作枚举

内联类的限制

1、主构造器必须有且仅有一个只读属性。

2、不能定义有backing-field的其他属性。

3、被包装类型必须不能是泛型类型。 

4、不能继承父类也不能被继承。 

5、内联类不能定义为其他类的内部类。 

7.12、kotlin中的JSON序列化

Gson:JSON解析库

kotlinx.serialization:JSON解析库

moshi:JSON解析库