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.
Related
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
So, I have two Sets with elements of my class Capability.
public class Capability {
private String name;
public Capability(){
//
}
public Capability(String name){
this.name = name;
//this.id = count.getAndIncrement();
}
public String getName(){
return name;
}
#Override
public String toString(){
return "Capability: "+name+".";
}
}
Please disregard the value of this class over a String, this is for future expansion.
I'm trying to compare two sets that I've gotten from importing a json file, so they are not the same object, nor contain the same object, just have the same content.
public boolean allCapabilitiesMet(){
int count = 0;
for(Capability taskCap : this.getReqCapabilities()){
for(Capability primCap : this.getPrimitive().getCapabilities())
{
System.out.println(taskCap.equals(primCap));
System.out.println(taskCap.getName().equals(primCap.getName()));
if(taskCap.equals(primCap)){
count++;
}
}
}
return count == this.getReqCapabilities().size();
//return this.getPrimitive().getCapabilities().containsAll(this.getReqCapabilities());
}
The goal is to see if one set is a subset of the other, which I could do with the commented return before I switched to importing from the json file.
The thing is, I could fix this right now by simply changing the if clause to the string comparison, because that does indeed work. This would be terrible once I start adding other fields to the main class.
Is there anything I can do to compare the sets content without manually checking their content?
So I just replaced the equals() and hashCode() methods in Capability after adding an id field.
#Override
public boolean equals(Object obj){
if(this == obj)
return true;
if(obj == null || obj.getClass() != this.getClass())
return false;
Capability cap = (Capability) obj;
return (cap.getName().equals(this.getName()) && cap.getId() == this.getId());
}
#Override
public int hashCode()
{
return (int) this.id;
}
With this, I can use the solution that I'd originally planned for the comparison
public boolean allCapabilitiesMet(){
return this.getPrimitive().getCapabilities().containsAll(this.getReqCapabilities());
}
Is there any issue with this implementation? Sadly, I'll have to add a term to the if statement everytime I want to add a field to Capability. Is there any other way?
I have created a Employee class with 3 parameters.
Id
Name
Age
Requirement: Search based on Name. This is a case where all employees have a unique name. Its mandatory to add the objects with key as id. There are rare cases where it is required to search based on name.
What i have done :
Within the class I am overriding hashCode and Equals method.
I am adding a list of these objects into the hashmap with id as key and value as Employee object
But while adding or searching from a hashmap both the methods do not get called
So what is the use of these methods in terms on hasmap?
Employee Class
public class Employee {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int hashCode() {
return name.hashCode();
}
public boolean equals(Employee emp) {
if (emp == null)
return false;
else if (emp.name.equalsIgnoreCase(this.name))
return true;
else
return false;
}
}
Main Method:
public class HashMapTest {
public static void main(String[] args) {
Employee emp1=new Employee();
emp1.setId(1);
emp1.setName("Maclean");
emp1.setAge(24);
Employee emp2=new Employee();
emp2.setId(2);
emp2.setName("Sampath");
emp2.setAge(25);
Employee emp3=new Employee();
emp3.setId(3);
emp3.setName("Achar");
emp3.setAge(27);
Employee emp4=new Employee();
emp4.setId(4);
emp4.setName("Sudheer");
emp4.setAge(25);
Employee emp5=new Employee();
emp5.setId(5);
emp5.setName("Kunder");
emp5.setAge(25);
HashMap<Integer, Employee> empmap=new HashMap();
empmap.put(emp1.getId(), emp1);
empmap.put(emp2.getId(), emp2);
empmap.put(emp3.getId(), emp3);
empmap.put(emp4.getId(), emp4);
empmap.put(emp5.getId(), emp5);
Employee emp=new Employee();
emp.setName("Maclean");
System.out.println(empmap.containsValue(emp));
System.exit(1);
}
}
Update Solution:
Thanks for all the answers.
1. hashCode method gets called only if the Key is a object and the method exists within the Key Class
2. Equals(Employee emp) is causing function overloading instead of overriding. I should have used equals(Object o)
Changes in the code to resolve the issue
#Override
public boolean equals(Object o) {
if (o == null)
return false;
if (!(o instanceof Employee))
return false;
Employee emp = (Employee) o;
if (emp.name.equalsIgnoreCase(this.name))
return true;
else
return false;
}
You are not overriding Object.equals(Object o), which you need to do. You are overloading it. That's why it's not being called.
Try this equals() instead:
public boolean equals(Object o) {
if (o == null)
return false;
if (!(o instanceof Employee))
return false;
Employee emp = (Employee) o;
if (emp.name.equalsIgnoreCase(this.name))
return true;
else
return false;
}
Within the class I am overriding hashCode and Equals method.
[...]
But while adding or searching from a hashmap both the methods do not get called
So what is the use of these methods in terms on hasmap?
If you have a Map<Key, Value>, and you call put or get on that map, then hashCode and equals are called on the Key class, not on the Value class.
In your case, that means that if you do empmap.put(emp1.getId(), emp1); then it checks the hash of emp1.getId() and whether that's already in the map. So it's normal that those methods are not called on your Employee class.
Also, if id is the "unique" attribute, then Employee.hashCode should probably be based on that (and equals, too, to be consistent with hashCode), and as noted in another answer, Employee.equals should accept any Object as parameter.
I did not test, but try it with
HashMap<Integer, Employee> empmap=new HashMap<>();
or even
HashMap<Integer, Employee> empmap=new HashMap<Integer, Employee>();
This can be done very nicely in Java 8 using streams.
empmap.values().stream().anyMatch(emp.getName().equals(searchedName));
This takes the set of all entries in the map and see if the stream matches any entry that has a name equal to your searchedName.
In a comparable you can also fetch all matching names by using Stream.filter()
Implementing a different version of equals/hashcode is tricky because it changes the behavior of the class in many ways.
The easiest solution for your problem is to add this method to the Employee class.
Silently, HashMap uses the equals(Object o) of a value object (in this case Employee) for checking the existence of that object.
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
if (name != null ? !name.equals(employee.name) : employee.name != null) return false;
return true;
}
Be careful, This implementation of equals(Object o) just works on the name and it does not check other fields.
Employee emp=new Employee();
emp.setId(10);
emp.setName("Maclean");
emp.setAge(240);
System.out.println(empmap.containsValue(emp));
System.out.println(emp1.equals(emp));
is
true
true
I have list (array list)that can contain many instances (between 500-3000 instances ).
during the program some function need to access to this list (many times)and search for specific instance or more ,to get the instance\s they need loop on the list and provide parentName and name (which is string) and are not uniqe key .
my question is since the list need to be accessed many time there is a way to define/design it better that the access to the list can be more efficient?
Please keep in mind that the functions that need to get instance/s from the list
cannot provide full key the can provide only name and parentName which can have more that one instance.
List<Obj> myList = new ArrayList<Obj>();
class obj
{
parentName
Name
type
curr
....
Use a Map<MyEntry, List<Obj>> where MyEntry is a class enclosing parent name and name as such:
public final class MyEntry
{
private final String parentName;
private final String name;
private final int hashCode;
public MyEntry(final String parentName, final String name)
{
this.parentName = parentName;
this.name = name;
hashCode = 31 * parentName.hashCode() + name.hashCode();
}
// Override .equals() and .hashCode()
#Override
public int hashCode()
{
return hashCode;
}
#Override
public boolean equals(final Object o)
{
if (this == o)
return true;
if (o == null)
return false;
if (getClass() != o.getClass())
return false;
final MyEntry other = (MyEntry) o;
return parentName.equals(other.parentName)
&& name.equals(other.name);
}
// Have a nice string representation
#Override
public String toString()
{
return "parent name: " + parentName + ", name: " + name;
}
}
You can, for instance, have a method in your Obj which returns the matching MyEntry object. Also, if you use Guava, have a look at MultiMap.
You will notice that the hash code is precomputed: this can be done since the MyEntry class is immutable. This allows for very fast usage as keys for a Map.
(edit: added .toString())
I have a Swing application with a JList that works with a custom listmodel.
The model has an ArrayList to store the objects used. The situation is as follows:
Upon closing the application, all objects in the listmodel are serialized (in the default way, the class just implements Serializable), and written to a file via an ObjectOutputStream. When the application starts up, all objects are read from the file and stored in my custom ListModel again.
I'm trying to add a feature to let the user import objects too, from a file he specifies. A static method in another class reads all objects from the file, and returns them in an ArrayList. I then use the for-each loop in my MainForm class to store each object from the returned ArrayList in the ListModel. In the loop, I want to check whether the ListModel already contains a certain object, but this does not work.
In code, I'm doing the following:
for(MyObject o: readObjects) {
if(!myListModel.contains(o)) //listmodel just calls contains() on its ArrayList
myListModel.addElement(o);
}
However, even when the object is already in the ArrayList (I keep importing objects from the same file), they are still added.
The question is, how come objects are not equal anymore when deserialized, and is there a way to compare them anyway?
Here is a simple java object
public class MyObject {
private String firstname;
private String lastname;
private String email;
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((firstname == null) ? 0 : firstname.hashCode());
result = prime * result
+ ((lastname == null) ? 0 : lastname.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;
MyObject other = (MyObject) obj;
if (firstname == null) {
if (other.firstname != null)
return false;
} else if (!firstname.equals(other.firstname))
return false;
if (lastname == null) {
if (other.lastname != null)
return false;
} else if (!lastname.equals(other.lastname))
return false;
return true;
}
}
(hashcode and equals auto generated by eclipse)
If you look at equals() method, you will see that 2 objects are compared on the fields firstname and lastname, and only those 2. This means that if you insert objects of this type in a list, you will be able to use contains(Object o) and if an object contains the same firstname and lastname, you'll find it.
You need to override the equals and hashcode method of your object (many examples are available on SO, for example here.
Once you have done that, the contains method of List will behave as you expect. Your problem does not seem related to serialization. In other words, the following code:
List<MyObject> list = new ArrayList<MyObject>();
list.add(new MyObject());
System.out.println(list.contains(new MyObject()));
will print false if you don't override equals & hashcode whereas it might print true if you do (depending on whether your equals method consider those 2 new MyObject() to be equals or not). Your IDE should have a way to auto generate the code for you.
Once you are happy that the contains method works as expected on your objects, serialising/deserialising should work as expected.