I am trying to use Java 8 Streams to find elements in a LinkedList. I want to guarantee, however, that there is 1 and only 1 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?

asked Mar 27 at 17:25
ryvantage

2,58921335

   
count() is a terminal operation so you can't do that. The stream can't be used after. – ZouZou Mar 27 at 17:42 
   
Ok, thanks @ZouZou. I wasn't entirely certain what that method did. Why is there noStream::size ? –  ryvantage Mar 27 at 17:44
2  
@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 at 17:45
   
Wow. That one comment helped me understand Streams so much more than I did before... –  ryvantage Mar 27 at 17:50

Technically there's an ugly 'workaround' that involves peek() and an AtomicInteger, but really you shouldn't be using that.

What I do in these cases 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);

I am not aware of a way to do this in the API, meanwhile I will work on another example involving a custom element.

Update, You should create your own Collector for this:

public static <T> Collector<T, List<T>, T> singletonCollector() {
return Collector.of(
ArrayList::new,
List::add,
(left, right) -> { left.addAll(right); return left; },
list -> {
if (list.size() != 1) {
throw new IllegalStateException();
}
return list.get(0);
}
);
}

What it does is:

  • It mimicks the Collectors.toList() collector.
  • It applies an extra finisher at the end, that throws an exception, or if no exception, returns the first element of the list.

Used as:

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

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

New update, I revised my old answer once more for singletonCollector(), it can actually be obtained like this:

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

answered Mar 27 at 17:33
skiwi

9,37332767

   
@ryvantage I updated my answer with how I would do it with writing the own custom Collector, which I believe is the correct method. –  skiwi Mar 27 at 18:03
   
what do you think of my reduction? –  assylias Mar 27 at 18:32
1  
@assylias I commented on your answer, I think this one i smore concise and more straight forward though and less error prone. –  skiwi Mar 27 at 18:38
   
The difference main between the two collectors is that the first will stop as soon as it finds a second matching item whereas the second will iterate through the whole list. – assylias Mar 27 at 22:57

Wow, such complexity! :-) 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.

answered Mar 28 at 15:39
Stuart Marks

12.5k11850

   
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 at 18:31
2  
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 at 3:24 

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.

answered Mar 27 at 17:51
Louis Wasserman

71k894155

   
@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 at 20:37
   
Fair enough. I was primarily aiming for speed, not conciseness. –  Louis WassermanMar 27 at 20:40
   
Yeah? Why is yours faster? –  ryvantage Mar 27 at 20:45
   
Mostly because allocating an all-up List is more expensive than a single mutable reference. –  Louis Wasserman Mar 27 at 20:52 
   
I was unable to get yours to compile –  ryvantage Mar 28 at 18: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.

answered May 24 at 19:26
Brian Goetz

8,86341733

 

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.

answered Mar 27 at 17:37
assylias

110k10153289

   
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. –  skiwiMar 27 at 18:36
   
@Skiwi if there are null elements the filter will throw a NPE first. –  assylias Mar 27 at 18:37

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.

answered Mar 28 at 7:03

   
It was said that count() is not good to use because it is a terminal operation. – ryvantage Mar 28 at 18:29

lambda -- Filter Java Stream to 1 and only 1 element的更多相关文章

  1. Java 8 Lambda 表达式及 Stream 在集合中的用法

    简介 虽然 Java 8 已经发布有一段时间了,但是关于 Java 8 中的 Lambda 表达式最近才开始系统的学习,刚开始就被 Stream 的各种骚操作深深的吸引住了,简直漂亮的不像 Java. ...

  2. Java Stream 使用详解

    Stream是 Java 8新增加的类,用来补充集合类. Stream代表数据流,流中的数据元素的数量可能是有限的,也可能是无限的. Stream和其它集合类的区别在于:其它集合类主要关注与有限数量的 ...

  3. 初探Lambda表达式/Java多核编程【1】从集合到流

    从集合到流 接上一小节初探Lambda表达式/Java多核编程[0]从外部迭代到内部迭代,本小节将着手使用"流"这一概念进行"迭代"操作. 首先何为" ...

  4. 初探Lambda表达式/Java多核编程【2】并行与组合行为

    今天又翻了一下书的目录,第一章在这之后就结束了.也就是说,这本书所涉及到的新的知识已经全部点到了. 书的其余部分就是对这几个概念做一些基础知识的补充以及更深层次的实践. 最后两个小节的内容较少,所以合 ...

  5. 十分钟学会Java8:lambda表达式和Stream API

    Java8 的新特性:Lambda表达式.强大的 Stream API.全新时间日期 API.ConcurrentHashMap.MetaSpace.总得来说,Java8 的新特性使 Java 的运行 ...

  6. JDK1.8中的Lambda表达式和Stream

    1.lambda表达式 Java8最值得学习的特性就是Lambda表达式和Stream API,如果有python或者javascript的语言基础,对理解Lambda表达式有很大帮助,因为Java正 ...

  7. Java Stream函数式编程案例图文详解

    导读 作者计划把Java Stream写成一个系列的文章,本文只是其中一节.更多内容期待您关注我的号! 一.什么是Java Stream? Java Stream函数式编程接口最初是在Java 8中引 ...

  8. Java Stream函数式编程图文详解(二):管道数据处理

    一.Java Stream管道数据处理操作 在本号之前发布的文章<Java Stream函数式编程?用过都说好,案例图文详解送给你>中,笔者对Java Stream的介绍以及简单的使用方法 ...

  9. Java Stream函数式编程第三篇:管道流结果处理

    一.Java Stream管道数据处理操作 在本号之前写过的文章中,曾经给大家介绍过 Java Stream管道流是用于简化集合类元素处理的java API.在使用的过程中分为三个阶段.在开始本文之前 ...

随机推荐

  1. PHP如何解决网站大流量与高并发的…

    首先,确认服务器硬件是否足够支持当前的流量. 普通的P4服务器一般最多能支持每天10万独立IP,如果访问量比这个还要大, 那么必须首先配置一台更高性能的专用服务器才能解决问题 ,否则怎么优化都不可能彻 ...

  2. 自己做的demo---c语言的基本语法,过阵子可以重新写一些算法跟数据结构了

    //输入a,b,输出a+b /*#include<stdio.h> int main() { int a,b; scanf("%d%d",&a,&b); ...

  3. xml--小结②XML的基本语法

    二.XML的基本语法1.文档声明:作用:用于标识该文档是一个XML文档.注意事项:声明必须出现在文档的第一行(之前连空行都不能有,也不能有任何的注释) 最简单的XML声明:<?xml versi ...

  4. U3D 精灵的点击监听

    U3D游戏中,可能会用到点击对象,完成某项操作, 方法一:可以通过接收Input对象的输入,进行利用 方法二:给对象绑定一个collier 组件,然后就能后使用内置方法 这里有点不同,方法一,是不管哪 ...

  5. c#中string.trimstart() 和string.trimend() 的用法

    trim(),trimstart(),trimend()这样写是去掉空格,trimstart(a)是去掉字符串开始包含char[] a的字符,trimend同trimstart. 例:char[] a ...

  6. SQL多表查询中的分页,字段组合综合实例解析

    原文:http://www.jb51.net/article/28753.htm http://xuzhihong1987.blog.163.com/blog/static/2673158720098 ...

  7. Windows phone 之自定义控件(补充)

    <UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x= ...

  8. [Neural Networks] (Convolutional Neural Networks)CNN-卷积神经网络学习

    参考:http://blog.csdn.net/zouxy09/article/details/8781543 ( 但其中有部分错误) http://ufldl.stanford.edu/wiki/i ...

  9. Linux运维需要掌握的技能 (转)

    本人是linux运维工程师,对这方面有点心得,现在我说说要掌握哪方面的工具吧说到工具,在行外可以说是技能,在行内我们一般称为工具,就是运维必须要掌握的工具.我就大概列出这几方面,这样入门就基本没问题了 ...

  10. sql数据库优化技巧汇总

    (转)SQL 优化原则 一.问题的提出 在应用系统开发初期,由于开发数据库数据比较少,对于查询SQL语句,复杂视图的的编写等体会不出SQL语句各种写法的性能优劣,但是如果将应用系统提交实际应用后,随着 ...