Java 8 avoiding lots of if/else statements

11,842

Solution 1

Use polymorphism for this. Create a class for every logical validator and chain them in the list. Here is nice answer with something you need: https://stackoverflow.com/a/23501390/1119473

public interface Validator<SomeObject>{
    public Result validate(SomeObject object);
}

implementation:

public class SomeFieldSizeValidator implements Validator<SomeObject> {

@Override
public Result validate(SomeObject obj) {
    // or you can return boolean true/false here if it's enough
    return obj.getField().getSize() > 500 ? Result.OK : Result.FAILED;
    }
}

Calling validation chain:

List<Validator> validators = ... create ArrayList of needed Validators
for (Validator v : validators) {
if (!v.validate(object)) { 
  ... throw exception, you know validator and object here
}

Solution 2

I might return the error but this would still use a few if's

public String isValidObject(SomeObject obj){
    if (obj.getField() == null) return "error code 1";
    if (obj.getField().getSize() > 500) return "error code 2";
    ......
    if (someCondition()) return "something";
    return OK;
}

This way you could unit test this method to see if it return the error you expect for different invalid objects.

I want to get rid of 50 if/else statements.

If you have 50 conditions and they all value different results you will need to do 50 checks. You could change the structure like this.

static final Map<Predicate<SomeObject>, String> checks = new LinkedHashMap<>();
static {
    checks.put((Predicate<SomeObject>) o -> o.getField() == null, "error code 1");
    checks.put((Predicate<SomeObject>) o -> o.getField().getSize() > 500, "error code 2");
}

public String isValidObject(SomeObject obj) {
    for (Predicate<SomeObject> test : checks.keySet())
        if (test.test(object))
            return checks.get(test);
    return OK;
}

However, personally this is not clearer and would be harder to debug e.g. breakpoint.

Solution 3

Use java.util.function.Predicate interface:

Predicate<SomeObject> p1 = (SomeObject so ) -> so.getField()!=null;
Predicate<SomeObject> p2 = (SomeObject so ) -> so.getField().getSize() > 500;

...

 SomeObject someObject = new SomeObject();
 Predicate<SomeObject> fullPredicate = p1.and(p2).and( ...


 boolean result = fullPredicate.test(someObject);

Except this will give you 50 Predicate one-line definitions, they'll just be a bit more compact.

Solution 4

I recommend a solution that uses a different approach: consider using Validator objects. Meaning: instead of putting all your checks into the same method, you put each check in its own class!

You define some Validator interface that provides a validate method. When validation fails, that method is supposed to throw some ValidationException (and that exception could contain an error code + message).

And then you create many small classes, each one implementing that interface.

Final step: you create a list in which you put one object of each impl class. And now your code boils down to iterating that list, and applying each impl after the other.

This decouples your validation steps, and adding new/other checks becomes super easy.

Solution 5

If you specifically wish to use lambdas, they mesh nicely with an enum:

public enum SomeValidators {
    E1 (1, o -> o.getField() == null),
    E2 (2, o -> o.getField().getSize() > 500)
    ;

    final int code;
    final Predicate<SomeObject> predicate;

    SomeValidators(int code, int predicate) {
        this.code = code;
        this.predicate = predicate;
    }
}

You can then use it to replicate your if-else if flow as follows:

boolean isValidObject(SomeObject o) {
    Optional<SomeValidators> firstError = 
        Arrays.stream(SomeValidators.values())
        .filter(v -> v.predicate.apply(o))
        .findFirst();

    firstError.ifPresent(e -> LOG.error("error code " + e.code));
    return firstError.isPresent();
}
Share:
11,842
Mihai
Author by

Mihai

Updated on June 12, 2022

Comments

  • Mihai
    Mihai almost 2 years

    I have something that looks like this:

     public boolean isValidObject(SomeObject obj){
        if(obj.getField() == null){
          LOG.error("error code 1");
          return false;
        }
        if(obj.getField().getSize() > 500){
          LOG.error("error code 2");
          return false;
        }
        ......
        if(someCondition()){
         log something
         return false;
        }
    
        return true;
    }
    

    What is the cleanest way of writing this in java 8 with lambdas?