小编典典

消费者 映射类 在HashMap中

java

我想创建一个IdentityHashMap<Class<T>, Consumer<T>>。基本上,我想使用一种说明该类型的方法来映射一个类型。

我希望能够动态地说出对象X,执行Y。我可以

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

但它很烂,因为然后我必须在使用它时将对象投射到兰巴中。

例:

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

我想做的是

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

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


阅读 223

收藏
2020-09-21

共1个答案

小编典典

本质上,这与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) {...}
}

其他说明:

  • 我会用一个常规的HashMapLinkedHashMap本。HashMap维护得更好,并且有许多IdentityHashMap没有的优化。

  • 如果有必要使用通用类型(如)Consumer<List<String>>,则需要使用类似Guava TypeToken的键作为键,因为Class它只能表示擦除类型。

  • 番石榴ClassToInstanceMap在需要的时候可以食用Map<Class<T>, T>

有时人们想通过类对消费者的映射来做这样的事情:

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|是指 删除
T。(请参阅JLS§4.3.2。)在上面的示例中,擦除Tis
Object,因此obj.getClass()返回一个plain 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并且没有边界通配符:

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

public class ClassToUnaryOpMap {
    private final Map<Class<?>, UnaryOperator<?>> map =
        new HashMap<>();

    @SuppressWarnings("unchecked")
    public <T> UnaryOperator<T> put(Class<T> key, UnaryOperator<T> op) {
        return (UnaryOperator<T>) map.put(key, op);
    }

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

? super一个示例中的有界通配符特定于消费者,我认为不带通配符的示例可能更易于阅读。

2020-09-21