Overloading in Java and multiple dispatch
Asked Answered
C

6

30

I have a collection (or list or array list) in which I want to put both String values and double values. I decided to make it a collection of objects and using overloading ond polymorphism, but I did something wrong.

I run a little test:

public class OOP {
    void prova(Object o){
        System.out.println("object");
    }

    void prova(Integer i){
    System.out.println("integer");
    }

    void prova(String s){
        System.out.println("string");
    }

    void test(){
        Object o = new String("  ");
        this.prova(o); // Prints 'object'!!! Why?!?!?
    }

    public static void main(String[] args) {
        OOP oop = new OOP();
        oop.test(); // Prints 'object'!!! Why?!?!?
    }
}

In the test seems like the argument type is decided at compile time and not at runtime. Why is that?

This question is related to:

Polymorphism vs Overriding vs Overloading
Try to describe polymorphism as easy as you can

EDIT:

Ok the method to be called is decided at compile time. Is there a workaround to avoid using the instanceof operator?

Cut answered 18/3, 2012 at 14:32 Comment(0)
J
21

This post seconds voo's answer, and gives details about/alternatives to late binding.

General JVMs only use single dispatch: the runtime type is only considered for the receiver object; for the method's parameters, the static type is considered. An efficient implementation with optimizations is quite easy using method tables (which are similar to C++'s virtual tables). You can find details e.g. in the HotSpot Wiki.

If you want multiple dispatch for your parameters, take a look at

  • groovy. But to my latest knowledge, that has an outdated, slow multiple dispatch implementation (see e.g. this performance comparison), e.g. without caching.
  • clojure, but that is quite different to Java.
  • MultiJava, which offers multiple dispatch for Java. Additionally, you can use
    • this.resend(...) instead of super(...) to invoke the most-specific overridden method of the enclosing method;
    • value dispatching (code example below).

If you want to stick with Java, you can

  • redesign your application by moving overloaded methods over a finer grained class hierarchy. An example is given in Josh Bloch's Effective Java, Item 41 (Use overloading judiciously);
  • use some design patterns, such as Strategy, Visitor, Observer. These can often solve the same problems as multiple dispatch (i.e. in those situations you have trivial solutions for those patterns using multiple dispatch).

Value dispatching:

class C {
  static final int INITIALIZED = 0;
  static final int RUNNING = 1;
  static final int STOPPED = 2;
  void m(int i) {
    // the default method
  }
  void m(int@@INITIALIZED i) {
    // handle the case when we're in the initialized `state'
  }
  void m(int@@RUNNING i) {
    // handle the case when we're in the running `state'
  }
  void m(int@@STOPPED i) {
    // handle the case when we're in the stopped `state'
  }
}
Jacobina answered 18/3, 2012 at 16:38 Comment(5)
Multijava is very interesting, but the last version is of the year 2006. Josh Bloch's Effective Java just says that you have to create many classes with just one method, that is something I would avoid. Patterns are the way to go.Cut
Oh - interesting and sad that Multijava is no longer maintained. I guess the guys are more focusing on other projects like the Java Modeling Language. About the example from Effective Java: can't you transfer that to more general cases? E.g. if you need to distinct 3 behaviors in your example, use a GeneralDecorator and two subclasses NumberDecorator and TextualDecorator? Then you only need OOP.prova(GeneralDecorator), which calls someMethod() from GeneralDecorator, which is single dispatched. Or would you call that a pattern already (e.g. inversion of control)?Jacobina
Btw, here is a blog post on how to solve your problem in Scala, which only has single dispatch, too. The solution is not possible in Java, though, since it uses Scala's traits.Jacobina
One further problem comes to my mind: I would have thought that Java's generics with type erasure are incompatible with multiple dispatching. Glancing over plrg.kaist.ac.kr/_media/research/publications/oopsla11.pdf, it somehow seems to work :-0Jacobina
Yes I think that Decorator Pattern is what I need. :) Scala is a very good language and I hope that it will become more mainstream. About generics and type erasure, I don't know type erasure, I would have to study a little bit to understand that article.Cut
Q
17

What you want is double or more general multiple dispatch, something that is actually implemented in other languages (common lisp comes to mind)

Presumably the main reason java doesn't have it, is because it comes at a performance penalty because overload resolution has to be done at runtime and not compile time. The usual way around this is the visitor pattern - pretty ugly, but that's how it is.

Qktp answered 18/3, 2012 at 14:49 Comment(4)
I second your answer (+1), though I'm not sure today's compiler techniques are much slower for multiple dispatch (see my answer below). Do you happen to have any current statistics on that?Jacobina
@DaveBall Haven't run any tests and I certainly doubt that it's "much slower" in most situations. But it certainly adds an additional level of complexity to the optimization and I can think of common situations where we'd get a virtual call instead of a direct call. Lots of Java classes implement an interface as the only class - that allows the usual optimization. This relies on the fact that lots of interfaces are implemented by only one class. Contrary to this, most methods take rather general parameters where there exist more than one applicable subclass (eg all collection classes)Qktp
cont. So we do get some overhead, it basically boils down to some if cascade and loading the class for all parameters (that's rather cheap though) at runtime. I don't think it affects the general case though where we don't need multiple dispatch and in cases where we do it avoids an annoying class of bugs and I really abhor the visitor pattern..Qktp
I found your answer very useful for my particular case. But what I didn't understand was the reason you said that visitor-style implementation is pretty ugly. As we can see in wiki it's one of its purposes, to implement double-dispatch.Batey
M
9

Old question but no answer provides a concrete solution in Java to solve the issue in a clean way.
In fact, not easy but very interesting question. Here is my contribution.

Ok the method to be called is decided at compile time. Is there a workaround to avoid using the instanceof operator?

As said in the excellent @DaveFar answer, Java supports only the single-dispatch method.
In this dispatching mode, the compiler bounds the method to invoke as soon as the compilation by relying on the declared types of the parameters and not their runtime types.

I have a collection (or list or array list) in which I want to put both String values and double values.

To solve the answer in a clean way and use a double dispatch, we have to bring abstraction for the manipulated data.
Why ?

Here a naive visitor approach to illustrate the issue :

public class DisplayVisitor {

    void visit(Object o) {
        System.out.println("object"));
    }

    void visit(Integer i) {
        System.out.println("integer");
    }

    void visit(String s) {
        System.out.println("string"));
    }

}

Now, question : how visited classes may invoke the visit() method ?
The second dispatch of the double dispatch implementation relies on the "this" context of the class that accepts to be visited.
So we need to have a accept() method in Integer, String and Object classes to perform this second dispatch :

public void accept(DisplayVisitor visitor){
    visitor.visit(this);
}

But impossible ! Visited classes are built-in classes : String, Integer, Object.
So we have no way to add this method.
And anyway, we don't want to add that.

So to implement the double dispatch, we have to be able to modify the classes that we want to pass as parameter in the second dispatch.
So instead of manipulating Object and List<Object> as declared type, we will manipulate Foo and List<Foo> where the Foo class is a wrapper holding the user value.

Here is the Foo interface :

public interface Foo {
    void accept(DisplayVisitor v);
    Object getValue();
}

getValue() returns the user value.
It specifies Object as return type but Java supports covariance returns (since the 1.5 version), so we could define a more specific type for each subclass to avoid downcasts.

ObjectFoo

public class ObjectFoo implements Foo {

    private Object value;

    public ObjectFoo(Object value) {
        this.value = value;
    }

    @Override
    public void accept(DisplayVisitor v) {
        v.visit(this);
    }

    @Override
    public Object getValue() {
        return value;
    }

}

StringFoo

public class StringFoo implements Foo {

    private String value;

    public StringFoo(String string) {
        this.value = string;
    }

    @Override
    public void accept(DisplayVisitor v) {
        v.visit(this);
    }

    @Override
    public String getValue() {
        return value;
    }

}

IntegerFoo

public class IntegerFoo implements Foo {

    private Integer value;

    public IntegerFoo(Integer integer) {
        this.value = integer;
    }

    @Override
    public void accept(DisplayVisitor v) {
        v.visit(this);
    }

