Подтвердить что ты не робот

Override getter для класса данных Kotlin

Учитывая следующий класс Котлина:

data class Test(val value: Int)

Как мне переопределить getter Int так, чтобы он возвращал 0, если значение отрицательное?

Если это невозможно, каковы некоторые методы для достижения подходящего результата?

4b9b3361

Ответ 1

Проведя почти полный год написания Kotlin ежедневно, я обнаружил, что делать это с классами данных - это плохая практика. Их 3 действительных подхода к этому, и после того, как я объясню, почему подход, предлагаемый другими людьми, является плохим.

  • У вашей бизнес-логики, которая создает data class, измените значение, равное 0 или выше, перед вызовом конструктора с плохим значением. Это, вероятно, лучший подход для большинства случаев.

  • Не используйте data class. Используйте обычный class, и ваша среда IDE генерирует методы equals и hashCode для вас (или не нужно, если они вам не нужны). Да, вам придется повторно генерировать его, если какое-либо из свойств будет изменено на объекте, но вы останетесь с полным контролем над объектом.

    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()
      }
    }
    
  • Создайте дополнительное безопасное свойство для объекта, который делает то, что вы хотите, вместо того, чтобы иметь частное значение, которое эффективно задерживается.

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

Плохой подход, который предлагают другие ответы:

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

Проблема с этим подходом заключается в том, что классы данных на самом деле не предназначены для изменения данных, подобных этому. Они действительно предназначены для хранения данных. Переопределение геттера для такого класса данных будет означать, что Test(0) и Test(-1) не будут equal друг друга и будут иметь разные hashCode s, но при вызове .value они будут иметь одинаковый результат, Это непоследовательно, и, хотя это может сработать для вас, другие люди в вашей команде, которые видят, что это класс данных, могут случайно злоупотреблять им, не понимая, как вы его изменили/сделали его не так, как ожидалось (т.е. t правильно работать в Map или Set).

Ответ 2

Вы можете попробовать что-то вроде этого:

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
  • В классе данных вы должны пометить параметры основного конструктора либо val, либо var.

  • Я назначаю значение _value в value, чтобы использовать желаемое имя для свойства.

  • Я определил пользовательский аксессуар для свойства с описанной вами логикой.

Ответ 3

Ответ зависит от того, какие возможности вы используете, что обеспечивает data. @EPadron упомянул отличный трюк (улучшенная версия):

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

Это будет работать так, как ожидалось, e.i имеет поле one, один получатель, правый equals, hashcode и component1. Уловка состоит в том, что toString и copy являются странными:

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

Чтобы устранить проблему с помощью toString, вы можете переопределить ее руками. Я не знаю, как исправить имя параметра, но не использовать data вообще.

Ответ 4

Вы можете просто

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

Обратитесь к Свойствам и полям

Ответ 5

Это, по-видимому, один (среди прочих) раздражающие недостатки Котлина.

Кажется, что единственное разумное решение, полностью поддерживающее обратную совместимость класса, состоит в том, чтобы преобразовать его в обычный класс (а не в класс данных) и реализовать вручную (с помощью IDE) методы: hashCode(), equals(), toString(), copy() и 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)
    }

}

Ответ 6

Я знаю, что это старый вопрос, но, похоже, никто не упоминал о возможности сделать ценность частной и писать пользовательский геттер следующим образом:

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

Это должно быть совершенно корректным, так как Kotlin не будет генерировать по умолчанию getter для частного поля.

Но в противном случае я определенно согласен с spierce7 в том, что классы данных предназначены для хранения данных, и вам следует избегать жесткой кодировки "бизнес-логики".