Master Java Lambda Expressions and Functional Programming
Java Lambda Expressions
Lambda expressions, introduced in Java 8, provide a concise way to represent anonymous functions. They enable functional programming paradigms in Java and are particularly powerful when combined with the Streams API and functional interfaces.
What are Lambda Expressions?
Lambda expressions are anonymous functions that can be passed as arguments, stored in variables, and returned from methods. They provide a more readable and concise alternative to anonymous inner classes for single-method interfaces.
import java.util.*;
import java.util.function.*;
public class LambdaIntroduction {
public static void main(String[] args) {
// Traditional approach with anonymous inner class
Runnable oldWay = new Runnable() {
@Override
public void run() {
System.out.println("Hello from anonymous class");
}
};
// Lambda expression approach
Runnable newWay = () -> System.out.println("Hello from lambda");
// Execute both
oldWay.run();
newWay.run();
// Lambda with parameters
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// Old way
names.forEach(new Consumer<String>() {
@Override
public void accept(String name) {
System.out.println("Hello, " + name);
}
});
// Lambda way
names.forEach(name -> System.out.println("Hello, " + name));
// Even more concise with method reference
names.forEach(System.out::println);
}
}
Lambda Syntax
Lambda expressions have several syntax forms depending on complexity:
import java.util.function.*;
public class LambdaSyntax {
public static void main(String[] args) {
// 1. No parameters
Runnable noParams = () -> System.out.println("No parameters");
// 2. Single parameter (parentheses optional)
Consumer<String> singleParam = name -> System.out.println("Hello " + name);
Consumer<String> singleParamWithParens = (name) -> System.out.println("Hello " + name);
// 3. Multiple parameters
BinaryOperator<Integer> multipleParams = (a, b) -> a + b;
// 4. Explicit parameter types
BinaryOperator<Integer> explicitTypes = (Integer a, Integer b) -> a + b;
// 5. Block body with multiple statements
Function<String, String> blockBody = (input) -> {
String processed = input.toUpperCase();
processed = processed.trim();
return "Processed: " + processed;
};
// 6. Single expression (return is implicit)
Function<Integer, Integer> singleExpression = x -> x * 2;
// Test the lambdas
noParams.run();
singleParam.accept("Alice");
System.out.println("Sum: " + multipleParams.apply(5, 3));
System.out.println(blockBody.apply(" hello world "));
System.out.println("Double: " + singleExpression.apply(10));
}
}
Functional Interfaces
Lambda expressions work with functional interfaces - interfaces with exactly one abstract method:
Built-in Functional Interfaces
import java.util.*;
import java.util.function.*;
public class FunctionalInterfacesDemo {
public static void main(String[] args) {
// Predicate<T> - boolean test(T t)
Predicate<String> isEmpty = String::isEmpty;
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<String> startsWithA = s -> s.startsWith("A");
System.out.println("Is empty: " + isEmpty.test(""));
System.out.println("Is even: " + isEven.test(4));
System.out.println("Starts with A: " + startsWithA.test("Apple"));
// Function<T, R> - R apply(T t)
Function<String, Integer> stringLength = String::length;
Function<Integer, String> intToString = Object::toString;
Function<String, String> toUpperCase = String::toUpperCase;
System.out.println("Length: " + stringLength.apply("Hello"));
System.out.println("String: " + intToString.apply(42));
System.out.println("Uppercase: " + toUpperCase.apply("hello"));
// Consumer<T> - void accept(T t)
Consumer<String> printer = System.out::println;
Consumer<List<String>> listPrinter = list -> list.forEach(System.out::println);
printer.accept("Hello World");
listPrinter.accept(Arrays.asList("A", "B", "C"));
// Supplier<T> - T get()
Supplier<Double> randomValue = Math::random;
Supplier<String> greeting = () -> "Hello";
Supplier<List<String>> listSupplier = ArrayList::new;
System.out.println("Random: " + randomValue.get());
System.out.println("Greeting: " + greeting.get());
System.out.println("New list: " + listSupplier.get());
// BinaryOperator<T> - T apply(T t1, T t2)
BinaryOperator<Integer> add = (a, b) -> a + b;
BinaryOperator<Integer> multiply = (a, b) -> a * b;
BinaryOperator<String> concat = (a, b) -> a + " " + b;
System.out.println("Add: " + add.apply(5, 3));
System.out.println("Multiply: " + multiply.apply(5, 3));
System.out.println("Concat: " + concat.apply("Hello", "World"));
// UnaryOperator<T> - T apply(T t)
UnaryOperator<String> toUpper = String::toUpperCase;
UnaryOperator<Integer> square = x -> x * x;
System.out.println("Upper: " + toUpper.apply("hello"));
System.out.println("Square: " + square.apply(5));
// BiFunction<T, U, R> - R apply(T t, U u)
BiFunction<String, String, String> stringConcat = (a, b) -> a + " " + b;
BiFunction<Integer, Integer, Integer> max = Math::max;
System.out.println("Concat: " + stringConcat.apply("Hello", "World"));
System.out.println("Max: " + max.apply(10, 5));
}
}
Custom Functional Interfaces
@FunctionalInterface
interface Calculator {
double calculate(double a, double b);
// Default methods are allowed
default void printResult(double a, double b) {
System.out.println("Result: " + calculate(a, b));
}
// Static methods are allowed
static Calculator getAdder() {
return (a, b) -> a + b;
}
}
@FunctionalInterface
interface StringProcessor {
String process(String input);
}
@FunctionalInterface
interface TriFunction<T, U, V, R> {
R apply(T t, U u, V v);
}
public class CustomFunctionalInterfaces {
public static void main(String[] args) {
// Calculator examples
Calculator adder = (a, b) -> a + b;
Calculator multiplier = (a, b) -> a * b;
Calculator divider = (a, b) -> b != 0 ? a / b : 0;
adder.printResult(10, 5);
multiplier.printResult(10, 5);
divider.printResult(10, 5);
Calculator staticAdder = Calculator.getAdder();
staticAdder.printResult(7, 3);
// StringProcessor examples
StringProcessor upperCase = String::toUpperCase;
StringProcessor reverse = s -> new StringBuilder(s).reverse().toString();
StringProcessor addPrefix = s -> "Processed: " + s;
System.out.println(upperCase.process("hello"));
System.out.println(reverse.process("hello"));
System.out.println(addPrefix.process("data"));
// TriFunction example
TriFunction<String, Integer, Boolean, String> formatter =
(text, number, flag) -> flag ? text.toUpperCase() + ":" + number : text + ":" + number;
System.out.println(formatter.apply("hello", 42, true));
System.out.println(formatter.apply("world", 24, false));
}
}
Method References
Method references provide a shorthand for lambda expressions that call a single method:
import java.util.*;
import java.util.function.*;
public class MethodReferences {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// 1. Static method references
// Lambda: x -> Integer.parseInt(x)
Function<String, Integer> parseInt = Integer::parseInt;
System.out.println("Parsed: " + parseInt.apply("123"));
// Lambda: (a, b) -> Math.max(a, b)
BinaryOperator<Integer> max = Math::max;
System.out.println("Max: " + max.apply(10, 5));
// 2. Instance method references on particular object
String prefix = "Hello ";
// Lambda: s -> prefix.concat(s)
Function<String, String> addPrefix = prefix::concat;
System.out.println(addPrefix.apply("World"));
// Lambda: s -> System.out.println(s)
Consumer<String> printer = System.out::println;
names.forEach(printer);
// 3. Instance method references on arbitrary object
// Lambda: s -> s.length()
Function<String, Integer> stringLength = String::length;
names.stream()
.map(stringLength)
.forEach(System.out::println);
// Lambda: s -> s.toUpperCase()
Function<String, String> toUpper = String::toUpperCase;
names.stream()
.map(toUpper)
.forEach(System.out::println);
// Lambda: (s1, s2) -> s1.compareToIgnoreCase(s2)
Comparator<String> caseInsensitiveComparator = String::compareToIgnoreCase;
names.sort(caseInsensitiveComparator);
System.out.println("Sorted: " + names);
// 4. Constructor references
// Lambda: () -> new ArrayList<>()
Supplier<List<String>> listSupplier = ArrayList::new;
List<String> newList = listSupplier.get();
// Lambda: size -> new ArrayList<>(size)
Function<Integer, List<String>> listWithCapacity = ArrayList::new;
List<String> listWith10Capacity = listWithCapacity.apply(10);
// Lambda: s -> new Person(s)
Function<String, Person> personCreator = Person::new;
Person person = personCreator.apply("John");
System.out.println("Created person: " + person.getName());
// Array constructor reference
// Lambda: size -> new String[size]
IntFunction<String[]> stringArrayCreator = String[]::new;
String[] stringArray = stringArrayCreator.apply(5);
System.out.println("Created array of length: " + stringArray.length);
}
static class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
}
Variable Capture and Closures
Lambda expressions can capture variables from their enclosing scope:
import java.util.*;
import java.util.function.*;
public class VariableCapture {
private String instanceField = "Instance field";
private static String staticField = "Static field";
public static void main(String[] args) {
new VariableCapture().demonstrateClosure();
}
public void demonstrateClosure() {
String localVariable = "Local variable";
final String finalLocal = "Final local";
// Effectively final variable
String effectivelyFinal = "Effectively final";
// Lambda can access:
// 1. Static fields
// 2. Instance fields (if lambda is in instance method)
// 3. Final or effectively final local variables
Runnable lambda = () -> {
System.out.println("Static field: " + staticField);
System.out.println("Instance field: " + instanceField);
System.out.println("Final local: " + finalLocal);
System.out.println("Effectively final: " + effectivelyFinal);
// System.out.println("Local variable: " + localVariable); // Error!
};
lambda.run();
// This would make effectivelyFinal not effectively final
// effectivelyFinal = "Changed"; // Uncommenting this causes compilation error
// Demonstrate closure with method parameters
processWithCallback("Hello", message -> {
System.out.println("Processing: " + message);
System.out.println("Instance field in callback: " + instanceField);
});
// Closure with collections
String prefix = "Item: ";
List<String> items = Arrays.asList("A", "B", "C");
items.stream()
.map(item -> prefix + item) // Captures prefix
.forEach(System.out::println);
// Counter example showing mutable closure
createCounters();
}
public void processWithCallback(String message, Consumer<String> callback) {
callback.accept(message);
}
public void createCounters() {
List<Supplier<Integer>> counters = new ArrayList<>();
// This won't work - local variables in lambda must be final/effectively final
/*
int count = 0;
for (int i = 0; i < 3; i++) {
counters.add(() -> count++); // Error: count is not effectively final
}
*/
// Solution 1: Use array (mutable container)
int[] count = {0};
for (int i = 0; i < 3; i++) {
counters.add(() -> ++count[0]);
}
// Solution 2: Use AtomicInteger
java.util.concurrent.atomic.AtomicInteger atomicCount =
new java.util.concurrent.atomic.AtomicInteger(0);
List<Supplier<Integer>> atomicCounters = new ArrayList<>();
for (int i = 0; i < 3; i++) {
atomicCounters.add(atomicCount::incrementAndGet);
}
// Test counters
System.out.println("Array-based counters:");
counters.forEach(counter -> System.out.println(counter.get()));
System.out.println("Atomic counters:");
atomicCounters.forEach(counter -> System.out.println(counter.get()));
}
}
Lambda Expressions with Collections
Lambdas are particularly powerful when working with collections:
import java.util.*;
import java.util.stream.*;
public class LambdaWithCollections {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 30, "Engineer"),
new Person("Bob", 25, "Designer"),
new Person("Charlie", 35, "Manager"),
new Person("David", 28, "Engineer"),
new Person("Eve", 32, "Designer")
);
// Sorting with lambdas
System.out.println("=== Sorting ===");
// Sort by age
people.sort((p1, p2) -> Integer.compare(p1.getAge(), p2.getAge()));
System.out.println("Sorted by age: " + people);
// Sort by name using method reference
people.sort(Comparator.comparing(Person::getName));
System.out.println("Sorted by name: " + people);
// Complex sorting
people.sort(Comparator.comparing(Person::getProfession)
.thenComparing(Person::getAge));
System.out.println("Sorted by profession then age: " + people);
// Filtering with lambdas
System.out.println("\n=== Filtering ===");
// Filter engineers
List<Person> engineers = people.stream()
.filter(p -> p.getProfession().equals("Engineer"))
.collect(Collectors.toList());
System.out.println("Engineers: " + engineers);
// Filter people over 30
List<Person> over30 = people.stream()
.filter(p -> p.getAge() > 30)
.collect(Collectors.toList());
System.out.println("Over 30: " + over30);
// Transform with lambdas
System.out.println("\n=== Transforming ===");
// Get all names in uppercase
List<String> upperNames = people.stream()
.map(p -> p.getName().toUpperCase())
.collect(Collectors.toList());
System.out.println("Uppercase names: " + upperNames);
// Get name and age strings
List<String> nameAge = people.stream()
.map(p -> p.getName() + " (" + p.getAge() + ")")
.collect(Collectors.toList());
System.out.println("Name and age: " + nameAge);
// Complex operations
System.out.println("\n=== Complex Operations ===");
// Group by profession
Map<String, List<Person>> byProfession = people.stream()
.collect(Collectors.groupingBy(Person::getProfession));
System.out.println("Grouped by profession: " + byProfession);
// Average age by profession
Map<String, Double> avgAgeByProfession = people.stream()
.collect(Collectors.groupingBy(
Person::getProfession,
Collectors.averagingInt(Person::getAge)
));
System.out.println("Average age by profession: " + avgAgeByProfession);
// Find operations
Optional<Person> youngest = people.stream()
.min(Comparator.comparing(Person::getAge));
youngest.ifPresent(p -> System.out.println("Youngest: " + p));
// Check if any engineer exists
boolean hasEngineer = people.stream()
.anyMatch(p -> p.getProfession().equals("Engineer"));
System.out.println("Has engineer: " + hasEngineer);
// Custom collectors
String allNames = people.stream()
.map(Person::getName)
.collect(Collectors.joining(", "));
System.out.println("All names: " + allNames);
}
static class Person {
private String name;
private int age;
private String profession;
public Person(String name, int age, String profession) {
this.name = name;
this.age = age;
this.profession = profession;
}
public String getName() { return name; }
public int getAge() { return age; }
public String getProfession() { return profession; }
@Override
public String toString() {
return name + "(" + age + ", " + profession + ")";
}
}
}
Lambda Expressions with Streams
Lambdas are the foundation of the powerful Streams API:
import java.util.*;
import java.util.stream.*;
public class LambdaWithStreams {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Basic stream operations
System.out.println("=== Basic Operations ===");
// Filter even numbers and square them
List<Integer> evenSquares = numbers.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println("Even squares: " + evenSquares);
// Sum of all numbers
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
System.out.println("Sum: " + sum);
// Alternative sum using method reference
int sum2 = numbers.stream()
.reduce(0, Integer::sum);
System.out.println("Sum (method ref): " + sum2);
// Find first number greater than 5
Optional<Integer> firstGreaterThan5 = numbers.stream()
.filter(n -> n > 5)
.findFirst();
firstGreaterThan5.ifPresent(n -> System.out.println("First > 5: " + n));
// Parallel processing
System.out.println("\n=== Parallel Processing ===");
long start = System.nanoTime();
double result = numbers.parallelStream()
.mapToDouble(n -> Math.sqrt(n))
.map(d -> Math.sin(d))
.sum();
long end = System.nanoTime();
System.out.println("Parallel result: " + result + " (time: " + (end - start) / 1_000_000 + " ms)");
// Complex stream pipeline
System.out.println("\n=== Complex Pipeline ===");
List<String> words = Arrays.asList(
"apple", "banana", "cherry", "date", "elderberry", "fig", "grape"
);
Map<Integer, List<String>> wordsByLength = words.stream()
.filter(word -> word.length() > 4)
.map(String::toUpperCase)
.sorted()
.collect(Collectors.groupingBy(String::length));
System.out.println("Words by length: " + wordsByLength);
// FlatMap example
List<List<String>> listOfLists = Arrays.asList(
Arrays.asList("a", "b"),
Arrays.asList("c", "d", "e"),
Arrays.asList("f", "g", "h", "i")
);
List<String> flattened = listOfLists.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
System.out.println("Flattened: " + flattened);
// Custom collectors
String concatenated = words.stream()
.collect(StringBuilder::new,
(sb, s) -> sb.append(s).append(" "),
StringBuilder::append)
.toString();
System.out.println("Concatenated: " + concatenated.trim());
// Infinite streams
System.out.println("\n=== Infinite Streams ===");
// Generate first 10 fibonacci numbers
List<Integer> fibonacci = Stream.iterate(new int[]{0, 1},
pair -> new int[]{pair[1], pair[0] + pair[1]})
.limit(10)
.map(pair -> pair[0])
.collect(Collectors.toList());
System.out.println("Fibonacci: " + fibonacci);
// Random numbers
List<Double> randomNumbers = Stream.generate(Math::random)
.limit(5)
.collect(Collectors.toList());
System.out.println("Random numbers: " + randomNumbers);
// Number range operations
int sumOfSquares = IntStream.rangeClosed(1, 10)
.map(n -> n * n)
.sum();
System.out.println("Sum of squares 1-10: " + sumOfSquares);
}
}
Advanced Lambda Patterns
Higher-Order Functions
import java.util.function.*;
public class HigherOrderFunctions {
public static void main(String[] args) {
// Function that returns a function
Function<String, Function<String, String>> createPrefixer = prefix ->
text -> prefix + text;
Function<String, String> addHello = createPrefixer.apply("Hello ");
Function<String, String> addGoodbye = createPrefixer.apply("Goodbye ");
System.out.println(addHello.apply("World"));
System.out.println(addGoodbye.apply("World"));
// Function composition
Function<Integer, Integer> multiplyBy2 = x -> x * 2;
Function<Integer, Integer> add3 = x -> x + 3;
Function<Integer, Integer> multiplyThenAdd = multiplyBy2.andThen(add3);
Function<Integer, Integer> addThenMultiply = multiplyBy2.compose(add3);
System.out.println("Multiply then add: " + multiplyThenAdd.apply(5)); // (5*2)+3 = 13
System.out.println("Add then multiply: " + addThenMultiply.apply(5)); // (5+3)*2 = 16
// Predicate composition
Predicate<String> startsWithA = s -> s.startsWith("A");
Predicate<String> endsWithE = s -> s.endsWith("e");
Predicate<String> startsWithAAndEndsWithE = startsWithA.and(endsWithE);
Predicate<String> startsWithAOrEndsWithE = startsWithA.or(endsWithE);
Predicate<String> notStartsWithA = startsWithA.negate();
String test = "Apple";
System.out.println("Starts with A and ends with e: " + startsWithAAndEndsWithE.test(test));
System.out.println("Starts with A or ends with e: " + startsWithAOrEndsWithE.test(test));
System.out.println("Not starts with A: " + notStartsWithA.test(test));
// Currying
demonstrateCurrying();
}
public static void demonstrateCurrying() {
// Non-curried function
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
// Curried version
Function<Integer, Function<Integer, Integer>> curriedAdd =
a -> b -> a + b;
Function<Integer, Integer> add5 = curriedAdd.apply(5);
System.out.println("Add 5 to 10: " + add5.apply(10));
System.out.println("Direct curried call: " + curriedAdd.apply(3).apply(7));
// Triple function currying
Function<Integer, Function<Integer, Function<Integer, Integer>>> tripleAdd =
a -> b -> c -> a + b + c;
System.out.println("Triple add: " + tripleAdd.apply(1).apply(2).apply(3));
}
}
Lambda Utilities and Patterns
import java.util.*;
import java.util.function.*;
public class LambdaUtilities {
public static void main(String[] args) {
// Memoization pattern
Function<Integer, Integer> expensiveFunction = memoize(n -> {
System.out.println("Computing for " + n);
// Simulate expensive computation
try { Thread.sleep(100); } catch (InterruptedException e) {}
return n * n;
});
System.out.println("First call: " + expensiveFunction.apply(5));
System.out.println("Second call: " + expensiveFunction.apply(5)); // Cached
// Safe execution pattern
List<Integer> numbers = Arrays.asList(1, 2, 0, 4, 5);
Function<Integer, Integer> safeDivide = safely(n -> 100 / n, -1);
numbers.stream()
.map(safeDivide)
.forEach(System.out::println);
// Conditional execution
Consumer<String> conditionalPrint = when(
s -> s.length() > 3,
System.out::println
);
conditionalPrint.accept("Hi"); // Won't print
conditionalPrint.accept("Hello"); // Will print
// Retry pattern
Supplier<String> unreliableOperation = retry(() -> {
if (Math.random() < 0.7) {
throw new RuntimeException("Random failure");
}
return "Success!";
}, 3);
try {
System.out.println(unreliableOperation.get());
} catch (Exception e) {
System.out.println("Failed after retries: " + e.getMessage());
}
}
// Memoization utility
public static <T, R> Function<T, R> memoize(Function<T, R> function) {
Map<T, R> cache = new HashMap<>();
return input -> cache.computeIfAbsent(input, function);
}
// Safe execution utility
public static <T, R> Function<T, R> safely(Function<T, R> function, R defaultValue) {
return input -> {
try {
return function.apply(input);
} catch (Exception e) {
return defaultValue;
}
};
}
// Conditional execution utility
public static <T> Consumer<T> when(Predicate<T> condition, Consumer<T> action) {
return input -> {
if (condition.test(input)) {
action.accept(input);
}
};
}
// Retry utility
public static <T> Supplier<T> retry(Supplier<T> supplier, int maxAttempts) {
return () -> {
Exception lastException = null;
for (int i = 0; i < maxAttempts; i++) {
try {
return supplier.get();
} catch (Exception e) {
lastException = e;
System.out.println("Attempt " + (i + 1) + " failed: " + e.getMessage());
}
}
throw new RuntimeException("All attempts failed", lastException);
};
}
}
Best Practices
1. Keep Lambdas Short and Readable
public class LambdaBestPractices {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "cherry");
// Good: Short and readable
words.stream()
.filter(word -> word.length() > 5)
.forEach(System.out::println);
// Bad: Too complex for lambda
words.stream()
.filter(word -> {
boolean startsWithVowel = "aeiou".contains(word.substring(0, 1));
boolean hasRepeatedChar = false;
for (int i = 0; i < word.length() - 1; i++) {
if (word.charAt(i) == word.charAt(i + 1)) {
hasRepeatedChar = true;
break;
}
}
return startsWithVowel && !hasRepeatedChar && word.length() > 3;
})
.forEach(System.out::println);
// Better: Extract to method
words.stream()
.filter(LambdaBestPractices::isComplexCondition)
.forEach(System.out::println);
}
private static boolean isComplexCondition(String word) {
boolean startsWithVowel = "aeiou".contains(word.substring(0, 1));
boolean hasRepeatedChar = false;
for (int i = 0; i < word.length() - 1; i++) {
if (word.charAt(i) == word.charAt(i + 1)) {
hasRepeatedChar = true;
break;
}
}
return startsWithVowel && !hasRepeatedChar && word.length() > 3;
}
}
2. Prefer Method References When Appropriate
import java.util.*;
import java.util.stream.Collectors;
public class MethodReferencePreference {
public static void main(String[] args) {
List<String> names = Arrays.asList("alice", "bob", "charlie");
// Good: Method reference
List<String> upperNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// Less preferable: Lambda when method reference is available
List<String> upperNames2 = names.stream()
.map(s -> s.toUpperCase())
.collect(Collectors.toList());
// Good: Static method reference
List<Integer> lengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
// Good: Constructor reference
List<StringBuilder> builders = names.stream()
.map(StringBuilder::new)
.collect(Collectors.toList());
}
}
3. Be Mindful of Performance
import java.util.*;
import java.util.stream.IntStream;
public class LambdaPerformance {
public static void main(String[] args) {
// Avoid boxing/unboxing with primitive streams
int[] numbers = IntStream.range(1, 1000000).toArray();
// Good: Use primitive streams
long sum1 = Arrays.stream(numbers).sum();
// Less efficient: Boxing to Integer stream
long sum2 = Arrays.stream(numbers)
.mapToObj(Integer::valueOf)
.mapToInt(Integer::intValue)
.sum();
// Consider parallel streams for CPU-intensive operations
long parallelSum = Arrays.stream(numbers)
.parallel()
.map(n -> n * n)
.sum();
System.out.println("Sums: " + sum1 + ", " + sum2 + ", " + parallelSum);
}
}
Summary
Lambda expressions in Java provide:
- Concise Syntax: Replace verbose anonymous inner classes
- Functional Programming: Enable functional programming paradigms
- Stream Integration: Power the Streams API for data processing
- Method References: Even more concise notation for method calls
- Higher-Order Functions: Functions that operate on other functions
Key Benefits:
- Readability: More expressive and readable code
- Maintainability: Easier to understand and modify
- Performance: Better optimization opportunities
- Flexibility: Enable functional programming patterns
Best Practices:
- Keep lambdas short and readable
- Use method references when appropriate
- Extract complex logic to named methods
- Be mindful of performance with primitive streams
- Leverage functional interfaces effectively
Lambda expressions revolutionize Java programming by enabling more expressive, functional, and maintainable code, especially when working with collections and streams.