【问题标题】:Java general lambda memoizationJava 通用 lambda 记忆
【发布时间】:2020-05-07 07:22:39
【问题描述】:

我想用 Java 编写一个用于一般记忆的实用程序,我希望代码看起来像这样:

Util.memoize(() -> longCalculation(1));

在哪里

private Integer longCalculation(Integer x) {
    try { 
        Thread.sleep(1000);
    } catch (InterruptedException ignored) {}
    return x * 2;
}

为此,我想我可以这样做:

public class Util{
    private static final Map<Object, Object> cache = new ConcurrentHashMap<>();
    public interface Operator<T> {
        T op();
    }

    public static<T> T memoize(Operator<T> o) {
        ConcurrentHashMap<Object, T> memo = cache.containsKey(o.getClass()) ? (ConcurrentHashMap<Object, T>) cache.get(o.getClass()) : new ConcurrentHashMap<>();
        if (memo.containsKey(o)) {
            return memo.get(o);
        } else {
            T val = o.op();
            memo.put(o, val);
            return val;
        }
    }
}

我原以为这会起作用,但我发现没有做任何记忆。我已经追踪到o.getClass() 每次调用都不同。 我在想我可以尝试获得T 的运行时类型,但我想不出办法。

【问题讨论】:

    标签: java generics lambda


    【解决方案1】:

    answer by Lino 指出了代码中的几个缺陷,但如果不重用相同的 lambda,则不起作用。

    这是因为o.getClass() 不返回 lambda 返回的类,而是 lambda 本身的类。因此,下面的代码返回两个不同的类:

    Util.memoize(() -> longCalculation(1));
    Util.memoize(() -> longCalculation(1));
    

    我认为在不实际执行可能长时间运行的代码的情况下找出返回类型的类的好方法,这当然是您要避免的。

    考虑到这一点,我建议将类作为第二个参数传递给memoize()。这会给你:

    @SuppressWarnings("unchecked")
    public static <T> T memoize(Operator<T> o, Class<T> clazz) {
      return (T) cache.computeIfAbsent(clazz, k -> o.op());
    }
    

    这是基于您将cache的类型更改为:

    private static final Map<Class<?>, Object> cache = new ConcurrentHashMap<>();
    

    不幸的是,您必须将Object 向下转换为T,但您可以通过@SuppressWarnings("unchecked") 注释保证它是安全的。毕竟,您可以控制代码并且知道值的类将与映射中的键相同。

    另一种方法是使用番石榴ClassToInstanceMap

    private static final ClassToInstanceMap<Object> cache = MutableClassToInstanceMap.create(new ConcurrentHashMap<>());
    

    然而,这不允许您在不强制转换的情况下使用computeIfAbsent(),因为它返回一个Object,所以代码会变得有点冗长:

    public static <T> T memoize(Operator<T> o, Class<T> clazz) {
      T cachedCalculation = cache.getInstance(clazz);
      if (cachedCalculation != null) {
        return cachedCalculation;
      }
      T calculation = o.op();
      cache.put(clazz, calculation);
      return calculation;
    }
    

    作为最后的附注,您不需要指定自己的功能接口,但可以使用Supplier 接口:

    @SuppressWarnings("unchecked")
    public static <T> T memoize(Supplier<T> o, Class<T> clazz) {
      return (T) cache.computeIfAbsent(clazz, k -> o.get());
    }
    

    【讨论】:

      【解决方案2】:

      你遇到的问题是:

      ConcurrentHashMap<Object, T> memo = cache.containsKey(o.getClass()) ? (ConcurrentHashMap<Object, T>) cache.get(o.getClass()) : new ConcurrentHashMap<>();
      

      您检查是否存在带有键 o.getClass() 的条目。如果是,你get() 否则你使用一个新初始化的ConcurrentHashMap。现在的问题是,您没有将这个新创建的地图保存回缓存中。

      所以要么:

      • cache.put(o.getClass(), memo);放在上一行之后
      • 或者更好地使用computeIfAbsent() 方法:

        ConcurrentHashMap<Object, T> memo = cache.computeIfAbsent(o.getClass(), 
                                                                  k -> new ConcurrentHashMap<>());
        

      还因为您知道 cache 的结构,您可以使其更加类型安全,这样您就不必到处进行强制转换:

      private static final Map<Object, Map<Operator<?>, Object>> cache = new ConcurrentHashMap<>();
      

      您还可以使用前面提到的computeIfAbsent() 进一步缩短您的方法:

      public static <T> T memoize(Operator<T> o) {
          return (T) cache
              .computeIfAbsent(o.getClass(), k -> new ConcurrentHashMap<>())
              .computeIfAbsent(o, k -> o.op());
      }
      
      1. (T):简单地将 Object 的未知返回类型转换为所需的输出类型 T
      2. .computeIfAbsent(o.getClass(), k -&gt; new ConcurrentHashMap&lt;&gt;()):当cache 中的键o.getClass() 没有映射时,调用提供的lambda k -&gt; new ConcurrentHashMap&lt;&gt;()
      3. .computeIfAbsent(o, k -&gt; o.op());:这是在 2.computeIfAbsent 调用的返回值上调用的。如果嵌套映射中不存在o,则执行lambda k -&gt; o.op(),然后将返回值存储在映射中并返回。

      【讨论】:

      • 谢谢!这在我将 lambda 保存为这样命名的情况下效果很好:Operator&lt;Integer&gt; someOp = () -&gt; longCalculation(1); 但在这种情况下不是:memoize(() -&gt; longCalculation(1);o.getClass()好像每次都不一样
      • o.getClass() 不返回 Integer.class,而是类似:class some.package$$Lambda$1/1107024580。这意味着两个调用的类不同。所以我认为这个解决方案行不通,但它指出了原始代码中的一些缺陷。如果您使用相同的 lambda 两次,作为您的第一个示例,它应该可以工作。
      • @saidaspen 遗憾的是,正如 Magnilex 提到的那样,() -&gt; longCalculation(1) 形式的两个 lambdas 永远不会相同,即使它们具有相同的主体。如果将要调用的 lambda 保存到变量中,则只能使用此 memoize 方法
      猜你喜欢
      • 2021-06-16
      • 1970-01-01
      • 2012-01-03
      • 2011-04-25
      • 2015-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多