-1

I'm trying to use Java 8 stream to check if all the distinct characters in a string are present in a map and to return their keys as a List. I am able to do it using Stream like below:

...
map.put("Apple",'a');
map.put("Ball",'b');
map.put("Cat",'c');
map.put("Doll",'d');

String format = "aabbbc";

List<String> list = format.chars().mapToObj(i -> (char) i).distinct()
                        .map(c -> map.entrySet().stream()
                                .filter(entry -> entry.getValue() == c).findFirst().get().getKey())
                        .collect(Collectors.toList());

So this returns me ["Apple","Ball","Cat"]. However, I would have an input where the character might not be in the map. For example: format = "aabbbczzzzz"

In this case, I want to throw an exception saying that the Character is not found in the map. So I did this

List<String> list = format.chars()
    .mapToObj(i -> (char) i)
    .distinct()
    .map(c -> map.entrySet().stream()
        .filter(entry -> entry.getValue() == c)
        .findFirst()
        .orElseThrow(() -> new Exception("Character not found in Map")))
    .collect(Collectors.toList());

But Java doesn't let me compile. Please help me with how to handle my requirement.

https://ideone.com/Kb2WoN

MC Emperor
  • 22,334
  • 15
  • 80
  • 130
v1shnu
  • 2,211
  • 8
  • 39
  • 68

3 Answers3

1

The lambda expression you pass to the map is a Function which doesn't let you throw a checked exception. The contract of a Functions is:

public interface Function<T, R> {
     R apply(T t);
}

It takes a value of type T and returns a value of type R. You cannot throw a checked exception from an implementation of apply.

You can change your code to throw an unchecked exception.

Also, you are missing a map call to map to the map key as stated in the other answer

List<String> list = format.chars().mapToObj(c -> (char) c).distinct()
              .map(c -> map.entrySet().stream()
                      .filter(entry -> entry.getValue() == c)
                      .map(Map.Entry::getKey) //extract the map key
                      .findFirst()
                      .orElseThrow(() -> new RuntimeException("Character not found in Map"))) 
              .collect(Collectors.toList());

You can also create a custom domain exception that extends RuntimeException and throw that.

References:

Java 8 Lambda function that throws exception?

Throwing checked exceptions in streams from Java Dev Central


Sidenote: You are looping through the map for each character in the string. To improve performance, you can create a reverse map so that you can look up (O(1)) if the character is present in the map and return the value.

Thiyagu
  • 17,362
  • 5
  • 42
  • 79
  • Can you please post an example. – v1shnu Dec 23 '20 at 13:57
  • Yes, I thought of a reverse map but I did not want to create a new map since this lookup will be used in my application only once. – v1shnu Dec 23 '20 at 14:01
  • I have a question, - why won't the second .map throw an exception if the character is not found in the map ? since we are directly using method references to get the key from the entry. – v1shnu Dec 23 '20 at 14:02
  • No. If the character is not in the map the `.map(Map.Entry::getKey)` won't be executed since nothing will pass the `filter` step. – Thiyagu Dec 23 '20 at 14:04
  • Got it. So the `map` will not be executed and then the `findFirst` is going to be an `Optional` of nothing and hence throws the exception ? – v1shnu Dec 23 '20 at 14:07
  • Yes. Correct. It will be an *empty* Optional. – Thiyagu Dec 23 '20 at 14:08
1
List<String> list = format.chars().mapToObj(i -> (char) i).distinct()
                .map(c -> {
                    Optional<Map.Entry<String, Character>> first = map.entrySet().stream().filter(entry -> entry.getValue() == c).findFirst();
                    if (first.isPresent()) {
                        return first.get().getKey();
                    } else {
                        throw new RuntimeException("Character not found in Map");
                    }
                }).collect(Collectors.toList());

This should resolve your problem.
Reason why you were getting compile error is when you write the following line

.filter(entry -> entry.getValue() == c).findFirst().orElseThrow(()-> new Exception("Character not found in Map"))).collect(Collectors.toList())

You are collecting result in List<Map.Entry<String, Character>>. But in the reference variable you have used List<String> list which is the issue.
Also, you are throwing a Checked Exception which you have not handled explicitly. Better to use an UnChecked Exception like RuntimeException.class

The above answer could've been written in complete functional style in following way

List<String> list = format.chars().mapToObj(i -> (char) i).distinct()
                .map(c -> map.entrySet().stream().filter(entry -> entry.getValue() == c)
                        .findFirst()
                        .map(Map.Entry::getKey)
                        .orElseThrow(RuntimeException::new))
                .collect(Collectors.toList());
  • `if (first.isPresent()) { return first.get().getKey();` This is not the way to write this in a functional style. Use `orElseThrow()` – Michael Dec 23 '20 at 14:20
0

First of all I think you should throw RuntimeException instead of Exception or you should catch that in your lambda expression.

Second of all after using findFirst, you'll get an Optional<Entry> object, which means that you should map your result form findFirst to Map.Entry::getKey:

List<String> list = format.chars()
      .mapToObj(i -> (char) i)
      .distinct()
      .map(c -> map.entrySet()
          .stream()
          .filter(entry -> entry.getValue() == c)
          .findFirst()
          .map(Map.Entry::getKey)
          .orElseThrow(() -> new RuntimeException("Character not found in Map")))
      .collect(Collectors.toList());
chubock
  • 834
  • 8
  • 16
  • Can you please explain how the Optional from findFirst() can be passed to the map function ? – v1shnu Dec 23 '20 at 13:57
  • @v1shnu can you explain what are you trying to achieve? it's vague for me. doesn't this code snippet do what you're after? – chubock Dec 23 '20 at 14:15