Skip to main content

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:

    1. (parameters) -> expression: Used when the Lambda body consists of a single expression. The result of the expression serves as the return value.
    2. (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:

    1. 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();
      }
      }
    2. 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();
      }
      }
  • 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:

    1. Increase debugging difficulty because Lambda expressions are anonymous, making it challenging to pinpoint which specific Lambda expression is causing an issue

2. Stream API

  1. For operations on collections such as fltering, mapping, and sorting.

  2. 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());
  3. 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, and b is the next element in the stream.
  4. 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's ListenableFuture.
    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 and CompletionStage.
    • Future represents the result of an asynchronous computation.
    • CompletionStage represents a stage in the asynchronous execution process, which may be triggered by another CompletionStage. As the current stage completes, it may also trigger the execution of a series of other CompletionStage 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 and thenCompose, 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();
}
  1. Only Circle, Rectangle, and Triangle can extend Shape
  2. Each subclass must be marked as final, sealed, or non-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());
    }
    }
  • 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());
    }
    }

3. Records

  1. Address the boilerplate issues of traditional immutable classes.
    • Immutable Class: Designed to represent data that does not change after creation
      1. Class declares with final to prevent subclassing.
      2. All fields are final to ensure immutability after construction.
    • Before Records
      Code
      Traditional way to create immutable class
          public 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) { }
  2. Records are inherently immutable; their field are final and cannot be changed after construction.
  3. Auto-Generated Method: Constructor, getter, equals(), hashCode(), and toString() methods; no setter, and the only way to create a new state is to instantiate a new immutable object.
  4. Records support compact constructors for parameter validation and static methods for additional functionality.

Java 21

1. Pattern Matching for Switch Statement

  1. Pattern Matching for Switch Statement
    1. case SavingsAccount sa -> result = sa.getSavings();
      Before Java 21
          public 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 21
          public 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 to hello {name}, welcome to the geeksforgeeks!.

3. Array Pattern

  • For instance, if (arr instanceof int[] {1, 2, 3})

Summary of Java 8 Features

Feature NameDescriptionExample or Explanation
Lambda ExpressionsSimplify anonymous inner classes, support functional programming(a, b) -> a + b replaces anonymous class implementing an interface
Functional InterfacesInterfaces with a single abstract method, marked with @FunctionalInterface annotationRunnable, Comparator, or custom @FunctionalInterface interface MyFunc { void run(); }
Stream APIProvides chained operations for collection data, supports parallel processinglist.stream().filter(x -> x > 0).collect(Collectors.toList())
Optional ClassEncapsulates potentially null objects, reduces null pointer exceptionsOptional.ofNullable(value).orElse("default")
Method ReferencesSimplify Lambda expressions by directly referencing existing methodsSystem.out::println is equivalent to x -> System.out.println(x)
Default and Static Methods in InterfacesInterfaces can define default implementations and static methods, enhancing extensibilityinterface A { default void print() { System.out.println("Default method"); } }
Parallel Array SortingUses multithreading to accelerate array sortingArrays.parallelSort(array)
Repeated AnnotationsAllows the same annotation to be used multiple times at the same location@Repeatable annotation used with container annotations
Type AnnotationsAnnotations can be applied to more locations (e.g., generics, exceptions)List<@NonNull String> list
CompletableFutureEnhances asynchronous programming with chained calls and composite operationsCompletableFuture.supplyAsync(() -> "result").thenAccept(System.out::println)

Summary of Java 17 Features

Feature NameDescriptionExample or Explanation
Sealed ClassesRestrict which classes can extend or implement a class or interface, enhancing control over inheritancesealed interface Shape permits Circle, Rectangle {} where only Circle and Rectangle can implement Shape
Pattern Matching for instanceofSimplifies type checking and casting in one step, reducing boilerplateif (obj instanceof String s) { System.out.println(s.toUpperCase()); } casts obj to String s automatically
RecordsImmutable data classes with concise syntax for data carriers, automatically providing equals, hashCode, and toStringrecord Point(int x, int y) {} creates a class with x, y fields, getters, and standard methods

Summary of Java 21 Features

Feature NameDescriptionExample or Explanation
Pattern Matching for switchEnhances switch with pattern matching, allowing type patterns and guards for concise logicswitch (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 safetyString name = "Alice"; String result = STR."Hello, \{name}!"; produces "Hello, Alice!"