5
  • Given an object, MyObj which for our purposes holds String message among other fields.
  • Given a HashMap, Map <MultiKey<String>,MyObj> map.

I want to loop through the HashMap finding any element where the MyObj's message is searchValue. I'm essentially trying to use stream().anyMatch() on a Map, I simply want to know if searchValue exists even once anywhere - i.e. short circuiting is preferable.

Foreach loop:

map.forEach((k,v) -> {
    if (v.message.equalsIgnoreCase(searchValue)) {
        return true;
    }
}

The issue with this is that it doesn't terminate early, neither break or return can be used in this lambda to terminate the loop early.

I see the stream anyMatch() function:

 map.entrySet().stream().anymatch(....

but I can't figure out the proper syntax - if that will work in this case at all (can it still be used if I'm not comparing the map's elements, rather I'm comparing each element's fields).

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
CeePlusPlus
  • 803
  • 1
  • 7
  • 26
  • 3
    `map.values().stream().map(v -> v.message).anyMatch(searchValue::equalsIgnoreCase)` – Louis Wasserman Dec 05 '17 at 16:47
  • 3
    FWIW, the notion of "first" doesn't really mean much for a standard `HashMap`, as its entries aren't sequenced/ordered. – Oliver Charlesworth Dec 05 '17 at 16:51
  • @LouisWasserman that seemed to me worth to be written as answer (one-liner as it is): `final String searchValue = ...; boolean has = map.values().stream().map(MyObj::message).anyMatch(searchValue::equalsIgnoreCase);` – Joop Eggen Dec 05 '17 at 16:55
  • @JoopEggen `.map(MyObj::message)` doesn't seem to work. – CeePlusPlus Dec 05 '17 at 17:10
  • forEach calls a method defined by your lambda for each element. If you return from this method handling one element, it doesn't terminate the entire foreach block, but only the "current" block of the one element. You can't terminate foreach any way, only by killing the entire thread (or system exit). Or maybe anybody knows any way? – fairtrax Dec 05 '17 at 17:16
  • Your question is too vague. Are you (A) Trying to find and return the "first" element that matches (understanding that "first" has no meaning); or (B) Determine only IF such an element exists, terminating the search after one such element is found? I suspect it is (B). – Jim Garrison Dec 05 '17 at 17:19
  • @JimGarrison B - "I just want to know if searchValue exists even once." -- clarified more as well. – CeePlusPlus Dec 05 '17 at 17:20
  • @fairtrax Based on [previous StackOverflow](https://stackoverflow.com/a/32566745/4867303) questions you terminate in middle by throwing an exception, which is very bad practice. – CeePlusPlus Dec 05 '17 at 17:22
  • @OliverCharlesworth The first was only used in the title, not in the body. I have edited it. I meant to use first to mean in the sense of short circuiting, not literally the first one. – CeePlusPlus Dec 05 '17 at 17:41
  • @CeePlusPlus: indeed, thanks, how could I forget an exception :) – fairtrax Dec 05 '17 at 18:04
  • @CeePlusPlus Louis Wasserman had an almost working version IMHO, I varied a bit, but `v -> v.message` should have worked. – Joop Eggen Dec 05 '17 at 18:56

2 Answers2

4

Working answers from previous posters (is there a way to tell which is more efficient?)

 //Note: anyMatch can be switched with noneMatch if need be

@JimGarrison: (seems the shortest/readable):

if (map.values().stream().anyMatch(v -> v.message.equalsIgnoreCase(searchValue))
    {...}

@Louis Wasserman:

if (map.values().stream().map(v -> v.message).anyMatch(searchValue::equalsIgnoreCase)) 
   {...}

@P3trur0:

if (null != map.values().stream().filter(v -> v.message.equalsIgnoreCase(searchValue)).findAny().orElse(null))
    {...}
CeePlusPlus
  • 803
  • 1
  • 7
  • 26
  • I'd just do `map.values().stream().anyMatch(v -> v.message.equalsIgnoreCase(searchValue));` This will short-circuit the stream when the first match is encountered. – Jim Garrison Dec 05 '17 at 17:30
  • @JimGarrison while yours certainly seems the simplest and more readable, how can I tell which ones short-circuit and which don't? – CeePlusPlus Dec 05 '17 at 17:34
  • By understanding how streams work (intermediate vs terminal operations) and carefully reading the Javadoc. – Jim Garrison Dec 05 '17 at 18:00
  • 2
    `stream.filter(condition).find{Any|First}().orElse(null) != null` can never be better than `stream.anyMatch(condition)` – fps Jan 16 '18 at 20:12
3

One way to solve that is by using findFirst method.

In your case, for example, you can write something like this:

 map.values().stream()
            .filter(e -> e.message.equalsIgnoreCase(searchValue))
            .findFirst()
            .get();
P3trur0
  • 3,155
  • 1
  • 13
  • 27
  • are you sure it will work? Hashmap is not sorted and order of returning elements is random. – Vadim Dec 05 '17 at 16:54
  • @Vadim CeePlusPlus states that "I want to loop through the HashMap finding a single element where the MyObj's message is", it seems to me he is not mentioning any order. – P3trur0 Dec 05 '17 at 16:57
  • He can just use .sorted() before or after .filter() – whatamidoingwithmylife Dec 05 '17 at 16:59
  • @P3trur0 First, I'm assuming I should be use map.values(), correct? Secondly, how do I actually check the return value? Is there a way to simply get a boolean or am I supposed to attempt to catch `NoSuchElementException`? Also findFirst() vs findAny()? – CeePlusPlus Dec 05 '17 at 17:00
  • maybe - but that "first" element will be always different key/object pair. If it is OK to CeePlusPlus then fine... – Vadim Dec 05 '17 at 17:01
  • 2
    @CeePlusPlus you can use orElse() to return specific value if there is no queried value in the stream. – whatamidoingwithmylife Dec 05 '17 at 17:02
  • @Vadim Order is irrelevant - it's meant to be used to see if data was available for a particular message. – CeePlusPlus Dec 05 '17 at 17:03
  • @CeePlusPlus findFirst() returns an Optional, than as pointed out by Fair Play, you can use any Optional method to deal with possible not returning values (e.g.: .orElse(...)). In my example I've used the get() method. – P3trur0 Dec 05 '17 at 17:04
  • Final way I'm using this one works if you want to update answer: `if (null != map.values().stream().filter(e -> e.message.equalsIgnoreCase(searchValue)).findAny().orElse(null);` – CeePlusPlus Dec 05 '17 at 17:05