流是数据序列,通过顺序或并行的方式对其进行一系列操作(通常用lambda表达式表示),用来筛选、转换、排序、减少或构造新的数据结构。流在Java 8中引入,是此版本中最重要的特性之一。
流基于Stream接口和java.util.stream包中的相关类和接口。它们还在许多类中引入新方法,通过不同的数据结构生成流。可以从实现Collection接口的每个数据结构创建Stream接口:File、Director、Array和其它许多源。
Java还包括从自定义源中创建流的不同机制,最重要的如下所示:
- Supplier接口:此接口定义get()方法,当需要处理其它对象时,通过Stream调用。可以使用Stream类的generate()静态方法从Supplier接口创建Stream。考虑到这个源可能是无限的,所以必须使用limit()或类似的方法来限制Stream中的元素数量。
- Stream.Builder接口:此接口提供accept()和add()方法来添加元素到Stream和build()方法,返回之前增加元素创建的Stream接口
- Spliterator接口:此接口定义了遍历和拆分源元素所需的方法,可以使用StreamSupport类的stream()方法生成Stream接口来处理Spliterator元素。
本节将学习如何实现自定义Spliterator接口,以及如何创建Stream接口来处理数据。我们将使用元素矩阵,正常的Steam接口应该一次处理一个元素,但是我们将使用Spliterator类一次处理一行。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤实现范例:
-
创建名为Item的类,继承矩阵每个元素的信息。包括三个私有属性:名为name的String属性和名为row、column的两个整型属性。创建获取和设置这些属性值的方法,此类代码很简单,不在这里列出。
-
创建名为MySpliterator的类,指定其实现Item类参数化的Spliterator接口。此类包括四个属性:名为items的Item对象矩阵和三个名为start、end和current的整型属性,分别存储待通过这个Spliterator接口处理的第一个和最后一个元素,以及正在被处理的当前元素。实现类构造函数,初始化这些属性:
public class MySpliterator implements Spliterator<Item> { private Item[][] items; private int start, end, current; public MySpliterator(Item[][] items, int start, int end) { this.items=items; this.start=start; this.end=end; this.current=start; }
-
实现characteristics(),此方法返回描述Spliterator行为的int值。这个值的含义将在后面的“工作原理”中讲解:
@Override public int characteristics() { return ORDERED | SIZED | SUBSIZED; }
-
实现estimatedSize()。此方法将返回通过这个Spliterator接口处理的元素数量,通过计算end和current属性值的差实现:
@Override public long estimateSize() { return end - current; }
-
现在实现tryAdvance()方法,调用此方法试图处理Spliterator()的一个元素。tryAdvance()方法的输入参数是实现Consumer接口的对象。这个接口将通过Stream API调用,所以只需要考虑其实现即可。如本节引言所述,我们有一个Item对象矩阵,且每次处理一行,接收到的Consumer函数将处理Item对象。因此,如果Spliterator接口始终有元素处理,我们将使用Consumer函数的accept()方法处理当前行的所有item元素:
@Override public boolean tryAdvance(Consumer<? super Item> consumer) { System.out.printf("MySpliterator.tryAdvance.start: %d, %d, %d\n", start,end,current); if (current < end) { for (int i=0; i<items[current].length; i++) { consumer.accept(items[current][i]); } current++; System.out.printf("MySpliterator.tryAdvance.end:true\n"); return true; } System.out.printf("MySpliterator.tryAdvance.end:false\n"); return false; }
-
接下来实现forEachRemaining(),此方法将接收Consumer接口的实现,并将此函数应用于Spliterator的剩余元素。本范例中,将对所有剩余的元素调用tryAdvance()方法:
@Override public void forEachRemaining(Consumer<? super Item> consumer) { System.out.printf("MySpliterator.forEachRemaining.start\n"); boolean ret; do { ret=tryAdvance(consumer); } while (ret); System.out.printf("MySpliterator.forEachRemaining.end\n"); }
-
最后实现trySplit()方法,并行流将调用此方法把Spliterator拆分为两个子集。它将返回新的Spliterator对象,包含其它线程待处理的元素,当前线程将此处理剩余的元素。如果spliterator对象无法拆分,需要返回null值。本范例中,将计算需要处理的在中间的元素。前半部分由当前线程处理,后半部分通过其它线程处理:
@Override public Spliterator<Item> trySplit() { System.out.printf("MySpliterator.trySplit.start\n"); if (end-start<=2) { System.out.printf("MySpliterator.trySplit.end\n"); return null; } int mid=start+((end-start)/2); int newStart=mid; int newEnd=end; end=mid; System.out.printf("MySpliterator.trySplit.end: %d, %d, %d, %d, %d, %d\n",start, mid, end, newStart, newEnd, current); return new MySpliterator(items, newStart, newEnd); } }
-
现在添加main()方法,实现本范例主类。首先,声明和初始化10行乘10列的Item对象:
public class Main { public static void main(String[] args) { Item[][] items; items= new Item[10][10]; for (int i=0; i<10; i++) { for (int j=0; j<10; j++) { items[i][j]=new Item(); items[i][j].setRow(i); items[i][j].setColumn(j); items[i][j].setName("Item "+i+" "+j); } }
-
然后,创建MySpliterator对象处理矩阵所有的元素:
MySpliterator mySpliterator=new MySpliterator(items, 0, items.length);
-
最后,使用StreamSupport类的stream()方法从Spliterator中创建流。将true值作为第二个参数传递,指明流是并行操作的。然后,使用Stream类的forEach()方法输出每个元素的信息到控制台:
StreamSupport.stream(mySpliterator, true).forEach( item -> { System.out.printf("%s: %s\n",Thread.currentThread() .getName(),item.getName()); }); } }
工作原理
范例的主元素是Spliterator,这个接口定义了用来处理和划分要使用的元素源的方法,例如Stream对象的源。很少需要直接使用Spliterator对象,只有当想要不同的行为时—也就是说,如果想要实现自定义的数据结构并从中创建Stream——使用Spliterator对象。
Spliterator有一组定义其行为的特征,如下所示:
- CONCURRENT:能够同时安全地修改的数据源
- DISTINCT:数据源的所有元素都是不同的
- IMMUTABLE:数据源中能够添加、删除或者替换元素
- NONNULL:数据源中没有null元素
- ORDERED:在数据源元素中有相遇排序
- SIZED:通过estimateSize()方法返回的值是Spliterator的确切大小。
- SORTED:对Spliterator的元素进行排序
- SUBSIZED:调用trySplit()方法之后,能够得到Spliterator两个部分的确切大小
本范例中,我们定义了Spliterator的DISTINCT、IMMUTABLE、NONNULL、ORDERED、SIZED和SUBSIZED特性。
然后,我们实现了Spliterator接口定义的所有没有默认实现的方法:
- characteristics():此方法返回Spliterator对象的特征。具体来说,它返回一个整数值,可以使用Spliterator对象的各个特征之间的位运算符或运算符(|)进行计算。需要注意返回值应该与Spliterator对象的实际特征一致。
- estimatedSize():如果在当前时刻调用forEachRemaining()方法,此方法将返回元素的数量。本范例中,我们返回已知的确切值,但是方法定义涉及到估计大小。
- tryAdvance():此方法将指定为参数的函数应用到待处理的下一个元素,如果有的话,并返回true。如果没有元素待处理,则返回false。本范例中,此方法接收处理一个Item对象的Consumer,但同一时间需要处理一行Item对象,所以转换行的所有元素,并且调用Consumer的accept()方法。
- trySplit():此方法用来把当前Spliterator分成两个不同部分,以便每一部分能够被不同的线程处理。理想情况下,应该将数据源分成两部分,每部分包含相同数量的元素。但本范例中,我们计算开始和结束位置中间的元素,生成了两个元素块。开始到中间部分的元素由当前的Spliterator处理,中间到结束部分的元素由新的Spliterator对象处理。如果无法拆分数据源,此方法返回空值。本范例中,Spliterator只有两个元素,所以不会拆分。
Spliterator接口的其它方法都有一个默认实现,但我们重写了forEachRemaining()方法。此方法将接收到的函数作为参数(Consumer接口实现),应用到尚未处理的Spliterator元素。我们实现了自定义的信息输出到控制台,并且使用tryAdvance()方法处理每个独立项目。
下图显示本范例在控制台输出的部分执行信息:
首先,调用trySplit()方法拆分数据源,然后调用forEachRemaining()方法来处理trySplit()方法生成的每个Spliterator的所有元素。
扩展学习
我们可以从不同的数据源获得Spliterator接口的实现。BaseStream类提供了spliterator()方法,此方法从流的元素返回spliterator。其它数据源,如ConcurrentLinkedDeque、ConcurrentLinkedQueue或者Collection,也提供了spliterator()方法获得接口实现,用来处理这些数据结构的元素。
更多关注
- 第六章“并行和响应式流”中的“创建不同来源的流”小节