简体   繁体   English

Java - 将泛型类型映射到所述类型的使用者

[英]Java - Map generic type to consumer of said type

I ran into this problem recently when I was playing around. 我最近在玩的时候遇到了这个问题。 Basically, I wanted to make a central class where consumers of a certain type could register themselves. 基本上,我想建立一个中心阶级,某种类型的消费者可以自己注册。 Something then publishes objects for the consumers, and only those consuming the published type should receive it. 然后,某些东西为消费者发布对象,只有那些使用已发布类型的对象才能接收它。

The entire program can be summarised to this: 整个计划可归纳为:

public class Main
{
    public interface Fruit
    {
    }

    public class Apple implements Fruit
    {
    }

    public class Banana implements Fruit
    {
    }

    public interface FruitConsumer<T extends Fruit>
    {
        public void consume(T fruit);
    }

    public class Consumers<T extends Fruit>
    {
        Map<Class<T>, Collection<FruitConsumer<T>>> consumers = new HashMap<>();

        public void register(Class<T> clazz, FruitConsumer<T> consumer)
        {
            consumers.putIfAbsent(clazz, new HashSet<>());
            consumers.get(clazz).add(consumer);
        }

        public void consume(T fruit)
        {
            Collection<FruitConsumer<T>> cons = consumers.get(fruit.getClass());
            for (FruitConsumer<T> c : cons)
            {
                c.consume(fruit); // <-- Breaks if T is Apple
            }
        }
    }

    public class BananaConsumer implements FruitConsumer<Banana>
    {
        void register(Consumers c)
        {
            c.register(Banana.class, this);
            c.register(Apple.class, this); // <-- Why does this work?
        }

        @Override
        public void consume(Banana banana)
        {
            System.out.println("Mmm, banana");
        }
    }

    public static void main(String... args)
    {
        Main main = new Main();
        main.run();
    }

    private void run()
    {
        Consumers consumers = new Consumers<>();
        BananaConsumer bananaConsumer = new BananaConsumer();

        bananaConsumer.register(consumers);

        Banana banana = new Banana();
        consumers.consume(banana);

        Apple apple = new Apple();
        consumers.consume(apple);
    }
}

Now, I think I understand why this crashes. 现在,我想我明白为什么会崩溃。 There is no way for the compiler to know that the T in the Consumers.register method is the same T , for both parameters. 有没有办法让编译器知道TConsumers.register方法是一样T ,这两个参数。 The compiler can only enforce that both arguments meets the requirement T extends Fruit . 编译器只能强制两个参数都满足T extends Fruit的要求。 I want to remember being able to use a similar code structure in C++, so there has to be something the two languages does different. 我想记住能够在C ++中使用类似的代码结构,所以必须有两种语言不同的东西。 Is my assumption here correct? 我的假设在这里是否正确?

Also, what would be the correct way of going about this? 此外,正确的方法是什么? I would like different consumers of subtypes of Fruit to be able to register themselves, and receive only those fruits they enlist themselves as consumers of. 我希望水果亚型的不同消费者能够自我注册,并且只接受他们自己作为消费者的那些水果。 Also, I would like some safety in what you can register yourself as a consumer off (I noticed that this code works as long as no one registers themselves "wrong"). 此外,我希望你可以将自己注册为消费者的一些安全性(我注意到只要没有人将自己注册为“错误”,这段代码就可以工作)。

Lastly, what is the name of this phenomena? 最后,这个现象的名称是什么? Basically, what do I google to learn more about this. 基本上,我会谷歌更多地了解这一点。

You're declaring void register(Consumers c) in BananaConsumer and its parameter is a raw type. 你在BananaConsumer声明了void register(Consumers c) ,它的参数是原始类型。 You could do like this: 你可以这样做:

    public interface FruitConsumer<T extends Fruit> {
    void register(final Consumers<T> c);
    void consume(T fruit);
}

And when you implement FruitConsumer<Banana> , this parameter will be a banana consumer. 当您实现FruitConsumer<Banana> ,此参数将成为香蕉消费者。

(I was explaining why that line works without reading all your code, and the solutions provided by other answers seems to be more logical for your problem.) (我解释了为什么这条线在没有阅读所有代码的情况下工作,而其他答案提供的解决方案似乎更符合您的问题。)

I think you've taken the generics a little too far. 我觉得你把仿制药带得太过分了。

Your Consumers object doesn't need to be generic, only the Map it holds. 您的Consumers对象不需要是通用的,只需要它所拥有的Map Are you ever going to need a Consumers<Banana> for example? 你是否会需要Consumers<Banana>

Try this: 尝试这个:

public interface Fruit {
}

public class Apple implements Fruit {
}

public class Banana implements Fruit {
}

public interface FruitConsumer<T extends Fruit> {
    void consume(T fruit);
}

public class Consumers {
    Map<Class<? extends Fruit>, Collection<FruitConsumer<? extends Fruit>>> consumers = new HashMap<>();

    public <T extends Fruit> void register(Class<T> clazz, FruitConsumer<T> consumer) {
        consumers.putIfAbsent(clazz, new HashSet<>());
        consumers.get(clazz).add(consumer);
    }

    public <T extends Fruit> void consume(T fruit) {
        Collection<FruitConsumer<? extends Fruit>> cons = consumers.get(fruit.getClass());
        for (FruitConsumer<? extends Fruit> con : cons) {
            // Fair to cast here because we KNOW (because of the key) that it is the right type.
            FruitConsumer<T> c = (FruitConsumer<T>)con;
            c.consume(fruit);
        }
    }
}

public class BananaConsumer implements FruitConsumer<Banana> {
    void register(Consumers c) {
        c.register(Banana.class, this);
        c.register(Apple.class, this); // <-- Now it breaks as expected.
    }

    @Override
    public void consume(Banana banana) {
        System.out.println("Mmm, banana");
    }
}

Now also notice the unexpectedly allowed behavior goes away. 现在还注意到意外允许的行为消失了。

You can use a type token the way ClassToInstanceMap does: 您可以像ClassToInstanceMap那样使用类型标记:

A map, each entry of which maps a Java raw type to an instance of that type. 一个映射,每个条目将Java原始类型映射到该类型的实例。 In addition to implementing Map , the additional type-safe operations putInstance(java.lang.Class<T>, T) and getInstance(java.lang.Class<T>) are available. 除了实现Map ,还有其他类型安全操作putInstance(java.lang.Class<T>, T)getInstance(java.lang.Class<T>)

So you could define a pair of methods that expose your registry like 所以你可以定义一对暴露你的注册表的方法

<F extend Fruit> FruitConsumer<? super F> consumerFor(Class<F> fruitType);
<F extend Fruit> void registerConsumer(Class<F> fruitType, FruitConsumer<? super F> consumer);

You'd need to do an unchecked conversion on fetching from the internal map but it should be typesafe if all register calls are typesafe. 您需要在从内部地图获取时执行未经检查的转换,但如果所有寄存器调用都是类型安全的,则它应该是类型安全的。

As long as you're not registering consumers for interface types, Consumers.consume could walk up the super-type chain to find an appropriate consumer. 只要您没有为接口类型注册消费者, Consumers.consume就可以走上超级链条来寻找合适的消费者。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM