If you are fortunate enough to be using JDK 7, the newly available Objects class is the obvious (at least to me) choice for implementing the "common" Java object methods such as equals(Object) [with Objects.equals(Object,Object)], hashCode() [with Objects.hashCode(Object) or Objects.hash(Object...)], and toString() [with Objects.toString(Object)] to appropriately override the default Object implementations. I have written posts about using Objects class: JDK 7: The New Objects Class and Java 7 Objects-Powered Compact Equals.
If you're not yet using Java 7, your best choices might be the Apache Commons builders ToStringBuilder and EqualsBuilder and HashCodeBuilder (if you're using a version of Java prior to J2SE 5) or Guava (if you're using J2SE 5 or later). In this post, I look at using Guava's Objects class to implement the three common methods equals
, hashCode
, and toString()
.
Without Guava or other library to help, the three common methods discussed in this post are often highlighted as shown in the next code listing. These methods were generated with NetBeans 7.1 beta.
TraditionalEmployeepackage dustin.examples; import java.util.Calendar; /** * Simple employee class using NetBeans-generated 'common' methods * implementations that are typical of many such implementations created * without Guava or other library. * * @author Dustin */ public class TraditionalEmployee { public enum Gender{ FEMALE, MALE }; private final String lastName; private final String firstName; private final String employerName; private final Gender gender; /** * Create an instance of me. * * @param newLastName The new last name my instance will have. * @param newFirstName The new first name my instance will have. * @param newEmployerName The employer name my instance will have. * @param newGender The gender of my instance. */ public TraditionalEmployee( final String newLastName, final String newFirstName, final String newEmployerName, final Gender newGender) { this.lastName = newLastName; this.firstName = newFirstName; this.employerName = newEmployerName; this.gender = newGender; } public String getEmployerName() { return this.employerName; } public String getFirstName() { return this.firstName; } public Gender getGender() { return this.gender; } public String getLastName() { return this.lastName; } /** * NetBeans-generated method that compares provided object to me for equality. * * @param obj Object to be compared to me for equality. * @return {@code true} if provided object is considered equal to me or * {@code false} if provided object is not considered equal to me. */ @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final TraditionalEmployee other = (TraditionalEmployee) obj; if ((this.lastName == null) ? (other.lastName != null) : !this.lastName.equals(other.lastName)) { return false; } if ((this.firstName == null) ? (other.firstName != null) : !this.firstName.equals(other.firstName)) { return false; } if ((this.employerName == null) ? (other.employerName != null) : !this.employerName.equals(other.employerName)) { return false; } if (this.gender != other.gender) { return false; } return true; } /** * NetBeans-generated method that provides hash code of this employee instance. * * @return My hash code. */ @Override public int hashCode() { int hash = 3; hash = 19 * hash + (this.lastName != null ? this.lastName.hashCode() : 0); hash = 19 * hash + (this.firstName != null ? this.firstName.hashCode() : 0); hash = 19 * hash + (this.employerName != null ? this.employerName.hashCode() : 0); hash = 19 * hash + (this.gender != null ? this.gender.hashCode() : 0); return hash; } /** * NetBeans-generated method that provides String representation of employee * instance. * * @return My String representation. */ @Override public String toString() { return "TraditionalEmployee{" + "lastName=" + lastName + ", firstName=" + firstName + ", employerName=" + employerName + ", gender=" + gender + '}'; } }
Although NetBeans 7.1 beta did the heavy lifting here, this code still must be maintained and can be made more readable. The next class is the same class, but with Guava-powered common methods instead of the NetBeans-generated 'typical' implementations shown above.
GuavaEmployeepackage dustin.examples; /** * Simple employee class using Guava-powered 'common' methods implementations. * * I explicitly scope the com.google.common.base.Objects class here to avoid the * inherent name collision with the java.util.Objects class. * * @author Dustin */ public class GuavaEmployee { public enum Gender{ FEMALE, MALE }; private final String lastName; private final String firstName; private final String employerName; private final TraditionalEmployee.Gender gender; /** * Create an instance of me. * * @param newLastName The new last name my instance will have. * @param newFirstName The new first name my instance will have. * @param newEmployerName The employer name my instance will have. * @param newGender The gender of my instance. */ public GuavaEmployee( final String newLastName, final String newFirstName, final String newEmployerName, final TraditionalEmployee.Gender newGender) { this.lastName = newLastName; this.firstName = newFirstName; this.employerName = newEmployerName; this.gender = newGender; } public String getEmployerName() { return this.employerName; } public String getFirstName() { return this.firstName; } public TraditionalEmployee.Gender getGender() { return this.gender; } public String getLastName() { return this.lastName; } /** * Using Guava to compare provided object to me for equality. * * @param obj Object to be compared to me for equality. * @return {@code true} if provided object is considered equal to me or * {@code false} if provided object is not considered equal to me. */ @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final GuavaEmployee other = (GuavaEmployee) obj; return com.google.common.base.Objects.equal(this.lastName, other.lastName) && com.google.common.base.Objects.equal(this.firstName, other.firstName) && com.google.common.base.Objects.equal(this.employerName, other.employerName) && com.google.common.base.Objects.equal(this.gender, other.gender); } /** * Uses Guava to assist in providing hash code of this employee instance. * * @return My hash code. */ @Override public int hashCode() { return com.google.common.base.Objects.hashCode( this.lastName, this.firstName, this.employerName, this.gender); } /** * Method using Guava to provide String representation of this employee * instance. * * @return My String representation. */ @Override public String toString() { return com.google.common.base.Objects.toStringHelper(this) .addValue(this.lastName) .addValue(this.firstName) .addValue(this.employerName) .addValue(this.gender) .toString(); } }
As the code above proves, the use of Guava improves the readability of the implementations of the three common methods. The only thing that's not so nice is the need to explicitly scope Guava's Objects
class in the code to avoid a naming collision with Java SE 7's Objects class. Of course, if one is not using Java 7, then this is not an issue and if one is using Java 7, it's most likely that the standard version should be used instead anyway.
Guava provides a nice approach for building safer and more readable common methods via its Objects
class. Although I'll use the new java.util.Objects
class instead for JDK 7 projects, Guava's com.google.common.base.Objects
class provides a nice alternative for working in versions of Java prior to JDK 7.
4 comments:
Java7 Objects work differently than Guava, commons.lang etc. It just calls the corresponding methods on the provided object.
E.g. Objects.toString(this); calls this.toString();
Sebastian,
If that was all Objects did, it wouldn't be very useful. I think the fact that it checks them for null first so that I don't have to is what makes it special and similar to how Guava's Objects class behaves. For example, java.util.Objects.hash(Objects...) is almost identical to com.google.common.base.Objects.hash(Objects...).
Dustin
"The only thing that's not so nice is the need to explicitly scope Guava's Objects class in the code to avoid a naming collision with Java SE 7's Objects class."
This is unnecessary, unless you import the whole java.util.*
But indeed, we can as well use Java 7's built-in methods, since they are identical to Guava one (I think).
Nice. Very precise and brief. I also add http://muhammadkhojaye.blogspot.com/2010/02/java-hashing.html which i also find useful how hashcode work with the concept of bucket.
Post a Comment