Android开发基础——Kotlin:延迟初始化和密封类

对变量延迟初始化

首先看一段之前的代码:

class MainActivity : AppCompatActivity(), View.OnClickListener {

    private val msgList = ArrayList()

    private var adapter:MsgAdapter ?= null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initMsg()
        val layoutManager = LinearLayoutManager(this)
        recyclerView.layoutManager = layoutManager
        adapter = MsgAdapter(msgList)
        recyclerView.adapter = adapter
        send.setOnClickListener(this)
    }

    override fun onClick(v: View?) {
        when (v) {
            send -> {
                val content = inputText.text.toString()
                if (content.isNotEmpty()) {
                    val msg = Msg(content, Msg.TYPE_SENT)
                    msgList.add(msg)
                    adapter?.notifyItemInserted(msgList.size - 1)
                    recyclerView.scrollToPosition(msgList.size - 1)
                    inputText.setText("")
                }
            }
        }
    }

    private fun initMsg() {
        val msg1 = Msg("Hello,guy:", Msg.TYPE_RECEIVED)
        msgList.add(msg1)
        val msg2 = Msg("Hello,who is that?", Msg.TYPE_SENT)
        msgList.add(msg2)
        val msg3 = Msg("This is Tom. Nice to meet you.", Msg.TYPE_RECEIVED)
        msgList.add(msg3)
    }
}

上面的变量中,adapter为全局变量,但是其初始化工作是在onCreate方法中进行的,因此不得不先在adapter赋值为null,同时将其类型声明为MsgAdapter?。

而虽然在onCreate方法中对adapter进行初始化,同时能确保onClick方法必然在onCreate方法之后才会调用,但是在onClick方法中调用adapter的任何方法时仍然要进行判空处理,否则编译不能通过。

而对全局变量进行延迟初始化可以解决该问题。

延迟初始化使用lateinit关键字,其会通知Kotlin编译器在晚些时候对该变量进行初始化,这样就不用在一开始就将之赋值null了。

而采用延迟初始化的写法,上面代码可以修改为:

class MainActivity : AppCompatActivity(), View.OnClickListener {

    private val msgList = ArrayList()

    private lateinit var adapter:MsgAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initMsg()
        val layoutManager = LinearLayoutManager(this)
        recyclerView.layoutManager = layoutManager
        adapter = MsgAdapter(msgList)
        recyclerView.adapter = adapter
        send.setOnClickListener(this)
    }

    override fun onClick(v: View?) {
        when (v) {
            send -> {
                val content = inputText.text.toString()
                if (content.isNotEmpty()) {
                    val msg = Msg(content, Msg.TYPE_SENT)
                    msgList.add(msg)
                    adapter.notifyItemInserted(msgList.size - 1)
                    recyclerView.scrollToPosition(msgList.size - 1)
                    inputText.setText("")
                }
            }
        }
    }

    private fun initMsg() {
        val msg1 = Msg("Hello,guy:", Msg.TYPE_RECEIVED)
        msgList.add(msg1)
        val msg2 = Msg("Hello,who is that?", Msg.TYPE_SENT)
        msgList.add(msg2)
        val msg3 = Msg("This is Tom. Nice to meet you.", Msg.TYPE_RECEIVED)
        msgList.add(msg3)
    }
}

 在上面的代码中,adapter变量前多了lateinit关键字,这样就不用在一开始的时候为变量,同时类型声明也就可以改为MsgAdapter了。而由于MsgAdapter是不可为空的类型,因此用户不需要再onClick方法中进行判空处理,直接调用adapter的相关方法即可。

而同时需要注意的是,如果用户lateinit声明的变量没有初始化的情况下就直接使用,那么程序就会直接崩溃。即在对全局变量使用了lateinit关键字时,要确保在调用之前完成了初始化,否则程序运行状况是未知的。

