0% found this document useful (0 votes)
7 views21 pages

Java 8 Streams

About Java 8 stream

Uploaded by

jtech0435
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views21 pages

Java 8 Streams

About Java 8 stream

Uploaded by

jtech0435
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 21

Java 8 Streams

Java Stream (Java 8)


1. Introduction

• Stream API → Introduced in Java 8, package: java.util.stream.


• Stream = Sequence of elements + Functional operations (like filter, map, reduce).
• It works on Collections, Arrays, I/O Channels.
• Stream does not modify the source collection; it only processes and produces results.

2. Why Streams?

• Avoid unnecessary loops & boilerplate code.


• Declarative & functional style programming.
• Can be parallelized easily for performance.

Collection vs Stream

• Collection = store and manage group of objects.


• Stream = process those objects.

3. Key Components of Stream

• Source → collection, array, file, etc.


• Operations →
o Intermediate (return another stream → lazy).
o Terminal (produce result → consumes stream).
• Pipeline → chaining of operations.
• Internal Iteration → handled by stream itself.

4. Important Operations

Intermediate (return Stream)

• filter(Predicate) → filters elements.


• map(Function) → transforms elements.
• sorted() → sorts elements.
• distinct() → removes duplicates.

Terminal (return result)

• collect(Collectors.toList()) → collect result into list/set/etc.


• forEach(Consumer) → iterate and perform action.
• reduce() → reduce elements into single value.
• toArray() → convert to array.
• min(), max() → get min/max elements.

Short-circuit

• anyMatch(), allMatch(), noneMatch()


• findFirst(), findAny()
• Work lazily and stop early if possible.

5. Example Programs

✅ Filter Even Numbers

List<Integer> list = Arrays.asList(2, 6, 9, 4, 20);

List<Integer> evens = list.stream()


.filter(i -> i % 2 == 0)
.collect(Collectors.toList());

System.out.println(evens); // [2, 6, 4, 20]

✅ Map Example (Grace Marks)

List<Integer> marks = Arrays.asList(30, 78, 26, 96, 79);

List<Integer> updated = marks.stream()


.map(i -> i + 6)
.collect(Collectors.toList());

System.out.println(updated); // [36, 84, 32, 102, 85]

6. Advanced Concepts

• Lazy Evaluation → Intermediate ops executed only when terminal op is called.


• Parallel Stream → list.parallelStream() for multithreading.
• Infinite Stream → Stream.generate() / Stream.iterate()
• Specialized Streams → IntStream, LongStream, DoubleStream.
• File I/O with Streams → Files.lines(Paths.get("file.txt")).

7. Java 9 Stream Improvements

• takeWhile() → take elements until condition fails.


• dropWhile() → skip elements until condition fails.
• iterate(seed, hasNext, next) → enhanced iterate.
• ofNullable() → handle null safely.

8. Real-Life Use Cases

• Filter grocery transactions by type.


• Search mobile numbers by operator.
• Processing large database results.
• File reading/writing with filter/map.

✅ Conclusion

Java Streams → clean, concise, functional way to process collections.

• Improves readability.
• Easier to maintain.
• Can boost performance (parallel streams).
• Must-know feature for modern Java developer.

Got it Let’s go step by step in English only.

1. What are Parallel Streams?

• Normally, Java streams process elements sequentially (one by one).


• A Parallel Stream splits the elements into multiple parts and processes them simultaneously using
multiple threads (leveraging multi-core CPUs).
• This can make processing faster, especially for large datasets.

2. Sequential Stream vs Parallel Stream

• Sequential Stream → Processes elements one at a time, in order.


• Parallel Stream → Divides elements into chunks and processes them concurrently on different
threads.

Example:
If you have 1000 numbers and want to filter even numbers:

• Sequential Stream will check one number after another.


• Parallel Stream will divide the list across threads and check multiple numbers at the same time.

3. When to use Parallel Streams

Use Parallel Streams when:

• The task is large and computationally heavy.


• The result does not depend on order.
• Each element can be processed independently.
• The data is immutable (not changing during processing).
4. When to avoid Parallel Streams

Avoid Parallel Streams when:

• The result depends on a specific order.


• The dataset is small (overhead of parallelization may slow things down).
• Shared mutable state is involved (can cause race conditions).

5. How to create Parallel Streams

There are two common ways:

1. Using parallel() method


Converts an existing stream into a parallel stream.
2. list.stream().parallel();
3. Using parallelStream() method
Directly creates a parallel stream from a collection.
4. list.parallelStream();

6. Performance Impact

• Parallel Streams can significantly improve performance for large datasets.


• For small datasets, the performance difference is often negligible or even worse due to overhead.
• Parallel is not always faster; it depends on the situation and the type of task.

✅ Conclusion

Use Parallel Streams when performance is critical and tasks can run independently without worrying about
order. Use Sequential Streams when correctness, order, or small data size matters more than speed.

ठीक है! चलिए Functional Interface को गहराई से, आसान Hinglish में समझते हैं—concepts, rules, pitfalls,
और best-practices सब कुछ एक ही जगह

Functional Interface (Java 8+): Deep Dive

1) Definition (What & Why)


Functional Interface = ऐसा interface जजसमें exactly 1 abstract method (SAM: Single Abstract Method)
हो।
इसी वजह से हम इसे lambda expression या method reference की “target type” के रूप में यज़
ू कर पाते
हैं।
• Package: कोई भी interface functional बन सकता है (e.g., java.lang.Runnable,
java.util.Comparator, Callable, आदि)
• Built-ins: java.util.function package में ready-made functional interfaces दिए हैं (Predicate,
Function, Consumer, …)

Quick example
@FunctionalInterface
interface MathOp {
int apply(int a, int b); // single abstract method (SAM)
// default/static allowed
default MathOp then(MathOp next) { return (x,y) -> next.apply(apply(x,y), y); }
static MathOp add() { return (a,b) -> a + b; }
}

Use:

MathOp add = (a,b) -> a + b;


int sum = add.apply(3, 5); // 8

2) Rules you must remember ✅


1. Exactly one abstract method होना चादहए.
o default और static methods count नह ीं होते.
o Object के methods (e.g., toString, equals, hashCode) भी count नह ीं होते.
2. @FunctionalInterface annotation optional है, पर िगाएँ — compiler गिततयाँ पकड़ िेता है।
3. Inheritance:
o एक FI िसू रे interface को extends कर सकता है जब तक effective abstract methods की
count 1 रहे .
o िो parents तभी extend कर सकते हैं जब िोनों का abstract method same signature हो
(ताकक total SAM = 1 रहे ), वरना compile error.
4. Generics allowed (FI generic हो सकता है).
5. Checked exceptions: SAM method की throws-clause decide करती है कक lambda क्या throw कर
सकती है (details नीचे).

3) Lambda, Method Reference, Target Typing


• Lambda की type context से आती है (target typing): जजस जगह lambda पास कर रहे हैं वहाँ का
functional interface decide करे गा parameter/return types.
• Method references (चार तरह):
1. TypeName::staticMethod
2. instanceRef::instanceMethod
3. TypeName::instanceMethod (arbitrary object of a type)
4. TypeName::new (constructor reference)
Examples:

Runnable r = () -> System.out.println("Hi");


Comparator<String> byLen = Comparator.comparingInt(String::length);
Supplier<List<String>> listMaker = ArrayList::new;

4) Scope, Capture & “this”


• Lambda local variables को capture कर सकती है only if they are effectively final (यातन
reassignment नहीीं).
• Lambda का this enclosing instance को refer करता है (anonymous class से अिग व्यवहार).
• Shadowing rules simpler हैं: lambda parameters outer scope को shadow कर सकते हैं, पर new scope
की तरह behave नहीीं करती जैसे anonymous class करती है।

Example:

int base = 10; // effectively final


Function<Integer,Integer> f = x -> x + base; // OK
// base++; // ❌ reassign करने से ऊपर वािी line illegal हो जाती

5) Checked Exceptions with Lambdas


SAM method अगर checked exception declare नहीीं करता, तो lambda भी checked exception नहीीं throw कर
सकती।

Workarounds

• SAM में throws जोड़ें (अगर आपका FI custom है), या


• Wrapper utility लिखें:

@FunctionalInterface
interface ThrowingFunction<T,R> {
R apply(T t) throws Exception;
}

static <T,R> Function<T,R> wrap(ThrowingFunction<T,R> f) {


return t -> {
try { return f.apply(t); }
catch (Exception e) { throw new RuntimeException(e); }
};
}

Use:

Function<Path, String> read = wrap(p -> Files.readString(p));

6) Built-in Functional Interfaces (java.util.function) — Cheat Sheet


Category Single “Bi-” Variant Notes

Predicate Predicate<T> BiPredicate<T,U> test → boolean, ops: and, or, negate

Function Function<T,R> BiFunction<T,U,R> transform, ops: compose, andThen

Consumer Consumer<T> BiConsumer<T,U> side-effects, andThen

Supplier Supplier<T> — no input, produces value

special case of Function (T→T); minBy,


Operators UnaryOperator<T> BinaryOperator<T>
maxBy

Int/Long/Double
autoboxing से बचने के लिए तेज़
e.g.,
Primitives
variants ObjIntConsumer<T>

Streams में mapping

• filter(Predicate), map(Function), flatMap(Function), forEach(Consumer),


reduce(BinaryOperator), anyMatch/allMatch/noneMatch(Predicate) आदि—सब FI का
इस्तेमाि करते हैं।

7) Comparator as Functional Interface (power moves)


Comparator<T> भी FI है:

Comparator<Person> byAge = Comparator.comparingInt(Person::getAge)


.thenComparing(Person::getName);

• Compose करने के लिए helpers: thenComparing, reversed, nullsFirst/nullsLast.

8) Overload Ambiguity (Common Pitfall)


Lambda ambiguous हो सकती है अगर overloaded methods अिग-अिग FI types िेते हों:

void use(Runnable r) { r.run(); }


<T> T use(Callable<T> c) throws Exception { return c.call(); }

// use(() -> 42); // ❌ ambiguous


int v = use((Callable<Integer>) () -> 42); // ✅ cast से clear

9) Equality, Identity, Serialization


• Lambdas की identity पर भरोसा न करें (two equal-looking lambdas ≠ equal objects).
• By default serializable नह ीं होतीीं (जब तक target FI Serializable ना हो और runtime support
करे ).
• Implementation details (invokedynamic) पर तनभभर करते हुए JVM पीछे anonymous class जैसा
synthetic object बना सकता है —पर ये internal है ; आप contract-level semantics follow करें ।

10) Functional Interface vs Abstract Class (When to use what)


Aspect Functional Interface Abstract Class

Methods 1 abstract (SAM) + default/static allowed कई abstract/ concrete methods

State No fields (constants ok) Fields allowed

Inheritance Multiple interfaces implement Single inheritance

Use case Behaviors as values (pass/return lambdas) Shared state + partial impl

11) Design Patterns with FI (Practical Uses)


• Strategy: behavior swap via lambda
• Callback / Listener: event handlers as Consumer/Function
• Pipelines: stream ops + andThen/compose
• Retry/Timing wrappers: higher-order functions wrapping Function/Callable
• Factory maps: Map<String, Supplier<Service>>

12) Interview-Level Nuggets


