JDK1.8 Lambda表达式与Stream

一、概述

      jdk1.8对Lambda 表达式的支持,了Stream以实现对集合更方便地进行函数式编程。本文主要介绍jLambda表达式和Stream的一些常用使用方式,并通过一些代码小例子向大家展示怎么使用。

二、函数式接口

    什么时函数式接口?

    函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

   怎么写函数接口?

   Java 1.8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注解可用于一个接口的定义上,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。

    JDK 1.8之前已有的函数式接口:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.security.PrivilegedAction
  • java.util.Comparator
  • java.io.FileFilter
  • java.nio.file.PathMatcher
  • java.lang.reflect.InvocationHandler
  • java.beans.PropertyChangeListener
  • java.awt.event.ActionListener
  • javax.swing.event.ChangeListener

    JDK1.8后新加的函数式接口

接口

说明

BiConsumer 代表了一个接受两个输入参数的操作,并且不返回任何结果
BiFunction 代表了一个接受两个输入参数的方法,并且返回一个结果
BinaryOperator 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果
BiPredicate 代表了一个两个参数的boolean值方法
BooleanSupplier 代表了boolean值结果的提供方
Consumer 代表了接受一个输入参数并且无返回的操作
Function 接受一个输入参数,返回一个结果
Predicate 接受一个输入参数,返回一个布尔值结果。
Supplier 无参数,返回一个结果。

  这些接口有很多,这里就不再一 一的说明,这些接口主要分为四大类supplier生产数据函数式接口、Consumer消费数据函数式接口、Predicate判断函数式接口、Function类型转换函数式接口。

