Java 21 Features
Java 21 (September 2023) तीसरी major LTS (Long-Term Support) release है Java 17 के बाद — इसमें Project Loom का सबसे बड़ा result, Virtual Threads, stable feature बनकर आया, जो Java में concurrency लिखने का तरीका fundamentally बदल देता है।
सबसे बड़े highlights: Virtual Threads (लाखों lightweight threads), Sequenced Collections (getFirst/getLast/reversed जैसे common methods), Record Patterns (records को destructure करना), और Pattern Matching for switch (आख़िरकार stable, guard conditions के साथ)।
कुछ features अभी भी preview में हैं (String Templates, Structured Concurrency, Unnamed Variables) — मतलब syntax future versions में थोड़ा बदल सकता है, लेकिन concept समझना interview के लिए ज़रूरी है।
// Virtual Thread — lakhon threads, halke wazan
Thread.ofVirtual().start(() -> System.out.println("Lightweight!"));
// Record Pattern — destructuring
record Point(int x, int y) {}
if (obj instanceof Point(int x, int y)) {
System.out.println(x + ", " + y);
}
// Sequenced Collection
List<String> list = new ArrayList<>(List.of("A", "B", "C"));
System.out.println(list.getFirst() + " " + list.getLast()); // A C- Java 21 = LTS release (Sept 2023)
- Virtual Threads = लाखों lightweight threads
- Sequenced Collections = getFirst/getLast/reversed
- Record Patterns = records destructure करना
- Pattern Matching for switch = अब stable है
Traditional Java thread एक OS thread का direct wrapper है — हर thread बनाने में memory (typically ~1MB stack) और OS resources लगते हैं, इसलिए एक JVM में practically कुछ हज़ार threads ही बन पाते हैं बिना system slow किए। High-concurrency applications (जैसे एक web server जो लाखों requests handle करे) इस limit से टकराते हैं।
Virtual thread JVM के अंदर ही manage होता है (OS thread नहीं) — बहुत हल्का है (kilobytes में, MB नहीं), और एक JVM में लाखों virtual threads बन सकते हैं बिना problem के। जब virtual thread block होता है (जैसे I/O wait), JVM उस underlying OS thread को free कर देता है दूसरे virtual threads के लिए — मतलब थोड़े से OS threads (carrier threads) बहुत सारे virtual threads को serve कर सकते हैं।
सबसे अच्छी बात: code लिखने का तरीका बिल्कुल same रहता है — Thread.ofVirtual().start(...) या Executors.newVirtualThreadPerTaskExecutor() use करो, बाकी सब (synchronized, try-catch, normal blocking calls) वैसे ही काम करता है जैसे normal thread में करता था। कोई नया async/reactive syntax सीखना नहीं पड़ता।
Interview trap: virtual threads के साथ ThreadLocal use करना careful रहना चाहिए — अगर लाखों virtual threads हर एक अपना ThreadLocal state रखें, memory usage बहुत बढ़ सकता है। इसीलिए Java 21 ने "Scoped Values" भी introduce किया (preview), जो इसका एक lighter alternative है।
- Memory में kilobytes, OS thread के ~1MB से काफी कम
- एक JVM में लाखों virtual threads चल सकते हैं
- Block होने पर carrier thread automatically free हो जाता है
- Existing blocking code बिना change किए काम करता है
// Ek virtual thread banao
Thread.ofVirtual().start(() -> {
System.out.println("Running on: " + Thread.currentThread());
});
// Ya ek executor se, jaise normal thread pool
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 100_000; i++) {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1)); // block hone par bhi thread cheap hai
return null;
});
}
} // 1 lakh threads bhi easily chal jaate hain!पुराने Java में List (जिसमें order होता है) और LinkedHashSet/LinkedHashMap (जिनमें भी predictable order होता है) के पास "first element" या "last element" निकालने का कोई common, consistent तरीका नहीं था — List.get(0) vs List.get(size()-1), या अलग-अलग collections में अलग methods।
Java 21 ने तीन नए interfaces दिए: SequencedCollection, SequencedSet, SequencedMap — जो List, LinkedHashSet, LinkedHashMap जैसे ordered collections को common methods देते हैं: getFirst(), getLast(), addFirst(), addLast(), removeFirst(), removeLast(), और reversed() (जो collection को reversed view में देखता है, बिना copy बनाए)।
Real-world use-case: जब किसी list का "most recent" या "oldest" item चाहिए हो (जैसे एक activity feed या undo history), पहले manual index math करना पड़ता था — अब getLast()/getFirst() से सीधा और readable code मिलता है।
- getFirst() / getLast() — पहला / आखरी element निकालो
- addFirst() / addLast() — शुरू / end में add करो
- removeFirst() / removeLast() — शुरू / end से हटाओ
- reversed() — reversed view, बिना copy बनाए
List<String> names = new ArrayList<>(List.of("Aman", "Riya", "Zoya"));
System.out.println(names.getFirst()); // Aman
System.out.println(names.getLast()); // Zoya
names.addFirst("Aarav");
System.out.println(names.reversed()); // [Zoya, Riya, Aman, Aarav]
// LinkedHashMap bhi ab yehi methods deta hai:
LinkedHashMap<String, Integer> scores = new LinkedHashMap<>();
scores.put("A", 90);
scores.putFirst("Z", 100); // naya method — sabse pehle daalta haiJava 17 में records आए थे data classes के लिए, और Java 16+ में instanceof pattern matching आई थी। Java 21 ने इनको combine कर दिया — अब record के अंदर के fields को सीधा "destructure" (निकाल) कर सकते हो एक instanceof या switch check में, बिना record.field() call किए।
जैसे अगर record Point(int x, int y) है, तो if (obj instanceof Point(int x, int y)) लिखने से, अगर obj एक Point है, तो x और y directly available हो जाते हैं — record के अंदर record भी हो सकता है (nested destructuring), जो deeply nested data structures के लिए बहुत clean code देता है।
Interview में record patterns को sealed classes के साथ combine करके पूछा जा सकता है — दोनों मिलकर functional languages जैसा "algebraic data type" pattern देते हैं, जहाँ data की shape और उसको process करने का logic दोनों explicit और type-safe होते हैं।
record Point(int x, int y) {}
record Line(Point start, Point end) {}
Object obj = new Point(3, 4);
// Record pattern — seedha destructure
if (obj instanceof Point(int x, int y)) {
System.out.println("x=" + x + ", y=" + y);
}
// Nested record pattern
Object line = new Line(new Point(0, 0), new Point(3, 4));
if (line instanceof Line(Point(int x1, int y1), Point(int x2, int y2))) {
System.out.println("Line from (" + x1 + "," + y1 + ") to (" + x2 + "," + y2 + ")");
}Java 17 में यह preview feature था, Java 21 में आख़िरकार stable/final हो गया। अब switch expression/statement में type-based branching, record destructuring, और guard conditions (when clause) सब एक साथ use कर सकते हो।
null भी अब switch में directly handle हो सकता है (case null ->) — पहले switch null पर NullPointerException देता था, इसलिए अलग से null check करना पड़ता था। Guard condition (when) से एक type-match के अंदर भी extra condition लगा सकते हो, जैसे case Integer i when i > 0 -> ....
Guard conditions का order matter करता है — switch ऊपर से नीचे cases check करता है, इसलिए specific guard वाले cases (जैसे when i > 0) को generic case से पहले रखना ज़रूरी है, वरना generic case पहले match हो जाएगा।
- Type patterns — case Integer i -> ...
- Record patterns — case Point(int x, int y) -> ...
- Guard conditions — case Integer i when i > 0 -> ...
- case null -> — अब directly handle हो सकता है
Object obj = 42;
String result = switch (obj) {
case null -> "It's null";
case Integer i when i > 0 -> "Positive integer: " + i;
case Integer i -> "Non-positive integer: " + i;
case String s -> "String: " + s;
case Point(int x, int y) -> "Point at (" + x + "," + y + ")"; // record pattern bhi
default -> "Unknown";
};String concatenation (+ से जोड़ना) या String.format() दोनों थोड़े clunky हैं लंबी strings के लिए। String Templates एक नया syntax लाती हैं — STR."Hello \{name}!" जैसा — जिसमें \{...} के अंदर directly Java expressions डाल सकते हो, और वो automatically evaluate होके result में embed हो जाते हैं।
यह एक preview feature है Java 21 में (मतलब --enable-preview flag के साथ compile/run करना पड़ता है, और future versions में change भी हो सकता है) — लेकिन यह दिखाता है Java string handling को कहाँ ले जा रहा है: readable, type-safe, और SQL-injection जैसी चीज़ें भी easily prevent करने वाले custom template processors (जैसे SQL processor) बन सकते हैं।
Interview में पूछा जा सकता है STR क्या है — यह एक built-in "template processor" है (StringTemplate.Processor interface implement करता है)। Developers अपना खुद का processor भी बना सकते हैं, जैसे एक जो automatically HTML-escape करे XSS से बचने के लिए।
String name = "Aarav";
int age = 25;
// Purana tareeka:
String s1 = "Hello " + name + ", you are " + age + " years old";
String s2 = String.format("Hello %s, you are %d years old", name, age);
// String Template (preview, Java 21):
String s3 = STR."Hello \{name}, you are \{age} years old";
// Expressions bhi chal sakti hain:
String s4 = STR."Next year you'll be \{age + 1}";जब एक task को complete करने के लिए multiple sub-tasks parallel में चलाने पड़ते हैं (जैसे एक user profile fetch करने के लिए अलग-अलग services से data, price और inventory), उन्हें manually manage करना (एक fail हो जाए तो बाकी cancel करना, सबका wait करना, errors handle करना) error-prone होता है।
StructuredTaskScope sub-tasks को एक "structured" तरीके से treat करता है — parent task तब तक खत्म नहीं होता जब तक सारे child tasks (जो उसने start किए) खत्म न हो जाएं, और अगर एक child fail हो, पूरा group automatically cancel हो सकता है (fail-fast)। यह virtual threads के साथ मिलकर concurrent code को बहुत safer और readable बनाता है।
यह traditional try-catch-finally के साथ manual thread management से बहुत ज़्यादा readable है — पुराने तरीके में अगर एक task fail हो जाए, दूसरे tasks को manually cancel करना पड़ता था और thread leaks का risk रहता था, StructuredTaskScope यह सब automatically handle कर लेता है।
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> user = scope.fork(() -> fetchUser());
Future<String> orders = scope.fork(() -> fetchOrders());
scope.join(); // dono ka wait karo
scope.throwIfFailed(); // agar koi fail hua, exception throw karo
System.out.println(user.resultNow() + orders.resultNow());
} // scope band hote hi saare threads cleanup ho jaate hainJava 17 में ZGC (Z Garbage Collector) experimental था, low-latency के लिए design किया गया (pause times consistently 10ms से कम) — लेकिन यह "generational" नहीं था, मतलब young और old objects को अलग treat नहीं करता था, जिससे कुछ workloads में efficiency कम हो जाती थी।
Java 21 में ZGC "generational" बन गया (दूसरे modern GCs जैसे G1 की तरह) — young generation (जहाँ ज़्यादातर objects जल्दी "die" हो जाते हैं) को ज़्यादा बार और efficiently collect करता है, old generation को कम बार। इससे throughput और memory efficiency दोनों बेहतर हुए, बिना low-latency guarantee खोए।
Interview में पूछा जा सकता है कि generational ZGC क्यों ज़रूरी था जब G1 already generational था — जवाब: G1 generational होने के बावजूद ZGC जैसा ultra-low pause time नहीं दे सकता था; Generational ZGC दोनों चीज़ें — low pause time AND generational efficiency — एक साथ देता है।
| GC | Focus |
|---|---|
| G1 (default) | Balanced throughput + pause time, general-purpose |
| ZGC (Java 17) | Ultra-low pause time, non-generational |
| Generational ZGC (Java 21) | Ultra-low pause time + better throughput (generational) |
कभी-कभी एक variable declare करना पड़ता है जिसका use ही नहीं करना (जैसे catch (Exception e) में अगर e use ही नहीं हो रहा, या record pattern में सिर्फ कुछ fields चाहिए बाकी नहीं)। Java 21 preview में underscore (_) को "unnamed variable" के रूप में use कर सकते हो — बताता है कि यह value जान-बूझकर ignore की जा रही है।
Record patterns में भी काम आता है — जैसे अगर Point(int x, int y) से सिर्फ x चाहिए, तो case Point(int x, var _) लिख सकते हो, मतलब y को explicitly "मुझे इसकी परवाह नहीं" बता सकते हो, code पढ़ने वाले को clarity मिलती है।
यह feature ख़ास तौर पर बड़े codebases में useful है जहाँ static analysis tools (जैसे SonarQube) "unused variable" warnings देते हैं — underscore (_) use करके तुम explicitly बता देते हो कि variable जान-बूझकर ignore हो रहा है, warning भी clear हो जाती है।
// catch block mein unused exception:
try {
riskyOperation();
} catch (IOException _) {
System.out.println("Kuch to gaya galat, details ki zaroorat nahi");
}
// Record pattern mein sirf zaroori field:
if (obj instanceof Point(int x, var _)) {
System.out.println("x = " + x + " (y ignore kiya)");
}