Tengo una situación interesante y me pregunto si hay una mejor manera de hacerlo. La situación es la siguiente: tengo una estructura de árbol (un árbol de sintaxis abstracta, específicamente) y algunos nodos pueden contener nodos secundarios de varios tipos pero todos extendidos desde una clase base determinada.Seguridad tipo, genéricos Java y consultas
Quiero hacer consultas con frecuencia en este árbol y me gustaría obtener los subtipos específicos que me interesan. Así que creé una clase de predicado que luego puedo pasar a un método de consulta genérico. Al principio no tenía un método de consulta que se veía así:
public <T extends Element> List<T> findAll(IElementPredicate pred, Class<T> c);
donde se utilizó el argumento Class
simplemente para indicar el tipo de retorno. Lo que me molestó acerca de este enfoque es que todos mis predicados ya eran para tipos específicos, así que aquí hay información redundante. Una llamada típica podría ser:
List<Declaration> decls =
scope.findAll(new DeclarationPredicate(), Declaration.class);
Así que refactorizado así:
public <T extends Element> List<T> findAll(IElementPredicate<T> pred);
Cuando la interfaz IElementPredicate
se ve así:
public interface IElementPredicate<T extends Element> {
public boolean match(T e);
public String getDescription();
public Class<T> getGenericClass();
}
El punto aquí es que el predicado la interfaz se expande para proporcionar el objeto Class
en su lugar. Hace que escribir el método real findAll
sea un poco más laborioso y agrega un poco más de trabajo al escribir el predicado, pero esas son cosas "pequeñas" y hacen que la llamada sea mucho más agradable porque no lo hace tiene que agregar el argumento extra (potencialmente redundante), por ej.
List<Declaration> decls = scope.findAll(new DeclarationPredicate());
No he notado este patrón antes. ¿Es esta una forma típica de tratar con la semántica de los genéricos de Java? Solo curiosidad si me falta un patrón mejor.
Comentarios?
ACTUALIZACIÓN:
Una pregunta era ¿qué es lo que necesita la clase de? Aquí está la implementación de findAll:
public <T extends Element> List<T> findAll(IElementPredicate<T> pred) {
List<T> ret = new LinkedList<T>();
Class<T> c = pred.getGenericClass();
for(Element e: elements) {
if (!c.isInstance(e)) continue;
T obj = c.cast(e);
if (pred.match(obj)) {
ret.add(c.cast(e));
}
}
return ret;
}
Si bien es cierto que sólo se necesita un partido de T, que necesito para asegurarse de que el objeto es un T antes de que pueda llamarlo. Para eso, necesito los métodos "isInstance" y "cast" de Class (hasta donde puedo decir).
Uso los visitantes con frecuencia (en este mismo proyecto, de hecho) pero en este caso particular, el patrón de visitante agregaría mucho más código y complejidad de lo que creo que sería necesario. –
Estoy de acuerdo contigo, es una especie de interruptor de complicación en la forma en que tiene grandes efectos secundarios. Es difícil revertir el generalismo como lo desea. Creo que su solución es la mejor, porque sin un visitante creo que necesitaría un enlace tardío o una verificación de tipo explícita (en la forma en que equals() funciona). Su solución es mejor porque centraliza la verificación de tipos en lugar de poner esa carga en los implementadores. También ves este problema en las implementaciones genéricas Comparables. –
Y con el enlace tardío quise decir que Java debería implementar una resolución de método sobrecargada basada en el tipo de objeto, no en el tipo de referencia. Esto es básicamente lo que implementó en su código. –