17

Is it possible to override Object#equals(Object) locally when using list.contains(someObject)?

Example:

class SomeObject {
    ...
    private int id;
    private String name;

    @Overrdide
    public boolean equals(Object other){
         ...
         return this.id == other.id;
    }
}

But what if I want another kind of equals when I use list.contains(someObject)? For example I want to know if a list contains a certain name? Is it possible to override Object#equals(Object) 'anonymously'?

More specific explanation why I would need it:

int objectId = ... // Some event which passes me the attribute of an object but not the object itself

Now I have List<SomeObject> someObjects and I would like to know if this list contains an object with objectId without necessarily iterating over it.

One "solution" I could think of would be using Map<Integer, SomeObject> mapping and then someObject = mapping.get(objectId)

EDIT: My question is not a duplicate since I am specifically asking to override Object#equals(Object).

SklogW
  • 839
  • 9
  • 22
  • It ought to be, but this is IMHO the worst thing about the collections API. You can use a custom Comparator in many places, but you're stuck with just one notion of `equals`. – Paul Boddington Mar 24 '16 at 12:44
  • 1
    If Java only had case-classes :-( – SklogW Mar 24 '16 at 12:48
  • Using a Comparator to specify equality works for a Set and for Map keys (but it damages performance - operations become O(log n) because you have to use TreeMap rather than HashMap). It doesn't help with List though. It ought to be possible to use a custom notion of equality with methods like `indexOf`, `contains` etc but it's not possible without creating a wrapper class and overriding equals as you want. It's very annoying. – Paul Boddington Mar 24 '16 at 12:53
  • @SklogW How big is the list? If you're not interested in order and one property are queried multiple times, you may re-sort the list with a comparator specific to this property, then check if `Collections.binarySearch(list, cmp) >= 0` – Alex Salauyou Mar 24 '16 at 13:00
  • This looks like you need a kind of indexing. – Alex Salauyou Mar 24 '16 at 13:01
  • Possible duplicate of [Finding all objects that have a given property inside a collection](http://stackoverflow.com/questions/587404/finding-all-objects-that-have-a-given-property-inside-a-collection) (among many others, I bet) – David Z Mar 24 '16 at 14:45
  • 1
    Not a duplicate since I am specifically asking about overriding equals(). The duplication is implicit IMHO. – SklogW Mar 24 '16 at 14:48
  • Possible duplicate of [What is the best way to filter a Java Collection?](http://stackoverflow.com/questions/122105/what-is-the-best-way-to-filter-a-java-collection) – Raedwald Mar 26 '16 at 12:40

3 Answers3

16

You can apply a filter on a stream object obtained from the list. The filter takes a predicate where you can set the condition. Then you can check if the filtered stream is not empty:

list.stream().filter(e -> e.name.equals(otherName)).findAny().isPresent()

You can make it reusable as follows for example:

private <T> boolean containsWithPredicate(List<T> list, Predicate<T> predicate) {
    return list.stream().filter(predicate).findAny().isPresent();
}

and call it as follows:

boolean containsElement = containsWithPredicate(myListOfObjects,
                                                 (e -> e.name.equals(otherName)));

EDIT:

There is a much simpler way of doing the exact same above by just calling Stream.anyMatch instead of doing filter then findAny:

list.stream().anyMatch(predicate);
M A
  • 71,713
  • 13
  • 134
  • 174
  • 7
    Not sure if it's a solution or a workaround since I could also iterate with a for-each loop. But it's definitely an approach. – SklogW Mar 24 '16 at 12:42
  • 2
    @SklogW I think it's a solution. An "`equals`" anonymous method is needed which can be customized so first thing comes to mind is functional style programming (you can pass the filter's predicate as an argument). It also seems simpler to read. – M A Mar 24 '16 at 12:49
  • 2
    @SklogW This is what you actually want to do. You don't want to make a new `equals` method, you're asking to check if lists contain things based on specific parameters, and for that you want a [`Predicate`](https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html) -- which is actually what the lambda in the [`filter`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#filter-java.util.function.Predicate-) method is. – Captain Man Mar 24 '16 at 14:21
  • Especially because a new equals would mean that I change the identity of an object. Your approach shows clearly my intention. Thx ! – SklogW Mar 24 '16 at 14:44
  • I'm a bit surprised that there's no `findAny` that takes a predicate. The equivalent in LINQ is [`Any`](https://msdn.microsoft.com/en-us/library/system.linq.enumerable.any%28v=vs.100%29.aspx), which has two overloads. One takes a predicate, the other doesn't. This way you don't have to do `Where` and `Any` (or `filter` and `findAny`) as two distinct operations. – Mason Wheeler Mar 24 '16 at 21:04
  • @MasonWheeler It can be done using `anyMatch`. See my update (nice catch BTW). – M A Mar 24 '16 at 21:18
8

You can use a TreeSet with a custom Comparator to achieve what you want. It doesn't use equals() for equality, but rather considers that if compare() returns 0, objects are equal.

Let's say we want all Strings to be equal if they are the same length:

TreeSet<String> set = new TreeSet(new Comparator<>() {
    public int compare(String o1, String o2) {
       return o1.length() - o2.length();
    }
}
set.add("asd");
set.contains("foo");    // Returns true

This can be a useful construct (works with TreeMap as well) in cases where you need to have different "equality definitions" for objects at different times, while still working with collections.

Kayaman
  • 72,141
  • 5
  • 83
  • 121
  • But it still gives me a a list with a permanent definition of what's equal or not, right ? What if I want a certain kind of equals only once ? Can I change the comparator for example ? – SklogW Mar 24 '16 at 12:46
  • 1
    It will define "what is equal" for that collection. You could write the `Comparator` so that it has different logic for different objects, but it will become more error-prone and you need to make sure that you adhere to `compare()`'s contract. – Kayaman Mar 24 '16 at 12:51
8

There is no way to have multiple equals for the same class. But you can subclass your class and override hashCode() and equals(), then use the desired instance depending on the equals you want to use.

I must say that I don't like doing that, the equals method should be well defined, it'll be very confusing to use multiple equals on different scenarios. Instead of subclassing, I would have total different implementations if I need to have two different equals methods. So I highly advise you to reconsider your design.

Maroun
  • 94,125
  • 30
  • 188
  • 241
  • 3
    Good advice. If I need different equals then maybe my design is the problem. I will think about that. – SklogW Mar 24 '16 at 12:50
  • 1
    @SklogW It's a good idea. Think about other programmers that might use your program, how will they know what implementation to choose? `equals` should be really well defined. – Maroun Mar 24 '16 at 12:51
  • It makes sense. I think I am trying to work around a bad design. Thx ! – SklogW Mar 24 '16 at 12:53