Kotlin: инициализировать атрибут класса в конструкторе

Я создаю Kotlin-класс с атрибутом класса, который я хочу инициализировать в конструкторе:

public class TestClass {

    private var context : Context? = null // Nullable attribute

    public constructor(context : Context) {
       this.context = context
    }

    public fun doSomeVoodoo() {
       val text : String = context!!.getString(R.string.abc_action_bar_home_description)
    }
}

К сожалению, я должен объявить атрибут как Nullable с "?" знак, хотя атрибут будет инициализирован в конструкторе. Объявление этого атрибута как атрибута Nullable заставляет всегда принудительно устанавливать значение NonNull с помощью "!!" или предоставить Null-проверку с "?".

Есть ли способ избежать этого, если атрибут класса будет инициализирован в конструкторе? Я хотел бы оценить решение, подобное этому:

public class TestClass {

    private var context : Context // Non-Nullable attribute

    public constructor(context : Context) {
       this.context = context
    }

    public fun doSomeVoodoo() {
       val text : String = context.getString(R.string.abc_action_bar_home_description)
    }
}

3 ответа

Решение

Если единственное, что вы делаете в конструкторе, это присваивание, то вы можете использовать Основной конструктор с частным свойством.

например:

public class TestClass(private val context: Context) {

  public fun doSomeVoodoo() {
     val text = context.getString(R.string.abc_...)
  }
}

Как показывает D3xter, у вас есть возможность установить его в конструкторе. У вас также есть другие варианты. Вот они все...

Создайте свойство в конструкторе (согласно @D3xter), это наиболее распространенный случай для простых свойств, инициализируемых непосредственно основным конструктором:

class TestClass(private val context: Context) {
    fun doSomeVoodoo() {
        val text : String = context.getString()
    } 
}

Вы можете объявить val свойство, а не инициализировать его, предполагая, что все возможные конструкторы действительно инициализируют его (согласно вашему второму примеру в задаваемом вопросе). Это нормально, когда у вас есть более одного конструктора, который может инициализировать значение по-разному:

public class TestClass {
    private val context: Context

    public constructor(context : Context) {
        this.context = context
    }

    // alternative constructor
    public constructor(pre: PreContext) {
        this.context = pre.readContext()
    }

    public fun doSomeVoodoo() {
        val text : String = context.getString()
    }
}

Вы можете передать параметры конструктора, которые не являются объявлениями свойств, а затем использовать их в рамках инициализации свойств. Это часто встречается, когда у вас более сложные инициализации или вам нужно использовать делегированные свойства:

class TestClass(context: PreContext) {
    private val context : Context by lazy { context.readContext() }
    private val other: List<Items> = run {
        context.items.map { it.tag }.filterNotNull()
    }
    private val simpleThing = context.getSimple()

    fun doSomeVoodoo() {
        val text : String = context.getString()
    }
}

С помощью lateinit модификатор, когда вы не можете инициализировать значение во время построения, но вы уверены, что это будет сделано до вашего первого доступа для чтения. Это часто случается, когда внедрение зависимостей, контейнер IoC или что-то создает пустую версию вашего класса, а затем немедленно инициализирует ее:

class TestClass() {
    private lateinit var context : Context // set by something else after construction

    fun doSomeVoodoo() {
        val text : String = context.getString()
    }
}

За lateinit собственность в настоящее время должна быть var и не работает с примитивными типами.

Вы также можете объявить var свойство и не инициализировать его, если вы используете делегат, разработанный для этой цели, такой как Delegates.notNull(), Это похоже на lateinit и часто, когда вы хотите var который не имеет начального состояния, но устанавливается позже после создания в неизвестный момент времени:

public class TestClass() {
    private var context: Context by Delegates.notNull()

    public fun doSomeVoodoo() {
        // if context is not set before this is called, an exception is thrown
        val text : String = context.getString()
    }
}

У меня была похожая проблема, когда я не хотел удерживать объект после строительства. С помощью lazy или же lateinit Это привело к неэффективному байт-коду, поэтому после некоторого исследования я остановился на этом подходе и вернулся, чтобы опубликовать ответ на случай, если он поможет:

Решение

class TestClass(context: Context) {
    private val homeDescription: String

    init {
        homeDescription = context.getString(R.string.abc_action_bar_home_description)
    }

    fun doSomeVoodoo() {
        val text : String = homeDescription
    }
}

альтернативно, вышеизложенное можно дополнительно упростить до:

class TestClass(context: Context) {
    private val homeDescription: String = context.getString(R.string.abc_action_bar_home_description)

    fun doSomeVoodoo() {
        val text : String = homeDescription
    }
}

Декомпилированный байт-код

И декомпилированная java-версия выглядит немного более приемлемой, чем другие подходы, и после построения не делается никаких ссылок на контекст:

public final class TestClass {
    private final String homeDescription;

    public final void doSomeVoodoo() {
        String text = this.homeDescription;
    }

    public TestClass(@NotNull Context context) {
        Intrinsics.checkParameterIsNotNull(context, "context");
        super();
        String var10001 = context.getString(2131296256);
        Intrinsics.checkExpressionValueIsNotNull(var10001, "context.getString(R.stri…ion_bar_home_description)");
        this.homeDescription = var10001;
    }
}
Другие вопросы по тегам