Java 8+: Lambda और Streams
Lambda expression (param) -> { code } से एक functional interface (जिसमें सिर्फ़ एक abstract method हो) को छोटे तरीक़े से implement करते हैं।
Stream API collections पर filter, map, sort जैसे operations chain करके लिखने देता है — बिना manual loops लिखे, code छोटा और readable बन जाता है।
List<Integer> nums = List.of(1, 2, 3, 4, 5, 6);
List<Integer> evenSquares = nums.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * n)
.toList();
// [4, 16, 36]Lambda expression ek naam-less function hai jo ek functional interface ko turant implement karta hai. Syntax kai roop mein ho sakta hai: (a, b) -> a + b (multiple params), x -> x * x (single param, bina parentheses), () -> 42 (no params), ya { } ke andar multiple statements ke saath (a, b) -> { int sum = a+b; return sum; }.
Lambda apne aas-paas ke local variables ko "capture" kar sakta hai, lekin sirf agar wo "effectively final" hon — matlab capture hone ke baad unki value kabhi badle nahi. Ye rule isliye hai kyunki lambda kisi doosre thread mein baad mein bhi chal sakta hai, aur agar original variable change ho jaaye to confusion ho jaayega.
int factor = 10; // effectively final
Function<Integer,Integer> multiply = n -> n * factor;
System.out.println(multiply.apply(5)); // 50
// factor = 20; <- ye line likhoge to lambda mein error aa jaayega!Java 8 ne java.util.function package mein ready-made functional interfaces diye taaki har chhoti cheez ke liye khud interface na banana pade: Predicate<T> (test(T): boolean — filtering ke liye), Function<T,R> (apply(T): R — ek type se doosre mein convert), Consumer<T> (accept(T): void — value leke kuch karo, return nahi), Supplier<T> (get(): T — bina input ke value produce karo).
Do-argument versions bhi hain: BiFunction<T,U,R> (do inputs, ek output), BiConsumer<T,U>, BiPredicate<T,U>. UnaryOperator<T> (Function jaha input aur output same type ho) aur BinaryOperator<T> (BiFunction jaha sab types same ho) special cases hain.
| Interface | Method | Kaam |
|---|---|---|
| Predicate<T> | test(T): boolean | Condition check (filtering) |
| Function<T,R> | apply(T): R | T ko R mein convert |
| Consumer<T> | accept(T): void | T leke action lo, return nahi |
| Supplier<T> | get(): T | Bina input, T produce karo |
| UnaryOperator<T> | apply(T): T | Function jaha input=output type |
| BinaryOperator<T> | apply(T,T): T | BiFunction jaha sab same type |
Predicate<String> isLong = s -> s.length() > 5;
Function<String, Integer> toLength = String::length;
Consumer<String> print = System.out::println;
Supplier<Double> randomVal = Math::random;
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;Jab lambda sirf ek existing method call kar raha ho, method reference (::) se aur chhota likh sakte ho. Type 1 — Static method: ClassName::staticMethod (jaise Integer::parseInt). Type 2 — Particular object ka instance method: object::instanceMethod (jaise System.out::println).
Type 3 — Arbitrary object ka instance method (class ke naam se, first argument automatically "this" ban jaata hai): ClassName::instanceMethod (jaise String::toUpperCase, jisme string khud pehla argument ban jaata hai). Type 4 — Constructor reference: ClassName::new, naya object banane ke liye.
list.forEach(System.out::println); // Type 2
List<String> upper = list.stream().map(String::toUpperCase).toList(); // Type 3
Supplier<ArrayList<String>> maker = ArrayList::new; // Type 4 (constructor)Sabse common: collection.stream() (List/Set se). Arrays.stream(array) array se. Stream.of(1, 2, 3) direct values se. IntStream.range(1, 10) numbers ki sequence ke liye (range() end exclude karta hai, rangeClosed() include karta hai).
Infinite streams bhi ban sakte hain: Stream.generate(supplier) (har baar supplier call hota hai) aur Stream.iterate(seed, fn) (pehli value se shuru karke fn baar-baar apply hota hai) — inke saath limit() lagana zaroori hai warna infinite chalte rahenge.
Stream<Integer> s1 = List.of(1,2,3).stream();
IntStream s2 = IntStream.rangeClosed(1, 5); // 1,2,3,4,5
Stream<Integer> s3 = Stream.iterate(1, n -> n * 2).limit(5); // 1,2,4,8,16Intermediate operations naya stream return karte hain aur "lazy" hote hain (jab tak terminal operation na aaye, chalte nahi). filter(predicate) — condition match karne wale elements rakho. map(function) — har element ko transform karo. flatMap(function) — nested streams (jaise List<List<T>>) ko flatten karke ek single stream banao.
distinct() — duplicates hatao. sorted() ya sorted(comparator) — order lagao. limit(n) — pehle n elements. skip(n) — pehle n elements chhodo. peek() — bina change kiye beech mein dekhne/debug karne ke liye.
List<String> names = List.of("Riya","Aman","Riya","Zoya");
names.stream()
.distinct()
.filter(n -> n.length() > 3)
.sorted()
.forEach(System.out::println); // Aman, Riya, ZoyaTerminal operations stream ko "consume" karke final result dete hain — ek stream sirf ek baar consume ho sakta hai. forEach(consumer) — har element par action. collect(collector) — result ko List/Set/Map mein badlo (agla subtopic dekho). reduce(identity, accumulator) — sab elements ko combine karke ek single value banao (jaise sum, product).
count() — kitne elements hain. anyMatch/allMatch/noneMatch(predicate) — boolean checks. findFirst()/findAny() — Optional<T> return karta hai. min()/max(comparator) — bhi Optional<T> return karte hain.
| Intermediate | Terminal | |
|---|---|---|
| Return karta hai | Naya Stream | Final result (List, int, boolean...) |
| Lazy? | Haan (terminal tak nahi chalta) | Chalte hi execute hota hai |
| Kitni baar chain kar sakte ho | Multiple times | Sirf ek terminal per stream |
| Examples | filter, map, sorted, distinct | collect, reduce, forEach, count |
List<Integer> nums = List.of(1,2,3,4,5);
int sum = nums.stream().reduce(0, (a,b) -> a+b); // 15
boolean hasEven = nums.stream().anyMatch(n -> n%2==0); // true
Optional<Integer> max = nums.stream().max(Integer::compareTo);Collectors class stream ko useful shapes mein collect karne ke helper methods deta hai. toList()/toSet() — simple collection. joining(", ") — Strings ko ek saath jodta hai (separator ke saath). groupingBy(fn) — elements ko key ke hisaab se Map<Key, List<T>> mein baantta hai (jaise students ko grade ke hisaab se group karna).
partitioningBy(predicate) — sirf true/false do groups mein baantta hai (Map<Boolean, List<T>>). counting(), summingInt(), averagingDouble() — aggregate calculations, aksar groupingBy ke saath combine hote hain.
Map<Boolean, List<Integer>> parts = nums.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
// {false=[1,3,5], true=[2,4]}
String joined = names.stream().collect(Collectors.joining(", "));
Map<Integer, List<String>> byLength = names.stream()
.collect(Collectors.groupingBy(String::length));collection.parallelStream() (ya .stream().parallel()) automatically kaam ko multiple CPU cores mein baant deta hai — bade datasets par speed badh sakti hai. Lekin: shared mutable state (jaise ek normal ArrayList mein baahar se add karna) ke saath use karna dangerous hai (race conditions), aur chhote datasets ke liye overhead ki wajah se ulta slow ho sakta hai.
Rule of thumb: sirf tab use karo jab dataset bada ho, operations CPU-intensive hon, aur elements ek-doosre par depend na karte hon (independent operations).
long count = hugeList.parallelStream()
.filter(x -> isPrime(x))
.count();Optional<T> null ke bajaye "value ho sakti hai ya nahi" ko explicitly represent karta hai. Banane ke tarike: Optional.of(value) (value null nahi honi chahiye), Optional.ofNullable(value) (null ho sakta hai), Optional.empty() (khaali).
Use karne ke tarike: isPresent()/isEmpty() check karo, get() (risky — agar empty ho to exception, avoid karo), orElse(default) fallback do, orElseGet(supplier) (lazy fallback, tabhi compute hota hai jab zaroorat ho), orElseThrow() (custom exception throw karo). map()/filter() se chain kar sakte ho bina manually null-check kiye.
Optional<String> name = Optional.ofNullable(getNameOrNull());
String result = name
.map(String::toUpperCase)
.filter(n -> n.length() > 2)
.orElse("GUEST");default method interface mein body ke saath hota hai — implementing classes ko override karna optional hai. static method interface ke naam se directly call hota hai (Interface.method()), implementing class ke through nahi. Ye Java 8 se pehle nahi tha — agar ek interface mein naya method chahiye hota tha, saari implementing classes todni padti thi.
Agar ek class do interfaces implement karti hai aur dono mein *same* default method ho ("diamond problem"), Java compile error deta hai — class ko explicitly override karke batana padta hai kaunsa version (ya apna khud ka) use karna hai.
interface A { default void greet() { System.out.println("Hi from A"); } }
interface B { default void greet() { System.out.println("Hi from B"); } }
class C implements A, B {
public void greet() { A.super.greet(); } // explicitly choose karna padta hai
}Purana java.util.Date aur Calendar mutable the (thread-unsafe) aur confusing API (months 0-indexed!). Java 8 laaya java.time package: LocalDate (sirf date, jaise 2026-07-28), LocalTime (sirf time), LocalDateTime (dono), aur ye sab *immutable* hain — koi bhi "modify" method actually ek naya object return karta hai.
Period date differences ke liye (years/months/days), Duration time differences ke liye (hours/minutes/seconds). DateTimeFormatter custom formatting ke liye (jaise "dd-MM-yyyy").
LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(2010, 5, 15);
Period age = Period.between(birthday, today);
System.out.println(age.getYears() + " saal ka");
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd-MM-yyyy");
System.out.println(today.format(fmt));CompletableFuture async (background) tasks likhna aasan banata hai bina manually threads manage kiye. supplyAsync(supplier) ek task background mein shuru karta hai aur turant ek "future" (promise jaisa) return karta hai. thenApply() result ko transform karta hai (jab ready ho), thenAccept() result consume karta hai, thenCombine() do async tasks ke results ko jodta hai.
CompletableFuture<Integer> future = CompletableFuture
.supplyAsync(() -> slowCalculation())
.thenApply(result -> result * 2);
System.out.println(future.get()); // wait karke final resultJava 8 ne Comparator interface mein handy default/static methods add kiye: Comparator.comparing(keyExtractor) se ek field ke basis par sort. thenComparing() se secondary sort criteria (jaise pehle name se, phir age se). reversed() order ulta karta hai. naturalOrder()/reverseOrder() built-in Comparable order ke liye.
students.sort(
Comparator.comparing((Student s) -> s.grade)
.thenComparing(s -> s.name)
.reversed()
);StringJoiner — Strings ko delimiter, prefix, suffix ke saath jodने ka utility (Collectors.joining() internally isi ka use karta hai). Base64 class — java.util.Base64 se encoding/decoding built-in mil gayi, bina external library ke. Repeating Annotations — ab ek hi annotation ko ek jagah multiple baar laga sakte ho.
StringJoiner sj = new StringJoiner(", ", "[", "]");
sj.add("A").add("B").add("C");
System.out.println(sj); // [A, B, C]
String encoded = Base64.getEncoder().encodeToString("Hello".getBytes());