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.
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?
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 POJO something like the one mentioned below. Here I'm referring Set collection attribute in POJO1. I understand that set does not contain duplicate. Do I need to override equals() and hashCode() methods in POJO2? Using a Set here is not really going to helpful unless we override equals and hashCode methods? Please help me to understand little bit more on this context!
public class POJO1 {
private String name;
private Set<POJO2> pj2;
public Company(){
pj2 = new HashSet<>();
}
//setter and getter methods
}
Yes the only way for Java to understand which objects are duplicates is to call equals() method. Default implementation of equals() checks that references of two objects point to the same location in memory.
But depending on exact implementation of your Set you might need to override hashCode/equals or implement Comparable interface.
Since you put objects of POJO2 into HashSet you need to verride hashCodeequalsmethods inPOJO2` class.
You do like this
import java.util.Set;
public class POJO1 {
private String name;
private Set<POJO2> pojo2;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<POJO2> getPojo2() {
return pojo2;
}
public void setPojo2(Set<POJO2> pojo2) {
this.pojo2 = pojo2;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
POJO1 pojo1 = (POJO1) o;
if (name != null ? !name.equals(pojo1.name) : pojo1.name != null) return false;
return pojo2 != null ? pojo2.equals(pojo1.pojo2) : pojo1.pojo2 == null;
}
#Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (pojo2 != null ? pojo2.hashCode() : 0);
return result;
}
}
Learn more at https://docs.oracle.com/javase/10/docs/api/java/lang/Object.html#equals(java.lang.Object)
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())