J'ai une question concernant la validation des données dans une boucle imbriquée.
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
}
Je souhaite valider name
et desc
dans tous les objets au lieu d'utiliser une boucle imbriquée comme suit
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 ...
}
}
Y a-t-il une meilleure façon de le faire?
Je vais simplement le dire ici, afin que personne ne fasse ce que vous voulez: utiliser un cadre approprié pour cela; personnellement, je choisirais Hibernate Validator - très facile à intégrer et à utiliser OMI. Cela facilitera votre imbrication que vous avez sans aucun problème et il y a des tonnes de tutoriels en ligne (même le leur est bien) et comment y parvenir; Indice: quelques dépendances et quelques annotations et vous avez terminé.
EDIT: Une idée pour externaliser le chèque, vous devez créer une interface fonctionnelle
ObjectValidatorInterface ov = new ObjectValidator();
if(!object1List.stream().allMatch(o -> ov.validate(o, Object1.class))) {
// throw error;
}
Et l'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;
}
}
RÉPONSE ORIGINALE
Vous pourrez peut-être le faire en déléguant la validation à chaque objet:
List<Object1> object1List = getObject1List();
if(!object1List.stream().allMatch(Object1::isValid)) {
//throw error
}
Et ajoutez une méthode isValid
à chaque objet
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;
}
}
Eh bien, vous pouvez certainement éviter l’imbrication en utilisant l’API Stream:
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
}
Une autre approche plus compacte, mais probablement moins efficace:
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 }
Donc, pour conclure si les performances sont une préoccupation, poursuivez votre approche ou essayez de voir si l'API de flux parallèle peut vous être utile, sinon, ce qui précède devrait suffire.
Un validator, en tant qu'interface fonctionnelle, est une Consumer
qui consomme une valeur d'un type spécifique, effectue des vérifications et lève une exception si quelque chose est désactivé.
La traversée de la structure de données (Tree) peut être effectuée sur streams (peek
à visit un noeud, flatmap
à recurse dans les enfants). Pour la validation, nous introduisons une opération de mappage NO-OP, qui valide et renvoie la valeur, permettant ainsi au flux de continuer.
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();
Eh bien, vous pouvez utiliser une fonction/boucle qui parcourt un objet (l’objet générique). Ainsi, vous ne devrez pas exécuter de boucle for distincte pour chaque objet si le nom de la variable membre "desc" est uniforme dans les trois objets. Mais il s'agit d'un autre niveau d'imbrication, vous n'êtes donc pas sûr de vouloir l'utiliser si vous cherchez simplement à éviter l'imbrication.
Si leur validation semble être la même, seul le type change, utilisez l'interface avec Object1
, Object2
et Object3
implémente ICommon
:
interface ICommon {
String getField1();
int getField2();
Iterable<? extends ICommon> getChildren();
}
Alors avoir ceci:
private void validate0(String prefix, ICommon common) {
if (common.getField1() == null) throw new ValidationException(prefix + ".field1 can't be null");
if (common.getField2() == null) throw new ValidationException(prefix + ".field2 can't be null");
int index = 0;
for (ICommon child : getChildren()) {
validate0(prefix + ".children[" + index + "]", child);
++index;
}
}
public void validate(ICommon common) {
validate0("common", common);
}
Vous pouvez également inverser cette opération à l'aide d'un BiConsumer<String, ICommon>
(ou Consumer<ICommon>
) à valider si vous ne vous souciez pas du rapport d'erreur correct).