2

Starting with this code:

public Thing thereCanBeOnlyOne(Stream<Thing> stream) {
  List<Thing> things = stream.collect(Collectors.toList());  
  if(things.size() != 1) {
    throw new IllegalArgumentException();
  }
  return things.get(0);
}

Is there a more succint way to express the method body?

What I tried so far:

I read the documentation for Collector and reduce, but didn't find anything.

Aniket Sahrawat
  • 12,410
  • 3
  • 41
  • 67
Alex R
  • 11,364
  • 15
  • 100
  • 180
  • 3
    `.reduce(IDENTITY, (a, b) -> { if(a.equals(IDENTITY)) return b; throw new IllegalArgumentException(); });` but it depends on your taste. – Aniket Sahrawat Apr 25 '21 at 18:08
  • 2
    Depends on your threshold of "idiomatic": Guava's [`Iterables.getOnlyElement`](https://guava.dev/releases/21.0/api/docs/com/google/common/collect/Iterables.html#getOnlyElement-java.lang.Iterable-) would be my go-to. – Andy Turner Apr 25 '21 at 18:11
  • 1
    Oh, Guava also has the [`MoreCollectors.onlyElement`](https://guava.dev/releases/22.0/api/docs/com/google/common/collect/MoreCollectors.html#onlyElement--) collector. – Andy Turner Apr 25 '21 at 18:16
  • @AndyTurner Surely an external library for this purpose is overkill? – Ole V.V. Apr 25 '21 at 18:20
  • @OleV.V. this isn't exactly an external library at work ;) sure, overkill if this is all you need it for. But it's nice and easy if you are already using it. Other libraries exist, ofc, so you may be able to find an equivalent in one you are already using. – Andy Turner Apr 25 '21 at 18:21
  • @AniketSahrawat with a slight correction that it might miss out on empty stream test case, considering that the code in the question actually verifies 'exactly one'. – Naman Apr 25 '21 at 18:26

2 Answers2

3

If using Guava is palatable to you, use MoreCollectors.onlyElement:

public Thing thereCanBeOnlyOne(Stream<Thing> stream) {
  return stream.collect(MoreCollectors.onlyElement());
}

Equivalents may well exist in other common libraries; I am just familiar with Guava.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • 1
    This is good. I also found `.collect(Collectors.reducing((a, b) -> null));` as an interesting alternative (returns null instead of throwing an exception). – Alex R Apr 25 '21 at 18:58
1

How about

public static Thing thereCanBeOnlyOne(Stream<Thing> stream) {
    Object[] array = null;
    if((array = stream.limit(2).toArray()).length != 1) {
            throw new IllegalArgumentException();
    }
    return (Thing)array[0];
}

To me it seems like less overhead.

WJS
  • 36,363
  • 4
  • 24
  • 39
  • 2
    `stream.limit(2).toArray()` would be better in the case of "lots" in the stream. Also, the null assignment is redundant, just assign the array from the stream when you declare it. – Andy Turner Apr 25 '21 at 18:13
  • not sure what overheads are mentioned here, but `stream.limit(2).toArray(Thing[]::new)` might provide you of the accurate type – Naman Apr 25 '21 at 18:18
  • I figured the overhead of calling a Collector and creating a list. And If I don't declare the array first, it won't be found to return the first element due to scoping issues. And yes, I could have just processed the stream and then checked the length but I didn't. I like the idea of the `limit` Wished I had though of it :) – WJS Apr 25 '21 at 18:20
  • One could also use `public static T thereCanBeOnlyOne(Stream stream)` along with a `@SuppressedWarnings("unchecked")` to make it more versatile. Casting an `Object to T` in this case shouldn't cause problems. – WJS Apr 25 '21 at 18:36
  • This isn't any more succinct or clear. The question isn't really about overhead, but since you mention it, overhead is the same when the stream is expected to contain only 1 element at run time. – Alex R Apr 25 '21 at 18:53
  • @AlexR Sorry it didn't work for you. Just trying to help. – WJS Apr 25 '21 at 19:33