Diberikan kelas Kotlin berikut:
data class Test(val value: Int)
Bagaimana cara menimpa Intgetter 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 Intgetter 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 classperubahan menjadi 0 atau lebih besar sebelum memanggil konstruktor dengan nilai buruk. Ini mungkin pendekatan terbaik untuk kebanyakan kasus.
Jangan gunakan data class. Gunakan reguler classdan minta IDE Anda menghasilkan equalsdan hashCodemetode 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 equalsatu 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 Mapatau 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 ...
Listdan MutableListtanpa 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 valatau var.
Saya menetapkan nilai _valueke valueuntuk 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 datamenyediakan. @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, hashcodedan component1. Tangkapannya begitu toStringdan copyaneh:
println(Test(1)) // prints: Test(_value=1)
Test(1).copy(_value = 5) // <- weird naming
Untuk memperbaiki masalah dengan toStringAnda dapat mendefinisikan ulang dengan tangan. Saya tahu tidak ada cara untuk memperbaiki penamaan parameter tetapi tidak menggunakan datasama 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 equalsdan 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 _valueadalah 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 _valuedinamai value, tetapi konsisten dan TestData(0).toString() == TestData(-1).toString().
_valuesedang dimodifikasi di blok init equalsdan hashCode tidak rusak.