    @Override
    public Integer getValue() {
        return value;
    }

}

Here is the DisplayVisitor class visiting Foo subclasses :

public class DisplayVisitor {

    void visit(ObjectFoo f) {
        System.out.println("object=" + f.getValue());
    }

    void visit(IntegerFoo f) {
        System.out.println("integer=" + f.getValue());
    }

    void visit(StringFoo f) {
        System.out.println("string=" + f.getValue());
    }

}

And here is a sample code to test the implementation :

public class OOP {

    void test() {

        List<Foo> foos = Arrays.asList(new StringFoo("a String"),
                                       new StringFoo("another String"),
                                       new IntegerFoo(1),
                                       new ObjectFoo(new AtomicInteger(100)));

        DisplayVisitor visitor = new DisplayVisitor();
        for (Foo foo : foos) {
            foo.accept(visitor);
        }

    }

    public static void main(String[] args) {
        OOP oop = new OOP();
        oop.test();
    }
}

Output :

string=a String

string=another String

integer=1

object=100


Improving the implementation

The actual implementation requires the introduction of a specific wrapper class for each buit-in type we want to wrap. As discussed, we don't have the choice to operate a double dispatch.
But note that the repeated code in Foo subclasses could be avoided :

private Integer value; // or String or Object

@Override
public Object getValue() {
    return value;
}

We could indeed introduce a abstract generic class that holds the user value and provides an accessor to :

public abstract class Foo<T> {

    private T value;

    public Foo(T value) {
        this.value = value;
    }

    public abstract void accept(DisplayVisitor v);

    public T getValue() {
        return value;
    }

}

Now Foo sublasses are lighter to declare :

public class IntegerFoo extends Foo<Integer> {

    public IntegerFoo(Integer integer) {
        super(integer);
    }

    @Override
    public void accept(DisplayVisitor v) {
        v.visit(this);
    }

}

public class StringFoo extends Foo<String> {

    public StringFoo(String string) {
        super(string);
    }

    @Override
    public void accept(DisplayVisitor v) {
        v.visit(this);
    }

}

public class ObjectFoo extends Foo<Object> {

    public ObjectFoo(Object value) {
        super(value);
    }

    @Override
    public void accept(DisplayVisitor v) {
        v.visit(this);
    }

}

And the test() method should be modified to declare a wildcard type (?) for the Foo type in the List<Foo> declaration.

void test() {

    List<Foo<?>> foos = Arrays.asList(new StringFoo("a String object"),
                                      new StringFoo("anoter String object"),
                                      new IntegerFoo(1),
                                      new ObjectFoo(new AtomicInteger(100)));

    DisplayVisitor visitor = new DisplayVisitor();
    for (Foo<?> foo : foos) {
        foo.accept(visitor);
    }

}

In fact, if really needed, we could simplify further Foo subclasses by introducing java code generation.

Declaring this subclass :

public class StringFoo extends Foo<String> {

    public StringFoo(String string) {
        super(string);
    }

    @Override
    public void accept(DisplayVisitor v) {
        v.visit(this);
    }

}

could as simple as declaring a class and adding an annotation on:

@Foo(String.class)
public class StringFoo { }

Where Foo is a custom annotation processed at compile time.

Musicale answered 24/12, 2017 at 21:25 Comment(0)
P
4

When calling a method that is overloaded, Java picks the most restrictive type based on the type of the variable passed to the function. It does not use the type of the actual instance.

Plover answered 18/3, 2012 at 14:38 Comment(0)
A
1

this isn't polymoprhism, you've simply overloaded a method and called it with parameter of object type

Apelles answered 18/3, 2012 at 14:37 Comment(1)
According to Wikipedia function overloading is Ad hoc polymorphism.Bodhisattva
I
1

Everything in Java is an Object/object (except primitive types). You store strings and integers as objects, and then as you call the prove method they are still referred to as objects. You should have a look at the instanceof keyword. Check this link

void prove(Object o){
   if (o instanceof String)
    System.out.println("String");
   ....
}
Interrelate answered 18/3, 2012 at 14:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.