รับคลาส Kotlin ต่อไปนี้:
data class Test(val value: Int)
ฉันจะแทนที่Intgetter อย่างไรเพื่อให้มันกลับมาเป็น 0 ถ้าค่าเป็นลบ
หากไม่สามารถทำได้มีเทคนิคอะไรบ้างเพื่อให้ได้ผลลัพธ์ที่เหมาะสม
รับคลาส Kotlin ต่อไปนี้:
data class Test(val value: Int)
ฉันจะแทนที่Intgetter อย่างไรเพื่อให้มันกลับมาเป็น 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อีกคนหนึ่งและจะมีแตกต่างกันhashCodes แต่เมื่อคุณเรียกว่า.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
ในชั้นเรียนข้อมูลคุณต้องเพื่อทำเครื่องหมายพารามิเตอร์ตัวสร้างหลักด้วยหรือvalvar
ฉันกำลังกำหนดค่าของ_valueto 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เป็นชื่อแต่มันเป็นเรื่องที่สอดคล้องกันและvalueTestData(0).toString() == TestData(-1).toString()
_valueกำลังถูกแก้ไขในบล็อก init equalsและhashCode  ไม่เสีย