Wednesday, May 10, 2006

Double dispatch in Java

Some programming languages provide the feature of dispatching a funtion call to different concrete functions depending on the runtime types of multiple objects involved in the call (including parameters). In Java dynamic method dispatch, the actual method call depends on the dynamic type of a single object (the object/interface on which the method is invoked), hence it is called single dispatch.

In the following piece of code (A java version of the original wikipedia example), you can see that, although an ExplodingAsteroid collidedWith a GiantSpaceShip, the output shows only a SpaceShip.

class SpaceShip {}
class GiantSpaceShip extends SpaceShip {}

class Asteroid {

public void collideWith(SpaceShip sp) {
System.out.println("Asteroid hit a SpaceShip");
}
public void collideWith(GiantSpaceShip gsp) {
System.out.println("Asteroid hit a GiantSpaceShip");
}
}

class ExplodingAsteroid extends Asteroid {

public void collideWith(SpaceShip sp) {
System.out.println("ExplodingAsteroid hit a SpaceShip");
}
public void collideWith(GiantSpaceShip gsp) {
System.out.println("ExplodingAsteroid hit a GiantSpaceShip");
}
}

public class DoubleDispatchTest { public static void main(String args[]) {
Asteroid ast = new Asteroid();
Asteroid ast1 = new ExplodingAsteroid();
SpaceShip sp = new SpaceShip();
SpaceShip sp1 = new GiantSpaceShip();
ast.collideWith(sp);
ast.collideWith(sp1);
ast1.collideWith(sp);
ast1.collideWith(sp1);
}
}

Output:
Asteroid hit a SpaceShip
Asteroid hit a SpaceShip
ExplodingAsteroid hit a SpaceShip
ExplodingAsteroid hit a SpaceShip
This is due to the fact that, though Java can recognize the runtime type of the Asteroid, it ignores the runtime type of the SpaceShip which is sent as an argument.
This problem can be solved by re-writing the above code as follows:

class SpaceShip {
public void collideWith(Asteroid inAsteroid) {
inAsteroid.collideWith(this);
}
}

class GiantSpaceShip extends SpaceShip {
public void collideWith(Asteroid inAsteroid) {
inAsteroid.collideWith(this);
}
}

class Asteroid {

public void collideWith(SpaceShip sp) {
System.out.println("Asteroid hit a SpaceShip");
}
public void collideWith(GiantSpaceShip gsp) {
System.out.println("Asteroid hit a GiantSpaceShip");
}
}

class ExplodingAsteroid extends Asteroid {

public void collideWith(SpaceShip sp) {
System.out.println("ExplodingAsteroid hit a SpaceShip");
}
public void collideWith(GiantSpaceShip gsp) {
System.out.println("ExplodingAsteroid hit a GiantSpaceShip");
}
}

public class DoubleDispatchJava {
public static void main(String args[]) {
Asteroid ast = new Asteroid();
Asteroid ast1 = new ExplodingAsteroid();
SpaceShip sp = new SpaceShip();
SpaceShip sp1 = new GiantSpaceShip();

sp.collideWith(ast);
sp.collideWith (ast1);

sp1.collideWith(ast);
sp1.collideWith(ast1);
}
}
Output:
Asteroid hit a SpaceShip
ExplodingAsteroid hit a SpaceShip
Asteroid hit a GiantSpaceShip
ExplodingAsteroid hit a GiantSpaceShip
In this case, we are still using the runtime type of just one object with each call, but we have an additional call embedded within the called method, which invokes another of the second object (Asteroid), thus achieving double dispatch. The same effect can be achieved by using a couple of if-else statements within the code, but the code starts to look ugly once more types of spaceships/asteroids are introduced.

4 comments:

Popular Posts