Benefits of New Features
- Improved Performance
- Reduced Code Verbosity (Lambda Expressions)
- Powerful Stream API
- Enhanced Parallel Processing Support
- Minimized NullPointerException Risks: Optional
- Nashorn Engine for Running JS Applications on JVM
Lambda Expressions
A Lambda Expression is an anonymous function, representing a block of code that can be passed around as data.
Core Concept
It acts as a concrete implementation of a functional interface.
Usage Syntax
(parameter list) -> expression or code block
->: Lambda operator- Left side: Parameter list matching the abstract method of the functional interface.
- Right side: Implementation body for the abstract method.
Key Points:
- Parameter types can be omitted (type inference). Parentheses can be omitted for a single parameter.
- The body can be a single expression or a block in
{}. Braces and thereturnkeyword can be omitted for a single expression that returns a value.
Syntax 1: No Parameters, No Return Value
Runnable task1 = new Runnable() {
@Override
public void run() {
System.out.println("Running task1");
}
};
task1.run();
// Using Lambda
Runnable task2 = () -> System.out.println("Running task2");
task2.run();
Syntax 2: One Parameter, No Return Value
Consumer<String> printer1 = new Consumer<>() {
@Override
public void accept(String msg) {
System.out.println(msg);
}
};
printer1.accept("Message1");
// Using Lambda
Consumer<String> printer2 = (String s) -> System.out.println(s);
printer2.accept("Message2");
// With type inference
Consumer<String> printer3 = (s) -> System.out.println(s);
printer3.accept("Message3");
// Omitting parentheses for single parameter
Consumer<String> printer4 = s -> System.out.println(s);
printer4.accept("Message4");
Syntax 3: Multiple Parameters, Return Value
Comparator<Integer> comp1 = new Comparator<>() {
@Override
public int compare(Integer a, Integer b) {
System.out.println(a);
System.out.println(b);
return a - b;
}
};
// Using Lambda
Comparator<Integer> comp2 = (Integer a, Integer b) -> {
System.out.println(a);
System.out.println(b);
return a - b;
};
// With type inference
Comparator<Integer> comp3 = (a, b) -> {
System.out.println(a);
System.out.println(b);
return a - b;
};
// Single expression body (return implied)
Comparator<Integer> comp4 = (a, b) -> a - b;
Functional Interfaces
- An interface with exactly one abstract method is a functional interface.
- Lambda expressions can create instances of such interfaces. If the lambda throws a checked exception, it must be declared in the interface's abstract method signature.
- The
@FunctionalInterfaceannotation can be used for verification. - The
java.util.functionpackage contains many built-in functional interfaces.
Core Built-in Functional Interfaces
Example Usage
// Consumer example
public void processValue(double value, Consumer<Double> handler) {
handler.accept(value);
}
// Test call
processValue(10.5, val -> System.out.println(val));
// Predicate example
public List<String> filterList(List<String> input, Predicate<String> condition) {
List<String> result = new ArrayList<>();
for (String item : input) {
if (condition.test(item)) {
result.add(item);
}
}
return result;
}
// Test call: filter strings containing "java"
List<String> filtered = filterList(list, str -> str.contains("java"));
Method References
Method references provide a shorthand syntax for lambda expressions when the implementation simply calls an existing method.
Core Requirement
The parameter list and return type of the functional interface's abstract method must match the referenced method.
Use Case
Use when the lambda body directly calls an existing method.
Case 1: Instance Method Reference via Object
// Lambda
Consumer<String> c1 = str -> System.out.println(str);
c1.accept("Hello");
// Method Reference (System.out is an instance of PrintStream)
Consumer<String> c2 = System.out::println;
c2.accept("World");
// Another example with a custom object
Employee emp = new Employee("Alice");
Supplier<String> nameSupplier1 = () -> emp.getName();
System.out.println(nameSupplier1.get());
Supplier<String> nameSupplier2 = emp::getName;
System.out.println(nameSupplier2.get());
Case 2: Static Method Reference via Class
// Lambda
Comparator<Integer> comp1 = (x, y) -> Integer.compare(x, y);
// Method Reference
Comparator<Integer> comp2 = Integer::compare;
// Another example
Function<Double, Long> rounder1 = num -> Math.round(num);
Function<Double, Long> rounder2 = Math::round;
Case 3: Instance Method Reference via Class (First Parameter as Target)
// Lambda for comparing strings
Comparator<String> strComp1 = (s1, s2) -> s1.compareTo(s2);
// Method Reference (compareTo is an instance method of String)
Comparator<String> strComp2 = String::compareTo;
// Lambda for checking equality
BiPredicate<String, String> equalityCheck1 = (str1, str2) -> str1.equals(str2);
BiPredicate<String, String> equalityCheck2 = String::equals;
Constructor References
Similar to method references, but used to refer to constructors.
Format
ClassName::new
Case 1: No-arguement Constructor
// Lambda
Supplier<Employee> empSupplier1 = () -> new Employee();
// Constructor Reference
Supplier<Employee> empSupplier2 = Employee::new;
Case 2: Constructor with Arguments
// Lambda for one-argument constructor
Function<Integer, Employee> empCreator1 = id -> new Employee(id);
// Constructor Reference
Function<Integer, Employee> empCreator2 = Employee::new;
// Lambda for two-argument constructor
BiFunction<Integer, String, Employee> empCreator3 = (id, name) -> new Employee(id, name);
// Constructor Reference
BiFunction<Integer, String, Employee> empCreator4 = Employee::new;
Array References
Treat an array type as a special class for reference.
Format
Type[]::new
Example
// Lambda to create a String array
Function<Integer, String[]> arrayCreator1 = size -> new String[size];
// Array Reference
Function<Integer, String[]> arrayCreator2 = String[]::new;