कोटलिन डेटा क्लास के लिए ओवरराइड गेट्टर


98

निम्नलिखित कोटलिन वर्ग को देखते हुए:

data class Test(val value: Int)

मैं Intगेटर को ओवरराइड कैसे करूंगा ताकि मान नकारात्मक होने पर यह 0 लौटे?

यदि यह संभव नहीं है, तो उपयुक्त परिणाम प्राप्त करने के लिए कुछ तकनीकें क्या हैं?


14
कृपया अपने कोड की संरचना को बदलने पर विचार करें ताकि नकारात्मक मान 0 में परिवर्तित हो जाएं जब क्लास तुरंत हो, और एक गेट्टर में नहीं। यदि आप नीचे दिए गए उत्तर में वर्णित के रूप में प्राप्त करने वाले को ओवरराइड करते हैं, तो अन्य सभी उत्पन्न विधियाँ जैसे कि समतुल्य (), toString () और घटक अभिगमन अभी भी मूल नकारात्मक मान का उपयोग करेंगे, जिससे संभवतः आश्चर्यजनक व्यवहार हो सकता है।
यूल 21:16

जवाबों:


148

कोटलिन के लेखन का लगभग एक पूरा साल दैनिक खर्च करने के बाद मैंने पाया है कि इस तरह से डेटा कक्षाओं को ओवरराइड करने का प्रयास एक बुरा अभ्यास है। इसके लिए 3 मान्य दृष्टिकोण हैं, और जब मैं उन्हें प्रस्तुत करता हूं, तो मैं समझाता हूं कि अन्य उत्तरों ने जो दृष्टिकोण सुझाया है वह खराब क्यों है।

  1. अपने व्यावसायिक तर्क रखें जो data classनिर्माता को खराब मान के साथ कॉल करने से पहले 0 या उससे अधिक होने के लिए परिवर्तन को बनाता है । यह संभवतः अधिकांश मामलों के लिए सबसे अच्छा तरीका है।

  2. एक का उपयोग न करें data class। एक नियमित का उपयोग करें classऔर अपने आईडीई आप के लिए 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()
      }
    }
    
  3. उस ऑब्जेक्ट पर एक अतिरिक्त सुरक्षित संपत्ति बनाएं जो आपके पास एक निजी मूल्य रखने के बजाय जो आप चाहते हैं वह प्रभावी रूप से ओवरराइड हो।

    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है, लेकिन जब आप कहा जाता है .value, वे एक ही परिणाम प्राप्त होगा। यह असंगत है, और जब यह आपके लिए काम कर सकता है, तो आपकी टीम के अन्य लोग, जो यह देखते हैं कि यह एक डेटा वर्ग है, गलती से बिना इसका एहसास किए दुरुपयोग कर सकता है कि आपने इसे कैसे बदल दिया है / यह अपेक्षा के अनुरूप काम नहीं किया है (यानी यह दृष्टिकोण ' t सही ढंग से Map(a Set) में काम करता है ।


क्रमबद्धता / deserialisation के लिए उपयोग किए जाने वाले डेटा वर्गों के बारे में क्या, एक नेस्टेड संरचना को समतल करना? जैसे मैंने अभी लिखा है data class class(@JsonProperty("iss_position") private val position: Map<String, Double>) { val latitude = position["latitude"]; val longitude = position["longitude"] }, और मैं इसे अपने मामले के लिए काफी अच्छा मानता हूं। आप इस बारे में क्या सोचते हैं? (वहाँ अन्य क्षेत्रों में थे और इसलिए मेरा मानना ​​है कि यह मेरे लिए कोई मतलब नहीं है कि मेरे कोड में नेस्टेड json संरचना को पुनः बनाने के लिए)
Antek

@Antek यह देखते हुए कि आप डेटा में परिवर्तन नहीं कर रहे हैं, मुझे इस दृष्टिकोण में कुछ भी गलत नहीं दिख रहा है। मैं यह भी उल्लेख करूंगा कि आप ऐसा कर रहे हैं इसका कारण यह है कि सर्वर-साइड मॉडल जिसे आपको भेजा जा रहा है, क्लाइंट पर उपयोग करने के लिए सुविधाजनक नहीं है। ऐसी स्थितियों का मुकाबला करने के लिए, मेरी टीम एक क्लाइंट-साइड मॉडल बनाती है, जिसे हम सर्वर-साइड मॉडल को डीरियलाइजेशन के बाद अनुवाद करते हैं। हम क्लाइंट-अप एपीआई में इस सब को लपेटते हैं। एक बार जब आप उदाहरणों को प्राप्त करना शुरू कर देते हैं जो आपके द्वारा दिखाए गए से अधिक जटिल होते हैं, तो यह दृष्टिकोण बहुत सहायक होता है क्योंकि यह क्लाइंट को खराब सर्वर मॉडल निर्णयों या एपिस से बचाता है।
spierce7

मैं इस बात से असहमत हूं कि आप "सर्वश्रेष्ठ दृष्टिकोण" का दावा क्या करते हैं। मुझे जो समस्या दिखाई देती है, वह यह है कि डेटा वर्ग में मान सेट करना चाहते हैं, और इसे कभी नहीं बदलना बहुत आम है। उदाहरण के लिए, एक स्ट्रिंग को एक इंट में पार्स करना। कस्टम गेटर्स / डेटा क्लास पर बसने वाले न केवल उपयोगी होते हैं, बल्कि आवश्यक भी होते हैं; अन्यथा, आप जावा बीन POJOs के साथ बचे हैं जो कुछ नहीं करते हैं और उनका व्यवहार + सत्यापन किसी अन्य वर्ग में निहित है।
अभिजीत सरकार

मैंने क्या कहा "यह ज्यादातर मामलों के लिए सबसे अच्छा तरीका है"। ज्यादातर मामलों में, जब तक कि कुछ परिस्थितियां उत्पन्न नहीं होती हैं, तब तक देवों को अपने मॉडल और एल्गोरिथम / व्यावसायिक तर्क के बीच एक स्पष्ट अलगाव होना चाहिए, जहां उनके एल्गोरिथ्म से परिणामस्वरूप मॉडल स्पष्ट रूप से संभावित परिणामों के विभिन्न राज्यों का प्रतिनिधित्व करता है। कोटलिन इसके लिए शानदार है, सीलबंद कक्षाएं और डेटा कक्षाएं। आपके उदाहरण के लिए parsing a string into an int, आप स्पष्ट रूप से अपने मॉडल वर्ग में गैर-संख्यात्मक स्ट्रिंग्स को पार्स करने और त्रुटि से निपटने के व्यावसायिक तर्क की अनुमति दे रहे हैं ...
spierce7

... मॉडल और व्यावसायिक तर्क के बीच की रेखा को मैला करने की प्रथा हमेशा कम बनाए रखने योग्य कोड की ओर ले जाती है, और मेरा तर्क है कि यह एक विरोधी पैटर्न है। संभवतः मेरे द्वारा बनाए गए डेटा वर्गों का 99% अपरिवर्तनीय / अभाव बसने वाले हैं। मुझे लगता है कि आप वास्तव में अपनी मॉडलों को अपरिवर्तनीय रखने के लिए अपनी टीम के लाभों के बारे में पढ़ने के लिए कुछ समय लेने का आनंद लेंगे। अपरिवर्तनीय मॉडलों के साथ मैं अपने मॉडलों को गारंटी दे सकता हूं कि गलती से कोड में किसी अन्य यादृच्छिक स्थान पर संशोधित नहीं किया गया है, जो दुष्प्रभाव को कम करता है, और फिर से, बनाए रखने योग्य कोड की ओर जाता है। यानी कोटलिन अलग नहीं हुआ Listऔर MutableListबिना किसी कारण के।
spierce7

31

आप कुछ इस तरह की कोशिश कर सकते हैं:

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करने के लिए valueआदेश संपत्ति के लिए वांछित नाम का उपयोग करने में।

  • मैंने आपके द्वारा वर्णित तर्क के साथ संपत्ति के लिए एक कस्टम एक्सेसर को परिभाषित किया।


2
मुझे आईडीई पर एक त्रुटि मिली, यह कहता है "शुरुआती को यहां अनुमति नहीं है क्योंकि इस संपत्ति का कोई समर्थन क्षेत्र नहीं है"
चेंग

6

उत्तर इस बात पर निर्भर करता है कि आप वास्तव में किन क्षमताओं का उपयोग करते हैं data। @ ईपैड्रॉन ने निफ्टी ट्रिक (बेहतर संस्करण) का उल्लेख किया:

data class Test(private val _value: Int) {
    val value: Int
        get() = if (_value < 0) 0 else _value
}

कि अपेक्षा के अनुरूप काम करता है, Ei यह है एक , क्षेत्र, एक गेटर सही equals, hashcodeऔर component1। पकड़ यह है toStringऔर copyअजीब हैं:

println(Test(1))          // prints: Test(_value=1)
Test(1).copy(_value = 5)  // <- weird naming

अपने साथ समस्या को ठीक करने के लिए toStringइसे हाथों से फिर से परिभाषित कर सकते हैं। मैं पैरामीटर नामकरण को ठीक करने का कोई तरीका नहीं जानता, लेकिन इसका उपयोग करने के लिए dataबिल्कुल नहीं।


2

मुझे पता है कि यह एक पुराना प्रश्न है, लेकिन ऐसा लगता है कि किसी ने भी इस तरह से मूल्य को निजी बनाने और कस्टम गेटर लिखने की संभावना का उल्लेख नहीं किया है:

data class Test(private val value: Int) {
    fun getValue(): Int = if (value < 0) 0 else value
}

यह पूरी तरह से मान्य होना चाहिए क्योंकि कोटलिन निजी क्षेत्र के लिए डिफ़ॉल्ट गेट्टर उत्पन्न नहीं करेगा।

लेकिन अन्यथा मैं निश्चित रूप से spierce7 से सहमत हूं कि डेटा कक्षाएं डेटा रखने के लिए हैं और आपको वहां "व्यापार" तर्क को हार्डकोड करने से बचना चाहिए।


मैं आपके समाधान से सहमत हूं, लेकिन कोड की तुलना में आपको इसे इस तरह से कॉल करना होगा val value = test.getValue() और अन्य गेटर्स की तरह नहीं val value = test.value
गोरी

हाँ। यह सही है। यह थोड़ा अलग है अगर आप इसे जावा से कहते हैं क्योंकि यह हमेशा है.getValue()
बायो 007

1

मैंने आपका उत्तर देखा है, मैं मानता हूं कि डेटा कक्षाएं केवल डेटा रखने के लिए होती हैं, लेकिन कभी-कभी हमें उनमें से कुछ बनाने की आवश्यकता होती है।

यहाँ मैं अपने डेटा वर्ग के साथ क्या कर रहा हूँ, मैंने कुछ संपत्तियों को वैल से 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 { ... }) के रूप में कार्य करता है । इसके अलावा शायद एक डेटा वर्ग वह नहीं है जो आप चाहते हैं, क्योंकि गैर-डेटा कक्षाओं के साथ आप अपने निर्माण के मापदंडों से अपने गुणों को अलग कर सकते हैं। अपनी कक्षा की परिभाषा में अपने उत्परिवर्तन इरादों के साथ स्पष्ट होना बेहतर है। अगर उन क्षेत्रों में भी परिवर्तनशील मार्ग हो सकते हैं, तो एक डेटा वर्ग ठीक है, लेकिन मेरे लगभग सभी डेटा वर्ग अपरिवर्तनीय हैं।
spierce7

@ spierce7 क्या यह वास्तव में बुरा है कि एक डाउन वोट के लायक है? वैसे भी, यह समाधान मुझे अच्छी तरह से सूट करता है, इसमें बहुत अधिक कोडिंग की आवश्यकता नहीं होती है और यह हैश रखता है और समान रूप से बरकरार रहता है।
सिमौ

0

यह कोटलिन की एक (अन्य) कष्टप्रद कमियों में से एक है।

ऐसा लगता है कि एकमात्र उचित समाधान, जो पूरी तरह से वर्ग के पिछड़ेपन को बनाए रखता है, उसे एक नियमित वर्ग (न कि "डेटा" वर्ग) में बदलना है, और हाथ से लागू करना है (आईडीई की सहायता से) तरीके: हैशकोड ( ), बराबर (), toString (), कॉपी () और कंपोनेंट ()

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)
    }

}

1
मुझे यकीन नहीं है कि मैं इसे एक कमी कहूंगा। यह केवल डेटा क्लास फ़ीचर की एक सीमा है, जो कि जावा की सुविधा नहीं है।
spierce7

0

मैंने पाया कि बिना ब्रेक के आपको क्या हासिल करना है 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()


@ spierce7 नहींं, यह नहीं है। _valueinit ब्लॉक में संशोधित किया जा रहा है और equalsऔर hashCode टूटते नहीं हैं।
Schatten

आप इस के साथ बाहर मेरी मदद कर सकते stackoverflow.com/questions/64015108/...
wishy
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.