Examining new Java 17 features

1 October 2022

Examining new Java 17 features

Java has had exciting development over the last couple of years, while yes, it can be argued that it is still leagues behind newer languages like Kotlin in terms of conciseness, it is still to be applauded how the developers managed to evolve Java in this new direction while making sure it retains it’s backwards compatibility. Lets have a look at some of the new features that were implemented since Java 8


Java has had exciting development over the last couple of years, while yes, it can be argued that it is still leagues behind newer languages like Kotlin in terms of conciseness, it is still to be applauded how the developers managed to evolve Java in this new direction while making sure it retains it’s backwards compatibility. Lets have a look at some of the new features that were implemented since Java 8.

Java Record

It is a common complaint that Java “is too verbose” or has “too much ceremony”. Some of the worst offenders are classes that are nothing more than immutable data carriers for a bunch of values. Properly writing such a data-carrier can involve a lot of low-value, repetitive code: constructors, accessors, equals, hashcode, toString, etc. Let’s have a look at one such offender:

public class Person {
    private final String name;
    private final String email;

    public Person(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(name, person.name) && Objects.equals(email, person.email);
    }

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

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

All of this can now be reduced down to a record:

public record Person(String name, String email) {
}

Neat! A record can be defined as a transparent, shallowly immutable data aggregate that basically states “I am simply a carrier for my data”.

So this means we can finally get rid of Lombok, right? Not so fast. While it’s true that records are syntactically convenient, that’s not really why they were brought to the Java language. Brian Goetz, a java language Architect, explains it best in the following post on Stack Overflow:

Lombok, and the record feature of the Java language, are different tools for different things. There is some superficial overlap, but don’t let that distract you.

Lombok is largely about syntactic convenience; it is a macro-processor pre-loaded with some known useful patterns of code. It doesn’t confer any semantics; it just automates the patterns, according to some knobs you set in the code with annotations. Lombok is purely about the convenience of implementing data-carrying classes.

Records are a semantic feature; they are nominal tuples. By making a semantic declaration that Point is a tuple of (int x, int y), the compiler can derive its representation, as well as construction, declaration, equality, hashing, and string representation protocols, from this state description. Because they carry semantics, readers and frameworks can also reason with higher confidence about the API of records. (This may also be syntactically convenient; if so, that’s great.)

Brian Goetz

Local-Variable Type inference with var

This one is pretty self explanatory, but really useful for shortening your code.

var list=new ArrayList<String>();  // infers ArrayList<String>
var stream=list.stream();          // infers Stream<String>

From what I have seen in industry code, this is slowly but surely getting more common. If you are about to introduce this in your local shop and are already frightened of the inevitable style guide discussion: The LVTI style guide has you covered.

Pattern matching & switch expressions

Another feature that greatly helps the readability and just feels good while writing it is the new Switch pattern that can be applied as follows

static String formatValue(Object o){
        return switch(o){
        case Double d->String.format("Double value is %f",d);
        case Integer i->String.format("Integer value is %d",i);
        case Long l->String.format("Long value is %d",l);
        case String s->String.format("String value is %s",s);
        default ->o.toString();
        };
}

And if you are a seasoned java developer, you are probably familiar with the following pattern, used to distinguish between instances of a type

if (animal instanceof Dog){
    Dog dog = (Dog) animal;
    doSomething(dog);
}

This was also simplified and makes the code a lot easier on the eyes:

if (animal instanceof Dog dog){
    doSomething(dog);
}

Conclusion

These were some of the Java language features that were introduced in the last versions of the Java language. Although there are additional features like Sealed Classes and Text blocks, the above features were the ones that I had most contact with in daily life. Especially records are a great addition to the Java language and similar to Kotlins data classes.

However, there is another big change ooming on the horizon, but this is a topic for another blog post.