Java 11 API Additions: Files.readString, Predicate.not

Introduction Besides the new String methodsClick to read tutorial: Java 11 String Methods: isBlank, lines, strip, repeat, Java 11 added a number of small methods across the core libraries which remove boilerplate from everyday code. This tutorial explains these Java 11 API additions with examples. We will start with Files.readString() and Files.writeString(), which read and write a text file in one line, along with Path.of(). We will then look at Predicate.not() and Pattern.asMatchPredicate() for filtering streams, Optional.isEmpty(), and Collection.toArray(IntFunction). Lastly, we will go through a few other additions such as TimeUnit.convert(Duration) and the null streams and writers. Files.readString(), Files.writeString() and Path.of() Definitions of the new Files and Path methods
public static Path writeString(Path path, CharSequence csq, OpenOption... options)
public static String readString(Path path)
public static Path of(String first, String... more)
Before Java 11, reading a whole text file into a String needed new String(Files.readAllBytes(path), StandardCharsets.UTF_8), and writing one needed Files.write() with a byte array or a list of lines. Java 11 adds methods which do this directly. Both use UTF-8 by default, and have overloads which take a Charset. Path.of() is a new way to create a Path, which does the same thing as the older Paths.get() but sits on the Path interface itself.
Reading and writing text files in one line
//ReadWriteStringDemo.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;

public class ReadWriteStringDemo {
  public static void main(String[] args) throws IOException {
    Path notes = Path.of("notes.txt");

    Files.writeString(notes, "Java 11 was released in September 2018.\n");
    Files.writeString(notes, "It is a long-term support release.\n", StandardOpenOption.APPEND);

    String content = Files.readString(notes);
    System.out.print(content);
    System.out.println("Lines: " + content.lines().count());

    Files.delete(notes);
  }
}
 OUTPUT of the above code
Java 11 was released in September 2018.
It is a long-term support release.
Lines: 2
Explanation of the code
  • Path.of("notes.txt") creates a path relative to the current directory. It also accepts several parts, as in Path.of("data", "2026", "notes.txt").
  • The first writeString() creates the file, or replaces its contents if it exists. Passing StandardOpenOption.APPEND adds to the end of the file instead.
  • readString() returns the whole file as one String, which we then split into lines with the new String.lines() method.
  • readString() reads the entire file into memory, so it is meant for small and medium-sized files. For large files, process the lines one at a time with Files.lines(). If the file is not valid UTF-8, readString() throws a MalformedInputException instead of silently replacing the bad bytes.
Predicate.not() and Pattern.asMatchPredicate() Definition of Predicate.not()
static <T> Predicate<T> not(Predicate<? super T> target)
Method referencesClick to read: Method references such as String::isBlank make stream filters short and readable, but there was no easy way to negate one. You had to fall back to a lambda, s -> !s.isBlank(), or write ((Predicate<String>) String::isBlank).negate(). The static method Predicate.not() solves this. Java 11 also added Pattern.asMatchPredicate(), which turns a regular expression into a predicate that tests whether the whole string matches.
Negating method references with Predicate.not()
//PredicateNotDemo.java
import java.util.List;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class PredicateNotDemo {
  public static void main(String[] args) {
    List<String> lines = List.of("Tom", "  ", "Harry", "", "Sam42", "Ann");

    List<String> nonBlank = lines.stream()
        .filter(Predicate.not(String::isBlank))
        .collect(Collectors.toList());
    System.out.println("Not blank: " + nonBlank);

    Predicate<String> onlyLetters = Pattern.compile("[A-Za-z]+").asMatchPredicate();
    System.out.println("Only letters: " + nonBlank.stream().filter(onlyLetters).collect(Collectors.toList()));
    System.out.println("Not only letters: " + nonBlank.stream().filter(Predicate.not(onlyLetters)).collect(Collectors.toList()));
  }
}
 OUTPUT of the above code
Not blank: [Tom, Harry, Sam42, Ann]
Only letters: [Tom, Harry, Ann]
Not only letters: [Sam42]
Explanation of the code
  • Predicate.not(String::isBlank) keeps the strings which are not blank, dropping " " and "". With a static import of java.util.function.Predicate.not, this can be written as just filter(not(String::isBlank)).
  • asMatchPredicate() uses Matcher.matches(), so "Sam42" fails because the whole string must consist of letters. The older asPredicate(), from Java 8, uses find() and would accept any string which contains a letter.
