Corri para este problema recentemente, quando eu estava brincando. Basicamente, eu queria fazer uma classe central onde os consumidores de um determinado tipo poderia registrar-se. Algo em seguida, publica objetos para os consumidores, e apenas aqueles que consomem o tipo publicada deve recebê-lo.
Todo o programa pode ser resumido a isto:
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);
}
}
Agora, eu acho que entendo por que essa falha. Não há nenhuma maneira para que o compilador sabe que a T
do Consumers.register
método é o mesmo T
, para ambos os parâmetros. O compilador só pode impor que ambos os argumentos atende ao requisito T extends Fruit
. Eu quero lembrar de ser capaz de usar uma estrutura de código semelhante em C ++, então tem que haver algo que os dois idiomas faz diferente. É a minha suposição aqui correto?
Além disso, qual seria a maneira correta de ir sobre isso? Gostaria diferentes consumidores de subtipos de frutas para ser capaz de registrar-se, e receber apenas os frutos eles se alistar como consumidores de. Além disso, eu gostaria de um pouco de segurança no que você pode registar-se como um consumidor off (eu notei que este código funciona, desde que ninguém registra-se "errado").
Por último, qual é o nome desse fenômeno? Basicamente, o que eu google para saber mais sobre isso.
Eu acho que você tomou os genéricos um pouco longe demais.
Seu Consumers
objeto não precisa ser genérica, apenas o Map
que detém. Você nunca vai precisar de um Consumers<Banana>
, por exemplo?
Tente isto:
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");
}
}
Agora também observar o comportamento inesperadamente permitiu vai embora.