【问题标题】:Java - Map generic type to consumer of said typeJava - 将泛型类型映射到所述类型的使用者
【发布时间】:2018-07-23 15:13:44
【问题描述】:

我最近在玩的时候遇到了这个问题。基本上,我想创建一个中心类,某种类型的消费者可以在其中注册自己。然后某些东西会为消费者发布对象,只有那些使用发布的类型的人才能收到它。

整个程序可以概括为:

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);
    }
}

现在,我想我明白为什么会崩溃了。对于两个参数,编译器无法知道Consumers.register方法中的TT 相同。编译器只能强制两个参数都满足T extends Fruit 的要求。我想记住能够在 C++ 中使用类似的代码结构,因此这两种语言必须有所不同。我的假设是否正确?

另外,正确的方法是什么?我希望水果子类型的不同消费者能够注册自己,并且只接收他们自己作为消费者的水果。另外,我希望您可以将自己注册为消费者,以确保安全(我注意到只要没有人将自己注册为“错误”,此代码就可以工作)。

最后,这种现象的名称是什么?基本上,我要通过 Google 搜索来了解更多信息。

【问题讨论】:

  • 您可以查找“类型安全的异构容器”。基本上你不能仅仅通过使用泛型来保证这个安全,你必须依赖封装。
  • 请参阅stackoverflow.com/q/44422685/2891664 以了解您正在尝试做的一些示例。 (另外,我可能错了,但我认为这种事情在 C++ 中也不方便,除非你试图使用类到消费者的映射模式作为 C++ 模板的笨拙替代品专业。)
  • 感谢您的指点。我将阅读这些主题,看看如何重建它以确保安全。我从来没有听说过“类型安全的异构容器”这个词,非常感谢!

标签: java generics


【解决方案1】:

您在BananaConsumer 中声明void register(Consumers c),其参数是原始类型。你可以这样做:

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

而当你实现FruitConsumer&lt;Banana&gt;时,这个参数将是一个香蕉消费者。

(我在解释为​​什么该行没有阅读您的所有代码就可以工作,而其他答案提供的解决方案似乎更符合您的问题。)

【讨论】:

【解决方案2】:

我认为你对泛型的理解有点过火了。

您的Consumers 对象不需要是通用的,只需它拥有的Map。例如,您是否需要Consumers&lt;Banana&gt;

试试这个:

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");
    }
}

现在还注意到意外允许的行为消失了。

【讨论】:

  • 你说得对,这个解决方案效果很好。感谢你的付出。这解决了“我如何使它正确”部分。其他一些答案为我提供了为什么我的代码会表现得如此的指针,所以总而言之,我认为我已经得到了我需要的所有答案。问题:那我该如何选择正确的答案呢?由于不同的答案回答了不同的子问题......
  • 这真的是你的选择。在您的位置上,我可能会选择最能涵盖问题标题的答案,但无论如何选择您认为最有帮助的答案或选择您喜欢的任何标准。最重要的是点赞数。
【解决方案3】:

您可以像ClassToInstanceMap 那样使用类型标记:

一个映射,它的每个条目都将一个 Java 原始类型映射到该类型的一个实例。除了实现Map,还可以使用额外的类型安全操作putInstance(java.lang.Class&lt;T&gt;, T)getInstance(java.lang.Class&lt;T&gt;)

因此您可以定义一对公开您的注册表的方法,例如

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

您需要在从内部映射中获取数据时进行未经检查的转换,但如果所有寄存器调用都是类型安全的,那么它应该是类型安全的。

只要您不为接口类型注册消费者,Consumers.consume 就可以沿着超类型链查找合适的消费者。

【讨论】:

  • 尤其是关于“走上超类型链”的部分是一个非常有用的见解,因为它让我解决了一个相关问题“我如何为所有类型的水果添加监听器”。非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-06-25
  • 1970-01-01
  • 2016-08-11
  • 2020-08-05
  • 2015-02-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多