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?
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.
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.
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.
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; ........
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.
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;
}
}
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.
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();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With