Kotlin lateinit свойства, опасность NPE?

Я использую свойства lateinit, чтобы избежать непрерывной проверки нуля с помощью? оператор. У меня много свойств вида, которые назначаются впервые в функции getViews (). Если этой функции не было, мое приложение вылетает с NPE, из кода Kotlin.

На мой взгляд, свойства lateinit в основном разрушают хорошие свойства безопасности языка. Я знаю, что они представлены в 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() } } 

Та же самая основная функция lateinit была фактически возможна с Delegates.notNull до M13.

Существуют и другие функции, которые также позволяют обойти применение правил недействительности. Привет! оператор преобразует значение с нулевым значением в ненулевое значение.

Дело не в том, чтобы строго требовать ограничения неопределенности, а в том, чтобы сделать допустимость NULL очень явной частью языка. Каждый раз, когда вы используете lateinit или !! вы принимаете сознательное решение оставить безопасность ограничений неопределенности, надеюсь, с полным основанием.

Как правило, лучше избегать lateinit , !! , и даже ? (обнулять) как можно больше.

Много времени вы можете использовать ленивого делегата, чтобы избежать lateinit, который может держать вас в сфере ненулевых vals (M14 теперь запрещает использование vals с lateinit ).

Код, к которому вы привязаны, включает множество просмотров. Вы можете сохранить их как ненулевые vals, сделав что-то вроде этого:

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

Это будет инициализировать значение при первом использовании mapView и использовать ранее инициализированное значение после этого. Предостережение заключается в том, что это может сломаться, если вы попытались использовать mapView перед вызовом setContentView . Однако это не похоже на то, что значительная часть сделки, и вы получили то преимущество, что ваша инициализация находится рядом с вашей декларацией.

Это то, что использовала библиотека kotterknife для достижения инъекции зрения.

Лётная делегация Котлина будет работать во многих случаях, хотя у вас возникнут проблемы при перезагрузке фрагментов, которые были сохранены в FragmentManager. Когда система Android перестроит фрагмент, она фактически воссоздает представление, вызывающее view?.findViewById(R.id.notif_map) чтобы фактически вернуть недопустимый вид.

В этих случаях вам придется использовать свойство только для чтения:

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