1 简介
Lambda表达式是java8提供的新特性,是一种匿名函数,也是函数式接口实现的快捷方式,类似js中的闭包,它允许把函数当做参数来使用,是面向函数式编程的思想,Lambda的格式为: (参数) -> {方法体},具有如下特征:
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值;
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号;
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号;
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
例子:
// 1 无参数的lambda表达式,返回值5
() -> 5
// 2 一个数值类型的参数,返回其2倍的值
x -> 2 * x
// 3 两个数值类型的参数,返回差值
(x, y) -> x – y
// 4 string类型的参数,在控制台打印,无返回类型
(String s) -> System.out.print(s)
java是一个面向对象的语言,而Lambda表达式却是一个匿名函数,因此java把Lambda表达式抽象成一个匿名内部类,并没有破坏面向对象的特性。
2 函数式接口
java8中通过注解@FunctionalInterface标明接口为一个函数式接口,函数式接口是只包含一个抽象方法声明的接口,但是允许有默认实现的方法(比如: java.util.function.BinaryOperator)。如果定义了多个抽象方法,编译器会报错,如下图。所以,当我们的代码中定义了函数式接口的时候,要记得加上@FunctionalInterface注解,防止被其他人增加额外的方法而破坏函数式接口的规范。
java.lang.Runnable 就是一种函数式接口,每个 Lambda 表达式都能隐式地赋值给函数式接口,例如,我们可以通过 Lambda 表达式创建 Runnable 接口的引用:
Runnable r = () -> System.out.println("hello world");
当不指明函数式接口时,编译器会自动进行类型推断,如下面代码,编译器会自动根据线程类的构造函数签名 public Thread(Runnable r) { },将该 Lambda 表达式赋给 Runnable 接口:
new Thread(
() -> System.out.println("hello world")
).start();
下表列举了一些常用的函数式接口
接口 | 参数 | 返回值 | 示例 |
---|---|---|---|
Consumer |
T | void | Consumer |
Supplier |
None | T | Supplier |
Function |
T | R | Function |
BiFunction |
T, R | U | BiFunction |
Predicate |
T | boolean | Predicate |
3 Lambda表达式的作用
Lambda表达式可以让代码更加简练,具有可读性。
举个栗子,这个例子大家都在举,当创建新线程时,不使用lambda的情况下:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("为了这一行有用的代码,写了那么多多余的行:( ");
}
}).start();
使用lambda表达式:
new Thread( () -> System.out.println("一起摇摆~~ ") ).start();
4 配合Stream让代码整洁到飞起
Stream是Java 8 API添加的一个新的抽象,称为流Stream,可以让我们以一种声明的方式处理数据。Stream类似SQL的存储过程,提供了一种对 Java 集合运算和表达的高阶抽象。
Stream的构成
当我们使用一个流的时候,通常包括三个步骤:
- 获取一个数据源(source),可以是集合,数组;
// method 1
Stream stream1 = Stream.of("1", "2", "3");
// method 2
List list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
Stream stream2 = list.stream();
// method 3
Stream stream3 = list.parallelStream();
// method 4
Stream stream4 = Arrays.stream(new String[]{"1", "2"});
// method 5
IntStream stream5 = IntStream.range(0, 100);
- 数据转换;
- 执行操作获取想要的结果。
流的操作
- 中间操作:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip,操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)
- 最终操作:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
使用
1 forEach(Consumer
public static void main(String[] args) {
// 输出Stream中的元素
Stream.of("1","2","3","4","5").forEach(e->System.out.println(e));
}
2 map(Function
public static void main(String[] args) {
Stream.of("1","2","3","4","5")
.map(Integer::parseInt) //转成int
.forEach(System.out::println);
}
3 flatMap(Function
public static void main(String[] args) {
Stream.of("a-b-c-d","e-f-g-h")
.flatMap(e->Stream.of(e.split("-")))
.forEach(e->System.out.print(e));
}
// abcdefgh
4 limit(long maxSize)
public static void main(String[] args) {
Stream.of(1,2,3,4,5,6)
.limit(3)
.forEach(e->System.out.println(e));
}
// 输出前三个 1,2,3
5 distinct()
Stream.of(1,1,2,3)
.distinct() //去重
.forEach(e->System.out.println(e));
// 1 2 3
6 filter(Predicate
// 可以用and()、or()逻辑函数来合并Predicate
Predicate p1 = (n) -> n.startsWith("chang"); //Predicate: 验证传进来的参数符不符合规则, 返回true|false
Predicate p2 = (n) -> n.length() == 5;
names.stream()
.filter(p1.and(p2))
.forEach((n) -> System.out.print(n));
7 forEachOrdered(Predicate
Stream.of(0,1,2,3,4,5,6,7,8,9)
.parallel()
.forEachOrdered(e->{
System.out.println(Thread.currentThread().getName()+": "+e);});
8 Collectors: 将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串
Liststrings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("筛选列表: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);
// 筛选列表: [abc, bc, efg, abcd, jkl]
// 合并字符串: abc, bc, efg, abcd, jkl
List personList = Lists.newArrayList();
// 同名分组
Map> map = personList.stream().collect(Collectors.groupingBy(Person::getName));
// 根据唯一id映射, 保证Person::getId是唯一的,否则报java.lang.IllegalStateException
Map map = personList.stream().collect(Collectors.toMap(Person::getId, p -> p));
9 更多例子
String k = "key";
HashMap map = new HashMap<>() ;
map.put(k, 1);
map.merge(k, 2, (oldVal, newVal) -> oldVal + newVal);
// 等同如下代码
String k = "key";
HashMap map = new HashMap<>() ;
map.put(k, 1);
int newVal = 2;
if(map.containsKey(k)) {
map.put(k, map.get(k) + newVal);
} else {
map.put(k, newVal);
}
复合lambda表达式
1 比较器复合
可以使用静态方法Comparator.comparing定义比较器,根据提取用于比较的键值的Function来返回一个Comparator,
如: Comparator
2 谓词复合
谓词接口包括三个方法:negate、and和or,让我们可以重用已有的Predicate来创建更复杂的谓词,如4.3.6中的filter方法
3 函数复合
Function f = x -> x + 1;
Function g = x -> x * 2;
Function h = f.andThen(g); // andThen方法会返回一个函数,它先对输入应用一个给定函数,再对输出应用另一个函数
int result = h.apply(1); // 返回结果为4
Function f = x -> x + 1;
Function g = x -> x * 2;
Function h = f.compose(g); // compose方法,先把给定的函数用作compose的参数里面给的那个函数,然后再把函数本身用于结果
int result = h.apply(1); // 返回结果为3
总结
- stream只能遍历一次,遍历完之后,这个流已经被消费掉了。 可以从原始数据源那里再获得一个新的流来重新遍历一遍,重复遍历会报IllegalStateException
List title = Arrays.asList("1", "2", "3");
Stream s = title.stream();
s.forEach(System.out::println);
s.forEach(System.out::println); // Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
- 简洁的lambda可以增加代码可读性,但这简历在团队成员对lambda都比较熟悉的基础上,如果写了太复杂的表达式,其他同学维护和理解起来也会增加相应的成本。