Tuesday, June 15, 2021

Java 16 Features


Java is nearing its next Long Term Support (LTS) Release which is Java 17. Thus far it has released Java 16 General Availability (GA) Release which can be used in Production. There are some new features available in Java 16 which we are going to look at right now.

Record 

Record is simplified way to declare a class which is used only to represent data. This type of classes are defined as Data Transfer Objects (DTO) or Entities in ORM frameworks to represent Tables. The current way to declare this type of classes is to create a public class with properties and all argument constructor and define all getters/setters along with equals, hashCode and toString. This requires a lot of additional code which can be reduced using code generation libraries and plugins like Lombok yet it is still an hassle. 

Java 16 introduces record type to ease creation of such classes. 

 import java.util.*;  
 public class RecordTest {  
      public static void main(String... args) {  
           Student s1 = new Student(1, "Shazin", 1);  
           Student s2 = new Student(2, "Shahim", 2);  
           System.out.println("Student 1 : "+s1);  
           System.out.println("Student 2 : "+s2);  
           System.out.println("s1 == s2 : "+(s1 == s2));  
           System.out.println("s1.equals(s1) : "+(s1.equals(s1)));  
           System.out.println("s1.equals(s2) : "+(s1.equals(s2)));  
           record GraduateStudent(Student student, List<String> qualifications) {};  
           GraduateStudent gs1 = new GraduateStudent(s1, Arrays.asList("A/S", "BSc"));  
           System.out.println("GraduateStudent 1 : " + gs1);  
      }  
      private static record Student(Integer id, String name, Integer grade) {  
           public Student {  // All args constructor 
                if (grade < 0 || grade > 13) {  
                     throw new IllegalArgumentException("Grade must be between 1 and 13");  
                }  
           }  
      }  
 }  
The above example shows the use a Record in Java 16. The record Student is defined below as a static variable with attributes id, name and grade. The getters/setters with equals, hashCode and toString will be generated by default for this.

Once instantiated the a record object acts as a regular object. The only difference is that generated accessors/modifiers don't follow Java Beans convention (getter for name is name() not getName()) thus not a drop in replacement for Java Beans. Due to this reason record types are not yet supported by JSON Serializers and ORM frameworks but its a work in progress.

New methods in Stream interface

There are several new methods introduced in Stream interface which can be used to increase performance and reduce boilerplate code. One is the Stream.toList() method.

           List<Integer> evenNos = nos.stream().filter(i -> i % 2 == 0).toList(); //.collect(Collectors.toList())  
           System.out.println("Even numbers : "+evenNos);  
This method reduces the need to call collect method when there is need to aggregate a Stream values to a List.

Also there is a new method introduced which is Stream.mapMulti() which is a more imperative implementation of Stream.flatMap(). The practical use of this is a bit complex but this article by Nicolai Parlog explains it well. I suggest you read it.

Pattern Matching

Pattern Matching is a very useful addition to Java 16 which eliminates the need of casting after the use of instanceof operator.

 import java.util.*;  
 public class PatternMatchingTest {  
      public static void main(String... args) {  
           String name = "Shazin";  
           List<Integer> nos = Arrays.asList(1, 2, 3, 4, 5);  
           Map map = Collections.singletonMap("Key", "Value");  
           Long bigInt = 1l;  
           System.out.println("Name Length : " + length(name));  
           System.out.println("Nos Length : " + length(nos));  
           System.out.println("Map Length : " + length(map));  
           System.out.println("bigInt Length : " + length(bigInt));  
      }  
      private static int length(Object o) {  
           if (o instanceof String s) { // s variable of type String  
                return s.length();  
           } else if (o instanceof Collection c) { // c variable of type Collection  
                return c.size();  
           } else if (o instanceof Map m) { // m variable of type Map  
                return m.size();  
           } else { // o variable of type Object  
                return 0;  
           }  
      }  
 }  
This feature allows to use a local scoped variable name right after the use of instanceof operator which matches the type used and can be used without casting. This eliminates a lot of boilerplate code.