When I find something interesting and new, I post it here - that's mostly programming, of course, not everything.

Sunday, November 16, 2008

What To Do with Dispatch of Static Methods for Java

A problem with Java that everybody encounters from time to time is the following: static methods cannot override each other. If class A has static method m, and class B has static method m, with the same signature, there is no automatic way to figure out which method should be called.

Look at these two classes:

public class A {
String x;

public A(String x) {
this.x = x;
}
public String toString() {
return "A(" + x + ")";
}

public static A combine(A a1, A a2) {
return new A(a1.x + " + " + a2.x);
}
}


and

public class B extends A {

public B(String x) {
super(x);
}
public String toString() {
return "B(" + x + ")";
}

public static B combine(B b1, B b2) {
return new B(b1.x + " * " + b2.x);
}
}


I would be nice if the right combine method would be called in each case. But no, the following code
import static A.*;
import static B.*;

public class Test {
public static void main(String[] args) {
A a1 = new A("a1");
A a2 = new A("a2");
B b1 = new B("b1");
B b2 = new B("b2");
A c = new B("c");
System.out.println(combine(a1, a2));
System.out.println(combine(b1, b2));
System.out.println(combine(a1, b2));
System.out.println(combine(b1, c));
}
}

will print
A(a1 + a2)
B(b1 * b2)
A(a1 + b2)


A(b1 + c)


Why does the second call prints B(b1 * b2)? Just because the method combine that takes two arguments of type B is available via static import B.*.

Interesting, we know the objects types, but dispatch is done statically. Can we fix it?

One awkward solution is to use reflection: add the following to A
public static A smartCombine(A a1, A a2) {
Class class1 = a1.getClass();
try {
Method method = class1.getMethod("combine", class1, a2.getClass());
System.out.println(method);
return (A)method.invoke(null, a1, a2);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}

then call it like this:
System.out.println(smartCombine(b1, c));

and we'll get
B(b1 * c)

Okay, not the best solution anyway. We won't seriously add a "smart" version to each static method.

So maybe we should not use static? Should we hate static? Let's define instance methods, in class A:
public A combineWith(A other) {
return new A(this.x + " | " + other.x);
}

and in class B:
public A combineWith(A other) {
return new A(this.x + " & " + other.x);
}

public B combineWith(B other) {
return new B(this.x + " && " + other.x);
}

Now let's try this:
System.out.println(b1.combineWith(b2));
System.out.println(b1.combineWith(c));
System.out.println(c.combineWith(b1));

We well see the following:

B(b1 && b2)
A(b1 & c)
A(c & b1)

What went wrong? The first case is ok, but how about others? The second case is due to the simple fact that the dispatch is done statically. So the compiler sees the parameter is an A, and calls the method that takes an A.
How about the next one, we use two instances of B, but still? It is trickier. The compiler sees an instance of A, and calls a method combineWith of class A; this method takes an A as a parameter. At runtime, since c is an instance of B an appropriate method of B is called; but the signature still is combineWith(A), and this sad truth destroys our plans.

So, what can we do? double dispatch should help, right? Here is a good example of how to use double dispatch in in Java.

Unless you do not know already, the trick is this. We have class A and and its subclass B, and a parallel hierarchy, X and its subclass Y. These two hirerachies interoperate, and we want to make sure that if, say, an instance of B works with an instance of Y, it would recognize this even if the instances are passed around as instances of A and X.

For double dispatch to work, class A should be aware of classes X and Y; similarly, class X should be aware of A and B.

Not so in our case; we have just one hierarchy, and of course our A is not aware of its subclass B.

So our naive attempt of using double dispatch will fail.

First, add to A the following:
static A combine(A a1, A a2) {
return a1.combineFollowedBy(a2);
}

A combineFollowedBy(A other) {
return other.combinePrecededBy(this);
}

A combinePrecededBy(A other) {
return new B(this.x + " | " + other.x);
}

and add to B a similar code:
A combineFollowedBy(A other) {
return other.combinePrecededBy(this);
}

A combineFollowedBy(B other) {
return other.combinePrecededBy(this);
}

A combinePrecededBy(B other) {
return new A(this.x + " && " + other.x);
}

A combinePrecededBy(A other) {
return new A(this.x + " & " + other.x);
}

As I already said, this won't bring happiness in this world:

A(a2 | a1)
A(b2 & b1)
A(b2 & a1)
A(c & b1)


The reason is obvious: nowhere in our call sequence for, say, c and b1 we have a chance to call B.combineX(B)

So, what should we do? For a case like this I have only one suggestion: use instanceof.

We don't need double dispatch. Let's simplify A:
static A combine(A a1, A a2) {
return a1.combineFollowedBy(a2);
}

A combineFollowedBy(A other) {
return new A(this.x + " | " + other.x);
}


And make B a little bit less kosher:
A combineFollowedBy(A other) {
return other instanceof B ?
combineFollowedBy((B)other) :
super.combineFollowedBy(other);
}

A combineFollowedBy(B other) {
return new B(this.x + " & " + other.x);
}


Now it works. A small stylistic sacrifice for fixing the lack of functionality.

No comments:

Followers

Subscribe To My Podcast

whos.amung.us