一个迭代并返回相同对象的迭代器。不好的做法?

时间:2012-08-09 23:33:00

标签: java iterator guava

我正在编写GC友好代码来阅读并向用户返回一系列byte[]消息。在内部我重复使用相同的ByteBuffer,这意味着我将在大多数情况下重复返回相同的byte[]实例

我正在考虑编写警示javadoc并将其作为Iterator<byte[]>公开给用户。 AFAIK它不会违反Iterator合同,但如果用户Lists.newArrayList(myIterator)并且在每个位置获得一个List填充byte[],用户肯定会感到惊讶!

问题:对于可能会变异并返回相同的对象以实现Iterator接口的类,不良做法

  • 如果是这样,最好的选择是什么? “不要改变/重用你的对象”是一个简单的答案。但它没有解决非常需要重用的情况。

  • 如果没有,您如何证明违反principle of least astonishment的理由?

两个小调:

  • 我正在使用Guava的AbstractIterator,所以remove()并不是真正令人担忧的问题。

  • 在我的用例中,用户是 me ,并且此类的可见性将受到限制,但我已经尝试通常要求这样做以便更广泛地应用。

更新:我接受路易斯的回答,因为它的票数是基思的3倍,但请注意,在我的用例中,我打算把我留下的代码留在评论中基思对生产的回答。

3 个答案:

答案 0 :(得分:10)

EnumMap在它的entrySet()迭代器中基本完成了这一点,这导致了令人困惑,疯狂,令人沮丧的错误。

如果我是你,我就不会使用Iterator - 我会写一个不同的API(可能与Iterator完全不同,甚至)并实现它。例如,您可以编写一个新的API作为输入 ByteBuffer来编写消息,因此API的用户可以控制缓冲区是否被重用。这似乎相当直观(用户可以编写明显且干净地重用ByteBuffer的代码),而不会产生不必要的混乱代码。

答案 1 :(得分:7)

我会定义一个可以使其无效的中间对象。所以你的函数会返回Iterator<ByteArray>,而ByteArray就是这样的:

class ByteArray {
    private byte[] data;
    ByteArray(byte[] d) { data = d; }
    byte[] getData() {
        if (data == null) throw new BadUseOfIteratorException();
        return data;
    }
    void invalidate() { data = null; }
}

然后您的迭代器可以使先前返回的ByteArray无效,以便将来访问(通过getData或您提供的任何其他访问者)将失败。然后,至少如果有人做Lists.newArrayList(myIterator)之类的事情,他们至少会得到一个错误(当访问第一个无效的ByteArray时),而不是默默地返回错误的数据。

当然,这不会抓住所有可能的不良用途,但可能是常见的用途。如果您对从未返回原始byte[]并提供byte get(int idx)之类的访问者感到满意,那么它应该能够捕获所有情况。

您必须为每个迭代器返回分配一个新的ByteArray,但希望这比为每个迭代器返回复制byte[]要便宜得多。

答案 2 :(得分:1)

就像Keith Randall一样,我也会创建Iterator<ByteArray>,但工作方式完全不同(下面的注释来自lombok):

@RequiredArgsConstructor
public class ByteArray {
    @Getter private final byte[] data;
    private final ByteArrayIterable source;
    void allowReuse() {
        source.allowReuse();
    }
}

public class ByteArrayIterable implements Iterable<ByteArray> {
    private boolean allowReuse;
    public allowReuse() {
        allowReuse = true;
    }
    public Iterator<ByteArray> iterator() {
        return new AbstractIterator<ByteArray>() {
            private ByteArray nextElement;
            public ByteArray computeNext() {
                if (noMoreElements()) return endOfData();
                if (!allowReuse) nextElement =
                    new ByteArray(new byte[length], ByteArrayIterable.this);
                allowReuse = false;
                fillWithNewData(lastElement.getData());
            }
        }
    }
}

现在在像Lists.newArrayList(myIterator)这样的调用中,总是会分配一个新的字节数组,所以一切正常。在你的循环中

for (ByteArray a : myByteArrayIterable) {
    a.allowReuse();
    process(a.getData());
}

缓冲区被重用。除非您误拨allowReuse(),否则不会造成任何伤害。如果你忘记给它打电话,那么你的表现会更差但行为正确。


现在我看到它可以在没有ByteArray的情况下工作,重要的是调用myByteArrayIterable.allowReuse(),这可以直接完成。