একটা মজার experiment দিয়ে শুরু করি। এই code টা আজকের যেকোনো JDK (Java Development Kit) এ চালান।
Integer i = 1, j = 1;
System.out.println(i == j); // true
Integer x = 1996, y = 1996;
System.out.println(x == y); // false
একই class, একই value। অথচ ১ এর জন্য true, ১৯৯৬ এর জন্য false। অদ্ভুত লাগছে? অনেক Java developer এই behavior এ একবার না একবার বিভ্রান্ত হয়েছেন। এই বিভ্রান্তির পেছনে আছে object identity। আর এই identity নিয়েই বদল আনছে Project Valhalla। JDK 28 এ আসছে JEP (JDK Enhancement Proposal) 401, Value Objects (Preview)।
Object identity মানে কী
Java তে প্রতিটা object এর একটা নিজস্ব আলাদা অস্তিত্ব আছে। এই অস্তিত্বের নাম identity। দুটো object এর data একদম এক হলেও, Java ওদের আলাদা আলাদা মনে করে। জমজ ভাইয়ের মতো। চেহারা এক, কিন্তু মানুষ আলাদা।
উপরের example এ ১ এর জন্য true আসার কারণ হলো Integer cache। Java ছোট সংখ্যার জন্য একটা নির্দিষ্ট object reuse করে। ১ এর জন্য তাই দুটো variable একই object কে দেখায়। কিন্তু ১৯৯৬ cache এর বাইরে। প্রতিবার আলাদা object তৈরি হয়, আর identity ভিন্ন হওয়ায় == এ false আসে।
LocalDate দিয়ে আরেকটা উদাহরণ দেখা যাক।
LocalDate d1 = LocalDate.of(1996, 1, 23);
LocalDate d3 = d1.plusYears(30).minusYears(30);
System.out.println(d1.equals(d3)); // true
System.out.println(d1 == d3); // false
d1 আর d3 দুটোই ১৯৯৬ সালের ২৩ জানুয়ারি represent করছে। equals বলছে true, কিন্তু == বলছে false। কারণ দুটো আলাদা object, দুটো আলাদা identity।
Identity এর খরচ কী
Identity শুধু বিভ্রান্তিই তৈরি করে না, খরচও বাড়ায়। প্রতিটা object তৈরি হলে JVM (Java Virtual Machine) কে memory বরাদ্দ করতে হয়। সেই memory আবার read করতে হয় object ব্যবহারের সময়। App যত object বানায়, memory তত খরচ হয়। GC (Garbage Collector) তত বেশি কাজ করে।
উদাহরণ দিলে পরিষ্কার হবে। int array আর LocalDate array এর পার্থক্য দেখুন। int[5] হলো memory তে পাঁচটা সংখ্যা, একটানা। কিন্তু LocalDate[5] হলো পাঁচটা pointer। প্রতিটা pointer আবার আলাদা আলাদা জায়গায় পড়ে থাকা object কে দেখায়। Data তো প্রায় একই রকম, মাত্র ৪৮ bit। অথচ memory খরচ বহুগুণ বেশি। আর object গুলো memory তে ছড়িয়ে ছিটিয়ে থাকলে CPU (Central Processing Unit) cache locality ও খারাপ হয়।
Java এই খরচ কমানোর চেষ্টা করে নিজে থেকেই কিছু optimization দিয়ে। কিন্তু object field বা array তে পড়লে সেটা আর কাজ করে না।
JEP 401 কী আনছে
Project Valhalla এই সমস্যার সমাধান করছে। ১০ বছরের বেশি সময় ধরে এর design নিয়ে আলোচনা চলেছে। অবশেষে ২০২৬ সালের July তে OpenJDK mainline এ integrate হলো। আর ২০২৭ সালের March এ JDK 28 এ preview feature হিসেবে আসছে।
JEP 401 এ নতুন value modifier আসছে।
value class Point {
private int x;
private int y;
}
value class এর object কে বলে value object। এদের কোনো identity নেই। সব fields implicitly final। মানে, object বানানোর পর আর বদলানো যাবে না। value object শুধু field value দিয়ে চেনা যায়। ৫ সংখ্যার মতো। ৫ যেখানেই থাকুক, ৫ ই। “এই ৫” আর “ঐ ৫” বলে কিছু নেই।
== operator এর নতুন behavior
সবচেয়ে চোখে পড়ার মতো পরিবর্তনটা হলো == operator এ। Identity object এর জন্য == এর behavior আগের মতোই থাকবে। কিন্তু value object এর জন্য == এখন field value দেখে compare করবে, identity না।
মানে, JDK 28 এ preview চালালে উপরের Integer উদাহরণে 1996 == 1996 এখন true হবে। ১ আর ১৯৯৬ এর মধ্যে কোনো পার্থক্য থাকবে না।
কিন্তু এর মানে এই না যে == কে equals এর বদলে use করা যাবে। JEP 401 স্পষ্ট বলছে, == কে equals এর replacement বানানো হয়নি। Value object এর internal state সবসময় এক নাও হতে পারে, যেটা object টা represent করছে। তাই equals দিয়ে compare করার অভ্যাসই ঠিক আছে। আর String এখনো identity class। String এর behavior বদলায়নি।
কোন class গুলো value class হবে
Preview চালু করলে Java Platform API (Application Programming Interface) এর ৩০ টা class value class হয়ে যাবে। এর মধ্যে আছে:
- java.lang: Integer, Long, Float, Double, Byte, Short, Character, Boolean
- java.util: Optional, OptionalInt, OptionalLong, OptionalDouble
- java.time: LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Duration
মানে, এই বদলটা আপনি নিজের daily code এ-ই সবচেয়ে আগে টের পাবেন। দুটো Integer object, একই value, এখন == এ true।
যেসব জিনিস ভাঙতে পারে
কিছু behavior বদলাবে, তাই আগে থেকে জেনে রাখা ভালো। Value object এর উপর synchronized করা যাবে না। Synchronized দিলে IdentityException আসবে। Reference (যেমন WeakReference) বানালেও একই exception আসবে।
আরেকটা জিনিস, preview দিয়ে compile করলে preview দিয়েই run করতে হবে।
Performance সুবিধা
আসল সুবিধাটা হলো, JVM এখন value object কে যেভাবে খুশি represent করতে পারে। JVM একটা value object কে flatten করতে পারে। মানে, object এর data সরাসরি field বা array element এ বসে যাবে। Pointer নেই, object header নেই। LocalDate array এখন int array এর মতোই হতে পারে, ৬৪ bit এর মধ্যে null flag সহ। Method argument হলে JVM register বা stack এও রাখতে পারে।
Memory কম লাগবে, GC কম কাজ করবে, CPU cache locality ভালো হবে। তবে কোনো guarantee নেই। প্রথম দিকে পুরনো নিয়মেই কাজ করবে, পরে ধীরে ধীরে optimize হবে। আর Brian Goetz, Java language architect, বলেছেন feature টা আগামী LTS (Long-Term Support) release এও preview ই থাকতে পারে।
Preview চালিয়ে দেখুন
নিজে হাতে try করার চেয়ে ভালো আর কিছু নেই। JDK 28 early access build নামিয়ে নিচের command টা চালান।
jshell --enable-preview
তারপর 1996 == 1996 চালিয়ে দেখুন। যেটা আজ false, সেটা এখন true। নিজে হাতে দেখলে identity আর value object এর পার্থক্য পরিষ্কার হয়ে যাবে।
শেষ করি একটা প্রশ্ন দিয়ে
আপনি কি কখনো Integer cache এর এই অদ্ভুত behavior এ বিভ্রান্ত হয়েছেন? JDK 28 এ value object এসে এই বিভ্রান্তিগুলো দূর হবে। Java এর object model এ এটা বড় পরিবর্তন। Preview feature হিসেবে experiment করে দেখার সময় এখনই।
বিস্তারিত পড়তে পারেন: