Custom hashcode/equals operation for HashMap - java

Is there a an implementation of the HashMap class (or Map interface) that will allow me to use alternate hashcode and equals operations...
Similar to how collections of the same type can be sorted in multiple ways using the Comparator in Collections.sort(list, comparator).
I would like to avoid if possible, creating a key wrapper providing the desired hashcode and equals operations.
In my case, one of the scenarios why I need something like this:
In my web application, for each request, I load the Location/ISP and other data.
In different parts of code (in my service and repository layers) I have "minimized" caches specific to its requirement.
Here is a simplified code example:
class GeoIpData{
private String countryName;
private String state;
private String city;
private String isp;
#Override
public int hashCode() {
//countryName hashCode
//state hashCode
//city hashCode
//isp hashCode
}
#Override
public boolean equals(Object obj) {
// compare countryName
// compare state
// compare city
// compare isp
}
}
Map<GeoIpData,#Type1> fullCache = ... //This cache needs to be unique per countryName,state,city and isp
Map<GeoIpData,#Type2> countryCache = ... //This cache needs to be unique per countryName
Map<GeoIpData,#Type2> ispCache = ... //This cache needs to be unique per countryName,isp
To achieve this the above 3 maps need 3 different hashcode and equals methods.
fullCache:
hashCode -> GeoIpData.hashCode();
equals -> GeoIpData.equals(Object obj);
countryCache:
hashCode -> {countryName hashCode }
equals -> {compare countryName }
ispCache:
hashCode -> {countryName hashCode & isp hashCode }
equals -> {compare countryName & compare isp hashCode }

GNU Trove allows you to provide specific TObjectHashingStrategy with your own hash and equals functions for TCustomHashMap.

Initial Java API bounded equals / hashCode behavior to type (class) because they didn't have lambdas back those days.
In order to reuse existing API / implementations - make ephemeral keys:
public CountryKey {
private String country;
#Override
public boolean equals(Object obj) {
if (!(obj instanceof CountryKey)) { return false; }
CountryKey that = (CountryKey) obj;
return this.country.equals(that.country);
}
#Override int hashCode() {
return country.hashCode();
}
}
or wrappers:
public CountryKey {
private GeoIpData holder;
#Override
public boolean equals(Object obj) {
if (!(obj instanceof CountryKey)) { return false; }
CountryKey that = (CountryKey) obj;
return this.holder.getCountry().equals(that.holder.getCountry());
}
#Override int hashCode() {
return holder.getCountry().hashCode();
}
}
and write helper constructors for keys:
public class GeoIpData {
public CountryKey buildCountryKey() {
return new CountryKey(this.country);
}
}

Related

Writing an hashcode function

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!

ArrayList removeAll() not removing objects

I have the simple ArrayLists of the member class:
ArrayList<Member> mGroupMembers = new ArrayList<>();
ArrayList<Member> mFriends = new ArrayList<>();
Member class:
public class Member {
private String userUID;
private String userName;
public String getUserUID() {
return userUID;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setUserUID(String userUID) {
this.userUID = userUID;
}
}
The ArrayList for friends contains all the users friends. What I simply wish to do is remove from the friends list, group members if present with:
mFriends.removeAll(mGroupMembers);
Yet it does nothing to the mFriends list...
Looking at the log statements, the friend does in fact appear within the mGroupMember list.
Why doesn't this work?
How are 2 members determined to be equal? I'm guessing if they have the same ID, you deem them equal, however java wants them to be the exact same reference in memory which may not be the case. To correct for this you can override the equals function to have it return if the ids are equal:
public class Member {
//..
#Override
public boolean equals(Object anObject) {
if (!(anObject instanceof Member)) {
return false;
}
Member otherMember = (Member)anObject;
return otherMember.getUserUID().equals(getUserUID());
}
}
Also when you override .equals it is recommended to also override hashCode so that the objects also work correctly in hashing functions like Set or Map.
You have to understand the sequence of invocation underneath it
-> ArrayList#removeAll(Collection)
|
| calls
|
|--> ArrayList#contains(Object)
|
| calls
|
|--> ArrayList#indexOf(Object)
|
| calls
|
|--> Object#equals
So if equals is not correctly overridden (following the equals contract rules), you're not getting the correct behaviour.
As mentioned in the comments, elements from the ArrayList will only be removed if their equals() method returns true. The non-overridden method checks for equality based on reference (i.e. they must be the same object in memory).
What you probably want is to override equals to be based on the properties of Member, such as in the example below:
#Override
public void equals(Object o) {
if(o == null) {
return false;
} else if (!(o instanceof Member)) {
return false;
} else {
return ((Member) o).getUserUID().equals(this.userUID) && ((Member) o).getUserName().equals(this.userName);
}
}
In addition, you should override hashCode() when overriding equals() so that when two objects are equal, they have the same hash code. The non-overriden implementation of hashCode is also based on equality by reference.

Java HashMap containsKey strange behavior

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;
}
}

Java indexof() search

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;
}
});

Is it proper equals method for my class Java

As in subject: Is it proper equals method for my class Java? I have generated it automaticly by Eclipse I don't know if it makes vector.remove(pracownik) will work correctrly. Or is it wrong to generate it by Eclipse?
import java.util.Date;
import java.util.Vector;
public class Pracownik extends Osoba {
private String stanowisko;
private int pensja;
private Date dataZatrudnienia;
public Pracownik(Adres adres, String telefon, String imie, String nazwisko,
int id, Date dataUrodzenia, String stanowisko, int pensja,
Date dataZatrudnienia) {
super(adres, telefon, imie, nazwisko, id, dataUrodzenia);
this.stanowisko = stanowisko;
this.pensja = pensja;
this.dataZatrudnienia = dataZatrudnienia;
}
public String getStanowisko() {
return stanowisko;
}
public int getPensja() {
return pensja;
}
public Date getDataZatrudnienia() {
return dataZatrudnienia;
}
#Override
public String toString() {
return super.toString() + "\nstanowisko=" + stanowisko + "\npensja="
+ pensja + "\ndataZatrudnienia=" + dataZatrudnienia;
}
private static Vector<Pracownik> ekstensja = new Vector<Pracownik>();//kolekcja zawierajaca ekstensje
private static void dodajPracownik(Pracownik pracownik) { //metoda dodajac aobiekt do ekstensji
ekstensja.add(pracownik);
}
private static void usunPracownik(Pracownik pracownik) {//metoda usuwajaca obiekt z ekstensji
ekstensja.remove(pracownik);
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Pracownik other = (Pracownik) obj;
if (dataZatrudnienia == null) {
if (other.dataZatrudnienia != null)
return false;
} else if (!dataZatrudnienia.equals(other.dataZatrudnienia))
return false;
if (pensja != other.pensja)
return false;
if (stanowisko == null) {
if (other.stanowisko != null)
return false;
} else if (!stanowisko.equals(other.stanowisko))
return false;
return true;
}
private static void pokazEkstensje(){ //wyswietlenie ekstensji przy pomocy petli for each
System.out.println("Ekstensja klasy Pracownik");
for(Pracownik pracownik: ekstensja)
System.out.println(pracownik);
System.out.println();
}
public static void main(String[] args){
Adres adres = new Adres("tara", "588 m.42", "03-422", "Warszawa");
Pracownik pracownik = new Pracownik(adres, "02-6451-4564", "Ala", "Kotowa", 323, new Date(), "szef", 14000, new Date()); //tworze pracownika
System.out.println(pracownik);//wyswietlam pracowanika
//tworze stazyste
Stazysta stazysta = new Stazysta(adres, "3232 9898", "frajer", "costam", 3232, new Date(), "podawanie kawy", 0, new Umowa(new Date(2010,10,5), new Date(2011,11,8)));
//wysswietlam stazyste
System.out.println(stazysta);
}
}
Generating the equals method using Eclipse is fine. It is important to make sure a field is included in the generation if, and only if, it affects logical equality of your objects.
When you override equals, you should also override hashCode.
In general, when overriding inherited methods you need to ensure the new methods conform to any rules stated in the superclass. The Object hashCode documentation states several rules, including "If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result."
The hashCode you inherit from Object follows that rule - if the equals method is also the one inherited from Object. It does not follow that rule when used with your equals method.
If you let Eclipse do its "Generate hashCode() and equals()" thing, it will get it right. If you write an equals method manually, you need to write your own hashCode to match.
As a practical matter, a class that does not follow the Object hashCode contract is a trap for future reuse. Hashed data structures, such as HashMap and HashSet, may fail to find an object that is actually present if it has a broken hashCode method. One lesson I've learned the hard way is that it is a mistake to depend on "I'll never use this that way.". It is much better to keep things safe as one goes along.
I am not expert on this but I believe double equals are comparing address rather than content of a variable. So, you may change it to .equals(). I hope someone with much more experience correct me if I am wrong.

Categories

Resources