Filter Java Stream to 1 and only 1 element

Go To StackoverFlow.com

165

I am trying to use Java 8 Streams to find elements in a LinkedList. I want to guarantee, however, that there is one and only one match to the filter criteria.

Take this code:

public static void main(String[] args) {

    LinkedList<User> users = new LinkedList<>();
    users.add(new User(1, "User1"));
    users.add(new User(2, "User2"));
    users.add(new User(3, "User3"));

    User match = users.stream().filter((user) -> user.getId() == 1).findAny().get();
    System.out.println(match.toString());
}

static class User {

    @Override
    public String toString() {
        return id + " - " + username;
    }

    int id;
    String username;

    public User() {
    }

    public User(int id, String username) {
        this.id = id;
        this.username = username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public int getId() {
        return id;
    }
}

This code finds a User based on their ID. But there are no guarantees how many Users matched the filter.

Changing the filter line to:

User match = users.stream().filter((user) -> user.getId() < 0).findAny().get();

Will throw a NoSuchElementException (good!)

I would like it to throw an error if there are multiple matches, though. Is there a way to do this?

2014-03-27 17:25
by ryvantage
count() is a terminal operation so you can't do that. The stream can't be used after - Alexis C. 2014-03-27 17:42
Ok, thanks @ZouZou. I wasn't entirely certain what that method did. Why is there no Stream::size - ryvantage 2014-03-27 17:44
@ryvantage Because a stream can only be used once: calculating its size means "iterating" over it and after that you can't use the stream any longer - assylias 2014-03-27 17:45
Wow. That one comment helped me understand Streams so much more than I did before.. - ryvantage 2014-03-27 17:50
This is when you realize that you had needed to use a LinkedHashSet (assuming you want insertion order preserved) or a HashSet all along. If your collection is only used to find a single user id, then why are you collecting all the other items? If there is a potential that you will always need to find some user id which also needs to be unique, then why use a list and not a set? You are programming backwards. Use the right collection for the job and save yourself this headach - smac89 2017-11-28 16:22


140

Create a custom Collector

public static <T> Collector<T, ?, T> toSingleton() {
    return Collectors.collectingAndThen(
            Collectors.toList(),
            list -> {
                if (list.size() != 1) {
                    throw new IllegalStateException();
                }
                return list.get(0);
            }
    );
}

We use Collectors.collectingAndThen to construct our desired Collector by

  1. Collecting our objects in a List with the Collectors.toList() collector.
  2. Applying an extra finisher at the end, that returns the single element — or throws an IllegalStateException if list.size != 1.

Used as:

User resultUser = users.stream()
        .filter(user -> user.getId() > 0)
        .collect(toSingleton());

You can then customize this Collector as much as you want, for example give the exception as argument in the constructor, tweak it to allow two values, and more.

An alternative — arguably less elegant — solution:

You can use a 'workaround' that involves peek() and an AtomicInteger, but really you shouldn't be using that.

What you could do istead is just collecting it in a List, like this:

LinkedList<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));
List<User> resultUserList = users.stream()
        .filter(user -> user.getId() == 1)
        .collect(Collectors.toList());
if (resultUserList.size() != 1) {
    throw new IllegalStateException();
}
User resultUser = resultUserList.get(0);
2014-03-27 17:33
by skiwi
Guava's Iterables.getOnlyElement would shorten these solutions and provide better error messages. Just as a tip for fellow readers who already use Google Guava - Tim Büthe 2015-10-29 15:09
i wrapped this idea up into a class - https://gist.github.com/denov/a7eac36a3cda041f8afeabcef09d16f - denov 2016-05-24 21:35
The custom collector still collects all the items, which is O(n), isn't there a way to shortcut it? Get a single item can be done in 1 step, checking if another one exists is also 1 step, no matter how many more items are in the filtered stream - TWiStErRob 2016-08-03 12:41
It's too bad Guava can't add some of these useful utilities... it's not the first time where a method has been in Guava for Iterable or Iterator, but there has been no built-in way to do the same thing for Stream. : - Trejkaz 2017-03-30 00:54
Inspired by all these nice answers, what about this collector version (brevity) ? Collectors.collectingAndThen(toList(), Iterables::getOnlyElement)bjmi 2018-01-22 03:28
@LonelyNeuron Please don't edit my code. It puts me in a situation where I need to validate my entire answer, which I have written four years ago, and I simply don't have the time for it right now - skiwi 2018-05-17 16:26
I didn't change the spirit of your answer. Apart from one name, nothing changed. The first problem is that the answer is structured poorly by using "Update" blocks: https://meta.stackexchange.com/a/127655/31626 - Lonely Neuron 2018-05-17 16:29
You have one code block which is completely unnecessary, since you improved on it with the next iteration. it should be removed. The last example you gave is the best one. It should be on top, as readers want to find good answers quickly. Please review the edits I made and you will see that they preserve the spirit of your answer and enhance the structuring. Also, SO was built on the idea of improving other posts by editing them. Please refrain from asking others to not edit your posts and don't roll back good edit - Lonely Neuron 2018-05-17 16:32
Please also read this post and the accepted answer on asking others not to edit your post - Lonely Neuron 2018-05-17 16:37
@skiwi: Lonely's edit was helpful and correct, so I re-instated it after review. People visiting this answer today don't care about how you came to the answer, they don't need to see the old version and new version and an Updated section. That makes your answer more confusing and less helpful. It is much better to put posts in a final state, and if people want to see how it all played out they can view the post history - Martijn Pieters 2018-05-24 15:01
@MartijnPieters But the code that is now in the answer is not the code that I have written, it has been changed. I'm personally fine with changes in the answer structure and text, but as I said I don't have time to review the changes to the code. And ultimately it's my name under the answer and I'd like to keep it that way. I'd prefer if the changes to the actual code (signature, names, etc.) would be reverted - skiwi 2018-05-24 16:33
@skiwi: The code in the answer is absolutely what you have written. All the editor did was clean up your post, only removing an earlier version of the singletonCollector() definition obsoleted by the version that remains in the post, and renaming it to toSingleton(). My Java stream expertise is a bit rusty, but the renaming looks helpful to me. Reviewing this change took me 2 minutes, tops. If you don't have time to review edits, can I suggest that you ask someone else to do this in future, perhaps in the Java chat room - Martijn Pieters 2018-05-24 16:48
I have to say that I really don't like the name toSingleton, since that is misleading. It is not a singleton it returns, which I consider to be a reserved word in programming. This is a 'single element', or 'one instance' - Javo 2018-12-06 14:53


86

For the sake of completeness, here is the ‘one-liner’ corresponding to @prunge’s excellent answer:

User user1 = users.stream()
        .filter(user -> user.getId() == 1)
        .reduce((a, b) -> {
            throw new IllegalStateException("Multiple elements: " + a + ", " + b);
        })
        .get();

This obtains the sole matching element from the stream, throwing

  • NoSuchElementException in case the stream is empty, or
  • IllegalStateException in case the stream contains more than one matching element.

A variation of this approach avoids throwing an exception early and instead represents the result as an Optional containing either the sole element, or nothing (empty) if there are zero or multiple elements:

Optional<User> user1 = users.stream()
        .filter(user -> user.getId() == 1)
        .collect(Collectors.reducing((a, b) -> null));
2016-01-07 19:51
by glts
I like the initial approach in this answer. For customization purposes, it is possible to convert the last get() to orElseThrow()arin 2017-06-07 16:52
I like the brevity of this one, and the fact that it avoid creating an un-necessary List instance each time it is called - LordOfThePigs 2018-01-23 10:37


77

The other answers that involve writing a custom Collector are probably more efficient (such as Louis Wasserman's, +1), but if you want brevity, I'd suggest the following:

List<User> result = users.stream()
    .filter(user -> user.getId() == 1)
    .limit(2)
    .collect(Collectors.toList());

Then verify the size of the result list.

2014-03-28 15:39
by Stuart Marks
What's the point of limit(2) in this solution? What difference would it make whether the resulting list was 2 or 100? If it's greater than 1 - ryvantage 2014-03-28 18:31
It stops immediately if it finds a second match. This is what all the fancy collectors do, just using more code. :- - Stuart Marks 2014-03-29 03:24
How about adding Collectors.collectingAndThen(toList(), l -> { if (l.size() == 1) return l.get(0); throw new RuntimeException(); })Lukas Eder 2016-01-11 12:35
Javadoc says this about limit's param: maxSize: the number of elements the stream should be limited to. So, shouldn't it be .limit(1) instead of .limit(2) - alexbt 2017-12-18 19:41
@alexbt The problem statement is to ensure that there is exactly one (no more, no fewer) matching element. After my code, one can test result.size() to make sure it equals 1. If it's 2, then there's more than one match, so it's an error. If the code instead did limit(1), more than one match would result in a single element, which can't be distinguished from there being exactly one match. This would miss an error case the OP was concerned about - Stuart Marks 2017-12-18 20:18
@StuartMarks Ah, got it, I don't know why I assumed limit(...) would throw when it goes beyond the limit. thank - alexbt 2017-12-18 21:06


46

Guava provides MoreCollectors.onlyElement() which does the right thing here. But if you have to do it yourself, you could roll your own Collector for this:

<E> Collector<E, ?, Optional<E>> getOnly() {
  return Collector.of(
    AtomicReference::new,
    (ref, e) -> {
      if (!ref.compareAndSet(null, e)) {
         throw new IllegalArgumentException("Multiple values");
      }
    },
    (ref1, ref2) -> {
      if (ref1.get() == null) {
        return ref2;
      } else if (ref2.get() != null) {
        throw new IllegalArgumentException("Multiple values");
      } else {
        return ref1;
      }
    },
    ref -> Optional.ofNullable(ref.get()),
    Collector.Characteristics.UNORDERED);
}

...or using your own Holder type instead of AtomicReference. You can reuse that Collector as much as you like.

2014-03-27 17:51
by Louis Wasserman
@skiwi's singletonCollector was smaller and easier to follow than this, that's why I gave him the check. But good to see consensus in the answer: a custom Collector was the way to go - ryvantage 2014-03-27 20:37
Fair enough. I was primarily aiming for speed, not conciseness - Louis Wasserman 2014-03-27 20:40
Yeah? Why is yours faster - ryvantage 2014-03-27 20:45
Mostly because allocating an all-up List is more expensive than a single mutable reference - Louis Wasserman 2014-03-27 20:52
I was unable to get yours to compil - ryvantage 2014-03-28 18:28
Compiles (and works great!) with change AtomicReference::newAtomicReference<E>::newnezda 2015-03-01 14:57
Still having compile problems - Eclipse tells me "The method compareAndSet(capture#8-of ? extends Object, capture#8-of ? extends Object) in the type AtomicReference is not applicable for the arguments (null, E)" for !ref.compareAndSet(null, (E) e). If i don't cast to E in the 2nd argument, i get "The method compareAndSet(E, E) in the type AtomicReference is not applicable for the arguments (null, Object)". For some reason the generics aren't being picked up properly. Wish it was working though, this is a great answer - jlb 2015-08-18 11:58
@LouisWasserman, the final update sentence about MoreCollectors.onlyElement() should actually be first (and perhaps the only :) - Piotr Findeisen 2017-08-17 06:38


33

Use Guava's MoreCollectors.onlyElement() (JavaDoc).

It does what you want and throws an IllegalArgumentException if the stream consists of two or more elements, and a NoSuchElementException if the stream is empty.

Usage:

import static com.google.common.collect.MoreCollectors.onlyElement;

User match =
    users.stream().filter((user) -> user.getId() < 0).collect(onlyElement());
2016-11-11 21:19
by trevorade
Note for other users: MoreCollectors is part of the yet unreleased (as of 2016-12) unreleased version 21 - qerub 2016-12-12 13:56
This answer should go upper - Emdadul Sawon 2017-07-24 06:47


28

The "escape hatch" operation that lets you do weird things that are not otherwise supported by streams is to ask for an Iterator:

Iterator<T> it = users.stream().filter((user) -> user.getId() < 0).iterator();
if (!it.hasNext()) 
    throw new NoSuchElementException();
else {
    result = it.next();
    if (it.hasNext())
        throw new TooManyElementsException();
}

Guava has a convenience method to take an Iterator and get the only element, throwing if there are zero or multiple elements, which could replace the bottom n-1 lines here.

2014-05-24 19:26
by Brian Goetz
Guava's method: Iterators.getOnlyElement(Iterator iterator) - anre 2016-04-28 14:13


19

Update

Nice suggestion in comment from @Holger:

Optional<User> match = users.stream()
              .filter((user) -> user.getId() > 1)
              .reduce((u, v) -> { throw new IllegalStateException("More than one ID found") });

Original answer

The exception is thrown by Optional#get, but if you have more than one element that won't help. You could collect the users in a collection that only accepts one item, for example:

User match = users.stream().filter((user) -> user.getId() > 1)
                  .collect(toCollection(() -> new ArrayBlockingQueue<User>(1)))
                  .poll();

which throws a java.lang.IllegalStateException: Queue full, but that feels too hacky.

Or you could use a reduction combined with an optional:

User match = Optional.ofNullable(users.stream().filter((user) -> user.getId() > 1)
                .reduce(null, (u, v) -> {
                    if (u != null && v != null)
                        throw new IllegalStateException("More than one ID found");
                    else return u == null ? v : u;
                })).get();

The reduction essentially returns:

  • null if no user is found
  • the user if only one is found
  • throws an exception if more than one is found

The result is then wrapped in an optional.

But the simplest solution would probably be to just collect to a collection, check that its size is 1 and get the only element.

2014-03-27 17:37
by assylias
I would add an identity element (null) to prevent using get(). Sadly your reduce is not working as you think it does, consider a Stream that has null elements in it, maybe you think that you covered it, but I can be [User#1, null, User#2, null, User#3], now it will not throw an exception I think, unless I'm mistaken here - skiwi 2014-03-27 18:36
@Skiwi if there are null elements the filter will throw a NPE first - assylias 2014-03-27 18:37
Since you know that the stream can’t pass null to the reduction function, removing the identity value argument would render the entire dealing with null in the function obsolete: reduce( (u,v) -> { throw new IllegalStateException("More than one ID found"); } ) does the job and even better, it already returns an Optional, eliding the necessity for calling Optional.ofNullable on the result - Holger 2016-11-04 09:38


9

An alternative is to use reduction: (this example uses strings but could easily apply to any object type including User)

List<String> list = ImmutableList.of("one", "two", "three", "four", "five", "two");
String match = list.stream().filter("two"::equals).reduce(thereCanBeOnlyOne()).get();
//throws NoSuchElementException if there are no matching elements - "zero"
//throws RuntimeException if duplicates are found - "two"
//otherwise returns the match - "one"
...

//Reduction operator that throws RuntimeException if there are duplicates
private static <T> BinaryOperator<T> thereCanBeOnlyOne()
{
    return (a, b) -> {throw new RuntimeException("Duplicate elements found: " + a + " and " + b);};
}

So for the case with User you would have:

User match = users.stream().filter((user) -> user.getId() < 0).reduce(thereCanBeOnlyOne()).get();
2015-05-13 02:01
by prunge


5

Using a Collector:

public static <T> Collector<T, ?, Optional<T>> toSingleton() {
    return Collectors.collectingAndThen(
            Collectors.toList(),
            list -> list.size() == 1 ? Optional.of(list.get(0)) : Optional.empty()
    );
}

Usage:

Optional<User> result = users.stream()
        .filter((user) -> user.getId() < 0)
        .collect(toSingleton());

We return an Optional, since we usually can't assume the Collection to contain exactly one element. If you already know this is the case, call:

User user = result.orElseThrow();

This puts the burden of handeling the error on the caller - as it should.

2018-05-24 16:49
by Lonely Neuron


4

Guava has a Collector for this called MoreCollectors.onlyElement().

2017-01-24 20:16
by Hans


1

We can use RxJava (very powerful reactive extension library)

LinkedList<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));

User userFound =  Observable.from(users)
                  .filter((user) -> user.getId() == 1)
                  .single().toBlocking().first();

The single operator throws an exception if no user or more then one user is found.

2016-01-07 21:40
by frhack
Correct answer, initialializing a blocking stream or collection is probably not very cheap (in terms of resources) though - Karl Richter 2018-04-13 18:26


1

If you don't mind using a 3rd party library, SequenceM from cyclops-streams (and LazyFutureStream from simple-react) both a have single & singleOptional operators.

singleOptional() throws an exception if there are 0 or more than 1 elements in the Stream, otherwise it returns the single value.

String result = SequenceM.of("x")
                          .single();

SequenceM.of().single(); // NoSuchElementException

SequenceM.of(1, 2, 3).single(); // NoSuchElementException

String result = LazyFutureStream.fromStream(Stream.of("x"))
                          .single();

singleOptional() returns Optional.empty() if there are no values or more than one value in the Stream.

Optional<String> result = SequenceM.fromStream(Stream.of("x"))
                          .singleOptional(); 
//Optional["x"]

Optional<String> result = SequenceM.of().singleOptional(); 
// Optional.empty

Optional<String> result =  SequenceM.of(1, 2, 3).singleOptional(); 
// Optional.empty

Disclosure - I am the author of both libraries.

2016-01-11 06:08
by John McClean


1

As Collectors.toMap(keyMapper, valueMapper) uses a throwing merger to handle multiple entries with the same key it is easy:

List<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));

int id = 1;
User match = Optional.ofNullable(users.stream()
  .filter(user -> user.getId() == id)
  .collect(Collectors.toMap(User::getId, Function.identity()))
  .get(id)).get();

You will get a IllegalStateException for duplicate keys. But at the end I am not sure if the code would not be even more readable using an if.

2016-09-08 07:52
by Arne Burmeister
Fine solution! And if you do .collect(Collectors.toMap(user -> "", Function.identity())).get(""), you have a more generic behaviour - glglgl 2017-05-26 21:04


1

I am using those two collectors:

public static <T> Collector<T, ?, Optional<T>> zeroOrOne() {
    return Collectors.reducing((a, b) -> {
        throw new IllegalStateException("More than one value was returned");
    });
}

public static <T> Collector<T, ?, T> onlyOne() {
    return Collectors.collectingAndThen(zeroOrOne(), Optional::get);
}
2017-05-16 07:15
by Xavier Dury
Neat! onlyOne() throws IllegalStateException for >1 elements, and NoSuchElementException(inOptional::get`) for 0 elements - simon04 2018-01-05 10:30
@simon04 You could overload the methods to take a Supplier of (Runtime)Exception - Xavier Dury 2018-06-26 08:29


0

I went with the direct-approach and just implemented the thing:

public class CollectSingle<T> implements Collector<T, T, T>, BiConsumer<T, T>, Function<T, T>, Supplier<T> {
T value;

@Override
public Supplier<T> supplier() {
    return this;
}

@Override
public BiConsumer<T, T> accumulator() {
    return this;
}

@Override
public BinaryOperator<T> combiner() {
    return null;
}

@Override
public Function<T, T> finisher() {
    return this;
}

@Override
public Set<Characteristics> characteristics() {
    return Collections.emptySet();
}

@Override //accumulator
public void accept(T ignore, T nvalue) {
    if (value != null) {
        throw new UnsupportedOperationException("Collect single only supports single element, "
                + value + " and " + nvalue + " found.");
    }
    value = nvalue;
}

@Override //supplier
public T get() {
    value = null; //reset for reuse
    return value;
}

@Override //finisher
public T apply(T t) {
    return value;
}


} 

with the JUnit test:

public class CollectSingleTest {

@Test
public void collectOne( ) {
    List<Integer> lst = new ArrayList<>();
    lst.add(7);
    Integer o = lst.stream().collect( new CollectSingle<>());
    System.out.println(o);
}

@Test(expected = UnsupportedOperationException.class)
public void failOnTwo( ) {
    List<Integer> lst = new ArrayList<>();
    lst.add(7);
    lst.add(8);
    Integer o = lst.stream().collect( new CollectSingle<>());
}

}

This implementation not threadsafe.

2018-04-02 18:38
by gerardw


0

Using reduce

This is the simpler and flexible way I found (based on @prunge answer)

Optional<User> user = users.stream()
        .filter(user -> user.getId() == 1)
        .reduce((a, b) -> {
            throw new IllegalStateException("Multiple elements: " + a + ", " + b);
        })

This way you obtain:

  • the Optional - as always with your object or Optional.empty() if not present
  • the Exception (with eventually YOUR custom type/message) if there's more than one element
2018-08-24 14:07
by Fabio Bonfante


-2

Have you tried this

long c = users.stream().filter((user) -> user.getId() == 1).count();
if(c > 1){
    throw new IllegalStateException();
}

long count()
Returns the count of elements in this stream. This is a special case of a reduction and is equivalent to:

     return mapToLong(e -> 1L).sum();

This is a terminal operation.

Source: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html

2014-03-28 07:03
by pardeep131085
It was said that count() is not good to use because it is a terminal operation - ryvantage 2014-03-28 18:29
If this really is a quote, please add your source - Lonely Neuron 2018-09-05 10:10