三、Lambda表达式

      其实Lambda表达式的本质只是一个"语法糖",由编译器推断并帮你转换包装为常规的代码,因此你可以使用更少的代码来实现同样的功能。

      Lambda 表达式语法

      Lambda表达式又三部分组成,(参数列表-》->(表达式或者代码块)

     如下例子:

(int x, int y) -> x + y

() -> 42

(String s) -> { System.out.println(s); }

 Lambda表达式等价一个匿名内部类,只不过这里只能是一个实现了函数式接口的一个匿名内部类

public class RunnableTest {

    @Test
    public void RunnableLambdaTest(){

        // 使用匿名内部类形式运行
        Runnable r1 = new Runnable() {
            public void run() {
                System.out.println("this is r1");
            }
        };

        // 执行内部方法, 并且没有传递任何参数
        Runnable r2 = () -> System.out.println("this is r2");

        // 运行
        r1.run();
        r2.run();
    }
}

三、Stream 

什么是流?

Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。

Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。

而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。

接下来,当把一个数据结构包装成 Stream 后,就要开始对里面的元素进行各类操作了。常见的操作可以归类如下。

中间操作:

  • map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

终端操作:

  • forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

下面我用一个个例子来对这些操作进行介绍、在介绍这些操作前我们先声明一个简单的User类,所有的Stream操作都是基于这个User集合进行介绍。

package com.sun.liems.model;

/***
 * 人员类主要用于lambda表达式和Stream练习使用
 * @author swh
 *
 */
public class User {
	// 账号ID
	private String userId;
	// 用户姓名
	private String userName;
	// 性别
	private String sex;
	// 年龄
	private int age;
	
	/***
	 * 构造器
	 * @param userId 用户id
	 * @param userName 姓名
	 * @param sex 性别
	 * @param age 年龄
	 */
	public User(String userId,String userName,String sex,int age) {
		this.userId = userId;
		this.userName =userName;
		this.sex = sex;
		this.age = age;
	}
	public String getUserId() {
		return userId;
	}
	public void setUserId(String userId) {
		this.userId = userId;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "账号:"+this.userId+" 姓名:"+this.userName+" 性别"+this.sex+" 年龄"+this.age;
	}
}

1、map

可以将一种类型的值转换成另外一种类型,map 操作就可以使用该函数,将一个流中的值转换成一个新的流。其实就是就是实现一个Function接口。

   Stream map(Function mapper);

 把一个人员转换成一个字符串

/***
	 * 把一个人员转换成一个字符串
	 * @return
	 */
	public static List map(){
		return userList.stream()
		        .map(s->{
		        	return s.toString();
		        })
		        .collect(Collectors.toList());
	}

2、flatMap

  flatmap 和map功能差不多,区别在于map的输出对应一个元素,必然是一个元素(null也是要返回),flatmap是0或者多个元素(为null的时候其实就是0个元素)。 flatmap的意义在于,一般的java方法都是返回一个结果,但是对于结果数量不确定的时候,用map这种java方法的方式,是不太灵活的,所以引入了flatmap。 flatMap可以把多个流程返回为一个流。

使用场景:如果map返回的一个数组或者是集合,可以用flatmap这些返回转换为同一个流。进行扁平化处理。

public static void flatMap(){
        String[] words = new String[]{"Hello","World"};
        List a = Arrays.stream(words)
                .map(word -> word.split(""))
                .flatMap(s->Arrays.stream(s))
                .collect(Collectors.toList());
        a.forEach(System.out::print);
	}

3、filter

filter操作用于数据过滤。

去除人员数组中的所有女性

public static List filter(){
		return userList.stream()
		        .filter(s->s.getSex().equals("男"))
		        .collect(Collectors.toList());
	}

4、distinct

distinct操作用于数据去重。distinct使用hashCode和equals方法来获取不同的元素。因此,我们的类必须实现hashCode和equals方法。

public static List distinct(){
		return userList.stream()
		        .distinct()
		        .collect(Collectors.toList());
	}
@Override
	public boolean equals(Object o) {
		User u = (User) o;
		return this.getUserId().equals(u.getUserId());
	} 
	@Override
	public int hashCode() {
		return this.getUserId().hashCode();
	}

5、sorted

sorted操作用于以自然序排序

把人员按年龄排序

public static List sorted(){
		return userList.stream()
		        .sorted(Comparator.comparing(s->s.getAge()))
		        .collect(Collectors.toList());
	}

6、peek

peek接收一个没有返回值的λ表达式,可以做一些输出,外部处理等。map接收一个有返回值的λ表达式,之后Stream的泛型类型将转换为map参数λ表达式返回的类型。和forEach是有所区别的。peek是一个中间操作而forEach中间操作

public static List peek(){
		return userList.stream()
				.filter(s->s.getSex().equals("男"))
				.peek(s->System.out.println(s))
		        .collect(Collectors.toList());
	}

7、limit

 limit: 对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素;

    /***
	 * 取前4个用户
	 * @return
	 */
	public static List limit(){
		return userList.stream()
                .limit(4)
				.collect(Collectors.toList());
	}

8、skip

skip: 返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,那么返回空Stream;

	/***
	 * 取出了前4个后面的用户
	 * @return
	 */
	public static List skip(){
		return userList.stream()
                .skip(4)
				.collect(Collectors.toList());
	}

9、forEach

作用是对容器中的每个元素执行action指定的动作,也就是对元素进行遍历。

把所有人员打印出来

10、reduce

reduce 操作可以实现从一组值中生成一个值。在上述例子中用到的 count、min 和 max 方法,因为常用而被纳入标准库中。事实上,这些方法都是 reduce 操作。

	/***
	 * 求所有人员年龄的合
	 */
	public static void reduce() {
		int ageSum = userList.stream()
		        .map(u->u.getAge())
		        .reduce(0,(sum,u)-> sum+u);
		System.out.println(ageSum);
	}

11、max及min

  求最大值和最小值,此时max及min接受的是Comparator comparator

	/***
	 * 求年龄最大的人员
	 */
	public static void max() {
		User user = userList.stream()
		        .max(Comparator.comparing(u->u.getAge()))
		        .get();
		System.out.println(user);
	}

12、collect

collect也就是收集器,是Stream一种通用的、从流生成复杂值的结构。只要将它传给collect方法,也就是所谓的转换方法,其就会生成想要的数据结构。这里不得不提下,Collectors这个工具库,在该库中封装了相应的转换方法。当然,Collectors工具库仅仅封装了常用的一些情景,如果有特殊需求,那就要自定义了。

方法 用途
toList 把结果转换为一个List
toSet 把结果转换为一个Set
groupingBy 分类用,例如按某个属性分类转换为一个map
toMap  
    /**
	 * 把人员按性别分组
	 *
	 */
	public static void collect() {
		Map> map = userList.stream()
		            .collect(Collectors.groupingBy(u->u.getSex()));
		System.out.println(map.size());
	}
	/***
	 * 得到人员账号和年龄的对应关系
	 */
	public static void toMap() {
		Map map = userList.stream()
		            .collect(Collectors.toMap(u->u.getUserId(), u->u.getUserName()));
		map.entrySet()
		   .forEach(e->{
			   System.out.println(e.getKey()+":"+e.getValue());
		   });
	}

    使用stream的toMap()函数时,当key重复,系统会报错相同的key不能形成一个map,那么需要解决这个问题,一:相同key的情况下,丢弃重复的只保留一个。

13、匹配方法
anyMatch:匹配上任何一个则返回 Boolean 
allMatch:匹配所有的元素则返回 Boolean

noneMatch跟allMatch相反,判断条件里的元素,所有的都不是,返回true

Optional是一个可以包含或不可以包含非空值的容器对象,

    /***
	 * 匹配
	 */
	public static void match() {
		 // 是否包含张三
		 boolean flagA  = userList.stream()
		            .anyMatch(u->u.getUserName().equals("张三"));
		 System.out.println( flagA);
		 // 是否所以人都是18岁
		 boolean flagB  = userList.stream()
		            .allMatch(u->u.getAge()==18);
		 System.out.println( flagB);
		 // 是否所有人都是成年人
		 boolean flagC  = userList.stream()
		            .noneMatch(u->u.getAge()<18);
		 System.out.println(flagC);

	}

四、总结

    steam提供的一些方法,简化了我们对集合的操作,让我们只关注代码逻辑,不用再去关注一些代码模板。能够让我们代码逻辑清晰。在代码运行效率其实是一样的。但是却可以提升我们的开发效率。

   如果想下载上面例子的源码请到点击下面链接

   https://github.com/sunwnehongl/LambadAndStream

你可能感兴趣的:(java)