Java 11 features with examples

Java 11 features

Java is one of the most popular programming languages widely used for building robust and scalable software applications. It released many important features, developers have access to a wide range of new features and improvements that can help them write better code with greater efficiency.

Oracle released Java 11 in September 2018. It was the second long-term support (LTS) version of Java. It means that supports bug fixes and security updates for a more extended period, making it a stable and reliable choice for developers.

Overall, Java 11 offers a wide range of features and enhancements that make it an exciting choice for developers looking to build modern, scalable applications.

Oracle vs. Open JDK

Oracle JDK and OpenJDK have two different distributions of the Java Development Kit (JDK) and Java Runtime Environment (JRE). Both used by developers widely used by developers.

Oracle JDK is a proprietary distribution of Java that is developed and maintained by Oracle Corporation. It includes additional features, such as Java Flight Recorder, Java Mission Control, and JavaFX, which are not available in the OpenJDK distribution.

The Java community develops and maintains OpenJDK, it’s a free and open-source distribution of Java. It is licensed under GNU, which allows users to use, modify, and distribute it freely. OpenJDK serves as the reference implementation for the Java language and most Linux distributions use it as their default JDK.

The main difference between the two is that Oracle JDK includes some additional features and comes with a commercial license, while OpenJDK is free and open-source.

In practice, the choice between Oracle JDK and OpenJDK often depends on the specific needs of the project or organization. If cost or licensing considerations are a concern, then OpenJDK may be the better choice.

Let’s Discuss all features of java 11 one by one

Java 11 introduced several new developer features that can help improve the developer experience and productivity.

Developer features in Java 11

New String Methods in java:

New String Methods in java: The string class has a lot of methods to deal with String in java but Java 11 introduces several new methods into the String class, such as isBlank(), strip(), stripLeading(), stripTrailing(), and repeat(int count)

Local variable type inference

To make it a better feature, Java 11 introduces the “var” keyword for local variable type inference, which allows developers to declare variables without explicitly specifying the type.

New File Methods

To provide more flexibility to file methods, Java 11 introduced new methods for reading and writing files using the readString() and writeString() methods, which can simplify file I/O operations.

Collection to Array

Java 11 introduces a new method toArray(IntFunction<T[]> generator) in the Collection interface, which allows collections to be easily converted into arrays

Predicate Not Method

Java 11 introduced a new method “not()” in the Predicate interface that returns a new predicate that represents the negation of the original predicate. This simplifies code that requires negating a predicate.

HTTP Client API

Java 11 introduced a new HTTP Client API that makes it easier for developers to send HTTP requests and handle responses asynchronously.

Nest-Based Access Control

Java 11 introduces Nest-Based Access Control, which allows classes that are logically part of the same code entity to access each other’s private members without the need for reflection.

Enhanced Unicode Support

Java 11 includes several enhancements to Unicode support, such as support for Unicode 10.0.0, additional scripts, and the ability to handle non-BMP Unicode characters.

Flight Recorder

Java 11 contains Java Flight Recorder, which is a profiling and diagnostic tool for collecting and analyzing runtime information about the JVM and the Java applications running on it.

Low Overhead Heap Profiling

Java 11 includes a new low-overhead heap profiling feature that can help developers diagnose memory-related issues in their applications more easily.

Dynamic Class-File Constants

Java 11 introduces a new constant-pool form, “Dynamic”, that allows constants to be dynamically computed at runtime.

Epsilon

Java 11 includes a new experimental garbage collector called Epsilon, which designes to be a no-op collector that simply allocates and discards memory without performing any actual garbage collection.

ZGC

Java 11 introduces a new experimental garbage collector called ZGC, which is designed to be a scalable, low-latency collector that can handle heaps ranging from a few hundred megabytes to several terabytes in size.

ChaCha20 and Poly1305 Cryptographic Algorithms

Java 11 supports the ChaCha20 and Poly1305 cryptographic algorithms, which can provide faster and more secure encryption and decryption than some of the older algorithms.

Launch Single-File Source-Code Programs

Java 11 features with examples

Local Variable Type Inference

Local Variable Type Inference was introduced in java 10. This feature allows developers to declare and initialize local variables without explicitly stating their data type. But there was some improvement area. So Java 11 continued to support this feature and introduced some improvements to make it more usable. In this post, we will see these improvements with examples.

Local variable type inference for lambda expressions in java 11

In java, the lambda expressions feature was introduced in java 8 that allows developers to write functional-style code. But we couldn’t use var as the parameter type for lambda expressions before java 11. After java 11, the compiler can identify the type of a lambda expression parameter based on its context. So now we can use var as the parameter type for lambda expressions.

