Anything wrong with instanceof checks here?

2020-03-24 07:23发布

问题:

With the introduction of generics, I am reluctant to perform instanceof or casting as much as possible. But I don't see a way around it in this scenario:

for (CacheableObject<ICacheable> cacheableObject : cacheableObjects) {
    ICacheable iCacheable = cacheableObject.getObject();
    if (iCacheable instanceof MyObject) {
        MyObject myObject = (MyObject) iCacheable;
        myObjects.put(myObject.getKey(), myObject);
    } else if (iCacheable instanceof OtherObject) {
        OtherObject otherObject = (OtherObject) iCacheable;
        otherObjects.put(otherObject.getKey(), otherObject);
    }
}

In the above code, I know that my ICacheables should only ever be instances of MyObject, or OtherObject, and depending on this I want to put them into 2 separate maps and then perform some processing further down.

I'd be interested if there is another way to do this without my instanceof check.

Thanks

回答1:

You could use double invocation. No promises it's a better solution, but it's an alternative.

Code Example

import java.util.HashMap;

public class Example {

    public static void main(String[] argv) {
        Example ex = new Example();
        ICacheable[] cacheableObjects = new ICacheable[]{new MyObject(), new OtherObject()};

        for (ICacheable iCacheable : cacheableObjects) {
            // depending on whether the object is a MyObject or an OtherObject,
            // the .put(Example) method will double dispatch to either
            // the put(MyObject) or  put(OtherObject) method, below
            iCacheable.put(ex);
        }

        System.out.println("myObjects: "+ex.myObjects.size());
        System.out.println("otherObjects: "+ex.otherObjects.size());
    }

    private HashMap<String, MyObject> myObjects = new HashMap<String, MyObject>();
    private HashMap<String, OtherObject> otherObjects = new HashMap<String, OtherObject>();

    public Example() {

    }

    public void put(MyObject myObject) {
        myObjects.put(myObject.getKey(), myObject);
    }

    public void put(OtherObject otherObject) {
        otherObjects.put(otherObject.getKey(), otherObject);
    }

}

interface ICacheable {
    public String getKey();
    public void put(Example ex);
}

class MyObject implements ICacheable {

    public String getKey() {
        return "MyObject"+this.hashCode();
    }

    public void put(Example ex) {
        ex.put(this);
    }
}

class OtherObject implements ICacheable {

    public String getKey() {
       return "OtherObject"+this.hashCode();
    }

    public void put(Example ex) {
        ex.put(this);
    }

}

The idea here is that - instead of casting or using instanceof - you call the iCacheable object's .put(...) method which passes itself back to the Example object's overloaded methods. Which method is called depends on the type of that object.

See also the Visitor pattern. My code example smells because the ICacheable.put(...) method is incohesive - but using the interfaces defined in the Visitor pattern can clean up that smell.

Why can't I just call this.put(iCacheable) from the Example class?

In Java, overriding is always bound at runtime, but overloading is a little more complicated: dynamic dispatching means that the implementation of a method will be chosen at runtime, but the method's signature is nonetheless determined at compile time. (Check out the Java Language Specification, Chapter 8.4.9 for more info, and also check out the puzzler "Making a Hash of It" on page 137 of the book Java Puzzlers.)



回答2:

Is there no way to combine the cached objects in each map into one map? Their keys could keep them separated so you could store them in one map. If you can't do that then you could have a

Map<Class,Map<Key,ICacheable>>

then do this:

Map<Class,Map<Key,ICacheable>> cache = ...;

public void cache( ICacheable cacheable ) {
   if( cache.containsKey( cacheable.getClass() ) {
      cache.put( cacheable.getClass(), new Map<Key,ICacheable>() );
   }
   cache.get(cacheable.getClass()).put( cacheable.getKey(), cacheable );
}


回答3:

You can do the following:

  1. Add a method to your ICachableInterface interface that will handle placing the object into one of two Maps, given as arguments to the method.
  2. Implement this method in each of your two implementing classes, having each class decide which Map to put itself in.
  3. Remove the instanceof checks in your for loop, and replace the put method with a call to the new method defined in step 1.

This is not a good design, however, because if you ever have another class that implements this interface, and a third map, then you'll need to pass another Map to your new method.



标签: java oop