【问题标题】:How can I prevent mutation of a list of iterators?如何防止迭代器列表的突变?
【发布时间】:2021-04-03 05:51:54
【问题描述】:

我想避免其他人对迭代器tests 的输入列表进行突变。我只希望其他人在 tests 的深层副本上运行。

如何在 Java 中实现这一点?

这是一个显示突变对tests 影响的示例。这两部分都对输入进行排序。但是第二部分没有什么需要排序的,因为第一部分的变异将迭代器迭代到最后。

您可以在此处在线运行以下示例: https://onlinegdb.com/NC4WzLzmt


import java.util.*;

public class ImmutableExample {
    public static void main(String[] args) {

        System.out.println("sort on demand");

        List<Iterator<Integer>> mutableTests = Arrays.asList(
                Arrays.asList(1, 2).iterator(),
                Arrays.asList(0).iterator(),
                Collections.emptyIterator()
        );

        List<Iterator<Integer>> tests = Collections.unmodifiableList(mutableTests);

        MergingIterator mergingIterator = new MergingIterator(tests);
        while (mergingIterator.hasNext()) {
            System.out.println(mergingIterator.next());
        }

        System.out.println("sort all at once");

        /*       uncomment the following will see the same result:*/
//        tests = Arrays.asList(
//                Arrays.asList(1, 2).iterator(),
//                Arrays.asList(0).iterator(),
//                Collections.emptyIterator()
//        );

        MergeKSortedIterators sol = new MergeKSortedIterators();
        Iterable<Integer> result = sol.mergeKSortedIterators(tests);

        for (Integer num : result) {
            System.out.println(num);
        }


    }
}

class PeekingIterator implements Iterator<Integer>, Comparable<PeekingIterator> {
    Iterator<Integer> iterator;
    Integer peekedElement;
    boolean hasPeeked;

    public PeekingIterator(Iterator<Integer> iterator) {
        this.iterator = iterator;
    }


    public boolean hasNext() {
        return hasPeeked || iterator.hasNext();
    }

    public Integer next() {
        int nextElem = hasPeeked ? peekedElement : iterator.next();
        hasPeeked = false;
        return nextElem;
    }

    public Integer peek() {
        peekedElement = hasPeeked ? peekedElement : iterator.next();
        hasPeeked = true;
        return peekedElement;
    }

    @Override
    public int compareTo(PeekingIterator that) {
        return this.peek() - that.peek();
    }


}


class MergingIterator implements Iterator<Integer> {
    Queue<PeekingIterator> minHeap;

    public MergingIterator(List<Iterator<Integer>> iterators) {
//        minHeap = new PriorityQueue<>((x, y) -> x.peek().compareTo(y.peek()));
        minHeap = new PriorityQueue<>();
        for (Iterator<Integer> iterator : iterators) {
            if (iterator.hasNext()) {
                minHeap.offer(new PeekingIterator(iterator));
            }
        }
    }

    public boolean hasNext() {
        return !minHeap.isEmpty();
    }

    public Integer next() {
        PeekingIterator nextIter = minHeap.poll();
        Integer next = nextIter.next();
        if (nextIter.hasNext()) {
            minHeap.offer(nextIter);
        }
        return next;
    }
}

class MergeKSortedIterators {


    public Iterable<Integer> mergeKSortedIterators(List<Iterator<Integer>> iteratorList) {
        List<Integer> result = new ArrayList<>();
        if (iteratorList.isEmpty()) {
            return result;
        }

        PriorityQueue<PeekingIterator> pq = new PriorityQueue<>();

        for (Iterator<Integer> iterator : iteratorList) {
            if (iterator.hasNext()) {
                pq.add(new PeekingIterator(iterator));
            }
        }

        while (!pq.isEmpty()) {
            PeekingIterator curr = pq.poll();
//            result.add(curr.peek());
// cannot use this one as hasNext() checks on `hasPeeked`
            result.add(curr.next());
            if (curr.hasNext()) {
                pq.add(curr);
            }
        }

        return result;
    }


}

【问题讨论】:

  • 您正在构建 API/库吗?如何创建一个 getter 并返回 tests 的深层副本,并将 tests 设为私有?
  • 我想防止人们在他们的输入上使用提供的方法,而没有意识到提供的方法可能会改变他们的输入。 @萨尔
  • 您通常不能复制迭代器,也不能使它们不可变。它们几乎不可避免地是可变的。您可以复制它们所基于的集合。
  • 让您的 API 采用 Iterable 而不是 Iterator 怎么样?
  • 该列表不可修改。迭代器将是可修改的。

标签: java iterator immutability


【解决方案1】:

这个问题似乎是基于一个误解......或两个。

如何防止迭代器列表发生突变?

您需要区分列表的可变性和列表中项目的可变性。我认为您实际上询问的是后者。 (因此,该列表与问题并不真正相关。正如我们将看到的。)

我想避免其他人对迭代器测试的输入列表进行变异。

再次,您似乎在询问列表,但我认为您实际上 是指询问迭代器。

我只希望其他人在测试的深层副本上运行。

这暗示您希望迭代器是不可变的。

问题来了:

  • Iterator 是一个固有的有状态/可变对象。事实上,如果不改变迭代器对象,就无法实现next()。

  • Iterator 对象通常不可深度复制。它们通常不支持clone() 或公共构造函数,并且它们通常不实现Serializable。 (确实,如果它们是可序列化的,那么序列化/反序列化的语义就会有问题。)

因此,基本上,您对不可变迭代器列表或(以某种方式)生成迭代器深层副本的列表的想法是不切实际的。


你评论了:

所以List&lt;Iterator&lt;Integer&gt;&gt; tests = Collections.unmodifiableList(mutableTests); 不能为List&lt;Iterator&lt;Integer&gt;&gt; 生成不可修改的列表?

嗯,是的,它可以。但这并不能解决问题。您需要一个不可修改的迭代器列表,而不是一个不可修改的迭代器列表。


可能的解决方案:

  1. 您可以为每次测试运行从其基本集合重新创建迭代器列表。

  2. 使用Iterable 而不是Iterator。您使用的集合类型都实现了Iterable,第三个迭代器可以从一个空列表中创建。

    List<Iterable<Integer>> tests = Arrays.asList(
            Arrays.asList(1, 2),
            Arrays.asList(0),
            Collections.emptyList()
    );
    
    // to use them ...
    
    for (Iterable<Integer> iterable : tests) {
        Iterator<Integer> iterator = iterable.iterator();
        // etc ...
    }
    
  3. 如果您的迭代器无法重新创建(例如,如果您正在迭代一个无法创建或“重绕”的源),您可以实现一个缓存迭代器包装器,该包装器可以记住迭代序列中的所有元素并且可以重置到序列的开头,或者生成一个新的迭代器来重放序列。 (但这在这里就有点过头了。)

【讨论】:

  • 我明白你的意思。迭代器本质上被设计为可变的。我看到了如何实现解决方案 1 和 3。您能否提供一些解决方案 2 的示例?
猜你喜欢
  • 1970-01-01
  • 2022-01-03
  • 2012-10-11
  • 1970-01-01
  • 2016-04-22
  • 1970-01-01
  • 2019-07-18
相关资源
最近更新 更多