参考并建议阅读:《Java 8函数式编程》
转载请注明出处:http://blog.youkuaiyun.com/cuiods/article/details/53676090
一、什么是函数式编程
函数式编程的核心是:在思考问题时,使用不可变值和函数,函数对一个值进行处理,映射成另一个值。
为支持函数式编程,Java8 引入了Lambda表达式。我对Java8中的Lambda表达式的理解是,使用匿名函数替换Java中的样本代码(匿名内部类)。
Runnable noArguments = () -> System.out.println("Hello World");
BinaryOperator<Long> add = (x, y) -> x + y;
BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;
Lambda表达式的类型依赖于上下文环境,是由编译器推断出来的。在引用外部变量时,不管是否声明为final(Java8取消了必须声明为final的限制),这个变量还是要是终态的,否则编译器会报错(local variables referenced from a Lambda expression must be final or effectively final)。也就是说,Lambda表达式在引用外部变量时引用的是值,而不是变量。
二、流(Stream)
1、内部迭代与外部迭代
在操作集合类时,我们通常需要编写大量代码。比如在计算来自伦敦的艺术家人数时,我们通常的写法是
int count = 0;
for (Artist artist : allArtists) {
if (artist.isFrom("London")) {
count++;
}
}
这样的迭代叫做外部迭代,外部迭代的实际原理是获取集合的迭代器Iterator,使用Iterator来控制迭代过程。而内部迭代的含义是迭代在集合内部控制下进行。Stream(流)是用函数式编程方法在集合类上进行复杂操作的工具,使用Stream可以将上面的迭代改写为:
long count = allArtists.stream()
.filter(artist -> artist.isFrom("London"))
.count();
在这种方法中,filter只是刻画描述了stream,并没有产生新的集合,它的返回值还是stream,这称为惰性求值方法,而count这样从stream产生值的方法叫做及早求值方法,这样的方法返回值一般为空或另一个值。
2、常用的流操作
(1)collect(toList())
collect(toList())方法由Stream里的值生成一个列表,是一个及早求职方法。
List<String> collected = Stream.of("a","b","c")
.collect(Collectors.toList());
(2)map
如果有一个函数可以将一种类型的值转换成另一种类型,map操作就可以使用该函数,将一个流中的值转换成一个新的流。
List<String> collected = Stream.of("a", "b", "hello")
.map(string -> string.toUpperCase())
.collect(toList());
assertEquals(asList("A", "B", "HELLO"), collected);
传给map的lambda表达式必须是实现Function接口的一个实例。
(3)filter
关于filter上面已经给过示例,很多情况下我们需要在列表中选出符合条件的某几项,这一类代码称为filter模式,其核心思想是保留stream中的一些元素而过滤掉其他的元素。
传给filter的lambda表达式必须是实现Predicate接口的一个实例。
(4)max和min
List<Track> tracks = asList(new Track("Bakai", 524),
new Track("Violets for Your Furs", 378),
new Track("Time Was", 451));
Track shortestTrack = tracks.stream()
.min(Comparator.comparing(track->track.getLength()))
.get();
min或max的输入参数是一个Comparator对象,Java8提供了新的方法comparing,我们需要提供需要比较内容的存取方法就可以实现比较器。
(5)reduce
reduce操作可以从一组值中生成一个值。上面示例的count、min、max其实都是reduce操作。
int count = Stream.of(1,2,3)
.reduce(0, (acc,element) -> acc + element);
第一个参数0为初始值,第二个参数lambda表达式是一个BinaryOperator实例。
3、多次调用流操作
尽量使用流的链式调用,而不是每一步强制对函数求值。
错误使用流的例子:
List<Artist> musicians = album.getMusicians()
.collect(toList());
List<Artist> bands = musicians.stream()
.filter(artist -> artist.getName().startsWith("The"))
.collect(toList());
Set<String> origins = bands.stream()
.map(artist -> artist.getNationality())
.collect(toSet());
正确的写法应该是:
Set<String> origins = album.getMusicians()
.filter(artist -> artist.getName().startsWith("The"))
.map(artist -> artist.getNationality())
.collect(toSet());
使用链式调用可以提高可读性和效率,便于自动并行化处理。
参考并建议阅读:《Java 8函数式编程》