我既是Kotlin,Java也不是Android开发方面的专家,我正在尝试学习“ Kotlin本机方式”来在Android中做事。具体来说,如何在Android界面/扩展中正确处理空性。
下面是一个使用RecyclerView
的工作示例的摘要,它似乎在滥用Kotlin的可空性范式。
首先是Adapter类:
class PersonListAdapter(private val list: ArrayList<Person>,
private val context: Context) : RecyclerView.Adapter<PersonListAdapter.ViewHolder>() {
注意:此示例使用ArrayList<>
而不是ArrayList<>?
。
然后在主要活动中,它具有以下片段:
private var adapter: PersonListAdapter? = null
private var personList: ArrayList<Person>? = null
private var layoutManager: RecyclerView.LayoutManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
personList = ArrayList<Person>()
layoutManager = LinearLayoutManager(this)
adapter = PersonListAdapter(personList!!, this)
请注意以下两点:(1) personList
是ArrayList<>?
(请注意可空性),以及(2)列表随后用{ {1}} ,基本上使我们摆脱了在Kotlin中安全使用可空对象的整个想法。
我不明白作者为什么这么做。为什么不将列表最初简单地分配给非空值呢?例如,类似:
personList!!
或者,如果这不容易完成,为什么不使用可为空的列表初始化适配器呢?我认为后者无法完成,因为我们正在扩展private var personList = ArrayList<Person>()
,但是我想问一下确定。我在想以下内容:
RecyclerView.Adapter
(请注意,用class PersonListAdapter(private val list: ArrayList<Person>?,
private val context: Context) : RecyclerView.Adapter<PersonListAdapter.ViewHolder>() {
代替ArrayList<Person>?
。)
还是有更好的范例?
在Android界面,扩展以及我们在Java代码深层基础上构建的其他情况下,在Kotlin中处理空性的正确方法是什么?
我想 确实 尽可能避免使用ArrayList<Person>
。虽然我是这个领域的新手,但在我看来!!
的重点是avoid the billion dollar mistake,而使用?
本质上是在说:“不,我想要那十亿美元错误返回”,不是吗?
答案 0 :(得分:1)
该代码的作者只是没有想到,或者他们还是Kotlin的初学者。
只要您需要实例化的对象在其构造函数中不需要有效的Context Object,就可以在任何方法外部对其进行实例化。我知道,当我刚开始进行Android开发时,发现某些事情只能在onCreate()
中进行实例化,因此我走了一条安全路线,并在那里实例化了所有内容。这可能是代码编写者正在做的事情,并将这种习惯转移给了Kotlin。
现在,处理延迟初始化变量的更好方法通常是使用lateinit
修饰符:
private lateinit var personList: ArrayList<Person>
,然后在onCreate()
中实例化它。它不再可以为空,因此不需要!!
修饰符。
当然,如果要保留当前可为空的状态,则始终可以仅将适配器的构造函数中的参数设置为也可为空。您只需要对适配器本身做一些时髦的事情:
getItemCount(): Int = list?.size!! //or list!!.size
val item = list?.get(position)
item?.whatever
我只能推测,但是我认为作者对后期的初始化有点不满意。其他两个参数都无法立即初始化,因此对列表执行相同操作可能只是一种反思。
不过,公平地说,这里的!!
并没有真正挽回“十亿美元的错误”。该列表在personList!!
上方进行了初始化,因此除非运行另一个线程可能使personList
在主线程到达适配器的初始化之前再次为空,否则保证personList
为非空
至于处理null安全的“最佳”方法……好吧,没有。但是,我可以说,这种情况下的“最佳”方式将是这样的:
private val personList = ArrayList<Person>() //this needs to be at the top now
private val adapter by lazy { PersonListAdapter(personList, this) } //by lazy will initialize this variable once it's first referenced and then retain that instance for future calls
private val layoutManager by lazy { LinearLayoutManager(this) }
现在,在初始化类时(或在首次引用变量时),一切都将得到照顾,而您不必担心可为空性。
我确实有一个示例,说明您引用的代码的结构在哪里起作用:单例。
class SomeSingleton private constructor() {
companion object {
private var instance: SomeSingleton? = null
fun getInstance(): SomeSingleton {
if (instance == null) instance = SomeSingleton()
return instance!! //since technically instance could've been made null from another Thread, Kotlin requires that you assert the non-null state
}
}
}
如果您查看反编译的Kotlin JVM项目,这实际上就是by lazy
的工作方式。当然,它不会在自己的类中,但是Kotlin将创建一个全局变量和一个getVariable()
方法,该方法与我上面介绍的getInstance()
方法完全一样。
整个事情可能被替换为:
class SomeSingleton private constructor() {
companion object {
val instance by lazy { SomeSingleton() }
}
}
TL; DR,我只能推测,但是看来作者坚持使用某些Java编程风格,要么忘记,不意识到,要么拒绝使用Kotlin替代方案。