2009-03-25 16 views
29

Tengo un conjunto bastante complicado de clases genéricas en Java. Por ejemplo, tengo una interfazEscriba alias para los genéricos de Java

interface Doable<X,Y> { 
    X doIt(Y y); 
} 

y la implementación

class DoableImpl implements Doable<Foo<Bar<Baz,Qux>>,Foo<Bar<Zot,Qux>>> { 
    Foo<Bar<Baz,Qux>> doIt(Foo<Bar<Zot,Qux>> fooBZQ) { ... } 
} 

En la implementación real, Doable tiene un buen número de métodos y así Foo<Bar<Baz,Qux>>, etc., aparecen una y otra vez. (Créalo o no, los tipos genéricos son un poco más dolorosos que este. Los simplifiqué para el ejemplo.)

Me gustaría simplificar estos, ahorrarme tipeo y aliviar la tensión en mis ojos Lo que me gustaría es tener un simple "alias de tipo" para Foo<Bar<Baz,Qux>>, etc., digamos FooBBQ y FooBZQ.

Mi idea actual es definir clases contenedoras:

class FooBBQ { 
    public static FooBBQ valueOf(Foo<Bar<Baz,Qux>> fooBBQ) { 
    return new FooBBQ(fooBBQ); 
    } 
    private Foo<Bar<Baz,Qux>> fooBBQ; 
    private FooBBQ(Foo<Bar<Baz,Qux>> fooBBQ) { 
    this.fooBBQ = fooBBQ; 
    } 
    public Foo<Bar<Baz,Qux>> toGeneric() { 
    return fooBBQ; 
    } 
} 

class FooBZQ { /* pretty much the same... */ } 

class DoableImpl implements Doable<FooBBQ,FooBZQ> { 
    FooBBQ doIt(FooBZQ fooBZQ) { ... } 
} 

Esto funciona bien, pero tiene algunos inconvenientes:

  1. Tenemos que definir envolturas separadas para cada instancia genérica. Las clases de envoltura son cortas y estilizadas, pero no puedo encontrar una manera de macro -izarlas.
  2. Tenemos la sobrecarga de traducción (conceptualmente, si no operacionalmente) de llamar a valueOf y toGeneric para convertir entre FooBBQ y Foo<Bar<Baz,Qux>>. Por ejemplo, si doIt llamadas en alguna rutina de biblioteca que espera un Foo<Bar<Zot,Qux>> (que la implementación real lo hace), que terminan con algo como

    return FooBBQ.valueOf(libraryCall(fooBZQ.toGeneric())) 
    

    en el que originalmente habría tenido

    return libraryCall(fooBZQ); 
    

¿Hay alguna otra forma de obtener el comportamiento de "tipo de alias" que quiero aquí? ¿Tal vez usando algún conjunto de herramientas macro de terceros? ¿O tengo que aceptar que voy a tener que escribir mucho, de una manera (usando los tipos genéricos en la implementación) o de la otra (escribiendo envoltorios para ellos)? Tal vez tener tantos parámetros genéricos volando es solo una mala idea y necesito volver a pensar el problema.

[ACTUALIZAR] OK, estoy prohibiendo cualquier respuesta adicional de "no hacer eso". Tómalo como un hecho que Foo<Bar<Baz,Qux>> tiene un valor genuino en mi dominio problemático (Pete Kirkham puede tener razón en que tiene valor suficiente para obtener una clase contenedora adecuada con un nombre descriptivo). Pero este es un problema de programación; no trates de definir el problema.

+0

Voy a decir 'probablemente sí' a la última, pero realmente no tengo comentarios constructivos: P ¡Parece un desastre! ¡Buena suerte! –

+6

Creo que esta pregunta sería una buena propuesta para Java. Si pudieras proponer una característica de Alias ​​de Tipo a Oracle, creo que sería una gran característica de lenguaje que simplificará el idioma y hará que el código fuente sea más legible. –

+1

Con la profundidad de la rodilla en Java hoy y quería limpiar un código detallado ... no solo no obtengo la inferencia de tipo, sino tampoco el alias de tipo. * sollozo * –

Respuesta

2

Tal vez tener tantos parámetros genéricos volando es simplemente una mala idea y necesito volver a pensar el problema?

Muy probablemente. ¿Es necesario especializar 'Doit' en 8 dimensiones?