• SAM क्या है ? default/static count क्यों नहीीं होते?
• @FunctionalInterface का benefit? compile-time safety.
• Lambda में effectively final rule?
• this का binding lambda vs anonymous class?
• Predicate vs Function vs Consumer vs Supplier real examples.
• Checked exceptions को lambda में handle कैसे करें गे?
• Primitive specializations क्यों? (autoboxing cost avoid).
• Overload ambiguity कैसे resolve करें गे? (explicit cast).

13) Mini Practice (try yourself)


1. @FunctionalInterface के साथ TriFunction<A,B,C,R> बनाइए और apply(a,b,c) implement
कीजजए।
2. Predicate<String> chain लिखखए जो non-null, non-blank, और length ≥ 5 check करे ।
3. Files की list को पढ़कर Function<Path, Long> से file size map कररए और top-5 sizes print कररए
(streams + Comparator.comparingLong).

बहुत अच्छा सवाि आप Java सीख रहे हैं तो Functional Interfaces को deeply समझना ज़रूरी है।
चलिए step by step चिते हैं।

🔹 Functional Interface in Java

• Java 8 में Functional Programming (Lambda Expressions, Streams, Method References) िाने के
लिए Functional Interfaces use ककए गए।
• Functional Interface = एक interface जजसमें लसर्फ एक abstract method होता है ।
• इसे SAM (Single Abstract Method) Interface भी कहते हैं।
• Functional interface को हम @FunctionalInterface annotation से declare कर सकते हैं।

Example:

@FunctionalInterface
interface MyFunctionalInterface {
void sayHello(); // only one abstract method
}

इसका use हम Lambda expression या Method reference के साथ कर सकते हैं।

🔹 Built-in Functional Interfaces (java.util.function package)

Java ने already कई Functional Interfaces provide ककए हैं, जजन्हें आप बार-बार खि


ु से लिखने की ज़रूरत
नहीीं पड़े।
इनको 4 मख्
ु य categories में divide कर सकते हैं:

1. Supplier Family (कुछ return करते हैं, input नह ीं िेते)


• Supplier → Takes nothing, returns T.

Supplier<String> supplier = () -> "Hello from Supplier";


System.out.println(supplier.get()); // Output: Hello from Supplier
• BooleanSupplier → returns boolean.
• IntSupplier, LongSupplier, DoubleSupplier → primitive return types.

2. Consumer Family (input िेते हैं, return कुछ नह ीं करते)


• Consumer → Takes T, returns nothing.

Consumer<String> consumer = (s) -> System.out.println("Hello " + s);


consumer.accept("Sachin"); // Output: Hello Sachin

• BiConsumer<T, U> → Two inputs, no return.


• IntConsumer, LongConsumer, DoubleConsumer → primitive consumers.
• ObjIntConsumer, ObjLongConsumer, ObjDoubleConsumer → Object + primitive.

3. Predicate Family (input िेते हैं, boolean return करते हैं)


• Predicate → Takes T, returns boolean.

Predicate<Integer> isEven = (n) -> n % 2 == 0;


System.out.println(isEven.test(10)); // true
System.out.println(isEven.test(7)); // false

• BiPredicate<T, U> → Two inputs, returns boolean.


• IntPredicate, LongPredicate, DoublePredicate → primitive predicates.

4. Function Family (input िेते हैं, कुछ return करते हैं)


• Function<T, R> → Takes T, returns R.

Function<String, Integer> lengthFunc = (s) -> s.length();


System.out.println(lengthFunc.apply("Sachin")); // 6

• BiFunction<T, U, R> → Two inputs, one output.


• UnaryOperator → Function<T, T> (input और output same type).

UnaryOperator<Integer> square = (n) -> n * n;


System.out.println(square.apply(5)); // 25

• BinaryOperator → BiFunction<T, T, T> (िो input और output भी same type).

BinaryOperator<Integer> add = (a, b) -> a + b;


System.out.println(add.apply(5, 10)); // 15

• ToIntFunction, ToLongFunction, ToDoubleFunction → input object, return primitive.


• IntFunction, LongFunction, DoubleFunction → primitive input, object return.
• ToIntBiFunction<T, U>, ToLongBiFunction<T, U>, ToDoubleBiFunction<T, U> → two inputs,
primitive return.
• IntToLongFunction, IntToDoubleFunction, LongToIntFunction, etc. → primitive-to-primitive
conversions.

🔹 Special Functional Interfaces

• Runnable → void run() (no input, no output) → Thread tasks के लिए।


• Callable → V call() (no input, returns value, throws Exception)।
• Comparator → int compare(T o1, T o2) → sorting के लिए।

🔹 Summary Table
Category Interface Examples

Supplier Supplier, IntSupplier, LongSupplier, BooleanSupplier

Consumer Consumer, BiConsumer<T,U>, IntConsumer, ObjDoubleConsumer

Predicate Predicate, BiPredicate<T,U>, IntPredicate, DoublePredicate

Function Function<T,R>, BiFunction<T,U,R>, UnaryOperator, BinaryOperator

Easy way याि रखने का:

• Supplier → िे ता है।
• Consumer → खाता है।
• Predicate → true/false बताता है।
• Function → input िेकर कुछ output बनाता है।

Great question . You’ve already understood Java 8 Streams, so now let’s go deep into Collectors
because they are one of the most powerful utilities when working with streams.

1. What is Collectors in Java 8?


Collectors is a utility class in java.util.stream package that provides reduction operations to process
stream elements and collect them into a result container such as a List, Set, Map, or even a single summary
value.

Think of it this way:


• A Stream generates or transforms data.
• A Collector is the terminal step that gathers the result into a desired form.

2. Why Do We Need Collectors?


Without Collectors, if you want to accumulate results, you would need loops and manual aggregation.
With Collectors, you can:

• Collect stream elements into collections (List, Set, Map).


• Perform summarization (count, average, sum, statistics).
• Group elements by some classifier (like SQL GROUP BY).
• Partition data into two categories (true/false).
• Join elements into a string.

3. Commonly Used Collectors


a) Collecting into Collections
List<String> names = Stream.of("John", "Peter", "Sam")
.collect(Collectors.toList());

Set<String> uniqueNames = Stream.of("John", "Peter", "John")


.collect(Collectors.toSet());

Map<Integer, String> map = Stream.of("John", "Peter", "Sam")


.collect(Collectors.toMap(String::length, name -> name));

• toList() → Collects into a List.


• toSet() → Collects into a Set.
• toMap() → Collects into a Map.

b) Counting Elements
long count = Stream.of("A", "B", "C")
.collect(Collectors.counting());

Equivalent to stream.count().

c) Summarizing Statistics
IntSummaryStatistics stats = Stream.of(1, 2, 3, 4, 5)
.collect(Collectors.summarizingInt(Integer::intValue));

System.out.println(stats.getCount()); // 5
System.out.println(stats.getSum()); // 15
System.out.println(stats.getAverage()); // 3.0
System.out.println(stats.getMax()); // 5
System.out.println(stats.getMin()); // 1
Similar methods exist for summarizingDouble and summarizingLong.

d) Averaging and Summing


double average = Stream.of(10, 20, 30)
.collect(Collectors.averagingInt(Integer::intValue));

int sum = Stream.of(10, 20, 30)


.collect(Collectors.summingInt(Integer::intValue));

e) Joining Strings
String joined = Stream.of("Apple", "Banana", "Mango")
.collect(Collectors.joining(", ", "[", "]"));

System.out.println(joined); // [Apple, Banana, Mango]

• joining() → Concatenate strings.


• Can add delimiter, prefix, suffix.

f) Grouping By (like SQL GROUP BY)


Map<Integer, List<String>> groupByLength =
Stream.of("John", "Anna", "Mark", "Bill")
.collect(Collectors.groupingBy(String::length));

System.out.println(groupByLength);
// {4=[John, Anna, Mark, Bill]}

You can also group by with downstream collectors:

Map<Integer, Long> groupByLengthCount =


Stream.of("John", "Anna", "Mark", "Bill", "Joe")
.collect(Collectors.groupingBy(String::length, Collectors.counting()));

g) Partitioning By (true/false classification)


Map<Boolean, List<Integer>> partitioned =
Stream.of(1, 2, 3, 4, 5, 6)
.collect(Collectors.partitioningBy(n -> n % 2 == 0));

System.out.println(partitioned);
// {false=[1, 3, 5], true=[2, 4, 6]}

h) Reducing with Collectors

You can use reducing() to perform reduction.

int sum = Stream.of(1, 2, 3, 4, 5)


.collect(Collectors.reducing(0, Integer::sum));

System.out.println(sum); // 15
4. Collectors vs. reduce()
• reduce() is a general reduction method, works on values (sum, min, max).
• Collectors is higher-level, designed for common collection and summarization tasks.

5. Custom Collectors
You can also create your own collector using Collector.of() if the built-ins don’t fit. Example:

Collector<String, StringJoiner, String> customCollector =


Collector.of(
() -> new StringJoiner(" | "), // supplier
StringJoiner::add, // accumulator
StringJoiner::merge, // combiner
StringJoiner::toString); // finisher

String result = Stream.of("A", "B", "C").collect(customCollector);


System.out.println(result); // A | B | C

6. Summary
• Collectors = predefined reduction strategies for streams.
• Used in collect() terminal operation.
• Can generate collections, counts, averages, groupings, partitions, concatenations.
• Very powerful for data processing in functional style, similar to SQL aggregation but inside Java
code.

Awesome—let’s go deep on Primitive Streams in Java 8: IntStream, LongStream, and DoubleStream.


They’re specialized streams that work with primitives directly, so you avoid boxing (Integer, Long,
Double) and get extra numeric operations.

What are Primitive Streams?


Java 8 provides three special stream types:

• IntStream → for int


• LongStream → for long
• DoubleStream → for double

They exist to:

1. Avoid boxing/unboxing overhead (fewer objects, less GC).


2. Provide numeric helpers (sum, average, range, statistics, etc.).
3. Offer primitive Optional results (OptionalInt, OptionalLong, OptionalDouble).
Creating Primitive Streams
// From arrays
IntStream s1 = Arrays.stream(new int[]{1,2,3});
LongStream s2 = LongStream.of(10L, 20L);
DoubleStream s3 = DoubleStream.of(1.5, 2.5);

// From ranges
IntStream r1 = IntStream.range(1, 5); // 1..4
IntStream r2 = IntStream.rangeClosed(1, 5); // 1..5

// From String (careful: chars() = UTF-16 code units)


IntStream codeUnits = "Hello".chars();
IntStream codePoints = " ".codePoints(); // correct for Unicode

// Infinite streams (use limit!)


IntStream randoms = new Random().ints(); // unbounded
IntStream bounded = new Random().ints(0, 100); // 0..99
DoubleStream gauss = new Random().doubles(1_000); // fixed size

// From object stream → primitive


IntStream ages = people.stream().mapToInt(Person::getAge);

Core Operations (what’s special)


All the usual stream ops exist (filter, map, flatMap, distinct, sorted, limit, skip, peek), plus primitive-
focused variants:

• Mapping between primitives:


o map, mapToObj, mapToLong, mapToDouble
• Flattening:
o flatMap, flatMapToInt, flatMapToLong, flatMapToDouble
• Numeric terminals:
o sum(), average() → OptionalDouble
o min()/max() → OptionalInt/OptionalLong/OptionalDouble
o summaryStatistics() → IntSummaryStatistics, LongSummaryStatistics,
DoubleSummaryStatistics
• Collecting:
o toArray() returns primitive arrays (int[], long[], double[])
o Need boxing? Use .boxed() then a normal collector.

Examples

int sumSquares = IntStream.rangeClosed(1, 100)


.map(x -> x * x)
.sum();

OptionalDouble avgSalary = employees.stream()


.mapToDouble(Employee::getSalary)
.average();

IntSummaryStatistics stats = IntStream.of(3, 7, 3, 10, -2)


.summaryStatistics();
// stats: count=5, sum=21, min=-2, average=4.2, max=10
Primitive Optionals
• Empty stream? average()/min()/max() return an empty optional.

OptionalInt mi = IntStream.empty().min();
int min = mi.orElse(Integer.MAX_VALUE);

Conversions (bridging)
// Primitive → boxed stream
Stream<Integer> boxed = IntStream.range(1, 4).boxed();

// Primitive → other primitive


DoubleStream ds = IntStream.of(1,2,3).asDoubleStream();

// Object stream → primitive


IntStream lengths = strings.stream().mapToInt(String::length);

// Primitive stream → object values


Stream<String> s = IntStream.rangeClosed(1, 3).mapToObj(i -> "No."+i);

Performance Notes (why they matter)


• Primitive streams avoid autoboxing on every element → fewer allocations + better cache use.
• Prefer mapToInt/mapToLong/mapToDouble for numeric aggregations (sum/avg/stats).
• If you immediately collect to List<Integer>, you’ll pay boxing anyway. Prefer int[] with
toArray() when possible.

Parallel & Numeric Accuracy


• Parallel works the same (parallel(), parallelStream()), but:
o Floating point reductions (double sums/averages) are not associative—parallel order may
change tiny rounding.
o For integer sum, beware overflow. If summing many ints, consider mapToLong(x -> x)
then sum(), or use Math.addExact in a reduce.

long safeSum = IntStream.of(Integer.MAX_VALUE, 1)


.mapToLong(x -> x)
.sum(); // no overflow

Specialized Functional Interfaces (use with primitive streams)


• IntPredicate, IntUnaryOperator, IntBinaryOperator,
IntFunction<R>, ToIntFunction<T>, ObjIntConsumer<T>
(and analogous Long*, Double* types)

These avoid boxing in lambdas used by primitive streams.

IntUnaryOperator square = x -> x * x;


IntPredicate isEven = x -> (x & 1) == 0;
Practical Patterns (mini recipes)
1) Top-N by value (primitive array)
int[] top5 = new Random().ints(1_000_000, 0, 1_000_000)
.sorted() // ascending
.skip(1_000_000 - 5) // keep largest 5
.toArray();

2) Histogram of letter frequencies (ASCII)


int[] counts = new int[26];
"abracadabra".chars()
.map(Character::toLowerCase)
.filter(c -> c >= 'a' && c <= 'z')
.forEach(c -> counts[c - 'a']++);

3) Safe average length of names


double avg = names.stream()
.mapToInt(String::length)
.average()
.orElse(0.0);

4) Flatten int[][] efficiently


int[] flat = Arrays.stream(matrix) // Stream<int[]>
.flatMapToInt(IntStream::of)
.toArray();

5) Unicode-safe character processing


int codePointCount = text.codePoints().map(cp -> 1).sum();

Common Pitfalls & Tips


• String.chars() gives UTF-16 code units, not Unicode code points. Use codePoints() when
needed.
• average() returns an OptionalDouble; handle empty streams with orElse, orElseThrow.
• If you need a List<Integer>, .boxed().collect(toList()) will allocate—prefer int[] when
you can.
• When summing many int, consider long sum to avoid overflow.
• range is exclusive of upper bound; rangeClosed is inclusive.

Quick Cheat Sheet


Task Use

1..N integers IntStream.rangeClosed(1, N)


Task Use

Sum/Avg/Min/Max sum() / average() / min() / max()

Full stats summaryStatistics()

Avoid boxing mapToInt, mapToLong, mapToDouble

Convert to array toArray() (primitive)

Convert to objects .boxed() or mapToObj(...)

Unicode-safe string scan codePoints()

Great question
You already know Java 8 introduced Lambda Expressions and Functional Interfaces. To make lambdas
more powerful and readable, Method Reference and Constructor Reference were introduced. Let’s go
step by step.

1. What is Method Reference?


Method Reference is a shorthand notation of a lambda expression that calls an existing method.
Instead of writing a lambda like (a) -> someMethod(a), you can simply write ClassName::methodName.

In short:

• Lambda = defines new logic inline.


• Method Reference = reuses existing method directly.

Syntax
ClassName::methodName
ObjectName::methodName
ClassName::new // (for constructor reference)

2. Types of Method References


a) Reference to a static method

Form: ClassName::staticMethodName

Example:

import java.util.function.Function;

public class StaticMethodRef {


public static int square(int n) {
return n * n;
}

public static void main(String[] args) {


// Using Lambda
Function<Integer, Integer> lambdaSquare = n -> StaticMethodRef.square(n);

// Using Method Reference


Function<Integer, Integer> methodRefSquare = StaticMethodRef::square;

System.out.println(lambdaSquare.apply(5)); // 25
System.out.println(methodRefSquare.apply(6)); // 36
}
}

b) Reference to an instance method of a particular object

Form: objectRef::instanceMethodName

Example:

import java.util.function.Supplier;

public class InstanceMethodRef {


public String getMessage() {
return "Hello from instance method";
}

public static void main(String[] args) {


InstanceMethodRef obj = new InstanceMethodRef();

// Using Lambda
Supplier<String> lambdaMessage = () -> obj.getMessage();

// Using Method Reference


Supplier<String> methodRefMessage = obj::getMessage;

System.out.println(lambdaMessage.get());
System.out.println(methodRefMessage.get());
}
}

c) Reference to an instance method of an arbitrary object of a particular type

Form: ClassName::instanceMethodName

Example:

import java.util.Arrays;

public class ArbitraryInstanceMethodRef {


public static void main(String[] args) {
String[] names = {"John", "Alice", "Bob"};

// Using Lambda
Arrays.sort(names, (s1, s2) -> s1.compareToIgnoreCase(s2));

// Using Method Reference


Arrays.sort(names, String::compareToIgnoreCase);

System.out.println(Arrays.toString(names)); // [Alice, Bob, John]


}
}
d) Reference to a constructor

Form: ClassName::new

This is also called Constructor Reference. It is used when you want to create new objects using functional
interfaces like Supplier, Function, etc.

Example:

import java.util.function.Supplier;

class Student {
String name;

// No-arg constructor
Student() {
this.name = "Default Student";
}

// Parameterized constructor
Student(String name) {
this.name = name;
}
}

public class ConstructorRef {


public static void main(String[] args) {
// No-arg constructor reference
Supplier<Student> studentSupplier = Student::new;
Student s1 = studentSupplier.get();
System.out.println(s1.name); // Default Student

// Parameterized constructor reference using Function


java.util.function.Function<String, Student> studentCreator = Student::new;
Student s2 = studentCreator.apply("Alice");
System.out.println(s2.name); // Alice
}
}

3. Why use Method and Constructor References?


• Cleaner and more readable code compared to verbose lambdas.
• Encourages reusability of already defined methods.
• Reduces boilerplate code.

4. Key Points to Remember


• Method reference is only possible when lambda is just calling an existing method.
• They work with Functional Interfaces like Supplier, Consumer, Predicate, Function, etc.
• Constructor reference is just a shortcut to object creation.
• Types must be compatible. Example:
o Supplier<Student> = Student::new; works for no-arg constructor.
o Function<String, Student> = Student::new; works for parameterized constructor.
✅ In short:

• Method Reference → Use an existing method instead of writing lambda.


• Constructor Reference → Create objects in a functional style.

You might also like