Kotlin 之类型进阶

类的构造器

  • 构造器

    • 主构造器

      //age 全局可用,name 只是普通的构造器参数
      class Person(var age: Int, name: String) {
          
      }
      
    • init 块

      //age 全局可用,name 只是普通的构造器参数
      class Person(var age: Int, name: String) {
      
          val n: String
          init {
              n = name
          }
      }
      

      init 块会在构造方法的最前面执行,可用于初始化

    • 属性初始化

      ​ 可在 init 块中进行初始化

    • 重写 get / set

      class Person() {
          var name: String = ""
              get() {
                  return "hello $field"
              }
              set(value) {
                  field = "hello $value"
              }
      }
      
  • 副构造器

    • 不定义主构造器

      open class A {
          constructor(name: String) {
              println("A的 一个参数构造器")
          }
      
          constructor(name: String, age: Int) {
              println("A的 两个参数构造器")
          }
      
          init {
              println("A的 init")
          }
      }
      
      class B : A {
      
          constructor(name: String) : this(name, 20) {
              println("B的 一个参数构造器")
          }
      
          constructor(name: String, age: Int) : super(name, age) {
              println("B的 两个参数的构造器")
          }
      }
      
      fun main() {
          var lv = B("张三")
      }
      
      A的 init
      A的 两个参数构造器
      B的 两个参数的构造器
      B的 一个参数构造器
      
    • 主构造器默认参

      open class A(age: Int, name: String = "张三") {
      }
      
      fun main() {
          var a = A(20)
      }
      
  • 工厂函数

    open class A(age: Int, name: String = "张三") {
    
        fun A(): A {
            return A(20)
        }
    
        fun A(age: Int, name: String): A {
            return A(age, name)
        }
    
    }
    

    函数的名字可以和类名相同

可见性对比

可见性对比 java Kotlin
public 公开 与 Java 相同,默认
internal X 模块内可见
default 包内可见,默认 X
protected 包内及子类可见 类内及子类可见
private 类内可见 类或文件可见

​ 模块的概念:直观的讲,大致可以认为是一个 Jar包,一个 aar

  • IntelliJ IDEA 模块

  • Maven 模块

  • Gradle SourceSet

  • Ant 任务中一次调用 的文件

    如果定义了 internal 的函数,但是不想让 java 访问,可以使用 @JvmName("") 注解来实现,在 java 中调用的时候注解测参数将会成为方法的名字,且报错,不可以调用。

构造器的可见性

  • 给构造器加注解的时候 constructor 就必须写出来
  • 如果使用单例,或者工厂等,就需要将构造器弄成私有的

顶级声明的可见性

  • 指文件内直接定义的属性,函数,类等
  • 顶级声明不支持 protected
  • 顶级声明被 private 修饰后表示文件内部可见

类属性的延时初始化

  • 类属性必须在构造时初始化

  • 某些成员只有在类构造后才会被初始化,例如android 中在 onCrate 中进行 findViewById

解决办法

  1. 直接判空,或者类型强转

  2. 使用 lateinit 关键字

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

    开发者必须在完全确定变量值生命周期的情况下使用 lateinit

    不要在复杂的逻辑中使用 lateinit

  3. 使用 lazy

    只有在首次使用的时候被访问执行

代理Delegate

​ 接口代理:对象 X 代替当前类 A 实现接口 B 的方法

​ 属性代理:对象 x 代理属性 a 实现 getter/setter 方法

//接口代理
interface Api {
    fun a()
    fun b()
    fun c()
}

class ApiImpl : Api {
    override fun a() {}

    override fun b() {}

    override fun c() {}
}

class ApiWrapper(val api: Api)
    //对象 api 代替类  ApiWrapper 实现接口 API
    : Api by api {

    override fun c() {
        println("-------- c")
        api.c()
    }

}

属性代理:

​ 属性代理是借助于代理设计模式,把这个模式应用于一个属性时,他可以将访问器的逻辑代理给一个辅助对象

​ 可以简单理解为属性的 setter,getter 访问器内部实现是交给一个代理对象来实现,相当于一个代理对象替换了原来字段的读写过程,而暴露在外部属性的操作还是不变的,照样是属性赋值和读取,只是 setter,getter内部实现变了

1,Lazy

​ 当变量第一次使用是进行初始化,可以实现懒加载

//属性代理
class Person(val name: String) {

    //lazy返回的对象代理了属性 firstName 的 getter
    val firstName by lazy {
        println("初始化-----")
        "345"
    }
}

