欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Kotlin 延迟初始化 lateinit 和密封类

程序员文章站 2022-10-03 17:15:52
文章目录对变量延迟初始化使用密封类优化代码首先新建一个 LearnDemo 作为例子来说明。首先是 Activity 代码:class SecondActivity : AppCompatActivity() { private val textData = ArrayList() private var mAdapter: TextAdapter? = null private val handler: Handler = Handler()...

首先新建一个 LearnDemo 作为例子来说明。
Kotlin 延迟初始化 lateinit 和密封类
70)
首先是 Activity 代码:

class SecondActivity : AppCompatActivity() {
    private val mData = ArrayList<DataBean>()
    private var mAdapter: TextAdapter? = null

    private val handler: Handler = Handler()

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

    private fun initRecyclerView() {
        val layoutManager = LinearLayoutManager(this)
        recyclerView.layoutManager = layoutManager
        mAdapter = TextAdapter(mData)
        recyclerView.adapter = mAdapter
    }

    private fun initData() {
        //使用一个 Handler 模仿延时加载数据
        handler.postDelayed(Runnable {
            for (i in 0..10) {
                if (i % 2 == 0) {
                    val dataBean1 = DataBean(i.toString(), DataBean.TYPE1)
                    mData.add(dataBean1)
                } else {
                    val dataBean2 = DataBean(i.toString(), DataBean.TYPE2)
                    mData.add(dataBean2)
                }
            }
            mAdapter?.notifyDataSetChanged()
        }, 3000)
    }
}

这里使用了一个 Handler 对象模仿延时加载数据,再调用了 Adapter 的 notifyDataSetChanged() 方法通知数据刷新。

Activity 的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SecondActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

Adapter 的代码:

class TextAdapter(private val data: List<DataBean>) :
    RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    class Type1ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.tv_text)
    }

    class Type2ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.tv_text)
    }

    override fun getItemViewType(position: Int): Int {
        val dataBean = data[position]
        return dataBean.type
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
        if (viewType == DataBean.TYPE1) {
            val view =
                LayoutInflater.from(parent.context).inflate(R.layout.item_text_type1, parent, false)
            Type1ViewHolder(view)
        } else {
            val view =
                LayoutInflater.from(parent.context).inflate(R.layout.item_text_type2, parent, false)
            Type2ViewHolder(view)
        }

    override fun getItemCount() = data.size

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val text = data[position]
        when (holder) {
            is Type1ViewHolder -> holder.textView.text = text.content
            is Type2ViewHolder -> holder.textView.text = text.content
            else -> throw  IllegalArgumentException()
        }
    }
}

注意上述代码中,根据 viewType 来创建了不同的布局,首先我们定义了 Type1ViewHolder 和 Type2ViewHolder 这两个 ViewHolder,分别用于缓存 item_text_type1 和 item_text_type2 布局中的控件。然后要重写 getItemViewType() 方法,并在这个方法中返回当前 position 对应的消息类型。

Adapter 中 item 的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/tv_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal" />

</LinearLayout>

对变量延迟初始化 lateinit

在例子中,我们将 adapter 设置为了全局变量,但是它的初始化工作是在 onCreate() 方法中进行的,因此不得不先将 adapter 赋值为 null,同时把它的类型声明成 TextAdapter?。

虽然我们会在 onCreate() 方法中对 adapter进行初始化,但是尽管是在 adapter 已经初始化以后,调用 adapter 的任何方法仍然要进行判空处理才行,否则编译无法通过。

当代码中有了越来越多的全局变量实例时,这个问题就会变得越来越明显,到时候可能必须的编写大量额外的判空代码。

这个时候,我们就会使用 lateinit 关键字对全局变量进行延迟初始化。

优化 Activity 中的代码:

class SecondActivity : AppCompatActivity() {
    private val mData = ArrayList<DataBean>()
    private lateinit var mAdapter: TextAdapter

    private val handler: Handler = Handler()

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

    private fun initRecyclerView() {
        val layoutManager = LinearLayoutManager(this)
        recyclerView.layoutManager = layoutManager
        if (!::mAdapter.isInitialized) {
            mAdapter = TextAdapter(mData)
        }
        recyclerView.adapter = mAdapter
    }

    private fun initData() {
        //使用一个 Handler 模仿延时加载数据
        handler.postDelayed(Runnable {
            for (i in 0..10) {
                if (i % 2 == 0) {
                    val dataBean1 = DataBean(i.toString(), DataBean.TYPE1)
                    mData.add(dataBean1)
                } else {
                    val dataBean2 = DataBean(i.toString(), DataBean.TYPE2)
                    mData.add(dataBean2)
                }
            }
            mAdapter.notifyDataSetChanged()
        }, 3000)
    }
}

::mAdapter.isInitialized 用于判断 mAdapter 变量是否已经初始化,这是固定的写法。我们对结果取反,如果没有初始化,那么就立即对 mAdapter 变量进行初始化,否则什么都不做。

使用密封类优化代码

作用和使用方法

很多时候密封类可以帮助你写出更加规范和安全的代码。首先了解一下密封类具体的作用,下面由一个简单的例子来说明。

新建一个 Kotlin 文件,文件名就叫 Result.kt,然后在这个文件中编写如下代码:

interface Result
class Success(val msg: String) : Result
class Failure(val error: Exception) : Result

这里定义了一个 Result 接口,用于表示某个操作的执行结果,接口中不用编写任何内容。然后定义了两个类去实现 Result 接口:
一个 Success 类用于表示成功时的结果。
一个 Failure 类用于表示失败时的结果。

接下来可以在 Activity 中定义一个 getResultMsg() 方法,用于获取最终执行结果的信息,代码如下:

    fun getResultMsg(result: Result) = when (result) {
        is Success -> result.msg
        is Failure -> result.error.message
        else -> throw IllegalArgumentException()
    }

getResultMsg() 方法中接收一个 Result 参数。我们通过 when 语句来判断:如果 Result 属于 Success,那么就返回成功的消息;如果 Result 属于 Failure,那么就返回错误信息。到目前为止,代码都是没有问题的,但是接下来我们还不得不再编写一个 else 条件,否则 Kotlin 编译器会认为这里缺少条件分支,代码将无法编译通过。但实际上 Result 的执行结果只可能是 Success 或者 Failure,这个 else 条件是永远走不到的,所以我们直接抛出了一个异常。

另外,编写 else 条件还有一个潜在的风险。如果我们新增了一个 Unknown() 类并实现 Result 接口,用于表示未知的执行结果,但是忘记在 getResultMsg() 方法中添加相应的条件分支,编译器在这种情况下是不会提醒我们的,而是会在运行的时候进入 else 条件里面,从而抛出异常并导致程序崩溃。

这时候密封类就可以解决这个问题。密封类的关键字时 sealed class,用法非常简单,只需要修改代码如下:

sealed class Result
class Success(val msg: String) : Result()
class Failure(val error: Exception) : Result()

可以看到,代码并没有什么太大变化,只是将 interface 关键字改成了 sealed class。另外,由于密封类是一个可继承的类,因此在继承它的时候需要在后面加上一对括号。

现在可以发现在 getResultMsg() 方法中的 else 条件已经不需要了,如下所示:

    fun getResultMsg(result: Result) = when (result) {
        is Success -> result.msg
        is Failure -> result.error.message
    }

当我们在 when 语句中传入一个密封变量作为条件时,Kotlin 编译器会自动检查该密封类有哪些子类,并强制要求你将每一个子类对应的条件全部处理。这样就可以保证,即使没有编写 else 条件,也不可能会出现漏写条件分支的情况。

还要注意的是,密封类及其所有子类只能定义在同一个文件的顶层位置,不能嵌套在其他类中,这是被密封类底层的实现机制所限制的。

结合 Adpter 中的 ViewHolder 来使用

观察 TextAdapter 中现在的代码,你会发现 onBindViewHolder() 方法中就存在一个没有实际作用的 else 条件,只是抛出一个异常而已。对于这部分代码,我们就可以借助密封类来进行优化。

新建一个 MyViewHolder.kt 文件,加入如下代码:

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

class Type1ViewHolder(view: View) : MyViewHolder(view) {
    val textView: TextView = view.findViewById(R.id.tv_text)
}

class Type2ViewHolder(view: View) : MyViewHolder(view) {
    val textView: TextView = view.findViewById(R.id.tv_text)
}

这里我们定义了一个密封类 MyViewHolder,并让它继承自 RecyclerView.ViewHolder(),然后让 Type1ViewHolder 和 Type2ViewHolder 继承自 MyViewHolder。这样就相当于密封类 MyViewHolder 只有两个已知子类,因此在 when 语句中只要处理这两种情况的条件分支即可。

修改 TextAdapter 中的代码,如下所示:

class TextAdapter(private val data: List<DataBean>) :
    RecyclerView.Adapter<MyViewHolder>() {

    override fun getItemViewType(position: Int): Int {
        val dataBean = data[position]
        return dataBean.type
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
        if (viewType == DataBean.TYPE1) {
            val view =
                LayoutInflater.from(parent.context).inflate(R.layout.item_text_type1, parent, false)
            Type1ViewHolder(view)
        } else {
            val view =
                LayoutInflater.from(parent.context).inflate(R.layout.item_text_type2, parent, false)
            Type2ViewHolder(view)
        }

    override fun getItemCount() = data.size

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val text = data[position]
        when (holder) {
            is Type1ViewHolder -> holder.textView.text = text.content
            is Type2ViewHolder -> holder.textView.text = text.content
        }
    }
}

这里我们将 Recyclerview.Adapter 的泛型指定成刚刚定义的密封类 MyViewHolder,这样 onBindViewHolder() 方法传入的参数就变成了 MyViewHolder。然后我们只要在 when 语句当中处理 Type1ViewHolder 和 Type2ViewHolder 这两种情况就可以了,不再需要 else,这种 Recyclerview 适配器的写法更加规范也更加推荐。

本文地址:https://blog.csdn.net/csdn1225987336/article/details/107377861

相关标签: Kotlin