2012-06-07 6 views
17

Digamos que tengo dos constructores de mi clase:Java - ¿Cómo lidiar con el borrado de tipos en los constructores?

public User (List<Source1> source){ 
... 
} 

public User (List<Source2> source) { 
... 
} 

Digamos que estos dos constructores proporcionan la misma información sobre un usuario y son igualmente formas válidas para la construcción de un usuario para diferentes casos de uso.

En Java, no puede hacer esto debido a la borradura de tipo: Java no aceptará dos constructores que tengan como parámetros la lista <? >.

Entonces, ¿cuál es la forma de evitar esto? ¿Cuál es una solución que no es exagerada sino que respeta el OO básico? Parece incorrecto tener que construir un método de fábrica u otra interfaz en torno a esto solo porque Java no tiene un sólido soporte de genéricos.

Estas son las posibilidades que se me ocurren:

1) aceptar un List<?> como un parámetro para el constructor y analizará en el constructor, que tipo de lógica que necesita, o lanzar una excepción si no es ninguna de las tipos aceptados

2) Cree una clase que acepte cualquiera de las listas, construya el objeto de usuario apropiado y lo devuelva.

3) Cree envolturas alrededor de List<Source1> y List<Source2> que se pueden pasar al constructor del usuario.

4) Subclase este tipo con dos clases, donde toda la funcionalidad se hereda excepto el constructor. El constructor de uno acepta Source1, el otro acepta Source2.

5) Envuelva este tipo con un constructor donde hay dos métodos diferentes de compilación para las dos fuentes de datos diferentes para la creación de instancias.

Mis preguntas son las siguientes:

1) ¿Es la necesidad de hacer esto con un defecto de Java o una decisión de diseño intencional? ¿Cuál es la intuición?

2) ¿Qué solución es más sólida en términos de mantener un buen código sin introducir una complejidad innecesaria? ¿Por qué?

Esta pregunta es similar: Designing constructors around type erasure in Java pero no entra en detalles, simplemente sugiere varias soluciones alternativas.

+8

por qué hacer eso, en primer lugar, ¿por qué no escriba parametrizar toda la clase? o simplemente crea la fábrica que es un buen patrón en este caso de todos modos. –

+0

Se podría escribir una función y luego probar los tipos de las listas al comienzo de la función y escriba fundido en función del tipo ... o usar un lenguaje mejor – NKamrath

+0

... o puede recurrir a reifiable tipos, como las matrices . –

Respuesta

8

El enfoque habitual es utilizar factory methods:

public static User createFromSource1(List<Source1> source) { 
    User user = new User(); 
    // build your User object knowing you have Source1 data 
    return user; 
} 

public static User createFromSource2(List<Source2> source) { 
    User user = new User(); 
    // build your User object knowing you have Source2 data 
    return user; 
} 

Si solamente desee utilizando la construcción Source1 o Source2 (es decir, que no tiene un constructor por defecto), sólo tiene que ocultar su constructor, obligando a los clientes a utilizar sus métodos de fábrica:

private User() { 
    // Hide the constructor 
} 

este problema surge porque no se puede nombrar a los constructores de manera diferente, lo que sería la forma en que te superar este si estos fueran métodos normales Debido a que los nombres de los constructores se fijan como el nombre de la clase, este patrón de código es solo una forma de distinguir y luego dar el mismo borrado de tipo.

+0

dice: "Parece mal tener para construir un método de fábrica u otra interfaz en torno a este sólo porque Java no tiene un fuerte apoyo genéricos". ... pero esta es solo una de las formas en que los constructores Java son mucho más limitantes que los métodos estáticos de fábrica. (Ver también: Effective Java y http://stackoverflow.com/questions/4029622/static-factory-method-for-constructors) –

1

1: mantenimiento de compatibilidad con borrado.

2: ¿Puede su clase usar genéricos? Algo como esto:

public class User<T> { 
    private List<T> whatever; 
    public User(List<T> source){ 
     .... 
    } 
} 

No estoy seguro de si esto es lo que entiende por (2)

0
public class User<T> { 

    public User(List<T> source){ 

    } 

} 

o probablemente mejor:

public class User<T extends SomeTypeOfYours> { 

    public User(List<T> source){ 

    } 
} 

wheer SomeTypeOfYours es un supertipo de Source1 y Source2.

0

Me gusta la idea de la fábrica en general, o el usuario genérico (como sugiere @Markus Mikkolainen), pero una alternativa posible sería pasar la clase de la lista como un segundo argumento y activar eso, p.

public User<List<?> source, Class<?> clazz) { 
    switch(clazz) { 
     case Source1.class: initForSource1(); break; 
     case Source2.class: initForSource2(); break; 
     default: throw new IllegalArgumentException(); 
    } 
} 

El <?> Puede haber algo más si hay alguna clase ancestro común. Me puedo imaginar muchos casos donde esta es una idea pobre, pero algunos donde podría ser aceptable.

1

El problema básico es el lenguaje fue diseñado (con el nombre del constructor fijado) antes existían los genéricos, por lo que no puede manejar choques debidos al tipo de borrado, que normlaly serían manejadas por el cambio de nombre de métodos para distinguirlos.

Una "solución", sin tener que recurrir a métodos de fábrica, es añadir otro parámetro no escrito para que el compilador para distinguirlos:

public User(List<Source1>, Source1 instance) { 
    // ignore instance 
} 

public User(List<Source2>, Source2 instance) { 
    // ignore instance 
} 

Esto es un poco escaso, aunque, como se podía reemplazar a los parámetros adicionales con cualquier cosa (por ejemplo, Integer y String, o simplemente tener uno de ellos omita el segundo parámetro) y todavía funcionaría. Además, el parámetro adicional se ignora: existe solo para distinguir los constructores. Sin embargo, permite que el código funcione sin agregar ningún método adicional o código especial.

Cuestiones relacionadas