Let’s take an example and use a lambda expression to sort a list of integers in descending order. First of all, we will see it with java 10 and then move the code to java 11.

import java.util.Arrays;
import java.util.List;

public class VarInLambda {

    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(3, 5, 2, 6, 1, 4);
        numbers.sort((Integer a, Integer b) -> b.compareTo(a));
        System.out.println(numbers);
    }
}

Output: [6, 5, 4, 3, 2, 1]

In the above code, we used the Integer type parameters for the lambda expression. Now let’s see it with the enhanced inference in Java 11, the parameter types can be omitted and replaced with var:

import java.util.Arrays;
import java.util.List;

public class VarInLambda {

    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(3, 5, 2, 6, 1, 4);
        numbers.sort((var a, var b) -> b.compareTo(a));
        System.out.println(numbers);
    }
}

Output: [6, 5, 4, 3, 2, 1]

Using var with Anonymous Inner Classes

Java 11 did one more improvement in local variable type inference. Now developers can use the var keyword with anonymous inner classes. Before java 11, anonymous inner classes required an explicit type declaration for their variables, which could make the code more verbose and harder to read. With the ability to use var in anonymous inner classes, developers can now write more concise and readable code.

Let’s take an example of anonymous class.

Runnable task = new Runnable() {
    @Override
    public void run() {
        System.out.println("Task executed");
    }
};

In the above code, We used to type with an anonymous inner class that implements the Runnable interface. The type declaration for the variable can be omitted:

var task = new Runnable() {
    @Override
    public void run() {
        System.out.println("Task executed");
    }
};

Converting a Collection to an Array

Java 11, introduces a new method in the Collection interface i.e. toArray(IntFunction generator) method. This method uses to convert a collection into an array. In this section, we will read about Converting a Collection to an Array in java 11

The toArray() method has been a part of the Collection interface since Java 1.2. But Java 11 provides a new method that allows for more control over the returned array. So now the toArray() method is an overloaded method. The method introduced in java 11 takes an IntFunction as an argument, which specifies the type of the returned array.

default <T> T[] toArray(IntFunction<T[]> generator)

It’s a default method that takes only one parameter. The IntFunction is a functional interface that takes an integer value and returns an array of type T[]. It is useful where the size of the collection is known beforehand. because it provides more control over the returned array size.

import java.util.ArrayList;
import java.util.List;

public class CollectionToArrayExample {
   public static void main(String[] args) {
      List<String> list = new ArrayList<>();
      list.add("apple");
      list.add("banana");
      list.add("orange");

      String[] array = list.stream().toArray(String[]::new);

      for (String s : array) {
         System.out.println(s);
      }
   }
}

Output: apple
banana
orange

How was this type of task performed before Java 11?

Object[] toArray()
<T> T[] toArray(T[] a)

The 1st overload method (Object[] toArray()) returns an array of type Object[] that contains all of the elements in the collection.

The second overload method (T[] toArray(T[] a)) took an array of type T[] as an argument, and returns that same array if it was large enough to hold all of the elements in the collection, or a new array of the same type if it was not.

import java.util.ArrayList;
import java.util.List;

public class CollectionToArrayExample {
   public static void main(String[] args) {
      List<String> list = new ArrayList<>();
      list.add("apple");
      list.add("banana");
      list.add("orange");

      String[] array = new String[list.size()];
      array = list.toArray(array);

      for (String s : array) {
         System.out.println(s);
      }
   }
}

Output: apple
banana
orange

Java 11 Files New Methods

Java 11 introduced some new methods in the Files class and these methods provide better handling. These methods are:

  1. readString(Path path, Charset cs) method
  2. writeString(Path path, CharSequence csq, OpenOption… options) method

readString(Path path, Charset cs) method

The readString(Path path, Charset cs) method exists in the java.nio.file.Files class in Java 11. It uses to read the content from a file into a string using the specified charset. Let’s try to explain it with some scenarios where we can use it.

Scenario 1: Reading a Text File
Suppose we want to read a text file that contains data and process it in a java application.

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;

public class FileReader {

    public static void main(String[] args) throws IOException {
        Path filePath = Paths.get("data.txt");
        String fileContent = Files.readString(filePath, StandardCharsets.UTF_8);
        System.out.println(fileContent);
    }
}

Here we create a Path object that represents the file we want to read. After that we can use the readString() method to read the content of the file into a string using the UTF-8 charset.

Scenario 2: Parsing a CSV File
Let’s try to read the CSV file that you need to parse in your Java application.

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;

public class CsvParser {

