Kotlin lateinit属性,NPE危险?

我正在使用lateinit属性,以避免连续空检查? 运营商。 我有很多在getViews()函数中首次分配的视图属性。 如果那个函数不在那里,我的应用程序将会从一个KOTlin代码的NPE中崩溃。

在我看来,lateinit属性基本上破坏了语言的漂亮的安全function。 我知道他们是在M13中引入的,因为有更好的框架支持,但是值得吗?

还是我在这里错过了什么?

这里是代码:

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() } } 

与M13之前的Delegates.notNull实际上可以使用lateinit的相同的基本function。

还有其他function,也可以让你绕过可空性执行。 !! 运算符会将可空值转换为非空值。

重点不在于严格要求可空性约束,而在于使可空性成为语言中非常明确的部分。 每次你使用lateinit 你正在做出有意识的决定,放弃可空性限制的安全,希望有充分的理由。

作为一个经验法则,最好避免迟到 甚至 (可空)尽可能多。

很多时候你可以使用懒惰的委托,以避免lateinit ,这可以让你在非空值的领域(M14现在禁止使用vallateinit )。

你链接到的代码包括很多lateinit视图。 你可以通过做这样的事情把它们保持为非空值:

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

这将初始化第一次使用mapView的值,然后使用之前的初始化值。 需要注意的是,如果您在调用setContentView之前尝试使用mapView,则可能会中断。 但是,这看起来并不是什么大不了的事情,而且您已经获得了您的初始化就在您的声明旁边的好处。

这是kotterknife库用来实现视图注入的东西。

Kotlin懒惰的代表团可以在很多情况下工作,尽管在重新加载已经保存在FragmentManager中的Fragments时会遇到麻烦。 当Android系统重建片段时,实际上将重新创建视图,导致一个view?.findViewById(R.id.notif_map)实际返回一个无效的视图。

在这些情况下,您将不得不使用只读属性:

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