简体   繁体   中英

Compare two custom objects in java with nullable fields

I have a Collection<A>

class A {
    String name;
    Long id;
}

Now I have UI screen where I can provide name & id value to match this collection and display the result.

Now I have 3 criteria:

  1. Only Id provided: Display all the records matchs with Id although name is null
  2. Only name
  3. Both Name and Id.

I can do it by three if check-based on null check of UI fields but I don't want to make if-else block because if I have multiple UI fileds it would be hectic to do nested if-else check.

Please suggest a better way in java 7.

You could provide some predicates that are used to filter the collection. Since java.util.function.Predicate has only been added with Java 8 you'd need to either roll your own or use a library such as Google Guava .

With that you could pass the predicates to be used in a filter:

Collection<Predicate<A>> predicates = ...;//you get them from your UI
Collection<A> filtered = Collections2.filter( originalCollection, Preciates.and( predicates ) );

The predicates could then look like this:

class ANamePredicate implements Predicate<A> {
  private String name; 

  public ANamePredicate( String n) { 
    name = n;
  }     

  boolean apply(@Nullable A input) { 
    //if one or both could be null, add handling for that
    return input.name.equals(n);
  }

  //implement equals(...) as well
}

Of course you could use anonymous classes as well but you'd then probably want to provide for an abstract base class that remove the requirement to always implement equals() :

abstract class AbstractPredicate<T> implements Predicate<T> {
  public boolean equals( Object o) { 
    //simplified, you might have to provide a better implementation, e.g. you want to provide a set of predicates
    return false;
  }
}

Then you create a collection of predicates when needed:

Collection<Predicate<A>> predicates = new LinkedList<>();
predicates.add( new AbstractPredicate<A>() {
   boolean apply(A input) { 
    return "Skabdus".equals(input.name);
   }
} );
predicates.add( new AbstractPredicate<A>() {
   boolean apply(A input) { 
    return input.id == 1;
   }
} );

You could use the decorator pattern with expressions. The principle is to have an interface that each "criteria" will implement (with one method : eval) and to have a distinction bewteen terminal operators and not terminal operators .

You would have a NameIsSet class, an IdIsSet class as terminal opeators (or other classes if you have other implementations in the future), and logical expressions as non-terminal operators : And , Or , Not , ...

Then, you should use the pattern this way :

Predicate predicate = new And(new IdIsSet("id"), new Not(new NameIsSet("name")));
if(predicate.eval()){
// Do something
}

If you want to set a particular behaviour for each case, you could create a List of these predicates and set a method to call if the predicate is true :

for(Predicate predicate: predicateList){
    if(predicate.eval()) {
       predicate.execute();
       //break if only one case is accepted
    }
}

Here is a sample of And class and NameIsSet class:

public class And implements Predicate{
    private Predicate pred1;
    private Predicate pred2;

    public And(Predicate p1, Predicate p2){
        this.pred1 = p1;
        this.pred2 = p2;
    }

    @Override
    public boolean eval(){
        return this.pred1.eval() && this.pred2.eval();
    }

    @Override
    public void execute(){
        // Not sure of you want to do here
    }

}

public class NameIsSet implements Predicate{
    private String name;

    public NameIsSet (String name){
        this.name = name;
    }

    @Override
    public boolean eval(){
        return this.name == null || this.name.length = 0;
    }

    @Override
    public void execute(){
        // Not sure of you want to do here
    }

}

In this implementation, you must create all your predicates beforehand, add them to a list and set the execute() method for each.

Hope this will be useful to you.

Your if statements don't need to be nested. You can, for example, do this:

static boolean matches(A candidate, A pattern) {
    if (pattern.name != null && !candidate.name.equals(pattern.name))
        return false;
    if (pattern.id != null && !candidate.id.equals(pattern.id))
        return false;
    return true;
}

This scales at a 1:1 rate with any number of fields. (You could use candidate.id != pattern.id there, but I used equals for consistency.)

If you have dozens of fields and you don't want to name them individually, you can use reflection and process all the fields with a single for loop:

static boolean matches(A candidate, A pattern) throws IllegalAccessException {
    for (Field fld : A.class.getDeclaredFields()) {
        Object c = fld.get(candidate);
        Object p = fld.get(pattern);
        if (p != null && !c.equals(p))
            return false;
    }
    return true;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM