Introduction
So, you know Java 8. It has revolutionised Java by introducing Lamda & Streams, the holy grails of functional programming in Java. While Lambda, Streams and FP are great 1, Java 7, 8, & 9 are not only about them.
Yes, learning Streams probably gives you the most bang for the buck, but in this post, let’s revisit some other nifty tricks you can use in Java now that it’s in its 10th iteration:
1. Enhanced Map APIs (Java 8)
Instead of boring you to death by listing and explaining the new java.util.Map APIs, let me try to illustrate them using a small example. The Java docs have done a great job on documenting how to use them, by the way.
Suppose we want to build a frequency table of letter occurences in a String:
String str = "hello";
// Get Letter Frequency (Pre-Java 8)
Map<Character, Integer> freq0 = new HashMap<>();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (!freq0.containsKey(c)) {
freq0.put(c, 0);
}
freq0.put(c, freq0.get(c) + 1);
}
System.out.println(freq0);
// => {e=1, h=1, l=2, o=1}
With the new Map APIs, there are multiple way you can achieve the same.
-
Using
putIfAbsentMap<Character, Integer> freq1 = new HashMap<>(); for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); freq1.putIfAbsent(c, 0); freq1.put(c, freq1.get(c) + 1); } System.out.println(freq1); // => {e=1, h=1, l=2, o=1} -
Using
getOrDefaultMap<Character, Integer> freq2 = new HashMap<>(); for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); // instead of putting if absent, get a default if absent freq2.put(c, freq2.getOrDefault(c, 0) + 1); } System.out.println(freq2); // => {e=1, h=1, l=2, o=1} -
Using
computeMap<Character, Integer> freq3 = new HashMap<>(); for (int i = 0; i < str.length(); i++) { // use with a one-liner with lambda freq3.compute(str.charAt(i), (k, v) -> v == null ? 1 : v + 1); } System.out.println(freq3); // => {e=1, h=1, l=2, o=1} -
Using
mergeMap<Character, Integer> freq4 = new HashMap<>(); for (int i = 0; i < str.length(); i++) { // even terser freq4.merge(str.charAt(i), 1, Integer::sum); } System.out.println(freq4); // => {e=1, h=1, l=2, o=1}
Which one do I prefer? Honestly, I like the merge version the most because it is succinct and clear. However, not many developers might know what merge does and especially what the arguments are (remappingFunction what??!!). On the other hand, putIfAbsent & getOrDefault are quite self-explanatory. So, for the sake of code readability, I might still go Method 1 or Method 2.
Side Note: there is another variant of putIfAbsent called computeIfAbsent. The following StackOverflow thread sums up the difference well: StackOverflow - Difference between putIfAbsent and computeIfAbsent
2. Optional (Java 8)
NullPointerException! The billion dollar mistake in Computer Science. You can’t somehow get rid of them. Or can you? Java 8 has Optional now. So, instead of having APIs returning null, return Optional instead!
Optional is quite straightforward to use. Nevertheless, there are some caveats:
-
Don’t use
Optional.of, useOptional.ofNullableinstead:String something = null; // BAD: Will throw NPE! Optional<String> optionalOfNull = Optional.of(something); // Good: Optional<String> optionalOfNullableNull = Optional.ofNullable(something); -
Favor the use of
map,filter, orflatMapover unwrappingOptionals using the terminal operations (e.g.ifPresent,orElseGet,orElse,isPresent). In the FP world,Optionalis considered a monad and we should strive to only flatten a monad at the boundary of our application. To illustrate:enum RecordStatus { PENDING, ACTIVE, EXPIRED } class CreditRating { private double rating; private RecordStatus status; private String customerId; // getter/ setters omitted for brevity sake } interface CreditRatingService { Optional<CreditRatingService> getCreditRating(String customerId); } // ... // How to use Optional<Double> creditRatingValue = creditRatingService .getCreditRating("C012345") .filter(creditRating -> creditRating.getStatus() == RecordStatus.ACTIVE) .map(CreditRating::getRating); -
Don’t use the
getmethod onOptionals. Reason:Optional<String> nullOptional = Optional.ofNullable(null); nullOptional.get(); // BOOM! NoSuchElementException: No value present!DUH! That defeats the purpose of having
Optionalin the first place, doesn’t it? -
BONUS: if you use Spring data, the new
Repositoryinterface now usesOptional. Upgrade soon if you’re still using the old version ‘cozOptionalis simply safer. For example:public interface ProductRepository extends CrudRepository<Product, Long> { Optional<Product> findByName(@Param("name") String name); // Optional<Product> findById(Long id); }As a side note, be aware, the above
findByNameonly expects 1 result (exception will be thrown otherwise). If it returns more than 1 results, you have to use the good oldCollection:Collection<Product> findByName(@Param("name") String name); -
Lastly,
Optionalis rarely apt to be used in method arguments and instance variables. See: StackOverflow - Should java 8 getters return optional type
3. Collection Factory Methods (Java 9)
I have covered this in my other blog post Java 9 Notes; nonetheless, to reiterate, we now have:
List<Integer> ints = List.of(1,2,3);
Set.of("first", "second");
// cannot put duplicate
// cannot put null
Map.of("Key1",1,"Key2",2);
// alternate key and value
Map.ofEntries(Map.entry("Key1", true), Map.entry("Key2",false));
// cannot put duplicate key
And as a reminder, the generated collections are immutable.
4. Try-with-Resource (Java 7)
Well this feature has been there for a while. Nevertheless, I still see a lot of code not utilizing it! It’s probably because people still mostly copy paste the pre Java 7 way of reading files from StackOverflow. I don’t blame them :P.
Basically, this verbose and buggy2 code snippet below:
static String readFirstLineFromFileWithFinallyBlock(String path)
throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
if (br != null) br.close();
}
}
can be re-written as:
static String readFirstLineFromFile(String path) throws IOException {
try (BufferedReader br =
new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
NB: Code snippets shamelessly copied from Oracle’s official documentation of this feature (see references below)
5. New DateTime API (Java 8)
It’s a shame that most people don’t use the latest Java 8 DateTime API. Instead, they still use the mostly deprecated Date class, clunky Calendar, or some still even advocate using joda-time. joda-time is definitely great, but it’s now obsolete considering built-in JDK already ships with equally elegant and powerful DateTime APIs. There are plenty of Java 8 Date time API tutorials on the web3, so I won’t waste my time rehashing them. The bottom line is, we should be aware and use this JSR 310.
Final Notes
The above is certainly a non-exhaustive list. It doesn’t include many useful enhancements in the concurrency utilities (e.g. CompletableFuture) and the obvious features which you might already be using such as Diamond operator (<>) for shorted generic instantiation and multicatch (catch (IOException|SQLException ex)) to reduce duplicated boilerplate code.
Hope you find this article useful! Comment below for any questions or suggestions!
References
- https://www.journaldev.com/2389/java-8-features-with-examples/amp#java8-core
- https://stackoverflow.com/questions/26327957/should-java-8-getters-return-optional-type/26328555#26328555
- https://stackoverflow.com/questions/48183999/what-is-the-difference-between-putifabsent-and-computeifabsent-in-java-8-map
- https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
Footnotes
-
Streams shouldn’t be overused though - see Effective Java 3rd Edition ↩
-
Why buggy? Think of what happens when both
readLineandclosethrow anException. ↩ -
One recommendation is: http://www.baeldung.com/java-8-date-time-intro ↩