Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Customizing equals method as per the use case

Tags:

java

I have a class defined as

   class Book{
        String author;
        String title;
        int id;
        public boolean equals(Object o){
            return id == ((Book)o).id;
        }
        public int hashCode{...}
   }

In most of the cases uniqueness of the Books is determined by id, hence works properly. In one particular case, I want to merge two lists based on author and title value. I cannot directly use a Set and add the second list to the Set as comparison will happen on ids and not on author/title value. Only way for me is to have two nested for loops to compare each object's value.

List<Book> list1=...;
    List<Book> list2 = ...;

    Iterator<Book> iterator = list1.iterator();
    while(iterator.hasNext()){
        Book b1 = iterator.next();
        for(Book b2:list2){
            if(b1.getAuthor().equals(b2.getAuthor()) && b1.getTitle().equals(b2.getTitle())){
                iterator.remove();
            }
        }
    }
    list2.addAll(list1);

Is there any way where we can override the equals method as per the use case (similar to Comparator where we can change the sort algorithm)?

Instead just have customized equals method that will check the author value and somehow following works

set.addAll(list2);
like image 563
sidgate Avatar asked Dec 08 '25 03:12

sidgate


2 Answers

You can do something liek that with closures, but it is not overriding as such. The problem you have is this is a O(N*M) time complexity which is not idea. A better approach is O(N) is

Map<String, Book> books = new LinkedHashMap<>();
for (Book book : list1) books.put(book.author+"/"+book.title, book);
for (Book book : list2) books.remove(book.author+"/"+book.title);
list2.addAll(books.values());

For closures, you need a few functions I couldn't find.

static class MapStream<K, V> {
    final Map<K, V> map;
    final Function<V, K> func;

    MapStream(Iterable<V> values, Function<V, K> func) {
        map = new LinkedHashMap<>();
        this.func = func;
        addAll(values);
    }

    private void addAll(Iterable<V> values) {
        for (V value : values)
            map.put(func.apply(value), value);
    }

    public MapStream<K, V> removeAll(Iterable<V> values) {
        for (V value : values) {
            map.remove(func.apply(value));
        }
        return this;
    }

    public Collection<V> values() {
        return map.values();
    }
}


public static <T> Function<T, String> and(Function<T, String> func1, Function<T, String> func2) {
    return (T t) -> func1.apply(t) + "\uffff" + func2.apply(t);
}


public static void main(String... ignored) {
    List<Book> list1 = new ArrayList<>();
    List<Book> list2 = new ArrayList<>();

    Function<Book, String> commonKey = and((Book b) -> b.author, (Book b) -> b.title);
    list2.addAll(new MapStream<>(list1, commonKey).removeAll(list2).values());
}

You can see that with some support you can see something with closures.

like image 57
Peter Lawrey Avatar answered Dec 10 '25 16:12

Peter Lawrey


In general you need to externalize equals and hashCode methods. Therefore you could have something like:

class MyModelClass {

    private EqualsImpl<MyModelClass> equalsImpl;

    public MyModelClass(EqualsImpl<MyModelClass> equalsImpl) {
        super();
        this.equalsImpl = equalsImpl;
    }

    @Override
    public boolean equals(Object obj) {
        return equalsImpl.equals(this, obj);
    }

    @Override
    public int hashCode() {
        return equalsImpl.hashCode(this);
    }

}

interface EqualsImpl<C> {

    public boolean equals(C obj1, Object obj2);

    public int hashCode(C obj);

}
like image 25
Francisco Spaeth Avatar answered Dec 10 '25 15:12

Francisco Spaeth