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> | T | void | Consumer<Integer> c = (int x) -> { System.out.println(x) }; |
Supplier<T> | None | T | Supplier<Person> supplier = Person::new; |
Function<T,R> | T | R | Function<String, Integer> fuc = Integer::parseInt; |
BiFunction<T, R, U> | T, R | U | BiFunction<Integer, Integer, Integer> fuc = (x, y) -> x + y; |
Predicate<T> | T | boolean | Predicate<Person> p = (Person person) -> { "zhangsan".equals(person.getName()) }; |
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<String> stream1 = Stream.of("1", "2", "3");
// method 2
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
Stream<String> stream2 = list.stream();
// method 3
Stream<String> stream3 = list.parallelStream();
// method 4
Stream<String> 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<T> action)
public static void main(String[] args) {
// 输出Stream中的元素
Stream.of("1","2","3","4","5").forEach(e->System.out.println(e));
}
2 map(Function<T, R> mapper)
public static void main(String[] args) {
Stream.of("1","2","3","4","5")
.map(Integer::parseInt) //转成int
.forEach(System.out::println);
}
3 flatMap(Function<T, Stream<R>> mapper): 会将每一个输入对象输入映射为一个新集合,然后把这些新集合连成一个大集合
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<T> predicate)
// 可以用and()、or()逻辑函数来合并Predicate
Predicate<String> p1 = (n) -> n.startsWith("chang"); //Predicate: 验证传进来的参数符不符合规则, 返回true|false
Predicate<String> p2 = (n) -> n.length() == 5;
names.stream()
.filter(p1.and(p2))
.forEach((n) -> System.out.print(n));
7 forEachOrdered(Predicate<T> 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 可用于返回列表或字符串
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> 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<Person> personList = Lists.newArrayList();
// 同名分组
Map<String, List<Person>> map = personList.stream().collect(Collectors.groupingBy(Person::getName));
// 根据唯一id映射, 保证Person::getId是唯一的,否则报java.lang.IllegalStateException
Map<Integer, Person> map = personList.stream().collect(Collectors.toMap(Person::getId, p -> p));
9 更多例子
String k = "key";
HashMap<String, Integer> map = new HashMap<>() ;
map.put(k, 1);
map.merge(k, 2, (oldVal, newVal) -> oldVal + newVal);
// 等同如下代码
String k = "key";
HashMap<String, Integer> 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<Apple> cmp = Comparator.comparing(Apple::getWeight),该表达式表示比较苹果的重量,如果想对苹果按重量递减排序,此时无需重新定义比较器,接口有一个默认方法reversed可以使给定的比较器逆序。因此仍然用开始的那个比较器,只要简单修改修改一下前一个例子就可以对苹果按重量递减排序: appleList.sort(cmp.reversed());
2 谓词复合
谓词接口包括三个方法:negate、and和or,让我们可以重用已有的Predicate来创建更复杂的谓词,如4.3.6中的filter方法
3 函数复合
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g); // andThen方法会返回一个函数,它先对输入应用一个给定函数,再对输出应用另一个函数
int result = h.apply(1); // 返回结果为4
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.compose(g); // compose方法,先把给定的函数用作compose的参数里面给的那个函数,然后再把函数本身用于结果
int result = h.apply(1); // 返回结果为3
总结
- stream只能遍历一次,遍历完之后,这个流已经被消费掉了。 可以从原始数据源那里再获得一个新的流来重新遍历一遍,重复遍历会报IllegalStateException
List<String> title = Arrays.asList("1", "2", "3");
Stream<String> 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都比较熟悉的基础上,如果写了太复杂的表达式,其他同学维护和理解起来也会增加相应的成本。