327

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?

Neuron
  • 5,141
  • 5
  • 38
  • 59
ryvantage
  • 13,064
  • 15
  • 63
  • 112
  • `count()` is a terminal operation so you can't do that. The stream can't be used after. – Alexis C. Mar 27 '14 at 17:42
  • Ok, thanks @ZouZou. I wasn't entirely certain what that method did. Why is there no `Stream::size` ? – ryvantage Mar 27 '14 at 17:44
  • 10
    @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 Mar 27 '14 at 17:45
  • 4
    Wow. That one comment helped me understand `Stream`s so much more than I did before... – ryvantage Mar 27 '14 at 17:50
  • 4
    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 headache – smac89 Nov 28 '17 at 16:22
  • The solution can consume a large list and only at the end it will realize there are too many. In order to avoid this you could use .limit(2) before the collect statement. – David Nouls Jun 03 '21 at 12:20
  • @ryvantage Plus, Streams can potentially be infinite in length. – Jacob Zimmerman Nov 11 '22 at 21:06

24 Answers24

292

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 instead 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);
skiwi
  • 66,971
  • 31
  • 131
  • 216
  • 43
    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 Oct 29 '15 at 15:09
  • 2
    i wrapped this idea up into a class - https://gist.github.com/denov/a7eac36a3cda041f8afeabcef09d16fc – denov May 24 '16 at 21:35
  • 2
    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 Aug 03 '16 at 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. :( – Hakanai Mar 30 '17 at 00:54
  • Inspired by all these nice answers, what about this collector version (brevity) ? `Collectors.collectingAndThen(toList(), Iterables::getOnlyElement)` – bjmi Jan 22 '18 at 03:28
  • 1
    @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 May 17 '18 at 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/316262 – Neuron May 17 '18 at 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 edits – Neuron May 17 '18 at 16:32
  • Please also read [this post](https://meta.stackoverflow.com/questions/367885/users-stating-that-they-do-not-want-their-posts-to-be-edited) and [the accepted answer](https://meta.stackoverflow.com/a/367886/4298200) on asking others not to edit your posts – Neuron May 17 '18 at 16:37
  • 2
    @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 May 24 '18 at 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 May 24 '18 at 16:33
  • 2
    @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](https://chat.stackoverflow.com/rooms/info/139/java)? – Martijn Pieters May 24 '18 at 16:48
  • 12
    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 Dec 06 '18 at 14:53
  • Great! In kotlin it's called single(). To provide more exception details throw new IllegalStateException(String.format("%d elements", list.size())); – IPP Nerd Dec 07 '20 at 15:47
  • This solution is not ideal, it collects all of the items and then fails if there are more than one. A better approach would be to have a fully custom collector that stops as soon as more than one items is processed. – David Nouls Jun 01 '21 at 08:21
  • @TWiStErRob Does that actually bother? I mean, you _expect_ to have one and only one element. If you will get multiple, I guess it won't be so many, that an O(n) operation is a problem. – Dániel Somogyi Nov 03 '21 at 09:09
  • @DánielSomogyi We're expecting one item *after* `filter`, before `filter` there could be millions/billions. If you look at the answer at the time of me writing that comment, you'll see it was doing collect *before* `filter` (`collectingAndThen`), so it does matter; or maybe just wasn't clear that it was supposed to be applied *after* a `filter`. – TWiStErRob Nov 03 '21 at 09:21
  • @TWiStErRob ohh, sure, mb, I was way too focused on the collecting itself. So actually, you want to filter, and after the second element passing through the filtering abort with an error. This avoids filtering the other god-knows-how-many elements. To answer your question: I guess the solution by reduction (e.g. by @assylias) would do that, or you use 3rd party packages like guava. You also could use a for-loop... – Dániel Somogyi Nov 03 '21 at 11:21
  • 1
    Using reduce is the most effective and compact way to do this https://stackoverflow.com/a/52006240/574147 – Fabio Bonfante Feb 22 '22 at 10:42
  • .NET LINQ exposes a very simple to use `.Single` method - the fact that Java **still** does not have this natively built-in after **18** versions is so absurd to me. – Ermiya Eskandary Apr 12 '22 at 12:30
  • 1
    As @FabioBonfante said, reduce will be more elegant and efficient. – Hiroshi_U Aug 05 '22 at 08:52
170

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));
glts
  • 21,808
  • 12
  • 73
  • 94
  • 12
    I like the initial approach in this answer. For customization purposes, it is possible to convert the last `get()` to `orElseThrow()` – arin Jun 07 '17 at 16:52
  • 7
    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 Jan 23 '18 at 10:37
  • 1
    In case your use case allows for the stream to be empty, omit the `.get()` on the end of the chain and then you'll get an `Optional` which will either be empty in case the stream is empty or will be populated with the single element. – Matthew Wise Dec 10 '20 at 12:43
  • I don't think its a good solution since in an error message we will have only first two elements which are invalid and we will not include value of more than two of them. – Tomasz S Jun 29 '21 at 10:51
  • It is a great solution due to its brevity and comprehensibility. – phe Sep 13 '21 at 08:06
  • Downvoting for a hacky solution on reducer as it provides misinformation - it will always show 2 values even if there is a lot more. – Migol Oct 25 '21 at 09:22
  • 1
    Caution: `Stream#reduce(...)` does behave differently than `Stream#collect(Collectors.reducing(...))`: Both make the stream return an `Optional`, however `Stream#reduce` will throw an NPE nevertheless when returning `null` inside the binary operator. So the code in the answer works, but it wont if you mix up the two code pieces – Qw3ry Mar 31 '22 at 12:46
96

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.

if (result.size() != 1) {
  throw new IllegalStateException("Expected exactly one user but got " + result);
}
User user = result.get(0);
Eric Duminil
  • 52,989
  • 9
  • 71
  • 124
Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
  • 6
    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 Mar 28 '14 at 18:31
  • 25
    It stops immediately if it finds a second match. This is what all the fancy collectors do, just using more code. :-) – Stuart Marks Mar 29 '14 at 03:24
  • 11
    How about adding `Collectors.collectingAndThen(toList(), l -> { if (l.size() == 1) return l.get(0); throw new RuntimeException(); })` – Lukas Eder Jan 11 '16 at 12:35
  • 1
    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 Dec 18 '17 at 19:41
  • 10
    @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 Dec 18 '17 at 20:18
  • @StuartMarks Ah, got it, I don't know why I assumed `limit(...)` would throw when it goes beyond the limit. thanks – alexbt Dec 18 '17 at 21:06
91

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.

Neuron
  • 5,141
  • 5
  • 38
  • 59
Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
  • @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 Mar 27 '14 at 20:37
  • 1
    Fair enough. I was primarily aiming for speed, not conciseness. – Louis Wasserman Mar 27 '14 at 20:40
  • 1
    Yeah? Why is yours faster? – ryvantage Mar 27 '14 at 20:45
  • 3
    Mostly because allocating an all-up `List` is more expensive than a single mutable reference. – Louis Wasserman Mar 27 '14 at 20:52
  • I was unable to get yours to compile – ryvantage Mar 28 '14 at 18:28
  • Compiles (and works great!) with change `AtomicReference::new` → `AtomicReference::new` – nezda Mar 01 '15 at 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 Aug 18 '15 at 11:58
  • 2
    @LouisWasserman, the final update sentence about `MoreCollectors.onlyElement()` should actually be first (and perhaps the only :) ) – Piotr Findeisen Aug 17 '17 at 06:38
  • Why not `return Collectors.reducing((a, b) -> { throw new IllegalArgumentException(); })`? – shmosel Aug 05 '22 at 21:43
79

Use Guava's MoreCollectors.onlyElement() (Source Code).

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());
Neuron
  • 5,141
  • 5
  • 38
  • 59
trevorade
  • 970
  • 6
  • 9
35

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.

Naman
  • 27,789
  • 26
  • 218
  • 353
Brian Goetz
  • 90,105
  • 23
  • 150
  • 161
27

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.

assylias
  • 321,522
  • 82
  • 660
  • 783
  • 1
    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 Mar 27 '14 at 18:36
  • 2
    @Skiwi if there are null elements the filter will throw a NPE first. – assylias Mar 27 '14 at 18:37
  • 3
    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 Nov 04 '16 at 09:38
23

I think this way is more simple:

User resultUser = users.stream()
    .filter(user -> user.getId() > 0)
    .findFirst().get();
pilladooo
  • 713
  • 6
  • 10
  • 14
    It find only first but the case was also to throw Exception when it is more than one – lczapski Sep 12 '19 at 10:39
  • This is bad practice. It leads to nondeterministic behavior if there are 2 or more objects. Whole JDK's `findFirst` is bad idea. – Radek Postołowicz Jun 21 '22 at 14:45
  • @RadekPostołowicz 1) Isn't it only nondeterministic if it's a parallel stream? 2) If there are multiple items that fit the filter, how often do you care which one it is? (Calling it 'first' was probably the bad idea) 3) None of that applies to this post, because of what lczapski said. – Jacob Zimmerman Nov 11 '22 at 21:17
  • It's nondeterministic because it selects first element while there might be many. Without explicit sorting, it's just randomly picking first one. – Radek Postołowicz Nov 14 '22 at 11:53
17

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();
prunge
  • 22,460
  • 3
  • 73
  • 80
17

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
Neuron
  • 5,141
  • 5
  • 38
  • 59
Fabio Bonfante
  • 5,128
  • 1
  • 32
  • 37
11

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

Neuron
  • 5,141
  • 5
  • 38
  • 59
Hans
  • 2,230
  • 23
  • 23
10

Using a Collector:

public static <T> Collector<T, ?, Optional<T>> singleElementCollector() {
    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(singleElementCollector());

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.

Neuron
  • 5,141
  • 5
  • 38
  • 59
4

Using Reduce and Optional

From Fabio Bonfante response:

public <T> T getOneExample(Collection<T> collection) {
    return collection.stream()
        .filter(x -> /* do some filter */)
        .reduce((x,y)-> {throw new IllegalStateException("multiple");})
        .orElseThrow(() -> new NoSuchElementException("none"));
}
2

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.

frhack
  • 4,862
  • 2
  • 28
  • 25
  • Correct answer, initialializing a blocking stream or collection is probably not very cheap (in terms of resources) though. – Kalle Richter Apr 13 '18 at 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.

Neuron
  • 5,141
  • 5
  • 38
  • 59
John McClean
  • 5,225
  • 1
  • 22
  • 30
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.

Arne Burmeister
  • 20,046
  • 8
  • 53
  • 94
  • 1
    Fine solution! And if you do `.collect(Collectors.toMap(user -> "", Function.identity())).get("")`, you have a more generic behaviour. – glglgl May 26 '17 at 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);
}
Xavier Dury
  • 1,530
  • 1
  • 16
  • 23
  • Neat! `onlyOne()` throws `IllegalStateException` for >1 elements, and NoSuchElementException` (in `Optional::get`) for 0 elements. – simon04 Jan 05 '18 at 10:30
  • @simon04 You could overload the methods to take a `Supplier` of `(Runtime)Exception`. – Xavier Dury Jun 26 '18 at 08:29
1
 List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
Integer value  = list.stream().filter((x->x.intValue()==8)).findFirst().orElse(null);

I have used Integer type instead of primitive as it will have null pointer exception. you just have to handle this exception... looks succinct, I think ;)

Aelaf
  • 118
  • 1
  • 11
1

Tried a sample code for my self and here is the solution for that.

User user = Stream.of(new User(2), new User(2), new User(1), new User(2))
            .filter(u -> u.getAge() == 2).findFirst().get();

and the user class

class User {
    private int age;

public User(int age) {
    this.age = age;
}

public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
 }
}
M-sAnNan
  • 704
  • 8
  • 15
0

If you don't use Guava or Kotlin, here's a solution based on @skiwi and @Neuron answers.

users.stream().collect(single(user -> user.getId() == 1));

or

users.stream().collect(optional(user -> user.getId() == 1));

where single and optional are statically imported functions returning corresponding collectors.

I reasoned it would look more succinct if the filtering logic had been moved inside the collector. Also nothing would break in the code if you happened to delete the string with .filter.

The gist for the code https://gist.github.com/overpas/ccc39b75f17a1c65682c071045c1a079

Overpass
  • 433
  • 5
  • 12
0
public List<state> getAllActiveState() {
    List<Master> master = masterRepository.getActiveExamMasters();
    Master activeMaster = new Master();
    try {
        activeMaster = master.stream().filter(status -> status.getStatus() == true).reduce((u, v) -> {
            throw new IllegalStateException();
        }).get();
        return stateRepository.getAllStateActiveId(activeMaster.getId());
    } catch (IllegalStateException e) {
        logger.info(":More than one status found TRUE in Master");
        return null;
    }
}
  1. In this above code, As per the condition if its find more than one true in the list then it will through the exception.
  2. When it through the error will showing custom message because it easy maintain the logs on server side.
  3. From Nth number of element present in list just want only one element have true condition if in list there are more than one elements having true status at that moment it will through an exception.
  4. after getting all the this we using get(); to taking that one element from list and stored it into another object.
  5. If you want you added optional like Optional<activeMaster > = master.stream().filter(status -> status.getStatus() == true).reduce((u, v) -> {throw new IllegalStateException();}).get();
-1
User match = users.stream().filter((user) -> user.getId()== 1).findAny().orElseThrow(()-> new IllegalArgumentException());
David Buck
  • 3,752
  • 35
  • 31
  • 35
Nitin
  • 541
  • 4
  • 5
  • 6
    While this code may solve the question, [including an explanation](https://meta.stackexchange.com/q/114762) of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please edit your answer to add explanations and give an indication of what limitations and assumptions apply. – David Buck Mar 28 '20 at 11:56
-1

Inspired by @skiwi, I solved it the following way:

public static <T> T toSingleton(Stream<T> stream) {
    List<T> list = stream.limit(1).collect(Collectors.toList());
    if (list.isEmpty()) {
        return null;
    } else {
        return list.get(0);
    }
}

And then:

User user = toSingleton(users.stream().filter(...).map(...));
JavAlex
  • 4,974
  • 1
  • 12
  • 14
  • 1
    This solution does not detect the case where there are multiple values in the stream. So it goes unnoticed. – David Nouls Jun 01 '21 at 08:13
  • Actually, I only wanted to get the first element in the stream. – JavAlex Jun 02 '21 at 09:54
  • 1
    The original question wanted the one and only one. The accepted answer throws an exception instead. – David Nouls Jun 03 '21 at 12:18
  • 1
    Yeah... If you want to do exactly the same, you can just do `stream.findFirst().orElse(null)` which is completely equivalent and a lot more readable that what you are doing here. – LordOfThePigs Jul 01 '21 at 13:31
-3

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

pardeep131085
  • 5,468
  • 2
  • 21
  • 9