Java 8
The main changes of the Java 8 release were these:
· Lambda Expression and Stream API
Java 9
Java 9 introduced these main features:
· Diamond Syntax with Inner Anonymous Classes
var
Switch
expressions allowed us to omit break
calls
inside every case
block.
Using Switch Expressions
days = switch (month) {
case JANUARY, MARCH, MAY, JULY, AUGUST, OCTOBER, DECEMBER -> 31;
case FEBRUARY -> 28;
case APRIL, JUNE, SEPTEMBER, NOVEMBER -> 30;
default -> throw new IllegalStateException();
};
The yield Keyword
days = switch (month) {
case JANUARY, MARCH, MAY, JULY, AUGUST, OCTOBER, DECEMBER -> {
System.out.println(month);
yield 31;
}
defining variable Car c and Bicycle b
public class PatternMatching {
public static double price(Vehicle v) {
if (v instanceof Car c) {
return 10000 - c.kilomenters * 0.01 -
(Calendar.getInstance().get(Calendar.YEAR) -
c.year) * 100;
} else if (v instanceof Bicycle b) {
return 1000 + b.wheelSize * 10;
} else throw new IllegalArgumentException();
}
}
public record VehicleRecord(String code, String engineType) {}
we cannot extend a record class
public sealed class Vehicle permits Bicycle, Car {...}
The final modifier on a class doesn’t allow anyone to extend it
What about when we want to extend a class but only allow it for some classes?
public sealed class Vehicle permits Bicycle, Car {...}
A Java functional interface is an interface that contains exactly one abstract method.
@FunctionalInterface annotation
can be used to explicitly mark an interface as functional
it serves as a compile-time check, preventing the addition of more abstract
methods and ensuring the interface adheres to the functional interface
contract.
Function Interfaces
· Function<T, R> - Takes one argument of type T and returns type R
· BiFunction<T, U, R> - Takes two arguments and returns a result
· UnaryOperator<T> - Special case of Function where input and output are the same type
· BinaryOperator<T> - Special case of BiFunction where both inputs and output are the same type
Consumer Interfaces
· Consumer<T> - Takes one argument and returns nothing (void)
· BiConsumer<T, U> - Takes two arguments and returns nothing
Supplier Interfaces
· Supplier<T> - Takes no arguments and returns a result
Predicate Interfaces
· Predicate<T> - Takes one argument and returns boolean
· BiPredicate<T, U> - Takes two arguments and returns boolean
@FunctionalInterface
public interface Calculator {
double calculate(double x, double y);
}
Lambda expressions: list.forEach(item -> System.out.println(item))
Method references: list.forEach(System.out::println)
Stream
operations: stream.map(String::toUpperCase).filter(s -> s.length() > 3)
// Function: Convert String to Integer
Function<String, Integer> stringToInt = s -> Integer.parseInt(s);
Integer num = stringToInt.apply("123"); // returns 123
// Consumer: Print a message
Consumer<String> printer = s -> System.out.println(s);
printer.accept("Hello, World!"); // prints "Hello, World!"
// Supplier: Get a random number
Supplier<Double> randomSupplier = () -> Math.random();
Double rand = randomSupplier.get(); // returns a random double
// Predicate: Check if a string is empty
Predicate<String> isEmpty = s -> s.isEmpty();
boolean result = isEmpty.test(""); // returns true
List<List<String>> listOfLists = List.of(
List.of("a", "b", "c"),
List.of("d", "e"),
List.of("f", "g", "h")
);
// Goal: Convert to ["a", "b", "c", "d", "e", "f", "g", "h"]
List<String> singleList = listOfLists.stream()
.flatMap(oneList -> oneList.stream()) // Function: List<String> -> Stream<String>
.toList();
// Input: 3 elements (lists) -> Output: 8 elements (strings)
List<String> lines = List.of("Hello world", "Java streams", "flatMap example");
// Goal: Get a list of all individual words: ["Hello", "world", "Java", "streams", "flatMap", "example"]
// Using map (WRONG - gives us streams of words, not words)
List<Stream<String>> wrongResult = lines.stream()
.map(line -> Arrays.stream(line.split(" ")))
.toList(); // A list of 3 streams! Not what we want.
// Using flatMap (CORRECT - flattens all the streams of words into one stream)
List<String> allWords = lines.stream()
.flatMap(line -> Arrays.stream(line.split(" "))) // Split line into a Stream<String>
.toList();
System.out.println(allWords);
// Output: [Hello, world, Java, streams, flatMap, example]
Optional<String> optionalValue = Optional.of("hello");
// Let's say we have a function that returns an Optional
Optional<String> toUpperCaseOptional(String s) {
return Optional.of(s.toUpperCase());
}
// Using map (results in Optional<Optional<String>> - BAD!)
Optional<Optional<String>> badResult = optionalValue.map(v -> toUpperCaseOptional(v));
// Using flatMap (results in Optional<String> - GOOD!)
Optional<String> goodResult = optionalValue.flatMap(v -> toUpperCaseOptional(v));
System.out.println(goodResult); // Output: Optional[HELLO]