一. Java8特性详解之lambda表达式

  • 前言
  • 1、Lambda是什么?
  • 2、Lambda如何使用?
  • 1.使用之前写遍历
  • 普通写法
  • 优雅写法
  • 2.使用之后写遍历
  • lambda表达式和Stream的配合写法
  • 3、Lambda解读
  • 1. Lambda的简化写法
  • 2. Lambda的外部引用
  • 最后


前言

本文主要介绍Java8对屌丝码农最有吸引力的一个特性—lambda表达式。

1、Lambda是什么?

官方解释

a function (or a subroutine) defined, and possibly called, without being bound to an identifier。

其实通俗点解释:就是一段带有输入参数的可执行语句块,不用被绑定到一个标识符上,就可能被调用的函数。

2、Lambda如何使用?

1.使用之前写遍历

普通写法
List<String> markList = new ArrayList<>();
        markList.add("A");
        markList.add("B");
        markList.add("C");
        List<String> lowercaseMark = new ArrayList<>();
        for (String mark : markList) {
            lowercaseMark.add(mark.toLowerCase());
        }
优雅写法
List<String> markList2 = new ArrayList<>();
        markList2.add("A");
        markList2.add("B");
        markList2.add("C");
        List<String> lowercaseMark2 = FluentIterable.from(markList2).transform(new Function<String, String>() {
            @Override
            public String apply(String mark) {
                return mark.toLowerCase();
            }
        }).toList();

这两种编程风格称为命令式编程和声明式编程(自行百度),具体孰优孰劣不好说,如果能摒弃习惯的命令式编程,寻求声明式的写代码方式,归纳提取好的实现,我们会站在更高层面写代码。

2.使用之后写遍历

lambda表达式和Stream的配合写法
List<String> markList3 = new ArrayList<>();
        markList3.add("A");
        markList3.add("B");
        markList3.add("C");
        List<String> lowercaseMark3 = markList3.stream().map((String mark) -> {
        return mark.toLowerCase();
        }).collect(Collectors.toList());
        //这段代码就是对一个字符串的列表,把其中包含的每个字符串都转换成全小写的字符串。注意代码中的map方法调用,这里map方法就是接受了一个lambda表达式(其实是一个java.util.function.Function的实例)。

这里先大致解释一下什么是Stream,具体下个章节讲
官方解释:

A sequence of elements supporting sequential and parallel aggregate operations.
  //Stream是元素的集合,这点让Stream看起来用些类似Iterator;
  //可以支持顺序和并行的对原Stream进行汇聚的操作;

其实通俗点解释:Stream可以当成一个高级版本的Iterator。Iterator用户只能一个一个的遍历元素并对其执行某些操作;而Stream用户只要给出需要对其包含的元素执行什么操作,具体这些操作如何应用到每个元素上,就给Stream就好了!

3、Lambda解读

1. Lambda的简化写法

(1)参数类型省略–绝大多数情况,编译器都可以从上下文环境中推断出lambda表达式的参数类型。这样lambda表达式就变成了:

(param1,param2, …, paramN) -> {
 statment1;
 statment2;
 //…
 return statmentM;
 }


所以我们最开始的例子就变成了(省略了List的创建):

List<String> lowercaseMark3 = markList3.stream().map((mark) -> {
return mark.toLowerCase();
}).collect(Collectors.toList());

(2)当lambda表达式的参数个数只有一个,可以省略小括号。lambda表达式简写为:

param1 -> {
 statment1;
 statment2;
 //…
 return statmentM;
 }


所以我们最开始的例子就变成了(省略了List的创建):

List<String> lowercaseMark3 = markList3.stream().map(mark -> {
return mark.toLowerCase();
}).collect(Collectors.toList());

(3)当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的分号。lambda表达式简化为:
param1 -> statmentM
所以我们最开始的例子就变成了(省略了List的创建):

List<String> lowercaseMark3 = markList3.stream().map(mark ->
 mark.toLowerCase()
 ).collect(Collectors.toList());

2. Lambda的外部引用

上面的例子,只看到可以访问给它传递的参数,也能自己内部定义变量。lambda表达式如何访问其外部变量?

String[] array = {"a", "b", "c"};
for(Integer i : Lists.newArrayList(1,2,3)){
  Stream.of(array).map(item -> 
    Strings.padEnd(item, i, '@')
  ).forEach(System.out::println);
}

这个例子中,map中的lambda表达式访问外部变量Integer i。并且可以访问外部变量是lambda表达式的一个重要特性,这样我们可以看出来lambda表达式的三个重要组成部分:

输入参数
可执行语句 
存放外部变量的空间

不过lambda表达式访问外部变量有一个非常重要的限制:变量不可变(只是引用不可变,而不是真正的不可变)。

String[] array = {"a", "b", "c"};
for(int i = 1; i<4; i++){
  Stream.of(array).map(item -> 
    Strings.padEnd(item, i, '@')
  ).forEach(System.out::println);
}

上面的代码,会报编译错误。因为变量i被lambda表达式引用,所以编译器会隐式的把其当成final来处理。回忆一下以前java的匿名内部类在访问外部变量的时候,外部变量必须用final修饰。Bingo,在java8对这个限制做了优化,可以不用显示使用final修饰,但是编译器隐式当成final来处理。