এই পর্বে যা শিখবেন
- “উত্তর নেই” null দিয়ে বোঝালে কেন bug অনেক দূরে গিয়ে ফাটে, আর Optional কীভাবে সেটা টাইপে নিয়ে আসে
of,ofNullable,empty;get()-এর বিপদ আরorElseThrow()orElseবনামorElseGet— ডিফল্টের হিসাব কখন চলে;map,filter,flatMapদিয়ে চেইনifPresentOrElse,or,Optional::stream, আর Stream থেকে আসাOptionalওOptionalDouble- কোথায় Optional ব্যবহার করবেন না, আর তার বিকল্প: ফাঁকা collection, Null Object, sealed ফলাফল-টাইপ
১ · “উত্তর নেই” — null দিয়ে বললে কী হয়
একটি অনলাইন দোকানের কথা ভাবুন। গ্রাহক ফোন করে মোবাইল নম্বর বললেন, আর আপনার কোড সেই নম্বর দিয়ে গ্রাহক খোঁজে: Customer findByMobile(String mobile). নম্বরটি তালিকায় থাকলে উত্তর সহজ। কিন্তু না থাকলে? Java-র পুরোনো রীতি ছিল null ফেরত দেওয়া। সমস্যা হলো, method-এর ঘোষণা দেখে সেটা বোঝার কোনো উপায় নেই। Customer লেখা টাইপ কথা দেয় একজন গ্রাহকের, অথচ মাঝে মাঝে দেয় “কিছুই না”।
ধরুন যে ডেভেলপার এই method ডাকছেন, তিনি ব্যস্ত, অথবা ধরে নিয়েছেন নম্বর সবসময় থাকবে। তিনি ফল একটি ভ্যারিয়েবলে রাখলেন, সেটি আরেকটি method-এ পাঠালেন, সেখান থেকে একটি তালিকায় গেল। তিন দিন পর রিপোর্ট বানানোর সময় কোনো একটি লাইনে customer.name() ডাকা হলো আর প্রোগ্রাম থেমে গেল NullPointerException দিয়ে। stack trace দেখাচ্ছে রিপোর্টের কোড — অথচ আসল ভুল ঘটেছিল খোঁজার জায়গায়, যেখানে “না পাওয়া” কথাটি চুপচাপ null হয়ে গিয়েছিল। null-এর সবচেয়ে বড় দোষ এটাই: সমস্যা যেখানে তৈরি হয়, ফাটে সেখান থেকে অনেক দূরে।
কুরিয়ার অফিসে পার্সেল নিতে গেছেন। দুইভাবে উত্তর আসতে পারে। প্রথম ধরন: কাউন্টারের লোক একটি সিল করা বাক্স দেন, আর বাক্সের গায়ে লেখা “ভেতরে পার্সেল থাকতেও পারে, না-ও পারে”। আপনি জানেন, খুলে দেখতে হবে। দ্বিতীয় ধরন: তিনি কিছু না বলে খালি হাত বাড়িয়ে দেন, আর আপনি ভাবেন হাতে কিছু আছে — বাসায় গিয়ে খুলতে গিয়ে টের পান হাত ফাঁকা ছিল। null হলো ওই খালি হাত। Optional হলো গায়ে লেখা বাক্স — বাক্সটি সবসময় আসে, শুধু ভেতরে জিনিস থাকতেও পারে, না-ও পারে।
এই সমস্যা পুরোনো আর বিখ্যাত। null reference-এর জনক Tony Hoare ১৯৬৫ সালে ALGOL W ভাষায় এটি যোগ করেছিলেন, আর ২০০৯ সালে একে বলেছেন তাঁর “billion-dollar mistake” (ভাবনার প্রশ্ন ১-এ বিস্তারিত)। পর্ব ১৫-এ দেখেছেন, reference ভ্যারিয়েবল যখন কোনো object দেখায় না, তখন তার মান null; আর পর্ব ২৯-এ দেখেছেন Java 14-এ আসা (Java 15 থেকে ডিফল্টে চালু) বিস্তারিত NPE বার্তা, যা অন্তত বলে দেয় কোন জিনিসটি null ছিল। কিন্তু সেই বার্তা আসে ব্যর্থ হওয়ার পরে. Optional-এর লক্ষ্য তার আগেই — compile করার সময়ই — ডাকার কোডকে মনে করিয়ে দেওয়া।
একই প্রশ্ন, দুই রকম উত্তর
Returning null hides absence; returning Optional makes it part of the signature
// পুরোনো রীতি — ঘোষণা দেখে বোঝার উপায় নেই
Customer findByMobile(String mobile) → গ্রাহক, অথবা চুপচাপ null
// Optional — টাইপেই লেখা “না-ও থাকতে পারে”
Optional<Customer> findByMobile(String mobile) → Optional[গ্রাহক] অথবা Optional.empty
String n = findByMobile(m).name(); ✗ compile হয় না — Optional-এ name() নেই
String n = findByMobile(m).map(Customer::name).orElse("অজানা"); ✓ সিদ্ধান্ত নিতে হলো
শেষ দুই লাইনেই আসল কৌশল। Optional<Customer> নিজে Customer নয়, তাই তার ওপর সরাসরি name() ডাকা যায় না — compiler থামিয়ে দেয়। মান বের করার প্রতিটি পথে আপনাকে একটি সিদ্ধান্ত লিখতে হয়: ডিফল্ট দেবেন, exception ছুড়বেন, নাকি কিছুই করবেন না। Optional এসেছে Java 8-এ (২০১৪), java.util package-এ, Stream API-র সাথে একই সময়ে — কারণ findFirst() বা max()-এর মতো stream-এর কাজগুলোরও “কিছুই পাওয়া গেল না” বলার একটি পরিষ্কার উপায় দরকার ছিল।
Optional কোনো নতুন জাদু নয়; এটি একটি ছোট generic class (পর্ব ২৮), যার ভেতরে একটি মান থাকে অথবা থাকে না। এর আসল কাজ যোগাযোগ: method-এর লেখক টাইপের মাধ্যমে ডাকার কোডকে জানান — “উত্তর না-ও থাকতে পারে, আর সেটা স্বাভাবিক।”
২ · Optional বানানো — of, ofNullable, empty
Optional-এর কোনো public constructor নেই। বানাতে হয় তিনটি static factory method দিয়ে, আর কোনটি বেছে নেবেন তা নির্ভর করে আপনি মানটি সম্পর্কে কতটা নিশ্চিত তার ওপর:
| method | কখন | null দিলে | ছাপলে |
|---|---|---|---|
Optional.of(x) | আপনি নিশ্চিত x null নয় | সাথে সাথে NullPointerException | Optional[x] |
Optional.ofNullable(x) | x null হতে পারে (যেমন পুরোনো API থেকে আসা) | ফাঁকা Optional | Optional.empty বা Optional[x] |
Optional.empty() | নিশ্চিতভাবে কোনো মান নেই | — | Optional.empty |
Optional.of-এ null দিলে কী হয়, যাচাই করা যাক:
import java.util.*;
class Main {
public static void main(String[] args) {
String mobile = null;
Optional<String> m = Optional.of(mobile);
System.out.println(m);
}
}Exception in thread "main" java.lang.NullPointerException at java.base/java.util.Objects.requireNonNull(Objects.java:233) at java.base/java.util.Optional.of(Optional.java:113) at Main.main(Main.java:6)
trace-এর ওপরের দুই লাইন দেখাচ্ছে, Optional.of ভেতরে Objects.requireNonNull ডাকে — পর্ব ২৯-এর সেই fail-fast হাতিয়ার। খেয়াল করুন, এখানে বার্তা নেই: helpful NPE বার্তা আসে শুধু JVM নিজে যখন null-এর ওপর কিছু করতে গিয়ে ব্যর্থ হয়; কোড নিজে throw করলে আসে না। এই আচরণটা আসলে উপকারী। of ব্যবহার করে আপনি ঘোষণা দিচ্ছেন “এখানে null আসার কথা নয়”; এলে bug, আর সেটা ধরা পড়ল ঠিক যেখানে ঘটেছে।
পুরোনো API, যেমন Map.get(), এখনো মান না পেলে null দেয়। সেখানে সেতু হলো ofNullable: Optional.ofNullable(settings.get("timeout")). আর নিজের method-এ যখন জানেন কিছু পাওয়া যায়নি, সরাসরি return Optional.empty(); লিখুন।
Optional-এর মান একবার তৈরি হলে বদলায় না (immutable). set জাতীয় কোনো method নেই। আর Optional ফেরত দেয় এমন method থেকে কখনো return null; নয় — তাহলে ডাকার কোড Optional-এর ওপর method ডাকতে গিয়েই NPE পাবে, আর Optional রাখার পুরো উদ্দেশ্য নষ্ট হবে। compiler এটা আটকায় না, তাই নিয়মটি আপনাকেই মানতে হবে।
৩ · মান বের করা — get()-এর ফাঁদ আর orElseThrow()
Optional হাতে পেলে নতুনদের প্রথম প্রবৃত্তি হয় পুরোনো null-যাচাইয়ের মতো লেখা:
Optional<Customer> c = findByMobile(mobile);
if (c.isPresent()) {
System.out.println("স্বাগতম, " + c.get().name());
} else {
System.out.println("গ্রাহক নেই");
}এটা কাজ করে, কিন্তু এটা আসলে if (c != null)-এর লম্বা রূপ — নতুন কিছু পাওয়া গেল না। isPresent() মান থাকলে true দেয়; Java 11 থেকে উল্টোটা isEmpty()-ও আছে। আর get() ভেতরের মান দেয় — কিন্তু মান না থাকলে exception ছোড়ে। নিচের তিন লাইনের প্রোগ্রামে কেউ যাচাই করতে ভুলে গেছেন:
import java.util.*;
class Main {
public static void main(String[] args) {
Optional<String> phone = Optional.empty();
String p = phone.get();
System.out.println(p);
}
}Exception in thread "main" java.util.NoSuchElementException: No value present at java.base/java.util.Optional.get(Optional.java:143) at Main.main(Main.java:6)
NoSuchElementException একটি unchecked exception, বার্তা No value present. অর্থাৎ যাচাই ছাড়া get() ডাকলে আমরা ঠিক null-এর সমস্যায় ফিরে গেলাম, শুধু exception-এর নাম বদলেছে। সমস্যা হলো নামটা: “get” শুনে মনে হয় নিরাপদে মান নেওয়া, অথচ এটি ব্যর্থ হতে পারে। Java-র নকশাকারীরা পরে নিজেরাই স্বীকার করেছেন যে এই নাম বিভ্রান্তিকর। তাই Java 10-এ এসেছে orElseThrow() — হুবহু একই কাজ, একই exception, কিন্তু নামেই লেখা “না থাকলে ছুড়ব”। Java 10 থেকে get()-এর Javadoc-ও বলে, এর পছন্দের বিকল্প orElseThrow().
if (c.isPresent()) {
return c.get().name();
}
return "অজানা";null-যাচাইয়ের মতোই চার লাইন, আর if ভুলে গেলে No value present. পাঠককে মাথায় রাখতে হয় যে get() নিরাপদ কারণ ওপরে যাচাই আছে।
return c.map(Customer::name).orElse("অজানা");এক লাইন। ভুল হওয়ার কোনো পথ নেই — মান না থাকলে map-এর ভেতরের কাজ চলেই না, সরাসরি ডিফল্ট আসে। অংশ ৫-এ এর ভেতরটা দেখবেন।
// এই অর্ডার আমরাই এইমাত্র সেভ করেছি — না পাওয়া মানে bug
Order o = findOrder(id).orElseThrow();মান না থাকা যদি সত্যিই অসম্ভব হয় (না থাকলে প্রোগ্রামে ভুল আছে), তাহলে orElseThrow() লিখুন। পাঠক এক নজরে বোঝেন: লেখক জেনে-বুঝে ধরে নিয়েছেন মান থাকবে।
নিষিদ্ধ নয়, কিন্তু সন্দেহের সংকেত — কোড রিভিউয়ে যাকে বলে “code smell”. বিশেষ করে isPresent()-এর ঠিক পরে get() দেখলেই প্রায় সবসময় map/orElse/ifPresent দিয়ে আরও ছোট আর নিরাপদ লেখা যায়। পরীক্ষায় (unit test) বা শুধু “আছে কি না” জানতে চাইলে isPresent()/isEmpty() একদম ঠিক আছে।
৪ · ডিফল্ট মান — orElse, orElseGet আর orElseThrow(Supplier)
একটি অ্যাপের config ভাবুন: সেটিং থাকলে সেটি, না থাকলে একটি ডিফল্ট। Map.get null দেয়, তাই ofNullable দিয়ে সেতু বানিয়ে:
int timeout = Optional.ofNullable(config.get("timeout")) // "45" অথবা null
.map(Integer::parseInt) // 45 অথবা ফাঁকা
.orElse(30); // না থাকলে 30config-এ "timeout" = "45" থাকলে ফল ৪৫; কোনো "retries" না থাকলে একই কোড ডিফল্ট দেয় (Java 21-এ চালিয়ে যাচাই করা)। Integer::parseInt হলো একটি method reference — পর্ব ৩৪-এ শেখা, s -> Integer.parseInt(s)-এর ছোট রূপ।
orElse বনাম orElseGet — ডিফল্টের হিসাব কখন চলে
দুটিই ডিফল্ট দেয়, কিন্তু একটি বড় পার্থক্য আছে যা ইন্টারভিউতে প্রায়ই জিজ্ঞেস করা হয়। orElse(value) একটি তৈরি মান নেয়। orElseGet(supplier) নেয় একটি Supplier — একটি lambda, যা দরকার হলে মান বানাবে। আর Java-তে method ডাকার আগে তার argument-এর হিসাব হয়ে যায়। তাই orElse(loadDefault()) লিখলে মান থাকুক বা না থাকুক, loadDefault() চলবেই। ধাপে ধাপে দেখুন:
1static String loadDefault() {2 System.out.println("(ডিফল্ট লোড হলো)");3 return "bn";4}5Optional<String> lang = findSetting("lang");6String a = lang.orElse(loadDefault());7String b = lang.orElseGet(() -> loadDefault());
| lang | |
| loadDefault() চলেছে | |
| a | |
| b |
- সেটিং পাওয়া গেছে।
lang-এ আছে"en". অর্থাৎ ডিফল্টের কোনো দরকারই নেই। orElse-এর argument আগে। Java method ডাকার আগে argument-এর মান বের করে — এটাই eager evaluation. তাইloadDefault()চলল, বার্তা ছাপল,"bn"বানাল —orElseতখনো শুরুই হয়নি।orElseদেখে মান আছে। ফেরত দিল"en". কষ্ট করে বানানো"bn"সোজা ফেলে দেওয়া হলো। ডিফল্টটি যদি ডেটাবেস থেকে আনা বা ফাইল পড়া হতো, সেই খরচ বৃথা যেত।orElseGetপায় একটি lambda.() -> loadDefault()নিজে কোনো মান নয়, একটি “রেসিপি”। এখানে কিছুই ছাপা হলো না — রেসিপি শুধু হাতে দেওয়া হয়েছে।- মান আছে, তাই রেসিপি কখনো চলেনি।
b = "en", আরloadDefault()এই লাইনে একবারও ডাকা হয়নি। দরকার হলে তবেই কাজ — এটাই lazy evaluation. - Optional ফাঁকা হলে পার্থক্য নেই। দুই পথেই
loadDefault()ঠিক একবার চলে, ফলও এক। পার্থক্য শুধু মান থাকা অবস্থায় — আর বাস্তবে সেটাই সাধারণত বেশি ঘটে।
সহজ নিয়ম: ডিফল্ট যদি আগে থেকে তৈরি একটি সস্তা মান হয় (30, "অজানা", একটি constant), orElse লিখুন — পড়তে সহজ। ডিফল্ট বানাতে যদি কোনো কাজ করতে হয় — নতুন object, method ডাকা, নেটওয়ার্ক, ডেটাবেস — orElseGet. আর বিশেষ সাবধান: ডিফল্টের কোডে যদি কোনো side effect থাকে (লগ লেখা, ডেটাবেসে নতুন সারি তৈরি), orElse দিলে সেটা প্রতিবার ঘটবে — মান থাকলেও।
orElseThrow(Supplier) — নিজের exception
কুপন কোডের কথা ভাবুন। ব্যবহারকারী একটি কোড লিখলেন, আর সেটি বৈধ নাও হতে পারে। খোঁজার method Optional<Coupon> দেয়, কারণ “এই কোড নেই” স্বাভাবিক ফল। কিন্তু অর্ডার নিশ্চিত করার স্তরে এসে অবৈধ কুপন মানে প্রক্রিয়া থামানো, আর ব্যবহারকারীকে স্পষ্ট বার্তা দেখানো:
Coupon c = findCoupon(code)
.orElseThrow(() -> new IllegalArgumentException("কুপন বৈধ নয়: " + code));এখানেও Supplier — exception object তৈরি হয় শুধু মান না থাকলে। (পর্ব ২৯-এর আলোচনা মনে করুন: exception বানানো সস্তা নয়, কারণ stack trace রেকর্ড হয়।) কোনো argument ছাড়া orElseThrow() দেয় NoSuchElementException; নিজের exception দিতে চাইলে এই রূপ।
৫ · map আর filter — বাক্স না খুলেই কাজ
এবার Optional-এর সবচেয়ে শক্তিশালী অংশ। গ্রাহকের ঠিকানা থাকতে পারে, না-ও পারে; ঠিকানা থাকলেও জেলা লেখা না-ও থাকতে পারে। পুরোনো রীতিতে জেলা বের করতে লিখতে হতো সিঁড়ির মতো null-যাচাই:
String district = "জেলা অজানা";
Customer c = findByMobileOld(mobile); // null দিতে পারে
if (c != null) {
Address a = c.address();
if (a != null) {
String d = a.district();
if (d != null) district = d;
}
}আসল কথা একটাই — “গ্রাহক → ঠিকানা → জেলা, কোথাও না পেলে ‘জেলা অজানা’” — কিন্তু চোখে পড়ে শুধু if. একটি যাচাই বাদ পড়লেই NPE. Optional দিয়ে একই কথা:
String district = findByMobile(mobile) // Optional<Customer>
.map(Customer::address) // Optional<Address>
.map(Address::district) // Optional<String>
.orElse("জেলা অজানা");map(f)-এর নিয়ম দুই লাইনের: মান থাকলে f চালাও আর ফলটি নতুন Optional-এ রাখো; মান না থাকলে f চালিয়োই না, ফাঁকাটিই এগিয়ে দাও। আরও একটি সূক্ষ্ম কথা: f যদি null ফেরত দেয়, map সেটাকে ফাঁকা Optional বানিয়ে দেয় (ভেতরে ofNullable). তাই যে record-এর accessor null দিতে পারে, তার ওপরও চেইন নিরাপদ। ধাপে ধাপে দেখুন — প্রথমে একজন গ্রাহক যাঁর ঠিকানায় জেলা লেখা নেই, তারপর একটি অচেনা নম্বর:
1Optional<Customer> c = findByMobile(m);2Optional<Address> a = c.map(Customer::address);3Optional<String> d = a.map(Address::district);4String r = d.orElse("জেলা অজানা");
| m | |
| c | |
| a | |
| d | |
| r |
- খোঁজা — বাক্সে করিম।
findByMobileপেয়েছে, তাইc=Optional[করিম]. বাক্সের গায়ে টাইপ লেখা:Optional<Customer>. - প্রথম
map— মান আছে, তাই কাজ চলে।Customer::addressচলল, ঠিকানা পাওয়া গেল, আর সেটি নতুন বাক্সে:Optional<Address>. টাইপ বদলেছে, বাক্স রয়ে গেছে। - দ্বিতীয়
map— ফল null, তাই বাক্স ফাঁকা। করিমের ঠিকানায় জেলা লেখা নেই,district()null দিল।mapnull-কে বাক্সে ভরে না; ফাঁকা Optional বানায়। এখানেই null-এর সাথে যোগাযোগ শেষ। orElse— সিদ্ধান্তের জায়গা। বাক্স ফাঁকা, তাই ডিফল্ট: “জেলা অজানা”. পুরো চেইনে সিদ্ধান্ত নেওয়া হয়েছে ঠিক একবার, একেবারে শেষে।- অচেনা নম্বর — শুরুতেই ফাঁকা।
c=Optional.empty. null-এর জগতে এখানেই পরের লাইনে NPE-র ঝুঁকি শুরু হতো। - পরের দুই
map— কাজই চলে না। ফাঁকা বাক্সেmapভেতরের function ডাকে না, শুধু ফাঁকাটি এগিয়ে দেয়। একে বলতে পারেন short-circuit: প্রথম ফাঁকা হওয়ামাত্র বাকি চেইন শুধু “পার করে দেয়”। - শেষে একই ডিফল্ট। তিন রকম “না থাকা” (গ্রাহক নেই, ঠিকানা নেই, জেলা নেই) — সবগুলো একই এক লাইনের
orElse-এ সামলানো হলো। সিঁড়ির মতো তিনটিifলাগল না।
এবার পুরোটা চালিয়ে দেখুন। তিনজন গ্রাহক: রহিমের পুরো ঠিকানা আছে, করিমের ঠিকানায় জেলা নেই, সুমির ঠিকানাই নেই; আর একটি অচেনা নম্বর। খোঁজার কাজটি পর্ব ৩৫-এর Stream দিয়ে — findFirst() নিজেই Optional দেয়:
import java.util.*;
record Address(String street, String district) {}
record Customer(String name, String mobile, Address address) {}
class Main {
static final List<Customer> CUSTOMERS = List.of(
new Customer("রহিম", "01711000001", new Address("মিরপুর ১০", "ঢাকা")),
new Customer("করিম", "01811000002", new Address("নিউ মার্কেট", null)),
new Customer("সুমি", "01911000003", null));
static Optional<Customer> findByMobile(String mobile) {
return CUSTOMERS.stream().filter(c -> c.mobile().equals(mobile)).findFirst();
}
static String districtOf(String mobile) {
return findByMobile(mobile).map(Customer::address)
.map(Address::district)
.orElse("জেলা অজানা");
}
public static void main(String[] args) {
System.out.println(findByMobile("01711000001").map(Customer::name).orElse("নেই"));
System.out.println(findByMobile("01500000000"));
for (String m : List.of("01711000001", "01811000002", "01911000003", "01500000000"))
System.out.println(m + " → " + districtOf(m));
}
}আউটপুট ছয় লাইন: রহিম, Optional.empty, তারপর 01711000001 → ঢাকা, 01811000002 → জেলা অজানা, 01911000003 → জেলা অজানা, 01500000000 → জেলা অজানা. দ্বিতীয় লাইন দেখাচ্ছে ফাঁকা Optional-এর toString() — Optional.empty; মান থাকলে ছাপা হয় Optional[...]. শেষ তিনটি লাইনে তিনটি আলাদা কারণে জেলা নেই — জেলা null, ঠিকানা null, গ্রাহকই নেই — কিন্তু districtOf-এ একটিও if নেই। খেয়াল করুন, Customer record-এর field-গুলো সাধারণ টাইপ (Address, Optional নয়); Optional শুধু findByMobile-এর ফেরত-টাইপে — কেন, অংশ ৮-এ। (অনলাইন runner-এর জন্য কোনো class public নয়; নিজের প্রজেক্টে public class Main থাকে Main.java-তে।)
filter — শর্ত না মিললে ফাঁকা
filter(শর্ত) মান থাকলে শর্ত যাচাই করে: মিললে Optional যেমন ছিল তেমন, না মিললে ফাঁকা। ধরুন কুপন পাওয়া গেছে, কিন্তু মেয়াদ শেষ হলে সেটা না-পাওয়ার মতোই:
int percent = findCoupon(code)
.filter(Coupon::isActive) // মেয়াদ শেষ হলে ফাঁকা
.map(Coupon::percent)
.orElse(0); // কোনো ছাড় নেইOptional.of(5).filter(n -> n > 10) ছাপলে আসে Optional.empty. Stream-এর map/filter-এর সাথে মিলটা লক্ষ করুন — Optional-কে ভাবতে পারেন সর্বোচ্চ একটি উপাদানের stream হিসেবে।
৬ · flatMap — বাক্সের ভেতরে বাক্স হলে
অনেক সময় চেইনের মাঝের method নিজেই Optional দেয়। ধরুন ঠিকানা আলাদা একটি সার্ভিস থেকে আসে, আর সেটা না-ও থাকতে পারে: Optional<Address> addressOf(User u). এখন যদি map দিয়ে লেখেন:
import java.util.*;
record Address(String district) {}
record User(String name) {}
class Main {
static Optional<User> findUser(String phone) { return Optional.of(new User("রহিম")); }
static Optional<Address> addressOf(User u) { return Optional.empty(); }
public static void main(String[] args) {
Optional<Address> a = findUser("017").map(Main::addressOf);
System.out.println(a);
}
}Main.java:10: error: incompatible types: inference variable U has incompatible bounds
Optional<Address> a = findUser("017").map(Main::addressOf);
^
equality constraints: Address
lower bounds: Optional<Address>
where U,T are type-variables:
U extends Object declared in method <U>map(Function<? super T,? extends U>)
T extends Object declared in class Optional
1 error
বার্তাটি ভয়ের মতো দেখালেও কথা সহজ। map function-এর ফলকে নতুন বাক্সে ভরে। function নিজেই বাক্স (Optional<Address>) দিচ্ছে, তাই ফল দাঁড়ায় Optional<Optional<Address>> — বাক্সের ভেতরে বাক্স। আপনি চেয়েছিলেন Optional<Address>; compiler বলছে “U-কে Address হতে হবে (equality), অথচ পাচ্ছি Optional<Address> (lower bound)”. সমাধান flatMap: সে function-এর দেওয়া বাক্সটিই সরাসরি ফেরত দেয়, নতুন করে মোড়ে না।
- findUser
Optional<User>এক বাক্স - addressOf
Optional<Address>নিজেই বাক্স দেয় - map দিলে
Optional<Optional>বাক্সে বাক্স - flatMap দিলে
Optional<Address>এক বাক্স - তারপর map
Optional<String>জেলা
findUser("017")
→ Optional[User[name=রহিম]]
টাইপ: Optional<User>
Optional<Address> addressOf(User u)
ঠিকানার সার্ভিসে তথ্য না-ও থাকতে পারে
তাই এর ফেরত-টাইপও Optional — ঠিক কাজ
.map(Main::addressOf) → Optional[Optional.empty] টাইপ: Optional<Optional<Address>> বাইরের বাক্স ভরা, ভেতরেরটা ফাঁকা — বিভ্রান্তিকর
.flatMap(Main::addressOf) → Optional.empty টাইপ: Optional<Address> user না থাকলেও ফাঁকা, ঠিকানা না থাকলেও ফাঁকা
findUser(p)
.flatMap(Main::addressOf)
.map(Address::district)
.orElse("জেলা অজানা")
- শুরুতে এক বাক্স।
findUserদেয়Optional<User>— ব্যবহারকারী থাকতে পারেন, না-ও পারেন। - পরের ধাপের method-ও Optional দেয়। এটা ভালো নকশা: ঠিকানা না থাকা স্বাভাবিক, তাই
addressOfসৎভাবে টাইপে সেটা জানাচ্ছে। সমস্যা method-এ নয়, কীভাবে জোড়া লাগাচ্ছেন তাতে। mapদিলে বাক্সে বাক্স।mapসবসময় ফলকে নতুন Optional-এ রাখে। ফল নিজেই Optional, তাইOptional<Optional<Address>>. ছাপলে দেখায়Optional[Optional.empty]— “বাইরে আছে, ভেতরে নেই”, যার কোনো কাজের অর্থ নেই। পরের.map(Address::district)-ও আর compile হবে না।flatMapমোড়ে না। মান থাকলে function চালায় আর তার দেওয়া Optional-টিই সরাসরি ফেরত দেয়; মান না থাকলে ফাঁকা। ফল এক স্তরেরOptional<Address>. “flat” মানে চ্যাপ্টা করা — দুই স্তরকে এক স্তর।- চেইন আবার পরিষ্কার। এখন
map(Address::district)আরorElseআগের মতো চলে। মনে রাখার নিয়ম: function সাধারণ মান দিলেmap, Optional দিলেflatMap.
Stream-এর flatMap-ও একই চিন্তা: প্রতিটি উপাদান থেকে একটি stream বানিয়ে সব stream-কে এক stream-এ চ্যাপ্টা করা (পর্ব ৩৫)। গণিত আর functional programming-এর ভাষায় এই ধরনের “বাক্স + flatMap” কাঠামোকে বলে monad — নামটা মনে রাখার দরকার নেই, তবে ভাবনার প্রশ্ন ২-এ এর ইতিহাস আছে।
৭ · মান ব্যবহার করা, আর Stream-এর সাথে Optional
সবসময় মান বের করে আনতে হবে এমন নয়। অনেক সময় শুধু “থাকলে কিছু করো” — যেমন গ্রাহক পাওয়া গেলে তাঁকে SMS পাঠানো:
findByMobile(m).ifPresent(c -> sms.send(c.mobile(), "আপনার অর্ডার পাঠানো হয়েছে"));
findByMobile(m).ifPresentOrElse( // Java 9
c -> System.out.println("স্বাগতম, " + c.name()),
() -> System.out.println("নতুন গ্রাহক — নিবন্ধন করুন"));
Optional<Customer> c = findInCache(m).or(() -> findInDatabase(m)); // Java 9ifPresent(action)— মান থাকলে action চালায়, না থাকলে কিছুই না (Java 8).ifPresentOrElse(action, emptyAction)— দুই পথই এক জায়গায় (Java 9).isPresent/get/else-এর পরিষ্কার বিকল্প।or(supplier)— ফাঁকা হলে আরেকটি Optional থেকে চেষ্টা (Java 9). ক্যাশে না পেলে ডেটাবেসে খোঁজা — আর supplier হওয়ায় ক্যাশে পেলে ডেটাবেস ডাকাই হয় না।orElseGetদেয় সাধারণ মান,orদেয় Optional — এটুকুই পার্থক্য।stream()— মান থাকলে এক উপাদানের stream, না থাকলে ফাঁকা stream (Java 9). এর আসল ব্যবহার নিচে।
JDK-র কোথায় কোথায় Optional
নিজে না বানালেও Optional প্রতিদিন হাতে আসবে, কারণ Stream API-র যে কাজগুলোর উত্তর না-ও থাকতে পারে, সবগুলো Optional দেয়:
| method | ফেরত-টাইপ | কেন ফাঁকা হতে পারে |
|---|---|---|
stream.findFirst(), findAny() | Optional<T> | stream ফাঁকা, বা filter-এর পর কিছু বাকি নেই |
stream.max(cmp), min(cmp) | Optional<T> | ফাঁকা তালিকার সর্বোচ্চ বলে কিছু নেই |
stream.reduce(Integer::sum) | Optional<T> | শুরুর মান ছাড়া ফাঁকা stream-এর যোগফল অনির্ধারিত |
intStream.max(), min() | OptionalInt | একই, primitive রূপে |
intStream.average() | OptionalDouble | ০টি সংখ্যার গড় অসংজ্ঞায়িত |
শেষ সারিটি বাস্তবে খুব কাজের। একজন শিক্ষার্থীর নম্বরের তালিকা ফাঁকা হলে গড় কত? ০ বললে ভুল তথ্য দেওয়া হয় — মনে হয় তিনি সব বিষয়ে শূন্য পেয়েছেন। তাই Java ফাঁকা stream-এর গড়ে দেয় OptionalDouble.empty, আর সিদ্ধান্ত আপনার। OptionalInt, OptionalLong আর OptionalDouble আলাদা class, কারণ Optional<Integer> হলে প্রতিটি সংখ্যা আগে autoboxing-এ Integer object হতো, তারপর আবার Optional-এ মোড়া হতো — দুইবার মোড়ানো। এদের method-এর নাম একটু আলাদা: getAsInt(), getAsDouble(); আর এদের map/flatMap/filter নেই।
Optional-এর stream — ফাঁকাগুলো বাদ দিয়ে মানগুলো রাখা
ধরুন একটি ঈদ অফারে ব্যবহারকারীরা চারটি কুপন কোড জমা দিয়েছেন, যার কিছু অবৈধ। প্রতিটি কোডের জন্য discount(code) দেয় Optional<Integer>. আপনি চান শুধু বৈধ ছাড়গুলোর একটি তালিকা। map করলে পাবেন Stream<Optional<Integer>> — আবার বাক্সে মোড়া উপাদান। Java 9-এর Optional::stream দিয়ে flatMap করলে প্রতিটি ভরা Optional হয় এক উপাদানের stream, প্রতিটি ফাঁকা হয় শূন্য উপাদানের stream, আর সব মিলে একটি সমতল stream:
1List<Integer> valid = Stream.of("EID10", "FAKE", "PUJA15", "")2 .map(Main::discount) // Stream<Optional<Integer>>3 .flatMap(Optional::stream) // Stream<Integer>4 .toList();
| কোড | |
| map-এর পর | |
| Optional::stream | |
| এ পর্যন্ত তালিকা |
- “EID10” — বৈধ।
discountদেয়Optional[10].Optional::streamএকে বানায় এক উপাদানের stream, আরflatMapসেই ১০-কে মূল stream-এ ঢেলে দেয়। - “FAKE” — অবৈধ। ফল
Optional.empty, যার stream-এ কোনো উপাদান নেই।flatMap-এর কাছে ঢালার মতো কিছু নেই — উপাদানটি নিঃশব্দে উধাও।filter(Optional::isPresent).map(Optional::get)-এর মতো দুই ধাপ আরget()লাগল না। - “PUJA15” — বৈধ। আবার এক উপাদান, তালিকা এখন
[10, 15]. খেয়াল করুন, Stream প্রতিটি উপাদানকে পুরো pipeline দিয়ে একটি একটি করে নিয়ে যায় (পর্ব ৩৫)। - ফাঁকা লেখা — অবৈধ। কুপনের
Map-এ এই কোড নেই,getদিল null, আরofNullableসেটাকে ফাঁকা Optional বানাল — বাদ। toList()— শুধু মানগুলো। ফল[10, 15], টাইপList<Integer>— ভেতরে কোনো Optional নেই। Optional থাকে পথে, ফলাফলে নয়।
এবার এই পর্বের তিনটি আচরণ একসাথে প্রমাণ করুন — orElse বনাম orElseGet, Optional::stream, আর ফাঁকা নম্বর-তালিকার গড়:
import java.util.*;
import java.util.stream.*;
class Main {
static String loadDefault() {
System.out.println(" (ডিফল্ট ভাষা লোড হলো)");
return "bn";
}
static final Map<String, Integer> COUPONS = Map.of("EID10", 10, "PUJA15", 15);
static Optional<Integer> discount(String code) {
return Optional.ofNullable(COUPONS.get(code)); // Map.get null দেয়
}
public static void main(String[] args) {
Optional<String> lang = Optional.of("en");
System.out.println("orElse: " + lang.orElse(loadDefault()));
System.out.println("orElseGet: " + lang.orElseGet(Main::loadDefault));
List<Integer> valid = Stream.of("EID10", "FAKE", "PUJA15", "")
.map(Main::discount).flatMap(Optional::stream).toList();
System.out.println("বৈধ ছাড়: " + valid);
List<Integer> marks = List.of();
OptionalDouble avg = marks.stream().mapToInt(Integer::intValue).average();
System.out.println(avg + " → " + avg.orElse(0.0));
System.out.println(IntStream.of(70, 80, 90).average());
}
}আউটপুট ছয় লাইন: (ডিফল্ট ভাষা লোড হলো), orElse: en, orElseGet: en, বৈধ ছাড়: [10, 15], OptionalDouble.empty → 0.0, OptionalDouble[80.0]. প্রথম লাইনটিই প্রমাণ: মান "en" থাকা সত্ত্বেও orElse-এর জন্য loadDefault() চলেছে — আর তার বার্তা ছাপা হয়েছে orElse: en-এর আগে, কারণ পুরো লেখা জোড়া লাগানোর আগেই argument-এর হিসাব হয়। orElseGet-এর লাইনে বার্তা নেই। ফাঁকা marks-এর গড় OptionalDouble.empty; এখানে orElse(0.0) দিয়েছি শুধু দেখাতে — আসল অ্যাপে “নম্বর নেই” আলাদা করে দেখানোই সৎ। lang-কে Optional.empty() করে চালালে প্রথম দুই লাইনেই ডিফল্টের বার্তা আসবে (অনুশীলন ২)।
৮ · সঠিক ব্যবহার — কোথায় Optional, কোথায় নয়
Optional নিয়ে সবচেয়ে বেশি ভুল হয় “কোথায়” ব্যবহার করবেন তা নিয়ে। Java-র ভাষা-স্থপতি Brian Goetz নিজে Stack Overflow-তে লিখেছেন: Optional-এর উদ্দেশ্য ছিল লাইব্রেরির method-এর ফেরত-টাইপে “কোনো ফল নেই” বোঝানোর একটি সীমিত ব্যবস্থা, যেখানে null ব্যবহার করলে ভুল হওয়ার সম্ভাবনা খুব বেশি। এটি সব null-এর সাধারণ বদলি হিসেবে বানানো হয়নি। Oracle-এর JDK দলের Stuart Marks তাঁর বক্তৃতাগুলোতে এই নির্দেশনাকে কয়েকটি নিয়মে সাজিয়েছেন। মূল কথাগুলো:
✓ ফেরত-টাইপে
যে method-এর উত্তর স্বাভাবিকভাবেই না-ও থাকতে পারে: খোঁজা, পার্স করা, সর্বোচ্চ বের করা। এটাই Optional-এর জায়গা।
“না পেলে Optional.empty()”✗ field-এ
field নিজেই null হতে পারে — তখন null-এর সমস্যা দ্বিগুণ। Optional Serializable নয়, প্রতিটি object-এ বাড়তি একটি object লাগে। field-এ সাধারণ টাইপ রাখুন; getter চাইলে Optional.ofNullable(field) দিক।
✗ parameter-এ
send(String to, Optional<String> cc) হলে ডাকার কোডকে লিখতে হয় Optional.empty() — আর কেউ null-ও পাঠাতে পারে। বদলে দুটি overloaded method লিখুন (পর্ব ১২)।
✗ collection-এ বা collection-কে
Optional<List<Order>> নয় — অর্ডার না থাকলে ফাঁকা List ফেরত দিন, “কিছু নেই” বোঝাতে সেটাই যথেষ্ট। List<Optional<T>>-ও নয়; ফাঁকাগুলো বাদ দিন।
আরও কয়েকটি নিয়ম, যা অভিজ্ঞ Java ডেভেলপাররা মেনে চলেন:
- Optional ভ্যারিয়েবল বা ফেরত-মান কখনো null নয়। ফাঁকা বোঝাতে সবসময়
Optional.empty(). - শুধু একটি
ifএড়াতে Optional বানাবেন না।Optional.ofNullable(name).orElse("অতিথি")লেখার চেয়েname != null ? name : "অতিথি"বাObjects.requireNonNullElse(name, "অতিথি")(Java 9) সহজ — Optional শুধু চেইন বানাতে তৈরি করা অপচয়। - চেইনের ভেতরে আরেকটি Optional চেইন, বা কোথাও
Optional<Optional<T>>দেখলে কোড সম্ভবত বেশি জটিল হয়ে গেছে — ভেঙে ছোট method বানান। - Optional-এ
==বাsynchronizedনয়। Javadoc একে value-based class বলে: দুটি Optional তুলনা করুনequalsদিয়ে, কারণ একই মানের দুটি Optional একই object হবে কি না তার নিশ্চয়তা নেই।
খরচ কতটা?
প্রতিটি ভরা Optional একটি ছোট object — heap-এ জায়গা, garbage collector-এর কাজ। একটি method প্রতি সেকেন্ডে কয়েক লাখবার ডাকা হলে এটা চোখে পড়তে পারে। JIT compiler অনেক সময় escape analysis দিয়ে এই object পুরোপুরি বাদ দিতে পারে, যখন সেটি method-এর বাইরে যায় না — তবে সেটা নিশ্চিত নয়। বাস্তব নিয়ম: সাধারণ ব্যবসার কোডে এই খরচ নগণ্য, পরিষ্কারতার লাভ অনেক বড়; খুব গরম লুপে বা বিশাল array-এর প্রতিটি উপাদানে Optional রাখার আগে মেপে দেখুন। ফাঁকা Optional-এর জন্য নতুন object লাগে না — বর্তমান JDK-তে Optional.empty() একটিই ভাগ করা ফাঁকা object দেয় (তবে == দিয়ে এর ওপর নির্ভর করবেন না)।
Optional ছাড়াও “না থাকা” বোঝানোর পথ
| পরিস্থিতি | ভালো পথ |
|---|---|
| অনেকগুলো ফল, হয়তো একটিও নেই | ফাঁকা List/Set/Map — List.of() (পর্ব ৩০) |
| “না থাকলে” একটি নিরীহ আচরণ আছে | Null Object pattern — যেমন কোনো ছাড় না থাকলে ০% ছাড়ের একটি NoDiscount object, যার ওপর সব method নিরাপদে চলে |
| “না থাকার” কারণও জানাতে হবে (কুপন মেয়াদোত্তীর্ণ, নাকি অস্তিত্বই নেই, নাকি এই গ্রাহকের জন্য নয়) | sealed interface আর record দিয়ে নিজের result type — Valid, Expired, NotFound, আর switch-এ pattern matching (পর্ব ২৬)। Optional শুধু “আছে/নেই” বলে, কারণ বলে না। |
| ব্যর্থতা সত্যিই ব্যতিক্রমী | exception (পর্ব ২৯) |
| field বা parameter null হতে পারে কি না জানানো | @Nullable/@NonNull জাতীয় nullability annotation আর static analysis টুল |
শেষ সারিটি নিয়ে একটু বলা দরকার। বহু বছর ধরে বিভিন্ন লাইব্রেরি নিজেদের @Nullable annotation বানিয়েছে — JetBrains, Android, Spring, FindBugs — প্রত্যেকের অর্থ একটু আলাদা। JSpecify নামে একটি যৌথ উদ্যোগ (Google, JetBrains সহ বেশ কয়েকটি প্রতিষ্ঠান) এগুলোর একটি অভিন্ন মান তৈরি করেছে — এর 1.0 সংস্করণ প্রকাশিত হয় ২০২৪ সালে। এই annotation compiler নিজে যাচাই করে না; IDE বা বাড়তি টুল (যেমন NullAway) তা পড়ে সতর্ক করে। Optional-এর সাথে এর বিরোধ নেই: Optional ফেরত-টাইপে, annotation field আর parameter-এ।
field, parameter আর collection-এ নয়।
C-তে “না পাওয়া” বোঝাতে NULL pointer বা -1 ফেরত দেওয়া হয় — ঠিক সেই সমস্যা যা Optional সমাধান করতে চায় (দেখুন C-এর পয়েন্টার)। C++17-এর std::optional একই ধারণা, কিন্তু মান-টাইপ: আলাদা heap object লাগে না, তাই C++-এ class-এর member হিসেবেও স্বাভাবিক; বিস্তারিত C++-এর optional, variant আর any পর্বে। Python-এ মান না থাকলে None ফেরত দেওয়া হয়, আর Optional[str] বা str | None শুধু একটি type hint — কোনো বাক্স নয়, mypy-এর মতো টুল যাচাই করে (Python-এর type hint).
Optional.ofআরofNullable-এর পার্থক্য? —of-এ null দিলে সাথে সাথে NullPointerException;ofNullablenull পেলে ফাঁকা Optional দেয়।orElseআরorElseGet? —orElse-এর argument সবসময় হিসাব হয় (eager);orElseGet-এর Supplier চলে শুধু Optional ফাঁকা হলে (lazy).mapআরflatMap? — function সাধারণ মান দিলেmap; Optional দিলেflatMap, নইলেOptional<Optional<T>>.- ফাঁকা Optional-এ
get()ডাকলে? —NoSuchElementException: No value present; ভালো বিকল্পorElseThrow()(Java 10). - Optional field কেন নয়? — field নিজেই null হতে পারে, Optional Serializable নয়, বাড়তি object লাগে; এর নকশা শুধু ফেরত-টাইপের জন্য।
৯ · AI-এর যুগে — Optional-এর কোড রিভিউ করুন
AI-কে “null-safe করে লেখো” বললে প্রায়ই দেখবেন কোড ভরে গেছে Optional দিয়ে — কিন্তু ভুল জায়গায়। AI শিখেছে ইন্টারনেটের অসংখ্য উদাহরণ থেকে, যার বড় অংশ Java 8-এর শুরুর দিকের, যখন সবাই Optional নতুন শিখছিলেন। তাই isPresent()-get() জোড়া, Optional field, আর orElse(repository.findDefault())-এর মতো কোড খুব সাধারণ। compile হয়, demo-তে চলে — সমস্যা লুকিয়ে থাকে।
| AI যা লেখে | কেন সমস্যা | আপনি যা চাইবেন |
|---|---|---|
if (opt.isPresent()) return opt.get(); | null-যাচাইয়ের লম্বা রূপ; if ভুলে গেলে No value present | map/orElse/ifPresentOrElse, অথবা orElseThrow() |
private Optional<String> email; | field নিজেই null হতে পারে; Serializable নয়; বাড়তি object | সাধারণ field, getter-এ Optional.ofNullable(email) |
void save(Optional<Long> id) | ডাকার কোডে অস্বস্তি, আর null-ও পাঠানো যায় | দুটি overloaded method বা স্পষ্ট নাম |
Optional method-এ return null; | Optional-এর ওপর method ডাকতেই NPE | সবসময় Optional.empty() |
orElse(loadFromDb()), orElse(new Config()) | মান থাকলেও দামি কাজ চলে, side effect ঘটে | orElseGet(() -> ...) |
Optional<List<Order>> ফেরত | দুই রকম “নেই” — ফাঁকা Optional আর ফাঁকা List | সরাসরি ফাঁকা List |
AI-কে Optional-সংক্রান্ত কোড লেখাতে বা রিভিউ করাতে এই কাঠামো ব্যবহার করুন:
AI-এর কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।
প্র ০১Tony Hoare ২০০৯ সালে null-কে বলেছিলেন তাঁর “billion-dollar mistake”. ১৯৬৫ সালের ALGOL W-তে সিদ্ধান্তটি কেন নেওয়া হয়েছিল, আর এটি এত ব্যয়বহুল হলো কেন?
কে এবং কখন। Sir Charles Antony Richard Hoare — সবাই চেনেন Tony Hoare নামে — quicksort অ্যালগরিদমের আবিষ্কারক, আর প্রোগ্রামের সঠিকতা প্রমাণের যুক্তিবিদ্যা “Hoare logic”-এর জনক। ১৯৬৫ সালে তিনি Niklaus Wirth-এর সাথে ALGOL W ভাষা নিয়ে কাজ করছিলেন, যে ভাষায় object-এর reference-এর জন্য একটি পূর্ণাঙ্গ টাইপ সিস্টেম বানানোর চেষ্টা ছিল। তাঁর লক্ষ্য ছিল প্রতিটি reference-এর ব্যবহার যেন নিরাপদ হয়, compiler যেন তা যাচাই করে।
সিদ্ধান্তটি। সেই সময়ে তিনি null reference যোগ করলেন — একটি বিশেষ মান, যা যেকোনো reference টাইপের ভ্যারিয়েবলে রাখা যায় আর যার অর্থ “কিছুই দেখায় না”। ২০০৯ সালে লন্ডনের QCon সম্মেলনে “Null References: The Billion Dollar Mistake” শিরোনামের বক্তৃতায় তিনি বলেন, এটি রাখার কারণ ছিল একটাই: বাস্তবায়ন করা খুব সহজ ছিল। যে ভ্যারিয়েবলের এখনো মান নেই, বা যে লিংকড লিস্টের শেষ, তা বোঝাতে null সুবিধাজনক। তিনি স্বীকার করেন, এর ফলে পরের দশকগুলোতে অগণিত ভুল, নিরাপত্তা-ছিদ্র আর system crash হয়েছে, যার মোট ক্ষতি হয়তো বিলিয়ন ডলার। সংখ্যাটি প্রতীকী — কেউ হিসাব করে বের করেননি।
কেন এত ব্যয়বহুল। null-এর আসল দোষ টাইপ সিস্টেমে একটি গর্ত তৈরি করা। Customer c ঘোষণা বলে “c একজন গ্রাহক”, কিন্তু null থাকার কারণে আসল অর্থ “c একজন গ্রাহক, অথবা কিছুই না” — আর compiler এই দুইয়ের পার্থক্য করে না। ফলে টাইপ যে নিশ্চয়তা দেওয়ার কথা, তা দেয় না। C ভাষায় null pointer dereference করলে অনির্ধারিত আচরণ — প্রোগ্রাম crash করতে পারে, বা আরও খারাপ, ভুল মেমরি পড়ে চলতে থাকতে পারে, যা নিরাপত্তা-ছিদ্রের উৎস। Java অন্তত নিশ্চিত করে যে NullPointerException হবে, তাই সমস্যা নিরাপত্তা থেকে সরে এসে নির্ভরযোগ্যতায় দাঁড়ায়। তবু NPE বহু বছর ধরে Java অ্যাপের সবচেয়ে পরিচিত crash-এর কারণ।
পরবর্তী প্রতিক্রিয়া। null-এর সমস্যা দুইভাবে সমাধানের চেষ্টা হয়েছে। একটি হলো লাইব্রেরি-স্তরে বাক্স: Java-র Optional, Scala-র Option, Guava-র Optional. অন্যটি টাইপ সিস্টেমে: Kotlin, Swift, আর পরে C# ৮-এর nullable reference type, যেখানে “null হতে পারে” টাইপেরই অংশ। Java, পুরোনো কোডের সাথে মিল রাখার দায় থেকে, null সরাতে পারেনি; সে Optional যোগ করেছে, আর Java 14-এ NPE-র বার্তা উন্নত করেছে।
মূল উপলব্ধি: সহজে বাস্তবায়ন করা যায় বলে নেওয়া একটি ছোট নকশার সিদ্ধান্ত পরের ষাট বছর ধরে লাখো প্রোগ্রামারের খরচ বাড়িয়েছে। ভাষা বা API নকশায় “এখন সহজ” আর “পরে নিরাপদ”-এর মধ্যে বেছে নেওয়ার সময় এই ইতিহাস মনে রাখুন।
প্র ০২Java-র Optional হঠাৎ আসেনি। Haskell-এর Maybe আর ML-এর option type থেকে শুরু করে এই ধারণার ইতিহাস কী, আর Java-র রূপটি কোথায় আলাদা?
ML-এর option. ১৯৭০-এর দশকে এডিনবরায় Robin Milner-এর দলের তৈরি ML ভাষা পরিবার ছিল “typed functional programming”-এর প্রথম বড় প্রয়োগ। ML-এর টাইপ সিস্টেমে sum type আছে — একটি টাইপ, যার মান কয়েকটি নির্দিষ্ট রূপের যেকোনো একটি। এর সবচেয়ে সরল ব্যবহার 'a option: মান হয় NONE, নয়তো SOME x. Standard ML-এর ভিত্তি-লাইব্রেরি আর পরের OCaml-এ এটি দৈনন্দিন হাতিয়ার। গুরুত্বপূর্ণ কথা হলো, ML-এ null বলে কিছু নেই — reference সবসময় কিছু একটা দেখায়। তাই “না থাকা” বোঝানোর একমাত্র পথ option, আর compiler নিশ্চিত করে যে NONE-এর ক্ষেত্রটি সামলানো হয়েছে (pattern matching-এ কোনো ক্ষেত্র বাদ পড়লে সতর্ক করে)।
Haskell-এর Maybe. ১৯৯০-এর দশকে মান পাওয়া Haskell ভাষায় একই ধারণার নাম Maybe a, যার দুই রূপ Nothing আর Just x. Haskell এটিকে আরও এক ধাপ এগিয়ে নেয় monad-এর ধারণা দিয়ে: Maybe-এর ওপর “bind” অপারেশন (>>=) আছে, যা হুবহু Java-র flatMap — মান থাকলে পরের ধাপ চালাও, না থাকলে Nothing এগিয়ে দাও। এই পর্বের map-চেইন stepper-এ যা দেখেছেন, Haskell-এ সেটাই do-notation দিয়ে লেখা যায়, যেন সাধারণ ধারাবাহিক কোড।
JVM-এ আগমন। Scala (২০০৪) JVM-এ Option নিয়ে আসে, Some আর None subclass সহ। Google-এর Guava লাইব্রেরিও Java 8-এর আগেই একটি Optional class দিয়েছিল, আর বহু প্রজেক্ট সেটা ব্যবহার করত। Java 8-এর নকশাকারীরা এসব অভিজ্ঞতা দেখে java.util.Optional বানান।
Java-র রূপ কোথায় আলাদা। প্রথমত, Java-তে null এখনো আছে, তাই Optional টাইপের গ্যারান্টি দুর্বল: একটি Optional ভ্যারিয়েবল নিজেই null হতে পারে, compiler বাধা দেয় না। ML বা Haskell-এ এমন হওয়া অসম্ভব। দ্বিতীয়ত, Java-র Optional একটি final class, sum type নয় — Some/None subclass নেই, তাই pattern matching দিয়ে খোলা যায় না (Java 21-এর switch-এ Optional-এর জন্য বিশেষ pattern নেই)। তৃতীয়ত, নকশাকারীরা ইচ্ছে করে এর ব্যবহার সীমিত রেখেছেন — শুধু ফেরত-টাইপ — যেখানে Haskell বা Scala-তে Maybe/Option field, parameter, সবখানেই স্বাভাবিক।
মূল উপলব্ধি: Optional হলো ৫০ বছরের পুরোনো একটি গাণিতিক ধারণা, যা একটি null-ভরা ভাষায় লাইব্রেরি হিসেবে বসানো হয়েছে। সেজন্যই এর শক্তি সীমিত, আর সেজন্যই এর সঠিক ব্যবহারের নিয়মগুলো জানা এত জরুরি।
প্র ০৩Java-র Optional ইচ্ছে করে Serializable করা হয়নি, আর Brian Goetz বলেছেন এটি শুধু ফেরত-টাইপের জন্য। এই সীমাবদ্ধতা কেন — দুর্বলতা না সচেতন নকশা?
Goetz-এর কথা। Brian Goetz Oracle-এ Java ভাষার স্থপতি, Java 8-এর lambda প্রজেক্টের নেতৃত্ব দিয়েছিলেন। ২০১৪ সালে Stack Overflow-তে এক প্রশ্নের উত্তরে তিনি লেখেন, Optional-এর উদ্দেশ্য ছিল লাইব্রেরির method-এর ফেরত-টাইপে “কোনো ফল নেই” বোঝানোর একটি সীমিত ব্যবস্থা দেওয়া, যেখানে null দিলে ভুল হওয়ার সম্ভাবনা অত্যন্ত বেশি। তিনি স্পষ্ট বলেন, এটি সাধারণ-উদ্দেশ্যের Maybe টাইপ হিসেবে বানানো হয়নি, আর field বা method parameter-এ ব্যবহারের পরামর্শ দেন না। একই উত্তরে তিনি get() নামটিকে ভুল বলে স্বীকার করেন — নামটা আরও সাবধানী হওয়া উচিত ছিল। সেই উপলব্ধিই Java 10-এর orElseThrow()-এ পরিণত হয়েছে।
Serializable না করার কারণ। Java-র serialization (পর্ব ৩৯-এ আরও) object-কে byte-এ রূপান্তর করে, আর একবার কোনো class Serializable হলে তার ভেতরের গঠন কার্যত চিরস্থায়ী চুক্তি হয়ে যায় — পরের সংস্করণে বদলালে পুরোনো সেভ করা ডেটা পড়া যায় না। JDK দল জানত, ভবিষ্যতে Optional-কে হয়তো value class বানানো হবে (প্রশ্ন ৫), যার গঠন আলাদা হবে। Serializable করলে সেই পথ বন্ধ হয়ে যেত। তার ওপর, Serializable না হওয়াটাই একটি সংকেত: যে object serialize হয়, তার field-এ Optional রাখা যায় না — ঠিক যেটা নকশাকারীরা নিরুৎসাহিত করতে চেয়েছেন।
সমালোচনা। অনেকে এটাকে অসুবিধাজনক মনে করেন। Scala বা Kotlin থেকে আসা ডেভেলপাররা field-এ Option রাখতে অভ্যস্ত; JPA entity বা DTO-তে “ঐচ্ছিক” field সত্যিই থাকে। কিছু framework (যেমন Jackson-এর একটি module) Optional-কে JSON-এ লেখার ব্যবস্থাও দেয়। বিতর্কটি এতটাই দীর্ঘ যে Stuart Marks তাঁর একটি বক্তৃতার নাম দিয়েছিলেন “Optional: The Mother of All Bikesheds” — অর্থাৎ যে বিষয়ে সবাই মতামত দেয়।
সচেতন নকশা। তবু যুক্তিটা পরিষ্কার: Java-তে null সরানো যাবে না, তাই সবখানে Optional ছড়িয়ে দিলে দুই রকম “না থাকা” (null আর empty) সহাবস্থান করত, যা আরও বিভ্রান্তিকর। সীমিত ভূমিকা দিলে নিয়মটা সহজ হয়: API-র সীমানায়, যেখানে এক দলের লেখা কোড আরেক দল ডাকে, সেখানে Optional; ভেতরের কোডে সাধারণ টাইপ আর সাবধানতা।
মূল উপলব্ধি: একটি ফিচার কতটুকু করবে তা ঠিক করাও নকশা। Optional-এর সীমাবদ্ধতা দুর্ঘটনা নয় — এটি ভবিষ্যতের পরিবর্তনের পথ খোলা রাখা আর সঠিক ব্যবহারে ঠেলে দেওয়ার একটি সচেতন সিদ্ধান্ত।
প্র ০৪Kotlin null-এর সমস্যা সমাধান করেছে টাইপ সিস্টেমে — String আর String? আলাদা টাইপ। Java-র Optional-এর সাথে তুলনা করলে কোন পথ বেশি শক্তিশালী?
Kotlin-এর পথ। JetBrains-এর তৈরি Kotlin (১.০ সংস্করণ ২০১৬) JVM-এ চলে আর Java-র সাথে সরাসরি মিশে কাজ করে; ২০১৯ সালে Google একে Android উন্নয়নের পছন্দের ভাষা ঘোষণা করে। Kotlin-এ String টাইপের ভ্যারিয়েবলে কখনো null রাখা যায় না — compile error. null রাখতে হলে লিখতে হয় String?. আর String?-এর ওপর সরাসরি .length ডাকা যায় না; হয় আগে if (s != null) যাচাই করতে হয় (compiler তখন বুঝে নেয় ভেতরে s null নয় — একে বলে smart cast), নয়তো লিখতে হয় s?.length (safe call: s null হলে পুরো এক্সপ্রেশন null), আর ডিফল্টের জন্য s?.length ?: 0 (Elvis অপারেটর)। জোর করে “আমি জানি null নয়” বলতে আছে s!!, যা null হলে NPE দেয় — Java-র orElseThrow()-এর মতো।
তুলনা। Kotlin-এর a?.b?.c ?: "অজানা" হুবহু Java-র map(...).map(...).orElse("অজানা") চেইনের কাজ করে — কিন্তু কোনো বাক্স ছাড়া। কারণ চিহ্নটা টাইপে, object-এ নয়: চলার সময় String? সাধারণ একটি reference, যা null হতে পারে। তাই কোনো বাড়তি object তৈরি হয় না, field আর parameter-এ ব্যবহারে কোনো সমস্যা নেই, আর সবচেয়ে বড় কথা — compiler প্রতিটি ভ্যারিয়েবলের জন্য নিশ্চয়তা দেয়, শুধু ফেরত-মানের জন্য নয়। Java-তে Optional ফেরত দেওয়া method-ও null দিতে পারে; Kotlin-এ String ঘোষিত ফেরত-মান null দেওয়ার কোনো উপায় নেই।
দুর্বলতা কোথায়। Kotlin-এর গ্যারান্টি ভাঙে Java কোডের সীমানায়। Java method থেকে আসা String null হবে কি না Kotlin জানে না; এগুলোকে বলে “platform type”, আর সেখানে আবার NPE-র ঝুঁকি। এজন্যই Java লাইব্রেরিতে @Nullable annotation আর JSpecify-র মতো উদ্যোগ Kotlin ব্যবহারকারীদের কাছেও গুরুত্বপূর্ণ — Kotlin compiler এই annotation পড়ে টাইপ বোঝে। আর Optional-এর একটি সুবিধা আছে যা Kotlin-এর ?-এ নেই: Optional<Optional<T>>-এর মতো স্তর বানানো যায়, যা কিছু generic কোডে (যেমন ক্যাশে “মান নেই” আর “এখনো খোঁজা হয়নি” আলাদা করা) কাজের।
Java-র ভবিষ্যৎ। Project Valhalla-র অংশ হিসেবে Java-তে null-restricted টাইপের (যেমন String!) প্রস্তাব নিয়ে আলোচনা চলছে। তবে এই লেখার সময় এটি ভাষার স্থায়ী অংশ নয়, আর রূপ বদলাতে পারে।
মূল উপলব্ধি: টাইপ সিস্টেমে nullability সবচেয়ে শক্তিশালী আর সস্তা সমাধান, কিন্তু তা শুরু থেকে ভাষায় থাকতে হয়। পুরোনো ভাষায় পরে যোগ করার সহজ পথ হলো লাইব্রেরির বাক্স — Optional ঠিক সেই আপস।
প্র ০৫Project Valhalla Optional-কে একটি value class বানাতে পারে। সেটা হলে কী বদলাবে, আর আজকের কোনো নিয়ম কি অপ্রয়োজনীয় হয়ে যাবে?
আজকের খরচ। Java-তে প্রতিটি object-এর একটি পরিচয় (identity) আছে: দুটি object-এর ভেতরের মান এক হলেও == দিয়ে তারা আলাদা। এই পরিচয় বজায় রাখতে প্রতিটি object heap-এ আলাদা জায়গা নেয়, একটি header বহন করে, আর reference দিয়ে পৌঁছাতে হয়। একটি ভরা Optional মানে তাই একটি বাড়তি heap object, শুধু একটি reference মুড়ে রাখার জন্য। JIT-এর escape analysis অনেক সময় এই object বাদ দিতে পারে, কিন্তু Optional যদি method-এর বাইরে যায় বা কোনো collection-এ জমা হয়, তা পারে না।
Valhalla কী। Project Valhalla OpenJDK-র একটি দীর্ঘমেয়াদি প্রজেক্ট, ২০১৪ সাল থেকে চলছে, যার লক্ষ্য সংক্ষেপে “codes like a class, works like an int”. এর কেন্দ্রে value class-এর প্রস্তাব (JEP 401): এমন class, যার object-এর কোনো পরিচয় নেই — শুধু মান। পরিচয় না থাকলে JVM স্বাধীন: object-টিকে heap-এ না রেখে সরাসরি ভ্যারিয়েবল বা array-এর ভেতরে চ্যাপ্টা করে রাখতে পারে, যেমন int রাখে। এই লেখার সময় এটি preview বা পরীক্ষামূলক পর্যায়ে; কোন Java সংস্করণে স্থায়ী হবে, তা নিশ্চিত নয়।
Optional কেন প্রস্তুত। JDK দল আগে থেকেই এর জন্য পথ তৈরি করে রেখেছে। Optional-এর Javadoc তাকে “value-based class” বলে, আর সতর্ক করে যে এর ওপর ==, identity hash code বা synchronized ব্যবহার করা যাবে না — কারণ এসব পরিচয়ের ওপর নির্ভর করে। Integer, LocalDate-এর মতো আরও অনেক class-ও একই তালিকায়। যে কোড এই নিয়ম মানে, Optional value class হয়ে গেলে সেটি কোনো পরিবর্তন ছাড়াই চলবে, শুধু দ্রুত হবে। Serializable না করার সিদ্ধান্তও (প্রশ্ন ৩) এই সম্ভাবনা মাথায় রেখে।
কী বদলাতে পারে। Optional value class হলে বাড়তি allocation-এর খরচ প্রায় চলে যেতে পারে; List<Optional<T>> বা গরম লুপের Optional আর সমস্যা থাকবে না। তবে field আর parameter-এর নিয়ম মূলত খরচের জন্য নয় — একটি Optional field নিজে null হতে পারে কি না, পুরোনো API-র সাথে মিল, পড়ার সহজতা — এসব যুক্তি তখনও থাকবে, যদি না null-restricted টাইপও আসে। আর == না ব্যবহারের নিয়ম আরও জরুরি হবে, কারণ value class-এ ==-এর অর্থই বদলে যায়।
মূল উপলব্ধি: ভালো API নকশা ভবিষ্যতের জন্য দরজা খোলা রাখে। “Optional-এ == নয়” — এমন একটি ছোট সতর্কবার্তা আজ অপ্রয়োজনীয় মনে হতে পারে, কিন্তু সেটাই কাল JVM-কে অনেক বড় optimization করার স্বাধীনতা দেবে।
অনুশীলন
বেশিরভাগ কাজে নতুন কোড লিখতে হবে না — ওপরের কোড বদলে দেখা আর ভাবাই আসল কাজ।
-
আউটপুট অনুমান করুন: অংশ ৫-এর গ্রাহক প্রোগ্রামে করিমের ঠিকানা বদলে
new Address("নিউ মার্কেট", "চট্টগ্রাম")করুন, আরmain-এর প্রথম লাইনে নম্বর বদলে"01500000000"দিন। কোন কোন লাইন বদলাবে?প্রথম লাইন হবে
নেই— গ্রাহক পাওয়া যায়নি,map(Customer::name)চলেনি,orElse("নেই")এসেছে। আর01811000002 → চট্টগ্রাম. বাকি লাইন আগের মতো: সুমির ঠিকানা এখনো null, অচেনা নম্বর এখনো ফাঁকা। লক্ষ করুন,districtOf-এর একটি অক্ষরও বদলাতে হয়নি — ডেটা বদলেছে, যুক্তি একই। -
ফাঁকা হলে কী হয়: অংশ ৭-এর প্রোগ্রামে
Optional.of("en")বদলেOptional.empty()করুন। প্রথম কয়েকটি লাইন কেমন হবে?loadDefault()কতবার চলবে?চার লাইন:
(ডিফল্ট ভাষা লোড হলো),orElse: bn,(ডিফল্ট ভাষা লোড হলো),orElseGet: bn. দুটি পথেই ঠিক একবার করে — মোট দুইবার। অর্থাৎ ফাঁকা Optional-এ দুটির আচরণ এক; পার্থক্য কেবল মান থাকলে। এজন্যইorElse-এর ভুল সহজে ধরা পড়ে না — যে পরীক্ষায় মান নেই, সেখানে সব ঠিক দেখায়। -
রিভিউ করে আবার লিখুন: নিচের কোডে অন্তত চারটি সমস্যা খুঁজুন, তারপর ঠিক করে লিখুন:
class Customer { private Optional<String> email; Optional<String> getEmail() { return email; } } static Optional<Customer> find(String mobile) { Customer c = db.get(mobile); if (c == null) return null; return Optional.of(c); } static String contact(String mobile) { Optional<Customer> c = find(mobile); if (c.isPresent() && c.get().getEmail().isPresent()) return c.get().getEmail().get(); return loadSupportEmail(); }সমস্যা: (১)
Optionalfield — নিজেই null হতে পারে (constructor না দিলে null-ই থাকে), Serializable নয়; (২)findnull ফেরত দেয় —contact-এc.isPresent()ডাকতেই NPE; (৩)isPresent()+get(), তাও দুই স্তরে; (৪) পুরো জিনিসটাOptional.ofNullableআরflatMap-এর একটি চেইন। একটি ভালো রূপ:class Customer { private String email; // null হতে পারে Optional<String> getEmail() { return Optional.ofNullable(email); } } static Optional<Customer> find(String mobile) { return Optional.ofNullable(db.get(mobile)); } static String contact(String mobile) { return find(mobile) .flatMap(Customer::getEmail) // getEmail নিজেই Optional দেয় .orElseGet(Main::loadSupportEmail); // শুধু দরকার হলে }এখানে
flatMapকারণgetEmailOptional দেয়; আরorElseGetকারণloadSupportEmail()একটি method, যা হয়তো ফাইল বা ডেটাবেস পড়ে। -
নকশার সিদ্ধান্ত: একটি ব্যাংক অ্যাপে তিনটি method-এর ফেরত-টাইপ ঠিক করুন — Optional, ফাঁকা collection, exception, নাকি sealed result type: (ক) অ্যাকাউন্ট নম্বর দিয়ে অ্যাকাউন্ট খোঁজা; (খ) একটি অ্যাকাউন্টের গত মাসের সব লেনদেন; (গ) টাকা পাঠানো, যা ব্যালেন্স কম, দৈনিক সীমা পার, বা প্রাপকের অ্যাকাউন্ট বন্ধ — এই কারণগুলোতে ব্যর্থ হতে পারে।
(ক)
Optional<Account>— না পাওয়া স্বাভাবিক ফল, আর কারণ একটাই (নেই)। (খ)List<Transaction>— লেনদেন না থাকলে ফাঁকা তালিকা;Optional<List>হলে “ফাঁকা Optional” আর “ফাঁকা List” দুটির অর্থ আলাদা করার ঝামেলা। (গ) এখানে শুধু “সফল/ব্যর্থ” যথেষ্ট নয়, কেন ব্যর্থ তা ব্যবহারকারীকে জানাতে হবে, তাই Optional চলবে না। দুটি ভালো পথ: sealed interfaceTransferResultআর তার record —Success,InsufficientBalance,LimitExceeded,AccountClosed— যাswitch-এ সব ক্ষেত্র সামলাতে বাধ্য করে (পর্ব ২৬); অথবা পর্ব ২৯-এর মতো checked exception. যুক্তির মূল কথা: Optional শুধু “আছে/নেই”, কারণ বহন করে না।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমJava-তে মোবাইল নম্বর দিয়ে গ্রাহক খুঁজে তাঁর জেলা ছাপার Optional চেইনের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 🔎 findByMobile(m) — Optional<Customer> পাওয়া
- 🏠 map(Customer::address) — Optional<Address>
- 📍 map(Address::district) — Optional<String>
- 🛟 orElse("জেলা অজানা") — সাধারণ String
- 🖨️ println দিয়ে জেলা ছাপা
Java-তে String s = null; Optional<String> o = Optional.of(s); চালালে কী হয়?
- o হয় Optional.empty
- NullPointerException
- NoSuchElementException
- o-এর ভেতরে null রাখা হয়
Java-তে Optional.empty().get() চালালে কোন exception আর কোন বার্তা আসে?
- কোনো exception নয়, null ফেরত আসে
- NullPointerException
- NoSuchElementException: No value present
- IllegalStateException: Optional is empty
Java-তে Optional.of("en").orElse(load()) লেখা হলো, যেখানে load() একটি লাইন ছাপে। কী ঘটে?
- load() চলে, কিন্তু ফল "en"
- load() চলে না, ফল "en"
- load() চলে, আর ফল load()-এর মান
- compile error
Java-তে Optional<User> u আর Optional<Address> addressOf(User x) আছে। u.map(this::addressOf)-এর টাইপ কী, আর সমতল Optional<Address> পেতে কী লিখবেন?
- Optional<Address>; কিছু বদলাতে হবে না
- Stream<Address>; filter লিখতে হবে
- Optional<Optional<Address>>; orElse(null) দিলেই হবে
- Optional<Optional<Address>>; u.flatMap(this::addressOf) লিখতে হবে
Java-তে Optional ব্যবহারের প্রচলিত ভালো অভ্যাস অনুযায়ী নিচের কোনটি সঠিক জায়গা?
- একটি entity class-এর private field
- একটি method-এর parameter
- খোঁজার method-এর ফেরত-টাইপ, যেমন Optional<Customer> findByMobile(String m)
- অর্ডারের তালিকা ফেরত দিতে Optional<List<Order>>
Java-তে IntStream.of().average() — অর্থাৎ ০টি সংখ্যার গড় — কী দেয়?
- 0.0
- OptionalDouble.empty
- NaN
- ArithmeticException
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Optional = গায়ে লেখা বাক্স: “উত্তর না-ও থাকতে পারে” কথাটি ফেরত-টাইপে নিয়ে আসে, যাতে ডাকার কোড ভুলে না যায়। null চুপচাপ দূরে গিয়ে ফাটে; Optional সিদ্ধান্ত চায় শুরুতেই।
of(null হলে NPE),ofNullable(null হলে ফাঁকা),empty(). Optional-ফেরত method কখনো null দেবে না।isPresent+getনয়: ফাঁকা Optional-এget()দেয়NoSuchElementException: No value present. ডিফল্টেorElse(সস্তা মান) বাorElseGet(lazy, দামি হিসাব); না থাকা bug হলেorElseThrow().- চেইন: সাধারণ মান দিলে
map, Optional দিলেflatMap, শর্তেfilter: প্রথম ফাঁকা হওয়ামাত্র বাকি ধাপ চলে না। Stream-এ ফাঁকাগুলো বাদ দিতে.flatMap(Optional::stream). - শুধু ফেরত-টাইপে: field, parameter, collection-এ নয়; অনেক ফলের জন্য ফাঁকা collection, কারণ জানাতে sealed result type, শুধু একটি
if-এর জন্য Optional নয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৩৭ · ফাইল I/O — java.nio.fileপরবর্তী পর্বমডিউল ৬-এর শুরু: Path, Files আর ফাইল পড়া-লেখার আধুনিক পথ।
- Java প্রোগ্রামিং · কোর্স হোম৫৯টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- C++ · optional, variant আর anyপাশের ট্র্যাকএকই ধারণা মান-টাইপ হিসেবে — heap object ছাড়া।
- Python · type hint আর mypyগভীরে যানOptional[str] যেখানে বাক্স নয়, শুধু টাইপের চিহ্ন।