Top 10 Java idioms I wish I'd learned earlier

本文最后更新于:2022年12月29日 早上

Hello guys, when your experience grow, your design, coding, refactoring and testing ability is the one which distinguish you from your competition. It’s quite possible that your experience grows but none of these skills grow because you are doing using them frequently in your day job. To be honest, you are not alone. Many people who works in big Investment banks like Citibank, Barclays Capital, UBS, or JP Morgan, spend more times fixing bugs and making config change, deployment and support then actually writing code from scratch or writing unit tests. While we will go with all those skills in coming articles, in this article, I am going to share popular Java coding idioms which can improve your coding skills.

Coding idioms are tried and tested way of writing code for a particular scenario. They are tested so they are bug free and by using them, you inherently rule out many corner cases and bugs which can occur if you write your own code. They are much like patterns and libraries for reusability but very low level.

One examples of idiom would include the most common way of writing an infinite loop (using where(true) rather than for(;;)).

The word ‘Idiom’ should be translated as ‘standard practice’. That is, if one were to look through a number of Java projects looking for the solution to a specific problem then the most common solution would be considered ‘Idiomatic”

So, if you want to improve your coding skills in Java, let’s take the first step and learn popular Java coding idioms

Here is a list of my favorite Java coding idioms which you can use to write better, cleaner, and robust code in Java:

1. Calling equals() on String literal or Known object

For a long time while writing Java code, I used to invoke equals method like below:

if(givenString.equals(“YES”)){
// do something.
}

It’s natural because it reads better but its not safe. You can prevent a potential NPE by calling equals() on String literal, if one object is happen to be Sring literal or on known object e.g.

“TRUE”.equals(givenBoolean)
“YES”.equals(givenString)

if you do reverse e.g. givenBoolean.equals(“YES”) then it will throw NullPointerException if givenBoolean is null, but if you follow this idiom then it will just return false without throwing any NPE. This is a much better code, its safe and robust. In fact this is one of the popular ways to avoid NullPointerException in Java.

2) Using entrySet to loop over HashMap

I used to loop over HashMap using key set as shown below :

Set keySet = map.keyset();

for(Key k : keySet){
value v = map.get(k);
print(k, v)
}

This does another lookup to get the value from Map, which could be O(n) in worst case. if you need both key and value, then its better to iterate over entry set rather than key set.

Entry entrySet = map.entrySet();

for(Entry e : entrySet){
Key k = e.getKey();
Value v = e.getValue();
}

This is more efficient because you are getting value directly from object, which is always O(1).

3) Using Enum as Singleton

I wish, I had know that we can write Singleton in just one line in Java as :

public enum Singleton{
INSTANCE;
}

It’s thread-safe, robust and Java guaranteed just one instance even in case of Serialization and Deserialization.

4) Using Arrays.asList() to initialize Collection or List.of(), Set.of()

Even If I know elements in advance, I used to initialize collection like this :

List listOfCurrencies = new ArrayList();
listOfCurrencies.add(“USD/AUD”);
listOfCurrencies.add(“USD/JPY”);
listOfCurrencies.add(“USD/INR”);

This is quite verbose, thankfully you can do all this in just one line by using this idiom, which take advantage and of Arrays.asList() and Collection’s copy constructor, as shown below :

List listOfPairs = new ArrayList(Arrays.asList(“USD/AUD”, “USD/JPY”, “USD/INR”);

Even though Arrays.asList returns a List, we need pass its output to ArrayList’s constructor because list returned by Arrays.asList() is of fixed length, you cannot add or remove elements there. BTW, its not just list but you can create Set or any other Collection as well e.g.

Set primes = new HashSet(Arrays.asList(2, 3, 5, 7);

And, from Java 9 onwards, you can use methods like List.of() and Set.of() to create a List and Set with values. They are actually better option because they return Immutable List and Set.

5. Checking wait() condition in loop

When I first started writing inter-thread communication code using wait(), notify() and notifyAll() method, I used if block to check if waiting condition is true or not, before calling wait() and notify() as shown below :

synchronized(queue) {
if(queue.isFull()){
queue.wait();
}
}

Thankfully, I didn’t face any issue but I realized my mistake when I read Effective Java Item of wait() and notify(), which states that you should check waiting condition in loop because it’s possible for threads to get spurious notification, and its also possible that before you do anything the waiting condition is imposed again. So correct idiom to call wait() and notify() is following :

synchronized(queue) {
while(queue.isFull()){
queue.wait();
}
}

6. Catching CloneNotSupportedException and returning SubClass instance

Even though Object cloning functionality of Java is heavily criticized for its poor implementation, if you have to implement clone() then following couple of best practices and using below idiom will help reducing pain :

public Course clone() {
Course c = null;
try {
c = (Course)super.clone();
} catch (CloneNotSupportedException e) {} // Won’t happen

return c;
}

This idiom leverages the fact that clone() will never throw CloneNotSupportedException, if a class implementers Cloneable interface. Returning Subtype is known as covariant method overriding and available from Java 5 but helps to reduce client side casting e.g. you client can now clone object without casting e.g.

Course javaBeginners = new Course(“Java”, 100, 10);
Course clone = javaBeginners.clone();

Earlier, even now with Date class, you have to explicitly cast the output of clone method as shown below :

Date d = new Date();
Date clone = (Date) d.clone();

7. Using interfaces wherever possible

Even though I have been programming from long time, I have yet to realize full potential of interfaces. When I started coding, I used to use concrete classes e.g. ArrayList, Vector and HashMap to define return type of method, variable types or method argument types, as shown below :

ArrayList listOfNumbers = new ArrayList();

public ArrayList getNumbers(){
return listOfNumbers;
}

public void setNumbers(ArrayList numbers){
listOfNumbers = numbers;
}

This is Ok, but its not flexible. You cannot pass another list to your methods, even though it is better than ArrayList and if tomorrow you need to switch to another implementation, you will have to change to all the places.

Instead of doing this, you should appropriate interface type e.g. if you need list i.e. ordered collection with duplicates then use java.util.List, if you need set i.e. unordered collection without duplicates then use java.util.Set and if you just need a container then use Collection. This gives flexibility to pass alternative implementation.

List listOfNumbers;

public List getNumberS(){
return listOfNumbers;
}

public void setNumbers(List listOfNumbers){
this.listOfNumbers = listOfNumbers;
}

If you want, you can even go one step further and use extends keyword in Generics as well for example you can define List as List<? extends Number> and then you can pass List or List to this method.

Top 10 Java idioms I wish I'd learned earlier

8. Using Iterator to traverse List

There are multiple ways to traverse or loop over a List in Java e.g. for loop with index, advanced for loop and Iterator. I used to use for loop with get() method as shown below :

for(int i =0; i<list.size; i++){
String name = list.get(i)
}

This works fine if you are iterating over ArrayList but given you are looping over List, its possible that List could be LinkedList or any other implementation, which might not support random access functionality e.g LinkedList. In that case time complexity of this loop will shoot up to N^2 becase get() is O(n) for LL. Using loop to go over List also has a disadvantage in terms of multi-threading e.g. CopyOnWriteArrayList - one thread changing list while another thread iterates over it using size() / get() leads to that IndexOutOfBoundsException.

On the other hand Iterator is the standard way or idiomatic way to traverse a List as shown below :

Iterator itr = list.iterator();
while(itr.hasNext()){
String name = itr.next();
}

It’s safe and also guard against unpredictable behavior.

9) Writing code using Dependency Injection in mind

It was not long ago, I used to write code like this :

public Game {

private HighScoreService service = HighScoreService.getInstance();

public showLeaderBoeard(){
List listOfTopPlayers = service.getLeaderBoard();
System.out.println(listOfTopPlayers);
}

}

This code looks quite familiar and many of us will pass it on code review but it’s not how you should write your modern Java code. This code has three main problems :

  1. Game class is tightly coupled with HighScoreService class, its not possible to test Game class in isolation. You must need HighScoreService class.

  2. Even if you create a HighScoreService class you cannot test Game reliably if your HighScoreService is making network connection, downloading data from servers etc. Problem is, you can not use a Mock instead of actual object here.

You can get rid of these issues by writing your Java class as POJO and using DI as below :

public Game {

private HighScoreService service;

public Game(HighScoreService svc){
this.service = svc;
}

public showLeaderBoeard(){
List listOfTopPlayers = service.getLeaderBoard();
System.out.println(listOfTopPlayers);
}

}

10. Closing streams in their own try block

I used to close streams, InputStream and OutputStream like this :

InputStream is = null;
OutputStream os = null;

try {
is = new FileInputStream(“application.json”)
os = new FileOutPutStream(“application.log”)
}catch (IOException io) {

}finally {
is.close();
os.close()
}

problems, if first stream will throw exception then close from second will never be called. You can read more about this pattern in my earlier article, right way to close Stream in Java.

That’s all about Java Idioms which can help you write better and more robust code. If you have been coding in Java for few years then most likely you already know these patterns but if you are just starting with Java or have 1 or 2 years of experience this idioms can help and open your mind with Java specific issues while writing code.

With new releases, these idioms may be replaced with better API methods like List.of() but knowing them still better than not knowing them. Also, if you know or follow any other Java coding idioms feel free to share with us on comments, I love to learn from knowledgeable readers.

I also plan to write second part of this article covering Java 8 idioms like using method reference instead of Lambdas etc. If you guys are interested, let me know in comments by saying yes, I would to see that article and any idiom you want to suggest.

References


Top 10 Java idioms I wish I'd learned earlier
https://baymax55.github.io/2022/12/29/java/Top 10 Java idioms I wish I'd learned earlier/
作者
baymax55
发布于
2022年12月29日
许可协议