[英]Optionally apply filter on 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類中使用列表收集器,但是似乎只是一種簡單的條件,就可以將流收集到列表中並重新進行流傳輸,這似乎是不必要的。 有更好的方法嗎? 請注意,在此的不同用法中,可能會有不同的屬性數。
據我了解您的邏輯:
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;
}
盡管謂詞看起來相同,但是假設ObjectA
和ObjectB
沒有定義這些屬性的公共基類(否則,這將太簡單了),它們在做不同的事情。 因此,這種重復是不可避免的。 試圖在謂詞中使用Function
委托來使其更加通用化,不太可能使代碼更簡單。
這是您可以玩的把戲之一:
final ObjectA[] tmp= new ObjectA[1];
ObjectA match = listOfObjectA.stream()
.filter(oA -> oB.getProp1().equals(oA.getProp1()))
.peek(oA -> tmp[0] = tmp[0] == null ? oA : tmp[0])
.filter(oA -> oB.getProp2().equals(oA.getProp2()))
.peek(oA -> tmp[0] = oA)
.filter(oA -> oB.getProp3().equals(oA.getProp3()))
.findFirst().orElse(tmp[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]);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.