objetos - metodos genericos java
¿Cómo hacer una clase Java que implemente una interfaz con dos tipos genéricos? (7)
Esta es la solución más transparente posible:
Podría crear una interfaz de contenedor para cada interfaz.
public class TwoTypesConsumer implements TomatoConsumer, AppleConsumer
{
private interface TomatoConsumer extends Consumer<Tomato> {}
private interface AppleConsumer extends Consumer<Apple> {}
public void consume(Tomato t) { ..... }
public void consume(Apple a) { ...... }
}
Estas envolturas serían diferentes incluso después del borrado de tipo. El compilador ya no tiene problemas con ellos y, debido a la jerarquía, su clase TwoTypesConsumer
se puede pasar como una interfaz.
Tengo una interfaz genérica
public interface Consumer<E> {
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<Tomato>, Consumer<Apple>
{
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<Object> {
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 compilación que proporcionan los genéricos.
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?
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<A,B> extends Consumer<A>{
public void consume(B b);
}
desafortunadamente, esto se considera Consumer<A>
y NO como Consumer<B>
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<A,B>{
private final Consumer<B> consumerAdapter = new Consumer<B>(){
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<A>
, simplemente puede pasar this
, y si el Consumer<B>
es necesario, pase consumerAdapter
adaptador de consumerAdapter
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<Fruit> {
La fruta es un antepasado de tomate y manzana.
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<Tomato> getTomatoConsumer() {
return new Consumer<Tomato>() {
public void consume(Tomato t) {
consumeTomato(t);
}
}
}
public Consumer<Apple> getAppleConsumer() {
return new Consumer<Apple>() {
public void consume(Apple a) {
consumeApple(t);
}
}
}
}
El requisito implícito de la pregunta eran los objetos Consumer<Tomato>
y Consumer<Apple>
que comparten estado. La necesidad de objetos Consumer<Tomato>, Consumer<Apple>
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.
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<Tomato> {
public void consume(Tomato t) { ..... }
}
public static class AppleConsumer implements Consumer<Apple> {
public void consume(Apple a) { ..... }
}
}
Si le molesta crear estas clases internas estáticas, puede usar clases anónimas:
public class TwoTypesConsumer {
private Consumer<Tomato> tomatoConsumer = new Consumer<Tomato>() {
public void consume(Tomato t) {
}
};
private Consumer<Apple> appleConsumer = new Consumer<Apple>() {
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).
No se puede hacer esto directamente en una clase, ya que la siguiente definición de clase no se puede compilar debido a la eliminación de tipos genéricos y la declaración de interfaz duplicada.
class TwoTypesConsumer implements Consumer<Apple>, Consumer<Tomato> {
// 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<Apple> createAppleConsumer();
Consumer<Tomato> 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<Tomato> {
public void consume(Tomato tomato) {
// you can access shared objects here
}
}
private class AppleConsumer implements Consumer<Apple> {
public void consume(Apple apple) {
// you can access shared objects here
}
}
// It is really important to return generic Consumer<Apple> here
// instead of AppleConsumer. The classes should be rather private.
public Consumer<Apple> createAppleConsumer() {
return new AppleConsumer();
}
// ...and the same here
public Consumer<Tomato> 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 declaración 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 genéricos 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.