The Source for Java Technology Collaboration
User: Password:



Andreas Schaefer's Blog

October 2004 Archives


All equals() are not born equal

Posted by schaefa on October 29, 2004 at 02:37 PM | Permalink | Comments (24)

During a discussion with a colleague we started talking about the problems of equals() with inheritance. He mentioned Joshua Bloch's Effective Java book which covers this topic quit well but more or less said that this problem cannot be resolved. This was enough to get me hooked onto this problem and I came up with a solution quite fast. Afterwards I started to think about other solutions and their problems. Because all of them made sense I started to question myself if my solution was even necessary and until now I am still not sure if it does or if it is just a solution for theorists in the ivory tower. But even if it is just theory I think it is an interesting problem to talk about.

The equals() method defined in java.lang.Object has some restrictions to its implementation. Among other things it needs to symmetrical meaning that target of the method can switch places with the argument and it must yield the same result (if A is equal to B then B must be equal to A). Also it must be transient meaning that if A is equal to B and B equal to C then also A must be equal to C. There are two common ways to implement an equals() method:

  1. The method ensures that it only compares to instance of its own kind (class)
  2. The method ensures that it tests against an interface of base class and only tests if they are equal to that. An example is the AbstractList in java.util.
The first is a simple solution but breaks if someone tries to extend this class. The second solution cannot test parts of the extension of a class without breaking the rules for equals.

Before I start explaining my solution I want to come up with a use case to reason why equals() should work with inheritance. The use case is quite synthetic but it is the best model I could think of that is short and intuitive. In math you have the irrational numbers representing numbers like 1, ½, pi or e. An extension of it are the complex numbers that contain two irrational numbers one for the real part and for the imaginary part. For more information visit Dave's short course on Complex Numbers. In complex numbers you can also express pure irrational numbers by setting the imaginary part to 0. This means that e = e + 0i. For us this means that an irrational number should be equal to a complex number if the imaginary part is set to 0. The algebra behind the complex numbers does ensure that equals is symmetrical and transient. Now, shouldn't this be good enough for us software developers to achieve the same? The first solution mentioned above fails because it breaks with inheritance and the second solution will break mathematical because any complex number with the same real part as the irrational number will be equal because there is no test against the complex part.

To solve the equals() test with inheritance the equals() method must be written with inheritance in mind because only a sub class is able to determine equality correctly but in order to support symmetry the base class must delegate the test to the sub class if a sub class instance is given. An equals method of the irrational number would look like:

 1 public boolean equals(Object o) {
 2    if(o == null) return false;
 3    if(o == this) return true;
 4    if(o instanceof Irrational) {
 5       if(o.getClass().equals(Irrational.class)) {
 6          return ((Irrational)o).real == real;
 7       } else {
 8           return o.equals(this);
 9       }
10    }
11    return false;
12 }
In line 5 we check if the given object is of the same type and then we test is like normal. In line 8 the given argument is of a sub class so we delegate the test to this sub class with this instance as parameter.

In the Complex class we need to adjust the equals method a little bit:\

 1 public boolean equals(Object o) {
 2    if(o == null) return false;
 3    if(o == this) return true;
 4    if(o instanceof Complex) {
 5       if(o.getClass().equals(Complex)) {
 6          return ((Complex)o).real == real && ((Complex)o).imag == imag;
 7       } else {
 8           return o.equals(this);
 9       }
10    } else {
11       if(o.getClass().equals(Irrational.class)) {
12          return imag == 0 && ((Irrational)o).real == real;
13       }
14    }
15    return false;
16 }
The only difference to the Irrational's equals method is that we here check in line 11 if the given object is of type Irrational and then we allow the instances to be equal if the imaginary part is set to 0 and the real part is equal. So line 8 in both classes ensures that the equals() method is symmetrical and line 11 to 13 in the Complex class ensures that it is transient. Even thought this looks great so far it has three problems:
  • All sub class must overwrite and adjust the equals() method otherwise line 8 in the base class will create an endless loop
  • The equals() method in the sub class cannot call the equals() method in the base class otherwise it ends up in an endless loop, too
  • Line 11 in the Complex class cannot check against a sub class of Irrational in a different branch (meaning it is not a sub class of Complex, too)

The solution above works quite well even when it has some restrictions. Still, you might think that this is not useful in the real world but imagine a scenario where you have an ArrayList of Irrational type (generic types in JDK 1.5). This allows you to add Irrational numbers but also Complex numbers. If the equals() method would not support inheritance you would not be able to find an Irrational instance with a Complex instance that represents the same irrational number because the imaginary part is set to zero. One would also fail if one will search a Complex number with imaginary part set to zero with an Irrational instance. In a world of distributed computing sometimes we cannot just change all the code and so inheritance provides a way to introduce changes with localized side effects. This also means we must put some effort in the design of a class so that inheritance does not break especially on crucial parts like equality.

Happy coding – Andy



Proposal to fix the Cloneable Problem

Posted by schaefa on October 13, 2004 at 09:02 AM | Permalink | Comments (9)

I know that probably backward compatibility is the main reason to keep java.lang.Cloneable as it is. Nevertheless as I hopefully showed in my rant about this still unresolved issue this shortcoming of the Cloneable interface is still haunting us. To prevent the impression that I only complain about problems I am going to suggest a solution to this problem that, I hope, will end this problem once and for all.

I would suggest the following addition to the JDK (Please adjust the name of the interface as you like I just could not think of a better one):

package java.lang;

public interface XCloneable
	extends Cloneable {
   // The exception still can be thrown if a reference is not cloneable
   public Object clone()
      throw CloneNotSupportedException;
}
Now all the classes in the JDK that implements Cloneable and provide a public clone() method could now extend XCloneable and we all could life happily ever after.

This should fix the shortcoming of Cloneable and still is backward compatible. It is just ugly to add another interface but, I guess, that is the price for backward compatibility.

Let's go Tiger - Andy

Cloneable: How an old Bug can bite for a very long time

Posted by schaefa on October 12, 2004 at 05:15 PM | Permalink | Comments (10)

I cannot remember when I complained about the missing public Object clone() method in the java.lang.Cloneable interface for the first time but in the latest release of Java (1.5) this bug hurts us developers even more.

Assume we want to create our very own list that allows deep copies when cloned. In 1.5 the code could look like this (disclaimer: I am not a 1.5 expert so bare with me if the syntax if wrong):

ArrayList<Cloneable> myCloneableList = new ArrayList<Cloneable>();
ArrayList<Cloneable> myDeepCopiedList = (ArrayList<Cloneable>)
      myCLoneableList.clone(); // Assuming the method is overwritten and public
Now the clone() method could look like this (if the problem would have been fixed):
public Object clone() {
   // Create the list here
   lNewList = new ArrayList<Cloneable>();
   Iterator i = getIterator();
   while(i.hasNext()) {
      lNewList.add(((Cloneable) i.next()).clone());
   }
   return lNewList;
}
But this does not work and the only way to make it work is to use reflection to get the clone() method, check that the method is overwritten and public and then call it. So my fellow JDK developers at Sun can you please explain to me why this is still such a hassle and nobody dared to fix it (JDK 1.0 was released more than 7 years ago)? Now with the generic types that bug is going to haunt us even more.

Happy coding - Andy

Unit Test are at least as Important as the Coding Itself

Posted by schaefa on October 12, 2004 at 02:08 PM | Permalink | Comments (5)

Tom talked in The Problem with Unit Testing about Unit testing. Even though that I agree with most of it I disagree with the view that Unit Testing is only a safety net. The only person responsible of the quality of the software is the author himself and he/she should be held accountable for that. Therefore, in my opinion, writing Unit tests is not just a good habit but an essential part of the deliverables. Ommiting the unit tests is like designing a car engine and installing into the finished prototyp untested. Nobody in the car industry would do that so why are developer still doing it?

The basic problem of unit testing is that managers do consider unit tests as a nice to have item rathar than an essential component and leave it to the developer to decide if they want to do it or not. So the experienced developer might say that he does not need because he knows what he is doing and the rather unexperienced developer does not know about Unit tests or does not know how to code it. Nevertheless no developer is immune against bugs and regressions and so should write units tests. It also helps to reduce regressions and so the gain from Unit tests increase every time a change is made without a regression. I think of a software developer as a professional that delivers good software so that he/she can be pride of. I would be embarassed, and was quit often in the past when I delivered software untested, if a stupid problem would make a product fail because of my bug. We, the software developers, should take pride in our work and not let allow our bad work habit getting us a bad reputation like, for example, a lawyer or is it already too late for that?

Here is a list of items I think are important for Unit Tests:

  • Unit tests are tests and probably the best tests you can get because it is on a pretty low level so that more code can be covered and fewer bugs can hide
  • Unit tests are written by developers which now their code the best. Of course, developer tend to write units around their bugs but, I think, they can learn to be more malious with their code
  • The time spend on a unit test (except for a prototyp that is actually thrown away later) is saved before the software is delivered and the savings from that test will only grow over time
  • Unit tests will let a developer write more new code than without
  • Unit tests can test nearly everything even a GUI
As described in a previous blog about jUnit tests a unit test can test even advanced components like a transaction manager in a J2EE server. It just needs some imagination and persistence.

Everything we code contains bugs and all the tests we can come up with leave a chance for a bug to remain undetected. The same also applies to the car industry where even Honda makes recalls of their cars to fix a problem. Please remember the 80/20 rule saying that if you can catch 80% of the bugs with 20% of the effort we are in good shape and will do great. This can only be accomplished if there is a unit test framework available, which should be setup when the project starts. In addition such a framework will remind developer to write unit tests and eliminates many of the exuses not to write them.

The test-first or test-driven development does not only deliver unit tests but also use them to help design your code because the tests act as the devil's advocate making sure that the design works when used in a client. The unit test is the client. If a developer cannot work this way then do not but still should write the unit tests later.

Happy testing - Andy



Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds