Kotlin:延迟初始化和密封类

2021-05-27  本文已影响0人  jingkaiqaq
kotlin&android.png

前言

什么?我屮艸芔茻,兄弟们竟然把我前两篇写的变量和函数、标准函数和静态方法给看完了,欢迎打怪升级来到我的第三篇关于Kotlin输出文章,想必前两篇文章已经让你已经都掌握了~😬😬😬😬😬😬😬😬😬😬
下面有请各位大佬观看通俗易懂Kotlin系列之第三篇文章——延迟初始化和密封类
发车了兄弟们GO GO GO ~ 😬

1:延时初始化

首先我给出一段代码

    class MainActivity : AppCompatActivity(), View.OnClickListener {
        private var adapter: Adapter? = null
        override fun onCreate(savedInstanceState: Bundle?) {
            ...
            adapter = Adapter(list)
            ...
        }
        override fun onClick(v: View?) {
            ...
            adapter?.todoSomeThing(list.size - 1)
            ...
        }
    }

上述代码中adapter是一个全局变量,由于需要在onCreate中初始化才能使用,所以不得不先将adapter赋值为null,同时把它的类型声明成MsgAdapter?,并且最操蛋的问题是我必须在任何一个使用的地方?.对adapter进行判空,那么一旦我们的项目中使用过多的全局变量是不是我们需要在任何一个调用的地方都进行判空? 就这?啊?就这?Kotlin就这?NO NO NO 当然不会,Kotlin当然给出了解决方式——使用是 lateinit关键字对全局变量进行延时初始化 ,意为告诉Kotlin编译器我会晚些时候对这个变量进行初始化,这样就不用再一开始的时候初始化为null了~!
实现代码如下:

    class MainActivity : AppCompatActivity(), View.OnClickListener {
        private lateinit var adapter:Adapter
        override fun onCreate(savedInstanceState: Bundle?) {
            ...
            adapter = Adapter(List)
            ...
        }
        override fun onClick(v: View?) {
            ...
            adapter.todoSomeThing(List.size - 1)
            ...
        }
    }
请注意再使用lateinit关键字的时候一定要确保你的对象已经被初始化了,否则就会抛出UninitializedPropertyAccessException异常 image.png

另外我们可以对对象判断是否进行了初始化,这样就可以避免重复初始化对象

    if (!::adapter.isInitialized) {
        adapter = MsgAdapter(msgList)
    }

2:使用密封类优化代码

他也可以帮助我们写出更加安全规范的代码,通常来说密封类可以结合RecyclerView适配器中的ViewHolder一起使用
下边我们会为了实现如下效果编写一个简单的RecyclerView聊天界面


image.png

代码如下

class MesActivity : AppCompatActivity(), View.OnClickListener {
    private val mesDatas = ArrayList<MesData>()
    private lateinit var mesAdapter: MesAdapter
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_mes)
        initMes();
        val layoutManager = LinearLayoutManager(this)
        recycler_view.layoutManager = layoutManager
        if (!::mesAdapter.isLateinit) {
            mesAdapter = MesAdapter(mesDatas)
        }
        recycler_view.adapter = mesAdapter
        send_mes_btn.setOnClickListener(this)

    }

    private fun initMes() {
        mesDatas.add(MesData("第一次听这首歌我初三,现在我都快30了,人在变,但是这首歌给我的感觉还是那么温暖", MesData.TYPE_LEFT))
        mesDatas.add(
            MesData(
                "周华健的声音沧桑却也潇洒,还不失温暖。最早对于流行乐的印象应该是很小的时候老爸对我唱他的《亲亲我的宝贝》",
                MesData.TYPE_RIGHT
            )
        )
        mesDatas.add(MesData("想着有机会一定要带老爸去听一次他的现场。", MesData.TYPE_LEFT))
        mesDatas.add(MesData("我曾经去过", MesData.TYPE_RIGHT))
        mesDatas.add(MesData("经典就是多年后再听到这首歌还是会忍不住单曲循环", MesData.TYPE_LEFT))

    }

    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.send_mes_btn -> sendMes()
        }
    }
    /**
     * 发送数据
     */
    private fun sendMes() {
        if (input_mes_ed.text.toString().isNotEmpty()) {
            mesDatas.add(MesData(input_mes_ed.text.toString().trim(), 1))
            mesAdapter.notifyItemInserted(mesDatas.size - 1)
            recycler_view.scrollToPosition(mesDatas.size - 1)
            input_mes_ed.setText("")
        }

    }
}
class MesAdapter(private val mesList: List<MesData>) :
    RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    inner class LeftViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val leftTv: TextView = view.findViewById(R.id.left_item_tv)
    }

    inner class RightViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val rightTv: TextView = view.findViewById(R.id.right_item_tv)
    }


    override fun getItemViewType(position: Int): Int {
        return mesList[position].type
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = if (viewType == TYPE_LEFT) {
        val view: View = LayoutInflater.from(parent.context)
            .inflate(R.layout.left_mes_layout, parent, false)
        LeftViewHolder(view)
    } else {
        val view: View = LayoutInflater.from(parent.context)
            .inflate(R.layout.right_mes_layout, parent, false)
        RightViewHolder(view)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val mes = mesList[position]
        when (holder) {
            is RightViewHolder -> holder.rightTv.text = mes.content
            is LeftViewHolder -> holder.leftTv.text = mes.content
            else -> throw IllegalArgumentException()
        }
    }

    override fun getItemCount() = mesList.size
}

