Type safety, Java generics and querying
我有一个有趣的情况,我想知道是否有更好的方法来做到这一点。情况是这样的,我有一个树结构(特别是抽象语法树),一些节点可以包含各种类型的子节点,但都是从给定的基类扩展而来的。
我想经常在这棵树上进行查询,我想返回我感兴趣的特定子类型。所以我创建了一个谓词类,然后我可以将它传递到一个通用查询方法中。起初我有一个查询方法,看起来像这样:
1 | public <T extends Element> List<T> findAll(IElementPredicate pred, Class<T> c); |
其中
1 2 | List<Declaration> decls = scope.findAll(new DeclarationPredicate(), Declaration.class); |
所以我把它重构成这样:
1 | public <T extends Element> List<T> findAll(IElementPredicate<T> pred); |
其中
1 2 3 4 5 | public interface IElementPredicate<T extends Element> { public boolean match(T e); public String getDescription(); public Class<T> getGenericClass(); } |
这里的要点是谓词接口被扩展为提供
1 | List<Declaration> decls = scope.findAll(new DeclarationPredicate()); |
我以前没有注意到这种模式。这是处理Java泛型语义的典型方法吗?只是好奇我是否错过了更好的模式。
评论?
更新:
一个问题是你需要这门课做什么?以下是findall的实现:
1 2 3 4 5 6 7 8 9 10 11 12 | 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; } |
虽然匹配只需要一个T,但我需要确保对象是一个T,然后才能调用它。为此,我需要类的"isInstance"和"cast"方法(据我所知)。
我认为最接近的"模式"是类型标记,泛型教程推荐它们。您还可以将基本谓词转换为超级类型标记(a.k.a.gafter gadget),并在定义新谓词时保存额外的几行。
您的树结构听起来非常像一个XMLDOM对象。您是否考虑过将树转换为DOM结构并使用xpath进行查询?它可能比自定义代码少得多。
如果需要,可以使用访问者模式或变体来绕过显式构造和强制转换。请参见Hibernate wiki页面。我一直在想,如果你完全考虑到类型擦除的特殊性,是否能解决你的问题,我不完全确定它会一直起作用。
编辑:我看到了你的附加内容。如果要使谓词层次结构遵循访问者层次结构,则在match调用之前不需要强制转换。如此(未经测试):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | interface Element { public boolean accept(ElementPredicateVisitor v); } class Declaration implements Element { public boolean accept(ElementPredicateVisitor v) { return v.visit(this); } } class TaxReturn implements Element { public boolean accept(ElementPredicateVisitor v) { return v.visit(this); } } interface IElementPredicate { public void match(Element e); } class ElementPredicateVisitor implements IElementPredicate { public boolean match(Element e) { return e.accept(this); } /** * default values */ boolean visit(Declaration d) { return false; } boolean visit(TaxReturn tr) { return false; } } class DeclarationNamePredicate extends ElementPredicateVisitor { boolean visit(Declaration d) { return d.dSpecificExtraName() =="something" } } class TaxReturnSumPredicate extends ElementPredicateVisitor { boolean visit(TaxReturn tr) { return tr.sum() > 1000; } } public <T extends Element> List<T> findAll(IElementPredicate pred) { List<T> ret = new LinkedList<T>(); for(Element e: elements) { if (pred.match(obj)) { ret.add((T) obj); } } return ret; } |
我认为这是一个很好和干净的方法,我可能会用同样的方法。