En muchos casos, estos tipos no existen en el vacío y debería pensar qué objetos de dominio representa su 'envoltura' en lugar de utilizarlos como una conveniencia de codificación.

0

Yo diría que, sí, necesita replantearse el problema. La declaración

class DoableImpl implements Doable<Foo<Bar<Baz,Qux>>,Foo<Bar<Zot,Qux>>> { 
    Foo<Bar<Baz,Qux>> doIt(Foo<Bar<Zot,Qux>> fooBZQ) { ... } 
} 

es un caso bastante claro de uso excesivo de genéricos.

+0

No estoy de acuerdo. Puede que no sea el uso excesivo de genéricos ... puede ser solo un uso poco claro de los genéricos. Si proporciona seguridad de tipo adicional, puede valer la pena, pero también debe haber una forma de hacerlo con mayor claridad. – Eddie

+0

¿Me creerías si dijera que en realidad es bastante claro (solo extremadamente detallado) en el código real? Obviamente, no tiene sentido parametrizar tus objetos por Foo, Bar y Qux ... –

+0

He visto (y hecho) cosas peores, hay momentos en los que tiene sentido ... pero pueden ser difíciles de seguir. Sin embargo, el tipo de seguridad puede valer la pena. – TofuBeer

9

Si desea seguridad de tipo completo, no creo que pueda hacerlo mejor sin algún tipo de clases de contenedor. Pero, ¿por qué no hacen esas clases heredan/implementar las versiones genéricas originales, como esta:

public class FooBBQ extends Foo<Bar<Baz,Qux>> { 
... 
} 

Esto elimina la necesidad de toGeneric() método, y es más claro, en mi opinión, que es sólo un tipo de alias . Además, el tipo genérico se puede convertir en FooBBQ sin una advertencia del compilador. Sería mi preferencia personal hacer interfaces Foo, Bar, Baz..., si es posible, incluso si se produjera alguna duplicación de código en la implementación.

Ahora, sin saber dominio del problema concreto, es difícil decir si es necesario, por ejemplo FooBBQ, como en el ejemplo, o tal vez una:

public class FooBar<X, Y> extends Foo<Bar<X, Y>> { 
... 
} 

Por otra parte, ¿has pensado en simplemente configurar el compilador de Java para que no muestre algunas de las advertencias genéricas, y simplemente omita las partes de la definición genérica? O bien, ¿usar estratégicamente colocado @SuppressWarnings("unchecked")? En otras palabras, se puede hacer DoableImpl solamente "parcialmente genericized":

class DoableImpl implements Doable<Foo<Bar>>,Foo<Bar>> { 
    Foo<Bar> doIt(Foo<Bar> foobar) { ... } 
} 

e ignorar las advertencias para el bien de menos desorden código? De nuevo, difícil de decidir sin un ejemplo concreto, pero es otra cosa que puedes probar.

+0

Extendiendo Foo > obtiene la traducción unidireccional libre (es decir, up-casting implícito) a costa de tener que escribir constructores más complicados (para el down-casting explícito). No está claro para mí que eso sea una victoria. Me gustaría evitar las anotaciones de @SuppressWarnings tanto como sea posible. –

5

Scala tiene buen soporte para alias tipo. Por ejemplo:

type FooBBQ = Foo[Bar[Baz,Qux]] 

Me doy cuenta de que esta respuesta no será útil si no tiene la opción de cambiar a Scala. Pero si tienes la opción de cambiar, es posible que tengas un tiempo más fácil.

1

Bueno, Java no tiene alias de tipo, así que no tienes suerte. Sin embargo, los alias de tipo a veces se pueden reemplazar con variables de tipo! ¡Así que resolvemos el problema de demasiados genéricos con incluso más genéricos!

Como has despojado todo el contenido de tu ejemplo no puedo adivinar dónde está o si tiene sentido para introducir variables de tipo adicionales, pero aquí es una descomposición posible:

class DoableImplFoo<A,B> implements Doable<Foo<A>,Foo<B>> { 
    public DoableImplFoo(SomePropertyOf<A,B> aAndBAreGoodEnough) { ... } 
    Foo<A> doIt(Foo<B> fooB) { ... } 
} 

Al crear una instancia de ABar<Baz,Qux> y B a Bar<Zot,Qux> más adelante, puede encontrar que hay una repetición repetitiva, pero podría ser menos de lo que originalmente tenía.