Diberikan kelas Kotlin berikut:
data class Test(val value: Int)
Bagaimana cara menimpa Int
getter sehingga mengembalikan 0 jika nilainya negatif?
Jika ini tidak memungkinkan, teknik apa sajakah untuk mencapai hasil yang sesuai?
Diberikan kelas Kotlin berikut:
data class Test(val value: Int)
Bagaimana cara menimpa Int
getter sehingga mengembalikan 0 jika nilainya negatif?
Jika ini tidak memungkinkan, teknik apa sajakah untuk mencapai hasil yang sesuai?
Jawaban:
Setelah menghabiskan hampir setahun penuh menulis Kotlin setiap hari, saya menemukan bahwa mencoba untuk mengganti kelas data seperti ini adalah praktik yang buruk. Ada 3 pendekatan yang valid untuk ini, dan setelah saya menyajikannya, saya akan menjelaskan mengapa pendekatan yang disarankan oleh jawaban lain itu buruk.
Miliki logika bisnis Anda yang membuat nilai data class
perubahan menjadi 0 atau lebih besar sebelum memanggil konstruktor dengan nilai buruk. Ini mungkin pendekatan terbaik untuk kebanyakan kasus.
Jangan gunakan data class
. Gunakan reguler class
dan minta IDE Anda menghasilkan equals
dan hashCode
metode untuk Anda (atau tidak, jika Anda tidak membutuhkannya). Ya, Anda harus membuatnya kembali jika salah satu properti diubah pada objek, tetapi Anda memiliki kendali penuh atas objek tersebut.
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()
}
}
Buat properti aman tambahan pada objek yang melakukan apa yang Anda inginkan alih-alih memiliki nilai pribadi yang diganti secara efektif.
data class Test(val value: Int) {
val safeValue: Int
get() = if (value < 0) 0 else value
}
Pendekatan buruk yang disarankan oleh jawaban lain:
data class Test(private val _value: Int) {
val value: Int
get() = if (_value < 0) 0 else _value
}
Masalah dengan pendekatan ini adalah bahwa kelas data tidak benar-benar dimaksudkan untuk mengubah data seperti ini. Mereka benar-benar hanya untuk menyimpan data. Mengganti getter untuk kelas data seperti ini berarti itu Test(0)
dan Test(-1)
tidak akan equal
satu sama lain dan akan memiliki perbedaan hashCode
, tetapi ketika Anda memanggil .value
, mereka akan memiliki hasil yang sama. Ini tidak konsisten, dan meskipun mungkin berhasil untuk Anda, orang lain dalam tim Anda yang melihat ini adalah kelas data, mungkin secara tidak sengaja menyalahgunakannya tanpa menyadari bagaimana Anda telah mengubahnya / membuatnya tidak berfungsi seperti yang diharapkan (yaitu pendekatan ini tidak akan ' t bekerja dengan benar di a Map
atau a Set
).
data class class(@JsonProperty("iss_position") private val position: Map<String, Double>) { val latitude = position["latitude"]; val longitude = position["longitude"] }
, dan saya anggap cukup bagus untuk kasus saya, tbh. Apa pendapat Anda tentang ini? (ada ofc bidang lain dan oleh karena itu saya percaya tidak masuk akal bagi saya untuk membuat ulang struktur json bersarang dalam kode saya)
parsing a string into an int
, Anda dengan jelas mengizinkan logika bisnis penguraian dan penanganan kesalahan String non-numerik ke dalam kelas model Anda ...
List
dan MutableList
tanpa alasan.
Anda bisa mencoba sesuatu seperti ini:
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
Dalam kelas data Anda harus menandai parameter konstruktor utama dengan val
atau var
.
Saya menetapkan nilai _value
ke value
untuk menggunakan nama yang diinginkan untuk properti tersebut.
Saya mendefinisikan pengakses khusus untuk properti dengan logika yang Anda jelaskan.
Jawabannya tergantung pada kemampuan apa yang sebenarnya Anda gunakan yang data
menyediakan. @EPadron menyebutkan trik bagus (versi perbaikan):
data class Test(private val _value: Int) {
val value: Int
get() = if (_value < 0) 0 else _value
}
Itu akan bekerja seperti yang diharapkan, ei itu memiliki satu bidang, satu pengambil, benar equals
, hashcode
dan component1
. Tangkapannya begitu toString
dan copy
aneh:
println(Test(1)) // prints: Test(_value=1)
Test(1).copy(_value = 5) // <- weird naming
Untuk memperbaiki masalah dengan toString
Anda dapat mendefinisikan ulang dengan tangan. Saya tahu tidak ada cara untuk memperbaiki penamaan parameter tetapi tidak menggunakan data
sama sekali.
Saya tahu ini adalah pertanyaan lama tetapi tampaknya tidak ada yang menyebutkan kemungkinan untuk menjadikan nilai pribadi dan menulis pengambil khusus seperti ini:
data class Test(private val value: Int) {
fun getValue(): Int = if (value < 0) 0 else value
}
Ini harus benar-benar valid karena Kotlin tidak akan menghasilkan pengambil default untuk bidang pribadi.
Tapi selain itu saya pasti setuju dengan spierce7 bahwa kelas data adalah untuk menyimpan data dan Anda harus menghindari logika "bisnis" hardcode di sana.
val value = test.getValue()
dan tidak seperti getter lainnya val value = test.value
.getValue()
Saya telah melihat jawaban Anda, saya setuju bahwa kelas data dimaksudkan untuk menyimpan data saja, tetapi terkadang kita perlu membuat sesuatu darinya.
Inilah yang saya lakukan dengan kelas data saya, saya mengubah beberapa properti dari val ke var, dan menimpanya di konstruktor.
seperti ini:
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 { ... }
). Mungkin juga kelas data bukanlah yang Anda inginkan, karena dengan kelas non-data Anda dapat memisahkan properti Anda dari parameter konstruktor. Lebih baik eksplisit dengan niat mutabilitas Anda dalam definisi kelas Anda. Jika bidang itu juga kebetulan bisa berubah, maka kelas data baik-baik saja, tetapi hampir semua kelas data saya tidak dapat diubah.
Ini tampaknya menjadi salah satu (di antara) kelemahan Kotlin yang mengganggu.
Tampaknya satu-satunya solusi yang masuk akal, yang sepenuhnya menjaga kompatibilitas kelas ke belakang adalah dengan mengubahnya menjadi kelas biasa (bukan kelas "data"), dan mengimplementasikan secara manual (dengan bantuan IDE) metode: hashCode ( ), sama dengan (), toString (), copy () dan 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)
}
}
Saya menemukan yang berikut ini menjadi pendekatan terbaik untuk mencapai apa yang Anda butuhkan tanpa putus equals
dan 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())
Namun,
Pertama, perhatikan bahwa _value
adalah var
, tidakval
, tapi di sisi lain, karena itu pribadi dan kelas data tidak dapat diwariskan dari, itu cukup mudah untuk memastikan bahwa itu tidak diubah dalam kelas.
Kedua, toString()
menghasilkan hasil yang sedikit berbeda daripada jika _value
dinamai value
, tetapi konsisten dan TestData(0).toString() == TestData(-1).toString()
.
_value
sedang dimodifikasi di blok init equals
dan hashCode
tidak rusak.