user3217644
user3217644

Reputation: 97

Alternative to instanceof and if-else statements for iterating over/de-referencing a list of mixed object types

I have an arraylist of non-primitive objects where each object is an instantiation of one of 10 different classes.

I would like to iterate through the list and for each object in the list invoke a method based on the class of the object.

Having gone through posts I see suggestions using casting;if/else; and instanceof that works. However with large number of classes I have I was wondering if there is a more elegant and terse solution.

Any input appreciated.

Upvotes: 2

Views: 3140

Answers (3)

Floegipoky
Floegipoky

Reputation: 3273

The approach I use most often for this type of dispatch in Java is to map class names to their respective handlers.

Base Handler (can be class or interface according to your needs):

interface Handler {
    // Can have a return value if you need it
    public void handle(Object k);
}

Example Implementation for String:

class StringHandler implements Handler {
    public void handle(Object k) {
        if (!(k instanceof String))
            throw new RuntimeException("You messed up your dispatcher!!!");
        String string = (String) k;
        System.out.println(string);
    }
}

// During initialization, wire up the dispatcher
Map<String, Handler> dispatcher = new HashMap<>{{
    put("classname", new HandlerForClassName());
    // more mappings...
}};

// Later
List<Object> objects = new ArrayList<>(); // Whatever you're trying to loop over 
for (Object k : objects)
    if (k != null)
        dispatcher.get(k.getClass()).handle(k);

To explain the last line, you're looking up the classname of k with the dispatcher to get the handler you wired it to. Then you're calling handle(Object) on that handler and passing it k. If you had wired String to StringHandler and k was a string, the value of k would be printed to stdout.

The code that actually cares about dispatching the objects to their handlers never has to change to accommodate more handlers/object types.

Note that all of the caveats of double brace initialization apply here. The short version is don't pass it around willy-nilly. Or you can just do it the long way.

Also note that this kind of runtime dispatch allows for a lot of flexibility, including modification on-the-fly.

// You can do this at any time
dispatcher.put("classname", new DifferentDispatcher());

Upvotes: 0

ZakiMak
ZakiMak

Reputation: 2102

There is also another approach with inheritance. You can make all the classes extend a Parent class like below given your classes don't already extend anything else:

public class Parent{
  public void doSomething(){
   //do generic stuff here 
 }
}

Then override it in the child class if needed:

public class Child extends Parent{

public void doSomething(){
//do child specific if needed else don't redefine if Parent method is okay for the child.
}

}

Once all list items extend this class, each can call doSomething() to do the job. (Iterate over the collection of Parent items.)

Upvotes: 0

Christian Hujer
Christian Hujer

Reputation: 17985

I'd indeed avoid such chains of instanceof-checks. In fact, I'd avoid chains of if/else or switch in general (exceptions apply).

What we often have, if we have a chain of instanceof-checks is misplaced responsibility and a lack of polymorphism.

Imagine the following code:

class Type1 {}
class Type2 {}
class Type3 {}

class TypeProcessor {
    public void processObjets() {
        final List<Object> objects = getObjectList();
        for (final Object o : objects) {
            if (o instanceof Type1) {
                processType1((Type1) o);
            } else if (o instanceof Type2) {
                processType2((Type2) o);
            } else if (o instanceof Type2) {
                processType2((Type2) o);
            }
        }
    }
    public void processType1(final Type1 o) {
        /* Process for Type 1. */
    }
    public void processType2(final Type2 o) {
        /* Process for Type 2. */
    }
    public void processType3(final Type3 o) {
        /* Process for Type 3. */
    }
}

This can be refactored like this:

interface Type {
    void process();
}

class Type1 implements Type {
    public void process() {
        /* Process for Type 1. */
    }
}

class Type2 implements Type {
    public void process() {
        /* Process for Type 2. */
    }
}

class Type3 implements Type {
    public void process() {
        /* Process for Type 3. */
    }
}

class TypeProcessor {
    public void processObjects() {
        final List<Type> objects = getObjectList();
        for (final Type o : objects) {
            o.process();
        }
    }
}

Upvotes: 8

Related Questions