Sunday 10 August 2014

Java: easy equals/hashCode/toString (less boilerplate; no magic)

This post describes a way to implement the equals hashCode and toString methods with relative brevity using Java 8 and KλudJe.

Plain Old Java Object

Consider this simple, three property type:

import java.time.LocalDate;
import java.util.Objects;

public class PersonPojo {
  private final long id;
  private final String name;
  private final LocalDate dateOfBirth;

  public PersonPojo(long id, String name, LocalDate dateOfBirth) {
    this.id = id;
    this.name = name;
    this.dateOfBirth = dateOfBirth;
  }

  public String getName() {
    return name;
  }

  public LocalDate getDateOfBirth() {
    return dateOfBirth;
  }

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

    PersonPojo that = (PersonPojo) o;
    return id == that.id
        && Objects.equals(name, that.name)
        && Objects.equals(dateOfBirth, that.dateOfBirth);
  }

  @Override
  public int hashCode() {
    int result = (int) (id ^ (id >>> 32));
    result = 31 * result + Objects.hashCode(name);
    result = 31 * result + Objects.hashCode(dateOfBirth);
    return result;
  }

  @Override
  public String toString() {
    return "PersonPojo {" 
        + id + ", " 
        + name + ", " 
        + dateOfBirth + '}';
  }
}

The hash could also be generated using Objects.hash(id, name, dateOfBirth) but this comes at the cost of autoboxing a primitive and allocating an array for the method varargs.

If the type is modified (say to add another property) then the developer must remember to update the all three methods.

Defining significant properties with lambdas

Java 8 doesn't provide property references but they can be created easily enough. Here is an updated version of the above class:

import uk.kludje.Meta;
import java.time.LocalDate;

public class PersonPojo {
  private static final Meta<PersonPojo> META =
      Meta.<PersonPojo>meta()
          .longs($ -> $.id)
          .objects($ -> $.name, $ -> $.dateOfBirth);

  private final long id;
  private final String name;
  private final LocalDate dateOfBirth;

  public PersonPojo(long id, String name, LocalDate dateOfBirth) {
    this.id = id;
    this.name = name;
    this.dateOfBirth = dateOfBirth;
  }

  public String getName() {
    return name;
  }

  public LocalDate getDateOfBirth() {
    return dateOfBirth;
  }

  @Override
  public boolean equals(Object obj) {
    return META.equals(this, obj);
  }

  @Override
  public int hashCode() {
    return META.hashCode(this);
  }

  @Override
  public String toString() {
    return META.toString(this);
  }
}

The expression $ -> $.id is basically shorthand for:

new LongGetter<PersonPojo> () {
  @Override
  public long getLong(PersonPojo person) {
    return person.id;
  }
}

Implementing these methods this way allows the specification of significant properties a single time and avoids the creation of extraneous objects, reflection or any preprocessing.

The KλudJe library is available here.

Alternatives

For experienced Java developers this topic is as old as the hills. Various static utilities, builders, reflective types and preprocessors are provided by Guava, Apache Commons Lang and Lombok.

No comments:

Post a Comment

All comments are moderated