Consumer <t>在HashMap

时间:2017-06-07 21:01:26

标签: java dictionary generics java-8 type-safety

我想创建一个IdentityHashMap<Class<T>, Consumer<T>>。基本上,我想用一种方法来映射一个类型,说明如何处理这种类型。

我想动态地能够用对象X说出来,执行Y.我可以做到

private IdentityHashMap<Class<?>, Consumer<?>> interceptor = new IdentityHashMap<>();

但它很糟糕,因为我在使用它时必须在lamba中投射对象。

示例:

interceptor.put(Train.class, train -> {
    System.out.println(((Train)train).getSpeed());
});

我想做的是

private <T> IdentityHashMap<Class<T>, Consumer<T>> interceptor = new IdentityHashMap<>();

但似乎不允许这样做。有没有办法做到这一点 ?使用此类型的方法映射类型的最佳解决方法是什么?

3 个答案:

答案 0 :(得分:7)

这基本上与type-safe heterogeneous container described by Joshua Bloch类似,但您无法使用Class投射结果。

奇怪的是,我无法在SO上找到一个很好的例子,所以这里有一个:

package mcve;
import java.util.*;
import java.util.function.*;

class ClassToConsumerMap {
    private final Map<Class<?>, Consumer<?>> map =
        new HashMap<>();

    @SuppressWarnings("unchecked")
    public <T> Consumer<? super T> put(Class<T> key, Consumer<? super T> c) {
        return (Consumer<? super T>) map.put(key, c);
    }

    @SuppressWarnings("unchecked")
    public <T> Consumer<? super T> get(Class<T> key) {
        return (Consumer<? super T>) map.get(key);
    }
}

这是类型安全的,因为键和值之间的关系是由put方法的签名强制执行的。

关于Java泛型的局限性的一个令人讨厌的事情是,这些容器中的一个不能为通用值类型编写,因为没有办法做到,例如:

class ClassToGenericValueMap<V> {
    ...
    public <T> V<T> put(Class<T> key, V<T> val) {...}
    public <T> V<T> get(Class<T> key) {...}
}

其他说明:

  • 我会使用常规HashMapLinkedHashMapHashMap得到了更好的维护,并且IdentityHashMap无法进行许多优化。

  • 如果需要使用通用类型,例如Consumer<List<String>>,那么您需要使用类似Guava TypeToken的内容作为关键字,因为Class只能代表一种类型的擦除。

  • 当您需要Map<Class<T>, T>时,番石榴有ClassToInstanceMap

有时人们希望通过类到消费者的地图来做这样的事情:

public <T> void accept(T obj) {
   Consumer<? super T> c = get(obj.getClass());
   if (c != null)
       c.accept(obj);
}

也就是说,给定任何对象,找到绑定到该对象的类的地图中的消费者,并将该对象传递给消费者的accept方法。

该示例不会编译,因为getClass()实际上已指定返回Class<? extends |T|>,其中|T|表示{em>删除 { {1}}。 (请参阅JLS §4.3.2。)在上面的示例中,T的删除为T,因此Object返回普通obj.getClass()

可以使用capturing helper method

解决此问题
Class<?>

此外,如果您希望修改版本的public void accept(Object obj) { accept(obj.getClass(), obj); } private <T> void accept(Class<T> key, Object obj) { Consumer<? super T> c = get(key); if (c != null) c.accept(key.cast(obj)); } 返回任何适用的消费者,您可以使用以下内容:

get

这让我们可以将普通超类消费者放在地图中,如下所示:

public <T> Consumer<? super T> findApplicable(Class<T> key) {
    Consumer<? super T> c = get(key);
    if (c == null) {
        for (Map.Entry<Class<?>, Consumer<?>> e : map.entrySet()) {
            if (e.getKey().isAssignableFrom(key)) {
                @SuppressWarnings("unchecked")
                Consumer<? super T> value =
                    (Consumer<? super T>) e.getValue();
                c = value;
                break;
            }
        }
    }
    return c;
}

然后使用子类型检索:

ctcm.put(Object.class, System.out::println);

这是一个稍微更一般的例子,这次使用Consumer<? super String> c = ctcm.findApplicable(String.class); c.accept("hello world"); 并且没有有界通配符:

UnaryOperator

The ? super bounded wildcard in the first example is specific to consumers,我认为没有通配符的例子可能更容易阅读。

答案 1 :(得分:6)

可以以类型安全的方式实现此功能,而无需任何未经检查的强制转换。解决方案在于将Consumer<T>包装到一个更通用的Consumer<Object>中,然后委托给原始的消费者:

public class ClassToConsumerMap {
    private final Map<Class<?>, Consumer<Object>> map = new IdentityHashMap<>();

    public <T> Consumer<? super T> put(Class<T> key, Consumer<? super T> c) {
        return map.put(key, o -> c.accept(key.cast(o)));
    }

    public <T> Consumer<? super T> get(Class<T> key) {
        return map.get(key);
    }
}

根据您的需要,get()也可以简单地返回Consumer<Object>。如果您只在运行时知道类型,那将是必要的,例如

classToConsumerMap.get(someObject.getClass()).accept(someObject);

我很确定我在2016年@ Devoxx比利时的演讲中看到了这个解决方案(或类似的东西),可能来自Venkat Subramaniam,但我肯定找不到它......

答案 2 :(得分:1)

我可以让IdentityHashMap与通常Class<?>Consumer<?>

一起
private IdentityHashMap<Class<?>, Consumer<?>> interceptor = new IdentityHashMap<>();

然后我将put操作包装在一个方法中。此方法接受相同泛型的类型和使用者。

public <T> void intercept(Class<T> type, Consumer<T> consumer)
{
    interceptor.put(type, consumer);
}

这让我写

intercept(Train.class, train -> {
    System.out.println(train.getSpeed());
});