New Developer Friendly Features After Java 8

New Developer Friendly Features After Java 8

In this article, I’m going to describe the most significant and developer-friendly features of Java since the 8th version. Why such an idea? You can find many articles with a list of new features per each Java version on the Web. However, there is a lack of articles that give you a brief summary of the most important changes since the 8th version. Ok, but why the 8th version? Surprisingly, it is still the most commonly used version of Java. And all this even though we are on the eve of the Java 16 release. You can take a look at the results of my survey on Twitter. As you see more than 46% of responders still use Java 8 in production. By contrast, only less than 10% of responders use Java 12 or later.

Of course, Java 8 has introduced a solid pack of changes including Lambda Expressions. After that there was no similar release with such a huge set of key features. Nevertheless, you will find some interesting new features since Java 8. I have already published all the examples with them on Twitter in a graphical form visible below. You may find them using the #AfterJava8 hashtag.

java-new-features-twitter

Source Code

If you would like to try it by yourself, you may always take a look at my source code. In order to do that you need to clone my GitHub repository. I’m using Maven with Java 15 for compilation.

Switch Expression (JDK 12)

With the Switch expression, you can define multiple case labels and return values using an arrow. This feature is available since JDK 12. It makes the Switch expression really more accessible.

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

With Java below 12, the same example is pretty 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";
   }
}

Sealed Classes (JDK 15)

With the Sealed Classes feature you can restrict the use of a superclass. With a new keyword sealed you may define which other classes or interfaces may extend or implement the current class.

public abstract sealed class Pet permits Cat, Dog {}

A permitted subclass must define a modifier. If you don’t want to allow any other extensions you need to use the final keyword.

public final class Cat extends Pet {}

On the other hand, you may open the class for extensions. In that case, you should use the non-sealed modifier.

public non-sealed class Dog extends Pet {}

Of course, the declaration visible below is NOT ALLOWED.

public final class Tiger extends Pet {}

Text Blocks (JDK 13)

A text block is a multi-line string literal that avoids using escape sequences and automatically formats the string in a predictable way. It also gives the developer control over the format of the string. Since Java 13, Text Blocks are available as a preview feature. They are starting with three double-quote marks ("""). Let’s see how easily we can create and format a JSON message.

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

The same JSON string before Java 13 is pretty more complicated to create.

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

New Optional Methods (JDK 9/ JDK 10)

There are several useful methods for Optional since Java 9 and Java 10. The two most interesting of them are orElseThrow and ifPresentOrElse. With the orElseThrow method, you throw NoSuchElementException if no value is present. Otherwise, it returns a value.

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

Thanks to that, you can avoid using if statement with isPresent method.

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 ifPresentOrElse. If a value is present, it performs the given action with the value. Otherwise, it performs the given empty-based action.

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

With Java 8 we can use if-else directly with the 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");
}

Collection Factory Methods (JDK 9)

With a new feature of Java 9 called Collection Factory Methods, you can easily create immutable collections with predefined data. You just need to use the of method on the particular 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 might use Collections, but it is definitely a more complex way.

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);
}

Also, just to create ArrayList from a table of objects, you could use Arrays.asList(...) method.

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

Records (JDK 14)

With Records, you can define immutable, data-only classes (getters only). It automatically creates toString, equals, and hashCode methods. In fact, you just need to define fields as shown below.

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

The class with similar functionality as the record contains fields, constructor, getters, and implementation of 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 +
                '}';
    }
}

Private Methods in Interfaces (JDK 9)

Since Java 8, you can have public default methods inside the interface. But only since Java 9, you will be able to take full advantage of this feature thanks to private methods in interfaces.

public 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");
   }
}

Local Variable Type Inference (JDK 10 / JDK 11)

Since Java 10 you can declare a local variable without its type. You just need to define the var keyword instead of a type. Since 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");
}

Pattern Matching for switch (JDK 17)

Java 14 has already introduced Pattern Matching for instanceof (JEP-394). Starting from Java 17 you can also use this pattern inside your switch statement. Assuming we use sample classes from the Sealed Classes section we may define such a switch statement:

public String newSwitchWithPatternMatching(Pet pet) {
   return switch (pet) {
      case Cat c -> "cat";
      case Dog d -> "dog";
      default -> "other pet";
   };
}

3 COMMENTS

comments user
Marco

Thank you for sharing this article. I wrote one similar last November (I don’t like to post article directly in comment) and I agree with you that such features are very friendly for developers.
I’d like to add “NullPointerException” features in your list as “dev-helper” function in order to find out in which part has raised such error

comments user
Elmar Brauch

Good selection of new features.
I like also the new Pattern matching for instanceof, which allows direct assignment to a variable.
Since Java 16 it is a official feature, since 14 it was in preview state.

    comments user
    piotr.minkowski

    Thanks!

Leave a Reply