3

I am looking for some magical Map-like utility that, given a type, return the value associated with the type or its closest super-type. This is to replace statements like

if (... instanceof A) return valueA;
else if (... instanceof B) return valueB;
...

I have read the answers to Avoiding instanceof in Java, which suggested a number of patterns, in particular, the Visitor pattern. However, since the goal is to return a simple value, implementing the visitors seems to be an overkill.

Unfortunately, the new JDK class ClassValue also don't qualify as it does not check super-types.

I just want to check if such utility exists in any well-known library before I roll my own. The implementation should be thread-safe and hopefully has les than linear cost w.r.t. the number of values inserted.

Community
  • 1
  • 1
billc.cn
  • 7,187
  • 3
  • 39
  • 79

3 Answers3

4

A map will do. If you want class inheritance, you'll need to walk upwards.

private final Map<Class<?>, Object> map = HashMap<>();

public void register(Class<?> clazz, Object value) {
    map.put(clazz, value);
}

public Object getValue(Class<?> clazz) {
    if (clazz == null) {
        return null;
    }
    Object value = map.get(clazz);
    if (value == null) {
        clazz = clazz.getSuperclass(); // May be null.
        return getValue(clazz);
    }
}

This will do nicely for int.class etcetera.

If there exists a relation between value and class:

public <T> T getValue(Class<T> clazz) {
    Object value = map.get(clazz);
    return clazz.cast(value);
}
Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • 2
    But this becomes a lot more messy if interfaces have to be considered. I tried to use Common Lang's `ClassUtils.hierarchy`, but the behaviour starts to differ from the `instanceof` chain as one cannot control the order the interfaces are checked. I have a feeling the chain might still be the most maintainable solution..... – billc.cn Aug 26 '15 at 14:59
  • It occurred to me I can add all the interfaces of a given type in the `register` method, register all the types in reverse order (compared to the `if`s), and search the map very carefully... Still a lot of work – billc.cn Aug 26 '15 at 16:01
  • Re: "But this becomes a lot more messy if interfaces have to be considered": If you consider interfaces, then Java supports multiple inheritance, and you've reintroduced [the diamond problem](https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem). Restricting yourself to subclass-superclass relationships, just like how ordinary virtual methods work, is a better strategy. – ruakh Aug 28 '15 at 00:19
2

You can remove most of the cruft using an interface with a default method to do the map lookup. You still have to register each class somehow - I've used a static initializer, you may have a better method in mind.

interface HasClassValue {

    // My big map.
    static final Map<Class<?>, Long> values = new HashMap<>();

    default Optional<Long> value() {
        Class<?> c = this.getClass();
        do {
            // Climb the class tree.
            Long value = values.get(c);
            if (value != null) {
                // Found it.
                return Optional.of(value);
            }
            // Up!
            c = c.getSuperclass();
        } while (c != null);
        // Not found.
        return Optional.empty();
    }

    // Maintain the map.
    public static void register(Class<?> c, long value) {
        values.put(c, value);
    }
}

static class X implements HasClassValue {

    static {
        HasClassValue.register(X.class, 100);
    }
}

static class Y extends X {

    static {
        HasClassValue.register(Y.class, 200);
    }
}

static class Z extends Y {
}

public void test() {
    X x = new X();
    Y y = new Y();
    Z z = new Z();
    System.out.println("X - " + x.value());
    System.out.println("Y - " + y.value());
    System.out.println("Z - " + z.value());
}
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
1

I have solved the problem in my project with below solution.

GenericClass object = (GenericClass) Class.forName(specificClassName).newInstance();

I defined one GenericClass ( Base class). I have many concrete implementations of those generic class. Specific concrete class will be loaded with className I pass as parameter.

Ravindra babu
  • 37,698
  • 11
  • 250
  • 211
  • Is it your goal to return the child class, which you then use *as* the parent class? – Makoto Aug 26 '15 at 14:57
  • Yes. Base class is abstract. – Ravindra babu Aug 26 '15 at 15:00
  • Why wouldn't you want to use the child class, though? Is there behavior in the parent class that you need to preserve? – Makoto Aug 26 '15 at 15:00
  • Parent had state of the object apart from abstract methods. Concrete classes implements specific rules on state stored in parent. – Ravindra babu Aug 26 '15 at 15:02
  • Okay, I think I understand your rationale. I'm not saying that I particularly agree with this design, as I think that it's a bit convoluted and you're relying on inheritance to provide state... – Makoto Aug 26 '15 at 15:04
  • I don't quite understand this answer, is this trying to implement `Class` to `T`? – billc.cn Aug 26 '15 at 15:09
  • I have used this method in pre generics era - around 15 years back. At that time, it's only the way for me – Ravindra babu Aug 26 '15 at 15:24