Let Eclipse Generate The Boring Code
Tuesday, April 10, 2007
When writing Java classes we often need a lot of boilerplate code that takes a lot of time, but isn't really very interesting to write. Fortunately good IDEs like Eclipse offer handy code generation features that can greatly reduce the time needed to write these classes.
In order to create classes that define a clean interface and properly hide implementation details, the class may need some rather boring code
like constructors, getters and setters and common methods like equals() and hashCode(). This is especially true for
business object classes that most of the time just have some fields defined on them.
When confronted with this kind of classes, some developers may be tempted to just make the fields public and be done with it. This is
never a good idea and luckily with most modern IDEs you can have your cake and eat it too. That is you can let the IDE generate most of the code
for you, saving you valuable time and giving you a clean interface at the same time.
Of course most developers already know they can let their IDE generate getters and setters, right? However, Eclipse has some more code generation features to offer, which are less well-known and less used. As you can see in the picture to the left Eclipse 3.2 also offers code generation features for delegate, hasCode and equals methods, as well as for constructors.
Let's start with generating constructors. This feature has been in Eclipse since version 3.0, but is less popular than generating getters and setters. This is probably due to the fact that generating getters and setters usually saves more time than generating constructors. Still you can save some time by generating the constructors, while ensuring consistency in the way you write your constructors. This also prevents common mistakes like forgetting to call the constructor of the supper class.
Suppose we have a simple Person class with just 3 fields: firstName, lastName and
dateOfBirth. To generate a constructor that takes these 3 fields as parameters, select Generate Constructor using
Fields... from the Source menu. This menu is available in the menu bar at the top, in the context menu when you right click in the
editor or by pressing Alt+Shift+S in the editor.
After selecting Generate Constructor using Fields... a dialog appears that lets you choose the fields the constructor should initialize,
the insertion point and the access modifier. If the superclass has more than one constructor you can choose which one should be invoked. The
option to check Omit call to default super() constructor is only available if the class extends Object. Finally you can
select to Generate constructor comments which will add a JavaDoc header to the constructor. Using the options selected in the picture
on the right, Eclipse will generate the constructor we see below.
The second method to generate your constructors is by selecting Generate Constructor from Superclass... from the Source menu which will enable you to generate constructors for all the constructors in the superclass in one go. This can be useful in case you extend a class and need all (or a selected few) of the constructors in the class to be available in the descendent.
As a word of warning, make sure that you really need the constructors you are generating. While code generation offers a lot of benefits, there's one big drawback. Because it's so easy to generate code, developers are sometimes tempted to generate a lot of code they don't really need. Especially with constructors this happens a lot, with developers generating all the different constructors they can think of or always generating constructors for all constructors in the superclass. You should always think about why you add a constructor or method to your class and not just generate code because you can do it quick and easy. Remember that it may not take a lot of time to generate the extra code, but you will then have to test, document and maintain that code. In the end this may cost you more time than you've saved by generating the code in the first place.
Lets continue with generating the equals() and hashCode() methods. We need to override the equals()
method in order to be able to compare different Person objects. Overriding the equals() method is a bit more complex
than writing a getter method or constructor, since there are quite some rules we have to obey. The JavaDoc documentation tells us that the
equals() must be reflexive, symmetric, transitive and consistent. Furthermore we need to handle
null references correctly and we should override the hashCode() method. Failure to override the hashCode()
method will lead to problems when using your class in hash-based collections like HashMap and HashSet.
Given the many rules involved in implementing the equals() and hashCode() methods, it's not surprising that it's easy
to make a mistake. In his book 'Effective Java', Joshua Bloch gives us two good recipes for implementing these methods (see
Resources for more information). Since version 3.2 Eclipse can generate both methods for us based on the recipes
proposed by Joshua Bloch, which we'll do next for the Person class.
Select Generate hashCode() and equals()... from the Source menu. In the dialog we can choose which fields must be equal in order
for the Person object to be equal. If the Person class had a unique id field to the class, we would only
need that field in the equals() and hashCode(), but for this example we need all 3 fields. As with generating
constructors we can also choose the insertion point and generate method comments. Using the options selected in the picture on the right,
Eclipse will generate the equals() and hashCode() methods we see below.
As we can see even for just 3 fields this generates quite a few lines of code, some of which we might forget on our first attempt when we implement the methods ourselves. It's easy to see that generating these methods can save use a lot of time not only in writing the code but probably also in debugging the code. This does not mean that by generating these methods, we can't make mistakes that may cost us valuable time to debug. Even worst, because we generated the code we may be tempted to skip these methods when debugging problems. Don't worry, there is nothing wrong with the generated code, but strange things may happen if we start to use inheritance, add new fields or do some refactoring. Let me explain what I mean.
What will happen if we create a new Male class that extend the Person class without adding any new fields, like
the class below?
Since we haven't introduced any new (differentiating) fields we can compare Male objects without overriding the
equals() (and hashCode()) method. However what will the following piece of code print?
Male male = new Male(
person.getFirstName(),
person.getLastName(),
person.getDateOfBirth());
System.out.println(male.equals(person));
You may expect this piece of code to print true, but it doesn't. The reason for this is the fact that the
equals() method in the Person class contains the lines:
if (getClass() != obj.getClass())
return false;
So because Male and Person are different classes the equals() method will always return
false. Usually we don't want objects from one class to be equal to objects from the superclass, but if the fields in the superclass
uniquely identify the object (remember the id field mentioned earlier) you may want to allow this. If this is the case we need
to use instanceof in the equals() method.
if (!(obj instanceof Person))
return false;
In the 3.3 release of Eclipse you no longer have to change this by hand, because you can select Use 'instanceof' to compare types in the dialog (see picture). Version 3.3 will be released at the end of the second quarter of 2007, but you can already download the latest milestone if you can't wait to use this new functionality.
Another problem can occur if we would decide to let our Person class extend the Mammal class.
public class Person extends Mammal {
...
}
If we try to generate the equals() and hashCode() method, Eclipse will warn us that these methods are not defined
on the Mammal class and that the generated code may not work correctly. The reason for this is that Eclipse will add some extra
code to first call the implementations of hashCode() and equals() in the superclass as we can see below.
As long as we don't ignore the warning and also add a correct implementation of these methods to the Mammal class, everything
is fine. But what happens if some other developer later on decides that a Person is not a Mammal and removes the
superclass from the class definition? Now Object becomes the superclass and the equals() method of the
Person class will only return true if the two references being compared refer to the same object, because this is the
default implementation of equals() for the Object class.
Similar problems can occur when classes that are used as fields don't have a correct implementation of the equals() and
hashCode methods. Eclipse will warn us again when we generate these methods, but we can still create problems for ourselves when we
start refactoring classes later on.
Of course all these problems can also occur when we write the code ourselves, but with code generation we may be lured into a false sense
of security and not test the code properly. The best way to avoid these problems is to still write unit tests for the more complex methods
like equals() and hashCode(). It's also a good idea to think about regenerating these methods if you make changes
to the structure of a class like adding fields or changing the superclass.
The last code generation feature we'll take a look at deals with generating delegate methods and has been in Eclipse since version 2.1. Technically, these methods should be called forwarding methods, although most developers use the term delegate instead of forwarding. To avoid confusion we'll use the term delegate here just as Eclipse does.
When designing classes we can use either inheritance or composition to reuse code. Joshua Bloch states in his book 'Effective Java' that we should favor composition over inheritance. One of the reasons developers often use inheritance where composition is more appropriate, is because inheritance automatically adds the interface (public, package and protected methods) of the superclass to your class. On the other hand, when using composition we have to manually add the methods we want to expose in the interface of the class. This is where the feature to generate delegate methods comes to our rescue.
Let's say we want to create a Team class. Since a team contains a number of persons, we may choose to define our
Team class as a specific type of a collection class.
public class Team extends ArrayList<Person> {
}
This will automatically give us the methods from the ArrayList class that we can use to add or remove persons to/from the team,
with a minimum of coding. However, inheritance should be used for is-a relationships and a team isn't exactly a type of list. We
just happen to use a List to store the different Person objects. This should be an implementation detail, so
composition would be a better choice.
public class Team {
private List<Person> members = new ArrayList<Person>();
}
While this correctly encapsulates the implementation details, we now need to add several methods for manipulating the persons in a team.
By selecting Generate Delegate Methods... from the Source menu, we can do this in a matter of seconds. In the dialog we can
select the methods we want to generate delegate methods for. For example we can select the add(), contains(),
iterator(), remove() and size() methods, which will generate the following code.
Note that by only selecting the methods we need, we make it easier to change the implementation. Suppose we later on decide we need to use
a Map instead of a List to make it easier to find persons by name, but want/need to keep the interface the same. We
now need to change the implementation of most of the generated methods. For example the add() method could end up looking like
this.
If we would have generated delegate methods for all 20 available methods of the ArrayList class, we would need to change a lot
more code. So only generate delegate methods for methods you know you need; it's less work to generate some extra methods later on than to
have to maintain code that you didn't really need in the first place but is now used by other classes.
As we have seen the code generation features of Eclipse can save us some valuable time, but we need to be careful not to generate code that
we don't really need. Failure to do so may end up costing us more time than we saved in the first place. We must also remember that we still
need to test the more complex methods like equals() and hashCode() to avoid tricky problems when we start to refactor
our classes.
If instead of Eclipse you're using one of IBM's products that is based on Eclipse, you may find some of the code generation features missing
if the product is based on an older version of Eclipse. See the table below for an overview.
| Product name | Based on | |
| Rational Application Developer 7.0 | Eclipse 3.2 | |
| Rational Application Developer 6.0 | Eclipse 3.0 | |
| WebSphere Application Developer 5.1 | Eclipse 2.1 | |
| WebSphere Application Developer 5.0 | Eclipse 2.0 |
If you're using another IDE that's not based on Eclipse, spend some time finding out what kind of code generation features it has to offer. It may save you some time that you can use to write more interesting code.
Resources
- You can download Eclipse from: www.eclipse.org
-
In chapter 3 (item 7 and 8) of 'Effective Java' you can find
the recipes for the
equals()andhashCodemethods. - For more information about 'Effective Java' read Improve Your Java Skills With 'Effective Java'.
-
For feedback and questions you can send an e-mail to :
developer@componative.com