Abstract: What do you do when an object cannot be properly constructed? In this newsletter, we look at a few options that are used and discuss what would be best. Based on the experiences of writing the Sun Certified Programmer for Java 5 exam.
Welcome to the 120th edition of The Java(tm) Specialists' Newsletter, this time sent from a cold but beautiful village of Hinxton in England. This week I am presenting two Design Patterns Courses at the European Bioinformatics Institute close to Cambridge in the UK. We are having an absolute blast, with stimulating discussions around design and Java.
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
Last month I wrote the latest Java Programmer Certification examination, version 5.0. An improvement over 1.1, even though some of the questions were still obscure. Hardly any parrot-style memorization was needed, but you did need an excellent understanding of Java 5. Some tips for the Sun Certified Programmer for the Java 2 Platform, Standard Edition 5.0:
I may not reveal any questions, or give hints of what may come up. You can get that information from Sun's website.
Now onto the topic of the newsletter, throwing exceptions from constructors.
What is the best way to deal with objects that cannot be properly instantiated? Here are some suggestions, which I will explore in more detail:
Let's deal with each suggestion.
This, believe it or not, is the most common approach in practice.
Input parameters are not adequately checked to ensure that they are within specification. As a result, the code fails later, rather than immediately. You want to know about errors as soon as possible. The sooner you see the problem, the easier it is to understand what caused it.
With this approach, completely innocent code experiences spurious values or runtime exceptions.
So, whilst this is the most undesirable of all approaches, it is the most common. I suggest you look in your code to see where it is possible to construct objects that actually should never see the light of day.
This tells the client that is constructing the object that something bad may happen when you try to make it. Examples are java.net.Socket and java.io.FileInputStream. In the first case, you might try to open a socket to an invalid address, in the second, the file might not be found.
Whilst this approach is not bad, it should only be used for situations that are beyond the control of the client using your objects.
It is debatable whether FileNotFoundException should be thrown by the constructor of FileInputStream. Ideally the client should first verify that the file exists before making an instance of it. However, since the operating system is beyond the control of Java, you cannot guarantee atomicity.
This is in my opinion usually the best approach. It expresses to the user accurately what the problem is - he presented an incorrect argument to the constructor.
To be fair to the user, document in your JavaDoc that you will throw the IllegalArgumentException, together with the conditions under which it will be thrown.
public class Person { private final String name; private final int age; private static final int MAXIMUM_AGE = 150; /** * Person constructor representing a natural * person. Name may not be null. Age must be * non-negative and less than MAXIMUM_AGE. * * @throws IllegalArgumentException if name is * null or if age is out of range. */ public Person(String name, int age) { this.name = name; this.age = age; if (this.age < 0 || this.age > MAXIMUM_AGE) { throw new IllegalArgumentException( "age out of range: " + this.age + " expected range 0 <= age < " + MAXIMUM_AGE); } if (this.name == null) { throw new IllegalArgumentException( "name is null"); } } }
NullPointerException was the first bug that I had to fix in some
"legacy" code, back in 1997. I usually equate
"NullPointerException" with bug. Not a bug in the client
code, but rather in the library that I am using. Examples of
code that throws NullPointerException deliberately includes the
Hashtable's put() method. The old Hashtable was written to not
handle null
values. This was corrected in the
java.util.HashMap.
import java.util.*; public class HashTableTest { public static void main(String[] args) { System.out.println("HashMap test"); HashMap hm = new HashMap(); hm.put(null, "hello"); System.out.println("Hashtable test"); Hashtable hs = new Hashtable(); hs.put(null, "hello"); } }
If you decide to go this route, make sure to document this fact to the user. Yes, no one reads comments, but at least you will be covered. You can point to the javadoc and say: "Didn't you read my comment?"
This is probably the worst way to signal to your user that
you could not construct the object due to the parameters he
sent you. An AssertionError
should only
be thrown in places where it cannot happen. If you see this
Error, you know that something completely strange has occurred.
For example, you have managed to divide an int by zero.
Another bad idea is putting Java 1.4 assertions into your code. You can switch them on and off at runtime, but the problem I have with that approach is that you may end up with a bad object, or you may not.
To summarise, in my opinion, the most correct approach is to
be strict in your constructors and throw
IllegalArgumentException
or
IllegalStateException
if you encounter something
you do not like.
Right, off to bed to catch up on some beauty sleep. Tomorrow we continue with the Adapter pattern and will discuss whether the MouseAdapter is an Object Adapter or a Class Adapter. It was quite fun today showing my class how to build dynamic proxies.
Kind regards
Heinz
We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.