J'ai une situation intéressante et je me demande s'il y a une meilleure façon de le faire. La situation est la suivante, j'ai une structure arborescente (un arbre de syntaxe abstraite, en particulier) et certains nœuds peuvent contenir des nœuds enfants de différents types, mais tous étendus à partir d'une classe de base donnée.Sécurité de type, génériques Java et interrogation
Je veux fréquemment faire des requêtes sur cet arbre et j'aimerais récupérer les sous-types spécifiques qui m'intéressent. J'ai donc créé une classe de prédicat que je peux ensuite passer dans une méthode de requête générique. Au début, j'avais une méthode de requête qui ressemblait à ceci:
public <T extends Element> List<T> findAll(IElementPredicate pred, Class<T> c);
où l'argument Class
a été utilisé juste pour indiquer le type de retour. Ce qui m'a dérangé à propos de cette approche, c'est que tous mes prédicats étaient déjà pour des types spécifiques, donc il y a des informations redondantes ici. Un appel typique pourrait ressembler à:
List<Declaration> decls =
scope.findAll(new DeclarationPredicate(), Declaration.class);
Je remaniée avec comme ceci:
public <T extends Element> List<T> findAll(IElementPredicate<T> pred);
Si l'interface IElementPredicate
ressemble à ceci:
public interface IElementPredicate<T extends Element> {
public boolean match(T e);
public String getDescription();
public Class<T> getGenericClass();
}
Le point ici est que le prédicat L'interface est développée pour fournir l'objet Class
à la place. Il rend l'écriture de la méthode findAll
un peu plus de travail et il ajoute un peu plus de travail dans l'écriture du prédicat, mais ce sont deux choses minuscules "une seule fois" et cela rend l'appel de requête beaucoup plus agréable parce que vous ne doit ajouter l'argument supplémentaire (potentiellement redondant), par exemple
List<Declaration> decls = scope.findAll(new DeclarationPredicate());
Je n'ai pas remarqué ce modèle auparavant. Est-ce une façon typique de traiter la sémantique des génériques Java? Juste curieux de savoir s'il me manque un meilleur motif.
Commments?
MISE À JOUR:
Une question est ce que vous avez besoin de la classe pour? Voici la mise en œuvre 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;
}
Il est vrai ce match ne prend que T, je dois vous assurer que l'objet est un T avant que je puisse l'appeler. Pour cela, j'ai besoin des méthodes "isInstance" et "cast" de Class (pour autant que je sache).
J'utilise fréquemment les visiteurs (dans ce même projet, en fait) mais dans ce cas particulier, le modèle de visiteur ajouterait beaucoup plus de code et de complexité que je ne le pense. –
Je suis d'accord avec vous, c'est un peu comme un changement de complicateur dans la façon dont il a de grands effets secondaires. Il est difficile d'inverser le généralisme comme vous voulez. Je crois que votre solution est la meilleure, parce que sans un visiteur je crois que vous auriez besoin d'une liaison tardive, ou une vérification de type explicite (de la même manière que equal() fonctionne). Votre solution est meilleure car elle centralise le contrôle de type au lieu de mettre ce fardeau sur les implémenteurs. Vous voyez également ce problème dans les implémentations génériques Comparable. –
Et avec une liaison tardive, je voulais dire que Java devrait implémenter une résolution de méthode surchargée basée sur le type d'objet, pas sur le type de référence. C'est essentiellement ce que vous avez implémenté dans votre code. –