145 votes

Remplacer getter pour Kotlin classe de données

Compte tenu de la suite de Kotlin classe:

data class Test(val value: Int)

Comment pourrais-je remplacer le Int de lecture de sorte qu'elle retourne 0 si la valeur négative?

Si ce n'est pas possible, ce sont quelques techniques pour obtenir un résultat convenable?

222voto

spierce7 Points 1212

Après avoir passé près d'une année complète de la rédaction de Kotlin au quotidien, j'ai trouvé que le fait de chercher à remplacer les classes de données comme c'est une mauvaise pratique. Il y a 3 approches valables pour le présent, et après je leur présente, je vais vous expliquer pourquoi l'approche d'autres réponses ont suggéré est mauvais.

  1. Avoir une logique d'entreprise qui crée de l' data class modifier la valeur à 0 ou plus avant d'appeler le constructeur de la mauvaise valeur. C'est probablement la meilleure approche pour la plupart des cas.

  2. N'utilisez pas de data class. Utilisez un régulier, class et votre IDE de générer de l' equals et hashCode méthodes pour vous (ou pas, si vous n'en avez pas besoin). Oui, vous allez avoir à re-générer si les propriétés sont modifiées sur l'objet, mais vous êtes de gauche avec un contrôle total de l'objet.

    class Test(value: Int) {
      val value: Int = value
        get() = if (field < 0) 0 else field
    
      override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is Test) return false
        return true
      }
    
      override fun hashCode(): Int {
        return javaClass.hashCode()
      }
    }
    
  3. Créer un nouveau coffre-fort à la propriété sur l'objet qui fait ce que vous voulez au lieu d'avoir une valeur privée c'est effectivement surchargé.

    data class Test(val value: Int) {
      val safeValue: Int
        get() = if (value < 0) 0 else value
    }
    

Une mauvaise approche que d'autres réponses sont ce qui suggère:

data class Test(private val _value: Int) {
  val value: Int
    get() = if (_value < 0) 0 else _value
}

Le problème avec cette approche est que les classes de données ne sont pas vraiment conçus pour modifier les données de ce genre. Ils sont vraiment simplement pour conserver les données. Primordial de la lecture pour une classe de données comme cela signifierait que Test(0) et Test(-1) ne serait pas equal l'un de l'autre et auraient des différents hashCodes, mais quand vous avez appelé, .value, ils auraient le même résultat. C'est incohérent, et tandis qu'il peut travailler pour vous, d'autres personnes dans votre équipe qui a le voir c'est une classe de données, peut accidentellement en abuser sans se rendre compte de la façon dont vous avez altéré il / elle ne fonctionne pas comme prévu (c'est à dire que cette approche ne fonctionne pas correctement dans un Map ou Set).

38voto

EPadronU Points 905

Vous pouvez essayer quelque chose comme ceci:

data class Test(private val _value: Int) {
  val value = _value
    get(): Int {
      return if (field < 0) 0 else field
    }
}

assert(1 == Test(1).value)
assert(0 == Test(0).value)
assert(0 == Test(-1).value)

assert(1 == Test(1)._value) // Fail because _value is private
assert(0 == Test(0)._value) // Fail because _value is private
assert(0 == Test(-1)._value) // Fail because _value is private
  • Dans une classe de données, vous devez marquer le principal paramètres du constructeur, soit avec val ou var.

  • Je suis d'affecter la valeur de _value de value afin d'utiliser le nom souhaité pour le bien.

  • J'ai défini une coutume accesseur pour le bien avec la logique que vous avez décrit.

11voto

voddan Points 16

La réponse dépend de ce que les capacités que vous utilisez réellement qu' data offre. @EPadron mentionné un truc astucieux (version améliorée):

data class Test(private val _value: Int) {
    val value: Int
        get() = if (_value < 0) 0 else _value
}

Qui fonctionne comme prévu, l'e.je elle a un champ, l'un getter, droit equals, hashcode et component1. Le hic, c'est qu' toString et copy bizarre:

println(Test(1))          // prints: Test(_value=1)
Test(1).copy(_value = 5)  // <- weird naming

Pour résoudre le problème avec l' toString vous pouvez la redéfinir par les mains. Je ne connais pas de moyen pour régler le paramètre de nommage, mais de ne pas utiliser data .

7voto

bio007 Points 118

Je sais que c'est une vieille question, mais il semble que personne n'a mentionné la possibilité de faire de la valeur privée et de l'écriture personnalisée getter comme ceci:

data class Test(private val value: Int) {
    fun getValue(): Int = if (value < 0) 0 else value
}

Ce doit être parfaitement valable que Kotlin ne générera pas de défaut de lecture pour le domaine privé.

Mais sinon, je suis absolument d'accord avec spierce7 que les classes de données sont pour la tenue des données et vous devriez éviter de coder en dur "métier".

1voto

Asher Stern Points 1859

Cela semble être une (parmi d'autres) inconvénients gênants de Kotlin.

Il semble que la seule solution raisonnable, ce qui maintient la compatibilité descendante de la classe est de le convertir en une classe ordinaire (pas un "données" de la classe), et de mettre en œuvre à la main (avec l'aide de l'IDE) les méthodes: les méthodes hashCode(), equals(), toString(), copy() et componentN()

class Data3(i: Int)
{
    var i: Int = i

    override fun equals(other: Any?): Boolean
    {
        if (this === other) return true
        if (other?.javaClass != javaClass) return false

        other as Data3

        if (i != other.i) return false

        return true
    }

    override fun hashCode(): Int
    {
        return i
    }

    override fun toString(): String
    {
        return "Data3(i=$i)"
    }

    fun component1():Int = i

    fun copy(i: Int = this.i): Data3
    {
        return Data3(i)
    }

}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X