Java 8 introduced several powerful features, includingLambda Expressions, Functional Interfaces, Streams API, Default Methods, Optional Class, New Date/Time API, and more. Below is a comprehensive Java 8 tutorial with explanations and coding examples
Java 8 Lambda Expressions
Lambda expressions allow us to writefunctional-style code concisely, particularly when working with collections and functional interfaces. Lambda expressions allow you to treat functionality as a method argument or create a more concise way of writing code, especially in places where you traditionally use anonymous inner classes.
Lambda Expression are used to implement the method defined in a functional interface. Java 1.8 Download
import java.util.*;
public class LambdaExample {
public static void main(String[] args) {
List list = Arrays.asList("Apple", "Banana", "Cherry");
// Using Lambda Expression to iterate
list.forEach(item -> System.out.println(item));
}
}
Explanation : item represents the item of a list, we are iterating the element of list using lambda expression/
output : Apple, Banana, Cherry
Learn more in Detail about Java 8 Lambda Expressions
Java 8 Functional Interface | Functional Interface in Java
A functional interface is an interface that contains only one abstract method. You can create your functional interface using the @FunctionalInterface annotation as well. There are some predefined functional Interface in Java like Predicate, Supplier, Consumer, Function, Bi-Function etc. Read about predefined functional interface.
@FunctionalInterface
interface MyFunctionalInterface {
void sayHello();
}
public class FunctionalInterfaceExample {
public static void main(String[] args) {
MyFunctionalInterface obj = () -> System.out.println("Hello from Lambda!");
obj.sayHello();
}
}
Learn more in Detail about Functional Interface in Java
Default & Static Methods In Functional Interface
Before Java 8, interfaces could only contain abstract methods (methods without a body). This limitation meant that when new methods were added to interfaces, all implementing classes had to provide an implementation for these methods.
To address this, Java 8 introduced default methods, which allow interfaces to have concrete methods with a body. This will enable you to add new functionality to interfaces without breaking existing implementations.
We can have N number of default and static methods in a functional interface
//Example of default and static method in functional interface
interface MyInterface {
default void show() {
System.out.println("Default Method in Interface");
}
static void printMessage() {
System.out.println("This is a static method in an interface.");
}
}
public class DefaultMethodExample implements MyInterface {
public static void main(String[] args) {
DefaultMethodExample obj = new DefaultMethodExample();
obj.show();
MyInterface.printMessage();
}
}
// output : Default Method in Interface This is a static method in an interface.
Java 8 Stream API
Streams API is one of the most important Java 8 features. A Stream is not a data structure; it is a sequence of elements that can be processed in parallel or sequentially. We can get streams from collections, arrays, or strings.
Streams support operations like:
- Filtering: Select elements based on a condition.
- Mapping: Transform elements into other forms.
- Sorting: Sort elements.
- Collecting: Collect results into collections, like lists or sets.
- Reducing: Combine elements into a single result.
//Stream API Example
import java.util.*;
import java.util.stream.*;
public class StreamExample {
public static void main(String[] args) {
// Sample List of integers
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Using Stream API to filter and print even numbers
System.out.println("Even numbers:");
numbers.stream().filter(n -> n % 2 == 0) // Filter out even numbers
.forEach(System.out::println); // Print each even number
// Using Stream API to find the sum of squares of odd numbers
int sumOfSquares = numbers.stream().filter(n -> n % 2 != 0)// Filter odd numbers
.map(n -> n * n).reduce(0, Integer::sum); // Sum up the results
System.out.println("Sum of squares of odd numbers: " + sumOfSquares);
// Using Stream API to convert List to a Set
Set uniqueNumbers = numbers.stream().collect(Collectors.toSet()); // Collect into a Set
System.out.println("Unique numbers as Set: " + uniqueNumbers);
}
}
output:
Even numbers:
2
4
6
8
10
Sum of squares of odd numbers: 165
Unique numbers as Set: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Explanation:
- Creating a Stream:
We create a Stream from the List<Integer> using the stream() method. There are many ways you can create a stream Read Here - Filtering:
The filter() method is used to filter elements. In this case, we filter out even numbers using n -> n % 2 == 0. - Mapping:
The map() method transforms the elements. We square each odd number using n -> n * n in this example. - Reducing:
The reduce() method combines the elements of the stream into a single result. We calculate the sum of the squares of odd numbers. - Collecting:
The collect() method is used to collect the results into a new collection. In this case, we collect the elements into Set to ensure uniqueness. - forEach:
The forEach() method is used to iterate over the elements in the stream and perform an action (printing in this case)
Learn More in Detail about all Java Stream API Methods
Optional Class in Java 8
The Optional class was introduced in Java 8. The primary purpose of Optional is to avoid NullPointerExceptions. Optional can be used to avoid null checks and safely handle missing values
import java.util.Optional;
public class OptionalExample {
public static void main(String[] args) {
// Example 1: Using Optional with non-null value
Optional optionalString = Optional.of("Hello, Optional!");
// Using ifPresent to print the value
optionalString.ifPresent(value -> System.out.println("Value: " + value));
// Example 2: Using Optional with a null value
Optional emptyOptional = Optional.ofNullable(null);
// Using orElse to provide a default value
String result = emptyOptional.orElse("Default Value");
System.out.println("Result: " + result);
// Prints "Default Value"
// Example 3: Using map to transform the value inside Optional
Optional upperCaseString = optionalString.map(String::toUpperCase);
upperCaseString.ifPresent(System.out::println);
// Prints "HELLO, OPTIONAL!"
// Example 4: Using orElseThrow to throw exception if value is not present
try {
String value = emptyOptional.orElseThrow(() -> new IllegalArgumentException("Value is missing"));
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
// Prints "Value is missing"
}
Learn More in Detail on Optional Class In Java 8
New Date API in Java 8
Java 8 introduced a new java.time package for better date/time handling. Java 8 Download 64 bit
import java.time.*;
public class DateTimeExample {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
LocalTime time = LocalTime.now();
System.out.println("Today's Date: " + today);
System.out.println("Current Time: " + time);
}
}
Method Reference in Java 8
Method reference is a shorthand syntax for calling a method using the :: operator. Method references allow referring to existing methods by their names.
ClassName::methodName
import java.util.*;
public class MethodReferenceExample {
public static void main(String[] args) {
List list = Arrays.asList("Apple", "Banana", "Cherry");
// Using method reference instead of lambda
list.forEach(System.out::println);
}
}
Collectors Class (Grouping & Partitioning) in Java 8
The Collectors class in Java 8 is part of the java.util.stream package. The Collectors class provides methods for grouping, partitioning, and summarizing data. Learn More about Collectors class methods and usage
//Collectors class in Java
import java.util.*;
import java.util.stream.Collectors;
class Student {
String name;
int grade;
public Student(String name, int grade) {
this.name = name;
this.grade = grade;
}
public int getGrade() {
return grade;
}
}
public class CollectorsExample {
public static void main(String[] args) {
List students = Arrays.asList(
new Student("Alice", 1),
new Student("Bob", 2),
new Student("Charlie", 1)
);
// Group students by grade
Map> groupByGrade = students.stream()
.collect(Collectors.groupingBy(Student::getGrade));
System.out.println(groupByGrade);
}
}
Output:
{1=[Alice (1), Charlie (1)], 2=[Bob (2)]}
Parallel Streams in Java 8
Parallel Streams in Java 8 refer to a feature of the Stream API that allows data processing to be carried out concurrently across multiple threads.
A parallel stream automatically splits the source data into smaller segments, processes them simultaneously on different threads, and combines the results. This can lead to significant performance improvements. Learn More About Parallel Streams
// parallel stream in java
import java.util.*;
import java.util.stream.*;
public class ParallelStreamExample {
public static void main(String[] args) {
List numbers = IntStream.range(1, 10).boxed().collect(Collectors.toList());
// Using parallel stream for faster processing
numbers.parallelStream().forEach(num -> System.out.println(num + " " + Thread.currentThread().getName()));
}
}
Output:
1 ForkJoinPool-1-worker-1
2 ForkJoinPool-1-worker-2
3 ForkJoinPool-1-worker-3
4 ForkJoinPool-1-worker-4
5 ForkJoinPool-1-worker-5
6 ForkJoinPool-1-worker-6
7 ForkJoinPool-1-worker-7
8 ForkJoinPool-1-worker-8
9 ForkJoinPool-1-worker-9
StringJoiner Class in Java 8
The StringJoiner class in Java 8, introduced in the java.util package concatenates strings with a specified delimiter, optional prefix, and suffix.
It provides a convenient and efficient way to join multiple strings or objects into a single string, especially when working with collections or arrays.
Before Java 8, joining strings was typically done using a loop or StringBuilder, but the StringJoiner class simplifies this process, making the code more readable and concise.
import java.util.StringJoiner;
public class StringJoinerExample {
public static void main(String[] args) {
StringJoiner joiner = new StringJoiner(", ", "[", "]");
joiner.add("Apple").add("Banana").add("Cherry");
System.out.println(joiner.toString());
}
}
Output: [Apple, Banana, Cherry]
Summary of Java 8 Features
Java 8 introduced a major transformation to the language, providing developers with a set of powerful features that simplify programming and improve performance. These features emphasize functional programming principles, concurrency, and code readability, making Java a more modern and efficient language.
Key Java 8 features, such as Lambda expressions, Stream API, and Optional class, have significantly enhanced the way developers write code. Lambda expressions allow for more concise and readable code by enabling the use of functional interfaces, reducing boilerplate code. The Stream API provides a powerful way to process collections in a functional style, enabling both sequential and parallel operations with ease, which can improve performance on large datasets. The Optional class is an essential addition that helps to handle null values more gracefully, reducing the risk of NullPointerException
and improving code safety.
Moreover, features like default methods in interfaces, new date and time API, java.util.function package, and method references offer increased flexibility, better handling of date-time operations, and a more comprehensive set of functional interfaces.
By embracing Java 8’s features, developers can write cleaner, more maintainable, and more efficient code. While there are challenges, such as understanding the intricacies of parallel streams or adopting a functional approach, the overall impact of Java 8 has been transformative, making Java a more powerful and modern programming language for today’s needs.
In conclusion, Java 8’s features significantly enhance both productivity and performance, allowing developers to create scalable, more readable, and maintainable applications.