第7章、kotlin 类型进阶

218 阅读8分钟

7.1、类的构造器

1、构造器的基本写法

/**
 * 1、constructor 可以省略。
 * 2、加上了val或者var,定义了构造器参数,并且定义了属性。
 * 3、带val或者var的,那么类内可见,如果不带,那么init块内可见。
 * 4、如果带了public,那么类外可见。
 */
class Person constructor(val name: String, age: Int) {

}

2、init块

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

    private val TAG: String = "Person"
    var age:Int

    /**
     * 1、init块类似于主构造器的方法体。
     * 2、init块可以有多个。
     * 3、init块中可以访问构造器中的参数。
     */
    init {
        this.age = age
    }

    init {
        Log.e(TAG,"1")
    }

}

思考:init块会合并吗?

init块会合并,最终会合并到构造器的方法体。

3、属性必须初始化

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

    private val TAG: String = "Person"
    var age:Int //属性必须初始化,要不会编译错误

    init {
        //this.age = age
    }

}

4、继承父类

abstract class Animal

class Person constructor(val name: String, age: Int):Animal(){
    
}

5、副构造器

package com.example.kotlindemo

abstract class Animal

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

    /**
     * 1、定义在类内部的构造器称为副构造器。
     * 2、副构造器必须要调用主构造器
     * 3、如果不调用主构造器,会出现编译错误。
     */
    constructor(age: Int): this("", age){

    }

}

6、不定义主构造器(不推荐)

package com.example.kotlindemo

abstract class Animal

/**
 * 注意:此时没有()
 */
class Person : Animal {

    /**
     * 1、通过super()调用父类构造器。
     * 2、如果父类构造器为无参,比如super(),可以省略。 
     * 3、如果有多个构造器,可以调用其它的构造器。
     */
    constructor(age: Int) : super() {

    }

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

    }

}

7、主构造器默认参数(推荐)

/**
 * 主构造器默认参数
 */
class Person(val age: Int, val name: String = "") : Animal() {
    
}

8、构造同名的工厂函数

第一步:定义Person类。

abstract class Animal

/**
 * 主构造器默认参数
 */
class Person(val age: Int, val name: String = "") : Animal() {

}

第二步:定义同名的工厂函数。

class MainActivity : AppCompatActivity() {

    val persons = HashMap<String, Person>()

    /**
     * 同名的工厂函数,当然也可以起其他的名字
     */
    fun Person(name: String): Person {
        return persons[name] ?: Person(1, name).also { persons[name] = it }
    }

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

}

7.2、类与成员的可见性

1、可见性对比

2、修饰对象

3、模块的概念

大致可以认为是一个jar包或者是一个aar。

4、internal VS default

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

kotlin中为了能让外界对于对于内部的实现是不知道的,可以用internal修饰,非常的方便。

java中为了不让外界访问,可以用default访问,但是这样就会导致这些类必须放在一个包下,难以维护。

5、构造器的可见性

package com.example.kotlindemo

abstract class Animal

/**
 * 1、为了构造一个单例。
 * 2、或者利用工厂方法生成对象。
 * 那么此时可以在构造方法前面增加private
 */
class Person private constructor(val age: Int, val name: String = "") : Animal() {

}

6、属性的可见性

package com.example.kotlindemo

abstract class Animal

/**
 * 属性的可见性
 * 属性增加private 外界无法访问
 */
class Person(private val age: Int, val name: String = "") : Animal() {

}

7、顶级声明的可见性

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

2、顶级声明不支持protected。

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

7.3、类属性的延迟初始化

1、为什么要延迟初始化?

1、类属性必须在构造时初始化。

2、某些成员只有在类构造之后才会被初始化。

比如Activity中的UI。

2、类属性如何处理?

1、初始化为null (不推荐)

private var name:TextView? = null 

2、使用lateinit(稍微推荐)

private lateint var name:TextView;

在后面延迟初始化,注意:必须要用var。

3、使用lazy(非常推荐)

class MainActivity : AppCompatActivity() {

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

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

}

7.4、代理

1、接口代理。

package com.example.kotlindemo

import android.util.Log

//第一步:定义一个接口
interface Api {
    fun a()
    fun b()
    fun c()
}

//第二步:定义一个接口实现类
class ApiImpl:Api{
    override fun a() {
    }

    override fun b() {
    }

    override fun c() {
    }

}

// 第三步:定义一个类,用于api功能的增强
class ApiWrapper(val api:Api):Api{

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

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

    override fun c() {
        // 比如增加一些日志
        api.c()
    }

}

// 针对第三步,可以有简单的写法
// 对象api代替类ApiWrapper实现接口Api
// 相当于通过by api 将实现交给了api来实现
// 对象api的要求就是实现被代理的接口
class ApiWrapper2(val api:Api):Api by api{

    // 对功能进行增强
    override fun c() {
        Log.e("test","test")
        api.c()
    }
}

2、属性代理 lazy

package com.example.kotlindemo

class Person(val name:String) {

