Skip to content

Memoization

pawel_labaj edited this page Apr 7, 2023 · 8 revisions

Memoization is a technique used to speed up repeated computations by caching the results of expensive function calls and returning the cached result when the same inputs occur again. It is a way to trade memory for computation time. For more details, please visit Wikipedia.

In AutoRecord, memoization can be applied to hashCode, toString, and any default method from a interface.

hashCode memoization

To enable hashCode memoization for a given interface, simply add the @AutoRecord.Options(memoizedHashCode = true) annotation to it. This will cause AutoRecordd to generate a memoized version of the hashCode method along with the equals method.

Example interface and generated record

Here's an example interface:

import pl.com.labaj.autorecord.AutoRecord;

@AutoRecord
@AutoRecord.Options(memoizedHashCode = true)
interface Person {
    String name();
    int age();
}

Here's the corresponding generated record that demonstrates hashCode memoization:

import static java.util.Objects.hash;
import static java.util.Objects.requireNonNull;

import java.lang.Integer;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.util.Objects;
import javax.annotation.Nullable;
import javax.annotation.processing.Generated;
import pl.com.labaj.autorecord.Memoized;
import pl.com.labaj.autorecord.Memoizer;

@Generated("pl.com.labaj.autorecord.AutoRecord")
record PersonRecord(String name, int age, @Nullable Memoizer<Integer> hashCodeMemoizer) implements Person {
  PersonRecord {
    requireNonNull(name, () -> "name must not be null");
  }

  PersonRecord(String name, int age) {
    this(name, age, new Memoizer<>());
  }

  @Memoized
  @Override
  public int hashCode() {
    return hashCodeMemoizer.computeIfAbsent(() -> _hashCode());
  }

  private int _hashCode() {
    return hash(name, age);
  }

  @Override
  public boolean equals(Object other) {
    if (this == other) {
      return true;
    }
    if (other == null) {
      return false;
    }
    if (!(other instanceof PersonRecord)) {
      return false;
    }
    if (hashCode() != other.hashCode()) {
      return false;
    }

    var otherRecord = (PersonRecord) other;
    return Objects.equals(name, otherRecord.name)
        && Objects.equals(age, otherRecord.age);
  }
}

An artificial hashCodeMemoizer component is introduced to keep memoized value of first _hashCode() method execution. To allow user not to use main constructor with it, additional constructor is generated with parameters recognized as components from the interface.

toString memoization

To enable toString memoization for a given interface, simply add the @AutoRecord.Options(memoizedToString = true) annotation to it. This will cause AutoRecordd to generate a memoized version of the toString method.

Example interface and generated record

Here's an example interface:

import pl.com.labaj.autorecord.AutoRecord;

@AutoRecord
@AutoRecord.Options(memoizedToString = true)
interface Person {
    String name();
    int age();
}

Here's the corresponding generated record that demonstrates toString memoization:

import static java.util.Objects.requireNonNull;

import java.lang.Override;
import java.lang.String;
import javax.annotation.Nullable;
import javax.annotation.processing.Generated;
import pl.com.labaj.autorecord.Memoized;
import pl.com.labaj.autorecord.Memoizer;

@Generated("pl.com.labaj.autorecord.AutoRecord")
record PersonRecord(String name, int age, @Nullable Memoizer<String> toStringMemoizer) implements Person {
  PersonRecord {
    requireNonNull(name, () -> "name must not be null");
  }

  PersonRecord(String name, int age) {
    this(name, age, new Memoizer<>());
  }

  @Memoized
  @Override
  public String toString() {
    return toStringMemoizer.computeIfAbsent(() -> _toString());
  }

  private String _toString() {
    return "PersonRecord[" +
        "name = " + name + ", " +
        "age = " + age +
        "]";
  }
}

An artificial toStringMemoizer component is introduced to keep memoized value of first _toString() method execution. To allow user not to use main constructor with it, additional constructor is generated with parameters recognized as components from the interface.

default method memoization

To enable memoization for a specific default method, you need to add the @Memoized annotation to the method in the interface. This will cause AutoRecordd to generate a memoized version of the method.

Example interface and generated record

Here's an example interface:

import pl.com.labaj.autorecord.AutoRecord;
import pl.com.labaj.autorecord.Memoized;

@AutoRecord
interface Person {
    String name();
    int age();

    @Memoized
    default long longComputing() {
        return 0L;
    }
}

Here's the corresponding generated record that demonstrates defualt method memoization:

import pl.com.labaj.autorecord.Memoized;
import pl.com.labaj.autorecord.Memoizer;

import javax.annotation.Nullable;
import javax.annotation.processing.Generated;

import static java.util.Objects.requireNonNull;

@Generated("pl.com.labaj.autorecord.AutoRecord")
record PersonRecord(String name, int age, @Nullable Memoizer<Long> longComputingMemoizer) implements Person {
    PersonRecord {
        requireNonNull(name, () -> "name must not be null");
    }

    PersonRecord(String name, int age) {
        this(name, age, new Memoizer<>());
    }

    @Memoized
    @Override
    public long longComputing() {
        return longComputingMemoizer.computeIfAbsent(() -> Person.super.longComputing());
    }
}

An artificial longComputingMemoizer component is introduced to keep memoized value of first original longComputing() method execution. To allow user not to use main constructor with it, additional constructor is generated with parameters recognized as components from the interface.

Clone this wiki locally