January 27, 2004

Failure to override

22 months ago, I wrote this article for ONJava.com (part of the O'Reilly Network.

I'd pretty much forgotten about the article, until I got an e-mail today from a New Zealand reader known simply as "Hemi". He was the first of (presumably) several thousand readers to point out an error in this code from the article:

public class LRUCache extends java.util.LinkedHashMap {
    public LRUCache(int maxsize) {
	super(maxsize*4/3 + 1, 0.75f, true);
	this.maxsize = maxsize;
    }
    protected int maxsize;
    protected boolean removeEldestEntry() { 
       return size() > maxsize; 
    }
}

This code is supposed to implement a least-recently-used (LRU) cache on top of the java.util.LinkedHashMap class. Can you spot the error? Hint: the class functions as a cache, but fails the LRU part.

Read on to see what I did wrong and learn how you can avoid this category of errors yourself.

My class attempts, but fails, to override the removeEldestEntry() method of LinkedHashMap. The problem is that I got the signature wrong. The method I wanted to override takes a Map.Entry object as an argument. My method takes no arguments, and therefore does not actually override anything. The point of overriding this method is to prune old entries from the cache. (See the javadoc for LinkedHashMap for details.)

My code looks like it overrides the method, but it doesn't. The upshot is that the cache never purges the least-recently used elements, and just grows and grows.

I should have tested my code.

Now, getting on to the main point of this post: Java 1.5 has a solution to this general problem of failing to override the method you intend to override. The new Java metadata/annotation language feature defined by JSR-175 specifes an @Overrides annotation that solves exactly this problem.

Update: In the proposed final draft for JSR-175, this annotation has been renamed @Override.

If I had been writing the article about Java 1.5, and had annotated my method with @Overrides, then the compiler would have issued an error when it noticed that my method did not, in fact, override anything.

Here's the code as I should have written it, updated for Java 1.5, and still untested:

public class LRUCache extends java.util.LinkedHashMap {
    public LRUCache(int maxsize) {
	super(maxsize*4/3 + 1, 0.75f, true);
	this.maxsize = maxsize;
    }
    protected int maxsize;
    @Overrides protected boolean removeEldestEntry(Map.Entry eldest) { 
        return size() > maxsize; 
    }
}
| TrackBack
Comments

if you were using IDE such as CodeGuide it would show you all methods that are overriding parent by placing a small red arrow after method signature - no arrow = no overriding. easy to spot ...

Posted by: Alek at February 10, 2004 08:40 PM

The IDE doesn't really help. It's not easy to spot if a library you are using changes the signature of one of its methods - it is infeasible to remember where all the red arrows are, and make sure that they are still there.

The JDK 1.5 solution is spot on.

Posted by: Jon at February 11, 2004 10:08 AM

A good example why explicit override is necessary. A question about @Overrides is whether the compiler will warn if it is not present when a method is overriding another one. If there is no warning, now you need to remember to add @Overrides each time, or you won't get all the benefits.

Posted by: Daniel Bonniot at March 5, 2004 03:27 PM

Daniel:

The compiler does not warn if you omit the @Override (the s has been dropped: this attribute is singular now). If it did, it would break a lot of existing code.

Posted by: David Flanagan at March 7, 2004 09:22 PM

Printing a warning would not really "break" code, since the compilation would still succeed. And the warning could also be customized (like only with -source 1.5). In Nice we are considering keeping a warning, but of course there we don't have the problem of strict source compatibility (and 'override' is a keyword).

Posted by: Daniel Bonniot at March 8, 2004 01:32 AM

Daniel:

I know that "break" was too strong a word. But regular users would be pretty upset to get a warning everytime they overrode a method in existing code.

I suppose a -Xlint warning that was not one of the default ones enabled by -Xlint might be suitable, then it could be turned on for projects in which @Override usage was mandatory style.

Posted by: David Flanagan at March 8, 2004 09:40 AM

Nice looks like a very interesting language, by the way. I've signed up to monitor it at sourceforge, and look forward to playing around with it when it reaches 1.0. (Or when I'm done documenting Java 1.5)

Posted by: David Flanagan at March 8, 2004 09:44 AM

It seems to me that the lesson is to write, keep and run tests.

Posted by: Jeff McKenna at April 3, 2004 01:50 PM