kotlin进价知识点(中篇)

  • 类的构造器


1.主构造器
①init块
init块会随着主构造器一起运行:
class LoadT(var inf:Any,image:Int){
        init {
                inf= ""
        }
        init {
                inf = image
        }
}
相当于:
class LoadT(var inf:Any,image:Int){
        init {
                inf= ""
                inf = image
        }
}
②属性初始化:
在类里面定义属性必须初始化
class LoadT(var inf:Any,image:Int){
        val  tt   //  这里的tt必须要初始化,不然会报错。
}

2.副构造器
①如果一个类有主构造器,那么所有的副构造器都必须通过this调用主构造器

class LoadT(var inf:Any,image:Int){
        constructor(gender:String) : this("", image = 1){
        }  
}
如果有多个构造器,副构造器可以调用主构造器,或者调用已经调用了主构造器的副构造器

class LoadT(var inf:Any,image:Int){
        constructor(gender:String) : this("", image = 1){
        }
        constructor():this(""){
        }
}

②也可以不定义主构造器,只定义副构造器。
但是不推荐,会有多条构造路径,增加复杂度。
class LoadT1{
        var inf:Int
        var image:String
        constructor():super(){         //如果父类的主构造器没有,可以省略super()
                this.inf = 5
                image = ""         //这里省略了this,但是效果是一样的。
        }
}
③当你没有主构造器的时候,有多个副构造器。
副构造器,可以调用已经调用的主构造器的副构造器。

class LoadT1{
        var inf:Int
        var image:String
        constructor(gender:String):super(){ //如果父类的主构造器没有,可以省略super()
                this.inf = 5
                image = "" //这里省略了this,但是效果是一样的。
        }
        constructor():this(""){
        }
}
④  主构造器默认参数
如果希望主构造器参数在java代码中用重载的方式调用:
在constructor的前面添加关键字@JvmOverloads
class LoadT1{
        var inf:Int = 5
        var image:String
        @JvmOverloads //实际上是定义在constructor的前面。
        constructor(gender:String):super(){ 
                image = "" 
        }
        constructor():this(""){
        }
}

3.构造工厂函数。
工厂函数可以加载缓存或者在构建类的实例的时候,类的实例是私有的,需要借助工厂函数创建。

val loadT = HashMap<String,LoadT>()
fun LoadTCatch(image: String):LoadT{         //通过LoadTCatch缓存对象
        return loadT[image]?: LoadT(1,image).also { loadT[image] = it }
}
return  如果不为空,返回前面的loadT[image],如果为空返回后面的内容。

类与成员可见性

1.与java对比的可见性  

Public很好理解,就是所有的都能访问到。

internal  修饰的仅模块内可见,模块内指的是同一个gradle或者maven项目.

Protected  修饰的只能在类或者子类中可见,通过包下面的类看不见:

class Os(){

        internal val i = 5

        protected val c= ""

}

fun oo(){

        val os = Os()

        os.i

        os.c //这里报错看不见

}

private  如果修饰的是顶级声明或者类,可见范围是文件可见。如果修饰的是成员,则是类中可见,即使在同一个包下面,也是不可见的。

下面的截图就是两个不同的文件。

    

2.虽然声明了internal关键字,但是用java还是能访问到。除非加上@JvmName(“”):

class Osajdoi(){

        internal val i = 5

        private val c= ""

        @JvmName("!daojcs") internal fun ia(){    //@JvmName标识只能放在方法中,属性不行。

        }

}

3.在属性中的get可见性必须和属性保持一致:

class Osajdoi(){

        var i :Int= 5

                get() {

                        return field        //get的可见性必须和属性保持一致

                }

                set(value) {

                        field=value //set的可见性必须小于属性的可见性

                }

}

类属性的延迟初始化

在kotlin中,属性必须在构造的时候必须初始化,但是像Android的UI绑定,需要在使用的时候再绑定。

所以就需要延迟初始化。

延迟初始化有三种方式:
1.跟java类似,初始化为null:
class MainActivity : ComponentActivity() {
        var nameText: TextView? = null             //通过?的方式表示有可能为空
        override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        nameText = findViewById(androidx.core.R.id.text)
        }
}
这样做不好的地方在于,每次调用nameText的时候,都需要加上?
像这样   nameText?
2.使用lateinit 关键字,这个关键字加在权限的后面:
class MainActivity : ComponentActivity() {
        lateinit var nameText: TextView            
        override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
                If(::nameText.isInitialized){
                        nameText = findViewById(androidx.core.R.id.text)
                }
        }
}
使用这个关键字用来延迟初始化,注意没初始化之前使用会报错。
在kotlin1.2之后,使用引用判断是否为空   ::property.isInitialized

3.使用lazy进行延迟初始化
by   lazy是放在property的最后面,并且把使用跟声明放在一起。
只有使用这个property才会调用里面的代码。:
class MainActivity : ComponentActivity() {
        private val nameText: TextView by lazy {
                findViewById(androidx.core.R.id.text)    //只有使用nameText才会调用方法体里面。
        }
}