请注意上边代码的两个Viewhodler, Kotlin中使用inner class关键字来定义内部类

data class MesData(val content: String, val type: Int) {
    companion object {
        const val TYPE_LEFT = 0
        const val TYPE_RIGHT = 1
    }
}

请注意上边代码,Koltin中 定义常量的关键字是const,注意只有在单例类、companion object或顶层方法中才可以使用const关键字
xml代码和.9图片我就不放了,这里默认为阅读者都为中级以上开发人员,我们只关注Kotlin的代码

ok实现了以上代码之后我们基本可以复现类似微信的对话形式
下边我们看一下怎么使用密封类
首先比那些如下代码

interface Result
class Success(val mes: String) : Result
class Failure(val err: Exception) : Result

创建一个接口,并且两个类success和failure分别实现result接口
接下来我们调用Result接口:

fun getResultData(result: Result) = when (result) {
    is Success -> println("isSuccess")
    is Failure -> println("isFailure")
    else ->throw IllegalArgumentException()
}
没有编写else的效果.png

可以看到我们在接受Result接口的时候必须编写 一个else 这但是我们的代码并没有第三中可能性所以我们在这里直接抛出了一个异常,只是为了满足Kotlin编译器的语法检查而已。
另外,编写else条件还有一个潜在的风险。如果我们现在新增了一个新类并实现Result接口,用于表示未知的执行结果,但是忘记在getResultMsg()方法中添加相应的条件分支,编译器在这种情况下是不会提醒我们的,而是会在运行的时候进入else条件里面,从而出现问题。

如果使用Kotlin密封类就可以解决上边的问题
密封类的关键字是sealed class,它的用法同样非常简单,我们将Result接口改造成密封类的写法:

sealed class Result
class Success(val mes: String) : Result()
class Failure(val err: Exception) : Result()
看一下没有编写Failure的情况 没有编写Failure的情况.png

编译器会直接报错 按下option+回车 直接可以提示我们添加没有写入的分支

fun getResultData(result: Result) = when (result) {
    is Success -> println("isSuccess")
    is Failure -> TODO()
}

when语句在传入一个密封类变量作为条件的时候,编译器会自动检查这个密封类有哪些分支,有没有分支没有实现,而且强制你来处理每一个分支,这样我们在添加其他分支的时候就不会丢下任何一个,也不会出现问题

请注意:密封类必须及其所有的子类都必须定义在顶层文件中

上边说过密封类可以结合RecyclerView适配器中的ViewHolder一起使用,
可以看到我们的adapter中就存在一个else语句

新建一个MesViewHodler类

sealed class MesViewHodler(view: View) : RecyclerView.ViewHolder(view)

class leftMesViewHolders(view: View) : MesViewHodler(view) {
    val leftTv: TextView = view.findViewById(R.id.left_item_tv)
}

class rightMesViewHolders(view: View) : MesViewHodler(view) {
    val rightTv: TextView = view.findViewById(R.id.right_item_tv)
}

然后修改一下adapter相关的代码

class MesAdapter(private val mesList: List<MesData>) :
    RecyclerView.Adapter<MesViewHodler>() {

    override fun getItemViewType(position: Int): Int {
        return mesList[position].type
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = if (viewType == TYPE_LEFT) {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.left_mes_layout, parent, false)
        leftMesViewHolders(view)
    } else {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.right_mes_layout, parent, false)
        rightMesViewHolders(view)
    }

    override fun onBindViewHolder(holder: MesViewHodler, position: Int) {
        val mes = mesList[position]
        when (holder) {
            is rightMesViewHolders -> holder.rightTv.text = mes.content
            is leftMesViewHolders -> holder.leftTv.text = mes.content
        }
    }

    override fun getItemCount() = mesList.size
}

我们将之前的Recyclerview.ViewHolder 改成MesViewHodler,另外在when语句中只需要处理相关的分支就可以了,else就可以不用处理了

延迟初始化和密封类到此结束了,不知道你有没有掌握牢固呢
点赞双击评论666
下篇文章来学习扩展函数和运算符重载啦~~~
有什么问题欢迎留言指出😜😜😜😜😜😜😜😜😜😜

上一篇下一篇

猜你喜欢

热点阅读