16

I have the following code:

ArrayList <String> entries = new ArrayList <String>();

entries.add("0");
entries.add("1");
entries.add("2");
entries.add("3");

String firstNotHiddenItem = entries.stream()
                    .filter(e -> e.equals("2"))
                    .findFirst()
                    .get();

I need to know what is the index of that first returned element, since I need to edit it inside of entries ArrayList. As far as I know get() returns the value of the element, not a reference. Should I just use

int indexOf(Object o)

instead?

Eran
  • 387,369
  • 54
  • 702
  • 768
Andrei
  • 801
  • 2
  • 10
  • 27

4 Answers4

19

You can get the index of an element using an IntStream like:

int index = IntStream.range(0, entries.size())
                     .filter(i -> "2".equals(entries.get(i)))
                     .findFirst().orElse(-1);

But you should use the List::indexOf method which is the preferred way, because it's more concise, more expressive and computes the same results.

Flown
  • 11,480
  • 3
  • 45
  • 62
  • 9
    The key difference between `List.indexOf` and using a stream with `filter` and `findFirst` is that `indexOf` uses the `equals` method of the class of the elements of the list, while the stream approach allows you to search based on potentially complex predicates that are not related to elements´ *equality*. – fps May 20 '17 at 00:02
8

You can't in a straightforward way - streams process elements without context of where they are in the stream.

However, if you're prepared to take the gloves off...

int[] position = {-1};

String firstNotHiddenItem = entries.stream()
        .peek(x -> position[0]++)  // increment every element encounter
        .filter("2"::equals)
        .findFirst()
        .get();

System.out.println(position[0]); // 2

The use of an int[], instead of a simple int, is to circumvent the "effectively final" requirement; the reference to the array is constant, only its contents change.

Note also the use of a method reference "2"::equals instead of a lambda e -> e.equals("2"), which not only avoids a possible NPE (if a stream element is null) and more importantly looks way cooler.


A more palatable (less hackalicious) version:

AtomicInteger position = new AtomicInteger(-1);

String firstNotHiddenItem = entries.stream()
        .peek(x -> position.incrementAndGet())  // increment every element encounter
        .filter("2"::equals)
        .findFirst()
        .get();

position.get(); // 2
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • 1
    Perfect answer! Simple and elegant and does exactly what I was looking for! Thanks! – Andrei May 22 '17 at 14:35
  • You also need to initialize the variable `position` in the 2nd variant w/ `-1`. `AtomicInteger position = new AtomicInteger(-1);` – einsA Jul 02 '19 at 09:33
  • dears actually I have a similar need but with some difference,I have Strin val={1,2,3,4,5} i want to have an index of element in the original array, i.e. i need to know which element index of String[] val was "3" then I store element address for example val[2] was "3" Without if and just with java8 – mi_mo May 20 '22 at 18:01
  • @milad this code will give you what you want. – Bohemian May 20 '22 at 18:34
  • dear @Bohemian i try this code but it just return a number of counter please assume I have array: int[] arr={1,2,3,4,5,6,..,15,...,20}, I want every element was arr[I] % 3 == 0 then i should insert in the correspond String[] strArray for example : arr={1,2,3,4,5,6,..,15,...,20} i must to have strArray={"1","2","Test",...,"Test","16","17","18","19","20"} ,Tnx – mi_mo May 20 '22 at 18:54
  • @milad `List out = IntStream.of(intArray).mapToObj(n -> n % 3 == 0 ? "Test" : String.valueOf(n)).collect(Collectors.toList());` – Bohemian May 20 '22 at 19:12
0

This will work using Eclipse Collections with Java 8

int firstIndex = ListIterate.detectIndex(entries, "2"::equals);

If you use a MutableList, you can simplify the code as follows:

MutableList<String> entries = Lists.mutable.with("0", "1", "2", "3");
int firstIndex = entries.detectIndex("2"::equals);

There is also a method to find the last index.

int lastIndex = entries.detectLastIndex("2"::equals);

Note: I am a committer for Eclipse Collections

Donald Raab
  • 6,458
  • 2
  • 36
  • 44
0

Yes, you should use indexOf("2") instead. As you might have noticed, any stream based solution has a higher complexity, without providing any benefit.

In this specific situation, there is no significant difference in performance, but overusing streams can cause dramatic performance degradation, e.g. when using map.entrySet().stream().filter(e -> e.getKey().equals(object)).map(e -> e.getValue()) instead of a simple map.get(object).

The collection operations may utilize their known structure while most stream operation imply a linear search. So genuine collection operations are preferable.

Of course, if there is no collection operation, like when your predicate is not a simple equality test, the Stream API may be the right tool. As shown in “Is there a concise way to iterate over a stream with indices in Java 8?”, the solution for any task involving the indices works by using the indices as starting point, e.g. via IntStream.range, and accessing the list via List.get(int). If the source in not an array or a random access List, there is no equally clean and efficient solution. Sometimes, a loop might turn out to be the simplest and most efficient solution.

Holger
  • 285,553
  • 42
  • 434
  • 765