考虑这个小程序,我们在其中获取一个流,对其进行排序,映射,然后对其进行迭代:
public class AlphabetOrdinals { private static final ListALPHABET = List.of('a', 'b', 'c', 'd', 'e', 'f'); private static final int STOP_ORDINAL = 'b' - 'a'; public static void main(String[] args) { System.out.println("java.runtime.version = " + System.getProperty("java.runtime.version")); Stream ordinals = ALPHABET.stream() .sorted() .map(AlphabetOrdinals::ordinal); int count = 0; Iterator iterator = ordinals.iterator(); while (iterator.hasNext()) { int ordinal = iterator.next(); if (ordinal > STOP_ORDINAL) { System.out.println("stopping at " + ordinal); break; } System.out.println("consuming " + ordinal); ++count; } System.out.println("consumed " + count + " ordinals"); } private static int ordinal(char letter) { int ordinal = letter - 'a'; System.out.println("performing EXTREMELY EXPENSIVE mapping of " + letter + " -> " + ordinal); return ordinal; } }
这个程序很愚蠢,但是与实际程序简化了,在实际程序中,迭代与另一个流上的迭代交织在一起,所以我不能轻易地用takeWhile / forEach替换它。
我希望该程序可以打印:
java.runtime.version = 11+28 performing EXTREMELY EXPENSIVE mapping of a -> 0 consuming 0 performing EXTREMELY EXPENSIVE mapping of b -> 1 consuming 1 performing EXTREMELY EXPENSIVE mapping of c -> 2 stopping at 2 consumed 2 ordinals
但它打印:
java.runtime.version = 11+28 performing EXTREMELY EXPENSIVE mapping of a -> 0 performing EXTREMELY EXPENSIVE mapping of b -> 1 performing EXTREMELY EXPENSIVE mapping of c -> 2 performing EXTREMELY EXPENSIVE mapping of d -> 3 performing EXTREMELY EXPENSIVE mapping of e -> 4 performing EXTREMELY EXPENSIVE mapping of f -> 5 consuming 0 consuming 1 stopping at 2 consumed 2 ordinals
如果我删除.sorted()
,它会打印出我期望的样子。
为什么会这样?
在实际程序中,映射步骤涉及从速度较慢的网络驱动器中读取数据负载,因此,我不希望这样做比绝对必要的次数更多!
无聊的答案:
这只是流API实现的编写方式。
少无聊的答案:
流具有某种操作链,可应用于输入。对于引用流,为排序而添加的操作是:(java.util.stream.SortedOps.RefSortingSink
假设您拥有与我相似的JDK)。对于地图,它是:
new StatelessOp(this, StreamShape.REFERENCE, StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) { @Override Sink opWrapSink(int flags, Sink sink) { return new Sink.ChainedReference (sink) { @Override public void accept(P_OUT u) { downstream.accept(mapper.apply(u)); } }; } };
实施的相关部分在java.util.stream.SortedOps.RefSortingSink
这里:
@Override public void begin(long size) { if (size >= Nodes.MAX_ARRAY_SIZE) throw new IllegalArgumentException(Nodes.BAD_SIZE); list = (size >= 0) ? new ArrayList((int) size) : new ArrayList (); } @Override public void end() { list.sort(comparator); downstream.begin(list.size()); if (!cancellationWasRequested) { list.forEach(downstream::accept); } else { for (T t : list) { if (downstream.cancellationRequested()) break; downstream.accept(t); } } downstream.end(); list = null; } @Override public void accept(T t) { list.add(t); }
如您所见,整个列表上的已排序传递传递到链中的下一个操作(下一个操作称为downstream
)。但是,映射操作会接收其接收的任何内容,使用映射功能,然后将其传递给下游。这意味着,如果仅使用map,您将获得懒惰的预期行为,而如果使用sorted,则整个已排序的流都将被塞进map的喉咙中list.forEach(downstream::accept)
,而map不能拒绝接受它,或者只能接受它的一部分。