fun main() {
    val p = Person("")
    println(p.firstName)
    println(p.firstName)
}
初始化-----
345
345

只有在第一次调用的时候才会执行。感觉比较适合安卓

初始化默认是线程安全的,通过 synchronized 锁来保证

不过还是会影响性能,如果 lazy 的初始化不会涉及到多线程,那么可以传入 LazyThreadSafetyMode.NONE 来取消同步锁

LazyThreadSafetyMode有三种模式:SYNCHRONIZED(默认模式)、PUBLICATION、NONE

其中PUBLICATION模式使用了AtomicReferenceFieldUpdater(原子操作)允许多个线程同时调用初始化流程。

2,自定义属性代理

class Example {
    var p: String by Delegate()
}


class Delegate {
    operator fun getValue(thisRef: Any, property: KProperty<*>): String {
        return "代理 get"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("代理 set")
    }
}


fun main() {
    val e = Example()
    println(e.p)
    e.p = "345"
}
代理 get
代理 set

3,observable

变量被赋值时会发出通知

Delegates.observable 可以传入两个参数,一个是初始值,另一个是变量被赋值时的 handle 方法

class User {
    var name: String by Delegates.observable("345") { property, oldValue, newValue ->
        println("$oldValue -> $newValue")
    }
}

fun main() {
    val user = User()
    user.name = "first"
    user.name = "second"
}

输出

345 -> first
first -> second

只要 user.name 被赋值,监听就会触发

类似的还有 vetoable ,只不过 vetoable 是在赋值前触发,observable 是在赋值后触发

4,vetoable

使用 vetoable 进行拦截

class User {
    var name: String by Delegates.vetoable("345") { property, oldValue, newValue ->
        if (newValue == "first") {
            return@vetoable true    //返回 true 表示first 可以赋值给 name
        }
        return@vetoable false //返回false 表示拦截其他赋值操作
    }
}

fun main() {
    val user = User()
    user.name = "first"
    println(user.name)
    user.name = "second"
    println(user.name)
}

first
first

5,对 map 的代理

fun main() {

    val map = mutableMapOf("name" to "张三", "age" to 1)

    var name: String by map
    println(name)

    name = "李四"
    println(map["name"])

}
张三
李四

6,局部变量代理

fun main() {

    a {
        345
    }
}


fun a(sum: () -> Int) {
    val s by lazy(sum)

    println(s) //345
}

案例

:使用属性代理读写 Properties

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) {
            try {
                ClassLoader.getSystemClassLoader().getResourceAsStream(path).use {
                    prop.load(it)
                }
                ClassLoader.getSystemClassLoader().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, "Hello!!l")
        }
    }

}

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
}

fun main() {
    val config = Config()
    print(config.author)    //getValue
    config.author = "345"   //setValue
    println(config.author)
}

单例

//单例的定义,默认是恶汉模式
object Signleton {
    var x = 2

    //模拟 java 中静态方法,只有在 object 中可以使用
    @JvmStatic
    fun y() {
        println("---------")
    }
}
// Signleton.INSTANCE.getX(); 从 java 中调用该类
//        Signleton.y();  JvmStatic :静态的

class Foo {
    //伴生对象,与普通类同名的 object
    //相当于java中的 public static void y(){}
    companion object {
        @JvmStatic
        fun y() {
            println("y")
        }

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

    //属性可以使用 JvmField,生成非静态 Field,
    @JvmField
    var x: Int = 2
}

fun main() {

    Signleton.x
    Signleton.y()


    println(Foo.y)
    println(Foo.y())
}

内部类

fun main() {


    //创建非静态内部类对象
    val inner = Outer().Inner()
    //创建静态内部类对象
    val staticInner = Outer.StaticInner()


    //匿名内部类,可以实现多个接口,并且继承一个类
    object : Runnable, Cloneable {
        override fun run() {

        }
    }


}


class Outer {

    //非静态内部类
    inner class Inner {

    }

    //静态内部类
    class StaticInner {

    }


    //一旦定义出来,就会被初始化
    object HH {

    }
}

数据类

data class Book(val id: Long, val name: String, val author: String) {
    
}
JavaBean data class
构造方法 默认无参构造 属性作为参数
字段 字段私有,get/set 公开 属性
继承性 可继承,可被继承 不可被继承
component 相等性,解构等

data class 为我们做了什么

​ 编译器会根据我们在构造函数中声明的属性自动导出下列成员:

  • equals / hashCode

  • toString

  • componentN()

  • copy()

    如果在类中明确定义或继承了上面的基础方法,则不会自动生成

限制:

  • 构造函数至少要一个参数
  • 所有构造函数的参数都必须使用 val 或者 var 标记
  • data class 不可以是 abstract,open sealed or inner
  • 不可以实现接口

数据类自带的结构方法:componentN()

fun main() {

    val book = Book(1L, "百科", "张三")
    val (id, name, author) = book
    println("$id  $name $author")

}

实际上,结构是调用了 Book 类的 component1() 和 component2() ,component3()。

如何作为 Java Bean 使用呢

需要解决两个限制:

​ 至少一个构造器,和 final。data class 是不可被继承的。

​ 如果解决呢:NoArg 插件,AllPoen 插件

在 build.gradle 中添加插件

plugins {
	......
    id("org.jetbrains.kotlin.plugin.noarg").version("1.3.50")
    id("org.jetbrains.kotlin.plugin.allopen").version("1.3.50")
}


noArg {
    //执行 init 块中的代码
    invokeInitializers = true
    annotation("com.kotlin.study.five.PoKo")
}

allOpen {
    annotation("com.kotlin.study.five.PoKo")
}

注意build.gradle 使用的是 kt 的代码

还需要定义一个注解:就是 annotation指定路径下的 PoKo 注解

public @interface PoKo {
}

空的即可

使用

//使用该注解后会在字节码中生成一个无参构造,
//可被继承
@PoKo
data class Book(val id: Long, val name: String, val author: String) {
    init {
        println("------")
    }
}
fun main() {    val b = Book::class.java.newInstance()}

枚举

fun main() {

    S1.Busy.run()
    S1.Idle.run()

    val state = State.Busy

    //条件分支
    val value = when (state) {
        State.Idle -> {
            0
        }
        State.Busy -> {
            1
        }
    }

    //枚举的比较,因为是有顺序的,所以可以。需要重载运算符

    //枚举的区间,因为有顺序
}

//枚举,父类是 Enum,所以不能继承其他类
enum class State {
    Idle,
    Busy
}

//带参数的构造
enum class S(val id: Int) {
    Idle(0),
    Busy(1)
}

//实现接口的枚举
enum class S1 : Runnable {
    Idle {
        //实现字节的 run 方法
        override fun run() {
            println("For Idle")
        }
    },
    Busy;

    override fun run() {
        println("For every state.")
    }

}

//为枚举定义扩展
fun State.next(): State {
    return State.values().let {
        val next = (ordinal + 1) % it.size
        it[next]
    }
}
  • 枚举
    • 枚举的基本知识:定义方法,属性,构造器,接口实现
    • 为枚举定义扩展
    • 分支表达式
    • 比较与区间

密封类

  • 密封类是一种特殊的抽象类,不能被实例化,但是可以有抽象成员
  • 密封类的子类定义在与自身相同的文件中
  • 密封类的子类个数是有限的

fun main() {

    //只能通过子类实例化对象
    val play: PlayerState = Start("小鸟")
    
}


//首先是一个抽象类,其次是一个密封类,构造器是私有的
sealed class PlayerState {

}

class Start(val song: String) : PlayerState() {

}

class Stop : PlayerState() {

}

  • 密封类
    • 概念:抽象,子类可数,子类封闭
    • 构造器私有:无法在外部文件中继续密封类
    • 子类定义:必须定义在当前文件中
    • 与枚举对比:enum:实例可数,sealed:子类可数

内联类

  • 内联类是对某一个类型的包装
  • 内联类是类似于 Java 装箱类型的一种类型
  • 编译器会尽可能使用被包装的类型进行优化
fun main() {

    var x = BoxInt(3)

    val newValue = x.value * 200
    println(newValue) //600
    x++
    println(x)  //BoxInt(value=4)

}

//内联类
//对 Int 做了包装,Int 必须定义在主构造器,且必须是 val 类型 和 public
//可实现接口。且不能继续和被继承
inline class BoxInt(val value: Int) {

    //内联类属性:不应改包含状态。也就是只能定义方法或者函数

    //重写自增运算符
    operator fun inc(): BoxInt {
        return BoxInt(value + 1)
    }

}

内联类目前还在公测。

限制

  • 主构造器必须有且只有一个只读属性。因为要对其进行包装

  • 不能定义有 backing-field 的其他属性。不能有状态

  • 被包装的类型不是是泛型

  • 不能继续且被继承

  • 内联类不能被定义为其他类的内部类

  • 定义的方法在编译的时候会被替换为静态方法

数据类的 Json 化、

依赖

kotlin("kapt") version ("1.3.50")
implementation("com.squareup.moshi:moshi:1.8.0")
implementation("com.squareup.moshi:moshi-kotlin:1.9.2")
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.8.0")
fun main() {

    //Gson
    val gson = Gson()
    println(gson.toJson(Person("Gson", 20)))
    println(gson.fromJson("""{"name":"Gson"}""", Person::class.java))


    println("----------------------")
    //MoShi
    val moshi = Moshi.Builder().build()
    val jsonAdapter = moshi.adapter(Person::class.java)
    println(jsonAdapter.toJson(Person("MoShi", 20)))
    println(jsonAdapter.fromJson("""{"name":"Gson"}"""))
    
}

@JsonClass(generateAdapter = true)
data class Person(val name: String, val age: Int = 10)

{"name":"Gson","age":20}
Person(name=Gson, age=0)
----------------------
{"name":"MoShi","age":20}
Person(name=Gson, age=10)

Bean 类中 age 的默认值是 10

在 gson 解析的时候没有指定 age 则为 0

而 moshi 解析的时候会按照构造方法中的默认值来进行解析。注意 moshi 用到了注解处理器 @JsonClass

递归整形案例

fun main() {

    val l1 = inListOf(0, 1, 3, 4)
    println(l1)
    println(l1.joinToString('-'))
    println(l1.sun())

    val (x, y, z) = l1
    println(x)
    println(y)
    println(z)
}


fun inListOf(vararg ints: Int): IntList {
    return when (ints.size) {
        0 -> IntList.Nil
        else -> {
            //递归调用,
            //slice:切割,从第一个到最后一个(注意不是第 0 个)
            //递归时,ints[0]永远是上一次的下一个
            IntList.Cons(ints[0], inListOf(*(ints.slice(1 until ints.size).toIntArray())))
        }
    }
}

sealed class IntList {

    object Nil : IntList() {
        override fun toString(): String {
            return "Nil"
        }
    }


    class Cons(val head: Int, val tail: IntList) : IntList() {
        override fun toString(): String {
            return "$head,$tail"
        }
    }

    fun joinToString(sep: Char = ','): String {
        return when (this) {
            Nil -> "Nil"
            is Cons -> {
                "${head}$sep${tail.joinToString(sep)}"
            }
        }
    }


    operator fun component1(): Int? {
        return when (this) {
            Nil -> null
            is Cons -> head
        }
    }

    operator fun component2(): Int? {
        return when (this) {
            Nil -> null
            //下一个 Cons
            is Cons -> tail
                .component1()
        }
    }

    operator fun component3(): Int? {
        return when (this) {
            Nil -> null
            is Cons -> tail
                .component2()
        }
    }
}


fun IntList.sun(): Int {
    return when (this) {
        IntList.Nil -> 0
        is IntList.Cons -> {
            head + tail.sun()
        }
    }
}
  • 类型进阶
    • 类的构造器
      • 主构造器:唯一的路径
      • 副构造器:提供其他的构造路径
      • init 快:可写多个,最后会合成一个执行
    • 可见性
      • public
      • internal:模块内可见,类似一个 jar,aar
      • protected:没有包内可见,只有子类看见
      • private
    • 类属性的延时初始化
      • 可空类型
      • lateinit
      • lazy
    • 代理 Delegate:
      • 接口代理
      • 属性代理:lazy,observable,vetoable 等
    • 使用属性代理读写 Properties
    • 单例 object
      • 伴生对象
      • 定义 java field 的 @JvmField
      • 定义静态成员的 @javaStatic
    • 内部类
      • 默认静态
      • 非静态用 inner 修饰
      • 匿名内部类可以实现多个接口
      • 本地类和本地函数
    • 数据类
      • component:主构造器中定义的属性
      • 编译器生成的方法:equals,hashCode,copy,componentN
      • 解构:定义了 componentN方法,即可通过解构获取到对应的值
      • 插件:noArg(增加默认构造方法),allOpen(去掉 final)
    • 其他类
      • 枚举类
      • 密封类:子类只能定义在父类相同的文件中
      • 内联类:类似于装箱拆箱
    • 数据类的 json 序列化
      • Gson,Moshi,Kotlinx.serialization
      • 特性:默认参数,init 块
    • 递归的整形列表的简单实现
      • 递归用的非常好
      • 可进行解构

参考自慕课网 Kotlin 从入门到精通

原创文章 119 获赞 77 访问量 3万+

猜你喜欢

转载自blog.csdn.net/baidu_40389775/article/details/105402657