รับคลาส Kotlin ต่อไปนี้:
data class Test(val value: Int)
ฉันจะแทนที่Int
getter อย่างไรเพื่อให้มันกลับมาเป็น 0 ถ้าค่าเป็นลบ
หากไม่สามารถทำได้มีเทคนิคอะไรบ้างเพื่อให้ได้ผลลัพธ์ที่เหมาะสม
รับคลาส Kotlin ต่อไปนี้:
data class Test(val value: Int)
ฉันจะแทนที่Int
getter อย่างไรเพื่อให้มันกลับมาเป็น 0 ถ้าค่าเป็นลบ
หากไม่สามารถทำได้มีเทคนิคอะไรบ้างเพื่อให้ได้ผลลัพธ์ที่เหมาะสม
คำตอบ:
หลังจากใช้เวลาเกือบปีเต็มในการเขียน 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 ทำงานได้อย่างถูกต้องใน a Map
หรือ a Set
)
data class class(@JsonProperty("iss_position") private val position: Map<String, Double>) { val latitude = position["latitude"]; val longitude = position["longitude"] }
และฉันคิดว่ามันค่อนข้างดีสำหรับกรณีของฉัน tbh คุณคิดอย่างไรเกี่ยวกับเรื่องนี้? (มีฟิลด์อื่น ๆ ของ ofc และด้วยเหตุนี้ฉันจึงเชื่อว่ามันไม่มีเหตุผลสำหรับฉันที่จะสร้างโครงสร้าง json ที่ซ้อนกันขึ้นใหม่ในรหัสของฉัน)
parsing a string into an int
คุณคุณอนุญาตให้ใช้ตรรกะทางธุรกิจในการแยกวิเคราะห์และข้อผิดพลาดในการจัดการสตริงที่ไม่ใช่ตัวเลขในคลาสโมเดลของคุณอย่างชัดเจน ...
List
และMutableList
ไม่มีเหตุผล
คุณสามารถลองสิ่งนี้:
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
to value
เพื่อใช้ชื่อที่ต้องการสำหรับคุณสมบัติ
ฉันกำหนดตัวเข้าถึงที่กำหนดเองสำหรับคุณสมบัติด้วยตรรกะที่คุณอธิบายไว้
คำตอบขึ้นอยู่กับสิ่งที่ความสามารถในการที่คุณใช้จริงที่data
ให้ @EPadron กล่าวถึงเคล็ดลับที่ดี (เวอร์ชันปรับปรุง):
data class Test(private val _value: Int) {
val value: Int
get() = if (_value < 0) 0 else _value
}
ที่จะทำงานตามที่คาด, เนมันมีหนึ่งสาขาหนึ่ง getter ขวาequals
, และhashcode
component1
สิ่งที่จับได้นั้นtoString
และcopy
แปลก:
println(Test(1)) // prints: Test(_value=1)
Test(1).copy(_value = 5) // <- weird naming
ในการแก้ไขปัญหาtoString
คุณสามารถกำหนดมันใหม่ด้วยมือ ฉันไม่รู้วิธีแก้ไขการตั้งชื่อพารามิเตอร์ แต่จะไม่ใช้data
เลย
ฉันรู้ว่านี่เป็นคำถามเก่า แต่ดูเหมือนว่าไม่มีใครพูดถึงความเป็นไปได้ที่จะทำให้คุณค่าเป็นส่วนตัวและเขียน getter ที่กำหนดเองเช่นนี้:
data class Test(private val value: Int) {
fun getValue(): Int = if (value < 0) 0 else value
}
สิ่งนี้ควรจะถูกต้องอย่างสมบูรณ์เนื่องจาก Kotlin จะไม่สร้าง getter เริ่มต้นสำหรับฟิลด์ส่วนตัว
แต่อย่างอื่นฉันเห็นด้วยกับ spierce7 อย่างแน่นอนว่าคลาสข้อมูลมีไว้สำหรับการเก็บข้อมูลและคุณควรหลีกเลี่ยงตรรกะ "ธุรกิจ" แบบฮาร์ดโค้ดที่นั่น
val value = test.getValue()
และไม่เหมือนกับ getters อื่น ๆ val value = test.value
.getValue()
ฉันได้เห็นคำตอบของคุณแล้วฉันยอมรับว่าคลาสข้อมูลมีไว้สำหรับการเก็บข้อมูลเท่านั้น แต่บางครั้งเราก็จำเป็นต้องทำบางสิ่งออกไป
นี่คือสิ่งที่ฉันทำกับคลาสข้อมูลของฉันฉันเปลี่ยนคุณสมบัติบางอย่างจาก val เป็น var และทับในตัวสร้าง
ดังนี้:
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 { ... }
) นอกจากนี้คลาสข้อมูลอาจไม่ใช่สิ่งที่คุณต้องการเนื่องจากด้วยคลาสที่ไม่ใช่ข้อมูลคุณสามารถแยกคุณสมบัติของคุณจากพารามิเตอร์ตัวสร้างของคุณได้ เป็นการดีกว่าที่จะอธิบายอย่างชัดเจนกับความตั้งใจที่ไม่แน่นอนของคุณในนิยามชั้นเรียนของคุณ หากฟิลด์เหล่านั้นไม่สามารถเปลี่ยนแปลงได้ด้วยเช่นกันคลาสข้อมูลก็ใช้ได้ แต่คลาสข้อมูลของฉันเกือบทั้งหมดไม่เปลี่ยนรูป
นี่ดูเหมือนจะเป็นข้อเสียที่น่ารำคาญอย่างหนึ่งของ Kotlin
ดูเหมือนว่าวิธีแก้ปัญหาที่สมเหตุสมผลเพียงวิธีเดียวซึ่งช่วยให้ความเข้ากันได้ของคลาสย้อนหลังอย่างสมบูรณ์คือการแปลงเป็นคลาสปกติ (ไม่ใช่คลาส "ข้อมูล") และใช้งานด้วยมือ (ด้วยความช่วยเหลือของ IDE) วิธีการ: hashCode ( ), เท่ากับ (), toString (), คัดลอก () และ 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)
}
}
ฉันพบว่าสิ่งต่อไปนี้เป็นแนวทางที่ดีที่สุดในการบรรลุสิ่งที่คุณต้องการโดยไม่ทำลายequals
และ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())
อย่างไรก็ตาม
ครั้งแรกที่ทราบว่า_value
เป็นvar
ไม่ได้val
แต่ในมืออื่น ๆ เนื่องจากเป็นส่วนตัวและการเรียนข้อมูลไม่สามารถรับมรดกมาจากมันค่อนข้างง่ายที่จะตรวจสอบให้แน่ใจว่าจะไม่แก้ไขภายในชั้นเรียน
ประการที่สองtoString()
ก่อให้ผลแตกต่างกันเล็กน้อยกว่ามันจะถ้า_value
เป็นชื่อแต่มันเป็นเรื่องที่สอดคล้องกันและvalue
TestData(0).toString() == TestData(-1).toString()
_value
กำลังถูกแก้ไขในบล็อก init equals
และhashCode
ไม่เสีย