Java has evolved significantly since version 8, introducing powerful features that make the language more expressive and productive. Understanding these modern features is crucial for writing efficient, maintainable code.
Lambda expressions provide a clear and concise way to implement single-method interfaces (functional interfaces) using an expression-style syntax. They enable functional programming in Java and significantly reduce boilerplate code.
A functional interface in Java must have exactly one abstract method. This fundamental requirement exists because:
@FunctionalInterface
interface Calculator {
int calculate(int x, int y); // Single abstract method
default int increment(int x) { // Default methods are allowed
return x + 1;
}
static int zero() { // Static methods are allowed
return 0;
}
}
// Lambda implementation
Calculator add = (x, y) -> x + y;
Calculator multiply = (x, y) -> x * y;
// Practical examples
List names = Arrays.asList("Java", "Kotlin", "Scala");
// Collection iteration
names.forEach(name -> System.out.println(name));
// Sorting with custom comparator
Collections.sort(names, (s1, s2) -> s1.compareToIgnoreCase(s2));
// Thread creation
new Thread(() -> System.out.println("Running in new thread")).start();
The Stream API enables functional-style operations on collections, allowing for efficient processing of data sequences. It provides a powerful and flexible way to process data with benefits including:
// Complex stream operations example
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
numbers.stream()
.filter(n -> n % 2 == 0) // Get even numbers
.map(n -> n * n) // Square them
.sorted() // Sort results
.limit(3) // Take first 3
.forEach(System.out::println); // Print results
Records, introduced in Java 16, are immutable data classes that reduce boilerplate code for classes that are used to store data. They automatically provide:
// Simple record example
public record Person(String name, int age) {
// Compact constructor for validation
public Person {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
}
// Additional methods can be added
public boolean isAdult() {
return age >= 18;
}
}
Pattern matching simplifies type checking and data extraction, making code more concise and readable. This feature has evolved across multiple Java versions, offering:
// Advanced pattern matching examples
public String formatValue(Object obj) {
return switch (obj) {
case String s when s.length() > 5 -> "Long String: " + s;
case String s -> "String: " + s;
case Integer i when i > 100 -> "Large number: " + i;
case Integer i -> "Number: " + i;
case Person p -> "Person: " + p.name();
case null -> "null value";
default -> obj.toString();
};
}