Skip to content

Memoization

pawel_labaj edited this page Aug 2, 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() method memoization for a given interface, simply add the @AutoRecord.Options(memoizedHashCode = true) annotation to it. This will cause AutoRecord to generate a memoized version of the hashCode() method along with the equals() method.

Example interface and generated record

Here's an example interface:

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

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

@Generated("pl.com.labaj.autorecord.AutoRecord")
@GeneratedWithAutoRecord
record PersonHRecord(String name, int age, @Nullable IntMemoizer hashCodeMemoizer) implements PersonH {
    PersonHRecord {
        requireNonNull(name, "name must not be null");

        hashCodeMemoizer = requireNonNullElseGet(hashCodeMemoizer, IntMemoizer::new);
    }

    PersonHRecord(String name, int age) {
        this(name, age, new IntMemoizer());
    }

    @Memoized
    @Override
    public int hashCode() {
        return hashCodeMemoizer.computeAsIntIfAbsent(this::_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 PersonHRecord)) {
            return false;
        }
        if (hashCode() != other.hashCode()) {
            return false;
        }

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

An artificial hashCodeMemoizer recordComponent is introduced to keep memoized value of first _hashCode() method execution. To not force user to use main constructor with artificial components, additional constructor is generated with expected parameters only.

toString memoization

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

Example interface and generated record

Here's an example interface:

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

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

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

        toStringMemoizer = requireNonNullElseGet(toStringMemoizer, Memoizer::new);
    }

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

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

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

An artificial toStringMemoizer recordComponent is introduced to keep memoized value of first _toString() method execution. To not force user to use main constructor with artificial components, additional constructor is generated with expected parameters only.

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 AutoRecord to generate a memoized version of the method.

Example interface and generated record

Here's an example interface:

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

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

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

@Generated("pl.com.labaj.autorecord.AutoRecord")
@GeneratedWithAutoRecord
record PersonMRecord(String name, int age, @Nullable LongMemoizer slowComputingMethodMemoizer) implements PersonM {
    PersonMRecord {
        requireNonNull(name, "name must not be null");

        slowComputingMethodMemoizer = requireNonNullElseGet(slowComputingMethodMemoizer, LongMemoizer::new);
    }

    PersonMRecord(String name, int age) {
        this(name, age, new LongMemoizer());
    }

    @Memoized
    @Override
    public long slowComputingMethod() {
        return slowComputingMethodMemoizer.computeAsLongIfAbsent(PersonM.super::slowComputingMethod);
    }
}

An artificial slowComputingMethodMemoizer recordComponent is introduced to keep memoized value of first original slowComputingMethod() method execution. To not force user to use main constructor with artificial components, additional constructor is generated with expected parameters only.