代理Delegate

代理分为两种,接口代理和属性代理。

1.接口代理:在实现的后面加上by  和  实现的接口(跟在类的参数有关)。

可能有点抽象,看个例子吧。

这个例子是代理数组,实现一个超级数组,可以接受list,也可以接受map(map就是key-value)

:

class SuperArray<E>(

    private val list:MutableList<E?> = ArrayList(),       //创建可变的List,并且给初始化

    private val map:MutableMap<Any,E> = HashMap() //创建可变的map,并且给初始化

):MutableList<E?> by list, MutableMap<Any,E> by map {   //实现接口MutableList和MutableMap

    override fun isEmpty(): Boolean =

        list.isEmpty()&& map.isEmpty()

    override val size: Int

        get() =  list.size+map.size

    override fun clear() {

        list.clear()

        map.clear()

    }

    override fun toString(): String {

        return "List{$list};Map:{$map}"

    }

    override  operator  fun set(index: Int, element: E?): E? {

        if (list.size <=  index){ //复写List中的Set逻辑,确保每个元素都能添进

            repeat(index-list.size + 1){

                list.add(null)

            }

        }

        return list.set(index,element)       //这里是调用List底层的set,不会形成无限循环

    }

}

实际开发的过程中,代理接口的需求没有那么多,代理属性的需求很多。

2.属性代理

①lazy,lazy属性代理的实质是接口lazy的实例代理了对象属性的getter。

看着有点绕,看源码:

②属性代理中的observable

本质上是observable对象的实例代理了getter和setter

:

class User {

    

    var age: Int by Delegates.observable(0) { property, oldValue, newValue ->

        println("Property ${property.name} changed from $oldValue to $newValue")

    }

}

 0是初始化值,property是指这个属性,oldValue是之前的值,newValue是新的值。

用observable可以侦测值的变化。

 ③可以自定义属性代理,负责代理属性的getter和setter

如果属性是var,可以代理getter和setter

如果属性是val,只能代理getter

下面通过一个例子来写出怎么实现代理:

class Xi{

        val x:Int by Agent()

        var y:Int by Agent()

}

class Agent{

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

                return 3

        }

        operator fun setValue(xi: Xi,property: KProperty<*>,i:Int){

        //如果确定要代理的类,可以把thisRef:Any?换成代理的类

        }

}

vetoable也是在Delegates类中,不过是检查属性变化之前的。可以通过自己抒写逻辑,决定是否让属性的值变化:

import kotlin.properties.Delegates

class TemperatureSensor {

  var temperature: Int by Delegates.vetoable(20) { _, oldValue, newValue ->

// 仅当新值在 -30 到 50 度之间时才允许更改

        if (newValue in -30..50) {

                println("Temperature changed from $oldValue to $newValue") true

        } else {

                 println("Attempted to set invalid temperature: $newValue") false }

        }

}

fun main() {

        val sensor = TemperatureSensor() println(sensor.temperature) // 输出:20

        sensor.temperature = 25 // 有效 println(sensor.temperature) // 输出:25

        sensor.temperature = 60 // 无效 println(sensor.temperature) // 输出:25

        sensor.temperature = -10 // 有效 println(sensor.temperature) // 输出:-10

}

使用属性代理读写Properties

在使用代理之前,需要了解Properties类,继承自Hashtable<Object,Object>,用于维护键值对,这些键值对通常是以字符串形式表示。

Properties类通常用来储存配置数据。

常用的方法有:

load(InputStream inStream)//从输入流中读取键值对

load(Read reader)   //从字符输入流中读取属性列表

store(OutputStream out,String comments)   // 将属性以输出流写出,comments是用来提示的。

store(Writer writer,String comments) //以字符流的方式输出,comments是用来提醒的。

常用的访问和修改属性的方法:

getProperty(String key)           //根据键获得相应的值

getProperty(String key,String defaultValue)  //根据键获得相应的值,如果键不存在,就返回默认的值。

SetProperty(String key,String value)   //设置键值对

propertyName()     //通过这个方法可以获取所有的键值对

实现效果:用属性代理的方式,获取或者改写配置文件。

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

}

//黄色标注部分是用来加载属性文件:

1.先尝试用当前类的类加载器从类路径中加载

2.如果加载失败,尝试使用系统类加载器从类路径中加载

3.如果仍失败,尝试从文件系统加载

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

}

}

abstract class AbsProperties(path: String){

        protected val absProperties = PropertiesDelegate(path)   //生成PropertiesDelegate的实例

}

class Config :AbsProperties("Config.properties"){

        var author by absProperties

        var version by absProperties

        var desc by absProperties           //这里的属性是配置文件中存在的属性名

}

fun main(){

        val config =Config()

        println(config)

        config.author = "me"

        println(config.author)

}

用属性代理,实现获取和改写配置文件。

Android开发的过程中还经常会用到sharedPreferences。

写出sharedPreferences属性代理的方式。

一定要先去了解sharedPreferences的用法,不然会看不懂的。

这里篇幅的原因,就不赘述

class SharedPreDelegates<T>(private val name: String,

                         private val defaultValue: T,

                        private val sharedPreferences: SharedPreferences){

    operator fun getValue(thisRef: Any?,property: KProperty<*>):T{

        return  when  (defaultValue){

            is Boolean -> sharedPreferences.getBoolean(name,defaultValue) as T

            is Float -> sharedPreferences.getFloat(name,defaultValue) as T

            is Int -> sharedPreferences.getInt(name,defaultValue) as T

            is Long -> sharedPreferences.getLong(name,defaultValue) as T

            is String -> sharedPreferences.getString(name,defaultValue) as T

            else -> throw  IllegalArgumentException("This type cannot be save")

        }

    }

    operator fun setValue(thisRef: Any?,property: KProperty<*>,i: T){

        with(sharedPreferences.edit()){

            when(i){

                is Boolean -> putBoolean(name,i)

                is Float -> putFloat(name,i)

                is Int -> putInt(name,i)

                is Long -> putLong(name,i)

                is String -> putString(name,i)

                else -> throw  IllegalArgumentException("")

            }.apply()

        }

    }

}

abstract  class  AbsSharePreferences(context: Context){

    private val shred: SharedPreferences =context.getSharedPreferences("MyPreferences",Context.MODE_PRIVATE)

    protected  fun <T> delegate(name: String,defaultValue: T):SharedPreDelegates<T>{

     return  SharedPreDelegates<T> (name,defaultValue,shred)

    }

}

class  SetShareP(context: Context):AbsSharePreferences(context){

    var userName by  delegate("userName","")

    var isLoggedIn by delegate("isLoggenIn",false)

    var userAge by delegate("passWord",0)

}

单例Object

  1. object的定义

object 就相当于是一个饿汉式的单例,Singleton既是类名也是对象名

饿汉式就是访问的时候会立即执行。

object Singleton{

}

  1. 成员访问

直接使用类名加.的方式访问,例如:

object Singleton{

        var i :Int = 3

        fun ig(){

        }

}

fun oyy(){

        Singleton.i

        Singleton.ig()

}

  1. 伴生对象

在定义一个类的时候,还可以给它定义一个伴生对象。

伴生对象是与普通类同名的object,例如:

fun oyy(){

        Room.o()       //访问伴生对象

}

class Room{

        companion object{

                @JvmStatic fun o(){

        }

}

}

4.两个注解,@JvmField和@JvmStatic

①对属性添加@JvmField后,对kotlin没有影响,可以正常访问。

  但是对于java的影响只能访问Field,没有了getter和setter。

如果是在object或者是companion object里面,

设置带@JvmField的属性,转换成java代码都是带static。;

但如果是直接在类里面对属性设置@JvmField,转换成java代码没有static

注意,不换成java代码的话,对kotlin没有任何影响,因为kotlin中不存在static类。

例如:

class Room{ //无论是age还是male,转java后都只能访问其Filed

        @JvmField var male = "" //转java后不会有static

        companion object{

                @JvmField var age= 5 //转java后会有static

        }

}

②@JvmStatic

只能在object或者是companion object的使用这个注解。

可以对属性或者是函数使用,转换成java后,相当于加上了static

class Room{ //无论是age还是male,转java后都只能访问其Filed

companion object{

                @JvmStatic var grade = ""    // 转换成java后,属性加上static

               @JvmStatic fun o(){  //  转换成java后,函数加上static

                }

        }

}

内部类

  1. 内部类的定义

默认在类中加类是静态内部类。

加上Inner关键字后,是非静态内部类。

class Outer{

    inner class Inner{

        //这里是非静态内部类,,访问需要持有外部实例的引用

    }

    class StaticInner {

        //这里相当于是静态类内部类,在访问的时候可以直接通过类目加点

    }

}

fun visit(){

    var  inner = Outer().Inner()    //持有外部类的实例访问

var staticInner = Outer.StaticInner()   //直接通过类名加.进行访问。

}

  1. 内部object

内部object是饿汉式,是立即执行,不存在非静态的情况,所以不能用inner修饰

访问可以通过类名加点的形式:

object  Singleton{

    object StaticInnerObject {

    }

}

fun oyy(){

    var visit = Singleton.StaticInnerObject

}

  1. 匿名内部类

object省略名字,在‘:’后面跟实现的接口类。

并且可以继承父类或实现多个接口

例如:

fun     implements(){

    object:Runnable,Cloneable {

        override fun run() {

            TODO("Not yet implemented")  //继承Runnable方法

        }

        override fun clone(): Any {

            return super.clone()    //继承Cloneable方法

        }

    }

}

  1. 本地类(LocalClass)

