我正在编写GC友好代码来阅读并向用户返回一系列byte[]
消息。在内部我重复使用相同的ByteBuffer
,这意味着我将在大多数情况下重复返回相同的byte[]
实例 。
我正在考虑编写警示javadoc并将其作为Iterator<byte[]>
公开给用户。 AFAIK它不会违反Iterator
合同,但如果用户Lists.newArrayList(myIterator)
并且在每个位置获得一个List
填充byte[]
,用户肯定会感到惊讶!
问题:对于可能会变异并返回相同的对象以实现Iterator
接口的类,不良做法是
如果是这样,最好的选择是什么? “不要改变/重用你的对象”是一个简单的答案。但它没有解决非常需要重用的情况。
如果没有,您如何证明违反principle of least astonishment的理由?
两个小调:
我正在使用Guava的AbstractIterator
,所以remove()并不是真正令人担忧的问题。
在我的用例中,用户是 me ,并且此类的可见性将受到限制,但我已经尝试通常要求这样做以便更广泛地应用。
更新:我接受路易斯的回答,因为它的票数是基思的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()
,这可以直接完成。