简体   繁体   English

Kotlin lateinit属性,NPE危险吗?

[英]Kotlin lateinit properties, NPE danger?

I am using lateinit properties in order to avoid continuous null checking with the ? 我正在使用lateinit属性,以避免连续的空值检查? operator. 运营商。 I have a lot of View properties that are assigned first time in the getViews() function. 我有很多在getViews()函数中第一次分配的View属性。 If that function weren't there, my application would crash with a NPE, from a Kotlin code. 如果那个函数不存在,我的应用程序将从一个NPE,从Kotlin代码崩溃。

In my opinion lateinit properties basically ruin the nice null safety features of the language. 在我看来,lateinit属性基本上破坏了语言的漂亮的空安全功能。 I know they are introduced in M13 because of better framework support, but does it worth it? 我知道它们是在M13中引入的,因为它有更好的框架支持,但是它值得吗?

Or am I missing something here? 或者我在这里遗漏了什么?

Here is the code: 这是代码:

package com.attilapalfi.exceptional.ui

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.View
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import com.attilapalfi.exceptional.R
import com.attilapalfi.exceptional.dependency_injection.Injector
import com.attilapalfi.exceptional.model.Exception
import com.attilapalfi.exceptional.model.ExceptionType
import com.attilapalfi.exceptional.model.Friend
import com.attilapalfi.exceptional.persistence.*
import com.attilapalfi.exceptional.rest.ExceptionRestConnector
import com.attilapalfi.exceptional.ui.helpers.ViewHelper
import com.attilapalfi.exceptional.ui.question_views.QuestionYesNoClickListener
import com.google.android.gms.maps.MapView
import java.math.BigInteger
import javax.inject.Inject

public class ShowNotificationActivity : AppCompatActivity(), QuestionChangeListener {
    @Inject
    lateinit val exceptionTypeStore: ExceptionTypeStore
    @Inject
    lateinit val friendStore: FriendStore
    @Inject
    lateinit val imageCache: ImageCache
    @Inject
    lateinit val metadataStore: MetadataStore
    @Inject
    lateinit val viewHelper: ViewHelper
    @Inject
    lateinit val exceptionInstanceStore: ExceptionInstanceStore
    @Inject
    lateinit val exceptionRestConnector: ExceptionRestConnector
    @Inject
    lateinit val questionStore: QuestionStore
    private lateinit var sender: Friend
    private lateinit var exception: Exception
    private lateinit var exceptionType: ExceptionType
    private lateinit var exceptionNameView: TextView
    private lateinit var exceptionDescView: TextView
    private lateinit var senderImageView: ImageView
    private lateinit var senderNameView: TextView
    private lateinit var sendDateView: TextView
    private lateinit var mapView: MapView
    private lateinit var questionText: TextView
    private lateinit var noButton: Button
    private lateinit var yesButton: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_show_notification)
        Injector.INSTANCE.applicationComponent.inject(this)
        questionStore.addChangeListener(this)
        getModelFromBundle()
        getViews()
        loadViewsWithData()
    }

    override fun onDestroy() {
        super.onDestroy()
        questionStore.removeChangeListener(this)
    }

    private fun getModelFromBundle() {
        val bundle = intent.extras
        val instanceId = BigInteger(bundle.getString("instanceId"))
        exception = exceptionInstanceStore.findById(instanceId)
        sender = friendStore.findById(exception.fromWho)
        exceptionType = exceptionTypeStore.findById(exception.exceptionTypeId)
    }

    private fun getViews() {
        exceptionNameView = findViewById(R.id.notif_full_exc_name) as TextView
        exceptionDescView = findViewById(R.id.notif_exc_desc) as TextView
        senderImageView = findViewById(R.id.notif_sender_image) as ImageView
        senderNameView = findViewById(R.id.notif_sender_name) as TextView
        sendDateView = findViewById(R.id.notif_sent_date) as TextView
        mapView = findViewById(R.id.notif_map) as MapView
        questionText = findViewById(R.id.notif_question_text) as TextView
        noButton = findViewById(R.id.notif_question_no) as Button
        yesButton = findViewById(R.id.notif_question_yes) as Button
    }

    private fun loadViewsWithData() {
        exceptionNameView.text = exceptionType.prefix + "\n" + exceptionType.shortName
        exceptionDescView.text = exceptionType.description
        imageCache.setImageToView(sender, senderImageView)
        senderNameView.text = viewHelper.getNameAndCity(exception, sender)
        sendDateView.text = exception.date.toString()
        loadQuestionToViews()
    }

    private fun loadQuestionToViews() {
        if (exception.question.hasQuestion) {
            showQuestionViews()
        } else {
            hideQuestionViews()
        }
    }

    private fun showQuestionViews() {
        questionText.text = exception.question.text
        val listener = QuestionYesNoClickListener(exception, exceptionRestConnector, noButton, yesButton)
        noButton.setOnClickListener(listener)
        yesButton.setOnClickListener(listener)
    }

    private fun hideQuestionViews() {
        questionText.visibility = View.INVISIBLE
        noButton.visibility = View.INVISIBLE
        yesButton.visibility = View.INVISIBLE
    }

    override fun onQuestionsChanged() {
        onBackPressed()
    }
}

The same basic feature of lateinit was actually possible with Delegates.notNull prior to M13. 在M13之前,Delegates.notNull实际上可以实现lateinit的相同基本功能。

There's other features that also allow you to bypass the nullability enforcements. 还有其他功能也允许您绕过可空性强制执行。 The !! !! operator will convert a nullable value into a non-null value. operator将可空值转换为非null值。

The point is not to strictly require nullability constraints, but to make nullability a very explicit part of the language. 关键不是要严格要求可空性限制,而是要使可空性成为语言的一个非常明确的部分。 Every time you use lateinit or !! 每次你使用lateinit!! you are making a conscious decision to leave the safety of the nullability constraints, hopefully with good reason. 你有意识地决定放弃可空性约束的安全性,希望有充分的理由。

As a rule of thumb it is best to avoid lateinit , !! 根据经验,最好避免延迟!! , and even ? ,甚至 (nullable) as much as possible. (可空)尽可能多。

A lot of the time you can use a lazy delegate to avoid lateinit which can keep you in the realm of non-null vals (M14 now prohibits using vals with lateinit ). 很多时候你可以使用一个懒惰的委托来避免延迟 ,这可以让你保持在非null值的范围内(M14现在禁止使用带有lateinit的val )。

The code you linked to includes a lot of lateinit views. 您链接的代码包含许多后期视图。 You could keep these as non-null vals by doing something like this: 您可以通过执行以下操作将这些保留为非null值:

private val mapView: MapView by lazy { findViewById(R.id.notif_map) as MapView }

This will initialize the value the first time mapView is used and use the previously initialized value thereafter. 这将在第一次使用mapView时初始化值,然后使用之前初始化的值。 The caveat is that this could break if you tried to use the mapView before the call to setContentView . 需要注意的是,如果在调用setContentView之前尝试使用mapView,则可能会中断。 However, that doesn't seem like that big of a deal and you've gained the benefit that your initialization is right next to your declaration. 但是,这似乎不是什么大不了的事情,并且您已经获得了初始化就在您的声明旁边的好处。

This is what the kotterknife library used to achieve view injection. 这就是kotterknife库用于实现视图注入的功能。

Kotlin's lazy delegation will work for many cases although you will run into trouble when reloading Fragments that have been saved in FragmentManager. Kotlin的惰性委托将适用于许多情况,尽管在重新加载已保存在FragmentManager中的碎片时会遇到麻烦。 When the Android system rebuilds the fragment it will actually recreate the view causing a view?.findViewById(R.id.notif_map) to actually return an invalid View. 当Android系统重建片段时,它实际上会重新创建视图,导致view?.findViewById(R.id.notif_map)实际返回无效的视图。

In these cases you will have to use a read-only property: 在这些情况下,您将不得不使用只读属性:

private val mapView: MapView
    get() = view?.findViewById(R.id.notif_map) as MapView

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM