Reputation: 13479
I am trying to use Java 8 Stream
s 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 User
s 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?
Upvotes: 340
Views: 392169
Reputation: 16359
Starting in JDK 24 (available as a preview in JDK 22 and 23), Stream Gatherers can be used to limit a stream to one and only one element as follows.
A state object tracks the element, if any:
class State<T> {
private T element;
public boolean accept(T element) {
if (this.element != null) {
throw new IllegalArgumentException("too many elements");
}
this.element = element;
return true;
}
public T element() {
if (element == null) {
throw new NoSuchElementException("no elements");
}
return element;
}
}
And a gatherer uses the state to get the only object, if there is one, and pass it downstream, or throw an exception if either a second element is seen or if the end of the stream is reached without any element being seen.
<T> Gatherer<T, State<T>, T> onlyOne() {
return Gatherer.ofSequential(State::new,
Gatherer.Integrator.ofGreedy((state, element, _) -> {
return state.accept(element);
}), (state, downstream) -> {
downstream.push(state.element());
});
}
When used like:
users.stream().filter(user -> user.getId() == 1).gather(onlyOne()).findFirst().get()
It will return the matching user, if there is one, or throw IllegalArgumentException
or NoSuchElementException
if there are no elements. I chose these to match Guava's MoreCollectors::onlyElement but you can change them to suit your needs.
It is efficient in space and time because it throws as soon as it sees a single duplicate element rather than waiting for all the elements, and it only stores a single reference to the first element seen, unlike solutions that accumulate an entire list.
It does not produce the matching element to the downstream as soon as it sees it but rather produces it in the finisher, so if there is going to be a duplicate element found, by the time it sees it and throws, it will not have already produced the first, matching element. Thus in the case where there are duplicates, you don't get the element and later, the exception. You just get the exception.
Upvotes: 0
Reputation: 1020
Use Guava's MoreCollectors.onlyElement()
(source code).
A collector that takes a stream containing exactly one element and returns that element. The returned collector throws an
IllegalArgumentException
if the stream consists of two or more elements, and aNoSuchElementException
if the stream is empty.
This does what you want, throwing an exception if the stream does not contain exactly one element.
import static com.google.common.collect.MoreCollectors.onlyElement;
User match =
users.stream().filter((user) -> user.getId() < 0).collect(onlyElement());
Upvotes: 84
Reputation: 132360
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);
Upvotes: 96
Reputation: 69259
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
List
with the Collectors.toList()
collector.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.
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);
Upvotes: 303
Reputation: 1
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;
}
}
Optional<activeMaster > = master.stream().filter(status -> status.getStatus() == true).reduce((u, v) -> {throw new IllegalStateException();}).get();
Upvotes: 0
Reputation: 804
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;
}
}
Upvotes: 1
Reputation: 191
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"));
}
Upvotes: 7
Reputation: 5823
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()
);
}
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.
Upvotes: 10
Reputation: 118
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 ;)
Upvotes: 1
Reputation: 95326
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.
Upvotes: 35
Reputation: 473
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
Upvotes: 0
Reputation: 5944
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(...));
Upvotes: -1
Reputation: 654
User match = users.stream().filter((user) -> user.getId()== 1).findAny().orElseThrow(()-> new IllegalArgumentException());
Upvotes: -1
Reputation: 751
I think this way is more simple:
User resultUser = users.stream()
.filter(user -> user.getId() > 0)
.findFirst().get();
Upvotes: 22
Reputation: 5188
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:
Optional.empty()
if not presentUpvotes: 22
Reputation: 5518
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
Upvotes: -3
Reputation: 5313
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.
Upvotes: 1
Reputation: 198023
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.
Upvotes: 97
Reputation: 1571
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);
}
Upvotes: 1
Reputation: 328598
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") });
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:
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.
Upvotes: 27
Reputation: 20594
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
.
Upvotes: 2
Reputation: 22684
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, orIllegalStateException
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));
Upvotes: 184
Reputation: 5112
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.
Upvotes: 2
Reputation: 23238
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();
Upvotes: 18