This question already has answers here:
Why do I need to override the equals and hashCode methods in Java?
(31 answers)
Closed 8 months ago.
How can I avoid inserting duplicate elements in a Set? If I have:
Set<User> user=new HashSet<>();
User user1=new User("11","Mark",null,"1");
User user2=new User("11","Mark",null,"1");
User user3=new User("12","Helen",null,"2");
user.add(user1);
user.add(user2);
Log.d("main_activity_user", "la dimensione è" +String.valueOf(user.size()));
Adn User class is:
public class User {
public String uid;
public String name;
public String pversion;
public String upicture;
public User(String uid,
String name,
String upicture, String pversion ){
this.uid=uid;
this.name=name;
this.upicture=upicture;
this.pversion=pversion;
}
public String get_uid(){
return uid;
}
public String get_name(){
return name;
}
public String get_pversion(){
return pversion;
}
public String get_upicture(){
return upicture;
}
#Override
public boolean equals(Object obj) {
User newObj = (User)obj;
if (this.get_uid().equals( newObj.get_uid()))
return true;
else
return false;
}
}
Now the Set also stores duplicates and prints me 3 elements instead of two. Why?
I have never used the Set class before and I don't understand it. So, every time I use the Set class, do I have to Override the Equals method? Why? Doesn't the class delete duplicates automatically?
As it has been already said in the comments, your User class needs to honor the hashcode and equals contracts by overriding the equals() and hashCode() methods.
https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#hashCode()
In your code, you're using a HashSet which is implemented as a HashMap under the hood. Instead, a HashMap is implemented as an array of buckets, where each entry is stored within a bucket based on the key's hashCode(). However, different keys may yield same hashcodes, so multiple entries may be listed within a same bucket. At that point, the HashMap has to resort to the equals() method to find the exact key within a bucket which corresponds to the inputted entry in order to retrieve or replace an element. This brief explanation shows you why it is so crucial to provide a proper definition of the hashCode() and equals() methods, because, as you could see, a HashMap heavily relies on these methods.
https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html
Here is a proper implementation of your User class where two users are said identical if they have same: uid, name, pversion and upicture. Instead, if two users are identical only by some of the mentioned fields, then you need to updated your equals() and hashCode() methods accordingly (they both must be based on the same fields).
public class User {
public String uid;
public String name;
public String pversion;
public String upicture;
public User(String uid, String name, String upicture, String pversion) {
this.uid = uid;
this.name = name;
this.upicture = upicture;
this.pversion = pversion;
}
public String getUid() {
return uid;
}
public String getName() {
return name;
}
public String getPversion() {
return pversion;
}
public String getUpicture() {
return upicture;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(uid, user.uid) && Objects.equals(name, user.name) && Objects.equals(pversion, user.pversion) && Objects.equals(upicture, user.upicture);
}
#Override
public int hashCode() {
return Objects.hash(uid, name, pversion, upicture);
}
}
Test Main
public class Main {
public static void main(String[] args) {
Set<User> user = new HashSet<>();
User user1 = new User("11", "Mark", null, "1");
User user2 = new User("11", "Mark", null, "1");
User user3 = new User("12", "Helen", null, "2");
user.add(user1);
user.add(user2);
System.out.println("la dimensione è: " + user.size());
}
}
Output
la dimensione è: 1
Related
I have a problem with a list of object in my Java code.
This object is populated with the following fields :
-name
-lastname
-birthdate
-car accident date
Now, this list could countain some object with same values (name,lastname,birthdate)
For example :
Luke skywalker 09/10/1970 10/10/2008
Luke skywalker 09/10/1970 10/10/2009
my goal is to remove the duplicated user and add in a new list only the one with the latest car accident for each user.
In the previous example :
Luke skywalker 09/10/1970 10/10/2009
Do you guys have any idea?
If you let your user class implement Comparable then you could use a SortedSet:
class User implements Comparable<User> {
// fields, constructor, methods etc...
#Override
public void compareTo(User other) {
// assuming carAccidentDate is comparable
return carAccidentDate.compareTo(other.carAccidentDate);
}
#Override
public boolean equals(Object other) {
return /* true if name, lastname and birthdate are equal */
}
#Override
public int hashCode() {
return /* hash code of name, lastname and birthdate */
}
}
I omitted the equals and hashCode implementation for simplicity, but they are also required for this to work.
Then simply create your SortedSet from the existing List, I use TreeSet:
Set<User> latestAccidentsUser = new TreeSet<>(users);
For this question, we have 2 requirements.
Remove duplications.
Keep the lastest car accident.
For requirement 1:
We have HashSet to remove duplication. In order to do that, we have to override equals() and hashcode(), since the default ones are based on the object memory location, while this problem is based on values(name, Lastname, birthdate).
For requirement 2:
First, sort the Users in descending order based on the accident date. And we will have the lastest car accident in the front among the duplicates. And for duplicates will not be added into HashSet since the user has already existed.
The code is as follows:
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
class User {
String name;
String lastname;
String birthdate;
String carAccidentDate;
public User(String name,
String lastname,
String birthdate,
String carAccidentDate){
this.name = name;
this.lastname = lastname;
this.birthdate = birthdate;
this.carAccidentDate = carAccidentDate;
}
#Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
User o = (User) other;
return o.name.equals(name) && o.lastname.equals(lastname)
&& o.birthdate.equals(birthdate);
}
#Override
public int hashCode() {
return Objects.hash(name, lastname, birthdate);
}
#Override
public String toString(){
return String.join(",", name,lastname,birthdate,carAccidentDate);
}
public static void main(String input[]) {
DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
User[] users = new User[]{
new User("Luke", "skywalker", "09/10/1970", "10/10/2008"),
new User("Luke", "skywalker", "09/10/1970", "10/10/2009")
};
Arrays.sort(users, (B, A) -> {
try {
return dateFormat.parse(A.carAccidentDate).compareTo(dateFormat.parse(B.carAccidentDate));
} catch (ParseException e) {
e.printStackTrace();
}
return 0;
});
Set<User> userSet = new HashSet(Arrays.asList(users));
for(User u: userSet){
System.out.println(u.toString());
}
}
}
class Details{
String name;
String age;
String email;
String location;
}
1) If there is List of Details as in List<Details> how to verify for a combination of name and email collectively unique. (i.e) For a single email address there cant be two name entry.
2) How to verify the combination of all fields in the class file is unique.
what would be a perfect data structure to address this ?.
You can hash values by a separator like #, and then find that all uniques or not. Hash value for a Details is name + "#" + "email in the first case, and is name + "#" + age + "#" + email + "#" + location in the second case.
You can using Hashmap to find duplicates if there is any with the specified key (or hash) for each instance of Details.
If you need to achieve unique values-only, you should use Set. You have to use your own equals & hashCode implementation, for example, for case 1):
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Details details = (Details) o;
return Objects.equals(name, details.name) &&
Objects.equals(email, details.email);
}
#Override
public int hashCode() {
return Objects.hash(name, email);
}
If you need all Details members to be unique just update hash & equals implementation with needed properties.
When you overrides an equals method within any object, you can perfectly check the equality of that Object with another one even though they are residing somewhere different within the memory.
So the below code
myList.contains(myObject);
will respond if there is an object within myList that the equals method return true with myObject.
In all major IDEs (like intelliJ, netbeans, eclipse, etc) the IDE will help you to override the equals method accurately. here is the auto-generated code for overriding equals method using intelliJ IDE
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Details details = (Details) o;
return Objects.equals(name, details.name) &&
Objects.equals(email, details.email);
}
Now if you want to avoid duplicate to be added inside your List, you should use Set instead of a List. A Set will add a new element if the combination of hashCode and equals method are different (comparing the existing object and newly intended to add object)
So we have to override hashCode method (generated by intelliJ IDE) if we want to use a flavor of Set class:
#Override
public int hashCode() {
return Objects.hash(name, email);
}
now if you create a Set and try to add two objects with similar name and email inside that Set, the set will only add the first unique name and email address, even thou the other fields of the second object have different values
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
class Details {
String name;
String age;
String email;
String location;
public Details(String name, String age, String email, String location) {
this.name = name;
this.age = age;
this.email = email;
this.location = location;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Details details = (Details) o;
return Objects.equals(name, details.name) &&
Objects.equals(email, details.email);
}
#Override
public int hashCode() {
return Objects.hash(name, email);
}
#Override
public String toString() {
return String.format("(name: %s, age: %s, email: %s, location: %s)", name, age, email, location);
}
public static void main(String[] args) {
Set<Details> mySet = new HashSet<>();
mySet.add(new Details("Mehdi", "12", "123#xyz.com", "Los Angeles"));
mySet.add(new Details("Mehdi", "34", "123#xyz.com", "Las Vegas"));
System.out.println(mySet);
}
}
This is the whole test app. There is something else that worth mentioning. if in any case you have saved your data inside a list and you want to remove the duplicate based on the rules you have (ex name, email) You can try this approach:
Set<Details> mySet = new HashSet<>(myList);
Which myList is your list. so in your app that will be like this:
public static void main(String[] args) {
List<Details> myList = new ArrayList<>();
myList.add(new Details("Mehdi", "12", "123#xyz.com", "Los Angeles"));
myList.add(new Details("Mehdi", "34", "123#xyz.com", "Las Vegas"));
Set<Details> mySet = new HashSet<>(myList);
System.out.println(mySet);
}
and here is the result without any duplicate:
[(name: Mehdi, age: 12, email: 123#xyz.com, location: Los Angeles)]
Besides of using a hash as propposed by #OmG, you could also use a TreeSet with the key being a concatenation of the unique fields, also using a separator between them.
A Set only admits unique keys.
I have a custom class as my key in my hashmap like so
// In the main function
HashMap<Drink, boolean> drinkMap = new HashMap<>();
// What I would like to be able to do:
drinkMap.get("beer");
// My drink Class which is used as the key
public class Drink implements Comparable<String> {
private String name;
private String info;
public String getName() {
return Name;
}
public Drink(String name, String info) {
this.name = name;
this.info = info;
}
}
What I want to do is have the get method for the hashmap compare the string that is passed in to Drink.name and if they are the same then return that hashmap entry, but I cannot figure out how to get this to work.
I tried implementing the equals and hashcode methods in my Drink class like so:
#Override
public int hashCode() {
return Name.hashCode();
}
#Override
public boolean equals(Object o) {
return o instanceof String && o.equals(Name);
}
But when I would do hashMap.get("beer") it kept returning null even though I know there exists a Drink object with the name "beer" in the map.
This is a terrible idea. You should always query a map with the same type (or a subtype thereof) as the intended key. Not doing that only opens you up to problems (as I'm sure you've started to notice).
You should consider either making the key of your map a String type, or querying your map by Drink.
(As to why your specific case isn't working: "beer".equals(drink) != drink.equals("beer").)
I just want to remove duplicate elements from list. To do this I have written a POJO class Student as :
class Student{
private String roll;
private String name;
public Student(String roll, String name) {
this.roll = roll;
this.name = name;
}
#Override
public boolean equals(Object obj) {
Student st = (Student) obj;
return st.getRoll().equals(roll);
}
public String getRoll() {
return roll;
}
public void setRoll(String roll) {
this.roll = roll;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return roll ;
}
}
And a test class as below :
public class TestMain {
public static void main(String[] args) {
List<Student> list = Arrays.asList(
new Student("19", "goutam kumar singh"),
new Student("20", "goutam kumar singh"),
new Student("11", "goutam kumar singh"),
new Student("19", "goutam kumar singh")
);
List<Student> arrayList = new CopyOnWriteArrayList<>(list);
Set<Student> set = new HashSet<>();
for(Student st : arrayList)
set.add(st);
System.out.println(set);
}
}
but in the output all the four elements in the set but i am expecting only three element as fourth element is duplicate and must be removed.
Where I am going wrong?
You have to override the hashCode() method too. Override hashCode() methods for those property for which you override equals() method.
While working with Collection it's useful to remember the contract between hashCode() and equals() method -
1. If two objects are equal, then they must have the same hash code.
2. If two objects have the same hashcode, they may or may not be equal.
For more information you may visit this link
A HashSet stores elements internally as keys in a HashMap. Because of this, it will use your Student object as the keys for that map, using the hash code for each object. Since you don't provide an implementation for this method hashCode(), the default one from Object is used and each of your students will have a different hash code.
You must extend this method in your class, being aware of the equals-hashCode contract. If two objects are equal, they must have the same hashCode (the reverse isn't allways true). For further details see this Object.hashCode()
I'm making a web app that queries an SQL db. I'm under the impression that I need to use entity classes and facade classes to allow persistence - across the whole site. The entity class templates have hashcodes and 1.) Im not sure if I need them and 2.) If I do, they want int's but all I have are String so, how to convert them to int and then back to String? Because I need the String value to appear on the site and the hash wants int's.
heres the code (imports have been remove to protect the innocent...):
#Embeddable
public class ComputerOwnersPK implements Serializable {
#Basic(optional=false)
#NotNull
#Column(name="Computer_Name")
private int computerNameId;
#Basic(optional=false)
#NotNull
#Column(name="User_ID")
private int userId;
public ComputerOwnersPK() {
}
public ComputerOwnersPK(int computerNameId,int userId) {
this.computerNameId=computerNameId;
this.userId=userId;
}
public int getComputerNameId() {
return computerNameId;
}
public void setComputerNameId(int computerNameId) {
this.computerNameId=computerNameId;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId=userId;
}
#Override
public int hashCode() {
int hash=0;
hash+=(int) computerNameId;
hash+=(int) userId;
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if(!(object instanceof ComputerOwnersPK)) {
return false;
}
ComputerOwnersPK other=(ComputerOwnersPK) object;
if(this.computerNameId!=other.userId) {
return false;
}
if(this.userId!=other.userId) {
return false;
}
return true;
}
#Override
public String toString() {
return "entity.ComputerOwnersPK[ computerNameId="+computerNameId+", userId="+userId+" ]";
}
}
Based on your comments I'm assuming you want computerNameId and userId to be Strings in your mapping and you have them mapped to ints because you don't know how to do the hashcode stuff.
In your hashCode method you should be able to concatenate the strings and then call hashcode on them. Very similar to what you are already doing.
private String computerNameId;
private String userId;
#Override
public int hashCode() {
// concatenate the interesting string fields
// and take the hashcode of the resulting String
return (computerNameId + userId).hashCode();
}
Make sure in your equals method you also change from != operators to !.equals method call for checking equality. Finally make sure you are keeping the contract between equals and hashCode or you could be in for some nasty surprises. Two objects that are equal must also have the same hashCode. Two objects that have the same hashCode may or may not be equal.