20

I have a few functions in my code where it makes much sense (seems even mandatory) to use memoization.

I don't want to implement that manually for every function separately. Is there some way (for example like in Python) I can just use an annotation or do something else so I get this automatically on those functions where I want it?

Albert
  • 65,406
  • 61
  • 242
  • 386

5 Answers5

18

Spring 3.1 now provides a @Cacheable annotation, which does exactly this.

As the name implies, @Cacheable is used to demarcate methods that are cacheable - that is, methods for whom the result is stored into the cache so on subsequent invocations (with the same arguments), the value in the cache is returned without having to actually execute the method.

oksayt
  • 4,333
  • 1
  • 22
  • 41
7

I came across a memoization library called Tek271 which appears to use annotations to memoize functions as you describe.

lbalazscs
  • 17,474
  • 7
  • 42
  • 50
Jacob Mattison
  • 50,258
  • 9
  • 107
  • 126
  • Ah I see. It seems that the lib provides a way to create a wrapper object for your object and it automatically memoizes those functions which were marked for memoization via an annotation. – Albert Oct 14 '10 at 16:26
  • 1
    The page has moved and can now be found at http://www.tek271.com/software/java/memoizer – Christian Semrau May 02 '11 at 21:10
6

I don't think there is a language native implementation of memoization.

But you can implement it easily, as a decorator of your method. You have to maintain a Map: the key of your Map is the parameter, the value the result.

Here is a simple implementation, for a one-arg method:

Map<Integer, Integer> memoizator = new HashMap<Integer, Integer>();

public Integer memoizedMethod(Integer param) {

    if (!memoizator.containsKey(param)) {
        memoizator.put(param, method(param));
    } 

    return memoizator.get(param);
}
Benoit Courtine
  • 7,014
  • 31
  • 42
  • How can I implement it in a general way as a decorator of my method? – Albert Oct 14 '10 at 15:38
  • @Albert: As Benoit stated, there is not native implementation of this (i.e. you can not do this in a general way without Java hacking), since the python decorator stuff uses some "meta-information" about the function. I.e. it is possible in python to let the decorator alter the original function. This is -- as far as I know -- not possible in Java. – phimuemue Oct 14 '10 at 15:44
  • "you can implement it easily, as a decorator of your method." <- how can I do it as a decorator? Or what do you mean by that? – Albert Oct 14 '10 at 15:46
  • @Albert, he means decorator in the more generic term of a wrapper around another function, not in the pythonic syntax way that makes it look clean and simple. You might be able to get what you want with AspectJ somehow, but I'm not personally familiar enough with it, and I still think it won't be nearly as easy as with python. – Karl Bielefeldt Oct 14 '10 at 16:05
4

You could use the Function interface in Google's guava library to easily achieve what you're after:

import java.util.HashMap;
import java.util.Map;

import com.google.common.base.Function;

public class MemoizerTest {
  /**
   * Memoizer takes a function as input, and returns a memoized version of the same function.
   * 
   * @param <F>
   *          the input type of the function
   * @param <T>
   *          the output type of the function
   * @param inputFunction
   *          the input function to be memoized
   * @return the new memoized function
   */
  public static <F, T> Function<F, T> memoize(final Function<F, T> inputFunction) {
    return new Function<F, T>() {
      // Holds previous results
      Map<F, T> memoization = new HashMap<F, T>();

      @Override
      public T apply(final F input) {
        // Check for previous results
        if (!memoization.containsKey(input)) {
          // None exists, so compute and store a new one
          memoization.put(input, inputFunction.apply(input));
        }

        // At this point a result is guaranteed in the memoization
        return memoization.get(input);
      }
    };
  }

  public static void main(final String[] args) {
    // Define a function (i.e. inplement apply)
    final Function<Integer, Integer> add2 = new Function<Integer, Integer>() {
      @Override
      public Integer apply(final Integer input) {
        System.out.println("Adding 2 to: " + input);
        return input + 2;
      }
    };

    // Memoize the function
    final Function<Integer, Integer> memoizedAdd2 = MemoizerTest.memoize(add2);

    // Exercise the memoized function
    System.out.println(memoizedAdd2.apply(1));
    System.out.println(memoizedAdd2.apply(2));
    System.out.println(memoizedAdd2.apply(3));
    System.out.println(memoizedAdd2.apply(2));
    System.out.println(memoizedAdd2.apply(4));
    System.out.println(memoizedAdd2.apply(1));
  }
}

Should print:

Adding 2 to: 1

3

Adding 2 to: 2

4

Adding 2 to: 3

5

4

Adding 2 to: 4

6

3

You can see that the 2nd time memoizedAdd2 is called (applied) to the arguments 2 and 1, the computation in the apply is not actually ran, it just fetched the stored results.

Tom Tresansky
  • 19,364
  • 17
  • 93
  • 129
  • Comes more close to what I want but still too specific. Is it possible to generalize this even more so that it can take any number of parameters (and not just one)? – Albert Oct 15 '10 at 09:12
  • Guava's Function class condenses all input into a single argument. Now, that argument's type could be a Object[], effectively allowing whatever, but reducing the effectiveness of typechecking. Or, it would be pretty straightforward to create a new Function2 interface generisized by for 2 arguments, a Function3, etc. – Tom Tresansky Oct 15 '10 at 12:59
  • In Guava the Suppliers class has built-in memoize and memoizeWithExpiration methods. – lbalazscs Mar 20 '15 at 00:04
1

Cyclops offers Memoisation for Functions, Suppliers, Callables, Predicates and by extension methods (via Method References) (see javadoc)

E.g.

Given a variable called that counts the number of time our method is actually called, we can see the memoised function actually executes the method just once.

int called = 0;

cached = Memoise.memoiseQuadFunction(this::addAll);

assertThat(cached.apply(1,2,3,4),equalTo(10));
assertThat(cached.apply(1,2,3,4),equalTo(10));
assertThat(called,equalTo(1));

private int addAll(int a,int b,int c, int d){
    called++;
    return a+b+c+d;
}
John McClean
  • 5,225
  • 1
  • 22
  • 30