{{announcement.body}}
{{announcement.title}}

Another 3 Techniques for Writing Better Java

DZone 's Guide to

Another 3 Techniques for Writing Better Java

We will delve into three often-overlooked aspects of the Java language — valueOf, instanceOf, and exceptions.

· Java Zone ·
Free Resource

Learning the fundamentals of a programming language such as Java is an essential part of becoming a good programmer, but it is the small details that allow us to progress from good programmers to great craftsmen. Just like a woodworker understands the nuances of his chisel and router, and a professional fighter understands the intricacies of balance and leverage, we must understand the small facets that provide the most significant results.

You may also like: 4 Techniques for Writing Better Java

In this entry in the Techniques for Writing Better Java series, we will delve into three often-overlooked aspects of the Java language. First, we will take a look at the valueOf methods provided by the box primitive types and how to avoid these methods whenever possible. Next, we will follow the same train of thought and explore the instanceof keyword and how to avoid abusing this feature.

Lastly, we will look at when and where to throw exceptions for maximum effectiveness and how throwing an exception in the correct place can make the difference between a well-designed class and a debugging nightmare.

The interested reader is encouraged to check out the other articles in this series to learn 12 more ways to utilize the Java language to solve problems more effectively:

1. Avoid valueOf When Possible

One of the most significant benefits of strongly-typed languages like Java is that the compiler can enforce our intentions at compile-time. By applying a type to every piece of data, we make an explicit statement about the nature of that data.

For example, if we define a variable as having a type of int, we state that the variable cannot be larger than 231 - 1 and cannot be less -231. With the introduction of Object-Oriented Programming (OOP), we can define new natures by creating classes and instantiating objects of that class. For example, we can define an Address class and instantiate a variable with a specific Address state:


Java
x
20
 
1
public class Address {
2
  
3
    private final String name;
4
    private final String street;
5
  
6
    public Address(String name, String street) {
7
        this.name = name;
8
        this.street = street;
9
    }
10
  
11
    public String getName() {
12
        return name;
13
    }
14
  
15
    public String getStreet() {
16
        return street;
17
    }
18
}
19
 
          
20
Address someAddress = new Address("John Doe", "117 Spartan Way");


This strong typing is the foundation of core OOP concepts, such as polymorphism and dynamic dispatch. These concepts come together to produce an application that explicitly states our intentions before Java executes the program. While strong typing can be tedious in many contexts, in many cases, it allows us to know whether our intentions are logically sound before deploying the application. For example, if we try to pass an int value as a name or street to when instantiating our Address object (i.e., new Address(1, 2)), the Java compiler will complain:

Plain Text
xxxxxxxxxx
1
 
1
error: incompatible types: int cannot be converted to String
2
        Address someAddress = new Address(1, 2);


We intended to have a String represent our name and street values, but we instead provided an int. Since the compiler cannot treat an int as a String (i.e., int does not have at least the behavior of a String), the compiler throws an error and refuses to compile our application.

This type safety is an essential tool when creating larger programmers, where hundreds and thousands of classes interact with one another and complete substantially complex tasks through their relationships.

Even though Java is a strongly-typed language, there are still ways to circumvent this type-checking. One of the most common ways is the use of String objects to represent all data. For example, it is not uncommon to see the following JavaScript Object Notation (JSON) data sent as the body of a Representational State Transfer (REST) request or response:

JSON
xxxxxxxxxx
1
 
1
{
2
    "name": "John Doe",
3
    "accountValue": "100"
4
}


At first, it may appear evident that the accountValue field is an int value, or maybe even a long, but there is a much more dubious problem here. While the value we see in this response is a whole number, it could be any String value. For example, nothing stops this body from being:

Java
xxxxxxxxxx
1
 
1
{
2
    "name": "John Doe",
3
    "accountValue": "unknown"
4
}


This now becomes a much trickier parsing problem. The root of our issue is: What values can accountValue take on? From a typing perspective, it can be any value represented by the String class. In practice, we know that this value should be a whole number, but semantically, there is no guarantee that it will be.

