/ DEDUPLICATION, API

Deduplication trick in legacy code

It might happen that you need to deduplicate a list of items…​ coming from legacy code. The class - let’s call it LegacyObject has already implementations for equals() and hashCode(). It’s not possible to change the implementation, for fear of breaking the running code. And unfortunately, the Java API doesn’t offer a distinctBy() feature.

In that case, a cheap trick is to create a wrapper class around LegacyObject, with the desired implementation:

public class LegacyObject {

  private final UUID id;
  private final String foo;
  private final int bar;

  public LegacyObject(UUID id, String foo, int bar) {
    this.id = id;
    this.foo = foo;
    this.bar = bar;
  }

  @Override
  public int hashCode() {
    return Objects.hash(id);
  }

  // Implementation of equals() using only the id field
  // Getters
}

public class DeduplicateWrapper {

  private final LegacyObject object;

  public DeduplicateWrapper(LegacyObject object) {
    this.object = object;
  }

  public LegacyObject getObject() {
    return object;
  }

  @Override
  public int hashCode() {
    return Objects.hash(object.getFoo());
  }

  // Implementation of equals() using only the foo field of the wrapped object
}

With that, it’s a no-brainer to deduplicate a collection using the stream API:

List<LegacyObject> duplicates = ...;

duplicates.stream()
    .map(DeduplicateWrapper::new)
    .distinct()
    .map(DeduplicateWrapper::getObject);

It works also using pre-Java 8 code, but with much more ceremony involved:

List<LegacyObject> deduplicated = new ArrayList<>();
Set<DeduplicateWrapper> wrappers = new HashSet<>();
for (LegacyObject duplicate: duplicates) {
  wrappers.add(new DeduplicateWrapper(duplicate));
}
for (DeduplicateWrapper wrapper: wrappers) {
  deduplicated.add(wrapper.getObject());
}

Of course, if you’re lucky enough being able to use Kotlin, this becomes a no-brainer:

val duplicates: List<LegacyObject> = ...
duplicates.distinctBy { it.foo }

Alternatively, third-party libraries exist that can get the job done.

Nicolas Fränkel

Nicolas Fränkel

Nicolas Fränkel is a Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Currently working for Exoscale. Also double as a teacher in universities and higher education schools, a trainer and triples as a book author.

Read More