Exploring the Exciting New Features in Java 8
- Functional Interfaces: Built-in Functionality
- Lambdas: Simplifying Functional Programming
- Stream API: Efficient Data Processing
- Optional: Handling Null Values Gracefully
- Default Methods: Extending Interfaces
- Date and Time API: Modernizing Date Handling
- CompletableFuture: Asynchronous Programming Made Easier
Functional Interfaces: Built-in Functionality
Functional Interface has only one abstract method using @FunctionalInterface
to mark an interface as functional. built-in functional interfaces like Predicate, Function, and Supplier.
Why? Leveraging functional interfaces for improved code readability.
@FunctionalInterface
interface MyFunctionalInterface {
void doSomething();
}
Example - Runnable Functional interfaces can be used to define tasks that can be executed concurrently. The Runnable interface is a functional interface with a single method void run(), commonly used for executing code in a separate thread.
@FunctionalInterface
interface Runnable {
void run();
}
// Usage
Runnable task = () -> System.out.println("Executing task");
Thread thread = new Thread(task);
thread.start();
Lambdas: Simplifying Functional Programming
Lambda Function allows us to define a block of code that can be passed around and executed later.
Times each integers
// times each integers by 2
// java 7
List<Integer> nums = Arrays.asList(1, 2, 3);
for (int num: nums){
System.out.println(num*2);
}
// java 8
nums.forEach(num -> {
System.out.println(num * 2);
});
Create Max Heap
// create max heap
PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
maxHeap.add(1);
maxHeap.add(2);
maxHeap.forEach(System.out::println);
Sorting Desc
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 6);
// Sorting in descending order using lambda function
Collections.sort(numbers, (a, b) -> b.compareTo(a));
System.out.println(numbers); // Output: [8, 6, 5, 2, 1]
Create Thread using lambda
new Thread(() -> System.out.println("this is java8")).start();
Stream API: Efficient Data Processing
Stream API support functional-style operations on streams of elements. It is process the data from a collection, does not change the original data.
In the stream api , we have two diff functions: intermediate function, terminal function.
Intermidiate function
Intermidiate function transform or filter the elements of a stream and return a new stream as the result.
map(Function<T, R> mapper)
: transforms the elements of the stream into a new type.filter(Predicate<T> predicate)
: selects only the elements that match a given condition.sorted()
: orders the elements based on a given condition.distinct()
: eliminate duplicate elements.limit(long maxSize)
: truncates the stream to a specified size.skip(long n)
: skips the first n elements in the stream.
Terminal function
terminal function is an operation that produces a result or a side effect.
forEach()
: performs an action on each elements.reduce()
: aggregates the elements into a single result.collect()
: return elements into a new collection -> list, arraylist.min()/ max()
: find the min/ max value in the stream.count()
: return the # of elements in the streamanyMatch()/ allMatch() / nonMatch()
: return a boolean, you will give condition, return true/false, if there any / all / none of elements match given a condition/predicate.
Examples with stream api
Example 1: find all the strings that start with “c”, make it upper case, and sort it
// res: [C4, C6]
List<String> myStreamList = Arrays.asList("a1","b2","a3","c4","c6");
var res = myStreamList.stream()
.filter(s -> s.startsWith("c"))
.map(String::toUpperCase)
.sorted().toList();
System.out.println(res);
Example 2: find any strings that contains lower case
// res: [a, cBBBB, caaaa]
List<String> myStreamList1 = Arrays.asList("a","SKKKKS","OOOO","cBBBB","caaaa");
List<String> res1 = myStreamList1.stream()
.filter(s -> s.chars().anyMatch(Character::isLowerCase))
.toList();
System.out.println(res1);
Example 3: find any strings contains upper case
// res: [SKKKKS, OOOO, cBBBB]
List<String> myStreamList1 = Arrays.asList("a","SKKKKS","OOOO","cBBBB","caaaa");
List<String> res1 = myStreamList1.stream()
.filter(s -> s.chars().anyMatch(Character::isUpperCase))
.toList();
System.out.println(res1);
Example 4: find any string contains lowercase and numbers
// res: [Abc123, abc123, abc123]
List<String> myStream2 = Arrays.asList("Abc123","abc123","abd","ABC123","abc123");
List<String> res2 = myStream2.stream()
.filter(s ->s.matches(".*\\d.*"))
.filter(s-> s.chars().anyMatch(Character::isLowerCase))
.toList();
System.out.println(res2);
Example 5: sum of even numbers
// res: 30
List<Integer> listNums = Arrays.asList(1,2,4,5,6,7,8,9,10);
int sumAll = listNums.stream()
.filter(n -> n%2==0)
.reduce(0, Integer::sum);
System.out.println(sumAll);
Example 6: find the max number
// res: 10
List<Integer> listNums = Arrays.asList(1,2,4,5,6,7,8,9,10);
int maxNum = listNums.stream().reduce(Integer::max).get();
System.out.println(maxNum);
Example 7: find if any numbers >= 5 | find all numbers >= 10
List<Integer> listNums = Arrays.asList(1,2,4,5,6,7,8,9,10);
// find any numbers >= 5, res: true
System.out.println(listNums.stream().anyMatch(n -> n>=5));
// find any numbers >= 10, res: false
System.out.println(listNums.stream().allMatch(n -> n>=10));
Optional: Handling Null Values Gracefully
Optional can avoid any nullPointerException. See the example to understand Optional easier.
- Define BankAccount class:
class BankAccount {
private final String accountNumber;
private final double balance;
private final Optional<String> password;
public BankAccount(String accountNumber, double balance, Optional<String> password) {
this.accountNumber = accountNumber;
this.balance = balance;
this.password = password;
}
public Optional<String> getPassword() {
return password;
}
}
- Create object and getpassword
BankAccount user1 = new BankAccount("a001", 1000.0, Optional.empty());
BankAccount user2 = new BankAccount("a002", 55.0, Optional.of("password123"));
//this is your subsystem that check each users have password
Optional<String> user1Password = user1.getPassword();
if (user1Password.isPresent()) {
String password = user1Password.get();
System.out.println(password);
} else {
System.out.println("user 1 does not have password");
}
Default Methods: Extending Interfaces
Date and Time API: Modernizing Date Handling
CompletableFuture: Asynchronous Programming Made Easier
Introduction to CompletableFuture
CompletableFuture
used to write asynchronous, non-blocking code.
Here are some key concepts and functionalities:
- public static CompletableFuture
runAsync(Runnable runnable)
- public static CompletableFuture
runAsync(Runnable runnable, Executor executor)
- public static CompletableFuture
supplyAsync(Supplier<U> supplier)
- public static CompletableFuture
supplyAsync(Supplier<U> supplier, Executor executor)
- public T
join()
- public T
get()
throws InterruptedException, ExecutionException
Advanced method: thenApply, thenRun, theAccept