Reputation: 20179
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
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
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
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.
You can create a Predicate with the custom conditions you need to determine if your objects are equal enough.
Hope it helps.
Upvotes: 2
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
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
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