The most complete summary of JDK new features in history, covering jdk8 to jdk15!

Posted by GundamSV7 on Thu, 10 Feb 2022 10:31:44 +0100

preface

In this article, I will describe the most important and developer friendly features of Java since version 8. Why do you have such an idea? On the Web, you can find many articles containing a list of new features for each java version. However, due to the lack of articles, it is not possible to briefly outline the most important changes since version 8. Version 8, but why? Surprisingly, it is still the most commonly used version of Java. Even though we have reached the eve of the Java 16 release. As you can see, more than 46% of respondents still use Java 8 in production. In contrast, less than 10% of respondents used Java 12 or later.

 

Next, we will introduce the new features provided by the new JDK to us from JDK8 to JDK15!

JDK8

  1. Lambda expression

The most direct effect is to reduce the code. The code is directly reduced by 50% +, which is very concise

 //Using java anonymous inner classes
  Comparator<Integer> cpt = new Comparator<Integer>() {
      @Override
      public int compare(Integer o1, Integer o2) {
          return Integer.compare(o1,o2);
      }
  };

  TreeSet<Integer> set = new TreeSet<>(cpt);

  System.out.println("=========================");

  //Using JDK8 lambda expressions
  Comparator<Integer> cpt2 = (x,y) -> Integer.compare(x,y);
  TreeSet<Integer> set2 = new TreeSet<>(cpt2); 

// Filter products with nike in Java 7
public  List<Product> filterProductByColor(List<Product> list){
    List<Product> prods = new ArrayList<>();
    for (Product product : list){
        if ("nike".equals(product.getName())){
            prods.add(product);
        }
    }
    return prods;
 }

// Using lambda
public  List<Product> filterProductByPrice(List<Product> list){
  return list.stream().filter(p->"nike".equals(p.getName())).collect(Collectors.toList());  
 } 

  1. Functional interface

Located in Java util. Under the function package, the most commonly used are described below

  • Predicate

Receive a value and return boolean

 Predicate p = t->true; 

  • Supplier

No accept parameter returns a value

Supplier<T> s = () -> new T(); 

  • Consumer

Accept a parameter with no return value

Consumer<String> c = c -> System.out.println(s); 

  • Function<T,R>

Accept parameter T and return parameter R

Function<Long,String> f = c -> String.valueof(c); 

  • There are other bifunctions, biconsumers, DoubleSupplier, etc. you are interested in reading the source code yourself
  1. Method reference
  • Static reference: Format: Class::static_method
List<String> list = Arrays.asList("a","b","c");
list.forEach(str -> System.out.print(str));
list.forEach(System.out::print); 

  • Constructor calls constructor method reference format: Class::new, call default constructor
List<String> list = Arrays.asList("a","b","c");
List<Test> list.stream().map(Test::new).collect(Collectors.toList());

public class Test{
    private final String desc;

    public Test(String desc){
      this.desc=desc;
    }
} 

  • Method call format: instance::method
List<String> list = Arrays.asList("a","b","c");
Test test = new Test();
List<String> list.stream().map(test::toAdd).collect(Collectors.toList());

public class Test{
    private final String desc;

    public Test(String desc){
      this.desc=desc;
    }

    public String toAdd(String desc){
        return desc+"add";
    }
} 

  1. Stream API
// Use jdk1 The Stream API in 8 performs collection operations
@Test
public void test(){

    // Circular filter element                                       
    proList.stream()
           .fliter((p) -> "gules".equals(p.getColor()))
           .forEach(System.out::println);

    // The map processes the elements and then iterates through them
    proList.stream()
           .map(Product::getName)
           .forEach(System.out::println);

   // map handles the transformation of elements into a List
   proList.stream()
           .map(Product::getName)
           .collect(Collectors.toList());
} 

  1. Default and static methods in the interface
public interface ProtocolAdaptor {

    ProtocolAdaptor INSTANCE = DynamicLoader.findFirst(ProtocolAdaptor.class).orElse(null);

    default ProtocolAdaptor proxy() {
        return (ProtocolAdaptor) Proxy.newProxyInstance(ProtocolAdaptor.class.getClassLoader(),
                new Class[]{ProtocolAdaptor.class},
                (proxy, method, args) -> intercept(method, args));
    }
} 

  1. Optional

Used to handle object null pointer exceptions:

 public String getDesc(Test test){
          return Optional.ofNullable(test)
                  .map(Test::getDesc).else("");
      } 

JDK9

  • Collection plant method

With a new feature of Java 9, the collection factory method, you can easily create immutable collections with predefined data. You only need to use the of method on a specific collection type.

List<String> fruits = List.of("apple", "banana", "orange");
Map<Integer, String> numbers = Map.of(1, "one", 2,"two", 3, "three"); 

Before Java 9, you could use Collections, but this is definitely a more complex approach.

public List<String> fruits() {
 List<String> fruitsTmp = new ArrayList<>();
 fruitsTmp.add("apple");
 fruitsTmp.add("banana");
 fruitsTmp.add("orange");
 return Collections.unmodifiableList(fruitsTmp);
}

public Map<Integer, String> numbers() {
 Map<Integer, String> numbersTmp = new HashMap<>();
 numbersTmp.put(1, "one");
 numbersTmp.put(2, "two");
 numbersTmp.put(3, "three");
 return Collections.unmodifiableMap(numbersTmp);
} 

Similarly, you can use arrays by creating only from the ArrayList object table asList(...) method.

public List<String> fruitsFromArray() {
 String[] fruitsArray = {"apple", "banana", "orange"};
 return Arrays.asList(fruitsArray);
} 

  • Private method in interface

Starting with Java 8, you can use public default methods inside the interface. However, starting with Java 9 only, you will be able to take full advantage of this functionality due to the private methods in the interface.

ublic interface ExampleInterface {

    private void printMsg(String methodName) {
        System.out.println("Calling interface");
        System.out.println("Interface method: " + methodName);
    }

    default void method1() {
        printMsg("method1");
    }

    default void method2() {
        printMsg("method2");
    }
} 

JDK10

Starting with Java 9 and Java 10, there are several useful methods for Optional. The two most interesting ones are orelsethlow and ifpresentoelse. If there is no value, the orElseThrow method is used to throw NoSuchElementException. Otherwise, it returns a value.

public Person getPersonById(Long id) {
 Optional<Person> personOpt = repository.findById(id);
 return personOpt.orElseThrow();
} 

Therefore, you can avoid using if statements with parameters with isPresentmethod.

public Person getPersonByIdOldWay(Long id) {
 Optional<Person> personOpt = repository.findById(id);
 if (personOpt.isPresent())
  return personOpt.get();
 else
  throw new NoSuchElementException();
} 

The second interesting method is ifpresentoelse. If a value exists, it will use it to perform the given operation. Otherwise, it will perform the given null based operation.

public void printPersonById(Long id) {
 Optional<Person> personOpt = repository.findById(id);
 personOpt.ifPresentOrElse(
   System.out::println,
   () -> System.out.println("Person not found")
 );
} 

In Java 8, we can use if else directly with isPresent method.

public void printPersonByIdOldWay(Long id) {
 Optional<Person> personOpt = repository.findById(id);
 if (personOpt.isPresent())
  System.out.println(personOpt.get());
 else
  System.out.println("Person not found");
} 

JDK 10 && JDK 11

Starting with Java 10, you can declare a local variable without its type. You only need to define the var keyword, not the type. Starting with Java 11, you can also use it with lambda expressions, as shown below.

public String sumOfString() {
 BiFunction<String, String, String> func = (var x, var y) -> x + y;
 return func.apply("abc", "efg");
} 

JDK 12

Using Switch expressions, you can define multiple case labels and use arrows to return values. This feature is available from JDK 12. It makes Switch expressions really easier to access.

 public String newMultiSwitch(int day) {
        return switch (day) {
            case 1, 2, 3, 4, 5 -> "workday";
            case 6, 7 -> "weekend";
            default -> "invalid";
        };
    } 

For Java below 12, the same example is much more complex.

public String oldMultiSwitch(int day) {
        switch (day) {
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
                return "workday";
            case 6:
            case 7:
                return "weekend";
            default:
                return "invalid";
        }
    } 

JDK 13

A text block is a multiline string text that avoids the use of escape sequences and automatically formats the string in a predictable manner. It also allows developers to control the format of strings. Starting with Java 13, text blocks can be used as preview functions. They begin with three double quotes (""). Let's see how we can easily create and format JSON messages.

 public String getNewPrettyPrintJson() {
        return """
               {
                    "firstName": "Piotr",
                    "lastName": "Mińkowski"
               }
               """;
    } 

Creating the same JSON string before Java 13 is much more complex.

 public String getOldPrettyPrintJson() {
        return "{\n" +
               "     \"firstName\": \"Piotr\",\n" +
               "     \"lastName\": \"Mińkowski\"\n" +
               "}";
    } 

JDK14

With Records, you can define immutable pure data classes (getter s only). It will automatically create toString, equals and hashCode methods. In fact, you only need to define the fields shown below.

public record Person(String name, int age) {} 

Classes with similar functions, such as record, contain fields, constructors, getter s, and implement toString, equals, and hashCode methods.

public class PersonOld {

    private final String name;
    private final int age;

    public PersonOld(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PersonOld personOld = (PersonOld) o;
        return age == personOld.age && name.equals(personOld.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "PersonOld{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

} 

JDK15

Using the sealed class function, you can limit the use of superclasses. Using the new keyword, sealed, you can define which other classes or interfaces can extend or implement the current class.

public abstract sealed class Pet permits Cat, Dog {} 

A subclass of modifiers must be allowed. If you do not want to allow any other extensions, you need to use the final keyword.

public final class Cat extends Pet {} 

On the other hand, you can open the extension class. In this case, the non sealed modifier should be used.

public non-sealed class Dog extends Pet {} 

Of course, the following visible statements are not allowed.

public final class Tiger extends Pet {} 

Topics: Java JDK Programmer architecture