基于谓词的Java 8 Stream indexOf方法

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返回结果。


原因是"索引"的概念仅在"有序集合"中具有含义。 Stream可以来自任何Collection,例如Set;具有" indexOf"方法实质上意味着对源Set进行get(int index)操作。更不用说Stream也可以来自集合以外的任何其他来源。

另一个原因是indexOf操作的实现是,它基本上需要Stream s元素类型的有状态visitor;有状态的,因为它必须计算已经访问过的成员。有状态操作和Stream不太匹配。

至于您的实用程序功能,我不明白您为什么要这样编写而不是这样:

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

我想说一个简单的解决方案是使用IntStream

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

过滤出null元素将防止它抛出NullPointerException