বিলম্বিত আরম্ভ (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 অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন