Friday 31 August 2007

Java 7 - Self-types

I just screamed at Java again. This time my ire has been raised by the lack of self-types in the language.

Self-types

So what is a self-type. Well, I could point you at some links, but I'll try and have a go at defining it myself...

A self-type is a special type that always refers to the current class. Its used mostly with generics, and if you've never needed it then you won't understand what a pain it is that it isn't in Java.

Here's my problem to help explain the issue. Consider two immutable classes - ZonedDate and LocalDate - both declaring effectively the same method:

 public final class ZonedDate {
   ZonedDate withYear(int year) { ... }
 }
 public final class LocalDate {
   LocalDate withYear(int year) { ... }
 }

The implementations only differ in the return type. This is essential for immutable classes, as you need the returned instance to be the same type as the original in order to call other methods (like withMonthOfYear).

Now consider that both methods actually contain the same code, and I want to abstract that out into a shared abstract superclass. This is where we get stuck:

 public abstract class BaseDate {
   BaseDate withYear(int year) { ... }
 }
 public final class ZonedDate extends BaseDate {
   // withYear removed as now in superclass, or is it?
 }
 public final class LocalDate extends BaseDate {
   // withYear removed as now in superclass, or is it?
 }

The problem is that we've changed the effect of the method as far as ZonedDate and LocalDate are concerned, because they no longer return the correct type, but now return the type of the superclass.

One solution is to override the abstract implementation in the subclass, just to get the correct return type (via covariance):

 public abstract class BaseDate {
   BaseDate withYear(int year) { ... }
 }
 public final class ZonedDate extends BaseDate {
   ZonedDate withYear(int year) {
     return (ZonedDate) super.withYear(year);
   }
 }
 public final class LocalDate extends BaseDate {
   LocalDate withYear(int year) {
     return (LocalDate) super.withYear(year);
   }
 }

What a mess. Imagine doing that for many methods on many subclasses. Its a large amount of pointless boilerplate code for no good reason. What we really want is self-types, with a syntax such as <this>:

 public abstract class BaseDate {
   <this> withYear(int year);
 }
 public final class ZonedDate extends BaseDate {
   // withYear removed as now correctly in superclass
 }
 public final class LocalDate extends BaseDate {
   // withYear removed as now correctly in superclass
 }

The simple device of the self-type means that the withYear method will now appear to have the correct return type in each of the subclasses - LocalDate in LocalDate, ZonedDate in ZonedDate and so on. And without reams of dubious boilerplate.

Summary

Self-types are a necessary companion for abstract classes where the subclass is to be immutable (and there are probably other good uses too...).

Opinions welcome as always!