This problem becomes much worse as we pass on the decision-making about the nature of accountValue to the rest of our application. For example, we can use a simple Plain Old Java Object (POJO) to deserialize this JSON:

Java
xxxxxxxxxx
1
11
 
1
public class Account {
2
  
3
    private String name;
4
    private String accountValue;
5
  
6
    public String getAccountValue() {
7
        return accountValue;
8
    }
9
  
10
    // ...getters & setters...
11
}


When another class in our application calls Account#getAccountValue, a String is returned. This String says nothing about the nature of accountValue (e.g., what its maximum or minimum can be or is it higher or lower, numerically than another account value). One quick fix for this issue is to convert the String immediately into an int or long (in this case, we will use long due to its greater precision):

Java
xxxxxxxxxx
1
11
 
1
public class Account {
2
  
3
    private String name;
4
    private String accountValue;
5
  
6
    public long getAccountValueAsLong() {
7
        return Long.valueOf(accountValue);
8
    }
9
  
10
    // ...getters & setters...
11
}


Using this approach, we encapsulate the accountValue field and hide its exact representation. From the outside, another class would think that accountValue is a long, since getAccountValueAsLong returns a long. This is a good use of OOP, but it only defers the problem. We know that accountValue should only be a long, but that does not stop it from being any String value.

Additionally, since the actual accountValue field in our JSON is a String, we still need to provide a getAccountValue that returns a String, and we must supply setAccountValue a String for deserialization to occur (without requiring other trickery).

For example, if accountValue were set to unknown in our JSON, calling Long.valueOf("unknown") would result in the following error:

Plain Text
xxxxxxxxxx
1
 
1
Exception in thread "main" java.lang.NumberFormatException: For input string: "unknown"
2
        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
3
        at java.lang.Long.parseLong(Long.java:589)
4
        at java.lang.Long.valueOf(Long.java:803)
5
        at Main.main(Main.java:5)


It is crucial to note that this exception occurs at runtime, not compile-time: The compiler did not have enough information during compilation to deduce that the value provided to Long.valueOf was not going to be a String that it could safely convert to a long. Instead, during execution, an exception was thrown when "unknown" was passed to Long.valueOf, causing our application to abruptly exit.

Due to our decision to represent accountValue as a String, we have now delayed our type checking from compile-time to runtime. Instead of allowing the compiler to perform static type-checking analysis on the source code of our program, we have instead deferred this check to runtime, where Long.valueOf is now responsible for implementing the type-checking.

Even if we attempt to catch this NumberFormatException in our getAccountValue method, we are now responsible for deciding on what to do when we encounter a number that is not a convertible to a long. We may be tempted to think that this is not possible since everyone must surely know that an account value is a number.

We would be wrong for thinking that, though, since nowhere in our JSON or even in our code is it documented that a long is expected. The type of our accountValue field is a String, and nothing stops a user that is unfamiliar with our application from rightly setting accountValue to a String.

Instead, we should change our JSON to properly represent our intent:

JSON
xxxxxxxxxx
1
 
1
{
2
    "name": "John Doe",
3
    "accountValue": 100
4
}


This allows us to correctly represent accountValue in our Account class:

Java
xxxxxxxxxx
1
 
1
public class Account {
2
    
3
    private String name;
4
    private long accountValue;
5
  
6
    // ...getters & setters...
7
}


When other classes in our application now access an Account object, they know that the accountValue will be a valid long. This allows them to make proper decisions based on the nature of a long. For example, another class could easily determine if accountValue is negative by comparing the value to 0: getAccountValue() < 0.

There are cases where valueOf method calls (such as Long.valueOf) are required. If the JSON representation were out of our control, we would have no choice but to treat accountValue as a String. In this case, we should immediately convert accountValue to a long and ensure that all other classes interact with the value as a long, not a String. One approach is to wrap the parsed account:

