Eleven Hidden Pearls of Java 11

Original author: Nicolai Parlog
  • Transfer
  • Tutorial

Java 11 did not introduce any innovative features, but it contains several gems that you might not have heard of yet. Already we are looking at trends in String, Optional, Collectionand other workhorse? If not, then you have come to the address: today we will look at 11 hidden gems from Java 11!


Type inference for lambda parameters


When writing a lambda expression, you can choose between explicitly specifying types and skipping them:


Function append = string -> string + " ";
Function append = (String s) -> s + " ";

Java 10 introducedvar , but it could not be used in lambdas:


// ошибка компиляции в Java 10
Function append = (var string) -> string + " ";

In Java 11 it is already possible. But why? It doesn't seem to vargive more than just a type pass. And although it is, the use varhas two minor advantages:


  • makes use varmore universal by removing the exception to the rule
  • allows you to add annotations to the parameter type without resorting to using its full name

Here is an example of the second case:


List> types = /*...*/;
types
    .stream()
    // нормально, но нам нужна аннотация @Nonnull на типе
    .filter(type -> check(type))
    // в Java 10 нужно сделать так ~> гадость
    .filter((@Nonnull EnterpriseGradeType type) -> check(type))
    // в Java 11 можно уже так ~> гораздо лучше
    .filter((@Nonnull var type) -> check(type))

Although mixing derived, explicit and implicit types in lambda expressions of the form (var type, String option, index) -> ...can be implemented, but ( in the framework of JEP-323 ) this work was not carried out. Therefore, it is necessary to choose one of the three approaches and adhere to it for all parameters of the lambda expression. The need to specify varfor all parameters in order to add annotation for one of them can be slightly annoying, but generally bearable.


Stream processing of strings with ‘String::lines’


Got a multi-line string? Want to do something with every line of it? Then String::linesthis is the right choice:


var multiline = "Это\r\nваша\r\nмногострочная\r\nстрока";
multiline
    .lines() //Stream
    .map(line -> "// " + line)
    .forEach(System.out::println);
// ВЫВОД:
// Это
// ваша
// многострочная
// строка

Notice that the original line uses Windows delimiters \r\nand, although I am on Linux, lines()it still crashed it. This happens due to the fact that, in spite of the current operating system, this method is interpreted \r, \nand \r\nas a line break - even if they are mixed in a single line.


A stream of lines never contains the line separators themselves. Lines can be empty ( "как\n\nв этом\n\nслучае"which contains 5 lines), but the last line of the original line is ignored if it is empty ( "как\nтут\n"; 2 lines). (Note by the translator: it’s convenient for them to eat line, but there is string, and we have both of them строка.)


Unlike split("\R"), lines()lazy and, I quote , "provides better performance [...] by faster searching for new line breaks." (If someone wants to file a benchmark on JMH for verification, let me know). It also better reflects the processing algorithm and uses a more convenient data structure (stream instead of array).


Remove whitespace from ‘String::strip’, etc.


Initially, I Stringhad a method trimfor removing whitespace, which I considered everything with codes up to U+0020. Yes, BACKSPACE( U+0008)this is a white space like BELL( U+0007), but LINE SEPARATOR( U+2028) is no longer considered as such.


Java 11 introduced a method stripwhose approach has more nuances. It uses a method Character::isWhitespacefrom Java 5 to determine what exactly needs to be removed. From its documentation it is clear that this:


  • SPACE SEPARATOR, LINE SEPARATOR, PARAGRAPH SEPARATORBut not non-breaking space
  • HORIZONTAL TABULATION( U+0009), LINE FEED( U+000A), VERTICAL TABULATION( U+000B), FORM FEED( U+000C), CARRIAGE RETURN( U+000D)
  • FILE SEPARATOR( U+001C), GROUP SEPARATOR( U+001D), RECORD SEPARATOR( U+001E), UNIT SEPARATOR( U+001F)

With the same logic, there are two more cleansing methods, stripLeadingand stripTailingthat do exactly what is expected of them.


And finally, if you just need to find out if the line becomes empty after removing whitespace, then there is no need to really delete them - just use isBlank:


" ".isBlank(); // пробел ~> true
" ".isBlank(); // неразрывный пробел ~> false

Repeating lines with ‘String::repeat’


Catch the idea:


Step 1: Keeping a Watch on JDK

Keeping a close eye on JDK development


Step 2: Finding StackOverflow Related Questions

Looking for related questions on Stackoverflow


Step 3: Arriving with a new answer based on future changes

Swoop in with new answer based on upcoming changes


Step 4: ????

Step 4: Profit

¯ \ _ (ツ) _ / ¯


As you understand, Stringa new method has appeared repeat(int). It works exactly in line with expectations, and there is little to discuss.


Creating paths with ‘Path::of’


I really like the API Path, but converting paths between different views (such as Path, File, URL, URIand String) is still annoying. This point has become less confusing in Java 11 by copying two methods Paths::getinto methods Path::of:


Path tmp = Path.of("/home/nipa", "tmp");
Path codefx = Path.of(URI.create("http://codefx.org"));

They can be considered canonical, since both old methods Paths::getuse new options.


Reading and writing files with ‘Files::readString’and‘Files::writeString’


If I need to read from a large file, I usually use it Files::linesto get a lazy stream of its lines. Similarly, to record a large amount of data that may not even be stored in memory in its entirety, I use Files::writetransferring them as .Iterable


But what about the simple case when I want to process the contents of a file as a single line? This is not very convenient, since Files::readAllBytessuitable options Files::writeoperate on byte arrays.


And then there is Java 11, adding readString, and writeStringin Files:


String haiku = Files.readString(Path.of("haiku.txt"));
String modified = modify(haiku);
Files.writeString(Path.of("haiku-mod.txt"), modified);

Clear and easy to use. If necessary, you can pass it Charsetto readString, and writeStringalso to an array OpenOptions.


Empty I / O s ‘Reader::nullReader’, etc.


Need OutputStreamone that doesn't write anywhere? Or empty InputStream? What about Readerand Writerthat do nothing? Java 11 has it all:


InputStream input = InputStream.nullInputStream();
OutputStream output = OutputStream.nullOutputStream();
Reader reader = Reader.nullReader();
Writer writer = Writer.nullWriter();

