¿Cómo hacer una clase Java que implemente una interfaz con dos tipos generics?

Tengo una interfaz genérica

public interface Consumer { public void consume(E e); } 

Tengo una clase que consume dos tipos de objetos, por lo que me gustaría hacer algo como:

 public class TwoTypesConsumer implements Consumer, Consumer { public void consume(Tomato t) { ..... } public void consume(Apple a) { ...... } } 

Aparentemente no puedo hacer eso.

Por supuesto, puedo implementar el despacho, por ejemplo

 public class TwoTypesConsumer implements Consumer { public void consume(Object o) { if (o instanceof Tomato) { ..... } else if (o instanceof Apple) { ..... } else { throw new IllegalArgumentException(...) } } } 

Pero estoy buscando la solución de comprobación y despacho de tipos en tiempo de comstackción que proporcionan los generics.

La mejor solución que puedo pensar es definir interfaces separadas, por ejemplo

 public interface AppleConsumer { public void consume(Apple a); } 

Funcionalmente, esta solución está bien, creo. Es vergonzoso y feo.

¿Algunas ideas?

Considera la encapsulación:

 public class TwoTypesConsumer { private TomatoConsumer tomatoConsumer = new TomatoConsumer(); private AppleConsumer appleConsumer = new AppleConsumer(); public void consume(Tomato t) { tomatoConsumer.consume(t); } public void consume(Apple a) { appleConsumer.consume(a); } public static class TomatoConsumer implements Consumer { public void consume(Tomato t) { ..... } } public static class AppleConsumer implements Consumer { public void consume(Apple a) { ..... } } } 

Si le molesta crear estas clases internas estáticas, puede usar clases anónimas:

 public class TwoTypesConsumer { private Consumer tomatoConsumer = new Consumer() { public void consume(Tomato t) { } }; private Consumer appleConsumer = new Consumer() { public void consume(Apple a) { } }; public void consume(Tomato t) { tomatoConsumer.consume(t); } public void consume(Apple a) { appleConsumer.consume(a); } } 

Debido al borrado de tipos, no puede implementar la misma interfaz dos veces (con diferentes parámetros de tipo).

Aquí hay una posible solución basada en la de Steve McLeod :

 public class TwoTypesConsumer { public void consumeTomato(Tomato t) {...} public void consumeApple(Apple a) {...} public Consumer getTomatoConsumer() { return new Consumer() { public void consume(Tomato t) { consumeTomato(t); } } } public Consumer getAppleConsumer() { return new Consumer() { public void consume(Apple a) { consumeApple(t); } } } } 

El requisito implícito de la pregunta eran los objetos Consumer y Consumer que comparten estado. La necesidad de objetos Consumer, Consumer proviene de otros métodos que esperan estos como parámetros. Necesito una clase para implementarlos a fin de compartir el estado.

La idea de Steve era usar dos clases internas, cada una implementando un tipo genérico diferente.

Esta versión agrega captadores para los objetos que implementan la interfaz del consumidor, que luego se pueden pasar a otros métodos que los esperan.

Al menos, puede hacer una pequeña mejora en su implementación del envío haciendo algo como lo siguiente:

 public class TwoTypesConsumer implements Consumer { 

La fruta es un antepasado de tomate y manzana.

Acabo de tropezar con esto. Simplemente sucedió que tuve el mismo problema, pero lo resolví de otra manera: acabo de crear una nueva interfaz como esta

 public interface TwoTypesConsumer extends Consumer{ public void consume(B b); } 

desafortunadamente, esto se considera Consumer y NO como Consumer contra toda la lógica. Entonces, tienes que crear un pequeño adaptador para el segundo consumidor como este dentro de tu clase

 public class ConsumeHandler implements TwoTypeConsumer{ private final Consumer consumerAdapter = new Consumer(){ public void consume(B b){ ConsumeHandler.this.consume(B b); } }; public void consume(A a){ //... } public void conusme(B b){ //... } } 

si se necesita un Consumer , simplemente puede pasar this , y si el Consumer es necesario, pase consumerAdapter adaptador de consumerAdapter

No se puede hacer esto directamente en una clase, ya que la siguiente definición de clase no se puede comstackr debido a la eliminación de tipos generics y la statement de interfaz duplicada.

 class TwoTypesConsumer implements Consumer, Consumer { // cannot compile ... } 

Cualquier otra solución para empaquetar las mismas operaciones de consumo en una clase requiere definir su clase como:

 class TwoTypesConsumer { ... } 

lo cual no tiene sentido ya que necesita repetir / duplicar la definición de ambas operaciones y no se hará referencia a ellas desde la interfaz. En mi humilde opinión, hacer esto es una mala duplicación de código y pequeña que estoy tratando de evitar.

Esto también podría ser un indicador de que hay demasiada responsabilidad en una clase para consumir 2 objetos diferentes (si no están acoplados).

Sin embargo, lo que estoy haciendo y lo que puede hacer es agregar un objeto de fábrica explícito para crear consumidores conectados de la siguiente manera:

 interface ConsumerFactory { Consumer createAppleConsumer(); Consumer createTomatoConsumer(); } 

Si en realidad esos tipos están realmente acoplados (relacionados), entonces recomendaría crear una implementación de esa manera:

 class TwoTypesConsumerFactory { // shared objects goes here private class TomatoConsumer implements Consumer { public void consume(Tomato tomato) { // you can access shared objects here } } private class AppleConsumer implements Consumer { public void consume(Apple apple) { // you can access shared objects here } } // It is really important to return generic Consumer here // instead of AppleConsumer. The classes should be rather private. public Consumer createAppleConsumer() { return new AppleConsumer(); } // ...and the same here public Consumer createTomatoConsumer() { return new TomatoConsumer(); } } 

La ventaja es que la clase de fábrica conoce ambas implementaciones, hay un estado compartido (si es necesario) y puede devolver más consumidores acoplados si es necesario. No hay una statement de método de consumo repetitivo que no se derive de la interfaz.

Tenga en cuenta que cada consumidor puede ser una clase independiente (aún privada) si no están completamente relacionados.

La desventaja de esa solución es una complejidad de clase superior (incluso si puede ser un archivo java) y para acceder al método de consumo necesita una llamada más, en lugar de:

 twoTypesConsumer.consume(apple) twoTypesConsumer.consume(tomato) 

tienes:

 twoTypesConsumerFactory.createAppleConsumer().consume(apple); twoTypesConsumerFactory.createTomatoConsumer().consume(tomato); 

Para resumir, puede definir 2 consumidores generics en una clase de nivel superior usando 2 clases internas, pero en el caso de una llamada necesita obtener primero una referencia al consumidor de implementación apropiado , ya que no puede tratarse simplemente de un objeto de consumo.