0

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?

MarioDS
  • 12,895
  • 15
  • 65
  • 121
  • 1
    Are you overriding `equals()` method in your object class? – Eric C. Apr 17 '12 at 15:21
  • @MarioDeSchaepmeester if you don't override equals, contains will test for reference equality (default behavior of equals in Object class). Even without using serialization, if you create 2 objects with the same fields, your code above will not do what you expect (try for example: `list.add(new MyObject());` and `list.contains(new MyObject())` will return false). – assylias Apr 17 '12 at 15:23
  • Yes, when deserialized, your object are recreated and have a new reference. If you don't override `equals()` method, it is the reference that is just checked. (I think) – Eric C. Apr 17 '12 at 15:24
  • @assylias, Can either of you explain what should be done in order to compare 2 objects successfully when overriding the methods? Any best practice? I've seen people put serialUID numbers or something in their classes, is that what I should use? I haven't used such thing before... – MarioDS Apr 17 '12 at 15:26
  • I don't think serialUID has anything to do with that. If you are using an IDE like eclipse, you can ask it to auto-override `equals()`. Basically, you have to choose which fields of the object are used to compare two objects. – Eric C. Apr 17 '12 at 15:34

2 Answers2

2

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.

Eric C.
  • 3,310
  • 2
  • 22
  • 29
  • Thank you, that's clear. but, wouldn't it be better to have a random-generated ID as `long` for each object? Because my object has first name and last name too, but it's not because 2 people coincidentially have the same first AND last name, they are the same people... Will it work when the object is another object but has the same first and last names? – MarioDS Apr 17 '12 at 16:36
  • 2
    @MarioDeSchaepmeester You need to write an equals method that gives you the desired behavior - it is a design decision. If 2 different people can have the same name and equals should therefore return false, you need to add a unique identifier to your class which the equals method would use to test for equality instead of the name. – assylias Apr 17 '12 at 16:43
1

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.

Community
  • 1
  • 1
assylias
  • 321,522
  • 82
  • 660
  • 783