
Eleven Hidden Pearls of Java 11
- 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
, Collection
and 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 var
give more than just a type pass. And although it is, the use var
has two minor advantages:
- makes use
var
more 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 var
for 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::lines
this 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\n
and, 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
, \n
and \r\n
as 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 String
had a method trim
for 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 strip
whose approach has more nuances. It uses a method Character::isWhitespace
from Java 5 to determine what exactly needs to be removed. From its documentation it is clear that this:
SPACE SEPARATOR
,LINE SEPARATOR
,PARAGRAPH SEPARATOR
But not non-breaking spaceHORIZONTAL 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, stripLeading
and stripTailing
that 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
Step 2: Finding StackOverflow Related Questions
Step 3: Arriving with a new answer based on future changes
Step 4: ????
Step 4: Profit
As you understand, String
a 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
, URI
and String
) is still annoying. This point has become less confusing in Java 11 by copying two methods Paths::get
into 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::get
use 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::lines
to 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::write
transferring 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::readAllBytes
suitable options Files::write
operate on byte arrays.
And then there is Java 11, adding readString
, and writeString
in 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 Charset
to readString
, and writeString
also to an array OpenOptions
.
Empty I / O s ‘Reader::nullReader’
, etc.
Need OutputStream
one that doesn't write anywhere? Or empty InputStream
? What about Reader
and Writer
that 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-io
Apache these classes have existed since about 2014.)
However, I am surprised - is it null
really 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::toArray
that 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 ).IntFunction
T[]::new
T
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
0
Collection
ArrayList
Check for absence with ‘Optional::isEmpty’
With heavy use Optional
, especially in large projects, where you often encounter a non- Optional
approach, 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 Optional
empty. No problem, just use it !opt.isPresent()
, right?
Of course, it can be so, but it is almost always if
easier to understand logic if its condition is not inverted. And sometimes it Optional
pops 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 Predicate
has 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::isBlank
on 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?
asPredicate
checks that the string or part of the string matches the pattern (works likes -> this.matcher(s).find()
)asMatchPredicate
checks that the entire string matches the pattern (works likes -> 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::asMatchPredicate
it 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