Java Version
Java 8
1. Lambda Expression
-
Used to create anonymous functions, primarily designed to simplify the use of functional interfaces (interfaces with a single abstract method)
-
It has two basic syntax forms:
(parameters) -> expression
: Used when the Lambda body consists of a single expression. The result of the expression serves as the return value.(parameters) -> { statements; }
: Used when the Lambda body contains multiple statements. Curly braces are required to enclose the statements, and if there is a return value, a return statement must be used.
-
The traditional anonymous inner class implementation tends to be verbose, whereas Lambda expressions can achieve the same functionality with a more concise syntax. For example, implementing the
Runnable
interface can be done in both ways:- Using Anonymous Inner Class
public class AnonymousClassExample {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Running using anonymous class");
}
});
t1.start();
}
} - Using Lambda Expression
public class LambdaExample {
public static void main(String[] args) {
Thread t1 = new Thread(() -> System.out.println("Running using lambda expression"));
t1.start();
}
}
- Using Anonymous Inner Class
-
Additionally, Lambda expressions can more clearly express the intent of the code, especially when handling collection operations such as filtering, mapping, etc. For example, filtering a list to get all even numbers:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class ReadabilityExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// Using Lambda expression with Stream API to filter even numbers
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNumbers); // Output: [2, 4, 6]
}
} -
Furthermore, Lambda expressions enable Java to support the functional programming paradigm, allowing functions to be passed as parameters. This enables the creation of more flexible and reusable code. For example, defining a generic calculation function:
interface Calculator {
int calculate(int a, int b);
}
public class FunctionalProgrammingExample {
public static int operate(int a, int b, Calculator calculator) {
return calculator.calculate(a, b);
}
public static void main(String[] args) {
// Passing addition function using Lambda expression
int sum = operate(3, 5, (x, y) -> x + y);
System.out.println("Sum: " + sum); // Output: Sum: 8
// Passing multiplication function using Lambda expression
int product = operate(3, 5, (x, y) -> x * y);
System.out.println("Product: " + product); // Output: Product: 15
}
} -
Drawback:
- Increase debugging difficulty because Lambda expressions are anonymous, making it challenging to pinpoint which specific Lambda expression is causing an issue
2. Stream API
-
For operations on collections such as fltering, mapping, and sorting.
-
Example 1: Filtering and collecting elements
- Without Stream API:
List<String> originalList = Arrays.asList("apple", "fig", "banana", "kiwi");
List<String> filteredList = new ArrayList<>();
for (String item : originalList) {
if (item.length() > 3) {
filteredList.add(item);
}
} - With Stream API:
List<String> originalList = Arrays.asList("apple", "fig", "banana", "kiwi");
List<String> filteredList = originalList.stream()
.filter(s -> s.length() > 3)
.collect(Collectors.toList());
- Without Stream API:
-
Example 2: Concatenating Strings with
reduce
List<String> words = Arrays.asList("apple", "banana", "kiwi");
String result = words.stream()
.reduce((a, b) -> a + "," + b)
.orElse("");
System.out.println(result); // Output: apple,banana,kiwi- The first argument
a
is the accumulated result, andb
is the next element in the stream.
- The first argument
-
Example 3: Given list of lists of integers, flatten it into a single list of integers
List<List<Integer>> nestedList = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4),
Arrays.asList(5, 6)
);
List<Integer> flatList = nestedList.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println(flatList); // Output: [1, 2, 3, 4, 5, 6]
3. Optional Class
4. Functional Interfaces
5. CompletableFuture
- Introduced in Java 8.
- Prior to Java 8, asynchronous operations were typically implemented using
Future
or Guava'sListenableFuture
.
ExecutorService executor = Executors.newFixedThreadPool(5);
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
System.out.println("执行step 1");
return "step1 result";
}, executor);
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
System.out.println("执行step 2");
return "step2 result";
});
cf1.thenCombine(cf2, (result1, result2) -> {
System.out.println(result1 + " , " + result2);
System.out.println("执行step 3");
return "step3 result";
}).thenAccept(result3 -> System.out.println(result3));
CompletableFuture
implements two interfaces:Future
andCompletionStage
.Future
represents the result of an asynchronous computation.CompletionStage
represents a stage in the asynchronous execution process, which may be triggered by anotherCompletionStage
. As the current stage completes, it may also trigger the execution of a series of otherCompletionStage
instances.
- Allows for diverse orchestration and combination of these stages based on actual business needs.
- We can use its provided functional programming methods, such as
thenApply
andthenCompose
, to compose and orchestrate these stages.
Java 17
1. Sealed Class
- To restrict which classes or interfaces can extend or implement a given class or interface to ensure more predictable and secure design.
public sealed class Shape permits Circle, Rectangle, Triangle {
public abstract double area();
}
- Only
Circle
,Rectangle
, andTriangle
can extendShape
- Each subclass must be marked as
final
,sealed
, ornon-sealed
2. Pattern Matching for instanceof
-
Traditional
instanceof
- Before pattern matching, using
instanceof
typically required explicit type checking followed by a cast.
public void processShape(Object obj) {
if (obj instanceof Circle) {
Circle circle = (Circle) obj; // Explicit cast
System.out.println("Circle with radius: " + circle.getRadius());
} else if (obj instanceof Rectangle) {
Rectangle rectangle = (Rectangle) obj; // Explicit cast
System.out.println("Rectangle with area: " + rectangle.getArea());
}
} - Before pattern matching, using
-
Pattern Matching with
instanceof
- Allow you to declare a variable directly in the
instanceof
check, eliminating the need for an explicit cast. - The variable is automatically typed and scoped to the block where the condition is true.
public void processShape(Object obj) {
if (obj instanceof Circle circle) {
System.out.println("Circle with radius: " + circle.getRadius());
} else if (obj instanceof Rectangle rectangle) {
System.out.println("Rectangle with area: " + rectangle.getArea());
}
} - Allow you to declare a variable directly in the
3. Records
- Address the boilerplate issues of traditional immutable classes.
- Immutable Class: Designed to represent data that does not change after creation
- Class declares with
final
to prevent subclassing. - All fields are
final
to ensure immutability after construction.
- Class declares with
- Before
Records
Code
Traditional way to create immutable classpublic class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point point = (Point) o;
return x == point.x && y == point.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
@Override
public String toString() {
return "Point{x=" + x + ", y=" + y + "}";
}
} - With
Records
public record Point(int x, int y) { }
- Immutable Class: Designed to represent data that does not change after creation
- Records are inherently immutable; their field are
final
and cannot be changed after construction. - Auto-Generated Method: Constructor, getter,
equals()
,hashCode()
, andtoString()
methods; no setter, and the only way to create a new state is to instantiate a new immutable object. - Records support compact constructors for parameter validation and static methods for additional functionality.
Java 21
1. Pattern Matching for Switch Statement
- Pattern Matching for Switch Statement
case SavingsAccount sa -> result = sa.getSavings();
Before Java 21public class SwitchBeforeJava21 {
static String processAccount(Object account) {
String result;
if (account instanceof SavingsAccount) {
SavingsAccount sa = (SavingsAccount) account;
result = "Savings Balance: " + sa.getSavings();
} else if (account instanceof CheckingAccount) {
CheckingAccount ca = (CheckingAccount) account;
result = "Checking Balance: " + ca.getChecking();
} else if (account == null) {
result = "Account is null";
} else {
result = "Unknown account type";
}
return result;
}
public static void main(String[] args) {
SavingsAccount savings = new SavingsAccount(1000);
CheckingAccount checking = new CheckingAccount(500);
System.out.println(processAccount(savings)); // Output: Savings Balance: 1000
System.out.println(processAccount(checking)); // Output: Checking Balance: 500
System.out.println(processAccount(null)); // Output: Account is null
}
}In Java 21public class SwitchInJava21 {
static String processAccount(Object account) {
return switch (account) {
case SavingsAccount sa -> "Savings Balance: " + sa.getSavings();
case CheckingAccount ca -> "Checking Balance: " + ca.getChecking();
case null -> "Account is null";
default -> "Unknown account type";
};
}
public static void main(String[] args) {
SavingsAccount savings = new SavingsAccount(1000);
CheckingAccount checking = new CheckingAccount(500);
System.out.println(processAccount(savings)); // Output: Savings Balance: 1000
System.out.println(processAccount(checking)); // Output: Checking Balance: 500
System.out.println(processAccount(null)); // Output: Account is null
}
}
2. String Template
- Previously,
"hello " + name + ", welcome to the geeksforgeeks!"
, In Java 21, this can be simplified tohello {name}, welcome to the geeksforgeeks!
.
3. Array Pattern
- For instance,
if (arr instanceof int[] {1, 2, 3})
Summary of Java 8 Features
Feature Name | Description | Example or Explanation |
---|---|---|
Lambda Expressions | Simplify anonymous inner classes, support functional programming | (a, b) -> a + b replaces anonymous class implementing an interface |
Functional Interfaces | Interfaces with a single abstract method, marked with @FunctionalInterface annotation | Runnable , Comparator , or custom @FunctionalInterface interface MyFunc { void run(); } |
Stream API | Provides chained operations for collection data, supports parallel processing | list.stream().filter(x -> x > 0).collect(Collectors.toList()) |
Optional Class | Encapsulates potentially null objects, reduces null pointer exceptions | Optional.ofNullable(value).orElse("default") |
Method References | Simplify Lambda expressions by directly referencing existing methods | System.out::println is equivalent to x -> System.out.println(x) |
Default and Static Methods in Interfaces | Interfaces can define default implementations and static methods, enhancing extensibility | interface A { default void print() { System.out.println("Default method"); } } |
Parallel Array Sorting | Uses multithreading to accelerate array sorting | Arrays.parallelSort(array) |
Repeated Annotations | Allows the same annotation to be used multiple times at the same location | @Repeatable annotation used with container annotations |
Type Annotations | Annotations can be applied to more locations (e.g., generics, exceptions) | List<@NonNull String> list |
CompletableFuture | Enhances asynchronous programming with chained calls and composite operations | CompletableFuture.supplyAsync(() -> "result").thenAccept(System.out::println) |
Summary of Java 17 Features
Feature Name | Description | Example or Explanation |
---|---|---|
Sealed Classes | Restrict which classes can extend or implement a class or interface, enhancing control over inheritance | sealed interface Shape permits Circle, Rectangle {} where only Circle and Rectangle can implement Shape |
Pattern Matching for instanceof | Simplifies type checking and casting in one step, reducing boilerplate | if (obj instanceof String s) { System.out.println(s.toUpperCase()); } casts obj to String s automatically |
Records | Immutable data classes with concise syntax for data carriers, automatically providing equals , hashCode , and toString | record Point(int x, int y) {} creates a class with x , y fields, getters, and standard methods |
Summary of Java 21 Features
Feature Name | Description | Example or Explanation |
---|---|---|
Pattern Matching for switch | Enhances switch with pattern matching, allowing type patterns and guards for concise logic | switch (obj) { case String s when s.length() > 5 -> s.toUpperCase(); case String s -> s; default -> "Unknown"; } |
String Templates (Preview) | Simplifies string construction with embedded expressions, improving readability and safety | String name = "Alice"; String result = STR."Hello, \{name}!"; produces "Hello, Alice!" |