【问题标题】:Java stream parameter limit with no limit (MongoDB inconsistency)Java 流参数限制无限制(MongoDB 不一致)
【发布时间】:2019-03-24 20:16:24
【问题描述】:

我有一个方法,它返回项目列表并将限制(由Stream#limit 使用)作为参数:

public List<Integer> getItems(Long limit) {
    return IntStream.range(1, 10)
            .limit(limit)
            .boxed()
            .collect(Collectors.toList());
}  

如何设置参数取所有项目(无限制)?

我的尝试:

    Long limit5 = 5L;
    System.out.println("With limit 5:" + getItems(limit5));
    // works fine: 5 items

    Long noLimitZero = 0L;
    System.out.println("Without limit (zero): " + getItems(noLimitZero));
    // why 0 mean "no items" instead of "all items"

   Long noLimitNegative = -1L;
    System.out.println("Without limit (negative number): " + getItems(noLimitNegative));
    // IllegalArgumentException

    Long noLimitNull = null;
    System.out.println("Without limit (null): " + getItems(noLimitNull));
    // NullPointerException

传递Long.MAX_VALUE 不是解决方案。

MongoDB 不一致

例如MongoDB的FindIterable#limit可以以0null为无限制。

public List<Integer> getItems(Long limit) {
    MongoDatabase mongo = new MongoClient().getDatabase("example");
    MongoCollection<Document> documents = mongo.getCollection("items");
    FindIterable<Document> founded = documents.find();
    List<Integer> items = new ArrayList<>();
    for (Document doc : founded.limit(limit.intValue())) {
        items.add(doc.getInteger("number"));
    }
    return items;
}

方法之间的这种不一致会导致不兼容,例如一个接口与方法 List&lt;Integer&gt; getItems(Long limit) 和两个实现:在内存中和 MongoDB。

保留方法 Stream#skipFindIterable#skip 的一致性。

          --------------------------
          | Java       | Mongo     |
------------------------------------
limit = 0 | none items | all items |
------------------------------------
skip = 0  | none skip  | none skip |
------------------------------------

使用 Stream#limit 重构方法

我想没有办法将“无限制”参数传递给Stream#limit,所以我必须重构这个方法以将“限制”和0null-1作为“无限制”。

public static List<Integer> getItems(Long limit) {
    if (limit == null || limit == 0 || limit == -1) {
        return IntStream.range(1, 10)
                .boxed()
                .collect(Collectors.toList());
    } else {
        return IntStream.range(1, 10)
                .limit(limit)
                .boxed()
                .collect(Collectors.toList());
    }
}

或者:

public static List<Integer> getItems(Long limit) {
    IntStream items = IntStream.range(1, 10);
    if (limit != null && limit != 0 && limit != -1) {
        items = items.limit(limit);
    }
    return items.boxed()
            .collect(Collectors.toList());
}

有没有更好的方法来实现方法limit之间的一致性?

【问题讨论】:

  • 你所拥有的对我来说看起来不错
  • 为什么“传递Long.MAX_VALUE不是解决方案”?
  • 传递Long.MAX_VALUE 不是解决方案,因为它只是一种解决方法。在这种情况下(在从数据库中获取的内存实现替换中)理论上风险可能超过2^63-1 记录;)(但实际上它不是一个论点)。但我想知道如何使用 Java 流。内存中的 Java 流可能很长甚至无穷大(例如 IntStream.iterate(0, i -&gt; i + 1)),并且可能存在 Long.MAX_VALUE 太小的情况。
  • 使用最新版本。不想要限制的时候不要打电话limit,但要避免代码重复。当然,当流源已经支持某个大小时(如IntStream.rangeArrays.stream(…)List.subList(…).stream()Random.ints(…)),您应该首先指定大小。使用limit,即使使用Long.MAX_VALUE性能缺陷。

标签: java mongodb java-stream


【解决方案1】:

因此,您尝试做的事情存在多层问题。

您说“实用性不是论据”,这很好,但我要指出 Long.MAX_VALUE 确实超过了地球上的原子数量,因此您获得的条目数量可能比来自数据库真的很小。更不用说您继续将这些数据收集到一个列表中,这样您也可能在自己的应用程序中遇到内存问题。

所以第二件事是limit()的语义是它对条目的数量施加了一个固定的限制,而“无穷大”不是一个固定的限制;因此limit() 不是您要找的。​​p>

第三,您似乎正在寻找一种解决方法,因此我们有一个您可以使用的模式,那就是维护您自己的计数器。你想要的是类似AtomicBigInteger 的东西,它在JDK but is shown here 中不存在。

所以你要做的就是像这样创建一个Predicate

class BelowValue<T> implements Predicate<T> {
    BigInteger limit = BigInteger.ZERO;
    AtomicBigInteger counter = new AtomicBigInteger();

    public BelowValue(BigInteger limit) {
        this.limit = limit;
    }        
    public BelowValue() {}

    public boolean test(T ignored) {
        // short circuit on zero
        if (BigInteger.ZERO.compareTo(limit) == 0) { return true; }

        // check actual condition
        return  counter.incrementAndGet().compareTo(limit) > 0;
    }
}

然后你可以在你的流中使用它(Java 8)

Predicate<T> filter = new BelowValue<>(limit);
return stream
    .filter(filter)
    .boxed()
    .collect(Collectors.toList());

但请注意,filter 不是短路操作,因此如果您有一个无限流,这将不会终止(如果您的流比限制长得多,效率会非常低大小)。

Java 9 的 takeWhile 是短路的,因此您可以在上面的示例中将其替换为 filter

【讨论】:

  • 不要这样做。计数器可能是线程安全的,但您认为处理顺序与遇到顺序不匹配的假设不是。
  • @Holger 我没想到。
  • 您的代码可以。它将与并行流中断,因为基于计数器的谓词允许元素根据评估的顺序传递。这不是语义顺序,因为 limit 操作应该让 first n 元素按遇到顺序传递。
猜你喜欢
  • 2020-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-07
  • 1970-01-01
  • 1970-01-01
  • 2013-01-27
相关资源
最近更新 更多