    public static void main(String[] args) throws IOException {
        Path filePath = Paths.get("data.csv");
        String fileContent = Files.readString(filePath, StandardCharsets.UTF_8);

        String[] rows = fileContent.split("\n");
        for (String row : rows) {
            String[] columns = row.split(",");
            System.out.println("Name: " + columns[0] + ", Age: " + columns[1]);
        }
    }
}

We used the readString() method to read the CSV file into a string using the UTF-8 charset. By use of the split() method the string is into rows using the newline character as the separator and split each row into columns using the comma character as the separator.

writeString(Path path, CharSequence csq, OpenOption… options)

The writeString(Path path, CharSequence csq, OpenOption… options) method was introduced in the java.nio.file.Files class in Java 11. It writes a string to a file using the specified charset and options.

Scenario 1: Writing a Text File
When we want to write to a text file in your Java application. Then we can use the writeString() method to write a string to a file.

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;

public class FileWriter {

    public static void main(String[] args) throws IOException {
        Path filePath = Paths.get("data.txt");
        String fileContent = "Hello, world!";
        Files.writeString(filePath, fileContent, StandardCharsets.UTF_8);
    }
}

Scenario 2: Appending to a Text File
Let’s try to read a text file that already contains some data. After that, we want to append some more data to the end of the file in your Java application.

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;
import java.nio.file.StandardOpenOption;

public class FileAppender {

    public static void main(String[] args) throws IOException {
        Path filePath = Paths.get("data.txt");
        String fileContent = "World!";
        Files.writeString(filePath, fileContent, StandardCharsets.UTF_8, StandardOpenOption.APPEND);
    }
}

How was this type of task performed before Java 11?

Let’s see how we read the file before java 11:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class FileReaderExample {

    public static void main(String[] args) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader("data.txt"));
            String line = null;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

The readString() method allows us to read the entire contents of a file into a String with a single method call. It’s much simpler and more convenient than BufferedReader to read the file line by line.

Advantages of readString() method over the BufferedReader approach

Simplicity: The readString() method makes the code simpler and easier to read because we don’t need to deal with a BufferedReader and a loop.

Efficiency: As we have seen above example readString() method reads the entire file at once and we don’t need to take the overhead of repeatedly calling readLine() and concatenating strings.

Error handling: The readString() method provide support to handle errors more consistently than the BufferedReader approach.

Let’s see how we wrote the file before java 11:

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class FileWriterExample {

    public static void main(String[] args) {
        FileWriter writer = null;
        try {
            File file = new File("data.txt");
            writer = new FileWriter(file);
            String fileContent = "Hello, world!";
            writer.write(fileContent);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedWriterExample {

    public static void main(String[] args) {
        BufferedWriter writer = null;
        try {
            writer = new BufferedWriter(new FileWriter("data.txt"));
            String fileContent = "Hello, world!";
            writer.write(fileContent);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Java 11 Predicate Not Method Example

Java 11 introduces a new method i.e. called the “not() method” which provides an easy way to negate a predicate. It presents in the Predicate interface. A Predicate interface is a functional interface that takes an argument and returns a boolean value. Mostly it uses to filter collections or streams of data.

Let’s understand what it does Negate a predicate is required using the “negate()” method. It’s a default method and static method that returns a new predicate that is the negation of the original predicate. Let’s see the syntax of not() method:

static <T> Predicate<T> not(Predicate<? super T> target)

Let’s see how does Predicate not() method work. Suppose we want to check even or odd numbers by use of a predicate.

import java.util.function.Predicate;

public class Example {
    public static void main(String[] args) {
        Predicate<Integer> isEven = num -> num % 2 == 0;
        Predicate<Integer> isOdd = isEven.not();

        System.out.println(isOdd.test(5)); // Output: true
    }
}

Here, we define a predicate called “isEven” that checks whether an integer is even. Now we want to create another predicate called “isOdd”, So we can use the “not()” method to create a new predicate called “isOdd” that checks whether an integer is odd.

We can use the “not()” method as in chained with other methods, like “and()” and “or()”, to create more complex predicates.

import java.util.function.Predicate;

public class Example {
    public static void main(String[] args) {
        Predicate<Integer> isEven = num -> num % 2 == 0;
        Predicate<Integer> isDivisibleBy3 = num -> num % 3 == 0;
        Predicate<Integer> isEvenAndDivisibleBy3 = isEven.not().and(isDivisibleBy3);

        System.out.println(isEvenAndDivisibleBy3.test(6)); // Output: true
        System.out.println(isEvenAndDivisibleBy3.test(9)); // Output: false
    }
}

Limitations

1. It only works for predicates that return a boolean value.
2. It’s not always easy to read and understand the code when multiple negations are involved.


		

Leave a Comment