Java Generics y Patrones de diseño: ¿No es parametrizar una referencia a un tipo genérico siempre es algo malo?

StackOverflow https://stackoverflow.com/questions/7327209

Pregunta

Esta pregunta está parcialmente relacionada con mi última pregunta.

Tengo una clase genérica que representa una colección de objetos genéricos:

public interface MyObject<N extends Number>{}

public interface MyCollecion<N extends Number> {
    public foo(MyObject<N> obj)
}

En mi diseño, estas colecciones de objetos están construidas por una clase de cliente (llamémoslo CollectionBuilder) a través de un enfoque de clase abstracta:

public interface AbstractCollectionFactory {
    public abstract <N extends Number> MyCollection<MyObject<N>> createCollection(String collectionType);

}

Las colecciones genéricas deben construirse de esta manera:

public class CollectionBuilder{
    AbstractCollectionFactory f;
    public void buildDoubleCollection(){

         MyCollection<MyObject<Double>> c = f.<Double>createCell("MyType");
    }
    public void buildIntegerCollection(){...}
}

Ok, ya que aquí está bien. CollectionBuilder es consciente de cuál es el tipo de concreto genérico para especificar (duplicar en este caso) y puede construir correctamente. Puedo compilar esto sin advertencia y todo debería funcionar bien.

Ahora tengo una pregunta relacionada tanto con los genéricos como con el diseño de mi programa.

Tengo otra clase en mi aplicación que necesita usar la colección creada por CollectionBuilder (llamemos a esta clase UserClass). La Clase de usuario:

  1. No necesita saber qué tipo de concreto en particular son su colección (MyCollection<MyObject<Double>> o MyCollection<MyObject<Integer>>).
  2. Realice algunas operaciones en estas colecciones que invocan algunos métodos definidos en la interfaz MyCollection.
  3. No me gustaría agregarle un tipo genérico.

En la situación descrita, ¿es una mala idea no parametrizar la clase de usuario de la clase genérica de mycollection?

public UserClass{
     MyCollection a;  //warning 
     MyCollection<?> b; //no warning

     public method(MyObject o){  //warning
          a.foo(b); //compile       
     }
     public method2(MyObject<?> o){
          b.foo(o); //error do not compile
     }
}

El compilador de Java siempre protesta con una advertencia si no especifico el parámetro genérico. De todos modos, desde el interior de UserClass, no conozco el parámetro concreto (y me gustaría no saberlo) e incluso declararlo con "?" No me permita llamar al método Foo en MyCollection.

Entonces la pregunta es:

  1. No parametrizar una referencia a un tipo genérico ¿siempre es algo malo?
  2. Si la respuesta en 1 es no, ¿es esta una situación correcta para no hacerlo?
  3. Si la respuesta en 1 es sí, ¿cómo puedo usar el método MyCollection desde dentro de UserClass sin conocer sus tipos genéricos?
  4. ¿El mío es un mal diseño?

Espero haber sido claro. He estado luchando en este problema de días, y no tengo idea. Por favor, ayúdame.

¿Fue útil?

Solución

  1. Según el compilador de Java, sí. Y esta respuesta Contiene muchos puntos buenos sobre por qué. Personalmente no estoy de acuerdo en algunas situaciones, especialmente donde el uso de Class está preocupado (como por ejemplo, si tiene un Map<Class, Object>). En tales casos, tener que volver siempre <?> Al final de la "clase" se siente como el tedio innecesario, y no hace que el código sea más legible.

    La idea de tener Class ser parametrizado según el tipo de objeto con el que se asocia siempre se ha sentido un poco dudoso en primer lugar, para mí. El punto de Class es tener una interfaz común que se pueda utilizar para obtener información sobre cualquier objeto (reflexión AL), independientemente de su tipo.

    Pero yo divago. De acuerdo con el compilador Java y los implementadores de tipos genéricos, siempre debe parametrizar su referencia, incluso si solo está usando <?>.

  2. No aplica. Aunque no siempre puede declarar información de tipo y luego usar @SuppressWarnings("unchecked") junto con un elenco explícito para hacer feliz al compilador.

  3. Ver la respuesta de Bohemian.

  4. Realmente no hay suficiente información para decir. Aunque fuera de mi cabeza, no estoy seguro de cuál sería el caso de uso real para tener "una clase genérica que represente una colección de objetos genéricos". ¿Por qué no usar las colecciones directamente?

Otros consejos

Casi siempre hay una forma de parametrizar su código. En resumen, estaría escribiendo UserClass, así:

public UserClass <N extends Number> {
     MyCollection<N> c;

     public method(MyObject<N> o){
          c.foo(o);      
     }
} 

Editar
Si le preocupa el código de su cliente con genéricos, puede hacer una clase de escritura abstracta y proporcionar implementaciones no tipo tipos, como esta:

public abstract UserClass <N extends Number> {
     MyCollection<N> c;

     public method(MyObject<N> o){
          c.foo(o);      
     }
} 

public class DoubleUserClass extends UserClass<Double> {}

Para reducir la hinchazón de clase, puede poner las clases no tipo en el interior La clase base:

public abstract UserClass <N extends Number> {
     MyCollection<N> c;

     public method(MyObject<N> o){
          c.foo(o);      
     }

     public static class DoubleImpl extends UserClass<Double> {}
     public static class FloatImpl extends UserClass<Float> {}
     public static class IntegerImpl extends UserClass<Integer> {}
} 

Y úsalos así: new UserClass.DoubleImpl() etc.

Es fácil dejarse llevar por genéricos. Tienes que parar en algún lugar. Así que no, no creo que no parametrizar una referencia a un tipo genérico siempre sea algo malo. A veces, el esfuerzo adicional requerido para obtener todos los genéricos exactamente correctos simplemente no vale la pena.

Sin embargo, es mejor especificar genéricos cuando sea posible, por supuesto. En su ejemplo, puede agregar un parámetro genérico a UserClass como sugiere Bohemian.

En cuanto a si su colección es un mal diseño: las interfaces/infraestructura que está definiendo aquí parece demasiado general. Que hace MyCollection tener que no se puede hacer con simplemente java.util.List<YourClass>? ¿Hay realmente una necesidad de un MyObject ¿interfaz? Si desea 'etiquetar' un cierto tipo de clases, seguramente puede encontrar una descripción más específica de lo que hace que estos objetos sean diferentes de cualquier otro Object?

 MyCollection<?> b; 

 public method2(MyObject<?> o){
      b.foo(o); 
 }

Si hay una garantía fuera de banda de que b.foo(o) es seguro, entonces necesitamos que esta llamada funcione. Introducir un método auxiliar

<N extends Number>
void foo(MyCollection<N> collection, MyObject<?> obj)
{
    @SuppressWarnings("unchecked")
    MyObject<N> obj2 = (MyObject<N>)obj;

    collection.foo(obj2);
}

MyCollection<?> b;
void method2(MyObject<?> o){
     foo(b, o); // now works
}

El elenco de MyObject<?> a MyObject<N> es seguro, debido a la Gaurantee fuera de banda. Es un elenco marcado en ese sentido, por lo que estamos justificados para suprimir la advertencia.

En una aplicación del mundo real no es raro que sepamos más sobre las relaciones de tipo de lo que el lenguaje puede expresar; Ningún programador debe disculparse si sabe más sobre su código que el compilador.

El casting que involucra genéricos es un poco complicado; No es intuitivo como foo(b,o) obras.

Está bien usar tipos crudos MyCollection, MyObject para evitar la molestia. Ignore la advertencia de que no usará el tipo sin procesar. Eso es una tontería.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top