Dada a seguinte classe de Kotlin:
data class Test(val value: Int)
Como eu substituiria o Int
getter para que retornasse 0 se o valor fosse negativo?
Se isso não for possível, quais são algumas técnicas para obter um resultado adequado?
Dada a seguinte classe de Kotlin:
data class Test(val value: Int)
Como eu substituiria o Int
getter para que retornasse 0 se o valor fosse negativo?
Se isso não for possível, quais são algumas técnicas para obter um resultado adequado?
Respostas:
Depois de passar quase um ano inteiro escrevendo Kotlin diariamente, descobri que tentar substituir as classes de dados dessa maneira é uma prática ruim. Existem 3 abordagens válidas para isso e, depois de apresentá-las, explicarei por que a abordagem que outras respostas sugeriram é ruim.
Tenha sua lógica de negócios que cria o data class
alter o valor para ser 0 ou maior antes de chamar o construtor com o valor incorreto. Esta é provavelmente a melhor abordagem para a maioria dos casos.
Não use um data class
. Use um regular class
e faça com que seu IDE gere os métodos equals
e hashCode
para você (ou não, se não precisar deles). Sim, você terá que gerá-lo novamente se alguma das propriedades for alterada no objeto, mas você terá controle total do objeto.
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()
}
}
Crie uma propriedade de segurança adicional no objeto que faz o que você deseja, em vez de ter um valor privado que é efetivamente substituído.
data class Test(val value: Int) {
val safeValue: Int
get() = if (value < 0) 0 else value
}
Uma abordagem ruim que outras respostas estão sugerindo:
data class Test(private val _value: Int) {
val value: Int
get() = if (_value < 0) 0 else _value
}
O problema com essa abordagem é que as classes de dados não são realmente destinadas a alterar dados como este. Eles servem apenas para armazenar dados. Substituir o getter para uma classe de dados como essa significaria isso Test(0)
e Test(-1)
não equal
um ao outro e teria hashCode
s diferentes , mas quando você chamasse .value
, eles teriam o mesmo resultado. Isso é inconsistente e, embora possa funcionar para você, outras pessoas em sua equipe que veem que se trata de uma classe de dados podem acidentalmente usá-la indevidamente, sem perceber como você a alterou / fez com que não funcionasse como esperado (ou seja, essa abordagem não funcionaria t funcionar corretamente em a Map
ou a Set
).
data class class(@JsonProperty("iss_position") private val position: Map<String, Double>) { val latitude = position["latitude"]; val longitude = position["longitude"] }
, e considero muito bom para o meu caso, tbh. O que você pensa sobre isso? (havia outros campos ofc e, portanto, acredito que não fazia sentido para mim recriar essa estrutura json aninhada em meu código)
parsing a string into an int
, você está permitindo claramente a lógica de negócios de análise e tratamento de erros não numéricos em sua classe de modelo ...
List
e MutableList
sem motivo.
Você pode tentar algo assim:
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
Em uma classe de dados, você deve marcar os parâmetros do construtor primário com val
ou var
.
Estou atribuindo o valor de _value
a value
para usar o nome desejado para a propriedade.
Eu defini um acessador personalizado para a propriedade com a lógica que você descreveu.
A resposta depende de quais recursos você realmente usa que data
fornece. @EPadron mencionou um truque bacana (versão aprimorada):
data class Test(private val _value: Int) {
val value: Int
get() = if (_value < 0) 0 else _value
}
Isso funcionará conforme o esperado, ei tem um campo, um getter, certo equals
, hashcode
e component1
. O problema é esse toString
e copy
são estranhos:
println(Test(1)) // prints: Test(_value=1)
Test(1).copy(_value = 5) // <- weird naming
Para corrigir o problema, toString
você pode redefini-lo manualmente. Não conheço nenhuma maneira de corrigir a nomenclatura do parâmetro, mas não de usá data
-la.
Eu sei que esta é uma questão antiga, mas parece que ninguém mencionou a possibilidade de tornar o valor privado e escrever um getter personalizado como este:
data class Test(private val value: Int) {
fun getValue(): Int = if (value < 0) 0 else value
}
Isso deve ser perfeitamente válido, pois o Kotlin não gerará um getter padrão para o campo privado.
Mas, caso contrário, eu definitivamente concordo com spierce7 que as classes de dados são para armazenar dados e você deve evitar codificar a lógica de "negócios" aí.
val value = test.getValue()
e não como outros getters val value = test.value
.getValue()
Eu vi sua resposta, concordo que as classes de dados são destinadas a conter apenas dados, mas às vezes precisamos fazer algumas coisas com elas.
Aqui está o que estou fazendo com minha classe de dados, alterei algumas propriedades de val para var e as substituí no construtor.
igual a:
data class Recording(
val id: Int = 0,
val createdAt: Date = Date(),
val path: String,
val deleted: Boolean = false,
var fileName: String = "",
val duration: Int = 0,
var format: String = " "
) {
init {
if (fileName.isEmpty())
fileName = path.substring(path.lastIndexOf('\\'))
if (format.isEmpty())
format = path.substring(path.lastIndexOf('.'))
}
fun asEntity(): rc {
return rc(id, createdAt, path, deleted, fileName, duration, format)
}
}
fun Recording(...): Recording { ... }
). Além disso, talvez uma classe de dados não seja o que você deseja, já que com classes que não são de dados, você pode separar suas propriedades dos parâmetros do seu construtor. É melhor ser explícito com suas intenções de mutabilidade em sua definição de classe. Se esses campos também forem mutáveis de qualquer maneira, uma classe de dados está bem, mas quase todas as minhas classes de dados são imutáveis.
Esta parece ser uma (entre outras) desvantagens irritantes do Kotlin.
Parece que a única solução razoável, que mantém completamente a compatibilidade com versões anteriores da classe, é convertê-la em uma classe regular (não uma classe de "dados") e implementar manualmente (com a ajuda do IDE) os métodos: hashCode ( ), equals (), toString (), copy () e 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)
}
}
Achei o seguinte a melhor abordagem para conseguir o que você precisa sem quebrar equals
e hashCode
:
data class TestData(private var _value: Int) {
init {
_value = if (_value < 0) 0 else _value
}
val value: Int
get() = _value
}
// Test value
assert(1 == TestData(1).value)
assert(0 == TestData(-1).value)
assert(0 == TestData(0).value)
// Test copy()
assert(0 == TestData(-1).copy().value)
assert(0 == TestData(1).copy(-1).value)
assert(1 == TestData(-1).copy(1).value)
// Test toString()
assert("TestData(_value=1)" == TestData(1).toString())
assert("TestData(_value=0)" == TestData(-1).toString())
assert("TestData(_value=0)" == TestData(0).toString())
assert(TestData(0).toString() == TestData(-1).toString())
// Test equals
assert(TestData(0) == TestData(-1))
assert(TestData(0) == TestData(-1).copy())
assert(TestData(0) == TestData(1).copy(-1))
assert(TestData(1) == TestData(-1).copy(1))
// Test hashCode()
assert(TestData(0).hashCode() == TestData(-1).hashCode())
assert(TestData(1).hashCode() != TestData(-1).hashCode())
Contudo,
Primeiro, observe que _value
é var
, nãoval
, mas, por outro lado, uma vez que é privado e classes de dados não pode ser herdada de, é bastante fácil de se certificar de que não é modificada dentro da classe.
Em segundo lugar, toString()
produz um resultado ligeiramente diferente do que produziria se _value
fosse nomeado value
, mas é consistente e TestData(0).toString() == TestData(-1).toString()
.
_value
está sendo modificado no bloco de inicialização equals
e hashCode
não está quebrado.