تاخیری ابتدا (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 ایپلیکیشنز بناتا ہے۔ ہم آپ کو مشورہ دیں گے اور بہترین حل تجویز کریں گے۔
مزید پڑھیں