在Android(Java)扩展和接口上使用Kotlin的Nullable

时间:2018-11-09 01:05:23

标签: android kotlin nullpointerexception

我既是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) personListArrayList<>?(请注意可空性),以及(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,而使用?本质上是在说:“不,我想要那十亿美元错误返回”,不是吗?

1 个答案:

答案 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替代方案。