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
putIfAbsent
Map<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
getOrDefault
Map<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
compute
Map<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
merge
Map<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.ofNullable
instead: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
, orflatMap
over unwrappingOptional
s using the terminal operations (e.g.ifPresent
,orElseGet
,orElse
,isPresent
). In the FP world,Optional
is 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
get
method onOptional
s. Reason:Optional<String> nullOptional = Optional.ofNullable(null); nullOptional.get(); // BOOM! NoSuchElementException: No value present!
DUH! That defeats the purpose of having
Optional
in the first place, doesn’t it? -
BONUS: if you use Spring data, the new
Repository
interface now usesOptional
. Upgrade soon if you’re still using the old version ‘cozOptional
is 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
findByName
only 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,
Optional
is 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
readLine
andclose
throw anException
. ↩ -
One recommendation is: http://www.baeldung.com/java-8-date-time-intro ↩