而用户还可以通过代码来判断一个全局变量是否已经完成了初始化,此时在某些时候就能够有效避免重复对某个变量进行初始化操作:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initMsg()
        val layoutManager = LinearLayoutManager(this)
        recyclerView.layoutManager = layoutManager
        if (!::adapter.isInitialized) {
            adapter = MsgAdapter(msgList)
        }
        recyclerView.adapter = adapter
        send.setOnClickListener(this)
    }

上边的语法中,::adapter.isInitialized可用于判断adapter变量是否已经初始化。虽然这种写法比较奇怪,但确实如此。

使用密封类优化代码

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

上面的代码中定义了Result接口,然后定义了两个类实现Result接口。

然后定义了getResultMsg方法,用于获取最终执行结果的信息:

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

getResultMsg方法中接收一个Result参数,然后是一个条件语句,只是最后还需要else部分进行判定,否则Kotlin编译器会认为语句不完整,而无法编译通过。也就是说else部分实际上是多余的。

并且,如果后来又有一个类实现了Result,那么在该条件语句下就会直接报错,这肯定是不可以的。

而使用Kotlin的密封类可以解决这个问题。

密封类的关键字是sealed class,其用法也很简单:

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

上面的代码并没有多大变化,只是在继承时Result后多了(),这是因为密封类是一个可继承的类。

而此时getResultMsg方法就可以修改为:

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

这表示当在when语句中传入一个密封类变量作为条件时,Kotlin编译器会自动检查该密封类有哪些子类,并强制要求将每一个子类所对应的条件全部处理。这样可以保证,即使没有编写else条件,也不可能会出现漏写条件分支的情况。而如果新增Reult的实现类,上面的方法便会报错,此时必须要多增加该实现类的分支。

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

由于密封类通常可以结合RecyclerView适配器中的ViewHolder一起使用。因此考虑下边的代码:

class MsgAdapter(val msgList: List):
    RecyclerView.Adapter(){

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

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

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

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = if (viewType == Msg.TYPE_RECEIVED) {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.msg_left_item, parent, false)
        LeftViewHolder(view)
    } else {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.msg_right_item, parent, false)
        RightViewHolder(view)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val msg = msgList[position]
        when (holder) {
            is LeftViewHolder -> holder.leftMsg.text = msg.content
            is RightViewHolder -> holder.rightMsg.text = msg.content
            else -> throw IllegalArgumentException()
        }
    }
    override fun getItemCount() = msgList.size
}

上面的代码中,MsgViewHolder的onBindViewHolder方法中就存在一个没用到的else部分,因此可以将上面的代码修改为:

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

class LeftViewHolder(view:View):MsgViewHolder(view) {
    val leftMsg: TextView = view.findViewById(R.id.leftMsg)
}

class RightViewHolder(view:View):MsgViewHolder(view) {
    val rightMsg: TextView = view.findViewById(R.id.rightMsg)
}

首先便是定义密封类,使之继承自RecyclerView.ViewHolder,然后使LeftViewHolder和RightViewHolder继承自MsgViewHolder,然后便是:

class MsgAdapter(val msgList: List):
    RecyclerView.Adapter(){

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

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = if (viewType == Msg.TYPE_RECEIVED) {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.msg_left_item, parent, false)
        LeftViewHolder(view)
    } else {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.msg_right_item, parent, false)
        RightViewHolder(view)
    }

    override fun onBindViewHolder(holder: MsgViewHolder, position: Int) {
        val msg = msgList[position]
        when (holder) {
            is LeftViewHolder -> holder.leftMsg.text = msg.content
            is RightViewHolder -> holder.rightMsg.text = msg.content
        }
    }
    override fun getItemCount() = msgList.size
}

上面的代码只是在MsgAdapter使RecyclerView.Adapter的泛型为MsgViewHolder,然后在onBindViewHolder方法中修改参数类型和else部分语句,也就和之前的形式一致了。

你可能感兴趣的:(#,Android开发基础,android,kotlin,延迟初始化,密封类,adapter)