可以在一个函数中,定义一个类实现多个接口。

例如:

fun     implements(){

    class LocalClass:Cloneable,Runnable{

        override fun run() {

            TODO("Not yet implemented")

        }

    }

}

  1. 本地函数

就是在函数里面套函数,很简单,就不写代码了。

  1. 交叉类型和联合类型。

对于本地类的访问类型,kotlin是不确定的。

实际上不是不确定,而是kotlin中没有,在TypeScript语言中,这是属于一个交叉类型

即Cloneable&Runnable 类型。

除此之外,还有Union类型,就是联合类型。

在表示一个类型可能是Cloneable或者是Runnable的时候,可以用下面的例子表示:

Cloneable| Runnable

这些交叉类型和联合类型虽然在kotlin中不支持,但是在TypeScript中支持,还有在现在如日中天的鸿蒙系统开发语言ArkTs语言中,也是支持的,多了解对面试还是有好处的。

数据类 data class

  1. data class的定义:

在类的前面加上data,很多人喜欢把它和JavaBean进行同等,但实际上差别很大。

下面会有一张图详细解释

data class Book(

    val id:Long,

    val name :String

){}    //     通常{}不会使用,如果要复写equal  toString方法的话,重新定义普通类是最好的选择。

对于data class 字段的属性只有Field,没有getter和setter

2.数据解构,component。

data class Book(

    val id:Long,

    val name :String

){}

fun function8(){

    val book = Book(1L,"")

    val id = book.component1()   // 通过component1获取第一个值

    val name = book.component2()  // 通过component2获取第二个值

   

}

3.如果一定要把data class 作为java Bean使用,需要解决两个问题

①主构造器

想要添加主构造器,可以在module下的build.gradle中添加插件

id("org.jetbrains.kotlin.plugin.noarg") version "1.3.60"

然后创建一个接口注释类

再在module级别的build.gradle中写入noArg,位置一定要和上面保持一致

noArg {

        invokeInitializers = true      //为true的时候,会执行init块。默认为false不执行init块

        annotations ("com.example.kotlintext.PoKo")

}

②去掉final才能被继承。目前的data类在kotlin中只能继承别人。

可以添加插件allopen,也是在module级别的build.gradle中添加:

plugins {

        id("com.android.application")

        id("org.jetbrains.kotlin.android")

        id("org.jetbrains.kotlin.plugin.noarg") version "1.3.60"

        id("org.jetbrains.kotlin.plugin.allopen") version "1.3.60"

}

noArg {

        invokeInitializers = true

        annotations ("com.example.kotlintext.PoKo")

}

allOpen {

        annotations ("com.example.kotlintext.PoKo")

}

添加上后,就能够去掉生成data类的final标记,getter和setter也会去掉final标记。

常见的高阶函数

五个常见的高阶函数,let、run、also、apply、use。

①let函数,默认it作为参数传入,返回lambda表达式的return:

class Bi(){

        var pocket = 0

}

fun main(){

        val bi = Bi()

        bi.let {

                println(it.pocket)

        }

}

②run函数,指定了receiver,返回lambda表达式的return:

class Bi(){

        var pocket = 0

}

fun main(){

        bi.run{

                println(run.pocket)

        }

}

③also函数,默认指定it,返回Unit:

class Bi(){

        var pocket = 0

}

fun main(){

        bi.also {

        it.pocket = 5

        println("also打印的结果是${it.pocket}")

        }

}

④apply函数,指定了receiver,返回Unit:

class Bi(){

        var pocket = 0

}

fun main(){

        bi.apply {

                this.pocket = 7

                println("apply打印的结果是${this.pocket}")

        }

}

⑤use函数,默认是it,返回的是lambda表达式的返回值

use函数可以自动关闭资源:

fun main() {

        val file = File("example.txt")

// 使用 use 读取文件内容

        file.bufferedReader().use { reader ->    //默认是it,这里进行了更改为reader

                val content = reader.readText()

                println(content)

        }

}

枚举类

  1. 枚举的定义

在class的前面加上关键字enum:

enum class State(){

}

  1. 枚举的属性

在枚举类中定义属性:

定义后,可以通过一些方法分别获取属性的值、序列号、所有内容

fun main(){

        println("??/")

        val r = State.Index1    //获取常量Index1

        println(r)

        val o = State.Index1.ordinal    //获取Index1的序列

        println(o)

        val all = State.entries      //获取枚举属性常量,类别是EnumEntries,也就是List;

        println(all)

        val question = State.values()    //获取枚举常量,返回的是一个数组

        println(question)

}

enum class State(){

        Index1,Index2    //定义枚举的属性

}

上面标红的地方直接打印的话,无法打印出详细的枚举属性,需要转换成List就可以了。

3.定义构造器

4.枚举实现接口

对于枚举的属性而言,可以有共同的实现方法,或者单独的实现方法。

会优先检测单独的方法,如果没有的情况下,再用公共的实现方法。

例如:

enum class State(val id:Int):Runnable{

        Index1(1){

        override fun run() {

                super.run()        //   Index1单独实现方法

        }

},Index2(2);

        override fun run() {

                TODO("Not yet implemented")     //公共实现方法

        }

}

枚举类继承自Enum,不能继承其他类

5.枚举类扩展函数

下面的例子是把枚举类获取下一个元素:

fun State.next():State{         //State类就是上面定义的枚举类

    return State.values().let {

        val nextOrdinal = (ordinal+1)% it.size

        it[nextOrdinal]

    }

}

6.也可以定义枚举类的条件分支

例如:

fun query(s:String):Any {

        val state = State.valueOf(s)

        return when (state) {

                State.Index1 -> { "" }

                State.Index2 -> { 8 }

        }

}

枚举类是有限个数,所以可以不加else。

7.枚举区间。

例如,我定义一个颜色枚举类,里面包含各种颜色。

我可以取一些颜色出来。:

enum class Color(){

    White,Black,Red,Blue,Yellow    //定义颜色的枚举

}

fun quColor(){

    val colorRange = Color.White..Color.Black

    val color = Color.Red

    println(color in colorRange)      //判断区间里面是否包含Red。

}

8.枚举的比较

fun main(){

        if (state<= State.Index2){

                println("Index1<=Index2")        

                //打印的结果是Index1<=Index2   因为比较的是ordinal序列号。

        }

}

密封类 sealed class

1.密封类的定义

在class定义的前面加上sealed。

密封类sealed本质是一种特殊的抽象类。

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

相比与枚举类,密封类的子类个数是有限的。

例如:

sealed class PlayState(){

   

}

2.密封类的构造器是私有的(即private)

如果改成public就会报错。

所以密封类在其他文件中是不可以被继承的。

3.密封类Vs枚举类

内联类 inline class

1.内联类的定义

在类定义的前面加上关键字inline

内联类需要有主构造器并且只有一个只读属性

inline class BoxString (val value: String){

}

2.内联类的属性

里面的属性不能有backing field,也就是,不能有属性的状态。

例如:

inline class BoxString (val value: String){

        var age: Int

                get() {

                        return 2

                }

                set(value) {

                        this.age = value

                }

}

3.内联类的继承结果

内联类可以实现接口,但是不能继承父类(防止父类有backing field),也不能被继承(防止复杂化)

4.内联类的编译优化

内联类只有在必要的时候转换成包装类型。

例如:

inline class BoxString (val value: String){

}

fun ca(){

        val box = BoxString("")   //编译优先转换成String类型

        val newBox = box.value * 200 // 使用String类型进行运算

        println(newBox) //这里会转换成包装类型

}

5.内联类模拟枚举

通常性能优化的时候,会考虑用内联类,因为内联类相比与枚举消耗的内存更少,运行更加顺畅。

6.内联类的限制

Moshi的序列化和反序列化

1.添加插件

2.添加depend依赖

3.moshi的序列化和反序列化

data class UserData(val count: String,val password:String){

}

fun main(){

        //moshi的序列化和反序列化

        val moshi = Moshi.Builder().build()

        val jsonAdapter = moshi.adapter(UserData::class.java)

        val userD= UserData("Aiden","asd790831")

        println(jsonAdapter.toJson(userD))     //moshi的序列化

        val json = """{"count":"An","password":"asd791125"}"""

        println(jsonAdapter.fromJson(json))            //moshi的反序列化

}

                                                        Gson的序列化和反序列化

Gson的序列化,是把kotlin对象转换成json或者xml格式。

反序列化则是把json或xml格式转换成对象。

序列化和反序列化有什么好处:

  1. 数据保存得更加久,因为用了序列化可以把对象的状态保存在文件中
  2. 在网络传输的时候把对象序列化为json或者XML利于网络传输。
  3. 不同的平台或者不同的编程语言的数据交换,需要使用通用格式,如json或xml。
  4. 远程调用,在分布式系统中,远程方法调用(RPC)或消息传递系统中,通常需要将参数对象序列化,以便发送给远程系统。反序列化则可以还原收到的对象,进行进一步处理。
  5. 简化数据传递:在Android开发中,Activity之间的数据传递、Bundle或Intent中传递复杂对象时,通常需要将对象序列化为可传递的格式,如Parcel。

使用Gson需要下面几个步骤:

①在文件的bulid.gradle中添加依赖

implementation ("com.google.code.gson:gson:2.8.9")

②使用Gson

data class UserData(val count: String,val password:String){

}

fun main(){

        val gson = Gson()

        val userData = UserData("An","asd791125")

        val serialization = gson.toJson(userData)    //把对象转换成json字符串,序列化

        println(serialization)                    //转换成序列化

        val json = """{"count":"An","password":"asd791125"}"""

           //把json转换成对象,反序列化

        val Deserialization = gson.fromJson(json,UserData::class.java)    

        println(Deserialization)

}

