web-dev-qa-db-fra.com

Alternative Java 8 pour la validation des données au sein de plusieurs boucles imbriquées

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? 

7
hades

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é. 

4
Eugene

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;
    }
}
3
Bentaye

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.

1
Aomine

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();
1
Peter Walser

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.

0
benignfoppery

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).

  • Cela permettra également de préciser quelle validation peut s’appliquer à votre objet.
  • Utiliser stream ne rend pas (toujours) plus clair (voir Peter Walser answer et essayez de comprendre le gros stream :)).
0
NoDataFound