Monday, May 20, 2013

Equals, HashCode and toString in Java

You probably know that equals, hashCode and toString methods are very important in Java. And they must be carefully implemented. Joshua Bloch has written about this in his famous book "Effective Java (2nd Edition)", items 8 and 9.

Let's create simple bean object and implement the mentioned above methods:
package org.halyph;

import java.util.List;

public class StandardBean {
    private String name;
    private int id;
    private List<StandardBean> references;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public List<StandardBean> getReferences() {
        return references;
    }

    public void setReferences(List<StandardBean> references) {
        this.references = references;
    }

    @Override
    public String toString() {
        return "StandardBean{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", references=" + references +
                "} " + super.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        StandardBean that = (StandardBean) o;

        if (id != that.id) return false;
        if (name != null ? !name.equals(that.name) : that.name != null) return false;
        if (references != null ? !references.equals(that.references) : that.references != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + id;
        result = 31 * result + (references != null ? references.hashCode() : 0);
        return result;
    }
}

These three methods (equals, hashCode and toString) were generated by IDE. But, we have one drawback - their maintenance, i.e. we should carefully regenerate/update them. Also, readability is not very good. There are several alternative ways to solve this issue.

1. Apache Commons Lang
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;

public class ApacheCommonsBean {
    
    ...

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("name", name)
                .append("id", id)
                .append("references", references)
                .toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ApacheCommonsBean that = (ApacheCommonsBean) o;

        return new EqualsBuilder()
                .appendSuper(super.equals(o))
                .append(name, that.name)
                .append(id, that.id)
                .append(references, that.references)
                .isEquals();
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder(31, 17)
                .appendSuper(super.hashCode())
                .append(name)
                .append(id)
                .append(references)
                .toHashCode();
    }

}
2. Google Guave
import com.google.common.base.Objects;

public class GuavaBean {

    ...

    @Override
    public String toString() {
        return Objects.toStringHelper(this)
                .add("name", name)
                .add("id", id)
                .add("references", references)
                .toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null) return false;

        if (o instanceof GuavaBean) {
            final GuavaBean other = (GuavaBean) o;
            return Objects.equal(name, other.name)
                    && Objects.equal(id, other.id)
                    && Objects.equal(references, other.references);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(name, id, references);
    }

}
3. Java 7 Utils
import java.util.Objects;

public class JavaSevenBean {
   
    ...

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        JavaSevenBean that = (JavaSevenBean) o;

        return Objects.equals(name, that.name)
                &&  Objects.equals(id, that.id)
                && Objects.equals(references, that.references);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, id, references);
    }
}
 
Summary


Both Apache Commons Lang and Google Guava are additional jars in you project. So, when should we use these libraries:
  • I prefer to avoid using additional libraries if I need only small amount of library functionality. I.e. don't add Apache Commons/Guava into your project if you need equals, hashCode and toString only.
  • Use full power of existent projects libraries. Don't reinvent the wheel. Almost all mid-size project has Apache Commons or Guava (or both).
  • Use modern Java 7 feature if it's possible in your current environment.
  • I can't make preference between Guava and Apache Commons. Just stick to the current project state and select library which will be more useful. Don't be dogmatic.
IDE generated equals, hashCode and toString methods are not readable. And  in general cases they are boilerplate and hard to maintain. It's very easy to forget re-generate/update them.

Links:

No comments:

Post a Comment