Java枚举类型的逻辑操作

8
我有一个项目,不同情况下我需要处理大数据集的不同子集。我的代码中有一个名为Collector的接口和一个实现了Collector的类DataCollector。这个DataCollector类通过枚举类型的条件实例化,用于创建子集。
例如,如果数据集是由100万个英文单词组成,我想要处理由奇数字母组成的单词子集,那么我可以进行以下操作:
DataCollector dataCollector = new DataCollector(CollectionType.WORDS_OF_ODD_LENGTH);
Set<String> fourLetteredWords = dataCollector.collect();

其中CollectionType是枚举类

enum CollectionType {
    WORDS_OF_ODD_LENGTH,
    WORDS_OF_EVEN_LENGTH,
    STARTING_WITH_VOWEL,
    STARTING_WITH_CONSONANT,
    ....
}

数据收集器根据其实例化时使用的枚举调用java.util.Predicate
到目前为止,这种方法已经足够强大和灵活,但现在我面临着越来越复杂的情况(例如,收集以元音字母开头的偶数长度单词)。我希望避免为每种情况添加新的CollectionType。我注意到许多这些复杂情况只是对简单情况的逻辑操作(例如,condition_1 &&& (condition_2 || condition_3))。
最终用户是指定这些条件的人,我唯一能控制的是我可以指定这些条件的集合。换句话说,最终用户只能从CollectionType中进行选择。现在,我正在尝试从仅能选择一个条件的能力推广到能够选择一个或多个条件的能力。为此,我需要类似于以下内容:
DataCollector dataCollector = new DataCollector(WORDS_OF_ODD_LENGTH &&
                                                STARTING_WITH_VOWEL);

有没有一种方法可以对我的枚举进行建模,以执行此类操作?我也欢迎其他想法(例如,我应该放弃这种基于枚举的方法来尝试其他方法等)。

2
Guava提供了许多实用函数(http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Predicates.html),可用于组合函数。您可以使用这些函数来组合枚举类型。让您的`enum`实现`Predicate<String>`接口。 - Boris the Spider
假设你可以使用Java 8。 - DennisW
1
@DennisW,OP提到了java.util.Predicate - Boris the Spider
啊,是的!我已经在使用谓词了......那么为什么不将其设为 enum implements Predicate<>呢? 这样优雅而简单 :-) - Chthonic Project
编码的简洁性是一个难以达成的目标。我仍在学习中,还有很长的路要走。这不是Boris第一次让我产生“额...嗯...哦,当然...哇”的评论。 - Chthonic Project
显示剩余2条评论
1个回答

16

我建议您使用Java 8,它具有支持谓词的Predicate和操作。

enum CollectionType implements Predicate<String> {
    WORDS_OF_ODD_LENGTH(s -> s.length() % 2 != 0),
    WORDS_OF_EVEN_LENGTH(WORDS_OF_ODD_LENGTH.negate()),
    STARTING_WITH_VOWEL(s -> isVowel(s.charAt(0))),
    STARTING_WITH_CONSONANT(STARTING_WITH_VOWEL.negate()),
    COMPLEX_CHECK(CollectionType::complexCheck);

    private final Predicate<String> predicate;

    CollectionType(Predicate<String> predicate) {
        this.predicate = predicate;
    }

    static boolean isVowel(char c) {
        return "AEIOUaeiou".indexOf(c) >= 0;
    }

    public boolean test(String s) {
        return predicate.test(s);
    }

    public static boolean complexCheck(String s) {
        // many lines of code, calling many methods
    }
}

你可以编写像这样的谓词:

Predicate<String> p = WORDS_OF_ODD_LENGTH.and(STARTING_WITH_CONSONANT);

甚至是以元音字母开头的五个字母单词

Predicate<String> p = STARTING_WITH_VOWEL.and(s -> s.length() == 5);

如果你想在读取文件时使用这个过滤器,你可以这样做:

List<String> oddWords = Files.lines(path).filter(WORDS_OF_ODD_LENGTH).collect(toList());

或者您可以在加载它们时将它们索引

Map<Integer, List<String>> wordsBySize = Files.lines(path)
                                .collect(groupBy(s -> s.length()));

即使您将枚举声明为谓词(Predicate),您仍可以像这样优化其用法。

if (predicate == WORDS_OF_ODD_LENGTH || predicate == WORDS_OF_EVEN_LENGTH) {
    // assume if the first word in a list of words of the same length
    // then take all words of that length.
    return wordsBySize.values().stream()
                               .filter(l -> predicate.test(l.get(0)))
                               .flatMap(l -> l.stream()).collect(toList());
} else {
    return wordsBySize.values().stream()
                               .flatMap(l -> l.stream())
                               .filter(predicate)
                               .collect(toList());
}

也就是说,通过使用enum,您可以识别一些谓词并针对它们进行优化。(这是否是一个好主意,我将由您来决定)


只有一个问题:我的谓词有点复杂,所以我应该在其他地方定义它们,以便代码可读性更好(例如,STARTING_WITH_VOWEL = _startingWithVowel,其中 private static _startingWithVowel = ... 是一个 10-15 行的代码,位于稍后)。这会导致“非法向前引用错误”。我应该创建一个单独的类来保存这些谓词,还是有简单的方法可以解决它? - Chthonic Project
1
@ChthonicProject 你可以像我在上面的例子中那样从谓词中调用一个方法。 lambda 表达式不需要超过一行(或使用赋值或字段)。 - Peter Lawrey
1
太好了!加上complexCheck的例子,加1分。 - Chthonic Project
@ChthonicProject,complexCheck 不一定要在同一个类中。你应该将它放在你认为合适的位置。 - Peter Lawrey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接