web-dev-qa-db-fra.com

Kotlin: initialiser l'attribut de classe dans le constructeur

Je crée une classe Kotlin avec un attribut de classe que je souhaite initialiser dans le constructeur:

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

Malheureusement, je dois déclarer l'attribut comme Nullable avec le "?" signe, bien que l'attribut sera initialisé dans le constructeur. Déclarer cet attribut en tant qu'attribut Nullable nécessite toujours de forcer une valeur NonNull avec "!!" ou pour fournir un null-check avec "?". 

Existe-t-il un moyen d'éviter cela si l'attribut de classe est initialisé dans le constructeur? Je voudrais apprécier une solution comme celle-ci:

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

Si la seule chose que vous faites dans le constructeur est une affectation, vous pouvez utiliser le constructeur principal avec une propriété privée.

par exemple:

public class TestClass(private val context: Context) {

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

Comme indiqué par D3xter, vous avez la possibilité de le définir dans le constructeur. Vous avez également d'autres options. Ici, ils sont tous ...

Créez la propriété dans le constructeur (selon @ D3xter), il s'agit du cas le plus courant de propriétés simples initialisées directement par le constructeur principal :

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

Vous pouvez déclarer la propriété val et ne pas l'initialiser, en supposant que tous les constructeurs possibles l'initialisent réellement (conformément à votre deuxième exemple dans la question posée). Ceci est normal lorsque vous avez plus d'un constructeur qui pourrait initialiser une valeur différemment :

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

Vous pouvez transmettre des paramètres de constructeur qui ne sont pas des déclarations de propriété, puis les utiliser dans les initialisations de propriété. Ceci est courant lorsque vous avez des initialisations plus complexes ou devez utiliser des propriétés déléguées :

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

Utilisation de lateinit modificateur lorsque vous ne pouvez pas initialiser la valeur pendant la construction, mais que vous êtes sûr que cela sera fait avant votre premier accès en lecture. Ceci est courant lorsqu'une injection de dépendance, un conteneur IoC ou quelque chose qui crée une version vide de votre classe puis l'initialise immédiatement :

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

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

Pour lateinit, la propriété doit être actuellement une var et ne fonctionne pas avec les types primitifs. 

Vous pouvez également déclarer une propriété var et ne pas l'initialiser si vous utilisez un délégué conçu à cet effet, tel que Delegates.notNull(). Ceci est similaire à lateinit et commun lorsque vous voulez une var qui n'a pas d'état initial, mais est définie ultérieurement après la construction à un moment inconnu :

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

J'ai eu un problème similaire où je ne voulais pas garder l'objet après la construction. L'utilisation de lazy ou lateinit a abouti à un bytecode inefficace. Après quelques recherches, j'ai choisi cette approche et suis revenue pour poster la réponse si cela peut aider:

Solution

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

alternativement, ce qui précède peut être encore simplifié en:

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

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

Bytecode décompilé

Et la version Java décompilée de cette version semble un peu plus acceptable que les autres approches et aucune référence au contexte n’est retenue après la construction:

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