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;
}
}