8️⃣ Java 8 Features
Explore the revolutionary features introduced in Java 8, including Lambdas, Streams, Optional, and the new Date/Time API.
1. Lambda Expressions
Lambda expressions introduced a new syntax to Java, allowing you to treat functionality as a method argument, or code as data. This significantly reduces the boilerplate required for anonymous inner classes.
- Syntax:
(parameters) -> expressionor(parameters) -> { statements; } - Functional Interface: A lambda expression can only be used where the expected type is a functional interface (an interface with exactly one abstract method).
- Capturing Variables: Variables used inside a lambda must be effectively final.
- Type Inference: The compiler can often infer parameter types based on the context.
import java.util.Arrays; import java.util.Collections; import java.util.List; public class LambdaDemo { public static void main(String[] args) { // Runnable with lambda Runnable r = () -> System.out.println("Running in a thread!"); new Thread(r).start(); // Sorting a list using lambda List<String> names = Arrays.asList("John", "Alice", "Bob"); // Old way: new Comparator<String>() { ... } // New way: Lambda expression Collections.sort(names, (a, b) -> a.compareTo(b)); System.out.println(names); // [Alice, Bob, John] } }
[Alice, Bob, John]
2. Functional Interfaces
The java.util.function package introduced several built-in functional interfaces to represent common functions, predicates, and consumers.
Predicate<T>: TakesT, returnsboolean(useful for filtering).Function<T, R>: TakesT, returnsR(useful for mapping/transforming).Consumer<T>: TakesT, returns void (useful for iterating/processing).Supplier<T>: Takes nothing, returnsT(useful for lazy initialization).BiFunction<T, U, R>: TakesTandU, returnsR.
import java.util.function.*; public class FunctionalInterfacesDemo { public static void main(String[] args) { // Predicate Predicate<Integer> isEven = n -> n % 2 == 0; System.out.println("Is 4 even? " + isEven.test(4)); // Function Function<String, Integer> lengthFunc = s -> s.length(); System.out.println("Length of 'Java': " + lengthFunc.apply("Java")); // Consumer Consumer<String> printer = s -> System.out.println("Printing: " + s); printer.accept("Hello Functional World"); // Supplier Supplier<Double> randomValue = () -> Math.random(); System.out.println("Random: " + randomValue.get()); // BiFunction BiFunction<Integer, Integer, Integer> adder = (a, b) -> a + b; System.out.println("Sum: " + adder.apply(10, 20)); } }
@FunctionalInterface to ensure they only have one abstract method.
3. Method References
Method references provide a way to refer to methods or constructors without executing them. They can be considered shorthand for lambdas calling only a specific method.
import java.util.Arrays; import java.util.List; import java.util.function.Supplier; class Person { String name; Person() { this.name = "Unknown"; } Person(String name) { this.name = name; } void printName() { System.out.println(this.name); } static void sayHello() { System.out.println("Hello"); } } public class MethodReferenceDemo { public static void main(String[] args) { List<String> names = Arrays.asList("Tom", "Jerry"); // 1. Static method reference (ClassName::staticMethod) Runnable r = Person::sayHello; r.run(); // 2. Bound instance method reference (obj::instanceMethod) Person p = new Person("Alice"); Runnable r2 = p::printName; r2.run(); // 3. Unbound instance method reference (ClassName::instanceMethod) // Passes the element as the target of the method names.forEach(System.out::println); // 4. Constructor reference (ClassName::new) Supplier<Person> personFactory = Person::new; Person newPerson = personFactory.get(); newPerson.printName(); } }
4. Stream API
The Stream API introduced a declarative way to process collections of objects. Streams can be executed sequentially or in parallel.
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamBasicsDemo { public static void main(String[] args) { List<String> items = Arrays.asList("apple", "banana", "cherry", "date", "apricot"); List<String> filtered = items.stream() .filter(s -> s.startsWith("a")) // Intermediate: filter .map(String::toUpperCase) // Intermediate: map .sorted() // Intermediate: sort .collect(Collectors.toList()); // Terminal: collect System.out.println(filtered); // [APPLE, APRICOT] } }
import java.util.*; import java.util.stream.Collectors; class Employee { String name, dept; int salary; Employee(String n, String d, int s) { name = n; dept = d; salary = s; } public String getDept() { return dept; } } public class StreamCollectorsDemo { public static void main(String[] args) { List<Employee> emps = Arrays.asList( new Employee("Alice", "IT", 5000), new Employee("Bob", "HR", 4000), new Employee("Charlie", "IT", 6000) ); // Group by department Map<String, List<Employee>> byDept = emps.stream() .collect(Collectors.groupingBy(Employee::getDept)); System.out.println("IT Employees count: " + byDept.get("IT").size()); // Joining names String names = emps.stream() .map(e -> e.name) .collect(Collectors.joining(", ")); System.out.println("All names: " + names); } }
import java.util.stream.IntStream; public class ParallelStreamDemo { public static void main(String[] args) { long start = System.currentTimeMillis(); long count = IntStream.range(1, 10000000) .parallel() .filter(n -> isPrime(n)) .count(); long end = System.currentTimeMillis(); System.out.println("Primes found: " + count + " in " + (end - start) + "ms"); } static boolean isPrime(int n) { if (n <= 1) return false; for (int i = 2; i * i <= n; i++) { if (n % i == 0) return false; } return true; } }
5. Optional Class
Optional<T> is a container object which may or may not contain a non-null value. It provides a better alternative to returning null, avoiding NullPointerException.
import java.util.Optional; public class OptionalDemo { public static void main(String[] args) { String name = null; // Creating Optional Optional<String> optName = Optional.ofNullable(name); // Providing default value String result = optName.orElse("Unknown User"); System.out.println(result); // Unknown User // Using map and filter Optional<String> upper = Optional.of("java") .filter(s -> s.length() >= 3) .map(String::toUpperCase); upper.ifPresent(System.out::println); // JAVA } }
6. Default and Static Methods in Interfaces
Interfaces can now have implementations via default methods, which help in evolving interfaces without breaking implementing classes. static methods in interfaces were also introduced.
interface Vehicle { void start(); // Default method default void honk() { System.out.println("Beep beep!"); } // Static method static void displayInfo() { System.out.println("Vehicles help in transport."); } } class Car implements Vehicle { public void start() { System.out.println("Car started."); } } public class InterfaceDefaultMethodDemo { public static void main(String[] args) { Car car = new Car(); car.start(); car.honk(); // Inherited default method Vehicle.displayInfo(); // Calling static interface method } }
7. New Date/Time API (java.time)
The old java.util.Date and Calendar were mutable, not thread-safe, and poorly designed. Java 8 introduced the Joda-Time-inspired java.time package.
import java.time.*; import java.time.format.DateTimeFormatter; public class DateTimeDemo { public static void main(String[] args) { // LocalDate, LocalTime, LocalDateTime LocalDate date = LocalDate.now(); LocalTime time = LocalTime.now(); LocalDateTime dt = LocalDateTime.now(); System.out.println("Current Date: " + date); // Formatting DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm"); System.out.println("Formatted: " + dt.format(formatter)); // ZonedDateTime ZonedDateTime tokyoTime = ZonedDateTime.now(ZoneId.of("Asia/Tokyo")); System.out.println("Tokyo Time: " + tokyoTime); // Period & Duration LocalDate future = date.plusDays(5); Period period = Period.between(date, future); System.out.println("Days diff: " + period.getDays()); } }
8. Base64 Encoding/Decoding
Java 8 provides standard built-in support for Base64 encoding and decoding.
import java.util.Base64; public class Base64Demo { public static void main(String[] args) { String originalStr = "Java 8 is awesome"; // Encode String encodedStr = Base64.getEncoder().encodeToString(originalStr.getBytes()); System.out.println("Encoded: " + encodedStr); // Decode byte[] decodedBytes = Base64.getDecoder().decode(encodedStr); System.out.println("Decoded: " + new String(decodedBytes)); } }
9. Nashorn JavaScript Engine
Nashorn was introduced in Java 8 to execute JavaScript code from Java applications. Note: It was deprecated in Java 11 and removed in Java 15.
10. Summary Table
| Feature | Description |
|---|---|
| Lambda Expressions | Enable functional programming in Java. |
| Stream API | Pipeline operations on collections. |
| Optional | Null-safe wrapper for values. |
| Date/Time API | Immutable and thread-safe date/time models. |
| Default Methods | Method implementations inside interfaces. |