कोटलिन - "आलसी" बनाम "लेटीनिट" का उपयोग करते हुए संपत्ति की शुरूआत


279

कोटलिन में यदि आप कंस्ट्रक्टर के अंदर या क्लास बॉडी के शीर्ष पर एक क्लास प्रॉपर्टी को इनिशियलाइज़ नहीं करना चाहते हैं, तो आपके पास मूल रूप से ये दो विकल्प हैं (भाषा संदर्भ से):

  1. आलसी प्रारंभिक

lazy () एक ऐसा फंक्शन है जो एक लैम्ब्डा लेता है और Lazy का एक उदाहरण देता है, जो एक आलसी प्रॉपर्टी को लागू करने के लिए एक प्रतिनिधि के रूप में काम कर सकता है: lazda (आलसी) को दिए गए पहले कॉल को निष्पादित करता है (और) परिणाम को याद करता है, बाद में कॉल करता है। प्राप्त करने के लिए () बस याद किए गए परिणाम को वापस करें।

उदाहरण

public class Hello {

   val myLazyString: String by lazy { "Hello" }

}

तो पहली कॉल और सबक्शनल कॉल, जहां भी हो, myLazyString में "हैलो" वापस आ जाएगी

  1. देर से शुरूआत

आम तौर पर, गैर-अशक्त प्रकार के रूप में घोषित संपत्तियों को कंस्ट्रक्टर में आरंभीकृत किया जाना चाहिए। हालाँकि, अक्सर यह सुविधाजनक नहीं होता है। उदाहरण के लिए, गुणों को निर्भरता इंजेक्शन के माध्यम से या एक इकाई परीक्षण के सेटअप विधि में शुरू किया जा सकता है। इस स्थिति में, आप कंस्ट्रक्टर में एक गैर-शून्य इनिशियलाइज़र की आपूर्ति नहीं कर सकते हैं, लेकिन आप अभी भी क्लास के शरीर के अंदर संपत्ति का संदर्भ देते समय अशक्त जांच से बचना चाहते हैं।

इस मामले को संभालने के लिए, आप संपत्ति को लेटीनिट संशोधक के साथ चिह्नित कर सकते हैं:

public class MyTest {
   
   lateinit var subject: TestSubject

   @SetUp fun setup() { subject = TestSubject() }

   @Test fun test() { subject.method() }
}

संशोधक का उपयोग केवल वर्ग के शरीर के अंदर घोषित प्राथमिक गुणों पर किया जा सकता है (प्राथमिक निर्माणकर्ता में नहीं), और केवल तब जब संपत्ति में कस्टम गेट्टर या सेटर न हो। संपत्ति का प्रकार गैर-अशक्त होना चाहिए, और यह एक आदिम प्रकार नहीं होना चाहिए।

तो, इन दो विकल्पों के बीच सही तरीके से कैसे चयन करें, क्योंकि दोनों एक ही समस्या को हल कर सकते हैं?

जवाबों:


334

संपत्ति lateinit varऔर by lazy { ... }प्रत्यायोजित संपत्ति के बीच महत्वपूर्ण अंतर हैं:

  • lazy { ... }प्रतिनिधि का उपयोग केवल valसंपत्तियों के लिए किया जा सकता है , जबकि lateinitकेवल varएस पर लागू किया जा सकता है , क्योंकि इसे किसी finalक्षेत्र में संकलित नहीं किया जा सकता है , इस प्रकार कोई अपरिवर्तनीयता की गारंटी नहीं दी जा सकती है;

  • lateinit varएक बैकिंग फ़ील्ड है जो मान संग्रहीत करता है, और by lazy { ... }एक प्रतिनिधि ऑब्जेक्ट बनाता है जिसमें मान एक बार गणना करने पर संग्रहीत किया जाता है, प्रतिनिधि को क्लास ऑब्जेक्ट में संदर्भित करता है और उस गुण के लिए गेटर बनाता है जो प्रतिनिधि उदाहरण के साथ काम करता है। इसलिए यदि आपको कक्षा में मौजूद बैकिंग फ़ील्ड की आवश्यकता है, तो उपयोग करें lateinit;

  • valएस के अलावा , lateinitगैर-अशक्त गुणों और जावा आदिम प्रकारों के लिए इस्तेमाल नहीं किया जा सकता है (इसका कारण है कि nullइसका उपयोग अनैतिक मूल्य के लिए किया जाता है);

  • lateinit varकिसी भी जगह से ऑब्जेक्ट को देखा जा सकता है, जैसे कि किसी फ्रेमवर्क कोड के अंदर से, और एकल वर्ग के विभिन्न ऑब्जेक्ट्स के लिए कई आरंभीकरण परिदृश्य संभव हैं। by lazy { ... }, बदले में, संपत्ति के लिए एकमात्र इनिलाइज़र को परिभाषित करता है, जिसे केवल उप-वर्ग में संपत्ति को ओवरराइड करके बदल दिया जा सकता है। यदि आप चाहते हैं कि आपकी संपत्ति को पहले से अज्ञात तरीके से बाहर से शुरू किया जाए, तो उपयोग करें lateinit

  • प्रारंभिक by lazy { ... }रूप से डिफ़ॉल्ट रूप से थ्रेड-सुरक्षित है और गारंटी देता है कि इनिशलाइज़र को एक ही बार में लागू किया जाता है (लेकिन इसे दूसरे lazyलोड के द्वारा बदल दिया जा सकता है )। lateinit varबहु-थ्रेडेड वातावरण में गुण को प्रारंभ करने के लिए, उपयोगकर्ता के कोड के मामले में ।

  • एक Lazyउदाहरण सहेजा जा सकता है, पास हो सकता है और यहां तक ​​कि कई गुणों के लिए उपयोग किया जा सकता है। इसके विपरीत, lateinit varकिसी भी अतिरिक्त रनटाइम स्थिति को संग्रहीत नहीं करते हैं (केवल nullअनैतिक मूल्य के लिए क्षेत्र में)।

  • यदि आप किसी उदाहरण का संदर्भ रखते हैं Lazy, isInitialized()तो आपको यह जांचने की अनुमति देता है कि क्या यह पहले से ही आरंभ किया जा चुका है (और आप एक प्रत्यायोजित संपत्ति से प्रतिबिंब के साथ ऐसा उदाहरण प्राप्त कर सकते हैं)। यह जांचने के लिए कि क्या एक लेटइनिट संपत्ति को शुरू किया गया है, आप कोटलिन 1.2 के बाद से उपयोगproperty::isInitialized कर सकते हैं

  • एक लंबो पास by lazy { ... }, उस संदर्भ से संदर्भों को कैप्चर कर सकता है, जहां इसका उपयोग उसके बंद होने में किया जाता है .. यह तब संदर्भों को संग्रहीत करेगा और उन्हें केवल एक बार जारी करेगा, जब संपत्ति को प्रारंभिक रूप दिया गया हो। इससे ऑब्जेक्ट पदानुक्रम हो सकते हैं, जैसे कि एंड्रॉइड गतिविधियां, जो बहुत लंबे समय तक जारी नहीं की जा रही हैं (या कभी भी, यदि संपत्ति सुलभ है और कभी एक्सेस नहीं होती है), इसलिए आपको इस बात से सावधान रहना चाहिए कि आप इनिशियल लैंबडा के अंदर क्या उपयोग करते हैं।

इसके अलावा, सवाल में उल्लेख नहीं किया गया एक और तरीका है: Delegates.notNull()जो गैर-प्रॉपर्टीज के आस्थगित आरोहण के लिए उपयुक्त है, जिसमें जावा आदिम प्रकार शामिल हैं।


9
बहुत बढ़िया जवाब! मैं यह जोड़ना चाहूंगा कि lateinitसेटर की दृश्यता के साथ इसके बैकिंग क्षेत्र को उजागर किया जाए ताकि संपत्ति कोटलिन से और जावा से अलग तरीके से एक्सेस किया जा सके। और जावा कोड से यह संपत्ति nullकोटलिन में बिना किसी चेक के भी सेट की जा सकती है । इसलिए lateinitआलसी इनिशियलाइजेशन के लिए नहीं है, लेकिन इनिशियलाइजेशन के लिए जरूरी नहीं कि कोटलिन कोड से।
माइकल

क्या स्विफ्ट के "के बराबर कुछ है!" ?? दूसरे शब्दों में, यह कुछ ऐसा है जो देर से शुरू होता है, लेकिन इसे विफल किए बिना शून्य के लिए जाँच की जा सकती है। यदि आप 'TheObject == null' की जाँच करते हैं, तो "लेटइनिट प्रॉपर्टी करंट यूज़र को प्रारंभ नहीं किया गया है" के साथ कोटलिन का 'लेटइनिट' विफल हो जाता है। यह तब उपयोगी होता है जब आपके पास एक ऐसी वस्तु होती है जो इसके मूल उपयोग परिदृश्य में शून्य नहीं होती है (और इस तरह एक अमूर्त के खिलाफ कोड करना चाहती है जहां यह गैर-अशक्त है), लेकिन असाधारण / सीमित परिदृश्य में शून्य है (यानी: वर्तमान में लॉगिंग एक्सेस करना) उपयोगकर्ता में, जो प्रारंभिक लॉगिन / लॉगिन स्क्रीन पर छोड़कर शून्य नहीं है)
Marchy

@ मार्की, आप ऐसा करने के लिए स्पष्ट रूप से संग्रहीत Lazy+ .isInitialized()का उपयोग कर सकते हैं । मुझे लगता है nullकि गारंटी के कारण ऐसी संपत्ति की जांच करने का कोई सीधा तरीका नहीं है, जो आपको नहीं मिल nullसकता है। :) इस डेमो देखें ।
हॉटकी

@hotkey क्या बहुत अधिक उपयोग करने के बारे में कोई बिंदु है by lazyजो निर्माण समय या रनटाइम को धीमा कर सकता है?
डॉ। जकी

मुझे असमान मूल्य के उपयोग lateinitको दरकिनार करने के लिए उपयोग करने का विचार पसंद आया null। इसके अलावा nullकभी भी उपयोग नहीं किया जाना चाहिए, और lateinitनल के साथ दूर किया जा सकता है। यह है कि मैं
कोटलिन से

26

इसके अलावा hotkeyअच्छा जवाब, यहाँ है कि मैं अभ्यास में दोनों में से कैसे चुनूँ:

lateinit बाहरी आरंभीकरण के लिए है: जब आपको किसी विधि को कॉल करके अपने मूल्य को इनिशियलाइज़ करने के लिए बाहरी सामान की आवश्यकता होती है।

जैसे कॉल करके:

private lateinit var value: MyClass

fun init(externalProperties: Any) {
   value = somethingThatDependsOn(externalProperties)
}

जब lazyकि यह केवल आपके ऑब्जेक्ट के लिए आंतरिक निर्भरता का उपयोग करता है।


1
मुझे लगता है कि हम अभी भी आलसी को इनिशियलाइज़ कर सकते हैं, भले ही वह किसी बाहरी वस्तु पर निर्भर हो। बस एक आंतरिक चर के लिए मूल्य पारित करने की आवश्यकता है। और आलसी आरंभीकरण के दौरान आंतरिक चर का उपयोग करें। लेकिन यह लेटिनीट के रूप में स्वाभाविक है।
एली १e

यह दृष्टिकोण UninitializedPropertyAccessException को फेंकता है, मैंने डबल चेक किया कि मैं मूल्य का उपयोग करने से पहले एक सेटर फ़ंक्शन को कॉल कर रहा हूं। क्या कोई विशिष्ट नियम है जो मैं लेटइनिट के साथ याद कर रहा हूं? आपके उत्तर में MyClass और Any with Android Context को प्रतिस्थापित करें, मेरा मामला है।
तल्हा

24

बहुत छोटा और संक्षिप्त जवाब

लेटनीट: यह गैर-शून्य गुणों को हाल ही में आरंभ करता है

आलसी आरंभीकरण के विपरीत, लेटनीट संकलक को यह पहचानने की अनुमति देता है कि गैर-शून्य संपत्ति का मूल्य सामान्य रूप से संकलित करने के लिए कंस्ट्रक्टर चरण में संग्रहीत नहीं किया गया है।

आलसी प्रारंभिक

कोजलिन में आलसी-इनिशियलाइज़ेशन करने वाले रीड-ओनली (वैल) गुणों को लागू करते समय आलसी बहुत उपयोगी हो सकता है ।

आलसी द्वारा {...} अपना इनिशियलाइज़र निष्पादित करता है जहाँ परिभाषित संपत्ति का पहली बार उपयोग किया जाता है, न कि इसकी घोषणा।


शानदार जवाब, विशेष रूप से "अपने
इनिशलाइज़र का

17

लेटीनिट बनाम आलसी

  1. lateinit

    i) इसे परिवर्तनशील चर [var] के साथ उपयोग करें

    lateinit var name: String       //Allowed
    lateinit val name: String       //Not Allowed

    ii) केवल गैर-अशक्त डेटा प्रकारों के साथ अनुमत

    lateinit var name: String       //Allowed
    lateinit var name: String?      //Not Allowed

    iii) यह संकलित करने का वादा है कि मूल्य को भविष्य में आरम्भ किया जाएगा।

नोट : यदि आप इसे प्रारंभ किए बिना लेटनीट चर का उपयोग करने का प्रयास करते हैं तो यह UnInitializedPropertyAccessException को फेंकता है।

  1. आलसी

    i) वस्तुओं के अनावश्यक आरंभ को रोकने के लिए Lazy initialization को डिज़ाइन किया गया था।

    ii) जब तक आप इसका उपयोग नहीं करते हैं, तब तक आपके चर को प्रारंभ नहीं किया जाएगा।

    iii) यह केवल एक बार आरंभिक है। अगली बार जब आप इसका उपयोग करते हैं, तो आपको कैश मेमोरी से मूल्य मिलता है।

    iv) यह थ्रेड सेफ है (यह उस थ्रेड में इनिशियलाइज़ होता है जहाँ इसका पहली बार उपयोग किया जाता है। अन्य थ्रेड्स कैश में संग्रहीत समान मान का उपयोग करते हैं)।

    v) चर केवल वैल हो सकता है ।

    vi) चर केवल गैर- अशक्त हो सकता है ।


7
मुझे लगता है कि आलसी चर में var नहीं हो सकता।
वैष्णव षष्ठी

4

सभी महान उत्तरों के अलावा, एक अवधारणा है जिसे आलसी लोडिंग कहा जाता है:

आलसी लोडिंग एक डिज़ाइन पैटर्न है जिसका उपयोग आमतौर पर कंप्यूटर प्रोग्रामिंग में किसी ऑब्जेक्ट की आरंभीकरण को रोकने के लिए किया जाता है जब तक कि उस बिंदु पर जिसकी आवश्यकता होती है।

इसका सही उपयोग करते हुए, आप अपने एप्लिकेशन के लोडिंग समय को कम कर सकते हैं। और इसे लागू करने का कोटलिन तरीका हैlazy() जिसके आपके चर के लिए आवश्यक मूल्य को लोड करता है जब भी इसकी आवश्यकता होती है।

लेकिन लेटनीट का उपयोग तब किया जाता है जब आप सुनिश्चित करते हैं कि एक चर शून्य या रिक्त नहीं होगा और इसे प्रारंभ करने से पहले इसे एंड्रॉइड के लिए onResume()विधि का उपयोग करने के लिए प्रारंभिक किया जाएगा- और इसलिए आप इसे एक अशक्त प्रकार के रूप में घोषित नहीं करना चाहते हैं।


हाँ, मैं भी प्रारंभ onCreateView, onResumeऔर साथ अन्य lateinit, लेकिन कभी कभी त्रुटियों वहाँ हुई (क्योंकि कुछ घटनाओं पहले शुरू कर दिया)। तो शायद by lazyएक उचित परिणाम दे सकता है । मैं lateinitगैर-शून्य चरों के लिए उपयोग करता हूं जो जीवनचक्र के दौरान बदल सकते हैं।
कूलमाइंड

2

ऊपर सब कुछ सही है, लेकिन तथ्यों में से एक सरल स्पष्टीकरण LAZY ---- ऐसे मामले हैं जब आप अपनी पहली आवृत्ति तक अपनी वस्तु के उदाहरण के निर्माण में देरी करना चाहते हैं। इस तकनीक को आलसी आरंभीकरण या आलसी तात्कालिकता के रूप में जाना जाता है। आलसी आरंभीकरण का मुख्य उद्देश्य प्रदर्शन को बढ़ावा देना और अपनी मेमोरी फ़ुटप्रिंट को कम करना है। यदि आपके प्रकार की एक आवृत्ति को इंस्टेंट करना एक बड़ी कम्प्यूटेशनल लागत को वहन करता है और कार्यक्रम वास्तव में इसका उपयोग नहीं कर सकता है, तो आप सीपीयू चक्रों को बर्बाद करने में देरी करना या बचना भी चाहेंगे।


0

यदि आप स्प्रिंग कंटेनर का उपयोग कर रहे हैं और आप नॉन-न्यूली बीन फील्ड को इनिशियलाइज़ करना चाहते हैं, lateinitतो बेहतर है।

    @Autowired
    lateinit var myBean: MyBean

1
ऐसा होना चाहिए@Autowired lateinit var myBean: MyBean
Cnfn

0

यदि आप एक अपरिवर्तनीय चर का उपयोग करते हैं, तो इसके साथ प्रारंभ करना बेहतर है by lazy { ... }याval । इस मामले में आप यह सुनिश्चित कर सकते हैं कि हमेशा जरूरत पड़ने पर और अधिक से अधिक 1 बार इसे शुरू किया जाएगा।

यदि आप एक गैर-अशक्त चर चाहते हैं, तो यह मूल्य, उपयोग बदल सकता है lateinit var। एंड्रॉइड डेवलपमेंट में आप बाद में इसे ऐसे इवेंट्स में इनिशियलाइज़ कर सकते हैं onCreate, जैसे onResume। ज्ञात हो, कि यदि आप REST अनुरोध को कॉल करते हैं और इस चर का उपयोग करते हैं, तो यह अपवाद हो सकता है UninitializedPropertyAccessException: lateinit property yourVariable has not been initialized, क्योंकि अनुरोध उस चर को तेजी से निष्पादित कर सकता है जो आरंभ में हो सकता है।

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