Java
xxxxxxxxxx
1
35
 
1
public class AccountState {
2
  
3
    private String name;
4
    private String accountValue;
5
   
6
    // ...getters & setters...
7
}
8
 
          
9
public class Account {
10
  
11
    private String name;
12
    private long accountValue;
13
  
14
    public Account(AccountState state) {
15
        this.name = state.getName();
16
        this.accountValue = extractAccountValue(state.getAccountValue());
17
    }
18
  
19
    private static long extractAccountValue(String value) {
20
        
21
        try {
22
            return Long.valueOf(value);
23
        }
24
        catch (NumberFormatException e) {
25
            return 0;
26
        }
27
    }
28
  
29
    // ...getters & setters...
30
}
31
 
          
32
AccountState state = // ...parse JSON into AccountState object
33
Account account = new Account(state);
34
 
          
35
account.getAccountValue() > 0;


Notice, though, we have to make a conscious decision about what value to set accountValue to when a non-long is provided, even though one should never be provided in the first place.

Therefore, when possible, we should avoid using valueOf methods that convert a String to some primitive value, including:

  • Integer.valueOf
  • Long.valueOf
  • Float.valueOf
  • Double.valueOf
  • Boolean.valueOf

Their inclusion in an application should be considered a code smell and is indicative that we have a String that should be represented by another data type.

2. Avoid instanceof When Possible

Similar to valueOf, the instanceof keyword provides an opportunity to circumvent the type-checking system of the Java compiler. While there are cases (especially when working with low-level code or when using reflection) that instanceof may be necessary, it should be treated as a code smell. It is likely a sign that we are skipping strict type-checking (and therefore losing the benefits of the Java type-checking system).

In many cases, instanceof is used to safely convert an object of a known supertype into an object of the desired subtype (called downcasting). This downcasting is common when implementing the equals method in a custom class:

Java
xxxxxxxxxx
1
19
 
1
public class Foo {
2
    
3
    private int value;
4
    
5
    @Override
6
    public boolean equals(Object obj) {
7
        
8
        if (this == obj) {
9
            return true;
10
        }
11
        else if (!(obj instanceof Foo)) {
12
            return false;
13
        }
14
        else {
15
            Foo other = (Foo) obj;
16
            return value == other.value;
17
        }
18
    }
19
}


By first checking to see if obj is an instance of the Foo class (i.e., obj instanceof Foo), we ensure that we are downcasting an actual Foo object to Foo. This is known as a safe, or checked, downcast. An unchecked downcast occurs if we do not check for the implementation type of an object before casting that object to that type:

Java
xxxxxxxxxx
1
17
 
1
public interface Vehicle {}
2
 
          
3
public class Boat implements Vehicle {}
4
 
          
5
public class Truck implements Vehicle {}
6
 
          
7
public class Foo {
8
    
9
    public void doSomething(Vehicle vehicle) {
10
        Truck truck = (Truck) vehicle;
11
        System.out.println(truck);
12
    }
13
}
14
 
          
15
Foo foo = new Foo();
16
Vehicle vehicle = new Truck();
17
foo.doSomething(vehicle);


We know that this cast is safe because we know a priori that we are providing doSomething with an object whose implementation type is Truck. We could, though, provide a Boat object instead:

Java
xxxxxxxxxx
1
 
1
Foo foo = new Foo();
2
Vehicle vehicle = new Boat();
3
foo.doSomething(vehicle);


Doing so results in the following error:

Java
xxxxxxxxxx
1
 
1
Exception in thread "main" java.lang.ClassCastException: class Boat cannot be cast to class Truck (Boat and Truck are in unnamed module of loader 'app')
2
    at Foo.doSomething(Application.java:16)
3
    at Application.main(Application.java:29)


Note that this is a runtime exception since the compiler cannot deduce what the type of vehicle will be until runtime. In our case, we know the implementation type because we have statically set it, but there may be times when the implementation type cannot be determined at compile time:

Java
xxxxxxxxxx
1
18
 
1
public class Bar {
2
  
3
    public Vehicle createVehicle() {
4
       
5
        int random = // randomly select 0 or 1
6
          
7
        if (random == 0) {
8
            return new Truck();
9
        }
10
        else {
11
            return new Boat();
12
        }
13
    }
14
}
15
 
          
16
Foo foo = new Foo();
17
Bar bar = new Bar();
18
foo.doSomething(bar.createVehicle());


In this case, the implementation type is determined randomly based on some criteria that can only be known at runtime (e.g., user input or a random number generator). Therefore, this checking must be deferred to runtime, where an exception is thrown if an unsafe downcast is performed. This issue is so common that Java even provides a warning when performing unsafe downcasts using generics (whose runtime generic type cannot be determined due to Java's use of unreified generic types):

Java
xxxxxxxxxx
1
 
1
public class Foo {
2
  
3
    public void doSomething(List<?> list) {
4
        List<Foo> foos = (List<Foo>) list;
5
    }
6
}


In this case, the (List<Foo>) list downcast will have the following warning:

Plain Text
xxxxxxxxxx
1
 
1
Type safety: Unchecked cast from List<capture#1-of ?> to List<Foo>


Being that downcasting can cause problems, it is always a sound idea to use a checked downcast through instanceof whenever possible. Whatsmore, we should avoid the use of instanceof altogether. In many cases, instanceof calls are used as an alternative to proper polymorphism.

For example, the following is a common use of instanceof:

Java
xxxxxxxxxx
1
30
 
1
public interface Vehicle {}
2
 
          
3
public class Boat implements Vehicle {
4
  
5
    public void engagePropeller() {
6
        // ...
7
    }
8
}
9
 
          
10
public class Truck implements Vehicle {
11
  
12
    public void engageAxel() {
13
        // ...
14
    }
15
}
16
 
          
17
public class VehicleDriver {
18
    
19
    public void drive(Vehicle vehicle) {
20
 
          
21
        if (vehicle instanceof Boat) {
22
            Boat boat = (Boat) vehicle;
23
            boat.engagePropeller();
24
        }
25
        else if (vehicle instanceof Truck) {
26
            Truck truck = (Truck) vehicle;
27
            truck.engageAxel();
28
        }
29
    }
30
}


In essence, we are attempting to treat each Vehicle implementation type (i.e., Boat and Truck) differently. This is precisely the use case for polymorphism. Instead of checking the implementation type of the Vehicle through instanceof and performing a downcast, we can create a new method in the Vehicle interface called drive and have each implementation type execute the proper method.

Java
xxxxxxxxxx
1
34
 
1
public interface Vehicle {
2
    public void drive();
3
}
4
 
          
5
public class Boat implements Vehicle {
6
  
7
    @Override
8
    public void drive() {
9
        engagePropeller(); 
10
    }
11
  
12
    public void engagePropeller() {
13
        // ...
14
    }
15
}
16
 
          
17
public class Truck implements Vehicle {
18
  
19
    @Override
20
    public void drive() {
21
        engageAxel(); 
22
    }
23
  
24
    public void engageAxel() {
25
        // ...
26
    }
27
}
28
 
          
29
public class VehicleDriver {
30
    
31
    public void drive(Vehicle vehicle) {
32
        vehicle.drive();
33
    }
34
}


Although there may be a few specific cases where instanceof is necessary (such as implementing the equals method), in general, instanceof checks and unsafe downcasts should be avoided. We should instead use polymorphism to vary behavior based on the implementation type of an object.

3. Throw Exceptions Early

When error checking must occur at runtime, it is best to throw exceptions as early as possible. In large, complex environments where objects are instantiated in one thread and called in another, improper exception handling can cause debugging nightmares. In many cases, the results of improper exception handling can be subtle and insidious.

For example, suppose we have the following class:

Java
xxxxxxxxxx
1
12
 
1
public class SecurityManager {
2
  
3
    private final SecurityTransactionRepository repo;
4
  
5
    public SecurityManager(SecurityTransactionRepository repo) {
6
        this.repo = repo;
7
    }
8
  
9
    public Optional<SecurityTransaction> findTransactionById(long id) {
10
        return repo.findById(id);
11
    }
12
}


This class seems simple enough: It takes in a SecurityTransactionRepository object in its constructor and defers the lookup of SecurityTransaction objects to this repository. We can execute findTransactionsById by merely supplying the correct object to the SecurityManager constructor and calling the findTransactionById method:

Java
xxxxxxxxxx
1
 
1
SecurityTransactionRepository repo = // create repository...
2
SecurityManager manager = new SecurityManager(repo);
3
 
          
4
Optional<SecurityTransaction> transaction = manager.findTransactionById(1);


Problems start to occur when things do no go as planned. For example, what if our repo object were null? In this case, our call sequence will amount to the following:

Java
xxxxxxxxxx
1
 
1
SecurityManager manager = new SecurityManager(null);
2
 
          
3
Optional<SecurityTransaction> transaction = manager.findTransactionById(1);


If we try to execute this code, we will see a stack trace like the following:

Java
xxxxxxxxxx
1
 
1
Exception in thread "main" java.lang.NullPointerException
2
    at SecurityManager.findTransactionById(Application.java:25)
3
    at Application.main(Application.java:33)


This NullPointerException (NPE) occurs because our SecurityTransactionRepository object that we passed into the SecurityManager construct is null. Once the findTransactionById method attempts to defer to the null SecurityTransactionRepository object, the NullPointerException is thrown. A simple way to keep this exception from occurring is to check for a null repo object:

Java
xxxxxxxxxx
1
18
 
1
public class SecurityManager {
2
  
3
    private final SecurityTransactionRepository repo;
4
  
5
    public SecurityManager(SecurityTransactionRepository repo) {
6
        this.repo = repo;
7
    }
8
  
9
    public Optional<SecurityTransaction> findTransactionById(long id) {
10
        
11
        if (repo == null) {
12
            return Optional.empty();
13
        }
14
        else {
15
            return repo.findById(id);
16
        }
17
    }
18
}


If we re-execute our application with a null SecurityTransactionRepository object passed to the SecurityManager constructor, our call to the findTransactionById method now results in an empty Optional object. While we have resolved the NPE issue, we have introduced a much more subtle problem that can come back to harm us.

Suppose that our SecurityManager object is created in one place, and the findTransactionById method is called in another place. Also, suppose that the findTransactionById method is called much later, maybe minutes or even hours after the SecurityManager object was constructed.

This is a common occurrence in Spring applications and OSGi applications, where beans or services are created and wired into entirely different parts of the appliance. In the case of OSGi, a service can be created in one bundle and injected into a wholly different bundle as a service.

In this case, if we attempt to execute our findTransactionById method with a null SecurityTransactionRepository object, an empty Optional will be returned. If we try to debug why an expected SecurityTransaction object was not found, we see that it is either because no such SecurityTransaction exists (but we expect it to exist) or because the SecurityTransactionRepository is null.

Once we discover that the SecurityTransactionRepository is null, we know that it must have been passed into the SecurityManager constructor as null (since it is qualified as final).

While this troubleshooting process is pretty standard, our debugging hits a significant roadblock at this point. We need to find where this SecurityManager object is being constructed with a null constructor argument. If this instantiation process occurs in another project or bundle, or if it occurred minutes, hours, or even days ago, we now have to search the entire application to find the root cause.

We can add another wrench into our debug process if more than one place instantiates SecurityManager objects. In this case, which one instantiated our SecurityManager with a null SecurityTransactionRepository? All of these problems stem from a simple fact: We are not handling the possibility of a null SecurityTransactionRepository in a correct location.

We do not want our SecurityManager to be created with a null SecurityTransactionRepository, and therefore, we should explicitly state that in the constructor of the SecurityManager. To do this, we can use the Objects#requireNonNull method, which throws an NPE if the argument passed to it is null or returns the argument passed to it if the argument is not null.

Java
xxxxxxxxxx
1
12
 
1
public class SecurityManager {
2
  
3
    private final SecurityTransactionRepository repo;
4
  
5
    public SecurityManager(SecurityTransactionRepository repo) {
6
        this.repo = Objects.requireNonNull(repo);
7
    }
8
  
9
    public Optional<SecurityTransaction> findTransactionById(long id) {
10
        return repo.findById(id);
11
    }
12
}


If we rerun our application with a null SecurityTransactionRepository, we will see an NPE get thrown, but this time, the exception originates from the constructor of the SecurityManager:

Java
xxxxxxxxxx
1
 
1
this.repo = Objects.requireNonNull(repo);


This ensures that if our application starts up and a null SecurityTransactionManager object is supplied to the constructor of a SecurityManager object, the application will throw an error from the source of the problem. Using the stack trace that is reported, we can find the caller of the SecurityManager constructor:

Plain Text
xxxxxxxxxx
1
 
1
Exception in thread "main" java.lang.NullPointerException
2
    at java.base/java.util.Objects.requireNonNull(Objects.java:221)
3
    at SecurityManager.<init>(Application.java:22)
4
    at Application.main(Application.java:32)


In the case of the stack trace above:

Plain Text
xxxxxxxxxx
1
 
1
Application.main(Application.java:32)


This concept of moving the null-check into the constructor is closely related to the concept of Design by Contract (DBC). In this technique, every method has the following aspects:

  1. Preconditions: Things that must be true for the method to be called
  2. Postconditions: Things that must be true once the method completes execution
  3. Invariants: Things that must be true throughout the life of an object

For example, a precondition might be that some data is available for use, and a postcondition may be that the result of a method, if multiplied by itself, must be equal to the argument supplied to the method (i.e., square root function). While DBC can be over-formalized, we can use the concept of an invariant to express our null-check. When we check that the SecurityTransactionRepository object supplied to our SecurityManager constructor is not null, we know that if the SecurityManager object is successfully constructed, the SecurityTransactionRepository will never be null.

Since the SecurityTransactionManager is marked as final, the constructor is the only mechanism that can set its value. If the value supplied to the constructor is non-null, then we know that the repo field will be non-null for the life of the object. Therefore, we never need to check again whether the repo field is null: We assume that it is non-null. This amounts to an invariant in our SecurityManager class. Thus, it would be a waste of code to check if the repo object is null inside our findTransactionById definition. Instead, we assume it is not null at that point in the class.

Generally, it is best to throw exceptions as soon as possible and at the point in which the error occurs. Deferring an exception to a later point will muddle the debugging process and waste the time of developers trying to find the root cause of issues. In the case of our SecurityManager, the error is that a null SecurityTransactionManager was passed to its constructor, and therefore, the NPE should be thrown in the constructor. If we expect that the SecurityTransactionRepository can be null (and thus throwing an NPE in the constructor would be incorrect), we should explicitly state this assumption using a mechanism such as an Optional or the Null Object pattern.

Conclusion

In this article, we looked at the valueOf methods included in each of the boxed primitive classes and how to avoid creating pitfalls with their use. Next, we looked at the instanceof method and how to avoid its use unless completely necessary.

Lastly, we looked at how throwing exceptions at the right point in our code can make the difference between well-designed classes and subtle snares. While these details may become buried in the day-to-day work of writing code in Java, it is these little details that make the difference between being a good programmer and a great craftsman.


Further Reading

7 Tips to Write Better Java Code You Should Know

A Systematic Approach to Write Better Code With OOP Concepts

10 Tips to Become a Better Java Developer

Topics:
java ,tutorial ,exceptions ,object-oriented ,valueof ,instanceof

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}