【问题标题】:How Predicate maintain state in Java 8Java 8 中 Predicate 如何维护状态
【发布时间】:2017-11-20 08:06:31
【问题描述】:

我正在查看code 并试图理解以下代码。

抄自 Stuart Marks 的回答

public static <T> Predicate<T> distinctByKey(Function<? super T,Object> keyExtractor) {
    Map<Object,Boolean> seen = new ConcurrentHashMap<>();
    return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}

BigDecimal totalShare = orders.stream()
    .filter(distinctByKey(o -> o.getCompany().getId()))
    .map(Order::getShare)
    .reduce(BigDecimal.ZERO, BigDecimal::add);

我的问题是每次调用 distinctByKey 并生成新的 ConcurrentHashMap。它是如何使用 new ConcurrentHashMap(); 维护状态的?

【问题讨论】:

    标签: java-8 java-stream


    【解决方案1】:

    由于这是一个捕获 lambda,实际上每次调用 distinctByKey 时都会返回一个新的 Predicate 实例;但这将发生在整个流中,而不是每个单独的元素。

    如果您愿意运行您的示例:

    Djdk.internal.lambda.dumpProxyClasses=/Your/Path/Here

    您会看到为您的Predicate 的实现生成了一个class。因为这是一个有状态的 lambda - 它捕获 CHMFunction,所以它将有一个 private 构造函数和一个返回实例的 static factory method


    distinctByKey 的每次调用都会产生一个不同的实例,但该实例将被 Stream 的每个元素重用。如果你运行这个例子,事情可能会更明显:

     public static <T> Predicate<T> distinctByKey(Function<? super T,Object> keyExtractor) {
        Map<Object,Boolean> seen = new ConcurrentHashMap<>();
        Predicate<T> predicate =  t -> {
            Object obj = keyExtractor.apply(t);
            System.out.println("stream element : " + obj);
            return seen.putIfAbsent(obj, Boolean.TRUE) == null;
        };
    
        System.out.println("Predicate with hash :" + predicate.hashCode());
        return predicate;
    }
    
    @Getter
    @AllArgsConstructor
    static class User {
        private final String name;
    }
    
    public static void main(String[] args) {
        Stream.of(new User("a"), new User("b"))
              .filter(distinctByKey(User::getName))
              .collect(Collectors.toList());
    
    }
    

    这将输出:

    Predicate with hash :1259475182
    stream element : a
    stream element : b
    

    一个Predicate 用于流的两个元素。

    如果你添加另一个filter:

    Stream.of(new User("a"), new User("b"))
          .filter(distinctByKey(User::getName))
          .filter(distinctByKey(User::getName))
          .collect(Collectors.toList());
    

    会有两个Predicates:

    Predicate with hash :1259475182
    Predicate with hash :1072591677
    stream element : a
    stream element : a
    stream element : b
    stream element : b
    

    【讨论】:

    • 每个流元素的谓词的不同实例?不,每次调用 distinctByKey 都会有一个 CHM 和一个谓词。
    • @StuartMarks get$Lambda 在调用时不会一直返回一个新的谓词?
    • 当然,但是 get$Lambda() 应该只在计算 lambda 表达式时调用,即从对 distinctByKey 的调用中调用。每个通过过滤器的流元素都会调用同一个 lambda 实例的 test() 方法。正是该实例保存了捕获的 CHM 和 keyExtractor 函数。
    【解决方案2】:

    这看起来很混乱,但很简单。实际发生的是 distinctByKey 方法被调用一次,所以只有一个 ConcurrentHashMap 的实例,它被 lambda 表达式捕获。因此,当distinctByKey 方法返回一个Predicate 对象时,我们会将其应用于流的每个元素。

    【讨论】:

    • parallelStream() 是这样吗?
    • @xyz:不要因为有流使用而感到困惑。看看代码结构。你有a().b(c()).d().e(),所以方法按acb(接收c的结果)、de的顺序调用。 a方法是streamcdistinctByKeybfilter等,但实际的流操作只是以e开始,即reduce。在此之前已经调用并完成了其他四个方法。
    • @xyz 同样适用。
    • @Aominè 确实该方法只会被调用一次,但会有 多个Predicate 实例返回
    • @Aominè 不用担心 - 当您想到仅调用一次 get 的引导方法时,它会变得更加有趣,CallSiteMethodHandle 包装到由 @987654344 构建的 get$Lambda 方法@只链接一次,有多少细节很疯狂
    猜你喜欢
    • 2010-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多