一、从外部迭代到内部迭代
使用for循环计算伦敦的艺术家人数:
int count=0; for(Artist artist:allArtists){ if(artist.isFrom("London")){ count++; } }每次迭代集合类时,都需要写较多样本代码。
调用iterator,产生一个新的Iterator对象,进而控制整个迭代过程,这就是外部迭代。
int count=0; Iterator<Artist> iterator=allArtists.iterator(); while(iterator.hasNext()){ Artist artist=iterator.next(); if(artist.isFrom("London")) { count++; } }
再来看内部迭代。
1 long count=allArtists.stream() 2 .filter(artist->artist.isFrom("London")) 3 .count();
stream是函数式编程方式在集合类上进行复杂操作的工具。这里的stream()方法的调用和iterator调用的方法类似,但是该方法返回的是一个接口:Stream,而不是一个对象。
二、实现机制
这里对上述内部迭代代码进行分析:
allArtists.stream().filter(artist->artist.isFrom("London"));这行代码中filter只刻画了Stream,但没有产生新的集合。这种不产生新集合的方法叫作惰性求值方法;而像count这样会产生值得方法叫作及早求值方法。
这样我们添加一两个例子:
allArtists.stream().filter(artist->{ System.out.println(artist.getName()); return artist.isFrom("London"); });//这里不会出项任何显示结果long count=allArtists.stream().filter(artist->{ System.out.println(artist.getName()); return artist.isFrom("London"); }).count(); //这里会显示出成员艺术家列表名单判断很简单:返回值是否为Stream。
整个过程和建造者模式有共通之处。
三、常用的流操作
1、collec(toList()) 方法由Stream里的值生成一个列表,是一个及早求值操作。Stream的of方法使用一组初始值生产新的Steam。
List<String> collected = Stream.of("a","b","c").collect(Collectiors.toList()); assertEquals(Array.asList("a","b","c"),collected);
2、map
map可以将一个流中的值转换为成一个新的流。
首先看一下一个平时所见的例子:
//for循环将字符串转换为大写List<String> collected=new ArrayList<>(); for(String string:asList("a","b","hello")){ String uppercaseString=string.toUpperCase(); collected.add(upercaseString); } assertEquals(asList("A","B","HELLO"),colected);//使用map操作将字符串转换为大写形式 List<String> collected=Stream.of("a","b","c").map(string->string.toUpperCase()).collect(toList()); assertEquals(asList("A","B","C"),collected);
3、filter
遍历数据并检查其中的元素时,可尝试使用stream中提供的新方法filter,通map有点类似
使用循环遍历列表,使用条件语句判断
List<string>buginningWithNumbers=new ArrayList<>(); for(String value:asList("a","1abc","abc1")) { if(isDigit(value.charAt(0)) { buginningWithNumbers.add(value); } }函数式风格:
List<String>beginningWithNumbers=Stream.of("a","1abc","abc1").filter(value->isDigit(value.charAt(0))).collect(toList());
4、flatMap
调用stream方法,将每个列表装换成Stream对象,其余不封由flatMap方法处理。例子如下:
List<Integer>together=Stream.of(asList(1,2),asList(3,4)).flatMap(numbers->numbers.stream()).collect(toList()); assertEquals(asLsit(1,2,3,4),together);
5、max和min
Stream上常用的操作之一是求最大值和求最小值。
使用Stream查找最短曲目:
List<Track>tracks=asList( new Track("Love",524), new Track("For my love",378), new Track("For Your Love",451)); Track shortestTrack=track.stream().min(Comparator.comparing(track->track.getLength())).get();assertEquals(tracks.get(1),shortestTrack);
6、通用模式
for循环查找专辑中最短的曲目
List<Track> tracks =asList( new Track("Dangers",524), new Track("Violets for Your Fures",378), new Track("Lost",451) ); Track shorttestTrack=track.get(0); for(Track track:tracks){ if(track.getLength()<shortestTrack.getLength()){ shortestTrack=track; } }
另外一种reduce模式
Object accumulato=initialValue; for(Object element : collection) { accumulator=combine(accumulatror,element); }
7、reduce
reduce操作可以实现从一个值中生成一个值。在上述例子中用到count、mun和max方法,因为常用被纳入标准库中。这些其实都是reduce操作。
使用reduce求和
int count =Stream.of(1,2,3).reduce(0,(acc,element)->acc+element); assertEquals(6,count);Lambda表达式返回值是最新的acc,是上一轮acc的值和当前元素相加的结果。reducer的类型BinaryOperator;
下面展开reduce操作
BinaryOperator<Integer> accumulator=(acc,element)->acc+element; int count=accumulator.apply(accumulator.apply(accumulator.apply(0,1),2),3)
8、整合操作
Stream()方法众多,在此我们介绍了Collect,map,filter,flatMap,max,min。在这儿我们总结一个例子来供自己理解。
第一个要解决的问题是,找出某张专辑上所有乐队的国籍。艺术家列表里既有个人,也有乐队。利用一点领域知识,假定一般乐队名以定冠词The开头。当然不是绝对的,但也差不多。
需要注意的是,这个问题绝对不是简单地调用几个API就足以解决。这既不是使用map将一组值映射为另一组值,也不是过滤,更不是将一个Stream中的元素最终归约为一个值。首先,可以将这个问题分解为以下步骤。
1、找出专辑上所有的表演者。
2、分辨出哪些表演者是乐队。
3、找出每个乐队的国籍。
4、将找出的国籍放入一个集合。
现在,找出每一步对应的Stream API也就相对容易了:
1、Album类有个getMusicians方法,该方法返回一个Stream对象,包含整张专辑中所有的表演者;
2、使用filter方法对表演者进行过滤,值保留乐队;
3、使用map方法将乐队映射为其所属国家;
4、使用collect(Collectors.toList())方法将国籍放入一个列表。
最后,整合所有的操作,就得到如下代码:
Set<String> origins=album.getMusicians() .filter(artist->artist.getName() .startsWith("Th")) .map(artist->artist.getNationality()) .collect(toSet());