ChiMu  
 
Menu Edge About   Products   Services   Projects   Publications  
  Publications                 

Double Dispatching in Java

Overview

This is an example and explanation of double dispatching in Java, just to add to the collection :-)

The following is the entrypoint to the thread on this topic:

Original Posting: Double-dispatching in Java

Roedy Green wrote:
> >double dispatching
>
> Would anyone care to post a small code snippet, preferably in Java or
> C++, that demonstrates what double dispatching is?

OK, I will give a relatively concise one in Java. Please see the GOF Design Patterns Book, The Smalltalk Design Patterns Companion, Kent Beck's Best Practice Patterns, and so on for more information and more detailed examples.


Say I need a 2D-Point with highly accurate X and Y numeric values:

import java.math.*;

class Point2D implements Addable {
    protected final BigDecimal myX;
    protected final BigDecimal myY;

    public Point2D(BigDecimal x, BigDecimal y) {
        myX = x;
        myY = y;
    }

    public BigDecimal getX() {
        return myX;
    }

    public BigDecimal getY() {
        return myY;
    }

    public String toString() {
        return "";
    }
}

I certainly would like to be able to add points together:

    public Point2D addToPoint(Point2D aPoint) {
        return new Point2D(
                this.getX().add(aPoint.getX()),
                this.getY().add(aPoint.getY())
            );
    }

And I decide I would also like to be able to add numbers to points. I will consider the number to be just an X value of a "virtual" point (so the Y value is considered '0'):

    public Point2D addToBigDecimal(BigDecimal aBigDecimal) {
        return new Point2D(
                getX().add(aBigDecimal),
                getY()
            );
    }
Nothing too complex and no double-dispatching. I can test it with:
class PointTest {
    static public void main(String[] args) {
        Point2D p1        = new Point2D(new NewBigDecimal("5"), new NewBigDecimal("6"));
        Point2D p2        = new Point2D(new NewBigDecimal("3.1"), new NewBigDecimal("4.5"));
        NewBigDecimal bd1 = new NewBigDecimal("7.2");
        NewBigDecimal bd2 = new NewBigDecimal("9.1");

        System.out.println("P1  = "  + p1);
        System.out.println("P2  = "  + p2);
        System.out.println("BD1 = " + bd1);
        System.out.println("BD2 = " + bd2);

        System.out.println();
        System.out.println("*Results*");
        Object result1a = p1.addToPoint(p2);
        Object result2a = p1.addToBigDecimal(bd1);

        System.out.println("P1+P2  = "+result1a);
        System.out.println("P1+BD1 = "+result2a);
    }
}
Now I decide to keep a collection of offsets/movements from some starting point. These offsets could either be a 2DPoint or a number (because they only shift the X axis). I simply want to add either points or numbers to a point and for the result to be correct:
       Object result3a = p1.addTo(p2).addTo(bd1);
I just add two new methods:
    public Point2D addTo(NewBigDecimal value) {
        return addToBigDecimal(value);
    }
    public Point2D addTo(Point2D value) {
        return addToPoint(value);
    }

I have now collapsed my longer names into shorter (overloaded) names and so can ignore whether a given argument is a point or a number.

Cool, I now have short operation names, and the program will choose between them based upon... No, wait... the only reason the above 'result3a' works is because I told the compiler what types the variables 'p2' and 'bd1' are. If I am getting these objects out of a collection, I will not know what type of object they are specifically, I will only know they an be added to a Point. The compiler can not tell me the difference, only the runtime object can. If both a1 and a2 are extracted from an Enumeration, the following:

        Object a1, a2;
        Object result3b = p1.addTo(a1).addTo(a2);
is unresolvable unless 'addTo' is more generic.

OK, wrong path (stupid overloading), so I switch to a more generic 'addTo' and a type check:

    public Point2D addTo(Object value) {
        if (anObject instanceof NewBigDecimal) return addToBigDecimal(value);
        if (anObject instanceof Point2D) return addToPoint(value);
        //....
    }
Ohhh.... yich... I don't really want to couple myself to specific classes -- I would have to keep changing Point2D::addTo over and over again as new classes are added -- I just want this object and its value to "do the right thing" depending on the capabilities of 'value'. Well, I have gotten halfway there... I know who I am so I can just delegate to my value to finish the process:
    public Point2D addTo(Object value) {
        return value.addToPoint(this);
    }
EUREKA, that's it... :-) I have now dispatched both on myself (to get to the Point2D::addTo method) and on 'value'. We have a double dispatch and
        Object result3b = p1.addTo(a1).addTo(a2);
will work correctly whether a1 or a2 is a Point or a number. That one line:
    return value.addToPoint(this);
