9

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

我正在考虑编写警示性 javadoc 并将其作为Iterator<byte[]>. AFAIK 它不会违反Iterator合同,但如果他们这样做并在每个位置Lists.newArrayList(myIterator)返回一个List相同的填充物,用户肯定会感到惊讶!byte[]

问题:对于一个可能发生变异并返回相同对象来实现接口的类,这是不好的做法吗?Iterator

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

  • 如果不是,你如何证明违反最小惊讶原则是正当的?

两个小提示:

  • 我正在使用番石榴AbstractIterator,所以 remove() 并不是真正值得关注的。

  • 在我的用例中,用户是,并且此类的可见性将受到限制,但我已尝试将这个问题普遍地提出来,以便更广泛地应用。

更新:我接受 Louis 的回答,因为它的投票数是 Keith 的 3 倍,但请注意,在我的用例中,我打算采用我在 Keith 对生产的回答的评论中留下的代码。

4

3 回答 3

10

EnumMap在它的entrySet()迭代器中基本上就是这样做的,这导致了今天令人困惑、疯狂、令人沮丧的错误。

如果我是你,我不会使用Iterator-- 我会编写一个不同的 API(甚至可能与 Iterator 完全不同)并实现它。例如,您可以编写一个新的 API,将消息写入其中,这样 API 的用户就可以控制缓冲区是否被重用ByteBuffer这看起来相当直观(用户可以编写明显且干净地重用ByteBuffer.

于 2012-08-09T23:39:53.650 回答
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[]为每个迭代器返回复制您的成本要低得多。

于 2012-08-09T23:47:32.180 回答
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()被调用,这可以直接完成。

于 2012-08-10T03:12:29.980 回答