Java 8 Features - Lambda, Streams, Optional, Method References | Java Codeex
1. Overview of Java 8
Java 8, released in March 2014, was one of the most significant releases since Java 5. It fundamentally changed the way Java developers write code by introducing functional programming concepts into the language. Before Java 8, Java was purely object-oriented; after Java 8, it became a multi-paradigm language supporting functional-style programming.
Key Features Introduced in Java 8
- Lambda Expressions — concise anonymous function syntax
- Functional Interfaces — single-abstract-method interfaces
- Stream API — declarative data processing pipeline
- Optional<T> — null-safe container type
- Method References — shorthand for lambdas calling existing methods
- Default and Static Interface Methods — interface evolution without breaking implementations
- New Date and Time API (java.time) — immutable, thread-safe date/time classes
- Collectors — powerful reduction operations for streams
- Nashorn JavaScript Engine — embedded JS engine (later removed)
- Base64 Encoding/Decoding — built-in java.util.Base64
Impact: Java 8 made Java competitive with functional languages like Scala and Haskell while retaining full backward compatibility.
2. Lambda Expressions
A lambda expression is a concise way to represent an anonymous function — a block of code that can be passed around as a value. Lambdas enable you to treat functionality as a method argument and eliminate verbose anonymous class boilerplate.
Syntax
(parameters) -> expression
(parameters) -> { statements; }
import java.util.*;
public class LambdaDemo {
public static void main(String[] args) {
// Example 1: Replacing anonymous Runnable
// Before Java 8
Runnable oldWay = new Runnable() {
@Override
public void run() {
System.out.println("Old way: anonymous class");
}
};
// Java 8 lambda
Runnable newWay = () -> System.out.println("New way: lambda");
oldWay.run();
newWay.run();
// Example 2: Replacing anonymous Comparator
List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
// Before Java 8
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
System.out.println("Sorted (old): " + names);
// Java 8 lambda
names.sort((a, b) -> b.compareTo(a)); // reverse
System.out.println("Sorted (lambda): " + names);
// Example 3: Lambda with block body and multiple statements
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
nums.forEach(n -> {
int square = n * n;
System.out.println(n + " squared = " + square);
});
}
}
New way: lambda
Sorted (old): [Alice, Bob, Charlie]
Sorted (lambda): [Charlie, Bob, Alice]
1 squared = 1
2 squared = 4
3 squared = 9
4 squared = 16
5 squared = 25
3. Functional Interfaces
A functional interface is an interface with
exactly one abstract method. Lambda expressions
are instances of functional interfaces — the compiler infers which
abstract method the lambda implements. The
@FunctionalInterface annotation is
optional but recommended: it causes a compile error if the
interface accidentally gets a second abstract method.
import java.util.concurrent.Callable;
// Custom functional interface
@FunctionalInterface
interface Transformer {
String transform(String input);
// Adding a second abstract method here would cause a compile error
}
public class FunctionalInterfaceDemo {
public static void main(String[] args) {
// Runnable — no args, no return value
Runnable r = () -> System.out.println("Running!");
r.run();
// Comparator — two args, returns int
java.util.Comparator<String> comp =
(s1, s2) -> s1.length() - s2.length();
System.out.println("Compare: " + comp.compare("Hi", "Hello"));
// Callable — no args, returns value, can throw
Callable<Integer> callable = () -> 42;
try {
System.out.println("Callable result: " + callable.call());
} catch (Exception e) {
e.printStackTrace();
}
// Custom functional interface
Transformer upper = s -> s.toUpperCase();
Transformer shout = s -> s.toUpperCase() + "!!!";
System.out.println(upper.transform("hello world"));
System.out.println(shout.transform("hello world"));
}
}
Compare: -3
Callable result: 42
HELLO WORLD
HELLO WORLD!!!
4. Predicate<T>
Predicate<T> (in
java.util.function) represents a
boolean-valued function of one argument. Its single abstract
method is boolean test(T t).
Predicates can be composed using
and(),
or(), and
negate().
import java.util.*;
import java.util.function.Predicate;
public class PredicateDemo {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<Integer> isGreater5 = n -> n > 5;
// test
System.out.println("4 is even? " + isEven.test(4));
System.out.println("7 is even? " + isEven.test(7));
// and — both conditions must be true
Predicate<Integer> evenAndGreater5 = isEven.and(isGreater5);
System.out.print("Even AND > 5: ");
numbers.stream()
.filter(evenAndGreater5)
.forEach(n -> System.out.print(n + " "));
System.out.println();
// or — at least one condition true
Predicate<Integer> evenOrGreater5 = isEven.or(isGreater5);
System.out.print("Even OR > 5: ");
numbers.stream()
.filter(evenOrGreater5)
.forEach(n -> System.out.print(n + " "));
System.out.println();
// negate — opposite condition
System.out.print("NOT even: ");
numbers.stream()
.filter(isEven.negate())
.forEach(n -> System.out.print(n + " "));
System.out.println();
// Filtering a list of strings
List<String> words = Arrays.asList("apple", "ant", "banana", "avocado", "cherry");
Predicate<String> startsWithA = s -> s.startsWith("a");
Predicate<String> longerThan5 = s -> s.length() > 5;
System.out.print("Starts with 'a' and longer than 5: ");
words.stream()
.filter(startsWithA.and(longerThan5))
.forEach(s -> System.out.print(s + " "));
System.out.println();
}
}
7 is even? false
Even AND > 5: 6 8 10
Even OR > 5: 2 4 6 7 8 9 10