परिचय: Kotlin डेवलपर की शाश्वत दुविधा
\n\nयदि आपने कभी Kotlin में लिखा है और किसी गुण के आरंभीकरण को स्थगित करने की आवश्यकता का सामना किया है, तो आपने निश्चित रूप से यह प्रश्न पूछा होगा: "मुझे क्या चुनना चाहिए — by lazy या lateinit?" यह प्रश्न Stack Overflow पर एक वास्तविक हिट है (हजारों व्यूज़, दर्जनों चर्चाएँ)। और यह व्यर्थ नहीं है: सही चुनाव न केवल कोड की पठनीयता बल्कि एप्लिकेशन के प्रदर्शन को भी प्रभावित करता है।
इस लेख में, हम केवल दस्तावेज़ीकरण को दोबारा नहीं बताएंगे। हम एक वास्तविक जाँच करेंगे: दोनों तंत्रों की आंतरिक संरचना को समझेंगे, उनकी गति, मेमोरी खपत की तुलना करेंगे और स्पष्ट सिफारिशें देंगे। और अंत में — विशिष्ट कार्य बिंदुओं के साथ सारांश प्रस्तुत करेंगे। चलिए शुरू करते हैं!
\n\nlateinit और by lazy क्या हैं? संक्षिप्त परिचय
\n\nशुरुआत में बुनियादी अवधारणाओं को ताज़ा करते हैं। दोनों दृष्टिकोण एक ही समस्या का समाधान करते हैं: किसी गुण के पहले उपयोग तक उसके आरंभीकरण को स्थगित करना। लेकिन वे इसे अलग-अलग तरीकों से करते हैं।
\n\nlateinit var — कंपाइलर से वादा
\n\nकीवर्ड lateinit कंपाइलर से कहता है: "मैं वादा करता हूँ कि मैं पहली बार एक्सेस करने से पहले इस गुण को आरंभ कर दूंगा, अभी के लिए बस इसके लिए जगह आवंटित करें।" इसका उपयोग केवल var के साथ और केवल mutable (परिवर्तनीय) गुणों के लिए किया जाता है। यह dependency injection (Dagger, Hilt, Koin) या उन गुणों के लिए आदर्श है जो Activity/Fragment में onCreate में आरंभ किए जाते हैं।
class MyActivity : AppCompatActivity() {\n lateinit var repository: UserRepository\n\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n repository = UserRepository() // आरंभीकरण\n }\n\n fun loadUser() {\n // यदि repository आरंभ नहीं किया गया है — UninitializedPropertyAccessException होगा\n repository.getUser()\n }\n}\n\n\nby lazy — आलसी प्रतिनिधि
\n\nप्रतिनिधि lazy एक अधिक लचीला तंत्र है। यह एक रैपर ऑब्जेक्ट बनाता है, जो पहली बार एक्सेस करने पर लैम्ब्डा को कॉल करता है और परिणाम को याद रखता है। बाद के सभी एक्सेस केवल संग्रहीत मान लौटाते हैं। डिफ़ॉल्ट रूप से यह थ्रेड-सेफ़ (सिंक्रोनाइज़्ड) है, लेकिन मोड को कॉन्फ़िगर किया जा सकता है।
class MyViewModel : ViewModel() {\n val heavyData: ExpensiveData by lazy {\n // यह ब्लॉक केवल एक बार निष्पादित होगा, पहली बार एक्सेस करने पर\n ExpensiveData()\n }\n}\n\n\nदोनों दृष्टिकोण "आलसी आरंभीकरण" की समस्या का समाधान करते हैं, लेकिन अलग-अलग समझौतों के साथ। आइए इन समझौतों को माइक्रोस्कोप के नीचे रखकर जाँचें।
\n\nप्रदर्शन: कौन तेज़ है?
\n\nयहाँ हम सबसे दिलचस्प भाग पर आते हैं — आंकड़ों पर। प्रदर्शन केवल गुण तक पहुँचने की गति नहीं है, बल्कि आरंभीकरण, मेमोरी, थ्रेड-सुरक्षा पर ओवरहेड भी शामिल है।
\n\nपहली बार एक्सेस की गति
\n\nlateinit — पूर्ण रिकॉर्ड धारक। पहली बार एक्सेस करने पर, कंपाइलर केवल यह जाँचता है कि फ़ील्ड null नहीं है (यदि यह nullable है — अपवाद फेंकता है)। कोई लैम्ब्डा नहीं, कोई सिंक्रोनाइज़ेशन नहीं। यह लगभग मुफ़्त है।
by lazy — धीमा है। पहली बार एक्सेस करने पर, एक सिंक्रोनाइज़्ड ब्लॉक कॉल किया जाता है (यदि डिफ़ॉल्ट मोड LazyThreadSafetyMode.SYNCHRONIZED है), लैम्ब्डा निष्पादित होता है, परिणाम कैश किया जाता है। इसमें माइक्रोसेकंड लगते हैं, लेकिन उच्च-लोड लूप या क्रिटिकल कोड में यह ध्यान देने योग्य हो सकता है।
बार-बार एक्सेस की गति
\n\nआरंभीकरण के बाद, दोनों दृष्टिकोण समान रूप से तेज़ी से काम करते हैं। lateinit बस फ़ील्ड को पढ़ता है (सामान्य var की तरह)। by lazy पहले आरंभीकरण के बाद भी कैश किए गए मान को पढ़ता है। इस चरण पर गति में अंतर लगभग शून्य है।
मेमोरी खपत
\n\nयहाँ lateinit स्पष्ट रूप से जीतता है। यह कोई अतिरिक्त ऑब्जेक्ट नहीं बनाता है, केवल एक फ़ील्ड आवंटित करता है। by lazy एक अतिरिक्त Lazy ऑब्जेक्ट बनाता है, जो अतिरिक्त मेमोरी लेता है।