विलंबित आरंभीकरण (lazy initialization) Kotlin में एक तंत्र है जिसमें किसी ऑब्जेक्ट की प्रॉपर्टी निर्माण के क्षण में नहीं, बल्कि पहली बार उस तक पहुँचने पर आरंभ की जाती है। JetBrains, 2024 के अनुसार, lateinit और lazy इस रणनीति को लागू करने के लिए दो अंतर्निहित उपकरण हैं। दोनों विलंबित आरंभीकरण की समस्या का समाधान करते हैं, लेकिन कार्य तंत्र और अनुप्रयोग के दायरे में मौलिक रूप से भिन्न हैं।
मुख्य बिंदु
विलंबित आरंभीकरण एक पैटर्न है जिसमें क्लास की प्रॉपर्टी अपना मान ऑब्जेक्ट निर्माण के समय नहीं, बल्कि बाद में, आवश्यकता पड़ने पर प्राप्त करती है। Kotlin में, यह पैटर्न दो मौलिक रूप से भिन्न तरीकों से कार्यान्वित किया जाता है: lateinit संशोधक और lazy प्रतिनिधि।
दोनों तंत्र एक सामान्य समस्या का समाधान करते हैं — एक प्रॉपर्टी को क्लास में मौजूद होना चाहिए, लेकिन उसका मान या तो ऑब्जेक्ट निर्माण के समय अज्ञात होता है, या उसकी गणना अनावश्यक रूप से करने के लिए बहुत अधिक संसाधन-गहन होती है। Google I/O 2023 के अनुसार, एक सामान्य Android एप्लिकेशन में 40% तक प्रॉपर्टी को विलंबित आरंभीकरण के माध्यम से अनुकूलित किया जा सकता है, जिससे स्टार्टअप समय 15–25% तक कम हो जाता है।
lateinit और lazy के बीच चुनाव तीन कारकों द्वारा निर्धारित होता है: प्रॉपर्टी की परिवर्तनशीलता (var या val), उसका जीवनचक्र (एकल या एकाधिक असाइनमेंट), और थ्रेड-सुरक्षा आवश्यकताएँ (एकल-थ्रेडेड या मल्टी-थ्रेडेड एक्सेस)।
पहला और सबसे सामान्य परिदृश्य डिपेंडेंसी इंजेक्शन है। फ्रेमवर्क (Dagger, Hilt, Koin) ऑब्जेक्ट निर्माण के बाद डिपेंडेंसी इंजेक्ट करता है, इसलिए प्रॉपर्टी को कंस्ट्रक्टर में आरंभ नहीं किया जा सकता। lateinit के बिना, सभी डिपेंडेंसी को nullable घोषित करना होगा और प्रत्येक उपयोग पर जाँच करनी होगी।
दूसरा परिदृश्य भारी संसाधन हैं: डेटाबेस, नेटवर्क क्लाइंट, फ़ाइल मैनेजर। उनके निर्माण में समय और मेमोरी लगती है, इसलिए उन्हें केवल वास्तविक उपयोग पर ही आरंभ किया जाना चाहिए। ऐसे मामलों के लिए lazy आदर्श है, जो एकल निर्माण की गारंटी देता है।
तीसरी स्थिति Android घटक (Activity, Fragment, ViewModel) हैं, जिनका जीवनचक्र ऑपरेटिंग सिस्टम द्वारा प्रबंधित किया जाता है। जो प्रॉपर्टी onCreate, onViewCreated या ViewModel के init ब्लॉक पर निर्भर करती हैं, उन्हें कंस्ट्रक्टर में आरंभ नहीं किया जा सकता।
lateinit var प्रॉपर्टी के लिए एक संशोधक है जो Kotlin कंपाइलर को आरंभीकरण स्थगित करने की अनुमति देता है। कंपाइलर कंस्ट्रक्टर में मान असाइनमेंट की आवश्यकता नहीं रखता, लेकिन प्रत्येक पहुँच पर रनटाइम जाँच उत्पन्न करता है: यदि प्रॉपर्टी आरंभ नहीं हुई है, तो यह UninitializedPropertyAccessException फेंकता है।
class MainActivity {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
lateinit की सीमाएँ: प्रॉपर्टी को var (val नहीं), नॉन-नल करने योग्य, और आदिम प्रकार (Int, Double, Boolean आदि) नहीं घोषित किया जाना चाहिए। कारण यह है कि आदिम प्रकार JVM आदिम में संकलित होते हैं, जिनकी कोई “आरंभ नहीं” स्थिति नहीं होती। नल करने योग्य प्रॉपर्टी के लिए, विलंबित आरंभीकरण आवश्यक नहीं है: null पहले से ही मान की अनुपस्थिति को दर्शाता है।
lateinit प्रॉपर्टी की स्थिति जाँचने के लिए, :: ऑपरेटर के माध्यम से अंतर्निहित संदर्भ का उपयोग करें: ::propertyName.isInitialized। बिना अपवाद के जोखिम के यह जाँचने का यह एकमात्र सुरक्षित तरीका है कि प्रॉपर्टी आरंभ हुई है या नहीं। यह जाँच केवल उसी क्लास या आंतरिक क्लास से उपलब्ध है, बाहरी कोड से नहीं।
class LoginFragment {
lateinit var binding: FragmentLoginBinding
fun isReady(): Boolean {
return ::binding.isInitialized
}
}
lateinit आरंभीकरण के बाद कोई ओवरहेड नहीं जोड़ता: मान असाइन होने के बाद, प्रॉपर्टी तक पहुँच सीधे फ़ील्ड तक पहुँच के समान होती है। एकमात्र लागत असाइनमेंट से पहले प्रत्येक पढ़ाई पर आरंभीकरण जाँच है। आरंभीकरण के बाद, JIT कंपाइलर जाँच को अनुकूलित कर देता है।
एक महत्वपूर्ण नोट: lateinit प्रॉपर्टी का उपयोग इनलाइन क्लास में नहीं किया जा सकता और कस्टम getter/setter वाली प्रॉपर्टी के लिए समर्थित नहीं है। यदि किसी प्रॉपर्टी को परिकलित एक्सेस की आवश्यकता है, तो lateinit के बजाय lazy का उपयोग करें।
lazy Kotlin मानक लाइब्रेरी में निर्मित एक प्रॉपर्टी प्रतिनिधि है। यह प्रॉपर्टी तक पहली पहुँच पर मान की गणना करता है और सभी बाद के कॉल के लिए परिणाम को कैश करता है। lateinit के विपरीत, lazy केवल val के साथ काम करता है, जिससे प्रॉपर्टी आरंभीकरण के बाद अपरिवर्तनीय हो जाती है।
class UserRepository {
private val database: Database by lazy {
Database.create("users.db")
}
fun getUser(id: String): User {
return database.query("SELECT * FROM users WHERE id = ?", id)
}
}
lazy एक वैकल्पिक पैरामीटर LazyThreadSafetyMode स्वीकार करता है जो थ्रेड-सुरक्षा तंत्र को नियंत्रित करता है। डिफ़ॉल्ट SYNCHRONIZED है — लॉकिंग के साथ दोहरी जाँच, जो एकाधिक थ्रेड से समवर्ती पहुँच के तहत भी एकल आरंभीकरण की गारंटी देती है।
PUBLICATION मोड समानांतर आरंभीकरण की अनुमति देता है: एकाधिक थ्रेड एक साथ आरंभीकरण ब्लॉक निष्पादित कर सकते हैं, लेकिन परिणाम केवल पहले पूर्ण होने वाले से स्वीकार किया जाता है। यह उच्च प्रतिस्पर्धा में SYNCHRONIZED से तेज़ है, लेकिन संसाधन खपत बढ़ाता है।
NONE मोड सिंक्रनाइज़ेशन को पूरी तरह से अक्षम करता है। इसका उपयोग केवल उन प्रॉपर्टी के लिए करें जिन तक पहुँच एक ही थ्रेड से सुनिश्चित हो। इस मोड में, lazy न्यूनतम ओवरहेड के साथ काम करता है — लगभग प्रत्यक्ष असाइनमेंट की तरह।
val heavyConfig: Config by lazy(LazyThreadSafetyMode.NONE) {
Config.loadFromFile("config.json")
}
lazy एक बार आरंभ होने वाली डिपेंडेंसी के लिए सही विकल्प है: रिपॉजिटरी, नेटवर्क क्लाइंट, कैश, डेटाबेस। val सिमैंटिक्स आकस्मिक ओवरराइटिंग से बचाता है, और डिफ़ॉल्ट थ्रेड-सुरक्षा कोड को मल्टी-थ्रेडेड वातावरण में सुरक्षित बनाती है। lazy आदिम प्रकारों के साथ भी सही ढंग से काम करता है, जो lateinit के साथ असंभव है।
Android में, lazy का उपयोग अक्सर by viewModels() के माध्यम से ViewModel डिपेंडेंसी को आरंभ करने या Retrofit क्लाइंट बनाने के लिए किया जाता है। हालाँकि, सावधान रहें: यदि lazy ब्लॉक Activity या Fragment का संदर्भ कैप्चर करता है, तो इससे मेमोरी लीक हो सकता है, क्योंकि प्रतिनिधि प्रॉपर्टी के जीवनकाल तक क्लोज़र को बनाए रखता है।
lateinit और lazy के बीच चुनाव प्राथमिकता का विषय नहीं है, बल्कि एक वास्तुशिल्प निर्णय है जो प्रॉपर्टी की प्रकृति से निर्धारित होता है। प्रत्येक तंत्र अपना कार्य हल करता है, और उनके अनुप्रयोग के क्षेत्र केवल आंशिक रूप से ओवरलैप होते हैं।
| मापदंड | lateinit | lazy |
|---|---|---|
| प्रॉपर्टी प्रकार | केवल var | केवल val |
| Nullable | अनुमति नहीं | अनुमति है |
| आदिम प्रकार | अनुमति नहीं | अनुमति है |
| थ्रेड सुरक्षा | गारंटी नहीं | SYNCHRONIZED डिफ़ॉल्ट |
| स्थिति जाँच | ::x.isInitialized | आवश्यक नहीं |
| त्रुटि पर अपवाद | UninitializedPropertyAccessException | init ब्लॉक में त्रुटि |
| कैशिंग | लागू नहीं | एकल गणना |
| Android Binding | View Binding, Data Binding | उपयोग नहीं होता |
| DI फ्रेमवर्क | Dagger, Hilt, Koin | मैन्युअल इंजेक्शन |
lateinit का उपयोग करें जब प्रॉपर्टी को आरंभीकरण के बाद बदलना हो या इसका निर्माण बाहरी कोड द्वारा प्रबंधित हो। एक विशिष्ट उदाहरण Android Activity में View Binding है: binding onCreate में बनाया जाता है लेकिन var रहता है क्योंकि फ्रेमवर्क इस परिदृश्य के लिए val का समर्थन नहीं करता।
lazy का उपयोग करें जब प्रॉपर्टी एक बार आरंभ होती है, इसकी गणना महँगी है, और ऑब्जेक्ट के जीवनकाल में मान नहीं बदलता। एक क्लासिक उदाहरण रिपॉजिटरी तक पहली पहुँच पर Retrofit क्लाइंट या Room डेटाबेस का विलंबित निर्माण है।
दोनों तंत्रों का उपयोग एक ही क्लास में एक साथ किया जा सकता है। उदाहरण के लिए, View Binding के लिए lateinit और रिपॉजिटरी के लिए lazy। यह एक सामान्य अभ्यास है जो विभिन्न प्रॉपर्टी के लिए अलग-अलग आवश्यकताओं को दर्शाता है। मुख्य बात सिमैंटिक्स को भ्रमित नहीं करना है: जहाँ val चाहिए वहाँ lateinit का उपयोग न करें, और जिन प्रॉपर्टी को पुनः असाइन करने की आवश्यकता है उनके लिए lazy का उपयोग न करें।
lateinit के साथ सबसे आम गलती प्रॉपर्टी के आरंभ होने से पहले उस तक पहुँचना है। इससे UninitializedPropertyAccessException होता है, जो संकलन समय पर नहीं पकड़ा जाता क्योंकि Kotlin डेवलपर पर सही आरंभीकरण क्रम सुनिश्चित करने का भरोसा करता है। समाधान अस्पष्ट स्थितियों में पहुँच से पहले ::property.isInitialized के माध्यम से हमेशा स्थिति की जाँच करना है।
दूसरी सामान्य समस्या सिमैंटिक रूप से val प्रॉपर्टी के लिए lateinit का उपयोग करना है। यदि मान एक बार सेट होता है और कभी नहीं बदलता, तो lazy अधिक सही विकल्प है। यह प्रॉपर्टी को अपरिवर्तनीय बनाता है, आकस्मिक ओवरराइटिंग को रोकता है, और मुफ्त में थ्रेड-सुरक्षा जोड़ता है।
तीसरी गलती साइड इफ़ेक्ट वाला lazy है। lazy आरंभीकरण ब्लॉक को बाहरी स्थिति को संशोधित नहीं करना चाहिए या अन्य lazy प्रॉपर्टी के आरंभीकरण क्रम पर निर्भर नहीं होना चाहिए, क्योंकि गणना अनुक्रम पहली पहुँच पर निर्भर करता है और स्पष्ट नहीं हो सकता। यदि lazy प्रॉपर्टी एक-दूसरे को संदर्भित करती हैं, तो यह चक्रीय निर्भरता और StackOverflowError की ओर ले जाता है।
चौथी समस्या Android में lazy के माध्यम से मेमोरी लीक है। यदि lazy ब्लॉक Activity या Fragment का संदर्भ कैप्चर करता है, तो प्रतिनिधि क्लोज़र को बनाए रखता है, और कचरा संग्रहकर्ता घटक को उसके विनाश के बाद भी मुक्त नहीं कर सकता। समाधान केवल अल्पकालिक ऑब्जेक्ट के साथ lazy का उपयोग करना या Activity के बजाय Application संदर्भ पास करना है।
पाँचवी विशिष्ट गलती आदिम प्रकारों पर lateinit लागू करने का प्रयास है। Kotlin कंपाइलर इसे सिंटैक्स स्तर पर रोकता है, लेकिन डेवलपर नल करने योग्य रैपर के माध्यम से सीमा को दरकिनार करने का प्रयास करते हैं। इससे अनावश्यक null जाँच होती है और विलंबित आरंभीकरण के लाभ पूरी तरह समाप्त हो जाते हैं।
अक्सर पूछे जाने वाले प्रश्न
lateinit var प्रॉपर्टी के लिए एक संशोधक है, जो कंस्ट्रक्टर के बाद आरंभीकरण की अनुमति देता है। lazy val प्रॉपर्टी के लिए एक प्रतिनिधि है, जो पहली पहुँच पर मान की गणना करता है और इसे कैश करता है। lateinit आदिम प्रकार और nullable का समर्थन नहीं करता, जबकि lazy डिफ़ॉल्ट रूप से थ्रेड-सेफ है।
हाँ, अंतर्निहित प्रॉपर्टी संदर्भ के माध्यम से: ::propertyName.isInitialized। यदि प्रॉपर्टी आरंभ हो चुकी है तो विधि true लौटाती है। lateinit फ़ील्ड के साथ काम करते समय UninitializedPropertyAccessException से बचने का यह एकमात्र सुरक्षित तरीका है।
आदिम प्रकार — Int, Double, Boolean और अन्य — JVM आदिम (int, double, boolean) में संकलित होते हैं, जिनकी कोई “आरंभ नहीं” स्थिति नहीं होती। lateinit null को फ़्लैग के रूप में उपयोग करता है, और आदिम null नहीं हो सकते, इसलिए यह तंत्र इन प्रकारों के लिए भौतिक रूप से असंभव है।
डिफ़ॉल्ट LazyThreadSafetyMode.SYNCHRONIZED है — लॉकिंग के साथ दोहरी जाँच, जो एकाधिक थ्रेड से समवर्ती पहुँच के तहत एकल आरंभीकरण की गारंटी देती है। एकल-थ्रेडेड परिदृश्य के लिए NONE का उपयोग करें, उच्च प्रतिस्पर्धा के लिए PUBLICATION का उपयोग करें।
जब प्रॉपर्टी को आरंभीकरण के बाद बदलना हो या इसका निर्माण फ्रेमवर्क द्वारा प्रबंधित हो। एक विशिष्ट उदाहरण Android Activity में View Binding है: binding onCreate में बनाया जाता है और इसे var होना चाहिए। एक बार आरंभ होने वाली val डिपेंडेंसी के लिए lazy का उपयोग करें।
सारांश
हम एक मोबाइल एप्लिकेशन टर्नकी विकसित करेंगे
IT Sectr 2017 से स्टार्टअप और व्यवसायों के लिए iOS और Android एप्लिकेशन बनाता है। हम आपको सलाह देंगे और सर्वोत्तम समाधान प्रस्तावित करेंगे।
यह भी पढ़ें