This is a question about how to implement the equals method when I need to find instance of the object in a List given a value that one of the instances my have in their member.
I have an object where I've implemented equals:
class User {
private String id;
public User(id) {
this.id = id;
}
public boolean equals(Object obj) {
if (!(obj instanceof User)) {
return false;
}
return ((User)obj).id.equals(this.id);
}
}
Now if I want to find something in the List I would do something like this:
public function userExists(String id) {
List<Users> users = getAllUsers();
return users.contains(new User(id));
}
But perhaps this might be a better implementation?
class User {
private String id;
public boolean equals(Object obj) {
if (!(obj instanceof User)) {
return false;
}
if (obj instanceof String) {
return ((String)obj).equals(this.id);
}
return ((User)obj).id.equals(this.id);
}
}
With this instead:
public function userExists(String id) {
List<Users> users = getAllUsers();
return users.contains(id);
}
Doing it the second way is dangerous, because it breaks symmetric property of the equality.
Java expects implementations of equals() to be reflexive, symmetric, and transitive. Second implementation breaks symmetry: if you compare User to a String representing it's ID you'd get true, but if you compare the string to the user, you will get a false.
Do not override equals for things that are not mathematically equal.
You might think it is a good idea to do
User bob = new User("Bob");
if (bob.equals("Bob")) {
...
}
but it rarely is. Do you want all of the equals observing code getting confused when Strings are "equal" to Users?
If you want a lookup method, write it
class User {
private String id;
public boolean equals(Object obj) {
if (obj instanceof User) {
User other = (User)obj;
if (id.equals(other.id)) {
return true;
}
}
return false;
}
public String getId() {
return id;
}
}
Then the code elsewhere to maintain the "fast lookup" table.
Map<String, User> idTable = new HashMap<String, User>();
User bob = new User("Bob");
idTable.put(bob.getId(), bob);
public User findUser(String id) {
return idTable.get(id);
}
Note that this doesn't mess around with the equals implementation, so now you can safely have Sets of Users, Lists of Users, etc. all without worrying if somehow a String will foul the works.
Now if you can't find a good place to maintain a Map of Users indexed by their id, you can always use the slower Iterator solution
List<User> users = new List<User>();
users.add(new User("Bob"));
users.add(new User("Steve"));
users.ass(new User("Ann"));
public User findUser(String id) {
Iterator<User> index = users.iterator();
while (index.hasNext()) {
User user = index.next();
if (id.equals(user.getId())) {
return user;
}
}
return null;
}
First, your second implementation is functionally equivalent to the first, because an instance of String is not an instance of User, and the first return statement will short-circuit it before the check for a String happens.
What I mean is,
public boolean equals(Object obj) {
if (!(obj instanceof User)) { // This will execute if obj is a String
return false;
}
if (obj instanceof String) {
// Never executes, because if obj is a String, we already
// returned false above
return ((String)obj).equals(this.id);
}
return ((User)obj).id.equals(this.id);
}
So for the remainder of the answer, I will assume that what was meant is
public boolean equals(Object obj) {
if ( obj == null ) return false; // Add a null check for good measure.
if (!(obj instanceof User)) {
if (obj instanceof String) {
// Now we're checking for a String only if it isn't a User.
return ((String)obj).equals(this.id);
}
return false;
}
return ((User)obj).id.equals(this.id);
}
Now we come to the actual problem.
Implementing an equals that returns true for a User-to-String comparison is bad practice because equals is expected to be symmetric ( a.equals(b) if and only if b.equals(a) ). And since a String can never equal a User, a User should never equal a String.
Don't forget to override Object.hashCode() if you override Object.equals().
Equal objects must have equal hash codes and you may run into probs with Collections if you do not obey the contract.
Second choice is very bad. That means you are allowing a user to pass something other than a User into User.equals() (i.e., really Object.equals() when you are not really trying to compare a String with the User. So you are in essence violating the contract for what equals is supposed to do.
Also, neither of your answers handle null-checking.
Related
I have these two java classes as below:
RelocationDisabledProduct
public class RelocationDisabledProduct {
#JsonProperty("productIdentifierType")
private ProductIdentifierType productIdentifierType;
#JsonProperty("id")
private String id;
#Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null) {
return false;
}
final Item that = (Item)o;
return Objects.equals(id, that.getId()) &&
Objects.equals(productIdentifierType, that.getProductIdentifierType());
}
#Override
public int hashCode() {
return Objects.hash(id, productIdentifierType);
}
}
Item
public class Item {
private String id;
private ProductIdentifierType productIdentifierType;
}
equals works fine
relocationDisabledProduct.equals(item) // true
but contains does not
List<RelocationDisabledProduct> relocationDisabledProducts;
...
getRelocationDisabledProducts().contains(item) // false
Since contains uses the equals method, not sure why my above syntax returns false?
I believe this is due to the definition of AbstractCollection<E> contains(Object o). The Javadoc says
public boolean contains​(Object o)
Returns true if this collection contains the specified element. More formally, returns true if and only if this collection contains at least one element e such that Objects.equals(o, e).
Note the order of arguments to contains(). It is using the equals() method from Item, not the one from RelocationDisabledProduct, which you would have probably seen if you stepped into the code in the debugger.
Both Item and RelocationDisabledProduct need to share a common equals() implementation, maybe a default method of a superinterface... you'll have to work out what makes sense in your case.
I want to write a Hashcode function for my class. The equals method is already existing. What could serve as a good hashcode() for below class having mentioned fields.
public class ReservationSHC extends AuditableModel {
/** The log instance for this class * */
private static Log log = Log.getLog("RESERVATION");
private Long id;
private String shc;
private boolean systemGenerated;
private boolean notifyableLoadOrDGR;
private Integer versionId;
private String ownerCarrierCode;
private String shcDesc;
private String shcCategory;
private CargoRecord cargoRecord;
private ShipmentReservation shipmentReservation;
private Set additionalDataElements;
private boolean autoGenerated;
Equals() method is already existing for the class.
public boolean equals(Object arg0) {
if (arg0 instanceof ReservationSHC) {
ReservationSHC reservationSHC = (ReservationSHC) arg0;
if (reservationSHC.getShc().equals(this.shc)) {
return true;
}
} else if(arg0 instanceof String) {
String tempShc = (String) arg0;
if (tempShc.equals(this.shc)) {
return true;
}
}
return super.equals(arg0);
}
Also, I don't see any #override annotation on the equals method. Is it getting overridden??
As a rule of thumb, just use your IDE to generate the equals() and hashCode() methods selecting the relevant fields.
In this case, it looks like only shc is relevant to equality of objects, so these will be the methods (as generated by intelliJ )
import java.util.Objects;
.
.
.
private String shc;
.
.
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Hello hello = (Hello) o;
return shc.equals(hello.shc);
}
#Override
public int hashCode() {
return Objects.hash(shc);
}
In your case i think you should re-evaluate the equals method too. Equating the object to the string seems incorrect.
The purpose of the annotation #Override is only meant to give you compiler warnings in case you do something wrong.
Example: if you changed your signature to
#Override
public boolean equals(String other)
then the compiler would know tell you that "no, this equals() method doesn't override anything" (because it uses String for its argument, but it must be other.
And note: your equals() implementation is wrong. When any x.equals(y) is true, then y.equals(x) needs to be true, too. And your idea to directly compare against Strings violates that condition. Example:
yourObject.equals("some"); // could give true, when yourObject.shc is "some"
but
"some".equals(yourObject); // will never be true, because yourObject isn't a string
Finally, you are comparing only one field, so that field should go into both methods, as written in the other answer!
I have Set of Object which has existing values and getting a new Set values as an update. If the new Set does contain the old Object then I do nothing, if the new Set contains new Object then I want send create update and if the new Set doesn't contain an existing object then I want send a delete update.
Object has two fields :
private PreferenceType preferenceType;
private String preferenceValue;
Currently I am comparing the existing Set objects against new Set objects and if I don't find any existing object in new set then sending the delete update.
private void sendDeletePreferenceMessage(Set<AccountPreference> existingAccountPreferences, Set<AccountPreference> accountPreferencesFromRequest) {
int counter = 0;
for(AccountPreference accountPreference : existingAccountPreferences) {
for(AccountPreference accountPreference1: accountPreferencesFromRequest) {
if(accountPreference.getPreferenceType().equals(accountPreference1.getPreferenceType()) &&
accountPreference.getPreferenceValue().equals(accountPreference1.getPreferenceValue()))
counter++;
}
if(counter == 0) {
accPrefDeleteSender.send(accountPreference);
}
}
}
And also comparing the new set of objects against existing set of Objects to find the new updates that I want send as a create update
private void sendCreatePreferenceMessage(Set<AccountPreference> accountPreferencesFromRequest, Set<AccountPreference> existingAccountPreferences) {
int counter = 0;
for(AccountPreference accountPreference : accountPreferencesFromRequest) {
for(AccountPreference accountPreference1: existingAccountPreferences) {
if(accountPreference.getPreferenceType().equals(accountPreference1.getPreferenceType()) &&
accountPreference.getPreferenceValue().equals(accountPreference1.getPreferenceValue()))
counter++;
}
if(counter == 0) {
accPrefCreateSender.send(accountPreference);
}
}
}
This works perfectly but I believe this could be simplified in a better way. Any suggestion of doing this better!
You are using two AccountPreference Objects: one with id field, and another without id. I don't know if it's possible without using interfaces or abstract classes. Nevertheless, according to your comments it seems to me you're confused how to override Object.equals method, so I'll give you an example:
class MyObjectWithId {
private long id;
private String someField;
public String getSomeField() {
return someField;
}
#Override
public boolean equals(Object other){
if (this == other) return true;
if (other == null || !other.getClass().isInstance(MyObject.class)) return false;
MyObject myObject = (MyObject) other;
return this.getSomeField().equals(myObject.getSomeField());
}
}
class MyObject {
private String someField;
public String getSomeField() {
return someField;
}
#Override
public boolean equals(Object other){
if (this == other) return true;
if (other == null || !other.getClass().isInstance(MyObjectWithId.class)) return false;
MyObjectWithId myObjectWithId = (MyObjectWithId) other;
return this.getSomeField().equals(myObjectWithId.getSomeField());
}
}
As you can see, you can ask if the two different types of Object are equal (in this case, there isn't need for overriding hashCode).
Now you should be able to do the rest (regarding your original question about two Sets)
Set has a method boolean contains(Object o).
You don't need two for loops, just iterate through one Set and check if another Set contains() the object!
You also need to override the boolean equals(Object o) in the POJO!
class MyObject {
private PreferenceType preferenceType;
private String preferenceValue;
#Override
public boolean equals(Object o){
if (this == o)
return true;
if (o == null || o.getClass()o.getClass() != getClass())
return false;
if(this.getPreferenceType().equals(o.getPreferenceType()) && this.getPreferenceValue().equals(o.getPreferenceValue()))
return true;
return false;
}
}
}
Trying to realize simple task stuck into strange problem:
class User{
String login;
String pwrd;
User(String lg,String pw){
this.login=lg;
this.pwrd=pw;
}
public String toString(){
return this.login;
}
public boolean equals(String a){
return this.login.equals(a);
}
public boolean equals(User t){
return this.login.equals(t.toString());
}
}
public class Foo{
public static void main (String[] args)
{
HashMap<User,Boolean> a=new HashMap<>();
User a1=new User("asd","123"),a2=new User("asd","134");
a.put(a1,false);
a.put(a2,false);
System.out.println(a.containsKey(a2));
System.out.println(a.containsKey("asd"));
}
}
As a result I expected both containsKey checks to be true. Further in code it would be used more and more. So the first thing is to understand why it behaves so and if possible fix it. Any help appreciated.
The keys of your Map are User instances, so a.containsKey("asd") will never return true, since "asd" is a String.
BTW, you didn't override Object's equals, which expects an Object argument. This means that a.containsKey(a2) also returns false, since a1==a2 is false.
A correct implementation of equals would be :
#Override
public boolean equals(Object other){
if (!(other instanceof User))
return false;
User u = (User) other;
return this.login.equals(u.login);
}
As Andy mentioned, you must also override hashCode, so that if a.equals(b) is true then a.hashCode()==b.hashCode().
EDIT :
I think you can make a.containsKey("asd") return true if you override equals in a way that treats String instances as equal to your User instance if they match the login property :
#Override
public boolean equals(Object other){
if (other instanceof User) {
User u = (User) other;
return this.login.equals(u.login);
} else if (other instanceof String) {
String u = (String) other;
return this.login.equals(u);
}
return false;
}
#Override
public int hashCode()
{
return login.hashCode();
}
I never tried such an implementation of equals, but based on my understanding of HashMap, it might work.
However, such an implementation of equals would violate the contract of equals as defined in the Javadoc of Object, since "asd".equals(a1) will return false even though a1.equals("asd") is true.
EDIT:
After checking the implementation of HashMap, I found this implementation of equals won't work, since the code of containsKey(key) compares the key against the keys of the existing entries instead of the other way round, and String.equals(obj) will always return false if obj is not a String. I guess there's a good reason not to break the contract of equals.
You need to override public boolean equals(Object other) and inside that method check for correct type of other object passed. Please note that the object passed might be null as well.
public class User {
public boolean equals(Object other) {
//Is the same
if(other == this) {
return true;
}
//Other is a user as well - Includes null-check (thanks, Kevin!)
if(other instanceof User) {
//equal if usernames are equal
return login.equals(other.login);
}
//anything else - not equal / null, whatever
return false;
}
}
I have a java LinkedList which contains several custom objects of the same type.
LinkedList<myClass> = new LinkedList<myClass>();
Within my objects I have a specific value
class myClass(){
public int id;
}
I want to be able to return the index of the linked list for a match of a specific value, i.e: Find the LinkedList index where the object id = 7
I have looked into using indexof, contains, and containsall, but without any luck (index of always returns -1).
Is this something I can do with a prebuild libary, or am I going to have to extend my own search function for custom objects?
Override the equals method on your myClass class so the LinkedList could find the object:
public class myClass {
private int id; //it should be private, not public
//other attributes...
//getters and setters...
#Override
public void equals(Object o) {
if (o == null) return false;
if (o == this) return true;
if (o instanceof myClass) {
myClass x = (myClass)x;
return x.getId() == this.id;
}
return false;
}
}
Since you're overriding equals, you should also override the hashCode method:
#Override
public int hashCode() {
return this.id;
}
The reason for this is explained in the Object class javadoc:
Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.
Maybe you shoud simple store your objects in an HashMap<key,value>
you put an object in it as value with a key. If you want to search for an Object you just get it over the key. So for example you take your class and use the objectID as key if its unique.
HashMap<Integer, myClass> list = new HashMap<Integer, myClass>();
list.put(newId, new MyClass(newId)); //just an example!
to find it now you just need one line like this:
list.get(newId);
if the newId does not exsist it return null.
LinkedList compares Object using their equals() method. So if you want two instances of your class to be considered equal when they have the same ID, you must override the equals() method:
#Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o == null) {
return false;
}
if (o.getClass() == this.getClass()) {
return this.id == ((MyClass) o).id;
}
return false;
}
When overriding equals(), hashCode() must also be overridden, because two equals objects MUST have the same hashCode:
#Override
public int hashCode() {
return id;
}
Note that if you don't want two instances to be considered equal when they have the same ID, then you have no choice other than iterating the list and finding the first element which has the same ID as the instance you're looking for. Or you must use another data structure, like a Map<Integer, MyClass> for example.
This can be implemented in terms of List's indexOf() method, all you have to do is override equals() and hashChode() in myClass to specify that the comparisons must be made using the id attribute (here is an explanation of why you need to override both methods). Simply add this methods to myClass:
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
myClass other = (myClass) obj;
if (id != other.id)
return false;
return true;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
return result;
}
Now to find the index of an element with id == 7 do something like this:
int idx = myList.indexOf(new myClass(7));
That is, assuming that there exists a constructor in myClass that takes the id as a parameter.
You can do it like this
list.indexOf(new Object() {
#Override
public boolean equals(Object o) {
MyClass mine = (MyClass) o;
return mine.id == yourValue;
}
});