Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 alternative for validating data inside multiple nested loops

Tags:

java

java-8

I have a question regarding validating data in a nested for loop.

public class Object1{
  private String obj1Name;

  private String obj1Desc;

  private List<Object2> object2List;

  //Setters and getters
}


public class Object2{
  private String obj2Name;

  private String obj2Desc;

  private List<Object3> object3List;

  //Setters and getters
}

public class Object3{
  private String obj3Name;

  private String obj3Desc;
  //Setters and getters
}

I wish to validate both name and desc in all objects, instead of using a nested loop like the following:

List<Object1> object1List = getObject1List();

for(Object1 object1 : object1List ){
   if(object1.getObj1Name() == null){
     //throw error
   }

   if(object1.getObj1Desc() == null){
     //throw error
   }

   for(Object2 object2 : object1.getObject2List()){
        if(object2.getObj2Name() == null){
            //throw error
        }

        if(object2.getObj2Desc() == null){
            //throw error
        }

        //loop Object 3 ...
   }
}

Is there any better way to do it?

like image 947
hades Avatar asked Dec 05 '18 12:12

hades


People also ask

What is a nested for loop in Java?

Nested loop means a loop statement inside another loop statement. That is why nested loops are also called as “ loop inside loop “. Syntax for Nested For loop: Note: There is no rule that a loop must be nested inside its own type. In fact, there can be any type of loop nested inside any type and to any level.

How many times does a for loop iterate in Java?

For example, you can put a while loop inside the body of a for loop. Example 1: Java Nested for Loop. When you run the program, the output will be: Here, the outer loop iterates 5 times. In each iteration of outer loop, the inner loop iterates 2 times.

Is it possible to solve the multiplication table without nested loop?

Question 1. Print the multiplication table of 2. You can solve this one without nested loop. So try to solve this before you check the answer! I know you can do this!! Here, I initialized the variable i into 1, and made it increase to 9.

Why do we need multiple validators for the same constraint?

Multiple validators for the same constraint might be needed if we want to target different elements specified by SupportedValidationTarget. If two ConstraintValidators refer to the same target type, an exception will occur. package javax.validation.constraintvalidation; ........


4 Answers

I am just going to say it here, so that no one does what you want to do - use a proper framework for this, personally I would go for Hibernate Validator - super easy to integrate and use IMO. It will support that nesting that you have without any problems and there are tons of tutorials online (even their own is good) and how to achieve that; hint: a few dependencies and a few annotations and you are done.

like image 52
Eugene Avatar answered Oct 14 '22 05:10

Eugene


EDIT: An idea to externalize the check, you need to create Functional Interface

ObjectValidatorInterface ov = new ObjectValidator();
if(!object1List.stream().allMatch(o -> ov.validate(o, Object1.class))) {
        // throw error;
}

And the interface

@FunctionalInterface
interface ObjectValidatorInterface {
    boolean validate(Object object, Class clazz);
}

class ObjectValidator implements ObjectValidatorInterface {

    public boolean validate(Object object, Class clazz) {
        boolean valid = false;

        if(Object1.class.getName().equals(clazz.getName())) {
            valid = validateObject1((Object1) object);

        } else if(Object2.class.getName().equals(clazz.getName())) {
            valid = validateObject2((Object2) object);

        } else if(Object3.class.getName().equals(clazz.getName())) {
            valid = validateObject3((Object3) object);
        }

        return valid;
    }

    private boolean validateObject1(Object1 o) {
        boolean valid;
        valid = o.getObj1Name() != null && o.getObj1Desc() != null;
        if(!(o.getObject2List() == null || o.getObject2List().isEmpty())) {
            valid = valid && o.getObject2List().stream().allMatch(o2 -> validate(o2, Object2.class));
        }
        return valid;
    }

    private boolean validateObject2(Object2 o) {
        boolean valid;
        valid = o.getObj2Name() != null && o.getObj2Desc() != null;
        if(!(o.getObject3List() == null || o.getObject3List().isEmpty())) {
            valid = valid && o.getObject3List().stream().allMatch(o3 -> validate(o3, Object3.class));
        }
        return valid;
    }

    private boolean validateObject3(Object3 o) {
        return o.getObj3Name() != null && o.getObj3Desc() != null;
    }
}

ORIGINAL ANSWER

You might be able to do it by delegating the validation to each object:

List<Object1> object1List = getObject1List();

if(!object1List.stream().allMatch(Object1::isValid)) {
    //throw error
}

And add an isValid method to each object

public class Object1 {
    private String obj1Name;
    private String obj1Desc;
    private List<Object2> object2List;

    public boolean isValid() {
        return obj1Name != null
            && obj1Desc != null
            && object2List.stream().allMatch(Object2::isValid);
    }
}

public class Object2 {
    private String obj2Name;
    private String obj2Desc;
    private List<Object3> object3List;

    public boolean isValid() {
        return obj2Name != null
            && obj2Desc != null
            && object3List.stream().allMatch(Object3::isValid);
    }
}

public class Object3 {
    private String obj3Name;
    private String obj3Desc;

    public boolean isValid() {
        return obj3Name != null
            && obj3Desc != null;
    }
}
like image 24
Bentaye Avatar answered Oct 14 '22 04:10

Bentaye


Well, you can definitely avoid the "nesting" by using the Stream API:

if(object1List.stream()
                .anyMatch(a -> a.getObj1Name() == null ||
                        a.getObj1Desc() == null)){
    // throw error
}else if(object1List.stream()
                .anyMatch(a -> a.getObject2List().stream()
                       .anyMatch(b -> b.getObj2Name() == null ||
                                            b.getObj2Desc() == null))){
    // throw error
}else if(object1List.stream()
                .anyMatch(a -> a.getObject2List().stream()
                        .anyMatch(b -> b.getObject3List().stream()
                                .anyMatch(c -> c.getObj3Name() == null ||
                                                      c.getObj3Desc() == null)))){
     // throw error
}

Another approach being more compact, but probably less efficient:

boolean result = object1List.stream()
                .flatMap(a -> a.getObject2List().stream()
                        .flatMap(b -> b.getObject3List().stream()
                                .flatMap(c -> Stream.of(a.getObj1Name(),
                                        a.getObj1Desc(), b.getObj2Name(),
                                        b.getObj2Desc(), c.getObj3Name(), c.getObj3Desc()))))
                .anyMatch(Objects::isNull); 

if(result){ // throw error }

So, to conclude if performance is a concern then proceed with your approach or try and see if the parallel stream API can do you any good, otherwise, the above should suffice.

like image 1
Ousmane D. Avatar answered Oct 14 '22 05:10

Ousmane D.


A validator, as a functional interface, is a Consumer which consumes a value of a specific type, performs checks and throws an Exception if something is off.

Traversal of the data structure (Tree) can be accomplished over streams (peek to visit a node, flatmap to recurse into the children). For the validation, we introduce a NO-OP map operation, which validates and returns the value, allowing the stream to continue.

BiConsumer<String, Object> checkRequired = (name, value) -> {
    if (value == null) {
        throw new IllegalArgumentException(name + " is required");
    }
};

Consumer<Object1> obj1Validator = obj1 -> {
    checkRequired.accept("Object1", obj1);
    checkRequired.accept("obj1Name", obj1.getObj1Name());
    checkRequired.accept("obj1Desc", obj1.getObj1Desc());
};

Consumer<Object2> obj2Validator = obj2 -> {
    checkRequired.accept("Object2", obj2);
    checkRequired.accept("obj2Name", obj2.getObj2Name());
    checkRequired.accept("obj2Desc", obj2.getObj2Desc());
};

Consumer<Object3> obj3Validator = obj3 -> {
    checkRequired.accept("Object3", obj3);
    checkRequired.accept("obj3Name", obj3.getObj3Name());
    checkRequired.accept("obj3Desc", obj3.getObj3Desc());
};

Object1 obj1 = ...; // assign some value

Stream.of(obj1)
    .peek(obj1Validator)
    .filter(x -> x.getObject2List() != null)
    .map(Object1::getObject2List)
    .filter(Objects::nonNull)
    .flatMap(List::stream)
    .peek(obj2Validator)
    .map(Object2::getObject3List)
    .filter(Objects::nonNull)
    .flatMap(List::stream)
    .peek(obj3Validator)
    .count();
like image 1
Peter Walser Avatar answered Oct 14 '22 05:10

Peter Walser