21

我创建了一个带有类属性的 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-attribute 使得始终需要使用“!!”强制 NonNull-value 或使用“?”提供 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)
    }
}
4

3 回答 3

22

如 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该属性当前必须是 avar并且不适用于原始类型。

如果您使用为此目的而设计的委托,您还可以声明一个var属性而不对其进行初始化,例如Delegates.notNull(). 当您想要一个没有初始状态但稍后在未知时间点构造后设置时,这类似于lateinitvar并且常见:

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()
    }
}
于 2016-01-06T03:01:14.013 回答
21

如果您在构造函数中所做的唯一事情是赋值,那么您可以将主构造函数与私有属性一起使用。

例如:

public class TestClass(private val context: Context) {

  public fun doSomeVoodoo() {
     val text = context.getString(R.string.abc_...)
  }
}
于 2015-06-25T08:38:24.923 回答
2

我有一个类似的问题,我不想在施工后抓住物体。使用lazylateinit导致字节码效率低下,因此经过一些研究后,我决定采用这种方法并返回发布答案以防万一:

解决方案

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;
    }
}
于 2018-06-05T15:35:30.607 回答