我正在阅读Java 8,尤其是“Streams API”。我想知道流如何实现延迟执行?
我认为流只是作为库添加的,并没有对语言进行任何更改来支持惰性。此外,如果有人告诉我这是通过反射实现的,我会感到震惊。
我正在阅读Java 8,尤其是“Streams API”。我想知道流如何实现延迟执行?
我认为流只是作为库添加的,并没有对语言进行任何更改来支持惰性。此外,如果有人告诉我这是通过反射实现的,我会感到震惊。
class LazySeq<T> {
private final List<T> list;
private Predicate<? super T> predicate;
public LazySeq(List<T> input) {
this.list = new ArrayList<>(input);
}
//Here you just store the predicate, but you don't perform a filtering
//You could also return a new LazySeq with a new state
public LazySeq<T> filter(Predicate<? super T> predicate) {
this.predicate = predicate;
return this;
}
public void forEach(Consumer<? super T> consumer){
if(predicate == null) {
list.forEach(consumer);
} else {
for(T elem : list) {
if(predicate.test(elem)) {
consumer.accept(elem);
}
}
}
}
}
filter
函数时,过滤操作并不会立即执行。例如:LazySeq<Integer> lazySeq = new LazySeq<>(Arrays.asList(1, 2, 3, 4));
lazySeq = lazySeq.filter(i -> i%2 == 0);
如果你在调用filter后查看序列的内容,你会发现它始终是1, 2, 3, 4
。然而,当调用终端操作,比如forEach
时,过滤将在使用消费者之前完成。例如:
lazySeq.filter(i -> i%2 == 0).forEach(System.out::println);
将打印2和4。
这与Stream
相同。从源中,您链接具有某些属性的操作。这些操作是中间操作,返回惰性流(例如filter
或map
),或终端操作(例如forEach
)。其中一些终端操作是短路操作(例如findFirst
),因此您可能不会遍历整个管道(例如,在数组中返回值的索引的for循环中的早期返回)。
调用终端操作时,此操作链开始执行,以便最终获得预期结果。
可以通过在应用中间操作时在管道上存储新状态来实现惰性计算,当调用终端操作时,您按顺序遍历所有状态。
Stream API并非真正以这种方式实现(它更加复杂),但原则确实存在。
ex: filter(Predicate<? super T>)
filter
期望一个函数,告诉我们流中的对象是否符合某些条件。ForkJoin
池进行并行执行。流处理并不是数据的容器,而是逻辑的容器。您只需要传入接口的实例来记住这种逻辑。
考虑以下代码:
class FilterIterable<T> implements Iterable<T>
{
private Iterable<? extends T> iter;
private Predicate<? super T> pred;
public FilterIterable(Iterable<? extends T> iter, Predicate<? super T> pred) {
this.iter = iter;
this.pred = pred;
}
public Iterator<T> iterator() {
return FilterIterator<T>();
}
class FilterIterator<T> implements Iterator<T>
{
private Iterator<? extends T> iterator = iter.iterator();
private T next = null;
FilterIterator() {
getNext();
}
private void getNext() {
next = null;
while (iterator.hasNext()) {
T temp = iterator.next();
if (pred.test(temp)) {
next = temp;
break;
}
}
}
public boolean hasNext() { return next != null; }
public T next() {
T temp = next;
getNext();
return temp;
}
}
}
逻辑被包裹在pred
中,但仅在对象被迭代时调用。实际上,这个类不存储数据,它只保留一个可包含数据甚至只是另一个逻辑容器本身的可迭代对象。
元素也是按需返回的。这种范式使得所谓的流API变得延迟。