Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Mastering Lambda Expressions, Streams, and Reactive Programming Principles for Java Productivity

Tech Oct 24 1

Lambda Expressions

Quick Example

Java 8 introduced lambda expressions, a compact way to represent anonymous functions tied to functional interfaces. First, define a @FunctionalInterface:

@FunctionalInterface
public interface InstanceCreator<T> {
    T make();
}

A sample Customer entity:

public class Customer {
    private String fullName;
    private int age;

    public Customer() {}
    public Customer(String fullName, int age) {
        this.fullName = fullName;
        this.age = age;
    }

    // Getters and setters omitted for brevity
}

Compare implemantation approaches:

public class LambdaDemo {
    public static void main(String[] args) {
        // 1. Concrete implementation
        InstanceCreator<Customer> concrete = new CustomerCreator();
        Customer cust1 = concrete.make();
        System.out.println(cust1);

        // 2. Anonymous inner class
        InstanceCreator<Customer> anonymous = new InstanceCreator<>() {
            @Override
            public Customer make() {
                return new Customer("Alice Chen", 24);
            }
        };
        Customer cust2 = anonymous.make();
        System.out.println(cust2);

        // 3. Lambda expression
        InstanceCreator<Customer> lambda = () -> new Customer("Alice Chen", 24);
        Customer cust3 = lambda.make();
        System.out.println(cust3);
    }

    static class CustomerCreator implements InstanceCreator<Customer> {
        @Override
        public Customer make() {
            return new Customer();
        }
    }
}

Syntax Simplifications

  • Single statement body: omit braces and return
    // Before
    (String input) -> { return "Received: " + input; }
    // After
    input -> "Received: " + input
    
  • Single parameter: omit parentheses and type (if inferable)
    num -> num * 10
    
  • Multiple parameters: omit types if inferable
    (a, b) -> a + b
    

Functional Interface Basics

A valid functional interface has exactly one abstract method, optionally marked with @FunctionalInterface for compile-time checks.

Built-in Java Functional Interfaces

Runnable

public class RunnableLambda {
    public static void main(String[] args) {
        new Thread(() -> {
            String threadName = Thread.currentThread().getName();
            System.out.printf("%s executing task%n", threadName);
        }).start();
    }
}

Supplier<T>

Produce a value without input:

import java.util.Arrays;
import java.util.function.Supplier;

public class SupplierLambda {
    public static void main(String[] args) {
        int[] nums = {9, 18, 3, 27, 12};
        int peak = findExtreme(() -> {
            int temp = nums[0];
            for (int n : nums) {
                if (n > temp) temp = n;
            }
            return temp;
        });
        System.out.println("Peak value: " + peak);
    }

    private static int findExtreme(Supplier<Integer> supplier) {
        return supplier.get();
    }
}

Consumer<T>

Accept a value without return, chainable via andThen:

import java.util.function.Consumer;

public class ConsumerLambda {
    public static void main(String[] args) {
        processText("Java 8+ Features", System.out::println);
        processDual("Java 8+ Features",
            s -> System.out.println(s.toUpperCase()),
            s -> System.out.println(s.toLowerCase())
        );
    }

    private static void processText(String input, Consumer<String> handler) {
        handler.accept(input);
    }

    private static void processDual(String input, Consumer<String> first, Consumer<String> second) {
        first.andThen(second).accept(input);
    }
}

Predicate<T>

Test a boolean condition, chainable with and, or, negate:

import java.util.function.Predicate;

public class PredicateLambda {
    public static void main(String[] args) {
        Predicate<Integer> isEven = n -> n % 2 == 0;
        Predicate<Integer> isPositive = n -> n > 0;
        System.out.println(isEven.and(isPositive).test(12));
    }
}

Function<T, R>

Transform a value, chainable with andThen or compose:

import java.util.function.Function;

public class FunctionLambda {
    public static void main(String[] args) {
        String input = "Bob,32";
        int adjustedAge = extractAge(input,
            s -> s.split(",")[1],
            Integer::parseInt,
            n -> n + 5
        );
        System.out.println("Adjusted age: " + adjustedAge);
    }

    private static int extractAge(String raw,
                                  Function<String, String> extract,
                                  Function<String, Integer> parse,
                                  Function<Integer, Integer> adjust) {
        return extract.andThen(parse).andThen(adjust).apply(raw);
    }
}

Lambda Internals

Lambda expressions are syntactic sugar. The JVM generates a synthetic class implementing the target functional interface, creates a singleton instance, and calls a synthetic static method containing your logic when the interface method is invoked.


Method References

Quick Example

Method references reuse existing methods where lambda logic is already implemented:

@FunctionalInterface
public interface TextDisplayer {
    void show(String text);
}

public class MethodRefDemo {
    public static void main(String[] args) {
        // Lambda
        display("Hello Stream", s -> System.out.println(s));
        // Method reference
        display("Hello Stream", System.out::println);
    }

    private static void display(String text, TextDisplayer displayer) {
        displayer.show(text);
    }
}

Syntax

Use :: operator:

Type Example
Class::static Integer::parseInt
Class::new Customer::new
Object::instance "test"::toUpperCase
Array::new int[]::new

Streams

Quick Example

Filter and process collections declaratively:

import java.util.ArrayList;
import java.util.List;

public class StreamDemo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Zhang San");
        names.add("Zhang San Feng");
        names.add("Zhang Xue You");
        names.add("Zhang Wei");

        names.stream()
            .filter(n -> n.startsWith("Zhang"))
            .filter(n -> n.split(" ").length == 3)
            .forEach(System.out::println);
    }
}

Stream Pipeline

Streams follow source → intermediate operations → terminal operation. Intermediate operations return a new stream and are lazy; terminal operations trigger computation and close the stream.

Common Operations

Source Ceration

import java.util.*;
import java.util.stream.Stream;

public class StreamSources {
    public static void main(String[] args) {
        // Collections
        List<String> list = List.of("a", "b");
        Stream<String> listStream = list.stream();

        Set<Integer> set = Set.of(1, 2, 3);
        Stream<Integer> setStream = set.parallelStream();

        Map<String, Integer> map = Map.of("x", 1, "y", 2);
        Stream<String> keyStream = map.keySet().stream();
        Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();

        // Arrays
        Double[] arr = {1.1, 2.2};
        Stream<Double> arrStream = Arrays.stream(arr);

        // Factory methods
        Stream<String> ofStream = Stream.of("m", "n");
    }
}

Intermediate Operations

  • filter: Keep elements matching a predicate
  • map: Transform elements
  • sorted: Sort elements
  • skip: Drop first N eleemnts
  • limit: Keep first N elements
  • distinct: Remove duplicates

Terminal Operations

  • forEach: Execute action per element
  • count: Get element count
  • collect: Gather elements to a collection
  • reduce: Accumulate elements to a single value
  • anyMatch/allMatch/noneMatch: Check conditions
import java.util.*;
import java.util.stream.Collectors;

public class StreamOperations {
    public static void main(String[] args) {
        String[] names = {"Zhang San", "Zhang San Feng", "Zhang Xue You", "Zhang Wei", "Li Si", "Zhang San"};

        List<String> filtered = Arrays.stream(names)
            .distinct()
            .filter(n -> n.startsWith("Zhang"))
            .sorted(Comparator.comparingInt(String::length))
            .skip(1)
            .limit(2)
            .collect(Collectors.toList());
        System.out.println(filtered);

        Optional<Integer> maxLen = Arrays.stream(names)
            .map(String::length)
            .reduce(Integer::max);
        maxLen.ifPresent(len -> System.out.println("Max name length: " + len));
    }
}
Tags: Java 8+

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.