Java记忆方法

问题描述 投票:0回答:3

我遇到了一个有趣的问题,想知道是否以及如何在 Java 中完成此操作: 创建一个可以记住任何函数/方法的方法。该方法具有以下参数:方法/函数及其参数。

例如,假设我有这个方法:

int addOne(int a) { return a + 1;}

我使用相同的参数调用我的记忆方法两次:例如 addOne 和 5,第一次调用实际上应该调用 addOne 方法并返回结果,并存储给定参数的结果。当我第二次打电话时,它应该知道之前已经被调用过,只需查找以前的答案即可。

我的想法是拥有类似

HashMap<Callable,HashMap<List<Objects>,Object>>
的东西,您可以在其中存储以前的答案并稍后查找它们。我认为这可以通过 lambda 表达式以某种方式完成,但我对它们不太熟悉。我不太确定如何编写这个方法,希望得到一些帮助。

用这种方法可以做到吗?

java lambda memoization
3个回答
33
投票

在 Java 8 中你可以使用

ConcurrentHashMap.computeIfAbsent
:

Map<Integer, Integer> cache = new ConcurrentHashMap<>();

Integer addOne(Integer x) {
    return cache.computeIfAbsent(x -> x + 1);
}

DZone 有 一个很好的教程,它提供了适用于任何方法的解决方案:

Memoizer
类非常简单:

public class Memoizer<T, U> {

  private final Map<T, U> cache = new ConcurrentHashMap<>();

  private Memoizer() {}

  private Function<T, U> doMemoize(final Function<T, U> function) {
    return input -> cache.computeIfAbsent(input, function::apply);
  }

  public static <T, U> Function<T, U> memoize(final Function<T, U> function) {
    return new Memoizer<T, U>().doMemoize(function);
  }
}

使用这个类也极其简单:

Integer longCalculation(Integer x) {
  try {
    Thread.sleep(1_000);
  } catch (InterruptedException ignored) {
  }
  return x * 2;
}
Function<Integer, Integer> f = this::longCalculation;
Function<Integer, Integer> g = Memoizer.memoize(f);

public void automaticMemoizationExample() {
  long startTime = System.currentTimeMillis();
  Integer result1 = g.apply(1);
  long time1 = System.currentTimeMillis() - startTime;
  startTime = System.currentTimeMillis();
  Integer result2 = g.apply(1);
  long time2 = System.currentTimeMillis() - startTime;
  System.out.println(result1);
  System.out.println(result2);
  System.out.println(time1);
  System.out.println(time2);
}

运行

automaticMemoizationExample
方法将产生以下结果:

2
2
1000
0

8
投票

如果您愿意放弃参数的类型安全性,您可以使用 Java 8 的

MethodHandle
和 lambda 来记忆任何函数:

public interface MemoizedFunction<V> {
    V call(Object... args);
}

private static class ArgList {
    public Object[] args;

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

        ArgList argList = (ArgList) o;

        // Probably incorrect - comparing Object[] arrays with Arrays.equals
        return Arrays.equals(args, argList.args);
    }

    @Override
    public int hashCode() {
        return args != null ? Arrays.hashCode(args) : 0;
    }
}

public static <V> MemoizedFunction<V> memoizeFunction(Class<? super V> returnType, Method method) throws
                                                                                                  IllegalAccessException {
    final Map<ArgList, V> memoizedCalls = new HashMap<>();
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle methodHandle = lookup.unreflect(method)
                                      .asSpreader(Object[].class, method.getParameterCount());
    return args -> {
        ArgList argList = new ArgList();
        argList.args = args;
        return memoizedCalls.computeIfAbsent(argList, argList2 -> {
            try {
                //noinspection unchecked
                return (V) methodHandle.invoke(args);
            } catch (Throwable throwable) {
                throw new RuntimeException(throwable);
            }
        });
    };
}

工作示例

这会创建一个包含该函数的可变参数 lambda,并且在构造 lambda 后几乎与直接调用该函数一样快(即,在

call(Object...args)
内部不会发生反射),因为我们使用的是
MethodHandle.invoke()
而不是
 Method.invoke()

您仍然可以在没有 lambda(用匿名类替换)和 MethodHandles(用 Method.invoke 替换)的情况下执行此操作,但是会有性能损失,这使得这对于注重性能的代码不太有吸引力。


0
投票

当您要求特定的方法来完成您的任务时,有一种 OOP+FP(面向对象编程与函数式编程集成)方法来解决问题。虽然我在下面对其进行了简化,但它支持多种方式来“通用地实现 memoize”。

我发现它在我的项目中特别有用。例如,我通过围绕各种

enum
生成“缓存”助手来利用它。例如,可以轻松地通过
enum
查找
String
值,同时忽略大小写。另一个示例是通过
enum
或数据库 ID 轻松查找
.ordinal()
值。

与我见过的建议的其他解决方案相比,这里列出了为客户带来的好处:

  1. 对键和值强制执行 Null 限制
  2. 假设键是实际上是不可变的(参见“区别 3”)
  3. 在构造时,可以选择指定默认 lambda 以从键派生值
  4. 在构造时,可以选择确保
    keySet().iterator()
    内的插入顺序(对于调试会话来说非常好)
  5. 访问时,可以选择指定一个覆盖 lambda 以从键派生值
public final class Memoizer<K, V> {

  private final Map<K, V> vByK;

  private final Optional<Function<K, V>> optionalDefaultDeriveVFromK;

  public static <K, V> Memoizer<K, V> from() {
    return from(Optional.empty());
  }

  public static <K, V> Memoizer<K, V> from(
      Optional<Function<K, V>> optionalDefaultDeriveVFromK
  ) {
    return new Memoizer<>(optionalDefaultDeriveVFromK, false);
  }

  public static <K, V> Memoizer<K, V> from(
      boolean isRetainingInsertionOrder
  ) {
    return new Memoizer<>(Optional.empty(), isRetainingInsertionOrder);
  }

  private Memoizer(
      Optional<Function<K, V>> optionalDefaultDeriveVFromK,
      boolean isRetainingInsertionOrder
  ) {
    this.optionalDefaultDeriveVFromK = Objects.requireNonNull(optionalDefaultDeriveVFromK);
    this.vByK = isRetainingInsertionOrder
        ? new LinkedHashMap<>()
        : new HashMap<>();
  }

  public V get(
      K k,
      Optional<Function<K, V>> optionalOverrideDeriveVFromK
  ) {
    return Optional.ofNullable(vByK.get(Objects.requireNonNull(k, "k must not be null")))
        .orElseGet(() -> {
          synchronized (vByK) {
            return Optional.ofNullable(vByK.get(k))
                .orElseGet(() -> {
                  var lambda = optionalOverrideDeriveVFromK
                      .or(() -> optionalDefaultDeriveVFromK)
                      .orElseThrow(() -> new IllegalArgumentException(
                          "when optionalDeriveVFromK is empty, optionalOverrideDeriveVFromK must not be empty"));
                  var v = Objects.requireNonNull(lambda.apply(k),
                      "v returned from %s function must not be null".formatted(
                          optionalOverrideDeriveVFromK.isEmpty()
                              ? "optionalDefaultDeriveVFromK"
                              : "optionalOverrideDeriveVFromK"));
                  vByK.put(k, v);

                  return v;
                });
          }
        });
  }

  public V get(K k) {
    return get(k, Optional.empty());
  }

  public Set<K> keySet() {
    return Collections.unmodifiableSet(vByK.keySet());
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.