如果只有一个值,则返回一个值的 Java 8 收集器
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/26810375/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java 8 Collector that returns a value if there's only a single value
提问by Ned Twigg
I'm a little green on this functional programming and streams stuff, but what little I do know has been very useful!
我对这个函数式编程和流的东西有点陌生,但我所知道的很少是非常有用的!
I've had this situation come up several times:
我已经多次出现这种情况:
List<SomeProperty> distinctProperties = someList.stream()
.map(obj -> obj.getSomeProperty())
.distinct()
.collect(Collectors.toList());
if (distinctProperties.size() == 1) {
SomeProperty commonProperty = distinctProperties.get(0);
// take some action knowing that all share this common property
}
What I really want is:
我真正想要的是:
Optional<SomeProperty> universalCommonProperty = someList.stream()
.map(obj -> obj.getSomeProperty())
.distinct()
.collect(Collectors.singleOrEmpty());
I think the singleOrEmpty
thing can be useful in other situations besides just in combination with distinct
. When I was an uber n00b I spent a lot of time reinventing the Java Collections Framework because I didn't know it was there, so I'm trying not to repeat my mistakes. Does Java come with a good way to do this singleOrEmpty
thing? Am I formulating it wrong?
我认为singleOrEmpty
除了与distinct
. 当我还是 uber n00b 时,我花了很多时间重新发明 Java Collections Framework,因为我不知道它在那里,所以我尽量不重复我的错误。Java 是否有一个很好的方法来做这singleOrEmpty
件事?我的表述有误吗?
Thanks!
谢谢!
EDIT: Here's some example data for the distinct
case. If you ignore the map
step:
编辑:这是distinct
案例的一些示例数据。如果您忽略该map
步骤:
Optional<SomeProperty> universalCommonProperty = someList.stream()
.map(obj -> obj.getSomeProperty())
.distinct()
.collect(Collectors.singleOrEmpty());
[] -> Optional.empty()
[1] -> Optional.of(1)
[1, 1] -> Optional.of(1)
[2, 2] -> Optional.of(2)
[1, 2] -> Optional.empty()
I find I need this when I screw up my types, or have legacy code. It's really nice to be able to quickly say "All the elements of this collection share this property, so now I can take some action using this shared property." Another example is when a user multi-selects some diverse elements, and you're trying to see what stuff you can do (if anything) that's valid for all of them.
当我搞砸我的类型或拥有遗留代码时,我发现我需要这个。能够快速说出“此集合的所有元素共享此属性,因此现在我可以使用此共享属性执行一些操作”真是太好了。另一个例子是当用户多选一些不同的元素时,你试图看看你可以做什么(如果有的话)对所有元素都有效。
EDIT2: Sorry if my example is a misleading. The key is singleOrEmpty. I commonly find that I put a distinct
in front, but it could just as easily be a filter
of some other kind.
EDIT2:对不起,如果我的例子有误导性。关键是singleOrEmpty。我通常发现我把 adistinct
放在前面,但它也可以很容易地成为filter
其他类型的 a 。
Optional<SomeProperty> loneSpecialItem = someList.stream()
.filter(obj -> obj.isSpecial())
.collect(Collectors.singleOrEmpty());
[special] -> Optional.of(special)
[special, special] -> Optional.empty()
[not] -> Optional.empty()
[not, special] -> Optional.of(special)
[not, special, not] -> Optional.of(special)
EDIT3: I think I screwed up by motivating the singleOrEmptyinstead of just asking for it on its own.
EDIT3:我认为我通过激励singleOrEmpty而不是仅仅要求它自己搞砸了。
Optional<Int> value = someList.stream().collect(Collectors.singleOrEmpty())
[] -> Optional.empty()
[1] -> Optional.of(1)
[1, 1] -> Optional.empty()
采纳答案by Thomas Jungblut
"Hacky" solution that only evaluates the first two elements:
仅评估前两个元素的“Hacky”解决方案:
.limit(2)
.map(Optional::ofNullable)
.reduce(Optional.empty(),
(a, b) -> a.isPresent() ^ b.isPresent() ? b : Optional.empty());
Some basic explanation:
一些基本解释:
Single element[1] -> map to [Optional(1)] -> reduce does
单个元素[1] -> 映射到 [Optional(1)] -> reduce 确实如此
"Empty XOR Present" yields Optional(1)
= Optional(1)
= 可选(1)
Two elements[1, 2] -> map to [Optional(1), Optional(2)] -> reduce does:
两个元素[1, 2] -> 映射到 [Optional(1), Optional(2)] -> reduce 是:
"Empty XOR Present" yields Optional(1)
"Optional(1) XOR Optional(2)" yields Optional.Empty
= Optional.Empty
= Optional.Empty
Here is the complete testcase:
这是完整的测试用例:
public static <T> Optional<T> singleOrEmpty(Stream<T> stream) {
return stream.limit(2)
.map(Optional::ofNullable)
.reduce(Optional.empty(),
(a, b) -> a.isPresent() ^ b.isPresent() ? b : Optional.empty());
}
@Test
public void test() {
testCase(Optional.empty());
testCase(Optional.of(1), 1);
testCase(Optional.empty(), 1, 1);
testCase(Optional.empty(), 1, 1, 1);
}
private void testCase(Optional<Integer> expected, Integer... values) {
Assert.assertEquals(expected, singleOrEmpty(Arrays.stream(values)));
}
Kudos to Ned (the OP) who has contributed the XOR idea and the above testcase!
感谢 Ned(OP),他贡献了 XOR 的想法和上面的测试用例!
回答by S.D.
If you don't mind using Guava, you can wrap your code with Iterables.getOnlyElement
, so it would look something like that:
如果你不介意使用Guava,你可以用 包裹你的代码Iterables.getOnlyElement
,所以它看起来像这样:
SomeProperty distinctProperty = Iterables.getOnlyElement(
someList.stream()
.map(obj -> obj.getSomeProperty())
.distinct()
.collect(Collectors.toList()));
IllegalArgumentException
will be raised if there is more than one value or no value, there is also a versionwith default value.
IllegalArgumentException
如果有多个值或没有值,则会引发,也有一个具有默认值的版本。
回答by dkatzel
You can easily write your own Collector
您可以轻松编写自己的 Collector
public class AllOrNothing<T> implements Collector<T, Set<T>, Optional<T>>{
@Override
public Supplier<Set<T>> supplier() {
return () -> new HashSet<>();
}
@Override
public BinaryOperator<Set<T>> combiner() {
return (set1, set2)-> {
set1.addAll(set2);
return set1;
};
}
@Override
public Function<Set<T>, Optional<T>> finisher() {
return (set) -> {
if(set.size() ==1){
return Optional.of(set.iterator().next());
}
return Optional.empty();
};
}
@Override
public Set<java.util.stream.Collector.Characteristics> characteristics() {
return Collections.emptySet();
}
@Override
public BiConsumer<Set<T>, T> accumulator() {
return Set::add;
}
}
Which you can use like this:
你可以这样使用:
Optional<T> result = myStream.collect( new AllOrNothing<>());
Here's your example test data
这是您的示例测试数据
public static void main(String[] args) {
System.out.println(run());
System.out.println(run(1));
System.out.println(run(1,1));
System.out.println(run(2,2));
System.out.println(run(1,2));
}
private static Optional<Integer> run(Integer...ints){
List<Integer> asList = Arrays.asList(ints);
System.out.println(asList);
return asList
.stream()
.collect(new AllOrNothing<>());
}
which when run will print out
运行时会打印出来
[]
Optional.empty
[1]
Optional[1]
[1, 1]
Optional[1]
[2, 2]
Optional[2]
回答by Misha
This will incur an overhead of creating a set but it's simple and will work correctly even if you forget to distinct() the stream first.
这将导致创建集合的开销,但它很简单,即使您忘记首先使用 distinct() 流也能正常工作。
static<T> Collector<T,?,Optional<T>> singleOrEmpty() {
return Collectors.collectingAndThen(
Collectors.toSet(),
set -> set.size() == 1
? set.stream().findAny()
: Optional.empty()
);
}
回答by Ned Twigg
It seems RxJava has similar functionality in its single()
operator.
似乎 RxJava在其single()
operator 中具有类似的功能。
single(?)
andsingleOrDefault(?)
if the
Observable
completes after emitting a single item, return that item, otherwise throw an exception (or return a default item)
single(?)
和singleOrDefault(?)
如果
Observable
在发出单个项目后完成,则返回该项目,否则抛出异常(或返回默认项目)
I'd rather just have an Optional
, and I'd rather it be a Collector
.
我宁愿只有一个Optional
,我宁愿它是一个Collector
。
回答by weston
Another collector approach:
另一种收集器方法:
Collectors:
收藏家:
public final class SingleCollector<T> extends SingleCollectorBase<T> {
@Override
public Function<Single<T>, T> finisher() {
return a -> a.getItem();
}
}
public final class SingleOrNullCollector<T> extends SingleCollectorBase<T> {
@Override
public Function<Single<T>, T> finisher() {
return a -> a.getItemOrNull();
}
}
SingleCollectorBase:
SingleCollectorBase:
public abstract class SingleCollectorBase<T> implements Collector<T, Single<T>, T> {
@Override
public Supplier<Single<T>> supplier() {
return () -> new Single<>();
}
@Override
public BiConsumer<Single<T>, T> accumulator() {
return (list, item) -> list.set(item);
}
@Override
public BinaryOperator<Single<T>> combiner() {
return (s1, s2) -> {
s1.set(s2);
return s1;
};
}
@Override
public Set<Characteristics> characteristics() {
return EnumSet.of(Characteristics.UNORDERED);
}
}
Single:
单身的:
public final class Single<T> {
private T item;
private boolean set;
public void set(T item) {
if (set) throw new SingleException("More than one item in collection");
this.item = item;
set = true;
}
public T getItem() {
if (!set) throw new SingleException("No item in collection");
return item;
}
public void set(Single<T> other) {
if (!other.set) return;
set(other.item);
}
public T getItemOrNull() {
return set ? item : null;
}
}
public class SingleException extends RuntimeException {
public SingleException(String message) {
super(message);
}
}
Tests and example usages, albeit lacking parallel tests.
测试和示例用法,尽管缺乏并行测试。
public final class SingleTests {
@Test
public void collect_single() {
ArrayList<String> list = new ArrayList<>();
list.add("ABC");
String collect = list.stream().collect(new SingleCollector<>());
assertEquals("ABC", collect);
}
@Test(expected = SingleException.class)
public void collect_multiple_entries() {
ArrayList<String> list = new ArrayList<>();
list.add("ABC");
list.add("ABCD");
list.stream().collect(new SingleCollector<>());
}
@Test(expected = SingleException.class)
public void collect_no_entries() {
ArrayList<String> list = new ArrayList<>();
list.stream().collect(new SingleCollector<>());
}
@Test
public void collect_single_or_null() {
ArrayList<String> list = new ArrayList<>();
list.add("ABC");
String collect = list.stream().collect(new SingleOrNullCollector<>());
assertEquals("ABC", collect);
}
@Test(expected = SingleException.class)
public void collect_multiple_entries_or_null() {
ArrayList<String> list = new ArrayList<>();
list.add("ABC");
list.add("ABCD");
list.stream().collect(new SingleOrNullCollector<>());
}
@Test
public void collect_no_entries_or_null() {
ArrayList<String> list = new ArrayList<>();
assertNull(list.stream().collect(new SingleOrNullCollector<>()));
}
}
回答by Necreaux
A more concise way to build a Collector for this is as follows:
为此构建收集器的更简洁方法如下:
Collectors.reducing((a, b) -> null);
The reducing collector will store the first value, and then on successive passes, pass the current running value and the new value into the lambda expression. At this point, null can always be returned since this will not be called with the first value, which will simply be stored.
减少收集器将存储第一个值,然后在连续传递中,将当前运行值和新值传递到 lambda 表达式中。此时,始终可以返回 null,因为不会使用第一个值调用 this,而第一个值将被简单存储。
Plugging this into the code:
将其插入代码中:
Optional<SomeProperty> universalCommonProperty = someList.stream()
.map(obj -> obj.getSomeProperty())
.distinct()
.collect(Collectors.reducing((a, b) -> null));
回答by Hans
Guava has a collector for this called MoreCollectors.toOptional()
Guava 有一个名为 MoreCollectors.toOptional() 的收集器