Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java8 filter and return if only element

How can I filter a list using java8 streams and return the found element if it is the only element in the filtered list, otherwise(if there are more which meet the condition, or there is no result that meets the condition) return for example an Optional.empty()

I would need something like this:

Suppose I have a:

List<String> list = Arrays.asList("Apple","Banana","Peach");

then I want:

Optional<String> string = list.stream()
    .filter(item -> item.startsWith("A"))
    .findOne();

I know I can do it by:

boolean singleElement = list.stream()
        .filter(item -> item.startsWith("A"))
        .count() == 1;

String string = null;

if(singleElement){
  string = list.stream().filter(item -> item.startsWith("A")).iterator().next();
}

but I was wondering if I can do it in a single stream?

Is there any single stream solution?

like image 863
Sunflame Avatar asked Mar 13 '18 10:03

Sunflame


People also ask

What is the return type of filter in Java 8?

Java stream provides a method filter() to filter stream elements on the basis of given predicate. Suppose you want to get only even elements of your list then you can do this easily with the help of filter method. This method takes predicate as an argument and returns a stream of consisting of resulted elements.

How do I get one element from a stream list?

Using Stream findFirst() Method: The findFirst() method will returns the first element of the stream or an empty if the stream is empty. Approach: Get the stream of elements in which the first element is to be returned. To get the first element, you can directly use the findFirst() method.

What is the difference between findFirst and findAny Java 8?

The findAny() method returns any element from a Stream, while the findFirst() method returns the first element in a Stream.

What is significance of filter() in Java?

The filter() function of the Java stream allows you to narrow down the stream's items based on a criterion. If you only want items that are even on your list, you can use the filter method to do this. This method accepts a predicate as an input and returns a list of elements that are the results of that predicate.


3 Answers

Not very pretty, but you could limit the stream to 2 elements, collect those in a List, and see if that list has exactly one element. This still has more than one line, and has some overhead for creating the list, but that overhead is limited (to a list of size 2) and it does not have to iterate the stream twice, either.

List<String> tmp = list.stream()
    .filter(item -> item.startsWith("A"))
    .limit(2)
    .collect(Collectors.toList());
Optional<String> res = tmp.size() == 1 ? Optional.of(tmp.get(0)) : Optional.empty();

(My other idea was to use reduce((s1, s2) -> null) after limit(2) and reduce any two matches to null, but instead of returning an Optional.empty this will just raise an Exception, i.e. it does not work, but maybe this triggers some better (working) ideas.)


Update: It seems like while reduce raises an Exceptions, Collectors.reducing does not, and instead returns an Optional.empty as desired, so this also works, as shown in this answer to a very similar question. Still, I'd add limit(2) to make it stop early:

Optional<String> res = list.stream()
    .filter(item -> item.startsWith("A"))
    .limit(2)
    .collect(Collectors.reducing((s1, s2) -> null));

(If you like this last part, please upvote the original answer.)

like image 107
tobias_k Avatar answered Oct 10 '22 01:10

tobias_k


You could use google Guava library's MoreCollectors.onlyElement as below:

    List<String> list = Arrays.asList("Apple", "Banana", "Peach");
    String string = null;
    try {
        string = list.stream()
                .filter(item -> item.startsWith("A"))
                .collect(MoreCollectors.onlyElement());
    } catch (NoSuchElementException | IllegalArgumentException iae) {
        System.out.println("zero or more than one elements found.");
    }
    Optional<String> res = string == null ? Optional.empty() : Optional.of(string);

Notice it throws NoSuchElementException if there is no element and it throws IllegalArgumentException if there are more than one elements.

like image 20
Vinay Prajapati Avatar answered Oct 10 '22 03:10

Vinay Prajapati


I don't know if this counts as a single operation to you, but you can do :

Arrays.asList("Apple", "Banana", "Peach")
            .stream()
            .collect(Collectors.collectingAndThen(
                    Collectors.partitioningBy(
                            x -> x.startsWith("A")),
                    map -> {
                        List<String> list = map.get(Boolean.TRUE);
                        return list.size() == 1 ? Optional.of(list.get(0)) : Optional.empty();
                    }));
like image 34
Eugene Avatar answered Oct 10 '22 02:10

Eugene