java函数式编程:流

java8对核心类库的改进主要包括集合类的API和新引入的流(Stream)
流使程序员得以站在更高层次上对集合进行操作

介绍Stream类中的一组方法,每个方法都对应集合上的一种操作

外部迭代->内部迭代

计算伦敦艺术家人数:

int count = 0;
for(Artist artist : allArtists){
	if(artist.isFrom("London")){
		count++;
	}
}

在集合上进行迭代,对每一个返回元素再进行处理

问题:
每次迭代集合时,都需要写很多样板代码,影响代码可读性

for循环是一个封装了迭代的语法糖,首先调用iterator方法产生一个新的Interator对象,
进而控制整个迭代过程,这就是外部迭代
迭代过程通过显式调用Interator对象的hasNext和next方法完成迭代

int count = 0;
Interator interator = allArtists.interator();
while(interator.hasNext()){
	Artist artist = interator.next();
	if(artist.isFrom("London")){
		count++;
    }
}

外部迭代的问题:
本质上是一种串行操作

使用for循环会将行为和方法混为一谈

内部迭代:

另一种迭代方式就是内部迭代

long count = allArtists.stream()
							.filter(artist -> artist.isFrom("London"))
							.count();

stream()方法和interator()方法作用一样,
该方法返回的并不是一个Interator对象,而是返回内部迭代中的相应接口Stream
Stream是用函数式编程方式在集合类上进行复杂操作的工具

可被分解为两步:
1,filter(artist -> artist.isFrom(“London”))找不所有来自伦敦的艺术家
2,count()计算他们的人数
每种操作都对应Stream接口的一个方法
找出来自伦敦的艺术家,需要对Stream对象进行过滤:filter
(过滤指:只保留通过某项测试的对象,测试由函数完成返回true或false)
count()方法计算给点Stream中包含的对象数量

实现机制

整个过程被分解为两种简单操作:过滤和计数
问题:两种操作是否需要两次循环?
事实上,返回的Stream对象不是一个新的集合,而是创建一个新集合的配方

惰性求值方法和早求值方法:
allArtists.stream()
			.filter(artist -> artist.isFrom("London"))

filter只描述了stream,没有产生新集合,这种方法称为惰性求值方法

而count方法最终会从Stream产生值,这种方法称为及早求值方法

验证惰性求值方法
long 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就是惰性求值方法,
若返回值是一个值或空,就是及早求值方法

整个操作类似于建造者模式,使用一系列操作设置属性和配置,最后调用build方法对象才被真正创建

常用流操作

1,collect(toList()):
由Stream里的值生成一个列表,是一个及早求值操作

Stream的of方法使用一组初始值生成新的Stream

List collected = Stream.of("a", "b", "c")
										.collect(Collectors.toList());

首先由列表生成一个Stream,然后进行Stream的collect操作,由Stream生成列表

2,map

使用for循环将字符串转换为大写

List collected = new ArrayList<>();
for(String string : asList("a", "b", "hello")){
	String uppercaseString = string.toUpperCase();
	collected.add(uppercaseString);
}

使用map操作将字符创转换为大写

List collected = Stream.of("a", "b", "hello")
											.map(string -> string.toUpperCase())
											.collect(collectors.toList)

Lambda表达式必须是Function接口的一个实例,
Function接口是只包含一个参数的普通函数接口,参数和返回值不必属于同一类型

3,filter

遍历数据并检查元素时使用

使用for循环遍历列表,使用条件语句做判断:

List beginningWithNumbers = new ArrayList<>();
for(String value : asList("a", "1abc", "abc1")){
	if(isDigit(value.charAt(0))){// 数字开头
		beginningWithNumbers.add(value);
	}
}

使用函数式风格:

List beginningWithNumbers = Stream.of("a", "1abc", "abc1")
															.filter(value -> isDigit(value.charAt(0)))
															.collect(toList())

for循环中的if是一个很强的信号,可使用filter方法替代
和map相似,filter接受一个函数作为参数,该函数使用Lambda表达式表示,值为true的元素会被保留下来
该Lambda表达式的函数接口是Predicate接口(T->Predicate->boolean)

4,flatMap

将多个Stream连接成一个Stream

List together = Stream.of(asList(1, 2), asList(3, 4))
										.flatMap(numbers -> numbers.stream())
										.collect(toList());

调用.stream()方法将每个列表转换成Stream对象,其余部分由flatMap方法处理
flatMap方法的函数接口也是Function,方法的返回值为Stream类型

5,max和min

在Stream上求最大值和最小值

使用for循环,根据曲目长度,查找最短曲目

List tracks = asList(new Track("abc", 1000),
							new Track("abc", 500),
							new Track("abc", 700))
Track shortestTrack = tracks.get(0);
for(Track track : tracks){
	if(track.getLength() < shortestTrack){
		shortestTrack = track;
	}
}

min()根据曲目长度,查找最短曲目

List tracks = asList(new Track("abc", 1000),
							new Track("abc", 500),
							new Track("abc", 700))
Track shortestTrack = tracks.stream()
									.min(Comparator.comparing(track -> track.getLength()))
									.get();

使用Comparator对象中java8新提供的静态方法comparing,实现一个比较器
comparing方法接受一个函数并返回另一个函数

还可以调用空Stream的max方法,返回Optional对象
Optional对象表示一个可能存在也可能不存在的值
如果Stream为空,该值不存在,如果不为空,则该值存在
通过调用get方法可以取出Optional对象中的值

6,reduce
reduce操作可以实现从一组值中生成一个值
count,min,max都是reduce方法

reduce的累加过程:

每一步都将Stream中的元素进行累加,遍历至Stream中的最后一个元素时,得到累加值

使用reduce求和:

int count = Stream.of(1, 2, 3)
				  .reduce(0, (acc, element) -> acc + element);

传入Stream的两个参数分别是:当前元素和acc,
acc是累加器,保存当前累加结果
Lambda表达式返回值为最新的acc,上一轮acc的值和当前元素的相加结果
reduce的类型是BinaryOperator

展开reduce操作:

BinaryOperator accumulator = (acc, element) -> acc + element
int count = accumuator.apply(
				accumuator.apply(
					accumuator.apply(0, 1), 
				2), 
			3);

命令式编程求和:

int acc = 0;
for(Integer element : asList(1, 2, 3)){
	acc = acc = element;
}

对于集合来说,循环在外部,且需要手动更新

你可能感兴趣的:(Java)