1
votes

Existe-t-il une manière élégante de convaincre le compilateur qu'un champ nullable auquel je viens d'attribuer une valeur réelle ne peut plus être nul?

J'ai lu cela en utilisant !! doit généralement être évité. Existe-t-il un moyen d'écrire le code suivant d'une manière plus élégante sans avoir à ajouter quelque chose comme des vérifications nulles obsolètes et des blocs de code dupliqués ou morts?

class A(var field: Thing?) {
    fun getField(): Thing {
        if (field == null) {
            field = Thing()
        }
        return field!!
    }
}

De plus, je ne comprends pas pourquoi le le compilateur nécessite le !! - 'prie-ceci-n'est-pas-nul-opérateur' pour être satisfait dans ce scénario.

EDIT: Considérez qu'il est important pour moi qu'une solution potentielle utilise l'initialisation paresseuse si le champ est nul!


2 commentaires

Un autre thread peut attribuer une valeur nulle à la propriété field , votre if-else ne peut pas garantir qu'il ne sera pas nul.


Wow merci je n'y ai pas pensé!


3 Réponses :


1
votes

Et ça?

class A(field: Thing?) {

    private lateinit var field: Thing

    init {
        field?.let { this.field = it }
    }

    fun getField(): Thing {
        if (!this::field.isInitialized) {
            field = Thing()
        }
        return field
    }
}


0 commentaires

2
votes

Comme Enzokie l'a déjà mentionné dans les commentaires, un autre fil de discussion aurait pu changer de champ après la vérification de null. Le compilateur n'a aucun moyen de le savoir, donc vous devez le dire.

val a = A(null) // field won't be initialized after this line...
a.field // ... but after this

Solution (désireux)

In Dans votre cas particulier, ce serait une bonne idée d'utiliser un paramètre f (vous pouvez aussi le nommer "field", mais j'ai évité cela pour plus de clarté) dans le constructeur (sans val code > / var ) et attribuez-le ensuite à une propriété field à laquelle vous attribuez soit f soit une nouvelle instance de Thing code>.

Cela peut être exprimé de manière très concise avec l'opérateur Elvis :? qui prend le côté gauche sinon nul et le côté droit de l'expression sinon. Donc, dans le champ de fin sera de type Thing.

class A(f: Thing?) {
    val field by lazy {
        f ?: Thing() // inferred type Thing
    }
}

Solution (Lazy)

Comme il a été mentionné par gidds , si vous avez besoin d'initialiser le champ paresseusement, vous pouvez le faire comme ceci en utilisant propriétés déléguées :

class A(f: Thing?) {
    val field = f ?: Thing() // inferred type Thing
}

Le le site d'appel ne change pas:

class A(var field: Thing?) {

    fun getField(): Thing {
        if (field == null) {
            field = Thing()
        }

        // another thread could have assigned null to field

        return field!! // tell the compiler: I am sure that did not happen
    }
}


4 commentaires

Je suppose qu'il y a une faute de frappe et que vous vouliez dire val field = f?: Thing () dans la dernière ligne ...?


Frais. Cela peut également valoir la peine de souligner que la bonne solution concise change l'initialisation de paresseux (première fois que la propriété est accédée) à impatiente (lorsque l'instance est construite pour la première fois). Dans la plupart des cas, ce n'est pas significatif; mais si Thing () était par exemple une connexion à la base de données qui n'a pas toujours été nécessaire, une initialisation hâtive peut entraîner un travail inutile. Si tel est le cas, une autre approche pourrait consister à utiliser un délégué by paresseux - ce serait simple et concis et éliminer les problèmes subtils liés au threading et à l'exactitude.


J'aime votre explication et vos efforts pour fournir une solution alternative, mais dans ce cas particulier, je cherchais effectivement une solution paresseuse. Les `` choses '' de mon projet ont potentiellement une très grande queue de données associées, des éléments qui ne sont pas nécessaires avant que l'élément soit accédé pour la première fois. Je vais essayer de comprendre ce que @gidds voulait dire par «par délégué paresseux».


@gidds avant même que l'op ait demandé, je voulais incorporer votre idée, mais maintenant, op vous a même demandé explicitement à ce sujet. Merci pour votre commentaire :)



1
votes

Lorsque vous définissez un champ, vous définissez en fait une variable plus deux méthodes d'accès :

fun count() {
    val counter = counter
    println("The counter is $counter, and it is still $counter.")
}

Il est possible de personnaliser les méthodes d'accès en écrivant ceci à la place:

val n = 0
val counter: Integer
    get() = n++

Cela exécutera le n ++ code> à chaque fois que vous accédez au champ compteur , qui renvoie donc des valeurs différentes à chaque accès. C'est rare et inattendu mais techniquement possible.

Par conséquent, le compilateur Kotlin ne peut pas supposer que deux accès au même champ retournent deux fois la même valeur. C'est généralement le cas, mais ce n'est pas garanti.

Pour contourner ce problème, vous pouvez lire le champ une fois en le copiant dans une variable locale:

val counter: Integer = 0

p>


0 commentaires