    // lazy 代理了firstName 当第一次调用firstName的时候,回去获取lazy返回的值,也就是lambda的表达式的值
    val firstName by lazy {
        name.split(" ")[0]
    }

}

3、属性代理 observable

第一步:定义属性代理。

class StateManager {

    private val TAG = StateManager::class.simpleName
    // state 属性被Delegates.observable属性代理。
    var state: Int by Delegates.observable(0) { property, oldValue, newValue ->
        Log.e(TAG, "property:" + property + ",oldValue:" + oldValue + ",newValue:" + newValue)
    }

}

第二步:在MainActivity中调用它。

        val stateManager = StateManager()
        stateManager.state = 5;

第三步:得到的日志如下

property:property state (Kotlin reflection is not available),oldValue:0,newValue:5

4、属性代理的例子

package com.example.kotlindemo

import kotlin.reflect.KProperty

class Foo {
    // 属性x被X()代理,
    // 当读取x属性的时候,回去执行X()的getValue方法
    // 当读取y属性的时候,会去执行X()的setValue方法
    val x: Int by X()
    var y: Int by X()
}

class X {

    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        return 2
    }

    operator fun setValue(foo: Any, property: KProperty<*>, i: Int) {

    }

}

7.5、利用属性代理读取Properties文件

比如有一个文件

Config.properties文件

author=zhangsan

version=1.0

desc=This is a demo

import java.io.File
import java.io.FileInputStream
import java.net.URL
import java.util.*
import kotlin.reflect.KProperty


class PropertiesDelegate(private val path: String, private val defaultValue: String = "") {
    private lateinit var url: URL

    private val properties: Properties by lazy {
        val prop = Properties()
        url = try {
            javaClass.getResourceAsStream(path).use {
                prop.load(it)
            }
            javaClass.getResource(path)
        } catch (e: Exception) {
            FileInputStream(path).use {
                prop.load(it)
            }
            URL("file://${File(path).canonicalPath}")
        }
        prop
    }

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return properties.getProperty(property.name, defaultValue)
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        properties.setProperty(property.name, value)
        File(url.toURI()).outputStream().use {
            properties.store(it, "Hey!!")
        }
    }
}

abstract class AbsProperties(path: String) {
    protected val prop = PropertiesDelegate(path)
}

class Config : AbsProperties("Config.properties") {
    var author by prop
    var version by prop
    var desc by prop

    override fun toString(): String {
        return """
            author:$author,
            version:$version,
            desc:$desc
        """.trimIndent()
    }
}

fun main() {
    val config = Config()
    println(config.toString())
    config.version = "1.1"
    println(config.toString())
}

7.6、单例

1、kotlin中的单例

kotlin中单例如下表示:

package com.example.demo

/**
 * 单例
 */
object Singleton {
    var x:Int = 2

    fun y(){
    }
}

java中如何访问这个单例

int x = Singleton.INSTANCE.getX();

kotlin中如何访问这个单例

val x = Singleton.x

2、静态成员@JvmStatic

在kotlin中定义用@JvmStatic修饰的属性

object Singleton{    @JvmStatic var x:Int = 2}

在Java中可以进行如下访问

int x = Singleton.getX();

3、不生成getter/setter @JvmField

在kotlin中定义如下

object Singleton{
    @JvmField var x:Int = 2
}

在java中访问

int x = Singleton.x;

4、普通类的静态成员

普通类使用@JvmStatic会出现编译错误

class Foo {
    // 会出现编译错误
    @JvmStatic fun y()
}

要想在普通类上使用静态成员,可以使用伴生对象

class Foo {
    companion object{
        @JvmStatic fun y() {

        }
    }
}

在Java中调用,类似于静态方法调用

Foo.y();

在kotlin中调用

Foo.y()

5、object 的构造器

默认有一个无参的构造器

6、object 的类继承(和Java中一致)

object Test:Runnable{
    override fun run() {
        
    }
}

7.7、内部类

1、内部类定义

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

2、内部类实例化

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
}

3、内部object

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

4、匿名内部类

java和kotlin匿名内部类的表示

5、匿名内部类实现多个接口

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

7.8、数据类

1、数据类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)

2、data类的特性

1、有component方法。

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

3、数据类解构

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


    }

}

4、数据类 java和kotlin的区别

7.9、枚举类

1、Java和kotlin枚举的区别

2、枚举的属性

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
}

结果如下:

3、枚举的构造器

4、枚举的接口

5、枚举的接口-各自实现

6、枚举进行扩展(和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]
        }
    }

}

7、枚举用来比大小

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
}

8、枚举的区间

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、定义

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

2、密封类VS枚举类

7.11、内联类

1、定义

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

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

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

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

2、内联类的属性

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

3、内联类的继承结构

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

4、内联类的编译优化

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

5、内联类的使用场景

6、内联类的限制

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

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

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

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

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

7.12、kotlin中的JSON序列化

Gson:JSON解析库

kotlinx.serialization:JSON解析库

moshi:JSON解析库