在clojure的seq
函数的docstring中,它提到:
注意,seq缓存值,因此seq不应在迭代器重复的任何Iterable上使用返回相同的可变对象。
这句话是什么意思?为什么要强调相同的mutable
对象?
后来添加了注释,并提到了此票证:
一些Java库返回迭代器,这些迭代器在每次调用时返回相同的可变对象:
- Hadoop ReduceContext Impl$ValueIterator
- Mahout DenseVector$AllIterator/NonDefaultIterator
- LensKit FastIterators
虽然在这些迭代器上仔细使用seq或迭代器seq在过去是有效的,但随着CLJ-1669的变化,这种情况已经不复存在了——迭代器seq现在产生了一个分块序列。因为在从seq中检索第一个值之前,在迭代器上调用next()32次,并且每次都返回相同的可变对象,所以像这样的迭代器中的代码现在会收到不同的(不正确的)结果。
方法:序列缓存值,因此与保存可变和可变的Java对象不兼容。我们将在seq和迭代器seq文档字符串中对此进行一些澄清。对于上面的迭代器,建议在循环/recurate中处理这些迭代器或将它们封装在惰性seq中,该惰性seq在缓存之前将每个重新返回的可变对象转换为适当的值。
seq
函数可以从许多类型的对象(如集合和数组)中创建序列。seq
还适用于从Java Collections框架实现java.util.Iterable
接口的任何对象。不幸的是,Clojure序列和java.util.Iterator
(与Iterable
一起使用)的语义并不是100%兼容的,正如@cfrick的回答中所指出的那样。
对于Iterator
的next
方法的每次调用,返回相同(可变)对象是可以的,或者在某个时候是可以的。只要next
的返回值在对next
的后续调用之前被使用和丢弃,这就有效。但是,如果保留next
的返回值并在以后使用,则可能会导致未定义的行为。这正是Clojure序列的某些实现中所发生的情况。
让我举例说明。下面是Java中一系列整数的玩具实现。注意方法next
的实现如何总是返回相同的对象。
package foo.bar;
import java.util.*;
public class MyRange implements Iterable<MyRange.Num> {
public static class Num {
private int n;
public int get() { return n; }
public String toString() { return String.valueOf(n); }
}
private int max;
public MyRange(int max) { this.max = max; }
// Implementation of Iterable
public Iterator<Num> iterator() {
return new Iterator<Num> () {
private int at = 0;
private Num num = new Num();
public boolean hasNext() {
return at < max;
}
public Num next() {
num.n = at++;
return num;
}
};
}
}
当以Java Collections框架的设计者所期望的方式使用时,这些代码可以很好地工作。例如:
(loop [i (.iterator (MyRange. 3))]
(when (.hasNext i)
(print (str (.next i) " "))
(recur i)))
;;=> 0 1 2
但一旦我们将Clojure序列纳入混合,事情就会出错:
(map #(.get %) (MyRange. 3))
;;=> (2 2 2)
我们得到了(2 2 2)
而不是(0 1 2)
。这正是seq
中的警告所关注的问题类型。
如果内存可用,那么Java 6中EnumhMap
的Iterator
的实现以效率的名义使用了可变对象实现。这样的实现不会在每次迭代中分配内存,因此速度更快,不会产生垃圾。但这种"技术"不仅对Clojure有问题,对一些Java用户也有问题。因此,Java 7中的行为发生了变化。