9

Anyone knows how to achieve following piece code in a Java 8 way respectively is there any stream methods to detect the first element in a forEach?

List<String> myList = new ArrayList<String>();
myList.add("A");
myList.add("B");

int i = 0;

for (final String value : myList) {
   if (i == 0) {
     System.out.println("Hey that's the first element");
   }

   System.out.println(value);

   i++;
 }

Java8:

myList.stream().forEach(value -> {
  // TODO: How to do something special for first element?

  System.out.println(value);
});

Furthermore, let's says that the goal is the following (console output):

A    Something special
B
C
D
E
F
David Dal Busco
  • 7,975
  • 15
  • 55
  • 96
  • Doing something specific for one element would be easier (and more appropriate) with just `doSomethind(list.get(0))` + loop/stream for the whole list. – esin88 Mar 24 '17 at 09:57
  • no ned to loop or even stream for getting only the 1st elememt. do get the element at index 0 – ΦXocę 웃 Пepeúpa ツ Mar 24 '17 at 09:58
  • Add a console output mockup to try to explain why the first element has to be detected – David Dal Busco Mar 24 '17 at 10:01
  • Use an [`IntStream`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html) but then its rather a duplicate of (a duplicate) [Java 8 forEach with index](http://stackoverflow.com/questions/22793006/java-8-foreach-with-index) ;-) – Roland Mar 24 '17 at 10:01
  • 4
    Java8 streams are part of the *functional programming* facilities in the JVM. As such any operation in a stream should be *stateless*, but to distinguish between the first or any other element *is* a state. Therefore the streams API is the wrong tool for what you want to do. – Timothy Truckle Mar 24 '17 at 10:02
  • 3
    This isn't really suited to using streams and forEach, which are intended for cases where the handling of all the items are homogeneous. Any solution you could come up with would be highly contrived and would probably break as soon as you tried it with a parallel stream. In other words, use a `for` loop. – David Conrad Mar 24 '17 at 10:02
  • rephrasing my comment... use an `IntStream` if you really think you need a `Stream` here... otherwise... don't? – Roland Mar 24 '17 at 10:18
  • 1
    @PeterParker first `forEach` must be replaced with `forEachOrdered`. Then what happens if you stream out of a Set? There are no guarantees that *first is actually first*. The most right way to do it would be to look at the absolute must-read here of the Holger answer : http://stackoverflow.com/questions/27547519/most-efficient-way-to-get-the-last-element-of-a-stream; that's using a custom Spliterator. – Eugene Mar 24 '17 at 10:20
  • Good point @Eugene, thx for the input, absolutely – David Dal Busco Mar 24 '17 at 10:39
  • 2
    `Iterator i=myList.iterator(); if(i.hasNext()) System.out.println("first: " + i.next()); i.forEachRemaining(System.out::println);` – Holger Mar 24 '17 at 12:19
  • 1
    @Holger this is exactly what I was thinking. Would you add that as an option to the answers? – Eugene Mar 24 '17 at 12:36

6 Answers6

8

May this work for you?

myList.stream().findFirst().ifPresent(e -> System.out.println("Special: " + e));
myList.stream().skip(1).forEach(System.out::println);

Output:

Special: A
B

Alternative:

myList.stream().findFirst().ifPresent(e -> somethingSpecial(e));
myList.stream().forEach(System.out::println);
Oneiros
  • 4,328
  • 6
  • 40
  • 69
  • 1
    I would rather use `myList.stream().findFirst().ifPresent(e -> System.out.println("Special: " + e));` instead of `peek`... Note that the javadoc of `findFirst()` says "If the stream has no encounter order, then any element may be returned.", but its ok for ordered lists. – Roland Mar 24 '17 at 10:18
  • 1
    @Oneiros you are assuming `myList` is a List, which is not bad, considering the OP wanted that, but if you (accidentally?) switch to `Set`, this will fail. The proper way would probably be to check the Spliterator for `SIZED` and `SUBSIZED` properties. – Eugene Mar 24 '17 at 10:29
  • 1
    Why would OP use a `Set` considering that he wants to do special actions on first element? Sets are not ordered, it would make no sense in this context. – Oneiros Mar 24 '17 at 10:36
  • 2
    @Oneiros: you’re right, treating the “first” element of an unordered `Set` specially makes no sense, but you wouldn’t believe how often people want exactly this… – Holger Mar 24 '17 at 12:14
  • 1
    I marked this answer as the solution but I've to say that the other were also valid. I opened that post for curiosity and because I wanted to learn something, which was achieved. At the end, mostly for performance reason, I most probably gonna use a for like I always did but gonna use forEachOrdered and findFirst in other occasions – David Dal Busco Mar 25 '17 at 17:25
1

I don't think there is something provided in stream.

There are several alternatives you may consider:

Method 1:

int[] i = {0};  // I am using it only as an int holder.
myList.stream().forEach(value -> {
  if (i[0]++ == 0) {
    doSomethingSpecial;
  }
  System.out.println(value);
});

Method 2:

If your list is quick for random access (e.g. ArrayList), you may consider:

IntStream.range(0, myList.size())
  .forEach(i -> {
    ValueType value = myList.get(i);
    if (i == 0) {
      doSomethingSpecial();
    }
    System.out.println(value);
  });
Adrian Shum
  • 38,812
  • 10
  • 83
  • 131
1

Try StreamEx

StreamEx.of("A", "B", "C") //
        .peekFirst(e -> System.out.println(e + " Something special")).skip(1) //
        .forEach(System.out::println);
user_3380739
  • 1
  • 14
  • 14
1

If you were trying to achieve it in a single stream statement, here's how I'd do it:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

public class Solution {
    public static void main(String[] args) {
        List<String> myList = new ArrayList<>();
        myList.add("A");
        myList.add("B");
        myList.add("C");
        myList.add("D");
        myList.add("E");
        myList.add("F");

        AtomicBoolean firstElementProcessed = new AtomicBoolean(false);
        myList.stream().map(s -> {
            if (firstElementProcessed.getAndSet(true)) {
                return s;
            }
            return (s + " Something special");
        }).forEach(System.out::println);
    }
}

And maybe for better readability, I'd refactor it to:

public class Solution {
        ...

        AtomicBoolean firstElementProcessed = new AtomicBoolean(false);
        myList.stream().map(s -> getString(firstElementProcessed, s)).forEach(System.out::println);
    }

    private static String getString(AtomicBoolean firstElementProcessed, String s) {
        if (firstElementProcessed.getAndSet(true)) {
            return s;
        }
        return (s + " Something special");
    }
}
vivekmore
  • 403
  • 5
  • 19
0

There is no reason to not use simple for loop, foreach is is pretty but limited to side effects.

Kamil Gołąbek
  • 65
  • 1
  • 10
0

Something like

    List<String> mylist = Arrays.asList("A", "B", "C", "D", "E", "F");
    Optional<String> findFirst = mylist.stream().findFirst();
    findFirst.ifPresent(i -> System.out.println("Hey! " + i + ", that's the first element"));
    mylist.stream().skip(1).forEach(System.out::println);
Viktor Mellgren
  • 4,318
  • 3
  • 42
  • 75