Rylander
Rylander

Reputation: 20179

Check if a Particular Object is in a List Using a Given Implementation of equals()

I need to check if an equivalent instance of a particular Object is in a List.

The Objects are instances of a Final Class that has an equals method that is too strict. I want to be able to provide a different implementation of equals to a "contains" method to check if the object is contained in the List.

The equals method in the class below will return false if the elements of partsInBox are in a different order; I need to change this behavior to be order indiscriminate.

public final class Box {
    String category;
    List<Integer> partsInBox;

    @Override
    public boolean equals(Object o) {
        if (this == o) { return true; }
        if (o == null || getClass() != o.getClass()) { return false; }

        Box box = (Box) o;

        return category.equals(box.category) 
                && partsInBox.equals(box.partsInBox);
    }
}

I would like to be able to do something like this:

List<Box> boxes; // list that I am checking
Box myBox; // what I am checking for

boolean contained = contatins(boxes, box, new EqualsMethod() {
    @Override
    public boolean areEqual(Box b1, Box b2) {
        if (b1 == b2) { return true; }

        return b1.category.equals(b2.category)
                && b1.partsInBox.containsAll(b2.partsInBox);
    }
});

What are my options for achieving this type of functionality?

Upvotes: 0

Views: 3357

Answers (6)

David SN
David SN

Reputation: 3519

You can't provide other method to make the comparation to the List. The best and most simple solution is to modify your equals() method. If you can't modify equals you can implement a Decorator class to create a list with the areEquals comparation that you need.

public class BoxList<E extends Box> implements List<E>{
    private List<E> list;

    public BoxList(List<E> list) {
        this.list = list;
    }

    //Modify the behavior of the methods
    @Override
    public boolean contains(Object o) {
        for(E element : list) {
            if (element.areEquals(o)) {
                return true;
            }
        }
        return false;
    }

    // Redirect all other List methods to the original list
    @Override
    public boolean add(E e) {
        return list.add(e);
    }
    @Override
    public void add(int index, E element) {
        list.add(index, element);        
    }
    ...

Upvotes: 0

David Tonhofer
David Tonhofer

Reputation: 15338

It would be easier (but not so self-evidently inefficient) in Groovy, using Closures. Well, here we go in Java:

package test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

// ------- Original code with comments added

public final class Box {

    // these should be final and List<Integer> should be immutable using
    // Collections.unmodifiableList() to avoid nasty surpises, and should 
    // possibly be pre-sorted

    String category;
    List<Integer> partsInBox;

    @Override
    public boolean equals(Object o) {
        // same instance, then true (works if null passed, too)
        if (this == o) {
            return true;
        }
        // Null and not exactly same class (instanceof not needed as "final"), then false
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Box box = (Box) o;
        // otherwise same category and exactly same list (including ordering)
        return category.equals(box.category) && partsInBox.equals(box.partsInBox);
    }
}

// ------- Create a wrapper class around Box, repainting the house

class WrappedBox {

    final Box box;     

    WrappedBox(Box box) {
        assert box != null;
        this.box = box;
    }

    public String getCategory() {
        return box.category;        
    }

    public List<Integer> getPartsInBox() {
        return box.partsInBox;        
    }

    public boolean equals(Object o) {
        // same instance, then true (works if null passed, too)
        if (this == o) {
            return true;
        }
        // Null and not same class, then false
        if (o == null || !(o instanceof WrappedBox)) {
            return false;
        }
        //
        // otherwise same category and the set of b1 parts is a superset of the set of b2 parts
        // this is not symmetric; should probably be a set comparison. What happens if there
        // are several integers with the same value??
        // return b1.category.equals(b2.category)
        //        && b1.partsInBox.containsAll(b2.partsInBox);
        //
        // SO RECODE AS AUXILIARY EXERCISE:
        //
        WrappedBox other = (WrappedBox)o;
        if (!this.getCategory().equals(other.getCategory())) {
            return false;
        }
        //
        // You probably want to buffer these somehow:
        //
        List<Integer> x1 = new ArrayList(this.getPartsInBox());
        List<Integer> x2 = new ArrayList(other.getPartsInBox());
        Collections.sort(x1);
        Collections.sort(x2);
        return x1.equals(x2); 
    }
}

// --------- Now we can ask for "contains", though one should really create a 
// ---------- List<WrappedBox> first if this happens often

class BoxHandler {

    static boolean containsBox(List<Box> boxes, Box box) {
        assert box != null;
        assert boxes != null;
        WrappedBox wbox = new WrappedBox(box);
        for (Box cur : boxes) {
            if (wbox.equals(new WrappedBox(cur))) {
                return true;
            }
        }
        return false;
    }
}

Upvotes: 0

user3001267
user3001267

Reputation: 304

The ideal solution would be changing the current behavior of the equals() method. However, it could be not possible for you if you don't have access to the other code.

Instead, you can use CollectionUtils.exists(collection, predicate) from Apache CollectionUtils.

http://commons.apache.org/proper/commons-collections/javadocs/api-3.2.1/org/apache/commons/collections/CollectionUtils.html

You can create a Predicate with the custom conditions you need to determine if your objects are equal enough.

Hope it helps.

Upvotes: 2

Dawood ibn Kareem
Dawood ibn Kareem

Reputation: 79875

Equator.java

public interface Equator<T> {
    boolean equals(T obj1, T obj2);
}

Some other class

public static <T> boolean contains(Collection<T> toSearch, T toSeek, Equator<T> equator) {
    for (T oneItem : toSearch) {
        if (equator.equals(oneItem, toSeek)) {
            return true;
        }
    }
    return false;
}

To use it

import static some.other.class.contains; // The contains method from the class above

List<Box> boxes; // list that I am checking
Box myBox; // what I am checking for

boolean contained = contains(boxes, box, new Equator<Box>() {
    @Override
    public boolean equals(Box b1, Box b2) {
        if (b1 == b2) { return true; }

        return b1.category.equals(b2.category)
                && b1.partsInBox.containsAll(b2.partsInBox);
    }
});

Upvotes: 1

constantlearner
constantlearner

Reputation: 5247

You could use a Comparator with Java's built-in methods for sorting and binary search. Suppose you have a class like this, where a and b are the fields you want to use for sorting:

class Thing { String a, b, c, d; }

You would define your Comparator:

Comparator<Thing> comparator = new Comparator<Thing>() {
  public int compare(Thing o1, Thing o2) {
    if (o1.a.equals(o2.a)) {
      return o1.b.compareTo(o2.b);
    }
    return o1.a.compareTo(o2.a);
  }
};

Then sort your list:

Collections.sort(list, comparator); And finally do the binary search:

int i = Collections.binarySearch(list, thingToFind, comparator);

Upvotes: 0

vikingsteve
vikingsteve

Reputation: 40438

Well since the class is final you can't extend it.

There is however the Comparator<T> interface which you could make use of, something like this:

public class BoxComparator implements Comparator<Box> {
    @Override
    public int compare(Box b1, Box b2) {
        if (b1 == b2) { return 0; }

        // return -1 or 0 or +1...
    }

    public static void main(String[] args) {
        Box box1, box2;
        ...

        boolean contains = new BoxComparator().compare(box1, box2) == 0;
    }
}

I'm not completely sure from your code examples above if you want to compare a Box to another Box or a List<Box> - in the latter case you can't derive Comparator, but you could do something similar, for example a BoxInListComparator.

Hope this helps.

Upvotes: 1

Related Questions