(Translator's note: in commons-ioApache these classes have existed since about 2014.)


However, I am surprised - is it nullreally the best prefix? I don’t like how it is used to mean “intentional absence” ... Perhaps it would be better to use noOp? (Translator's note: most likely this prefix was chosen due to widespread use /dev/null.)


{ } ~> [ ] from ‘Collection::toArray’


How do you convert collections to arrays?


// до Java 11
List list = /*...*/;
Object[] objects = list.toArray();
String[] strings_0 = list.toArray(new String[0]);
String[] strings_size = list.toArray(new String[list.size()]);

The first option objects,, loses all the information about the types, so it’s in flight. What about the rest? Both are bulky, but the first is shorter. The latter creates an array of the required size, so that it looks more productive (that is, it "seems more productive", see credibility ). But is it really more productive? No, on the contrary, it is slower (at the moment).


But why should I care about this? Isn't there a better way to do this? In Java 11 there are:


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

A new option has appeared Collection::toArraythat accepts , i.e. a function that receives the size of the array and returns an array of the required size. It can be briefly expressed as a reference to a constructor of the form (for a known one ).IntFunctionT[]::newT


Interesting fact, the default implementation always passes to the array generator. At first, I decided that this solution was based on better performance with zero-length arrays, but now I think that the reason may be that for some collections, calculating the size can be a very expensive operation and you should not use this approach in the default implementation . However, specific collection implementations, such as , may change this approach, but they do not change in Java 11. Not worth it, I guess.Collection#toArray(IntFunction)0CollectionArrayList


Check for absence with ‘Optional::isEmpty’


With heavy use Optional, especially in large projects, where you often encounter a non- Optionalapproach, you often have to check if it has a value. There is a method for this Optional::isPresent. But just as often you need to know the opposite - that is Optionalempty. No problem, just use it !opt.isPresent(), right?


Of course, it can be so, but it is almost always ifeasier to understand logic if its condition is not inverted. And sometimes it Optionalpops up at the end of a long chain of calls and if you need to check it for nothing, then you have to put it !at the very beginning:


public boolean needsToCompleteAddress(User user) {
    return !getAddressRepository()
        .findAddressFor(user)
        .map(this::canonicalize)
        .filter(Address::isComplete)
        .isPresent();
}

In this case, skipping is !very easy. Starting with Java 11 there is a better option:


public boolean needsToCompleteAddress(User user) {
    return getAddressRepository()
        .findAddressFor(user)
        .map(this::canonicalize)
        .filter(Address::isComplete)
        .isEmpty();
}

Invert predicates with ‘Predicate::not’


Speaking of inverting ... The interface Predicatehas an instance methodnegate : it returns a new predicate that performs the same check, but inverts its result. Unfortunately, I rarely manage to use it ...


// хочу распечатать не пустые строки
Stream
    .of("a", "b", "", "c")
    // тьфу, лямбда ~> хочу использовать ссылку на метод и инвертировать
    .filter(s -> !s.isBlank())
    // компилятор не знает во что превратить ссылку на метод ~> ошибка
    .filter((String::isBlank).negate())
    // тьфу, каст ~> так даже хуче чем с лямбдой
    .filter(((Predicate) String::isBlank).negate())
    .forEach(System.out::println);

The problem is that I rarely have access to an instance Predicate. More often, I want to get such an instance through a link to a method (and invert it), but for this to work, the compiler must know what to bring the reference to the method to - without it, it can do nothing. And this is exactly what happens if you use the construction (String::isBlank).negate(): the compiler no longer knows what it should be String::isBlankon this and surrenders. A correctly specified caste fixes this, but at what cost?


Although there is a simple solution. Do not use the instance method negate, but use the new static method from Java 11:Predicate.not(Predicate)


Stream
    .of("a", "b", "", "c")
    // статически импортированный `java.util.function.Predicate.not`
    .filter(not(String::isBlank))
    .forEach(System.out::println);

Already better!


Regular expressions as a predicate with ‘Pattern::asMatchPredicate’


Is there a regular expression? Need to filter data on it? How about this:


Pattern nonWordCharacter = Pattern.compile("\\W");
Stream
    .of("Metallica", "Motörhead")
    .filter(nonWordCharacter.asPredicate())
    .forEach(System.out::println);

I was very happy to find this method! It is worth adding that this is a method from Java 8. Oops, I missed it then. Java 11 added another similar method: Pattern::asMatchPredicate. What is the difference?


  • asPredicatechecks that the string or part of the string matches the pattern (works like s -> this.matcher(s).find())
  • asMatchPredicatechecks that the entire string matches the pattern (works like s -> this.matcher(s).matches())

For example, we have a regular expression that checks phone numbers, but it does not contain ^it $to track the beginning and end of a line. Then the following code will not work as you might expect:


prospectivePhoneNumbers
    .stream()
    .filter(phoneNumberPatter.asPredicate())
    .forEach(this::robocall);

Did you notice a mistake? The view string "о ФЗ-152 слышал? +1-202-456-1414"will be filtered, because it contains a valid phone number. On the other hand, Pattern::asMatchPredicateit will not allow this, because the entire string will no longer match the pattern.


Self test


Here is an overview of all eleven pearls - do you still remember what each method does? If so, you have passed the test.


  • in String:
    • Stream lines()
    • String strip()
    • String stripLeading()
    • String stripTrailing()
    • boolean isBlank()
    • String repeat(int)
  • in Path:
    • static Path of(String, String...)
    • static Path of(URI)
  • in Files:
    • String readString(Path) throws IOException
    • Path writeString(Path, CharSequence, OpenOption...) throws IOException
    • Path writeString(Path, CharSequence, Charset, OpenOption...) throws IOException
  • in InputStream:static InputStream nullInputStream()
  • in OutputStream:static OutputStream nullOutputStream()
  • in Reader:static Reader nullReader()
  • in Writer:static Writer nullWriter()
  • in Collection:T[] toArray(IntFunction)
  • in Optional:boolean isEmpty()
  • in Predicate:static Predicate not(Predicate)
  • in Pattern:Predicate asMatchPredicate()

Have fun with Java 11!

Only registered users can participate in the survey. Please come in.

What version of Java do you use in prod?

  • 1.2% Java 12 - at the forefront of attack 3
  • 19.2% Java 11 - Keeping Up With the Times 45
  • 76.9% Java 8 - forever in our hearts 180
  • 5.9% Java 7 - We Are Alright 14
  • 2.5% Java 6 - I'm Retrograde 6
  • 1.2% Java 5 - our stability is all 3
  • 2.9% Java 4 - did anything new come out? 7

Also popular now: