Вопрос

Here's the situation: I want to test 2 objects for uniqueness based on 2 different ID's. Example:

// Note I'm using JSON notation to keep things simple; the actual code 
// is with Java Objects

// OBJECT A
{
    main_id: 0,
    id_a: 123,
    id_b: 456
}

// OBJECT B
{
    main_id: 1,
    id_a: 123,
    id_b: 456
}

// OBJECT C
{
    main_id: 2,
    id_a: 123,
    id_b: 789
}

In the Example, Objects A and B are the same because id_a and id_b are the same, and Object C is different.

To determine this in the code, I'm planning on converting both ID's to a string and concatenating them together with a separator char in the middle (e.g., "{id_a},{id_b}"), then adding them to a Set<String> to test for uniqueness.

My question is, is there a better way? (By better, I mean more efficient and/or less kludgy)

Это было полезно?

Решение

If you want to use HashSet, you can override hashCode and equals to exclusively look at those two members.

Hash code: (31 is just a prime popularly used for hashing in Java)

return 31*id_a + id_b;

Equals: (to which you'll obviously need to add instanceof checks and type conversion)

return id_a == other.id_a && id_b == other.id_b;

If you don't want to bind these functions to the class because it's used differently elsewhere, but you still want to use HashSet, you could consider:

  • Creating an intermediate class to be stored in the set, which will contain your class as a member and implement the above methods appropriately.

  • Use your string approach

  • Use HashSet<Point> - Point is not ideal for non-coordinate purposes as the members are simply named x and y, but I do find it useful to have such a class available, at least for non-production code.


Alternatively, if you want to use TreeSet, you could have your class implement Comparable (overriding compareTo) or provide a Comparator for the TreeSet, both of which would compare primarily on the one id, and secondarily on the other.

The basic idea would look something like this:

if (objectA.id_a != objectB.id_a)
  return Integer.compare(objectA.id_a, objectB.id_a);
return Integer.compare(objectA.id_b, objectB.id_b);

Другие советы

Not sure this is any more efficient or less kludgy. You could keep the original hashcode/equals using the main id (as per your comment) and then create a wrapper that has a hashcode/equals for the composite ida, idb. Maybe over the top for what you need though.

CompositeIdEntity.java

public interface CompositeIdEntity {

    long getIdA();

    long getIdB();

}

Entity.java

public class Entity implements CompositeIdEntity {

    private final long mainId;

    private final long idA;

    private final long idB;

    public Entity(long mainId, long idA, long idB) {
        this.mainId = mainId;
        this.idA = idA;
        this.idB = idB;
    }

    @Override
    public long getIdA() {
        return idA;
    }

    @Override
    public long getIdB() {
        return idB;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + (int) (mainId ^ (mainId >>> 32));
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Entity other = (Entity) obj;
        if (mainId != other.mainId)
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "Entity [mainId=" + mainId + ", idA=" + idA + ", idB=" + idB
                + "]";
    }

}

CompositeIdWrapper.java

public class CompositeIdWrapper {

    private final CompositeIdEntity compositeIdEntity;

    public CompositeIdWrapper(CompositeIdEntity compositeIdEntity) {
        this.compositeIdEntity = compositeIdEntity;
    }

    public CompositeIdEntity getCompositeIdEntity() {
        return compositeIdEntity;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + (int) (compositeIdEntity.getIdA() ^ (compositeIdEntity
                        .getIdA() >>> 32));
        result = prime * result
                + (int) (compositeIdEntity.getIdB() ^ (compositeIdEntity
                        .getIdB() >>> 32));
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        CompositeIdWrapper other = (CompositeIdWrapper) obj;
        if (compositeIdEntity.getIdA() != other.compositeIdEntity.getIdA())
            return false;
        if (compositeIdEntity.getIdB() != other.compositeIdEntity.getIdB())
            return false;
        return true;
    }

}

Test.java

import java.util.HashSet;
import java.util.Set;

public class Test {

    public static void main(String[] args) {
        Entity en1 = new Entity(0, 123, 456);
        Entity en2 = new Entity(1, 123, 456);
        Entity en3 = new Entity(2, 123, 789);
        Entity en4 = new Entity(2, 123, 456);
        Entity en5 = new Entity(1, 123, 789);

        // Set based on main id
        Set<Entity> mainIdSet = new HashSet<>();
        mainIdSet.add(en1);
        mainIdSet.add(en2);
        mainIdSet.add(en3);
        mainIdSet.add(en4);
        mainIdSet.add(en5);

        System.out.println("Main id set:");
        for (Entity entity : mainIdSet) {
            System.out.println(entity);
        }

        // Set based on ida, idb
        Set<CompositeIdWrapper> compositeIdSet = new HashSet<>();
        compositeIdSet.add(new CompositeIdWrapper(en1));
        compositeIdSet.add(new CompositeIdWrapper(en2));
        compositeIdSet.add(new CompositeIdWrapper(en3));
        compositeIdSet.add(new CompositeIdWrapper(en4));
        compositeIdSet.add(new CompositeIdWrapper(en5));

        System.out.println("Composite id set:");
        for (CompositeIdWrapper wrapped : compositeIdSet) {
            System.out.println(wrapped.getCompositeIdEntity());
        }

    }

}

Output

Main id set:
Entity [mainId=1, idA=123, idB=456]
Entity [mainId=2, idA=123, idB=789]
Entity [mainId=0, idA=123, idB=456]
Composite id set:
Entity [mainId=0, idA=123, idB=456]
Entity [mainId=2, idA=123, idB=789]

See this, Here I override the equals() and hashcode() to ensure uniqueness on "name" field of a Person object

public class SetObjectEquals {
    Person p1 = new Person("harley");
    Person p2 = new Person("harley");

    public void method1() {
        Set<Person> set = new HashSet<Person>();
        set.add(p1);
        set.add(p2);
        System.out.println(set);
    }

    public static void main(String[] args) {
        SetObjectEquals obj = new SetObjectEquals();
        obj.method1();
    }

}

class Person {
    String name;

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Person other = (Person) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

    Person(String name) {
        this.name = name;
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top