- Lambda Expressions
- Streams
- Functional Interfaces
- Method References
- Default Methods
- Optional
- New Date and Time API (java.time)
- Collection Enhancements
1. Lambda Expressions
What? Lambda expressions in Java allow you to define and use small, anonymous functions. They simplify the way you write code by providing a more concise syntax for defining methods, particularly in functional interfaces.
Why?
Lambda expressions make your code shorter and more readable by reducing boilerplate code. They are particularly useful when working with functional interfaces like Runnable
and Callable
.
Pre-Java 8 Example:
Before Java 8, you'd typically use anonymous inner classes for similar functionality. For instance, a Runnable
could be defined as follows:
Runnable runnable = new Runnable() {
public void run() {
System.out.println("Hello from Runnable!");
}
};
Java 8 Example: With lambda expressions, the same can be achieved with a much cleaner syntax:
Runnable runnable = () -> System.out.println("Hello from Runnable!");
2. Streams
What? Streams in Java provide a high-level abstraction for processing sequences of data. They allow you to perform operations like filtering, mapping, and reducing in a functional style.
Why? Streams make it easier to work with collections by providing a more declarative and concise way to manipulate data. They also enable parallel processing for improved performance.
Pre-Java 8 Example: Before Java 8, you would often use loops to perform similar operations on collections, which can be less expressive and harder to parallelize.
Java 8 Example: Here's an example using streams to filter and sum a list of integers:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.filter(n -> n % 2 == 0)
.mapToInt(Integer::intValue)
.sum();
3. Functional Interfaces
What?
Functional interfaces are interfaces that have a single abstract method. Java 8 introduced the @FunctionalInterface
annotation to explicitly mark interfaces as functional.
Why? Functional interfaces provide a clear contract for lambda expressions and method references, making it easier to work with functional programming constructs.
Pre-Java 8 Example:
Before Java 8, functional interfaces weren't explicitly defined, but you'd often encounter them in APIs. For instance, the java.util.Predicate
interface is effectively a functional interface.
Java 8 Example:
Here's an example using the Predicate
functional interface to filter a list:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
Predicate<String> startsWithA = (str) -> str.startsWith("A");
names.stream().filter(startsWithA).forEach(System.out::println);
4. Method References
What? Method references in Java allow you to refer to methods or constructors without invoking them. They are often used in conjunction with lambda expressions to make your code cleaner.
Why? Method references simplify the syntax when a lambda expression simply calls an existing method. They make your code more readable and reduce redundancy.
Pre-Java 8 Example: Before Java 8, you'd use full lambda expressions even when calling an existing method.
Java 8 Example: Here's an example using a method reference to convert strings to uppercase:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(System.out::println);
5. Default Methods
What? Default methods in Java allow you to add new methods to interfaces without breaking existing implementations. They provide a mechanism for interface evolution.
Why? Default methods enable backward compatibility when extending interfaces, making it possible to add new functionality to interfaces while still supporting existing implementations.
Pre-Java 8 Example: Before Java 8, interfaces could not contain method implementations, so adding methods to an interface could break existing classes implementing it.
Java 8 Example: Here's an example of a default method in an interface:
interface Greeting {
void greet();
default void farewell() {
System.out.println("Goodbye!");
}
}
6. Optional
What?
The Optional
class in Java is a container that may or may not contain a non-null value. It helps prevent null pointer exceptions and encourages more explicit handling of potentially absent values.
Why?
Optional
encourages developers to explicitly handle the absence of values, reducing the risk of null pointer exceptions and improving code reliability.
Pre-Java 8 Example:
Before Optional
, developers often used null
to indicate the absence of a value, leading to frequent null pointer exceptions.
Java 8 Example:
Here's an example of using Optional
to handle a potentially null value:
Optional<String> name = Optional.ofNullable(getName());
String result = name.orElse("Unknown");
7. New Date and Time API (java.time)
What?
Java 8 introduced the java.time
package, providing a comprehensive set of date and time classes that address the shortcomings of the older java.util.Date
and java.util.Calendar
classes.
Why? The new Date and Time API offers improved clarity, immutability, and better support for handling time zones and date/time arithmetic.
Pre-Java 8 Example: Before Java 8, working with date and time was error-prone and cumbersome using the old classes.
Java 8 Example:
Here's an example of creating a LocalDate
using the java.time
API:
LocalDate today = LocalDate.now();
8. Collection Enhancements in Java 8
What? Java 8 introduced several enhancements to the Collections framework, including new methods and interfaces that simplify common operations and improve the expressiveness of working with collections.
Why? These enhancements make it easier for developers to work with collections, reducing boilerplate code and improving code readability.
Pre-Java 8 Example: Before Java 8, working with collections often required more verbose code and custom iteration.
Java 8 Example:
a. forEach() method in Iterable
The forEach()
method was added to the Iterable
interface, allowing you to iterate through collections using a lambda expression. It simplifies iteration and makes code more concise.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println("Hello, " + name));
b. Stream API Integration
The Stream API introduced in Java 8 is tightly integrated with collections, allowing you to perform powerful operations on collections in a functional style.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Filter and print even numbers
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(System.out::println);
c. New Collectors
Java 8 introduced new collectors in the Collectors
class to simplify collecting elements into collections or aggregating data.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> filteredNames = names.stream()
.filter(name -> name.length() > 4)
.collect(Collectors.toList());
d. Enhancements to Map
The Map
interface received several enhancements, including forEach()
, compute()
, computeIfAbsent()
, and computeIfPresent()
methods, making it easier to work with key-value pairs.
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 95);
scores.put("Bob", 88);
// Compute the new score for Alice
scores.compute("Alice", (key, oldValue) -> oldValue + 5);
Comments
Post a Comment