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. [PDF] PDFOperation--C#PDF文件操作帮助类 (转载)

    点击下载 PDFOperation.rar 这个类是关于PDFOperation的帮助类,主要是实现C#PDF的文件操作,具体实现功能如下1.构造函数2.私有字段3.设置字体4.设置页面大小5.实例化 ...

  2. 如何在OpenWRT环境下做开发

    1.搭建开发环境 首先,在执行make menuconfig后,会出现下图: 其中,图中红框部分是我定制路由器的系统版本,大家可以根据不同的路由器进行不同的选择:绿框部分表示我们需要编译一个SDK开发 ...

  3. maven 创建的符号连接命令

    E:\Joyplus\src\main\webapp\WEB-INF>mklink /d lib ..\..\..\.\..\target\ediHelperSuite-0.5\WEB-INF\ ...

  4. ACM HDU 2044 一只小蜜蜂

    Problem Description 有一只经过训练的蜜蜂只能爬向右侧相邻的蜂房,不能反向爬行.请编程计算蜜蜂从蜂房a爬到蜂房b的可能路线数. 其中,蜂房的结构如下所示. Input 输入数据的第一 ...

  5. 使用jquer获取当前时间,并赋值到input上。

    今天,做一个项目,需要将自动获取的时间能够赋值到input上.我一开始试用javascript写的,写之后使用很多办法都赋值失败,最后使用了jquery写了出来,下面附上代码. <script ...

  6. 配置并学习微信JS-SDK(1)

    <script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script> 微信JS-SDK ...

  7. C#Winform开发平台企业版V4.0功能表

    企业版V4.0 - 功能列表及模板窗体 C/S系统开发框架-企业版 V4.0 (Enterprise Edition) 简介: http://www.csframework.com/cs-framew ...

  8. 【转】深入 char * ,char ** ,char a[ ] ,char *a[] 内核

    原文出处:http://blog.csdn.net/daiyutage/article/details/8604720    C语言中由于指针的灵活性,导致指针能代替数组使用,或者混合使用,这些导致了 ...

  9. linq 多个left join 和 sql union all -> linq union 方法

     (   from s in Base_SysMenus   join r in Base_RoleRights on s.Menu_Id equals r.Menu_Id into temp   f ...

  10. 将小度WiFi改造为无线网卡(小度WiFi能够接收WiFi信号)

    安装官方的小度WiFi的驱动器,只能让它当做无线信号的发射装置,但是我想通过小度WiFi让我的台式电脑能都接收无线信号,于是经过一番折腾终于成功了.我的是win7. 小度WiFi无法接受无线信号,不能 ...