Overriding equals method without breaking symmetry in a class that has a primary key
the answer to this question is probably "not possible", but let me ask regardless :)
Assuming we have a very simple JAVA class that has a primary key, for example:
class Person {
String ssid;
String name;
St开发者_如何学JAVAring address;
...
}
Now, I want to store people in a collection, meaning I will have to override the equals method. Not a completely trivial matter, but on a bare basis I will have something along the lines of:
@Override
public boolean equals (Object other) {
if(other==this) return true;
if(!other.getClass().equals(this.getClass()) return false;
Person otherPerson = (Person)other;
if(this.ssid.equals(otherPerson.getSsid()) return true;
}
Excuse any obvious blunders, just typing this out of my head. Now, let's say later on in the application I have a ssid I obtained through user input. If I want to compare my ssid to a Person, I would have to call something like:
String mySsid = getFromSomewhere();
Person myPerson = getFromSomewhere();
if(myPerson.equals(new Person(mySsid)) doSomething();
This means I have to create a convenience constructor to create a Person based on ssid (if I don't already have one), and it's also quite verbose. It would be much nicer to simply call
myPerson.equals(mySsid)
but if I added a string comparison to my Person equals class, that would break the symmetry property, since the String hasn't got a clue on how to compare itself to a Person.
So finally, the big question, is there any way to enable this sort of "shorthand" comparisons using the overriden equals method, and without breaking the symmetry rule?
Thanks for any thoughts on this!
EDIT: Just to clarify, this is more of an open question than a problem seeking an exact solution. The equals should cater for cases when I want to extract a Person from a collection. So it should be possible to do something like this:
List<Person> people = ...
people.get(ssid);
It would appear obvious and intuitive to be able to define equality on a class based on a primary key, but I haven't found a straightforward way of doing that.
A better bet is to store your People in a map, then you can retrieve them easily:
HashMap<String, Person> people = new HashMap<String, Person>();
Person p = constructPersonFromStuff();
people.put(p.ssid, p);
and then later, you can see if the person exists:
String ssid = getFromSomewhere();
if(people.contains(ssid)){
Person thatGuy = people.get(ssid);
}else{
//that person DOESN'T EXIST! HE'S A FIGMENT OF YOUR IMGAINATION!
}
You're not comparing two Person
s, you're comparing two ssid
s. I would use:
myPerson.getSsid().equals(mySsid);
It makes no sense to say that a Person
is equal to a string, do not go down that path.
I just don't understand your problem. You have this code:
String mySsid = getFromSomewhere();
Person myPerson = getFromSomewhere();
if (myPerson.getSsid().equals(mySsid) doSomething();
That just doesn't look bad to me. I guess you could define a function to do that for you:
if (myPerson.ssidEquals(mySsid)) doSomething();
But that's really not that big improvement.
What's the problem?
I'd argue that a Person isn't equal to an ssid. The fact that you use ssid comparison to determine whether two Persons are equal is a shortcut -- we accept as a convention that two Persons with the same ssid refer to the same real-world person -- but isn't actually what makes the equality true.
Perhaps what you really want here is to give your Person a boolean "hasSsid()" method. Or just call myPerson.getSsid().equals(mySsid)
.
the equals method must never return true when its argument is not an instance of the class on which the method was invoked.
if you like, you can create an interface, say Identifiable.
public interface Identifiable {
public Serializable getSsid();
}
public class Person implements Identifiable { ...
you can then write your code in a generic way in terms of Identifiable ... this might help.
(i'm assuming that the real problem is to handle your identifiable objects in a generic fashion for utility routines and such).
You could create a custom data structure that uses a map or set internally:
public interface EntityStore<T,K> {
T get(K id);
boolean contains(Object o);
void put(K id, T entity);
void remove(Object o);
// ...
}
public class MapEntityStore<T,K> {
private Map<K,T> entities = new HashMap<K,T>();
public T get(K id) { return entities.get(id); }
public boolean contains(Object o) {
if (entities.keySet().contains(o))
return true; // search by id
if(entities.values().contains(o))
return true; // search by value (this can be optimized if necessary)
return false;
}
...
}
You can use a factory method to create instances of EntityStore so that you can change to optimized / fancy implementations as necessary.
精彩评论