is all there is to a double dispatch in concept.

Except we are in Java and I slipped in a bit of "optimistic" untyped code and left out a little of the implementation. Objects in Java would not know how to handle 'addToPoint' and Java would not know that 'addToPoint' returns a Point2D. Unfortunately, things get even messier VERY quickly with Java because it does not have Covariant return types, we can not augment existing classes, and so on. The following is a fuller set of code to make the above really work:


/**
An addable is an object that can be added to another object.  The exact
meaning of that addition
depends on the two objects involved.
**/
interface Addable {
    Addable addTo(Addable anObject);

        /**
         *This is really "private" within the Addable implementations
         */
    Addable     addToBigDecimal(NewBigDecimal aBigDecimal);

        /**
         *This is really "private" within the Addable implementations
         */
    Addable         addToPoint(Point2D aPoint);
}


/**
NewBigDecimal is simply the BigDecimal class with the additional
operations
of the Addable interface.

We just want to augment BigDecimal with 'addTo' methods, but Java does
not support augmentation of existing classes so we have to create our
own subclass.
**/
class NewBigDecimal extends java.math.BigDecimal implements Addable {
    public NewBigDecimal(String val) throws NumberFormatException {
        super(val);
    }
    public Addable addTo(Addable addable) {
        return addable.addToBigDecimal(this);
    }
    public Addable  addToBigDecimal(NewBigDecimal aBigDecimal) {
            //Hack to handle the class conversion between BigDecimal and NewBigDecimal
        return new NewBigDecimal(this.add(aBigDecimal).toString());
    }
    public Addable addToPoint(Point2D aPoint) {
            //just reverse the call because Points are more interesting then BigDecimals
        return aPoint.addToBigDecimal(this);
    }
}

import java.math.*;

class Point2D implements Addable {
    final BigDecimal myX;
    final BigDecimal myY;
    public Point2D(BigDecimal x, BigDecimal y) {
        myX = x;
        myY = y;
    }
    public Addable addTo(Addable addable) {
        return addable.addToPoint(this);
    }
    public Addable  addToBigDecimal(NewBigDecimal aBigDecimal) {
        return new Point2D(
                getX().add(aBigDecimal),
                getY()
            );
    }
    public Addable addToPoint(Point2D aPoint) {
        return new Point2D(
                this.getX().add(aPoint.getX()),
                this.getY().add(aPoint.getY())
            );
    }
    public BigDecimal getX() {
        return myX;
    }
    public BigDecimal getY() {
        return myY;
    }
    public String toString() {
        return "";
    }
}


class PointTest {
    static public void main(String[] args) {

        Point2D p1        = new Point2D(new NewBigDecimal("5"), new NewBigDecimal("6"));
        Point2D p2        = new Point2D(new NewBigDecimal("3.1"), new NewBigDecimal("4.5"));
        NewBigDecimal bd1 = new NewBigDecimal("7.2");
        NewBigDecimal bd2 = new NewBigDecimal("9.1");

        Addable a1 = p2;
        Addable a2 = bd1;
        Addable a3 = bd2;

        System.out.println("P1  = "  + p1);
        System.out.println("P2  = "  + p2);
        System.out.println("BD1 = " + bd1);
        System.out.println("BD2 = " + bd2);

        System.out.println();
        System.out.println("A1 = " + a1);
        System.out.println("A2 = " + a2);
        System.out.println("A3 = " + a3);

        System.out.println();
        System.out.println("*Results*");
        Object result1a = p1.addTo(p2);
        Object result2a = p1.addTo(bd1);
        Object result3a = p1.addTo(p2).addTo(bd1);

        Object result1b = p1.addTo(a1);
        Object result2b = p1.addTo(a2);
        Object result3b = p1.addTo(a1).addTo(a2);


        Object result4 = a2.addTo(a1);
        Object result5 = a3.addTo(a2);

        System.out.println("P1+P2  = "+result1a);
        System.out.println("P1+BD1 = "+result2a);
        System.out.println("P1+P2+BD1 = "+result3a);

        System.out.println("P1+A1 = "+result1b);
        System.out.println("P1+A2 = "+result2b);
        System.out.println("P1+A1+A2 = "+result3b);

        System.out.println("A2+A1 = "+result4);
        System.out.println("A3+A2 = "+result5);


    }
}

Some C++ examples and relatively recent discussions are at:

--Mark
mark.fussell@chimu.com

Subsequent Discussions

Adding more classes

Steve Wart wrote:
> Mark Fussell wrote in message <36B648A9.F98571CC@chimu.com>...
> >Roedy Green wrote:
> >> Would anyone care to post a small code snippet, preferably in Java or
> >> C++, that demonstrates what double dispatching is?
> >
> >OK, I will give a relatively concise one in Java.  Please see the GOF
> >Design Patterns Book, The Smalltalk Design Patterns Companion, Kent
> >Beck's Best Practice Patterns, and so on for more information and more
> >detailed examples.
> >
> [ amazingly comprehensive example deleted ]
Why thank you :-)
> What happens as more classes need to implement the Addable interface?
> Do things get more complex in a combinatorial way?

It isn't combinatorial because you only have a single entry per type of object that you will need for the second dispatch. The number of dispatching operations could be equal to the number of implementation classes (times the number of "actual" operations), but it is likely to be somewhat smaller than that if there is no externally important difference for the particular type's perspective. For example, you could have Colored-2d-Points without extending the the Addable interface.

It does cause a focal point (bottleneck) in that Addable interface: you have to have the union of all dispatching operations within it, so it will be hard to "close" it against future changes. The other option is to keep creating subtypes of the interface ("MoreAddable extends Addable") that have new capabilities, but this then returns us to the starting problem of needing to behave differently depending on what the type of the argument is. Say MoreAddable understands ColoredPoints where Addable only understands Points. Then the staticly bound version would be:

    public ColoredPoint2D addTo(Addable value) {
        return value.addToPoint(this);
    }

    public ColoredPoint2D addTo(MoreAddable value) {
        return value.addToColoredPoint(value);
    }
And so the dynamically bound version would have to do a third dispatch on Addable/MoreAddable or (more likely) a quick typecheck to see if a 'MoreAddable' value was received.
    public Point2D addTo(Addable value) {
        if (anObject instanceof MoreAddable) {
             return value.addToColoredPoint(value);
        } else {
             return value.addToPoint(this);
        }
    }
Which returns us to an ugly (but hopefully unusual) typecheck throughout the code.
> Is this what people are talking about when
> they speak of the strengths of strong typing or is this a contrived example to make a Java
> implementation look much more complex than the Smalltalk equivalent?
I don't think it is a contrived example: this is exactly what the Visitor pattern relies on and the need for that pattern is much more common in strongly typed environments than in Smalltalk. The advantage of the Java version vs. a Smalltalk equivalent is that the possible intercommunication between objects is completely identified and documented. Its disadvantage is that there is a significant increase in work as the possible interactions (including different granularities of operation-bundles [e.g. Types]) increase.

Strong typing certainly becomes more "cumbersome" as flexibility needs to increase and the runtime objects determine more and more of the desired behavior. Generally, this just discourages producing these types of very generic structures (e.g. no general "Magnitude" / Addable in Java).

> Or is it really not that bad, I am just so used to Smalltalk that it seems worse than it is?
It's not *that* bad but it certainly can be bad. Just a lot more work to do what might be a very simple (in Smalltalk) concept. A more sophisticated typed language like Eiffel is a bit better, and "augmentation" (Envy extensions) of existing classes would help a lot, but the tradeoff is always there.
--Mark
mark.fussell@chimu.com

Difficulty of double-dispatch in Java compared to Dylan

Peter wrote:
> Mark Fussell wrote:
> > OK, I will give a relatively concise one in Java.
>
> [huge 9,300 character example deleted]
>
> I'd hate to see a verbose example.  :-)
Well, to be fair, more than half of those characters were actually my narrative. And of the rest, half of it was a test program. The lines involved in the double dispatch for Java were:
{Within Addable}
    Addable     addToBigDecimal(NewBigDecimal aBigDecimal);
    Addable     addToPoint(Point2D aPoint);

{Within NewBigDecimal}
    public Addable addTo(Addable addable) {
        return addable.addToBigDecimal(this);
    }

{Within Point2D}
   public Addable addTo(Addable addable) {
        return addable.addToPoint(this);
    }
For a total of 8 lines, two of which are an interface declaration. The equivalent in Smalltalk would be 6 lines because the interface declaration is not needed. Obviously in Dylan and similar multi-method languages there would be exactly zero lines to do the double dispatch. Of course the double dispatching itself is only a portion of the actual functionality (adding Points to Points and Points to Numbers), although it is much more significant than normal in this particularly simple and focused example.
> It seems like an exercise in frustration to simulate a limited form of
> multi-method disptach in these mono-dispatch language.  Why settle for a
> substitute when you can have the real thing.

This is a more interesting topic: what are the advantages and disadvantages of changing to a multi-dispatch form over a single-dispatch form. Since I have a version of multi-dispatching in Smalltalk, I might as well try to comment on that, but it is a longer and separate post.

[[Rest of discussion snipped... tangential topic]]

--Mark
mark.fussell@chimu.com
 
Publications