views:

323

answers:

7

Java: How do I perform list operations with different definitions of equals?

I have two lists of generic POJOs. I need to perform some set operations on the lists based on different ways of comparing the POJOs within the lists.

For example, if my POJO had the following structure:

public class GenericPojo {
    private String id;
    private String address;
    private String city;
    private String country;
    private String extraDetails;
}

(with the appropriate getters and setters)

Given List1<GenericPojo> and List2<GenericPojo>, how would I find:

List1 - List2 (where the GenericPojo classes are equal if just the IDs are equal)

Intersect of List1 and List2 (where id, address, city, country, but not extraDetails of GenericPojo are equal)

Would two different custom comparator classes be helpful here? Are there any libraries that handle these operations effectively or should I try implementing my own?

+1  A: 

If your lists contain no duplicates (with repsect to the hypothetical custom comparator classes) you can use two TreeSets instead, instantiated with your two comparators respectively.

A drawback of this (apart from the duplication constraint) is that the order you get when iterating over elements depend on the comparators.

Buhb
+1  A: 

Given your specific requirements on equality, List#removeAll() and List#retainAll() won't fit your needs so I think you'll need a custom implementation to do something similar to both operations.

Pascal Thivent
A: 

There is no solution that obeys the List contract, and none of the existing implementations of list allow you to supply a comparator. The List contract defines the behavior of the list in terms of each element's equals(Object) method.

The suggestion of using TreeSets with different comparators is also a violation of the contract if the comparator's compare method is inconsistent with each element's equals(Object) method.

In practice you may need to implement your own list classes. You could make it a List implementation that doesn't strictly follow the List contract, but you need to be careful that this doesn't break other library methods / classes.

Stephen C
A: 

If you don't want to program the set operations yourself and you don't mind wasting some CPU and memory resources, you could:

  • construct WrappedPojos based on your GenericPojos
  • give the WrappedPojo suitable implementations of equals()
  • create new lists of the appropriate kind of WrappedPojo to do the operation on
  • copy the contained GenericPojos back to their original containers (if needed) after the operation's finished.

Ugly but simple.

Carl Smotricz
A: 

You could keep all of this in the domain objects by the following approach:

  1. Implement equals(...) on GenericPojo based only on id.
  2. Define WrappedPojo as a wrapper around GenericPojo with an equals(...) based on the additional GenericPojo fields.
  3. For the second use case, use a list of wrapped instances.

I suggest that the root issue is trying to have a single domain class with different definitions of equality.

joel.neely
The Base POJO is not one I have control over, maybe a wrapper solution is more appropriate for what I am trying to accomplish
Yassa
+1  A: 

If you must manipulate class out of your control, I would suggest using delegation. Here is my try:

  1. Create a RichList<T> wrapper around Lists implementing the List contract, based on the decorator pattern.
  2. Create an EqualityChecker<T> inteface, with a single method `public boolean equal( T t1, T, t2).
  3. Implement this interface for your generic pojo twice: one checking just the ID and the other checking the other fields.
  4. Add both methods you are interested in (set substraction and set intersection), but with a supplementary argument which is the EqualityChecker<T> concrete instance that will do the equality test for you.

So you can add both operations to all existing Lists for any kind of object for which you have written an EqualityChecker.

Further improvements: You can also write a default EqualityChecker<T> which just calls the equals method of the compared objects. You can then overload both new operations to default the EqualityChecker.

paradigmatic
A: 

Both operations you are trying to perform are functional, though Java doesn't support these very well and you are likely to be writing them in a very different way. You may have to rethink what it is you are trying to achieve to suit java.

What you are doing is performing an operation on a projection of the data type (i.e. for a sub set of fields)

The operations you are using are also Set operations, rather than List operations. e.g. you cannot take an intersection of two lists (or at least you have to define what that means) Remove may not perform exactly as you expect for list as well.

Imagine you have a method which returns a collection of pojos with just the fields you specify. I have written a library to do this efficiently with dynamicly generated class in the past, have a look at functional Java or similar.

public static <Pojo, Pojo2> Set<Pojo2> project(Collection<Pojo> collection,
      String... fieldsToRetain);

List1 - List2 (where the GenericPojo classes are equal if just the IDs are equal)

Set<PojoWithId> setOfIds = project(list1, "id")
setOfIds.retainAll(project(list2, "id"));

Intersect of List1 and List2 (where id, address, city, country, but not extraDetails of GenericPojo are equal)

Set<PojoWithThreeFields> intersection = project(list1, "id", "address", "city", "country");
intersection.retainAll(project(list2, "id", "address", "city", "country"));
Peter Lawrey