Optional.isEmpty() OptionalClick to read: Optional has had isPresent() since Java 8. Java 11 adds its opposite, isEmpty(), which reads better than !optional.isPresent(), and can be used as a method reference -
Checking for an empty Optional
//OptionalIsEmptyDemo.java
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class OptionalIsEmptyDemo {
  static final Map<String, String> EMAILS = Map.of("Tom", "tom@example.com");

  static Optional<String> findEmail(String name) {
    return Optional.ofNullable(EMAILS.get(name));
  }

  public static void main(String[] args) {
    for (String name : List.of("Tom", "Harry")) {
      Optional<String> email = findEmail(name);
      if (email.isEmpty()) {
        System.out.println(name + ": no email on record");
      } else {
        System.out.println(name + ": " + email.get());
      }
    }
    long missing = List.of("Tom", "Harry", "Sam").stream()
        .map(OptionalIsEmptyDemo::findEmail)
        .filter(Optional::isEmpty)
        .count();
    System.out.println("Names without email: " + missing);
  }
}
 OUTPUT of the above code
Tom: tom@example.com
Harry: no email on record
Names without email: 2
Collection.toArray(IntFunction) Definition of Collection.toArray(IntFunction)
default <T> T[] toArray(IntFunction<T[]> generator)
Converting a collection to a typed array used to need list.toArray(new String[0]). Java 11 adds an overload which takes an array constructor reference, in the same way as Stream.toArray()Click to read: Stream.toArray() -
Converting a collection to a typed array
//ToArrayDemo.java
import java.util.Arrays;
import java.util.List;

public class ToArrayDemo {
  public static void main(String[] args) {
    List<String> names = List.of("Tom", "Harry", "Sam");

    String[] before = names.toArray(new String[0]);   // before Java 11
    String[] after = names.toArray(String[]::new);     // Java 11

    System.out.println(Arrays.toString(before) + " " + before.getClass().getSimpleName());
    System.out.println(Arrays.toString(after) + " " + after.getClass().getSimpleName());
  }
}
 OUTPUT of the above code
[Tom, Harry, Sam] String[]
[Tom, Harry, Sam] String[]
Both give a String[]. The new form is shorter and makes the intent clearer. Note that the array constructor is called with a size of 0, and the result is then filled using toArray(T[]), so it is not faster than the old form. Other additions in Java 11
TimeUnit.convert(Duration) and null streams
//OtherAdditionsDemo.java
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.Writer;
import java.time.Duration;
import java.util.concurrent.TimeUnit;

public class OtherAdditionsDemo {
  public static void main(String[] args) throws IOException {
    // TimeUnit.convert(Duration)
    Duration timeout = Duration.ofMinutes(2).plusSeconds(30);
    System.out.println(TimeUnit.SECONDS.convert(timeout) + " seconds");
    System.out.println(TimeUnit.MILLISECONDS.convert(timeout) + " milliseconds");

    // Null streams, readers and writers
    System.out.println("Bytes read: " + InputStream.nullInputStream().readAllBytes().length);
    try (OutputStream sink = OutputStream.nullOutputStream()) {
      sink.write("discarded".getBytes());
    }
    Writer discard = Writer.nullWriter();
    long copied = new StringReader("Java 11").transferTo(discard);
    System.out.println("Characters transferred to nullWriter: " + copied);
  }
}
 OUTPUT of the above code
150 seconds
150000 milliseconds
Bytes read: 0
Characters transferred to nullWriter: 7
  • TimeUnit.convert(Duration) converts a java.time.Duration to a count of the given unit, which helps when calling older concurrency APIs that take a long and a TimeUnit.
  • InputStream.nullInputStream(), OutputStream.nullOutputStream(), Reader.nullReader() and Writer.nullWriter() return streams which contain no data, or discard everything written to them. They are handy in tests, and when an API requires a stream whose output you do not need.
Summary In this tutorial we went through the smaller API additions in Java 11. We read and wrote text files with Files.readString() and Files.writeString(), and created paths with Path.of(). We negated method references with Predicate.not(), matched whole strings with Pattern.asMatchPredicate(), and checked for an empty value with Optional.isEmpty(). Lastly, we converted collections to typed arrays with toArray(String[]::new), and looked at TimeUnit.convert(Duration) and the null streams. In the next tutorial, we will look at the biggest API addition in Java 11 - the HTTP Client APIClick to read tutorial: Java 11 HTTP Client API Tutorial with Examples.