键入安全性,Java泛型和查询

Type safety, Java generics and querying

我有一个有趣的情况,我想知道是否有更好的方法来做到这一点。情况是这样的,我有一个树结构(特别是抽象语法树),一些节点可以包含各种类型的子节点,但都是从给定的基类扩展而来的。

我想经常在这棵树上进行查询,我想返回我感兴趣的特定子类型。所以我创建了一个谓词类,然后我可以将它传递到一个通用查询方法中。起初我有一个查询方法,看起来像这样:

1
public <T extends Element> List<T> findAll(IElementPredicate pred, Class<T> c);

其中Class参数仅用于指示返回类型。这种方法困扰我的是,我所有的谓词都已经是针对特定类型的,所以这里有多余的信息。一个典型的呼叫可能看起来像:

1
2
List<Declaration> decls =
    scope.findAll(new DeclarationPredicate(), Declaration.class);

所以我把它重构成这样:

1
public <T extends Element> List<T> findAll(IElementPredicate<T> pred);

其中IElementPredicate接口如下:

1
2
3
4
5
public interface IElementPredicate<T extends Element> {
    public boolean match(T e);
    public String getDescription();
    public Class<T> getGenericClass();
}

这里的要点是谓词接口被扩展为提供Class对象。它使编写实际的findAll方法的工作量增加了一点,并且在编写谓词时增加了一点工作量,但这两个方法本质上都是很小的"一次性"事情,而且它使查询调用变得更好,因为您不必添加额外(可能是多余的)参数,例如。

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;
}


我认为这是一个很好和干净的方法,我可能会用同样的方法。