如何解决Java流的迭代器强制flatmap在获取第一项之前遍历子流
我需要从流中创建一个迭代器。父流和子流均由互不干扰的无状态操作组成,明显的策略是使用 flatMap。
结果是迭代器在第一次“hasNext”调用时遍历整个第一个子流,我不明白为什么。尽管 iterator()
是一个终端操作,但明确指出它不应该消耗流。
我需要从子流生成的对象一一生成。
import java.util.Iterator;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
public class FreeRunner {
public static void main(String[] args) {
AtomicInteger x = new AtomicInteger();
Iterator<C> iterator = Stream.generate(() -> null)
.takeWhile(y -> x.incrementAndGet() < 5)
.filter(y -> x.get() % 2 == 0)
.map(n -> new A("A" + x.get()))
.flatMap(A::getBStream)
.filter(Objects::nonNull)
.map(B::toC)
.iterator();
while(iterator.hasNext()) {
System.out.println("after hasNext()");
C next = iterator.next();
System.out.println(next);
}
}
private static class A {
private final String name;
public A(String name) {
this.name = name;
System.out.println(" > created " + name);
}
public Stream<B> getBStream() {
AtomicInteger c = new AtomicInteger();
return Stream.generate(() -> null)
.takeWhile(x -> c.incrementAndGet() < 5)
.map(n -> c.get() % 2 == 0 ? null : new B(this.name + "->B" + c.get()));
}
public String toString() {
return name;
}
}
private static class B {
private final String name;
public B(String name) {
this.name = name;
System.out.println(" >> created " + name);
}
public String toString() {
return name;
}
public C toC() {
return new C(this.name + "+C");
}
}
private static class C {
private final String name;
public C(String name) {
this.name = name;
System.out.println(" >>> created " + name);
}
public String toString() {
return name;
}
}
}
执行时显示:
> created A2
>> created A2->B1
>>> created A2->B1+C
>> created A2->B3
>>> created A2->B3+C
after hasNext()
A2->B1+C
after hasNext()
A2->B3+C
> created A4
>> created A4->B1
>>> created A4->B1+C
>> created A4->B3
>>> created A4->B3+C
after hasNext()
A4->B1+C
after hasNext()
A4->B3+C
Process finished with exit code 0
在调试中很明显 iterator.hasNext()
触发了对象 B 和 C 的生成。
相反,所需的行为是:
> created A2
>> created A2->B1
>>> created A2->B1+C
after hasNext()
A2->B1+C
>> created A2->B3
>>> created A2->B3+C
after hasNext()
A2->B3+C
> created A4
>> created A4->B1
>>> created A4->B1+C
after hasNext()
A4->B1+C
>> created A4->B3
>>> created A4->B3+C
after hasNext()
A4->B3+C
我在这里遗漏了什么?
解决方法
我找到了出路,但我不得不牺牲主流的懒惰。正如我在上面的评论中发布的那样,我试图简化模拟代码的问题是要逐张读取 excel 文件(按工作表名称过滤)并遍历所有行以根据电子表格中的数据创建对象。>
最初的想法对我来说仍然很好,但显然,Stream.iterator()
实现在创建第一个 hasNext()
对象时操作的第一个 A
调用中使用每个嵌套流。>
所以我放弃了 flatMap()
并使用 reduce(Stream::concat)
来连接所有由 A.getBStream()
生成的流:
public static void main(String[] args) {
AtomicInteger x = new AtomicInteger();
Iterator<C> it = Stream.generate(() -> null)
.takeWhile(y -> x.incrementAndGet() < 5)
.filter(y -> x.get() % 2 == 0)
.map(a -> new A("A" + x.get()))
.map(A::getBStream)
.filter(Objects::nonNull)
.reduce(Stream::concat)
.orElseGet(Stream::empty)
.filter(Objects::nonNull)
.map(B::toC)
.iterator();
while(it.hasNext()) {
System.out.println("after hasNext()");
C next = it.next();
System.out.println(next);
}
}
这会产生以下输出:
> created A2
> created A4
>> created A2->B0
>>> created A2->B0+C
after hasNext()
A2->B0+C
>> created A2->B1
>>> created A2->B1+C
after hasNext()
A2->B1+C
>> created A2->B2
>>> created A2->B2+C
after hasNext()
A2->B2+C
>> created A2->B3
>>> created A2->B3+C
after hasNext()
A2->B3+C
>> created A2->B4
>> created A4->B0
>>> created A4->B0+C
after hasNext()
A4->B0+C
>> created A4->B1
>>> created A4->B1+C
after hasNext()
A4->B1+C
>> created A4->B2
>>> created A4->B2+C
after hasNext()
A4->B2+C
>> created A4->B3
>>> created A4->B3+C
after hasNext()
A4->B3+C
>> created A4->B4
要付出的代价是预先生成 A2
和 A4
,但所有 B
对象都是延迟生成的
这是我能想到的最接近你想要的行为方式。我把它放在这里是为了帮助讨论。在您的示例中,您有两个标识符;一个用于创建 A 对象,另一个用于创建 B 对象。使用此代码,这些标识符是使用与您相同的逻辑预先创建的(尽管我将 AtomicInteger
替换为 IntStream
)。 flatmap
仍在使用,但不在对象创建时使用。
import java.util.Iterator;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class FreeRunner3 {
public static void main(String[] args) throws InterruptedException {
Iterator<C> iterator = IntStream.range(1,5)
.filter(i -> i % 2 == 0)
.boxed()
.flatMap(i -> IntStream.range(1,5)
.filter(j -> j % 2 != 0)
.mapToObj(j -> new int[] { i,j }))
.collect(Collectors.toList())
.stream()
.map(id -> new A("A" + id[0]).toB(id[1]))
.map(B::toC)
.iterator();
while (iterator.hasNext()) {
System.out.println("after hasNext()");
C next = iterator.next();
System.out.println(next);
}
}
private static class A {
private final String name;
public A(String name) {
this.name = name;
System.out.println(" > created " + name);
}
public B toB(int i) {
return new B(this.name + "->B" + i);
}
public String toString() {
return name;
}
}
private static class B {
private final String name;
public B(String name) {
this.name = name;
System.out.println(" >> created " + name);
}
public String toString() {
return name;
}
public C toC() {
return new C(this.name + "+C");
}
}
private static class C {
private final String name;
public C(String name) {
this.name = name;
System.out.println(" >>> created " + name);
}
public String toString() {
return name;
}
}
}
与此实现的一个不同之处在于,为 B 的每个实例都创建了一个 A 实例。
> created A2
>> created A2->B1
>>> created A2->B1+C
after hasNext()
A2->B1+C
> created A2
>> created A2->B3
>>> created A2->B3+C
after hasNext()
A2->B3+C
> created A4
>> created A4->B1
>>> created A4->B1+C
after hasNext()
A4->B1+C
> created A4
>> created A4->B3
>>> created A4->B3+C
after hasNext()
A4->B3+C
我想我也会包括这个来帮助讨论(希望没有人投反对票)。它与您想要的相反,在调用迭代器之前急切地预先创建所有对象。
Iterator<C> iterator = IntStream.range(1,5)
.filter(i -> i % 2 == 0)
.mapToObj(i -> new A("A" + i))
.flatMap(A::getBStream)
.map(B::toC)
.collect(Collectors.toList())
.iterator();
while (iterator.hasNext()) {
System.out.println("after hasNext()");
C next = iterator.next();
System.out.println(next);
}
,
与其创建一个迭代器然后为其中的每个元素做一些,您可以简单地使用一个 forEach
并直接在 Stream 中执行它:
AtomicInteger x = new AtomicInteger();
Stream.generate(() -> null)
.takeWhile(y -> x.incrementAndGet() < 5)
.filter(y -> x.get() % 2 == 0)
.map(n -> new A("A" + x.get()))
.flatMap(A::getBStream)
.filter(Objects::nonNull)
.map(B::toC)
.forEach(c -> {
System.out.println("after (no) hasNext()");
System.out.println(c);
});
输出:
> created A2
>> created A2->B1
>>> created A2->B1+C
after (no) hasNext()
A2->B1+C
>> created A2->B3
>>> created A2->B3+C
after (no) hasNext()
A2->B3+C
> created A4
>> created A4->B1
>>> created A4->B1+C
after (no) hasNext()
A4->B1+C
>> created A4->B3
>>> created A4->B3+C
after (no) hasNext()
A4->B3+C
如果你需要返回列表,你可以简单地返回一个流:
AtomicInteger x = new AtomicInteger();
Stream<C> stream = Stream.generate(() -> null)
.takeWhile(y -> x.incrementAndGet() < 5)
.filter(y -> x.get() % 2 == 0)
.map(n -> new A("A" + x.get()))
.flatMap(A::getBStream)
.filter(Objects::nonNull)
.map(B::toC);
// return it here
stream.forEach(c -> {
System.out.println("after (no) hasNext()");
System.out.println(c);
});
输出是一样的。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。