Java 17 features – new since Java 11

Advertisements

With the new cadence of Java releases we see a new java version every 6 months, however we need to pay attention to the LTS versions. On September 2021 the latest LTS version of Java was released, Java 17. For mature or production ready application always is recommended to use the latest stable a LTS version, some people might be already using Java 11 (previous LTS version) and in the short coming might start thinking about upgrading to Java 17.

That’s why it’s very important to have a summary of the features that we are available when we upgrade from Java 11 to Java 17, and this article will focus on it. Most of those features were introduce on previous version of Java (12,13,14,15 or 16) as incubator or preview features.

New Features

Sealed Classes

The goal of sealed classes or interfaces is to provide a way to allow a class to be widely accessible but not widely extensible. These can be extended or implemented only by those classes and interfaces that are explicitly allowed.

A class or interface is sealed by applying the sealed modifier to its definition, after any extends or implements the permits clause must be added followed by the classes that we allow to be extended.

public abstract sealed class Shape
    permits Circle, Rectangle, Square, WeirdShape { ... }

Then, in the implementations (permitted classes) we must extend the sealed class.

public final class Circle extends Shape { ... }
public final class Rectangule extends Shape { ... }
public final class Square extends Shape { ... }
public final class WeirdShape extends Shape { ... }

Sealed classes can be applied also to interfaces and records, and must follow some rules when extending the sealed class. For more information about it, I recommend to read this article Understanding Sealed classes.

Pattern Matching for Instance Of

Very simple but helpful change!!! We won’t need to cast the object after one instanceof statement in order to use it safely.

//Java 11
if (obj instanceof User) {
  User user = (User) obj;
  System.out.println(user.getName());
}
//Java 17
if (obj instanceof User user) {
  System.out.println(user.getName());
}

Switch Expression

This is the first step towards a more declarative and “null-safe” programming style, allowing a better way to express and model data, recognizing the semantics of the data model through patterns. Definitively improving the readability of our code, avoiding nested if/elseif/else for many circumstances, and providing features that we see in other programming languages.

Let’s take the following code as reference, with some if/else if and instanceof.

Advertisements
static String formatter(Object o) {
    String formatted = "unknown";
    if (o instanceof Integer i) {
        formatted = String.format("int %d", i);
    } else if (o instanceof Long l) {
        formatted = String.format("long %d", l);
    } else if (o instanceof Double d) {
        formatted = String.format("double %f", d);
    } else if (o instanceof String s) {
        if(s.length > 3){
          formatted = String.format("Short String %s", s);
        }  else {
          formatted = String.format("Large String %s", s);
        }
    }
    return formatted;
}

The previous code can be converted with a switch expression

static String formatterPatternSwitch(Object o) {
    return switch (o) {
        case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case Double d  -> String.format("double %f", d);
        case String s && (s.length > 3) -> String.format("Short String %s", s);
        case String s && (s.length > 10 -> String.format("Large String %s", s);
        default        -> o.toString();
    };
}

For more details about the new Switch Expression and other details, I recommend to read this article.

Text blocks

It allows us to create multiline strings easily. The multiline string has to be written inside a pair of triple-double quotes.

String sqlSentence = """
					select * from Item i
                    where i.price > 5000
  					and i.saleDate = '01/01/2020'
        			""";

String textBlockJSON = """
		{
			"name":"Pankaj",
			"website":"JournalDev"
		}""";

Records

This is one of my favorite new features added in Java. It reduces a lot of our code, and I am sure I will be a good option for the famous DTO (Data Transfer Object). A record compacts the syntax for declaring a class that stores pure inmutable data, and has no logic. It’s similar to the Data Class in Kotlin or the Case Class in Scala. Records avoid to add boiler plate code because the constructor, accessors (getters setters),  equals,  hashCode  and  toString methods are generated automatically.

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

If we want to do the same with a regular Java Class, we might need to generate something like the following code.

public class Person {
  private String name;
  private String lastName;
  private int age;
  
  public String getName(){
    return name;
  }
  public void setName(String name){
    this.name = name;
  }
  
  public String getLastName(){/*Code*/}
  public void setLastName(String lastName){/*Code*/}
  public int getAge(){/*Code*/}
  public void setAge(int age){/*Code*/}
  
  public boolean equals(Object obj){/*Code*/}
  public int hashCode(){/*Code*/}
  public String toString(){/*Code*/}
}

For sure, the classic POJOs (Plain Old Java Object) are still valid, but now we have another structure to use in our code according to our requirements. That’s why it is import to keep in mind the following characteristics of a record.

  • The declared fields are private and final.
  • Accesors or autogenerated methods can be redifined.
  • It is possible to create new methods into a record.
  • Records don’t allow to create instance fields in its body.
  • Records allow to create static fields in its body.
  • Records allow multiple constructors.
  • Records can implement Interfaces
  • Records can not extend other classes.
  • Records can not be abstract.

Add Stream.toList Method

The aim is to reduce the boilerplate with some commonly used Stream collectors, such as Collectors.toList and Collectors.toSet

List<String> integersAsString = Arrays.asList("1", "2", "3");
List<Integer> intsEquivalent = integersAsString.stream()
  .map(Integer::parseInt)
  .toList();

Instead of the previous code.

List<Integer> ints = integersAsString.stream()
  .map(Integer::parseInt)
  .collect(Collectors.toList());

Conclusions

As we can see Java as a programming language is evolving constantly adding new features to simplify the developer tasks, reducing boiler plate code, adding new patterns, and making the language up to the new trends and features offered by other languages. For sure, upgrading from Java 11 to Java 17 will make some cool features available.

References

Advertisements

2 comments

  1. Thanks to do this great summary about the latest java changes. I loved the Records option, it’s really useful since it won’t be necessary to include dependency like Lombok to avoid doing repeat code.

Leave a Reply

Your email address will not be published.