Filter Java Stream to 1 and only 1 element

JavaLambdaJava 8Java Stream

Java Problem Overview


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?

Java Solutions


Solution 1 - Java

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);

Solution 2 - Java

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));

Solution 3 - Java

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);
}

Solution 4 - Java

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.

Solution 5 - Java

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());

Solution 6 - Java

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.

Solution 7 - Java

###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.

Solution 8 - Java

I think this way is more simple:

User resultUser = users.stream()
    .filter(user -> user.getId() > 0)
    .findFirst().get();

Solution 9 - Java

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();

Solution 10 - Java

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

Solution 11 - Java

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

Solution 12 - Java

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.

Solution 13 - Java

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"));
}

Solution 14 - Java

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.

Solution 15 - Java

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.

Solution 16 - Java

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.

Solution 17 - Java

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);
}

Solution 18 - Java

 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 ;)

Solution 19 - Java

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

Solution 20 - Java

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;
 }
}

Solution 21 - Java

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();

Solution 22 - Java

User match = users.stream().filter((user) -> user.getId()== 1).findAny().orElseThrow(()-> new IllegalArgumentException());

Solution 23 - Java

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(...));

Solution 24 - Java

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionryvantageView Question on Stackoverflow
Solution 1 - JavaskiwiView Answer on Stackoverflow
Solution 2 - JavagltsView Answer on Stackoverflow
Solution 3 - JavaStuart MarksView Answer on Stackoverflow
Solution 4 - JavaLouis WassermanView Answer on Stackoverflow
Solution 5 - JavatrevoradeView Answer on Stackoverflow
Solution 6 - JavaBrian GoetzView Answer on Stackoverflow
Solution 7 - JavaassyliasView Answer on Stackoverflow
Solution 8 - JavapilladoooView Answer on Stackoverflow
Solution 9 - JavaprungeView Answer on Stackoverflow
Solution 10 - JavaFabio BonfanteView Answer on Stackoverflow
Solution 11 - JavaHansView Answer on Stackoverflow
Solution 12 - JavaNeuron - Freedom for UkraineView Answer on Stackoverflow
Solution 13 - JavaNicolas MafraView Answer on Stackoverflow
Solution 14 - JavafrhackView Answer on Stackoverflow
Solution 15 - JavaJohn McCleanView Answer on Stackoverflow
Solution 16 - JavaArne BurmeisterView Answer on Stackoverflow
Solution 17 - JavaXavier DuryView Answer on Stackoverflow
Solution 18 - JavaAelafView Answer on Stackoverflow
Solution 19 - JavaOverpassView Answer on Stackoverflow
Solution 20 - JavaM-sAnNanView Answer on Stackoverflow
Solution 21 - JavaKalpesh PatilView Answer on Stackoverflow
Solution 22 - JavaNitinView Answer on Stackoverflow
Solution 23 - JavaJavAlexView Answer on Stackoverflow
Solution 24 - Javapardeep131085View Answer on Stackoverflow