Chad
Chad

Reputation: 2071

Make Java consider the most specific type when choosing method among overloaded methods

I would like to perform several operations based on the type of an object and without using instanceof. At first I was thinking of overloading methods based on types (as seen below), and thought that maybe Java would choose the method appropriately (based on most specific class of object).

import java.util.ArrayList;
import java.util.List;


public class TestA {

    public static void main(String[] args)
    {
        List<Object> list = new ArrayList();
        list.add(new A());
        list.add(new B());
        list.add(new C());
        list.add(new Object());

        TestA tester = new TestA();

        for(Object o: list)
        {
            tester.print(o);
        }
    }

    private void print(A o)
    {
        System.out.println("A");
    }

    private void print(B o)
    {
        System.out.println("B");
    }

    private void print(C o)
    {
        System.out.println("C");
    }

    private void print(Object o)
    {
        System.out.println("Object");
    }
}

class A {

}

class B extends A {

}

class C {

}

The output is:

Object
Object
Object
Object

However the output I'm after is:

A
B
C
Object

Upvotes: 8

Views: 2552

Answers (9)

wandering-geek
wandering-geek

Reputation: 1380

Well my answer would be to make classes A,B and C implement a common interface.

And then each of them can have their own specific implementations of this interface.

This way, you can call the same method for all the objects(thus avoiding overloaded methods), and also ensure custom functionality based on the type of the object(i.e the class from which it was instantiated).

Upvotes: 11

m0skit0
m0skit0

Reputation: 25873

This answer considers that modifying given classes and their relationship is not an option.

Is there a way to make Java choose the method based on the most specific type of the parameter object?

Java compiler cannot cast this for you because maybe that's not what you want (in your case that's what you want, but maybe other people don't want this behavior and they would be no solution for them). Since you're passing an Object reference to print(), the compiler will call print(Object).

If not, what alternatives can I look at for such functionality, without the aid of instanceof

You can wrap your Object with its type in a wrapper class, e.g.:

public class ObjectWrapper {
    private final Object object;
    private final int type;
}

This way you can safely cast to the type of the Object without using instanceof. Although this IMHO is more complicated than simply using instanceof, and actually it only creates your own instanceof...

I'm honestly against instanceof because I've read using it is bad practice; in this case, would it still be bad practice?

In programming, nothing is always a bad practice. Everything depends on the situation. In this situation I think you can use instanceof and do unsafe casting because the design requires so. And anyway, if instanceof operator exists in the language, is because it's used. If instanceof was always a bad practice, it wouldn't exist as part of the language. See this and this.

I was actually trying to simulate the visitor pattern however it seems, what makes visitor pattern work is because of the double dispatch, which makes the parameter being "accepted" be in the correct type during function call, particularly, visitor.accept(this) in class A causes the function visitor.accept(A o) be called.

Check my second link for cons on using the visitor pattern.

Upvotes: 5

overthink
overthink

Reputation: 24443

I would just use instanceof here. It's simple and clear.

It's not like there is a hard rule to not use instanceof. There are no hard rules :) Lots of use of instanceof may be a sign that you could change things to make the compiler do more work for you. Whether that is actually worth doing needs to be looked at case by case. In your case you mention you aren't able to change the classes in question, so it's not even an option.

Upvotes: 1

Bernd Ebertz
Bernd Ebertz

Reputation: 1327

From my point of view it's not the question whether or not use instanceof. The question is how to minimise the impact gained by this situation. So I would recommend instance adapter here in case there are several operations to deal with. That way you can at least avoid spreading that complexity all over the system:

public abstract class Adapter {
  public abstract void print();

  public static Adapter wrap(Object o) {
    if (o instanceof A) {
      return new AAdapter();
    }
    if (o instanceof B) {
      return new BAdapter();
    }
    return new ObjectAdapter():
}

public class AAdapter {
  public void print() {
    System.out.printLn("A");
  }
}

Then loop:

for (Object o: things) {
  Adapter.wrap(o).print();
}

Of course our Adapter will have a reference to the wrapped object as the method name implies. That way you get a coding model close to direct modification of those classes.

Upvotes: 0

Diego Urenia
Diego Urenia

Reputation: 1630

What pops out of my mind without use instanceof now is:

  • Use a common interface for all of them to share a common method:

Code:

public interface YourInterface {
    public void doStuff();
}

public class A implements YourInterface {
    @Override
    public doStuff() {
        System.out.print("A");
    }
}

public class B implements YourInterface {
 @Override
    public doStuff() {
        System.out.print("A");
    }
}

List<YourInterface> list = new ArrayList<YourInterface>();
    list.add(new A());
    list.add(new B());

for(YourInterface e: list)
{
    e.doStuff();
}        
  • Use an Enum declaring an abstract method tha will be implemented by every type you have:

Code:

public enum YourEnum { 
    TYPE1 {
    @Override
    public void doStuff() {
        System.out.print("Type 1"); 
    },

    TYPE2 {
    @Override
    public void doStuff() {
        System.out.print("Type 2");         
    };

    public abstract void doStuff();
}

List<YourEnum> list = new ArrayList<YourEnum>();
    list.add(YourEnum.TYPE1);
    list.add(YourEnum.TYPE2);

for(YourEnum e: list)
{
    e.doStuff();
}        

Upvotes: 0

Abhishek Singh
Abhishek Singh

Reputation: 9755

After reading above answer

One solution which i can suggest would be to use check the object type by using o.getClass and continue

However many better solutions are posted. I totally agree with what @krisna kishor shetty said

Upvotes: 0

Evgeniy Dorofeev
Evgeniy Dorofeev

Reputation: 136012

Consider Visitor design pattern http://en.wikipedia.org/wiki/Visitor_pattern. Though it will need changes to A,B,C otherwise there is no other way.

Upvotes: 2

Ankit
Ankit

Reputation: 1250

    package com.ank.says;
    import java.util.ArrayList;
    import java.util.List;

    class Base{
        public void print(){
            System.out.println("Base");
        }

    }

    class A extends Base{
        @Override
        public void print() {
            System.out.println("A");
        }
    }

    class B extends Base{
        @Override
        public void print(){
            System.out.println("B");
        }
    }

    class C extends Base{
        @Override
        public void print(){
            System.out.println("C");
        }
    }



    public class TestPlayGround {

        public static void main(String[] args) throws ParseException {
              List<Base> list = new ArrayList<Base>();
                list.add(new A());
                list.add(new B());
                list.add(new C());
                list.add(new Base());

                TestPlayGround tester = new TestPlayGround();

                for(Base o: list)
                {
                    o.print();
                }

        }


    }

Output : - A B C Base

Upvotes: 0

Aditya Jain
Aditya Jain

Reputation: 1095

Method Overloading is a compile time polymorphism, in your for loop , you have declared o as Object and hence always print(Object o) will be called.

Solution: Use Dynamic Polymorphism:

class A{

  void print(){
     System.out.println('in A');
  }
}

class B{

  void print(){
     System.out.println('in B');
  }
}

and the for loop

 for(Object o: list){
     o.print();
 }

Upvotes: 3

Related Questions