【问题标题】:How to modify collection's elements with a spliterator如何使用拆分器修改集合的元素
【发布时间】:2018-07-26 04:57:10
【问题描述】:

经过大量搜索,我发现Spliterator 支持的唯一操作是从Collection 中读取元素。

谁能告诉我Spliterator 可以支持的CRUD 中的另一个操作。我尝试使用Spliterator 修改Collection 中的元素,但没有成功:

    Set<Integer> set = new TreeSet<>();
    set.add(2);
    set.add(3);
    set.add(5);
    set.add(6);
    Spliterator<Integer> si = set.spliterator();
    Spliterator<Integer> sa  = si.trySplit();
    while(sa.tryAdvance(e ->  e= ++e));

    System.out.println("original iterator");
    while(si.tryAdvance(e-> e = ++e));
    System.out.println(set.toString());

【问题讨论】:

    标签: java-8 spliterator


    【解决方案1】:

    Spliterator 不能修改底层的Collection,主要是因为Spliterator(与Iterator 不同)不是严格的Collection 绑定接口。

    Iterator的定义(来自Iterator's JavaDoc):

    集合上的迭代器。 [...]

    迭代器允许调用者在迭代期间以定义明确的语义从底层集合中删除元素。

    Spliterator的定义(来自Spliterator's JavaDoc):

    用于遍历和划分源元素的对象。 Spliterator 覆盖的元素来源可以是,例如,数组、Collection、IO 通道或生成器函数。


    编辑:我刚刚阅读了您发布的代码。在此代码中,您尝试在 Spliterator 调用中改变不可变的 Integer 实例。在这种情况下,Iterator 和 Spliterator 都不起作用,因为元素是不可变的。

    为此使用Stream(或IntStream),再加上map(),后跟collect()。

    【讨论】:

    • 我不同意 Iterator 严格绑定到 Collection 对象的概念,因为以下代码语句也是有效的,并从无限源流中获取等效的迭代器。因此,具有明确语义的移除概念并不成立。 final Iterator&lt;Integer&gt; numbers = Stream.iterate(1, n -&gt; n + 1).iterator();
    • 好吧,我可能用“严格”和Collection这两个词夸大了。不是“严格”,因为 - 正如您所展示的 - 我们在许多其他地方遇到 Iterators(例如,在 Java 8 之前,人们 - 包括我 - 经常编写自定义的 Iterator 实现)。另一方面,Iterator 被定义 来迭代Collection(这就是它有一个remove() 方法的原因——尽管它是可选的)。另一方面,Spliterator 明确声明它不一定一定是Collection-bound。
    • 这种解释具有误导性,因为即使直接在集合上调用 forEach 也不会改变这样一个事实,即像 e -&gt; e= ++e 这样的消费者甚至可以承受两个过时的操作而没有效果。 ++e 修改了参数e,而赋值e= 再次修改了参数,为它分配了与++e 操作相同的值。为此,不管我们使用int还是Integer,参数variable是可变的,但是和方法一样,修改参数对调用者没有影响,无论哪个我们正在使用的接口。
    • @Holger:我在阅读代码并注意到它的作用后立即编辑了答案。我不确定除了附加到当前答案之外,我是否应该做更多的事情。在答案之前会被认为可以吗?
    【解决方案2】:

    接受的答案提到的问题是Integer 对象是不可变的,因此通过其迭代器或拆分器使用您的集合都不会起作用,因为您无法修改整数实例就地。但是,如果您的对象被认为是可变的,您可以通过 Iterator#next 或在 Spliterator#tryAdvance 的使用者中获取它们并将它们修改为常规可变对象(例如:通过暴露的 setter)。

    作为一种变通解决方案,您可以流式传输您的集合,将每个整数实例映射到具有所需更改的新整数实例,然后您可以将它们收集回新的TreeSet&lt;Integer&gt;。

    final Set<Integer> newSet = set.stream()
        .map(number -> number + 1)
        .collect(Collectors.toCollection(TreeSet::new));
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-12-22
      • 2012-03-24
      • 2019-08-26
      • 2015-07-05
      • 2011-11-14
      • 1970-01-01
      相关资源
      最近更新 更多