visit
I work in the banking sector. There are conservative approaches - people prefer to use only technologies that have been tested for years. So we have a lot of codebases written on java 7 and 8. I have provided a couple of workshops speaking of new java features and decided to write an article to combine all valuable new features. I only want to talk about the API I started using myself and find it very helpful.
For each new java version, I will give the most valuable features. Let’s get started.
In JAVA 9, we got factory methods for collections. Now we can get the unmodifiable collection:
List.of(E... elements)
Set.of(E... elements)
Map.of(K k1, V v1, K k2, V v2, ...)
and Map.ofEntries(Entry<? extends K, ? extends V>... entries)
So now you can work with an immutable collection more easily without such ugly constructions, like before:
List<String> list = new ArrayList<>();
list.add("abc");
list.add("xyz");
Collections.unmodifiableList(list);
You can do it in one line:
List.of("abc","xyz");
Also, it gives us more safety because we increase the number of immutable lists in our program.
I use these methods in every my program and strongly recommend you to do so. The following helpful method you can find in this version of Java is:
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
I try to use Optional class in every place where I can get a null reference. So this method can help us avoid redundant if-else statements:
Optional<Email> mail = ...
if (mail.isPresent()) {
send(mail.get());
} else {
reportError();
}
You could rewrite it in more flexible manner:
opt.ifPresentOrElse(
mail -> send(mail),
() -> reportError());
With method
Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)
we can generate a new optional class if the base statement return empties optional:
Optional<String> optional = Optional.<String>ofNullable("first")
.or(() -> Optional.of("second"));
Also, we can generate a Stream from the Optional just-only call method
stream()
.Optional.of(obj).stream()
It’s much better than:
Optional.of(obj).map(Stream::of).orElse(Stream.empty())
Stream interface is similarly getting new methods. So, in Java 8, we had handy intermediate operations like limit and skip. But these operations do not give us enough flexibility to operate with the Stream. From the next release of Java, we have methods:
default Stream <T> takeWhile(Predicate<? super T> predicate)
default Stream<T> dropWhile(Predicate<? super T> predicate)
These methods accept predicate, so you can give some logic on limiting or skip elements, not just by the number of elements.
Method iterate also got variant with the predicate. Now, you can write some logic to stop generate objects:
IntStream
.iterate(1, i -> i < 16, i -> i + 1)
.forEach(System.out::println);
Instead of:
IntStream
.iterate(1, i -> i + 1)
.takeWhile(i -> i < 16)
.forEach(System.out::println);
So, we mentioned streams. Here I should talk about a new method from Java 16 - toList().
We have a list on which we need to perform some transformation and return a new one. You have probably seen this code many times:
collection.stream()
// operations
.collect(Collectors.toList());
In Java 16, this is possible with the new Stream.toList() method:
collection.stream()
// operations
.toList();
It gives us a more convenient code, and thanks to optimization, it has a little bit more performance rate than
collect(Collectors.toList())
.I think almost every program works with time. And sometimes, it is a big headache to handle the date and time in your program accurately. Java 8 made a big leap to make it more friendly, but some methods are absent. Java 9 gives us these methods in java.time package. Now you could use Duration class more productive. It got methods toDaysPart(), toHoursPart(), toMinutesPart(), toSecondsPart() etc. You can get “time parts” from the Duration class. There is an example:
Duration dur = Duration.between(now().minusDays(10).minusSeconds(567), now());
System.out.println(dur.toDaysPart()); // 10
System.out.println(dur.toMinutesPart()); // 9
And now we could even divide time by time!
Duration dur = Duration.of(3, ChronoUnit.HOURS);
Duration ans = dur.dividedBy(Duration.of(20, ChronoUnit.MINUTES));
System.out.println(ans); // 9
This method is a handy method when the time interval must be divided into segments.
In LocalDate, we got new method datesUntil.
This method returns a Stream of dates between two dates:
Stream<LocalDate> dates = LocalDate.of(2021, 3, 3).datesUntil(LocalDate.of(2021, 10, 9));
In the 11 version of java, we get a bunch of handful methods for strings.
The String.isBlank() method lets you know if the string is composed solely of whitespace:
System.out.println(" \t \n \r".isBlank()); // true
stripLeading(), stripTrailing(), and strip() methods remove the whitespace characters at the beginning of the string, at the end of the string, or from both ends:
String str = "\tHello, world!\t\n\r";
System.out.println(str.stripLeading()); // "Hello, world!\t\n\r"
System.out.println(str.stripTrailing()); // " \tHello, world!"
System.out.println(str.strip()); // "Hello, world!"
The repeat() method concatenates the string with itself n times:
System.out.println("Hello, world!".repeat(3));
Java 15 introduces blocks of text - string literals that can consist of one or more lines:
String str = """
Hello,
World!""";
This feature gives exceptional flexibility, it is not a new API, but I have to mention it because it significantly increases readability. For example, when making JDBC queries or during tests, we can specify the content of a multi-line query in text, and it will be good readable.
Also, in this version, the String class got a new method formatted().
So, instead of String.format we could write:
"Hi, %s!".formatted(user);
This method also works for text blocks.
In Java 11 interface got the default method not(). With Predicate.not() you can replace lambdas with negation with method references:
filter(str -> !str.isBlank())
And now using not():
filter(not(String::isBlank))
Code is more readable, and we don't use any new variables.
I really like it!
In the end, I want to tell you about the pattern-matching feature.
The purpose of this feature is to remove a lot of code that usually accompanies the instanceof statement:
if (cat instanceof Animal) {
Animal animal = (Animal) person;
animal.voice();
}
It is a widespread pattern in Java. In 99% percent of cases, we use conversion to type inside if statement.
With pattern matching, you can write it more shortly:
if (cat instanceof Animal animal) {
animal.voice();
}
You can do even more:
if (cat instanceof Animal animal && animal.hasVoice()) {
animal.voice();
}
So you can avoid redundant if statement in your code.
Thank you! Don't be afraid to use new versions of java. With each new version, it gives us lighter and better-looking code.