Another 3 Techniques for Writing Better Java
Another 3 Techniques for Writing Better Java
We will delve into three often-overlooked aspects of the Java language — valueOf, instanceOf, and exceptions.
Join the DZone community and get the full member experience.Join For Free
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:
- 4 Techniques for Writing Better Java
- 4 More Techniques for Writing Better Java
- Yet 4 More Techniques for Writing Better Java
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
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
street to when instantiating our
Address object (i.e.,
new Address(1, 2)), the Java compiler will complain:
We intended to have a
String represent our
street values, but we instead provided an
int. Since the compiler cannot treat an
int as a
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
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:
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:
When another class in our application calls
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
long (in this case, we will use
long due to its greater precision):
Using this approach, we encapsulate the
accountValue field and hide its exact representation. From the outside, another class would think that
accountValue is a
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
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
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:
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
Instead, we should change our JSON to properly represent our intent:
This allows us to correctly represent
accountValue in our
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
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:
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:
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.
instanceof When Possible
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:
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:
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:
Doing so results in the following error:
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:
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):
In this case, the (
List<Foo>) list downcast will have the following warning:
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
In essence, we are attempting to treat each
Vehicle implementation type (i.e.,
Truck) differently. This is precisely the use case for polymorphism. Instead of checking the implementation type of the
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.
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:
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
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:
If we try to execute this code, we will see a stack trace like the following:
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
SecurityTransactionRepository object, the
NullPointerException is thrown. A simple way to keep this exception from occurring is to check for a
If we re-execute our application with a
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
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
Once we discover that the
null, we know that it must have been passed into the
SecurityManager constructor as
null (since it is qualified as
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
SecurityTransactionRepository? All of these problems stem from a simple fact: We are not handling the possibility of a
SecurityTransactionRepository in a correct location.
We do not want our
SecurityManager to be created with a
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
If we rerun our application with a
SecurityTransactionRepository, we will see an NPE get thrown, but this time, the exception originates from the constructor of the
This ensures that if our application starts up and a
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
In the case of the stack trace above:
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:
- Preconditions: Things that must be true for the method to be called
- Postconditions: Things that must be true once the method completes execution
- 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
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
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.
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.
Opinions expressed by DZone contributors are their own.