If/else representation with stream and Java 8

Viewed 1812

I have the following interface with two implementations:

public interface Parser {
    void parse();
    boolean canParse(String message);
}

class StackParser implements Parser {
    public void parse(){
        System.out.println("Parsing stackoverflow");
    }
    public boolean canParse(String message){
        return message.equals("stackoverflow");
    }
}

class YoutubeParser implements Parser {
    public void parse() {
        System.out.println("Parsing youtube");
    }
    public boolean canParse(String message) {
        return message.equals("youtube");
    }
}

I go to check incoming message and parse "stackoverflow" or "youtube":

public class Main {
    private List<Parser> parsers;


    public static void main(String[] args) {
        new Main().doSomething("youtube");
    }

    void doSomething(String message){
        parsers.stream()
                .filter(p -> p.canParse(message))
                .forEach(p -> p.parse());
    }

}

Okay, pretty good. But what if message is not "stackoverflow" or "youtube"? App will be silent, but I want to send another default message if no matches were found, like "I can't parse this web!".

I know that will not works (even compile), but it's also should print "I can't parse this web" only one time, not for every false condition.

parsers.stream()
       .filter(p -> {
          if (p.canParse(message) == false) {
               System.out.println("I can't parse it!");
             }
      })
      .forEach(p -> p.parse());

How can I do it?

5 Answers

This is a perfect example of when to use the Optional#orElse or the Optional#orElseThrow method(s). You want to check if some condition is met so you filter, trying to return a single result. If one does not exist, some other condition is true and should be returned.

try {
    Parser parser = parsers.stream()
            .filter(p -> p.canParse(message))
            .findAny()
            .orElseThrow(NoParserFoundException::new);

    // parser found, never null
    parser.parse();
} catch (NoParserFoundException exception) {
   // cannot find parser, tell end-user
}

In case only one parser can parse the message at a time you could add a default parser:

class DefaultParser implements Parser {
    public void parse() {
        System.out.println("Could not parse");
    }
    public boolean canParse(String message) {
        return true;
    }
}

And then use it via

// make sure the `DefaultParser` is the last parser in the `parsers`
parsers.stream().filter(p -> p.canParse(message)).findFirst().get().parse();

or alternatively drop the DefaultParser and just do

Optional<Parser> parser = parsers.stream().filter(p -> p.canParse(message)).findFirst();
if (parser.isPresent()) {
    parser.get().parse();
} else {
    // handle it 
}

You can use simply a forEach if-else inside

parsers.forEach(p -> {
                if (!p.canParse(message)) {
                   System.out.println("I can't parse it!");
                } else {
                    p.parse();
                }
       });

That's a pretty interesting question, which fortunately I had to face some time ago. Mi approach consisted on declaring a list of Supplier<T> which would be iterated only if there's and exception thrown (the intention of this approach was to retrieve data from DB based on the parameters given, so I would be able to search by id or by an instance).

import java.util.function.Supplier;

public class AbstractFacadeUtil {

    public <R> R tryOr(Supplier<R>...fns) {
        R result = null;
        boolean success = false;
        int i = 0;
        while (!success && i < fns.length) {
            Supplier<R> fn = fns[i++];
            try {
                result = fn.get();
                success = true;
            } catch (Exception e) {

            }
        }
        if (!success) {
            throw new RuntimeException(new Exception(String.format("[%s] Couldn't find a successful method to apply\"", this.getClass())));
        }
        return result;
    }

}

Some notes:

  • I'd used Supplier<T> because it's body didn't contain anything that would throw an undeclared exception which, otherwise, would be needed to use Callable<T> instead.
  • Yeah, could have given it a better name though.
  • Maybe an Iterator<T> would make that piece of code more understandable and clean.

In your specific case, I'd use Jason's approach by adding a Supplier<T> at the end of the list that would throw an NoParserFoundException

[EDIT] Also, you should iterate the List<Supplier<T>> or List<Callable<T>> wheter the Parser can't parse and it throws an CantParseException. So, as you see, exceptions can help a lot even I'm not sure this would be the most efficient or expert approach. Hope it helps you.

[EDIT2] This is an example of how I implemented the solution given above.

Ok, I'm gonna say it. Look at every solution posted - what do you think? Are they neat, clean code that you would expect from functional approach?

Nope, why? Because the design is wrong, it doesn't fit functional approach.

Fix the design:

  1. get rid of void return type (brr)
  2. don't overuse methods like canDoSomething (treat them like isPresent in Optional - it's there for extreme cases, mostly technical, not business code)
  3. look into VAVR (or similar library) - classes like Try, Either etc.

Solution will come naturally then and in every case - not only this specific one you posted here.

Related