Java 11 introduced no ground-breaking features, but contains a number of gems that you may not have heard about yet.
Sure, you likely know about the reactive HTTP/2 API and executing source files without compiling them, but did you try out the additions to String
, Optional
, Collection
, and other workhorses?
If not, you've come to the right place: Here are eleven hidden gems in Java 11!
▚Eleven Gems
▚Type Inference For Lambda Parameters
When writing a lambda expression, you can choose between specifying the types or omitting them:
Function<String, String> append = string -> string + " ";
Function<String, String> append = (String s) -> s + " ";
Java 10 introduced var
, but you couldn't use it in lambdas:
// compile error in Java 10
Function<String, String> append = (var string) -> string + " ";
In Java 11 you can.
Why, though?
It's not like var
adds anything over just omitting the type.
While that is the case, allowing var
has two minor advantages:
- makes the mental model for
var
more uniform by removing a special case - allows type annotations on lambda parameters without having to resort to a full type name
Here's an example for the second point:
List<EnterpriseGradeType<With, Generics>> types = /*...*/;
types.stream()
// this is fine, but we need @Nonnull on the type
.filter(type -> check(type))
// in Java 10, we need to do this ~> ugh!
.filter((@Nonnull EnterpriseGradeType<With, Generics> type) -> check(type))
// in Java 11, we can do this ~> better
.filter((@Nonnull var type) -> check(type))
While mixing implicit types, explicit types, and var
in lambdas like (var type, String option, index) -> ...
could be supported, it would (apparently) make the implementation more complicated.
You hence have to choose one of the three approaches and stick with it for all parameters.
Having to add var
to all parameters just to apply an annotation to one of them, may be mildly annoying, but I think it's bearable.
▚Streaming Lines With String::lines
Got a multiline string?
Want to do something with every line?
Then String::lines
is the right choice:
var multiline = "This\r\nis a\r\nmultiline\r\nstring";
multiline.lines()
// we now have a `Stream<String>`
.map(line -> "// " + line)
.forEach(System.out::println);
// OUTPUT:
// This
// is a
// multiline
// string
Note that the string uses Windows' \r\n
and even though I'm on Linux, lines()
still splits it.
That's because regardless of the operating system, the method treats \r
, \n
, and \r\n
as line terminators and splits there - even if they are mixed in the same string.
The streamed lines never contain the line terminator itself.
They can be empty ("like\n\nin this\n\ncase"
, which has 5 lines), but the line at the end of the string will be ignored if its empty ("like\nhere\n"
; 2 lines).
Unlike split("\R")
, lines()
is lazy and, I quote, "provides better performance [...] by faster search of new line terminators".
(If someone feels like firing up JMH to verify this, let me know.) It's also much better at conveying what you want to do and returns a more convenient data structure (stream instead of array).
Neat.
▚Stripping Whitespace With String::strip
Since forever, String
offered trim
to remove whitespace, which it considered everything with a Unicode up to U+0020.
Yep, BACKSPACE
(U+0008) is whitespace and so is BELL
(U+0007) but LINE SEPARATOR
(U+2028) isn't.
🤔
Java 11 introduces strip
, which has a little more nuanced approach.
It uses Java 5's Character::isWhitespace
to determine what to strip.
From its Javadoc that is:
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)
Building on this logic, there are two more stripping methods, stripLeading
and stripTailing
, which do what you'd expect.
Finally, if you just need to know whether a string would be empty after stripping whitespace, no need to actually do it - use isBlank
instead:
" ".isBlank()); // space ~> true
" ".isBlank()); // non-breaking space ~> false
▚Repeating Strings with String::repeat
Life hack:
Step 1: Obsessively observe JDK development.
Step 2: Scour StackOverflow for related questions.
Step 3: Swoop in with new answer based on upcoming changes.
Step 4: ¯\_(ツ)_/¯
Step 5:
As you can see, String
now has a method repeat(int)
.
It behaves exactly according to expectations and there aren't any nooks and crannies to discuss.
▚Creating Paths With Path::of
I really like the Path
API but going back and forth between paths as Path
, File
, URL
, URI
, and String
, is really annoying.
This has gotten a tiny bit less confusing in Java 11 by copying the two Paths::get
methods to Path::of
:
Path tmp = Path.of("/home/nipa", "tmp");
Path codefx = Path.of(URI.create("https://nipafx.dev"));
They can be deemed to be the canonical choice as both Paths::get
methods forward to them.
▚Reading From And Writing To Files With Files::readString
and Files::writeString
If I need to read from a large file, I usually use Files::lines
to get a lazy stream of its content.
Likewise, for writing a lot of content that may not be present in memory all at once, I use Files::write
by passing it an Iterable<String>
.
But what about the easy case where I can handle the entire content as a simple string?
That hasn't been terribly convenient because Files::readAllBytes
and the matching overload for Files::write
operate with byte arrays.
🤢
Here's where Java 11 interjects by adding readString
and writeString
to Files
:
String haiku = Files.readString(Path.of("haiku.txt"));
String modified = modify(haiku);
Files.writeString(Path.of("haiku-mod.txt"), modified);
Straightforward and simple to use.
If need be, you can also pass a CharSet
to readString
and OpenOption
s to writeString
.
▚Null I/O With Reader::nullReader
et al
Need an OutputStream
that discards input bytes?
Need an empty InputStream
?
What about Reader
and Writer
that do nothing?
Java 11 has got you covered:
InputStream input = InputStream.nullInputStream();
OutputStream output = OutputStream.nullOutputStream();
Reader reader = Reader.nullReader();
Writer writer = Writer.nullWriter();
I wonder, though, is null
really the best prefix here?
I don't like how it's used to mean "intended absence"... Maybe noOp
would have been better?
▚{ }
~> [ ]
With Collection::toArray
How do you turn a collection into an array?
// before Java 11
List<String> 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
, looses all type information, so it's out.
What about the other two?
Both are cumbersome, but the first is more succinct.
The latter creates an array with the required size, so it's more performanty (i.e.
"appears more performant"; cf.
truthy).
But does it actually perform better?
No, on the contrary, it's slower (at the moment).
But why should we care about that? Isn't there a better way to do this? In Java 11 there is:
String[] strings_fun = list.toArray(String[]::new);
There's a new overload of Collection::toArray
that takes an IntFunction<T[]>
, i.e.
a function that accepts the length of the array to produce as input and returns an array of that size.
That can be expressed succinctly as a constructor reference T[]::new
(for concrete T
).
Fun fact, the default implementation of toArray(IntFunction<T[]>)
always passes 0 to the provided array generator.
At first, I thought that decision was made based on the better performance of starting out with such a 0-length array, but now I think it may be because, for some collections, computing the size can be very expensive and so it wouldn't be a good default implementation on Collection
.
Concrete collections like ArrayList
could then override, but, in Java 11, they don't.
Not worth it, I guess.
This new method supersedes toArray(T[])
unless you already have an array lying around, in which case the old method remains useful.
▚Not Present With Optional::isEmpty
When you use Optional
a lot, particularly in large code bases where you interact with a lot of non-Optional
-bearing code, you'll frequently have to check whether the value is present.
Optional::isPresent
is there for you.
But about just as often, you want to know whether the Optional
is empty.
No problem, just use !opt.isPresent()
, right?
Sure, that works, but it's almost always easier to understand an if
if the condition is not negated.
And sometimes, the Optional
pops up at the end of a longer call chain and if you want to know whether it's empty, you need to put the !
all the way to the front:
public boolean needsToCompleteAddress(User user) {
return !getAddressRepository()
.findAddressFor(user)
.map(this::canonicalize)
.filter(Address::isComplete)
.isPresent();
}
The !
is easy to miss.
From Java 11 on, there's a better option:
public boolean needsToCompleteAddress(User user) {
return getAddressRepository()
.findAddressFor(user)
.map(this::canonicalize)
.filter(Address::isComplete)
.isEmpty();
}
▚Inverting Predicates With Predicate::not
Talking about "not"... The Predicate
interface has an instance method negate
; it returns a new predicate that executes the same test but inverts the result.
Unfortunately, I very rarely get to use it...
// want to print non-blank strings
Stream.of("a", "b", "", "c")
// ugh, lambda ~> want to use method reference and negate it
.filter(string -> !string.isBlank())
// compiler has no target for method reference ~> error
.filter((String::isBlank).negate())
// ugh, cast ~> this is way worse than lambda
.filter(((Predicate<String>) String::isBlank).negate())
.forEach(System.out::println);
The problem is that I rarely have a Predicate
instance at hand.
Much more often, I want to create one with a method reference (and then invert it), but for that to work the compiler needs to know the target - without it, it doesn't know what to turn the reference into.
And that's what happens when you attach a method call as in (String::isBlank).negate()
: There's no longer a target for String::isBlank
and so the compiler barfs.
A properly placed cast fixes that but at what cost?
There's an easy solution, though.
Don't use the instance method negate
, use Java 11's new static method Predicate.not(Predicate<T>)
instead:
Stream.of("a", "b", "", "c")
// statically import `Predicate.not`
.filter(not(String::isBlank))
.forEach(System.out::println);
I like it!
▚Regular Expressions As Predicate With Pattern::asMatchPredicate
Have a regular expression? Want to filter based on it? What about this:
Pattern nonWordCharacter = Pattern.compile("\\W");
Stream.of("Metallica", "Motörhead")
.filter(nonWordCharacter.asPredicate())
.forEach(System.out::println);
I was really happy to discover this method!
This Java 8 method, I should add.
Oops, missed that one.
😂 Java 11 adds another such method: Pattern::asMatchPredicate
.
What's the difference?
asPredicate
checks whether the string or any substring matches this pattern (it behaves likes -> this.matcher(s).find()
)asMatchPredicate
is only content if the entire string matches this pattern (it behaves likes -> this.matcher(s).matches()
)
Say you have a regular expression verifying phone numbers, but it doesn't contain ^
and $
to mark begin and end of line.
Then the following may not do what you want it to:
prospectivePhoneNumbers.stream()
.filter(phoneNumberPattern.asPredicate())
.forEach(this::robocall);
Did you spot the error?
A string like "y u want numberz? +1-202-456-1414"
would pass the filter because it contains a valid phone number.
Pattern::asMatchPredicate
, on the other hand, would not have let it pass because the string, in its entirety, doesn't match the pattern.
▚Reflection
Here are all eleven-something gems at a glance - see if you still remember what each of them does. If you do, you passed. 😉
- on
String
:Stream<String> lines()
String strip()
String stripLeading()
String stripTrailing()
boolean isBlank()
String repeat(int)
- on
Path
:static Path of(String, String...)
static Path of(URI)
- on
Files
:String readString(Path) throws IOException
Path writeString(Path, CharSequence, OpenOption...) throws IOException
Path writeString(Path, CharSequence, Charset, OpenOption...) throws IOException
- on
InputStream
:static InputStream nullInputStream()
- on
OutputStream
:static OutputStream nullOutputStream()
- on
Reader
:static Reader nullReader()
- on
Writer
:static Writer nullWriter()
- on
Collection
:T[] toArray(IntFunction<T[]>)
- on
Optional
:boolean isEmpty()
- on
Predicate
:static Predicate<T> not(Predicate<T>)
- on
Pattern
:Predicate<String> asMatchPredicate()
And that's beyond the reactive HTTP/2 API and single-source-file execution. Have fun with Java 11!