Kotlin 延迟初始化 lateinit 和密封类
首先新建一个 LearnDemo 作为例子来说明。
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