Search Tutorials


1Z0-830 Java SE 21 - Streams and Lambdas | JavaInUse

1Z0-830 Streams and Lambdas - Java SE 21 Certification Prep

Functional Interfaces

A functional interface has exactly one abstract method.
// Defining a functional interface
@FunctionalInterface
public interface Calculator {
    int calculate(int a, int b);
    
    // Default and static methods allowed
    default void log() { }
    static void helper() { }
}

// Common built-in functional interfaces:

// Predicate<T> - test condition, returns boolean
Predicate<String> isEmpty = s -> s.isEmpty();
boolean result = isEmpty.test("");  // true

// Function<T, R> - transform T to R
Function<String, Integer> length = s -> s.length();
int len = length.apply("hello");  // 5

// Consumer<T> - accept T, return nothing
Consumer<String> printer = s -> System.out.println(s);
printer.accept("hello");

// Supplier<T> - supply T (no input)
Supplier<Double> random = () -> Math.random();
double value = random.get();

// UnaryOperator<T> - T to T (extends Function)
UnaryOperator<String> upper = s -> s.toUpperCase();

// BinaryOperator<T> - (T, T) to T (extends BiFunction)
BinaryOperator<Integer> add = (a, b) -> a + b;

// BiPredicate, BiFunction, BiConsumer - two parameters
BiPredicate<String, Integer> lengthCheck = (s, len) -> s.length() > len;
BiFunction<String, String, String> concat = (a, b) -> a + b;
BiConsumer<String, Integer> print = (s, n) -> System.out.println(s + n);
Exam Tip: Memorize: Predicate (test), Function (apply), Consumer (accept), Supplier (get). The primitive versions use specific method names: IntPredicate, LongFunction, DoubleConsumer.

Lambda Expressions

// Lambda syntax
// (parameters) -> expression
// (parameters) -> { statements; }

// Single parameter (parentheses optional)
x -> x * 2
(x) -> x * 2

// Multiple parameters
(a, b) -> a + b

// No parameters
() -> System.out.println("Hello")

// With explicit types
(String s) -> s.length()
(int a, int b) -> a + b

// Block body with return
(int a, int b) -> {
    int sum = a + b;
    return sum;
}

// Variable capture (effectively final)
int factor = 2;  // Must be effectively final
Function<Integer, Integer> multiply = n -> n * factor;
// factor = 3;  // Compile error - variable used in lambda

// var in lambda parameters (Java 11+)
BiFunction<String, String, String> concat = (var a, var b) -> a + b;

// var with annotations
(@NonNull var a, @NotEmpty var b) -> a + b

Method References

// Four types of method references:

// 1. Static method reference
// ClassName::staticMethod
Function<String, Integer> parser = Integer::parseInt;
// Same as: s -> Integer.parseInt(s)

// 2. Instance method on specific object
// instance::instanceMethod
String prefix = "Hello ";
Function<String, String> greeter = prefix::concat;
// Same as: s -> prefix.concat(s)

// 3. Instance method on parameter
// ClassName::instanceMethod
Function<String, Integer> length = String::length;
// Same as: s -> s.length()

BiFunction<String, String, Boolean> startsWith = String::startsWith;
// Same as: (s, prefix) -> s.startsWith(prefix)

// 4. Constructor reference
// ClassName::new
Supplier<ArrayList> listFactory = ArrayList::new;
// Same as: () -> new ArrayList()

Function<Integer, ArrayList> sizedList = ArrayList::new;
// Same as: size -> new ArrayList(size)

// Array constructor reference
Function<Integer, String[]> arrayFactory = String[]::new;
// Same as: size -> new String[size]
Exam Tip: Method reference String::length works because the String instance becomes the first parameter. strings.stream().map(String::length) calls length() on each string.

Stream Creation

// From collection
List<String> list = List.of("a", "b", "c");
Stream<String> stream = list.stream();

// From array
String[] array = {"a", "b", "c"};
Stream<String> arrStream = Arrays.stream(array);

// Stream.of()
Stream<String> ofStream = Stream.of("a", "b", "c");

// Infinite streams
Stream<Integer> infinite = Stream.iterate(0, n -> n + 1);
Stream<Double> randoms = Stream.generate(Math::random);

// Bounded iterate (Java 9+)
Stream<Integer> bounded = Stream.iterate(0, n -> n < 10, n -> n + 1);

// Stream.ofNullable (Java 9+)
Stream<String> nullable = Stream.ofNullable(getValue()); // Empty if null

// Primitive streams
IntStream ints = IntStream.of(1, 2, 3);
IntStream range = IntStream.range(1, 10);      // 1 to 9
IntStream rangeClosed = IntStream.rangeClosed(1, 10);  // 1 to 10
LongStream longs = LongStream.of(1L, 2L, 3L);
DoubleStream doubles = DoubleStream.of(1.0, 2.0);

// Empty stream
Stream<String> empty = Stream.empty();

// Concatenating streams
Stream<String> combined = Stream.concat(stream1, stream2);

Intermediate Operations

// Intermediate operations return a stream and are lazy

List<String> list = List.of("apple", "banana", "cherry", "date");

// filter - keep elements matching predicate
list.stream()
    .filter(s -> s.length() > 5)  // banana, cherry

// map - transform elements
list.stream()
    .map(String::toUpperCase)  // APPLE, BANANA...

// flatMap - flatten nested structures
List<List<Integer>> nested = List.of(List.of(1, 2), List.of(3, 4));
nested.stream()
    .flatMap(Collection::stream)  // 1, 2, 3, 4

// distinct - remove duplicates
Stream.of(1, 2, 2, 3, 3, 3)
    .distinct()  // 1, 2, 3

// sorted - natural order or with comparator
list.stream().sorted()
list.stream().sorted(Comparator.reverseOrder())
list.stream().sorted(Comparator.comparing(String::length))

// limit - take first n elements
Stream.iterate(1, n -> n + 1)
    .limit(5)  // 1, 2, 3, 4, 5

// skip - skip first n elements
list.stream().skip(2)  // cherry, date

// peek - debug without modifying
list.stream()
    .peek(System.out::println)
    .map(String::toUpperCase)
    .collect(Collectors.toList());

// takeWhile / dropWhile (Java 9+)
Stream.of(1, 2, 3, 4, 5, 1)
    .takeWhile(n -> n < 4)  // 1, 2, 3 (stops at first false)
    
Stream.of(1, 2, 3, 4, 5)
    .dropWhile(n -> n < 4)  // 4, 5

Terminal Operations

// Terminal operations trigger processing and produce result

List<String> list = List.of("apple", "banana", "cherry");

// forEach - perform action on each
list.stream().forEach(System.out::println);

// toArray - convert to array
String[] array = list.stream().toArray(String[]::new);

// collect - gather into collection
List<String> newList = list.stream().collect(Collectors.toList());
Set<String> set = list.stream().collect(Collectors.toSet());

// count - number of elements
long count = list.stream().filter(s -> s.length() > 5).count();

// min / max - with comparator
Optional<String> min = list.stream().min(Comparator.naturalOrder());
Optional<String> max = list.stream().max(Comparator.naturalOrder());

// findFirst / findAny - get element
Optional<String> first = list.stream().findFirst();
Optional<String> any = list.stream().findAny();

// anyMatch / allMatch / noneMatch - test predicates
boolean anyLong = list.stream().anyMatch(s -> s.length() > 5);
boolean allShort = list.stream().allMatch(s -> s.length() < 10);
boolean nonEmpty = list.stream().noneMatch(String::isEmpty);

// reduce - combine elements
Optional<String> concat = list.stream().reduce((a, b) -> a + b);
String withIdentity = list.stream().reduce("", (a, b) -> a + b);

// Primitive stream operations
IntStream.of(1, 2, 3, 4, 5).sum();      // 15
IntStream.of(1, 2, 3, 4, 5).average(); // OptionalDouble[3.0]
IntStream.of(1, 2, 3, 4, 5).max();     // OptionalInt[5]
IntStream.of(1, 2, 3, 4, 5).summaryStatistics();

Collectors

// Basic collectors
List<String> toList = stream.collect(Collectors.toList());
Set<String> toSet = stream.collect(Collectors.toSet());
LinkedList<String> toLinkd = stream.collect(Collectors.toCollection(LinkedList::new));

// Joining strings
String joined = list.stream().collect(Collectors.joining());        // abc
String delim = list.stream().collect(Collectors.joining(", "));     // a, b, c
String prefix = list.stream().collect(Collectors.joining(", ", "[", "]")); // [a, b, c]

// Counting
Long count = stream.collect(Collectors.counting());

// Summing / Averaging
Integer sum = stream.collect(Collectors.summingInt(String::length));
Double avg = stream.collect(Collectors.averagingInt(String::length));

// Min / Max
Optional<String> max = stream.collect(Collectors.maxBy(Comparator.naturalOrder()));

// Grouping
Map<Integer, List<String>> byLength = 
    list.stream().collect(Collectors.groupingBy(String::length));
// {5=[apple], 6=[banana, cherry]}

// Grouping with downstream collector
Map<Integer, Long> countByLength = 
    list.stream().collect(Collectors.groupingBy(String::length, Collectors.counting()));

// Partitioning (two groups: true/false)
Map<Boolean, List<String>> partition = 
    list.stream().collect(Collectors.partitioningBy(s -> s.length() > 5));
// {false=[apple], true=[banana, cherry]}

// toMap
Map<String, Integer> map = list.stream()
    .collect(Collectors.toMap(Function.identity(), String::length));

// toMap with merge function (for duplicates)
Map<Integer, String> map2 = list.stream()
    .collect(Collectors.toMap(String::length, Function.identity(),
        (existing, replacement) -> existing));

// Teeing (Java 12+) - two collectors combined
record MinMax(String min, String max) {}
MinMax result = list.stream().collect(Collectors.teeing(
    Collectors.minBy(Comparator.naturalOrder()),
    Collectors.maxBy(Comparator.naturalOrder()),
    (min, max) -> new MinMax(min.orElse(""), max.orElse(""))
));

Optional

// Creating Optional
Optional<String> full = Optional.of("hello");     // NullPointerException if null
Optional<String> nullable = Optional.ofNullable(getValue());  // Empty if null
Optional<String> empty = Optional.empty();

// Checking and getting
if (opt.isPresent()) {
    String value = opt.get();  // Throws NoSuchElementException if empty
}

boolean isEmpty = opt.isEmpty();  // Java 11+

// Getting with fallback
String value = opt.orElse("default");
String lazy = opt.orElseGet(() -> expensiveDefault());
String orThrow = opt.orElseThrow();  // NoSuchElementException
String custom = opt.orElseThrow(() -> new CustomException());

// Conditional actions
opt.ifPresent(System.out::println);
opt.ifPresentOrElse(
    System.out::println,
    () -> System.out.println("Empty")
);

// Transforming
Optional<Integer> length = opt.map(String::length);
Optional<String> upper = opt.filter(s -> s.length() > 3).map(String::toUpperCase);

// flatMap for nested Optional
Optional<Optional<String>> nested = Optional.of(Optional.of("hi"));
Optional<String> flat = opt.flatMap(o -> o);  // Avoid nesting

// or() - provide alternative Optional (Java 9+)
Optional<String> result = opt.or(() -> Optional.of("fallback"));

// stream() - convert to Stream (Java 9+)
List<String> list = optList.stream()
    .flatMap(Optional::stream)
    .collect(Collectors.toList());
Exam Tip: Never use Optional.get() without checking isPresent(). Prefer orElse(), orElseGet(), or orElseThrow(). Optional should not be used as method parameters or class fields.

1Z0-830 Java SE 21 Certification - Table of Contents

Master all exam topics with comprehensive study guides and practice examples.


Popular Posts