Skip to content

Instantly share code, notes, and snippets.

@vkorobkov
Last active October 5, 2016 18:31
Show Gist options
  • Save vkorobkov/24055fd77ce869f689112e60a6125c24 to your computer and use it in GitHub Desktop.
Save vkorobkov/24055fd77ce869f689112e60a6125c24 to your computer and use it in GitHub Desktop.
When you need a custom hashcode/equals in a Set
import java.util.AbstractCollection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class DecoratedSet<K> extends AbstractCollection<K> implements Set<K> {
@FunctionalInterface
public interface Decorator<K> {
Wrapped<K> wrap(K element);
}
@FunctionalInterface
public interface Wrapped<K> {
K getOriginal();
}
private final Set set;
private final Decorator<K> decorator;
public DecoratedSet(Decorator<K> decorator) {
this(new HashSet(), decorator);
}
public DecoratedSet(Set set, Decorator<K> decorator) {
this.set = set;
this.decorator = decorator;
}
@Override
public int size() {
return set.size();
}
@Override
public boolean contains(Object o) {
return set.contains(wrap(o));
}
@Override
public Iterator<K> iterator() {
Iterator<Wrapped<K>> iterator = set.iterator();
return new Iterator<K>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public K next() {
return iterator.next().getOriginal();
}
@Override
public void remove() {
iterator.remove();
}
};
}
@Override
public boolean add(K k) {
return set.add(wrap(k));
}
@Override
public boolean remove(Object o) {
return set.remove(wrap(o));
}
@Override
public void clear() {
set.clear();
}
private Wrapped<K> wrap(Object k) {
return decorator.wrap((K)k);
}
}
@vkorobkov
Copy link
Author

vkorobkov commented Oct 5, 2016

Sometimes you want to have a hashCode/equals which depend on an external context as an addition to internal objects state.

Here's an example of usage(unit test written in groovy+spock framework):

import spock.lang.Specification

class DecoratedSetTest extends Specification {

    static class Person {
        String name
        int age
    }

    def "unique by name"() {
        given:
        def set = new DecoratedSet<Person>({ person ->
            new DecoratedSet.Wrapped<Person>() {

                Person getOriginal() {
                    person
                }

                @Override
                int hashCode() {
                    person.name.hashCode()
                }

                @Override
                boolean equals(Object obj) {
                    Objects.equals(person.name, obj.original.name)
                }
            }
        })

        when:
        set.add(new Person(name: "Vlad", age: 25))
        set.add(new Person(name: "Vlad", age: 28))

        then:
        set.size() == 1
        set[0].age == 25
    }

    def "unique by age"() {
        given:
        def set = new DecoratedSet<Person>({ person ->
            new DecoratedSet.Wrapped<Person>() {

                Person getOriginal() {
                    person
                }

                @Override
                int hashCode() {
                    person.age.hashCode()
                }

                @Override
                boolean equals(Object obj) {
                    Objects.equals(person.age, obj.original.age)
                }
            }
        })

        when:
        set.add(new Person(name: "Diman", age: 25))
        set.add(new Person(name: "Vlad", age: 25))

        then:
        set.size() == 1
        set[0].name == "Diman"
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment