webentwicklung-frage-antwort-db.com.de

Kotlin: Klassenattribut im Konstruktor initialisieren

Ich erstelle eine Kotlin-Klasse mit einem Klassenattribut, das ich im Konstruktor initialisieren möchte:

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

Leider muss ich das Attribut mit dem "?" Als Nullwert deklarieren. sign, obwohl das Attribut im Konstruktor initialisiert wird. Wenn Sie dieses Attribut als Nullable-Attribut deklarieren, muss ein NonNull-Wert immer mit "!!" erzwungen werden. oder eine Nullprüfung mit "?" 

Gibt es eine Möglichkeit, dies zu vermeiden, wenn das Klassenattribut im Konstruktor initialisiert wird? Ich würde gerne eine Lösung wie diese schätzen:

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)
    }
}
14
Christopher

Wenn das einzige, was Sie im Konstruktor tun, eine Zuweisung ist, , Können Sie den primären Konstruktor mit einer privaten Eigenschaft verwenden.

z.B:

public class TestClass(private val context: Context) {

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

Wie von D3xter gezeigt, haben Sie die Möglichkeit, es im Konstruktor festzulegen. Sie haben auch andere Möglichkeiten. Hier sind sie alle ...

Erstellen Sie die Eigenschaft innerhalb des Konstruktors (gemäß @ D3xter). Dies ist der häufigste Fall für einfache Eigenschaften, die direkt vom primären Konstruktor initialisiert werden :

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

Sie können die val -Eigenschaft deklarieren und nicht initialisieren, sofern alle möglichen Konstruktoren sie tatsächlich initialisieren (wie in Ihrem zweiten Beispiel in der gestellten Frage). Dies ist normal, wenn Sie mehr als einen Konstruktor haben, der einen Wert anders initialisieren könnte :

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

Sie können Konstruktorparameter übergeben, die keine Eigenschaftendeklarationen sind, und diese dann innerhalb von Eigenschaftsinitialisierungen verwenden. Dies ist üblich, wenn Sie komplexere Initialisierungen haben oder delegierte Eigenschaften verwenden müssen :

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

Verwenden Sie den Modifizierer lateinit , wenn Sie den Wert während der Konstruktion nicht initialisieren können, Sie jedoch sicher sind, dass dies vor dem ersten Lesezugriff erfolgt. Dies ist üblich, wenn eine Abhängigkeitsinjektion, ein IoC-Container oder etwas eine leere Version Ihrer Klasse erstellt und diese sofort initialisiert :

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

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

Für lateinit muss die Eigenschaft derzeit eine var sein und funktioniert nicht mit primitiven Typen. 

Sie können auch eine var -Eigenschaft deklarieren und nicht initialisieren, wenn Sie einen für diesen Zweck bestimmten Delegaten verwenden, z. B. Delegates.notNull(). Dies ist ähnlich wie lateinit und wird häufig verwendet, wenn Sie eine var wünschen, die keinen Anfangsstatus hat, aber später nach der Konstruktion zu einem unbekannten Zeitpunkt festgelegt wird :

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()
    }
}
18
Jayson Minard

Ich hatte ein ähnliches Problem, bei dem ich mich nach dem Bauen nicht an dem Objekt festhalten wollte. Die Verwendung von lazy oder lateinit führte zu ineffizientem Bytecode. Nach einigen Nachforschungen entschied ich mich für diesen Ansatz und kehrte zurück, um die Antwort zu posten, falls dies hilfreich ist:

Lösung

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

alternativ kann das Obige weiter vereinfacht werden in:

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

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

Decompiled Bytecode

Und die dekompilierte Java-Version fühlt sich ein bisschen akzeptabler als die anderen Ansätze und es wird nach der Konstruktion kein Hinweis auf den Kontext gegeben:

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