③Gson默认不会序列化为null的字段,如果要包含null,可以使用GsonBuilder配置

例如:

val gNull = GsonBuilder().serializeNulls().create()   //创建一个包含Null的Gson

④在kotlin中使用默认值,然而在Gson反序列时不会使用这些默认值。解决方法可以使用@SerializedName或者自定义反序列化

⑤如何自定义序列化和反序列化

fun main(){

    val cGson = GsonBuilder() //创建一个GsonBuilder对象,用于自定义Gson

        .registerTypeAdapter(

            Date::class.java,     //这行告诉Gson,只有遇到Date的时候需要使用自定义序列化

            Serializers.DateSerializer

        )

        .registerTypeAdapter(

            Date::class.java,

            Serializers.DateDeserializer

        )

        .create()

    val cPersonDate = PersonDate("Aiden","female",Date())

    println(cGson.toJson(cPersonDate))    //自定义序列化

    println(cGson.fromJson("""{"name":"An","gender":"male","brithDay":"2024-08-15  22:45"}""",PersonDate::class.java))

}

  //定义一个PersonData数据类,储存个人数据信息。birthDay信息是Date类

data class PersonDate(val name :String,val gender:String,val brithDay:Date)

//自定义序列化Date类型的数据

object  Serializers{

    //定义df常量,用于格式化日期

    private val df:DateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm")

    object DateDeserializer:JsonDeserializer<Date>{    //采用单例模式,存放序列化和反序列化

        override fun deserialize(  // 复写反序列化

            json: JsonElement?,

            typeOfT: Type?,

            context: JsonDeserializationContext?

        ): Date {

            return df.parse(json?.asString

                ?:throw Exception("Json element is null"))   //空类型安全,用elvis表达式,如果会空就throw Exception

        }

    }

    object DateSerializer: JsonSerializer<Date>{

        override fun serialize(       //复写序列化

            src: Date?,

            typeOfSrc: Type?,

            context: JsonSerializationContext?

        ): JsonElement {

            return JsonPrimitive(df.format(src))

        }

    }

}

                                         KotlinX Serialization的序列化和反序列化

1.添加插件:

1.9.22 是我的版本号,你可以改成自己的版本号。

版本号的查看,在tools->kotlin->configure kotlin plugin updates

2.添加依赖

3.使用序列化和反序列

记得给数据类添加注解:

@Serializable

data class Count (val name: String,val age :Int){

}

fun main(){

        //kotlinX .serialization 的序列化和反序列化

        val count = Count("",5)

        println(Json.encodeToString(count))         //kotlinX的序列化

        val dCount = """{"name":"","age":}"""

        println(Json.decodeFromString<Count>(dCount))     //kotlinX的反序列化

        }

}

泛型的基本概念

  1. 泛型的定义。

用于在编写类、接口、方法或函数时,使其能够处理不同类型的数据,而不必明确指定具体的数据类型。泛型通过引入类型参数来实现这一点,使代码更加灵活和可复用,同时还可以提供类型安全。

①如果是在函数定义成泛型

在fun后面<T>  

fun <T> type(a:T){

}

②类申明为泛型

在类名的后面加<T>

class Type<T>(){

}

2.泛型形参和实参

class Box<T>(var value: T)

fun main() {

        val intBox = Box(123) // T 被推断为 Int

        val stringBox = Box("Hello") // T 被推断为 String println(intBox.value) // 输出 123

        println(stringBox.value) // 输出 Hello

}

泛型约束

1.添加类型约束

fun <T:Comparable<T>> compare(a:T,b:T):T{

        return if (a>b) a else b     //约束T实现Comparable T

}

2.实现多个约束

fun <T> call (a:T,b:T)

        where T:Comparable<T>,T:() -> Unit{

        if (a>b) a()else b()

        }

T约束为Comparable,和参数为空,返回为Unit的函数类型。

3.多个泛型参数

fun <T,R> callMax(a:T,b:T):R    //传入泛型T和R R还作为返回值

        where T:Comparable<T>,T:()->R, //实现Comparable 接口,并且必须是一个函数类型

                R:Number{ // R必须是Number的子类

                return if (a>b )a() else b()

        }

泛型中违反型变约束

有时候,申明协变,即加上关键字out,应该是作为生产者 有返回值T。

但是出现了逆变点(在参数中)。

有下面几种情况,都是违反型变约束:

  1. 申明为协变(out),出现逆变点(参数中)
  2. 申明为逆变(in),出现协变点(返回值)

避免不了这种情况出现的时候,怎么使用。

可以在前面加上@UnsafeVariance,代表让编译器忽略,我们自己处理

保证安全的前提:

1.泛型参数协变,逆变点不能引起修改,即只读不写

2.泛型参数逆变,协变点不得外部获取,即只写不读

泛型的型变

泛型的型变分为三种。

不变、协变、逆变。

1.不变

默认泛型是不变的。即使两个类型之间存在子类型关系,泛型版本是没有任何关系的。

比如Int 和Number,Int是Number的子类:

class Box<T> (val value:T){

}

fun useBox(box:Box<Number>){

}

fun iu(){

        val intBox = Box(1) //进行类型推导intBox类型为Box<Int>+

        useBox(intBox) //会报错,需要的类型为Number,Int不可以。

}

2.协变

协变作为生产者,使用于返回值的场景

协变允许你使用一个泛型类型的子类型作为另一个泛型类型的参数。

使用out关键字来申明。

interface Book1

interface FaceBook:Book1

class BookStore<out T:Book1>{     //out 关键字

    fun getBook():T{     //这里的T,就是协变点

        TODO()

    }

}

fun foundBook(){

    val fBook = BookStore<FaceBook>()

    val book1:BookStore<Book1> = fBook      //向上兼容

    val fbook2:BookStore<FaceBook> =book1  //无法向下转,会报错。

    val book2 = book1.getBook()

    val book3:FaceBook = book1.getBook()  //getBook返回的是Book1,类型不匹配会报错。

}

协变可以向上兼容,不能向下

3.逆变

逆变作为消费者,适用于输入参数的场景

函数的参数类型为T,可以定义为逆变点。

使用关键字in来申明

逆变允许泛型类型的父类型传递给泛型的子类型

open class Garbage

class DryGar: Garbage()

class DustBin<in T:Garbage>{

    fun putGar(t:T){

    }

}

fun findGarbage(){

    val dustBin = DustBin<Garbage>()   //逆变之后dustBin成为了子类

    val dryGar:DustBin<DryGar> = dustBin  //可以向下兼容

    val d:DustBin<Garbage> =dryGar    //无法向上兼容

    val instanceG = Garbage()

    val instanceD = DryGar()

    dustBin.putGar(instanceG)   //子类可以替换所有的父类,所以没问题

    dustBin.putGar(instanceD)

    dryGar.putGar(instanceD)

    dryGar.putGar(instanceG )   //父类的dryGar不能传递给子类,所以需要类型是DryGar().

}

泛型中的星投影

星投影是一种处理泛型类型不确定或不关心类型参数的使用场景。

主要的应用场景有以下两点:

①忽略类型参数:

当你不关心泛型类型参数是什么类型的时候,可以使用星投影表示任何类型

②处理未知类型:

当你不知道泛型类型参数的具体类型的时候,可以使用星投影。

  1. 无限制星投影

假如你有一个泛型类,使用Box<*>可以安全读取Box的内容,无法写入。

无法写入是因为很多能导致类型不匹配出错。(除非写入的类型是null)

class Box<T>(val value: T)

fun printBox(box: Box<*>) {

    val value = box.value

    println(value) // value 的类型为 Any?,可以安全地读取

}

val intBox: Box<Int> = Box(42)

val stringBox: Box<String> = Box("Hello")

printBox(intBox)   // 输出 42

printBox(stringBox) // 输出 Hello

2.协变星投影

协变类型有一个上限类型,上限类型为定义的泛型的约束类型。

对于协变类型参数(声明为 out T),Box<out T> 的星投影类似于 Box<out Any?>,即你可以读取值,但不能写入值(除了 null)。

不能写入的原因也是因为很可能造成类型不匹配。

interface Source<out T> {

    fun nextT(): T

}

fun useSource(source: Source<*>) {

    val value = source.nextT()

    println(value) // value 的类型为 Any?

}

3.逆变星投影

逆变类型有个下限,下限类型为nothing

对于逆变类型参数(声明为 in T),Box<in T> 的星投影类似于 Box<in Nothing>,即你可以写入 null,但无法安全读取。

interface Consumer<in T> {

    fun consume(t: T)

}

fun useConsumer(consumer: Consumer<*>) {

    // 无法向 consumer 传递任何非 null 的值,因为类型不确定

    // consumer.consume(Any()) // 错误

    consumer.consume(null) // 可以传递 null

}

星投影的使用范围:

①不能直接或者间接应用在属性或者函数上。

QueryMap<String,*>()  或   maxOf<*>(1,3)

②可以使用在类型描述的场景。

Val queryMap:QueryMap<*,*>

③特殊在类型构造中,泛型参数中还需要一个泛型参数,可以使用*

HashMap<String,List<*>>()

泛型的类型擦除和内联特化

Kotlin和java中的泛型实际上是伪泛型,在编译时会擦除类型。

运行时没有实际类型生成。

例如:

fun <T> type(t:T) {

    val jcalss = T:class.java    //编译时类型擦除了

}

可以使用内联特化解决这个问题。

因为内联特化,可以在使用的时候,才运行里面的代码。

这样泛型就会有具体的类型

例如:

inline    fun <reified T> t(t:T) {

        val jcalss = T::class.java   //调用的时候,T会变成实际的类

}

内联特化实际应用:

可以简化Gson传输的时候只用传入Json,省略掉反射部分。

反射的类型,可以用类型推导或者是泛型参数。

模仿Scala中的self  Type

//利用泛型  模拟Scala中的Self Type

typealias OnConfirm = ()->Unit

typealias OnCancel = ()->Unit

private val EmptyFunction={}

open class Notification(

    val title:String,

    val content:String){

}

class ConfirmNotification(

    title: String,

    content:String,

    val onConfirm: OnConfirm,

    val onCancel: OnCancel

):Notification(title, content)

interface SelfType<Self>{

    val self:Self

        get() = this as Self

}

open  class NotificationBuilder<Self:NotificationBuilder<Self>>:SelfType<Self> {

    //这里把泛型Self的类型加以限制为NotificationBuilder<Self>,这样只能传入其子类,保证类型安全。

    protected var title: String = ""

    protected var content:String = ""

    fun title(title: String):Self{

        this.title = title

        return self

    }

    fun content(content: String):Self{

        this.content = content

        return self

    }

    open fun build() = Notification(this.title,this.content)

}

class ConfirmNotificationBuilder:NotificationBuilder<ConfirmNotificationBuilder>(){

    private var onConfirm:OnConfirm = EmptyFunction

    private var onCancel:OnCancel = EmptyFunction

    fun onConfirm(onConfirm: OnConfirm):ConfirmNotificationBuilder{

        this.onConfirm = onConfirm

        return this

    }

    fun onCancel(onCancel: OnCancel):ConfirmNotificationBuilder{

        this.onCancel = onCancel

        return this

    }

    override fun build(): ConfirmNotification {

        return  ConfirmNotification(title,content,onConfirm,onCancel)

    }

}

fun main() {

    ConfirmNotificationBuilder()

        .title("Hello")

        .onCancel {

            println("onCancel")

        }.content("World")

        .onConfirm {

            println("onConfirmed")

        }

        .build()

        .onConfirm()

}

简化View model和model的交流

通过对模型管理的自动化、类型安全的依赖注入、以及线程安全的操作.

简化了应用中model与 ViewModel 的交互,提升了代码的可读性和可维护性。

例如:

object Models{

    //ConcurrentHashMap建立键值对,键是class::,值是AbsModel.

    private val modelMap = ConcurrentHashMap<Class<out AbsModel>,AbsModel>()

    //this.java是KClass<T>的扩展属性,用于于KClass关联的java Class对象

    fun AbsModel.register(){

        modelMap[this.javaClass]= this

    }

    //this.javaClass是任何kotlin对象的扩展方法,用于获取java Class对象

    fun <T:AbsModel> KClass<T>.get():T{

        return modelMap[this.java] as T

    }

}

/*

定义所有Model的基类,每个子类实现的时候,都会执行init中的注册功能.

ConcurrentHashMap 是一种适用于高并发环境的线程安全映射数据结构。

它通过分段锁定和无锁操作等机制,实现了在多线程访问时的高效性和一致性。

*/

abstract class AbsModel{

    init {

        Models.run {

            register()

        }

    }

}

class DatabaseModel:AbsModel(){

    fun query(sql:String):Int=0

}

class NetworkModel:AbsModel(){

    fun get(url:String):String="""{"code":0}"""

}

/*

ReadOnlyProperty是用于val,只读属性。

<R,T>需要传入两个参数,第一个参数是持有者,第二个参数是输出类型。

*/

class  ModelDelegate<T:AbsModel>(val Kclass:KClass<T>):ReadOnlyProperty<Any,T>{

    //KClass是kotlin中的class,和java中的class相比,作用域不同。

    override fun getValue(thisRef: Any, property: KProperty<*>): T {

        return Models.run { Kclass.get() }

    }

}

//初始化model

fun initModels(){

    DatabaseModel()

    NetworkModel()

}

/*

简化拿到类型反射。

使用内联特化防止类型擦除。

*/

inline fun <reified T:AbsModel> modelOf():ModelDelegate<T>{

    return ModelDelegate(T::class)

}

class MainViewModel{

    val databaseModel by modelOf<DatabaseModel>()

    val networkModel by modelOf<NetworkModel>()

}

fun main(){

    initModels()

    val mainActivity = MainViewModel()

    /*这里的let(::println),相当于

    mainActivity.databaseModel.query("").let({

        println(it)

    })*/

    mainActivity.databaseModel.query("select * from mysql.user").let(::println)

    mainActivity.networkModel.get("https://wwww...").let(::println)

}

恭喜你,学到这里,你已经掌握了kotlin的百分之80的知识点和用法了!!!

只需再学习kotlin(下),就能把所有的知识学完了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值