可选择在流

时间:2017-10-25 17:10:51

标签: java java-8 java-stream

我正在尝试在对象列表上执行“最佳匹配”。我想实现一个级联过滤器,目标是最终只有一个对象最终成为“最佳匹配”。我有一个ObjectA列表,以及一个我正在比较其属性的ObjectB。如果有多个元素,是否可以选择将过滤器应用于流?

目前我已经实现了这样:

List<ObjectA> listOfObjectA;
ObjectB oB;
List<ObjectA> matchedByProp1 = listOfObjectA.stream()
        .filter(oA -> oB.getProp1().equals(oA.getProp1())).collect(Collectors.toList());
if (matchedByProp1.isEmpty()) {
    // If no objects match, then return null
    return null;
} else if (matchedByProp1.size() == 1) {
    // If one object matches prop1, this is easy
    return matchedByProp1.stream().findFirst().orElse(null);
}

// If more than one object is left, filter further by prop2
List<ObjectA> matchedByProp2 = matchedByProp1.stream()
        .filter(oA -> oB.getProp2().equals(oA.getProp2()))
        .collect(Collectors.toList());
if (matchedByProp2.isEmpty()) {
    // If further filtering is not successful, return one from the previous set
    return matchedByProp1.stream().findFirst().orElse(null);
} else if (matchedByProp2.size() == 1) {
    // If one object matches prop2, this is easy
    return matchedByProp2.stream().findFirst().orElse(null);
}

// If more than one object is left, filter further by prop3
List<ObjectA> matchedByProp3 = matchedByProp2.stream()
        .filter(oA -> oB.getProp3().equals(oA.getProp3()))
        .collect(Collectors.toList());
if (matchedByProp3.isEmpty()) {
    // If further filtering is not successful, return one from the previous set
    return matchedByProp2.stream().findFirst().orElse(null);
} else if (matchedByProp3.size() == 1) {
    // If one object matches prop3, this is easy
    return matchedByProp3.stream().findFirst().orElse(null);
}

// We still have too many options, just choose one
return matchedByProp3.stream().findFirst().orElse(null);

这适用于这种情况,但似乎有很多重复的代码。此外,ObjectA和ObjectB可以切换,因此我不得不重复此代码两次,一次是ObjectA列表,一次是ObjectB列表。我想做的是更像这样的事情:

ObjectA match = listOfObjectA.stream()
    .filter(oA -> oB.getProp1().equals(oA.getProp1()))
    .optionallyFilter(oA -> oB.getProp2().equals(oA.getProp2()))
    .optionallyFilter(oA -> oB.getProp3().equals(oA.getProp3()))
    .getFirst().orElse(null);

我已尝试按如下方式实现此方法,但遇到了我试图使用该流两次的问题。

private class Matcher<T, U> {
    private final U u;
    private final Stream<T> stream;

    public Matcher(U u) {
        this.u = u;
        stream = Stream.empty();
    }

    public Matcher(U u, Stream<T> stream) {
        this.u = u;
        this.stream = stream;
    }

    public Matcher<T, U> from(Stream<T> stream) {
        return new Matcher<>(u, stream);
    }

    public Matcher<T, U> mustMatch(Function<T, Object> tProp, Function<U, Object> uProp) {
        return new Matcher<>(u, stream.filter(t -> tProp.apply(t).equals(uProp.apply(u))));
    }

    public Matcher<T, U> shouldMatch(Function<T, Object> tProp, Function<U, Object> uProp) {
        if (stream.filter(t -> tProp.apply(t).equals(uProp.apply(u))).count() > 0) {
            return new Matcher<>(stream.filter(t -> tProp.apply(t).equals(uProp.apply(u))));
        }
        return this;
    }

    public Optional<T> get() {
        return stream.findFirst();
    }
}

ObjectA match = new Matcher<ObjectA, ObjectB>(oB, listOfObjectA.stream())
    .mustMatch(ObjectA::getProp1, ObjectB::getProp1)
    .shouldMatch(ObjectA::getProp2, ObjectB::getProp2)
    .shouldMatch(ObjectA::getProp3, ObjectB::getProp3)
    .get().orElse(null);

现在我可以在我的Matcher类中使用列表收集器,就像我现在正在做的那样,但似乎只是一个简单的条件将流收集到列表中并重新流式传输它似乎是不必要的。有没有更好的方法呢?请注意,在不同的用途中,可能会有不同的属性。

2 个答案:

答案 0 :(得分:4)

据我了解你的逻辑:

List<Predicate<ObjectA>> props = Arrays.asList(
    oA -> oB.getProp1().equals(oA.getProp1()),
    oA -> oB.getProp2().equals(oA.getProp2()),
    oA -> oB.getProp3().equals(oA.getProp3()));

ObjectA previousChoice = null;

for(Predicate<ObjectA> p: props) {
    listOfObjectA = listOfObjectA.stream().filter(p).collect(Collectors.toList());
    if(listOfObjectA.isEmpty()) return previousChoice;
    else {
        previousChoice = listOfObjectA.get(0);
        if(listOfObjectA.size() == 1) break;
    }
}
return previousChoice;

或没有溪流:

listOfObjectA = new ArrayList<>(listOfObjectA);
ObjectA previousChoice = null;

for(Predicate<ObjectA> p: props) {
    listOfObjectA.removeIf(p.negate());
    if(listOfObjectA.isEmpty()) return previousChoice;
    else {
        previousChoice = listOfObjectA.get(0);
        if(listOfObjectA.size() == 1) break;
    }
}
return previousChoice;

这也可以变得更加普遍,以处理您的两种情况:

static ObjectB get(List<ObjectB> list, ObjectA oA) {
    return get(list,
        oB -> oA.getProp1().equals(oB.getProp1()),
        oB -> oA.getProp2().equals(oB.getProp2()),
        oB -> oA.getProp3().equals(oB.getProp3()));
}
static ObjectA get(List<ObjectA> list, ObjectB oB) {
    return get(list,
        oA -> oB.getProp1().equals(oA.getProp1()),
        oA -> oB.getProp2().equals(oA.getProp2()),
        oA -> oB.getProp3().equals(oA.getProp3()));
}
static <T> T get(List<T> listOfT, Predicate<T>... props) {
    listOfT = new ArrayList<>(listOfT);
    T previousChoice = null;

    for(Predicate<T> p: props) {
        listOfT.removeIf(p.negate());
        if(listOfT.isEmpty()) return previousChoice;
        else {
            previousChoice = listOfT.get(0);
            if(listOfT.size() == 1) break;
        }
    }
    return previousChoice;
}

虽然谓词看起来完全一样,但假设ObjectAObjectB没有定义这些属性的公共基类,它们会做不同的事情(否则它会太简单)。所以这种重复是不可避免的。尝试使用谓词中的Function委托更多地使其更加通用,不太可能使代码更简单。

答案 1 :(得分:0)

这是你可以玩的技巧之一:

final ObjectA[] tmp= new ObjectA[2];
ObjectA match = listOfObjectA.stream()
        .filter(oA -> oB.getProp1().equals(oA.getProp1()))
        .peek(oA -> tmp[0] = oA)
        .filter(oA -> oB.getProp2().equals(oA.getProp2()))
        .peek(oA -> tmp[1] = oA)
        .filter(oA -> oB.getProp3().equals(oA.getProp3()))
        .findFirst().orElse(tmp[1] == null ? tmp[0] : tmp[1]);

或者:

{{1}}