Java 8 Stream indexOf method based on predicate
我只是遇到这样的情况,我需要知道列表中元素的索引(位置),但是只有一个谓词表达式才能标识该元素。 我找了一个Stream函数
1 | int index = list.stream().indexOf(e ->"TESTNAME".equals(e.getName())); |
但无济于事。 当然,我可以这样写:
1 2 | int index = list.indexOf(list.stream().filter(e ->"TESTNAME".equals(e.getName())) .findFirst().get()); |
但这将a)遍历列表两次(在最坏的情况下该元素将是最后一个元素),并且b)如果没有任何元素与谓词匹配(我希望使用-1索引),它将失败。
我为此功能编写了一个实用程序方法:
1 2 3 4 5 6 7 8 9 10 | public static < T > int indexOf(List< T > list, Predicate<? super T> predicate) { int idx = 0; for (Iterator< T > iter = list.iterator(); iter.hasNext(); idx++) { if (predicate.test(iter.next())) { return idx; } } return -1; } |
但是,因为这似乎是一个非常琐碎的算法,所以我希望它在Java 8 Stream API中的某个位置。 我只是想念它,还是真的没有这种功能? (奖金问题:如果没有这种方法,是否有充分的理由?在函数式编程中使用索引可能是一种反模式?)
您的循环还不错,但是您可以简化它:
1 2 3 4 5 | public static < T > int indexOf(List< T > list, Predicate<? super T> predicate) { for(ListIterator< T > iter = list.listIterator(); iter.hasNext(); ) if(predicate.test(iter.next())) return iter.previousIndex(); return -1; } |
您可以使用类似
1 2 3 4 5 | public static < T > int indexOf(List< T > list, Predicate<? super T> predicate) { return IntStream.range(0, list.size()) .filter(ix -> predicate.test(list.get(ix))) .findFirst().orElse(-1); } |
但是如果列表很大而不是随机访问,则效率将非常低下。我会坚持下去。
从Java9开始,还有其他选择
1 2 3 4 | public static < T > int indexOf(List< T > list, Predicate<? super T> predicate) { long noMatchPrefix = list.stream().takeWhile(predicate.negate()).count(); return noMatchPrefix == list.size()? -1: (int) noMatchPrefix; } |
这对于"对第一个匹配元素进行计数"任务确实具有表达力,但与"获取第一个匹配元素的索引"并不完全相同,后者显示了何时没有匹配项,因此我们需要替换然后用
原因是"索引"的概念仅在"有序集合"中具有含义。
另一个原因是
至于您的实用程序功能,我不明白您为什么要这样编写而不是这样:
1 2 3 4 5 6 7 8 | static < T > int indexOf(List< T > findIn, Predicate<? super T> pred) { for (int i = 0; i < findIn.size(); i++) { if (pred.test(findIn.get(i))) { return i; } } return -1; } |
1 2 3 4 5 6 7 | IntPredicate isNotNull = i -> Objects.nonNull(list.get(i)); IntPredicate isEqualsTestName = i -> list.get(i).getName().equals("TESTNAME"); int index = IntStream.range(0, list.size()) .filter(isNotNull.and(isEqualsTestName)) .findFirst() .orElse(-1); |
我想说一个简单的解决方案是使用
1 2 3 4 5 | IntStream.range(0, list.size()) .filter(i -> Objects.nonNull(list.get(i))) .filter(i ->"TESTNAME".equals(list.get(i).getName())) .findFirst() .orElse(-1); |
过滤出