Java 8 (March 2014)¶
Release Information
Release Date: March 18, 2014 End of Support: December 2030 (Extended) Theme: Functional Programming Revolution
Overview¶
Java 8 was a revolutionary release that transformed Java from a purely object-oriented language into one that embraces functional programming paradigms. This release is still widely used and remains the baseline for many enterprise applications.
Lambda Expressions¶
Interview Favorite
One of the most asked features. Know the syntax and functional interfaces.
The Problem¶
Creating instances of single-method interfaces required verbose anonymous classes.
Before/After¶
// Verbose anonymous class
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button clicked!");
}
});
// Sorting with Comparator
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
Key Points¶
- Lambda syntax:
(parameters) -> expressionor(parameters) -> { statements; } - Works with functional interfaces (interfaces with single abstract method)
@FunctionalInterfaceannotation ensures compile-time checking
Stream API¶
Interview Favorite
Know the difference between intermediate and terminal operations.
The Problem¶
Processing collections required imperative loops with mutable state.
Before/After¶
// Filter and transform a list
List<String> filtered = new ArrayList<>();
for (Person person : people) {
if (person.getAge() >= 18) {
filtered.add(person.getName().toUpperCase());
}
}
// Find sum of values
int total = 0;
for (Order order : orders) {
if (order.getStatus() == Status.COMPLETED) {
total += order.getAmount();
}
}
// Declarative, readable pipeline
List<String> filtered = people.stream()
.filter(p -> p.getAge() >= 18)
.map(Person::getName)
.map(String::toUpperCase)
.collect(Collectors.toList());
// Sum with streams
int total = orders.stream()
.filter(o -> o.getStatus() == Status.COMPLETED)
.mapToInt(Order::getAmount)
.sum();
// Parallel processing - just change stream() to parallelStream()
List<String> filtered = people.parallelStream()
.filter(p -> p.getAge() >= 18)
.map(Person::getName)
.collect(Collectors.toList());
Common Stream Operations¶
| Type | Operations |
|---|---|
| Intermediate | filter, map, flatMap, sorted, distinct, limit, skip |
| Terminal | collect, forEach, reduce, count, findFirst, anyMatch |
Optional¶
The Problem¶
NullPointerException was the most common runtime error. Null checks cluttered code.
Before/After¶
// Elegant Optional chain
String city = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.orElse("Unknown");
// Other useful methods
Optional<User> user = findUserById(id);
user.ifPresent(u -> System.out.println(u.getName()));
User result = user.orElseThrow(() -> new UserNotFoundException(id));
Optional Best Practices¶
Do
- Use as return type for methods that might not return a value
- Use
map()andflatMap()for transformations - Use
orElse(),orElseGet(),orElseThrow()for defaults
Don't
- Use Optional as method parameter
- Use Optional as field type
- Call
get()without checkingisPresent()
Date/Time API (java.time)¶
The Problem¶
java.util.Date and Calendar were confusing, mutable, and not thread-safe.
Before/After¶
// Confusing - month is 0-indexed!
Calendar cal = Calendar.getInstance();
cal.set(2024, Calendar.JANUARY, 15); // January, not February!
Date date = cal.getTime();
// Mutable - dangerous in multithreaded code
date.setTime(System.currentTimeMillis());
// Formatting requires separate class
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String formatted = sdf.format(date); // Not thread-safe!
// Clear and intuitive
LocalDate date = LocalDate.of(2024, 1, 15); // January 15
LocalDateTime dateTime = LocalDateTime.of(2024, 1, 15, 10, 30);
ZonedDateTime zoned = ZonedDateTime.now(ZoneId.of("America/New_York"));
// Immutable - safe to share
LocalDate tomorrow = date.plusDays(1); // Returns new instance
// Built-in formatting
String formatted = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
DateTimeFormatter custom = DateTimeFormatter.ofPattern("dd/MM/yyyy");
String customFormatted = date.format(custom);
// Duration and Period
Duration duration = Duration.between(startTime, endTime);
Period period = Period.between(startDate, endDate);
java.time Classes¶
| Class | Use Case |
|---|---|
LocalDate |
Date without time (birthday, holiday) |
LocalTime |
Time without date (opening hours) |
LocalDateTime |
Date and time without timezone |
ZonedDateTime |
Date and time with timezone |
Instant |
Machine timestamp (epoch-based) |
Duration |
Time-based amount (hours, minutes) |
Period |
Date-based amount (years, months, days) |
Default Methods in Interfaces¶
The Problem¶
Adding methods to interfaces broke all implementations.
Before/After¶
// Default implementation in interface
public interface Vehicle {
void start();
default void stop() {
System.out.println("Vehicle stopped");
}
// Static methods also allowed
static Vehicle create(String type) {
return switch(type) {
case "car" -> new Car();
case "bike" -> new Bike();
default -> throw new IllegalArgumentException();
};
}
}
// Existing implementations continue to work
// Or override if needed
public class Car implements Vehicle {
public void start() { }
// stop() is inherited with default behavior
}
Method References¶
The Problem¶
Even simple lambdas that just call a method were somewhat verbose.
Syntax¶
| Type | Lambda | Method Reference |
|---|---|---|
| Static method | x -> Math.abs(x) |
Math::abs |
| Instance method | s -> s.toUpperCase() |
String::toUpperCase |
| Instance on object | x -> obj.process(x) |
obj::process |
| Constructor | s -> new Person(s) |
Person::new |
Example¶
// Lambda vs Method Reference
list.forEach(s -> System.out.println(s));
list.forEach(System.out::println); // Cleaner
list.stream().map(s -> s.toUpperCase());
list.stream().map(String::toUpperCase); // Cleaner
list.stream().map(name -> new Person(name));
list.stream().map(Person::new); // Cleaner
Quick Reference¶
// Lambda
(a, b) -> a + b
x -> x * 2
() -> System.out.println("Hello")
// Stream
list.stream()
.filter(x -> x > 0)
.map(x -> x * 2)
.collect(Collectors.toList());
// Optional
Optional.ofNullable(value)
.map(v -> v.transform())
.orElse(defaultValue);
// Date/Time
LocalDate.now();
LocalDateTime.of(2024, 1, 15, 10, 30);
date.plusDays(1);