JDK8中Lambda表达式用法及Stream流详解

一、导引

Lambda 表达式(lambda expression)是一个匿名函数。想要理解Lambda表达式一定要先理解匿名内部类

二、简介

Java 8的一个大亮点是引入Lambda表达式,使用它设计的代码会更加简洁。当开发者在编写Lambda表达式时,也会随之被编译成一个函数式接口。下面这个例子就是使用Lambda语法来代替匿名的内部类,代码不仅简洁,而且还可读。语法形式为 () -> {},其中 () 用来描述参数列表,{} 用来描述方法体,-> 为 lambda运算符 ,读作(goes to)。

不采用Lambda的老方法:

Runnable runnable1=new Runnable(){
    @Override
    public void run(){
            System.out.println("Running without Lambda");
    }
};

使用Lambda:

Runnable runnable2=()->System.out.println("Running from Lambda");

正如你所看到的,使用Lambda表达式不仅让代码变的简单、而且可读、最重要的是代码量也随之减少很多。

三、四种接口介绍:

Lambda表达式使得在java编程当中,因为高效的编码方式和对接口与功能实现的分离,极大的减少了编程代码量,下面对四种Lambda内置接口进行简易的描述和功能介绍

 * Consumer<T> : 消费型接口
 * 		void accept(T t);  :有参数,但是没有返回值
 * 
 * Supplier<T> : 供给型接口     :有返回值,但是没有参数
 * 		T get(); 
 * 
 * Function<T, R> : 函数型接口 :有返回值,也有参数
 * 		R apply(T t);
 * 
 * Predicate<T> : 断言型接口   :返回boolean 类型,有参数
 * 		boolean test(T t);

使用方式:

  1.消费型:

//消费型 consumer:特点有一个参数,这个参数进行处理,然后没有返回值
lamdbs.consumerMoney(100, (e)->{System.out.println("我买东西消费了"+e+"元钱");});
//消费型Lamdba使用:
public void consumerMoney(double money,Consumer<Double> cons) {
	cons.accept(money);
}

2.供给型:

//供给型,没有参数, 但是能得到一个返回值:如给定一个数,产生一个随机的数组,供给型,没有参数, 但是能得到一个返回值:如给定一个数,产生一个随机的数组	
List<Integer> numsBySuppliers = lamdbs.getNumsBySuppliers(10, ()->(int)(Math.random()*100));
 
public List<Integer>  getNumsBySuppliers(int num,Supplier<Integer>  sup){
			List<Integer>  numList=new ArrayList<>();
			for(int i=0;i<num;i++) {
				Integer intNum = sup.get();
				numList.add(intNum);
			}
			
			return numList;
		}

3.函数型:

int strLengthByFunction = lamdbs.getStrLengthByFunction("wo shi yanghongtang", (e)->e.length());
		
//函数Lamdba使用--》传进去一个值,然后处理完成后返回一个值,比如:传进去一个字符串,返回该字符串的长度
public int getStrLengthByFunction(String str,Function<String, Integer> fun) {
	return fun.apply(str);
}

4.断言型

List<String> filterStr = lamdbs.filterStr(Arrays.asList("alsfowage","aegt","wq4y","wtqt"), (e)->(e.length()>3));
//将满足条件的字符串,放入集合当中
public List<String> filterStr(List<String> list,Predicate<String> pre){
	List<String>  list1=new ArrayList<>();
	for (String string : list) {
		if(pre.test(string)) {
			list1.add(string);
		}
	}
	return list1;		
}

四、使用 Lambda 表达式原因

Lambda 是一个匿名函数,可以把 Lambda表达式 理解为是一段可以传递的代码 (将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升

Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “ -> ” , 该操作符被称为 Lambda 操作符或剪头操作符。它将 Lambda 分为两个部分:
左侧: 指定了 Lambda 表达式需要的所有参数
右侧: 指定了 Lambda 体,即 Lambda 表达式要执行
的功能。
语法形式为 () -> {},其中 () 用来描述参数列表,{} 用来描述方法体,-> 为 lambda运算符 ,读作(goes to)。

语法格式

package com.company.demo;
 
/**
 * 无参无返回值的lambda表达式,不属于Lambda内置的四种函数
 */
interface MyInterface{
    public void method();
}
 
public class Test01 {
    public static void main(String[] args) {
        //完整写法
        MyInterface myInterface1 = ()-> {System.out.println("无参无返回值,一条语句");};
        myInterface1.method();
        //括号省略写法
        MyInterface myInterface2 = ()-> System.out.println("无参无返回值,一条语句,省略{}");
        myInterface2.method();
        //多条语句不可省略括号{}
        MyInterface myInterface3 = ()->{
            System.out.println("无参无返回值");
            int n = 6;
            System.out.println(n);
        };
        myInterface3.method();
    }
}

语法格式一 : 无参数,无返回值

Runnable r1 = () -> System.out.println("Hello Lambda!");

语法格式二 : 有一个参数,并且无返回值

Consumer<String> Con = (x) -> System.out.println(x)

语法格式三 : 若只有一个参数,小括号可以省略不写

Consumer<String> con = x -> System.out.println(x)

Consumer<String> con = (x) -> System. out .println(x);
con.accept( "啦啦啦,我是卖报的小行家" );

语法格式四 : 有两个以上的参数,有返回值,并且 Lambda 体中有多条语句

Comparator <Integer> com = (x, y) -> {
    System.out.println("函数式接口");
    return Integer.compare(x, y);
};

语法格式五 : 若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写

Comparator <Integer> com = (x, y) -> Integer.compare(x, y);

语法格式六 : Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”

(Integer x, Integer y) -> Integer.compare(x, y);

注 : Lambda 表达式中的参数类型都是由编译器推断得出的。 Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。 Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的 “类型推断”

上联 : 左右遇一括号省
下联 : 左侧推断类型省
横批 : 能省则省

 

Lambda 表达式需要 “函数式接口” 的支持

函数式接口 : 接口中只有一个抽象方法的接口,称为函数式接口,可以通过 Lambda 表达式来创建该接口的对象 (若 Lambda表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明) 可以使用注解 @FunctionalInterface 修饰可以检查是否是函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口

@FunctionalInterface
public interface MyFun {
    Integer getValue(Integer num);
}

函数式接口中使用泛型 :

@FunctionalInterface
public interface MyFun< T > {
    T getValue( T t);
}

从匿名类到 Lambda 的转换

// 原来的匿名内部类作为参数传递
Comparator<Integer> comparator = new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return Integer. compare (o1, o2);
    }
};
TreeSet<Integer> treeSet = new TreeSet<>(comparator);

// Lambda 表达式作为参数传递
Comparator<Integer> comparator = (o1, o2) -> Integer. compare (o1, o2);
TreeSet<Integer> treeSet = new TreeSet<>(comparator);

作为参数传递 Lambda 表达式 : 为了将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型

import lombok.Data;

import java.util.ArrayList;
import java.util.List;

//需求 : 雇员对象如下,有一个包含许多员工信息的对象 employees,要求获取当前公司中员工年龄大于 35 的员工信息
@Data
public class Employee {
    private String name;
    private int age;

    public Employee() {
    }

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // ...
    @Override
    public String toString() {
        return "Employee{" + "name='" + name + ", age=" + age + '}';
    }
}

    //最简单的方式是采用 foreach 循环遍历,以下是各种优化方式
    //优化方式一:策略设计模式

@FunctionalInterface
interface MyPredicate<T> {
    boolean test(T t);
}

public class FilterEmployeeByAge implements MyPredicate<Employee> {
    @Override
    public boolean test(Employee employee) {
    return employee.getAge() >= 35;
    }
}

public List<Employee> filterEmployees3(List<Employee> employees, MyPredicate<Employee> myPredicate) {
    List<Employee> emps = new ArrayList<>();
    for (Employee employee : employees) {
        if (myPredicate.test(employee)) {
            emps.add(employee);
        }
    }
    return emps;
}
//调用
List<Employee> emps=filterEmployees3(employees,new FilterEmployeeByAge());
for(Employee employee:emps){
    System.out.println(employee.toString());
}

//优化方式二:匿名内部类
List<Employee> emps=filterEmployees3(employees,new MyPredicate<Employee>(){
    @Override
    public boolean test(Employee employee){
      return employee.getAge()>=35;
     }
});
for(Employee employee:emps){
 System.out.println(employee.toString());
}

//优化方式三:Lambda
filterEmployees3(employees,employee->employee.getAge()>=35).forEach(System.out::println);

//优化方式四:Stream API
employees.stream().filter((employee)->employee.getAge()>=35).limit(5).forEach(System.out::println);

Java8 内置的四大核心函数式接口

函数式接口参数类型返回类型用途

Consumer<T>

消费型接口

T

void

对类型为T的对象应用操作,包含方法 :

void accept(T t)

Supplier<T>

供给型接口

T

返回类型为T的对象,包含方法 :

T get()

Function<T, R>

函数型接口

T

R

对类型为T的对象应用操作,并返回结果。结果是R类型的对象,包含方法 :

R apply(T t)

Predicate<T>

断定型接口

T

boolean

确定类型为T的对象是否满足某约束,并返回boolean 值。包含方法 :

boolean test(T t)

//Consumer<T> 消费型接口 :
public void happy( double money, Consumer<Double> con) {
    con.accept(money);
}
happy( 10000 , (m) -> System. out .println( "吃大餐,每次消费:" + m + "元" ));

//Supplier<T> 供给型接口 :
// 需求 : 产生指定个数的整数,并放入集合中
public List<Integer> getNumList( int num, Supplier<Integer> sup) {
    List<Integer> list = new ArrayList<>();
    for ( int i = 0 ; i < num; i++) {
        Integer n = sup.get();
        list.add(n);
    }
    return list;
}
getNumList( 10 , () -> ( int ) (Math. random () * 100 )).forEach(System. out ::println);

//Function<T, R> 函数型接口 :
// 需求:用于处理字符串
public String strHandler(String str, Function<String, String> fun) {
    return fun.apply(str);
}
String newStr = strHandler( " \t\t\t Good Good Study,Day Day up.   " , (str) -> str.trim());
System. out .println(newStr);

//Predicate<T> 断言型接口 :
// 需求:将满足条件的字符串,放入集合中
public List<String> filterStr(List<String> list, Predicate<String> pre) {
    List<String> strList = new ArrayList<>();
    for (String str : list) {
        if (pre.test(str)) {
        strList.add(str);
        }
    }
    return strList;
}
List<String> list = Arrays. asList ( "Hello" , "atguigu" , "Lambda" , "www" , "ok" );
filterStr(list, (s) -> s.length() > 3 ).forEach(System. out ::println);

其他接口

函数式接口参数类型返回类型用途
BiFunction<T, U, R>T, UR

对类型为 T, U 参数应用操作, 返回 R 类型的结果,包含方法为 :

R apply(T t, U u)

UnaryOperator<T>

(Function子接口)

TT

对类型为T的对象进行一元运算, 并返回T类型的结果,包含方法为 :

T apply(T t)

BinaryOperator<T>

(BiFunction 子接口)

T,TT

对类型为T的对象进行二元运算, 并返回T类型的结果,包含方法为

T apply(T t1, T t2)

BiConsumer<T, U>T, Uvoid

对类型为T, U 参数应用操作,包含方法为

void accept(T t, U u)

ToIntFunction<T>

ToLongFunction<T>

ToDoubleFunction<T>

T

int

long

double

分 别 计 算 int 、 long 、double、 值的函数

IntFunction<R>

LongFunction<R>

DoubleFunction<R>

int

long

double

R参数分别为int、 long、double 类型的函数

方法引用

若 Lambda 体中的功能,已经有方法提供实现,可以使用方法引用 (可以将方法引用理解为Lambda 表达式的另外一种表现形式)。当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!) 方法引用:使用操作符 “::” 将方法名和对象或类的名字分隔开来。

如下三种主要使用情况:

  • 对象的引用 :: 实例方法名
  • 类名 :: 静态方法名
  • 类名 :: 实例方法名

注 : 1>  方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致(就是函数签名和返回值一致)
       2> 若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式 : ClassName::MethodName

PrintStream ps = System. out ;
Consumer<String> con1 = (str) -> ps .println(str);
Consumer<String> con2 = ps::println;
Consumer<String> con3 = System. out::println;

对象的引用 :: 实例方法名

Employee emp = new Employee( 101 , "张三" , 18 , 9999.99 );
Supplier<String> sup = () -> emp .getName();
System. out .println(sup.get());
Supplier<String> sup2 = emp::getName;
System. out .println(sup2.get());

类名 :: 静态方法名

Comparator<Integer> comparator1 = (x, y) -> Integer. compare (x, y);
Comparator<Integer> comparator2 = Integer:: compare ;

BiFunction<Double, Double, Double> fun = (x, y) -> Math. max (x, y);
System. out .println(fun.apply( 1.5 , 22.2 ));
BiFunction<Double, Double, Double> fun2 = Math:: max ;
System. out .println(fun2.apply( 1.2 , 1.5 ));

类名 :: 实例方法名

BiPredicate<String, String> bp = (x, y) -> x.equals(y);
System. out .println(bp.test( "abcde" , "abcde" ));
BiPredicate<String, String> bp2 = String::equals;
System. out.println(bp2.test( "abc", "abc"));

Function<Employee, String> fun = (e) -> e.show();
System. out .println(fun.apply( new Employee()));
Function<Employee, String> fun2 = Employee::show;
System. out.println(fun2.apply( new Employee()));

注 : 当需要引用方法的第一个参数是调用对象,并且第二个参数是需要引用方法的第二个参数 (或无参数) 时 :  ClassName::methodName构造器引用
构造器的参数列表,需要与函数式接口中参数列表保持一致 (就是函数签名一致)
1> 类名 :: new

Supplier<Employee> sup = () -> new Employee();
System. out .println(sup.get());
Supplier<Employee> sup2 = Employee:: new ;
System. out .println(sup2.get());

Function<String, Employee> fun = Employee:: new ;
BiFunction<String, Integer, Employee> fun2 = Employee:: new ;

数组引用 类型[] :: new

Function<Integer, String[]> fun = (args) -> new String[args];
String[] strs = fun.apply( 10 );
System. out .println(strs. length );

Function<Integer, Employee[]> fun2 = Employee[]:: new ;
Employee[] emps = fun2.apply( 20 );
System. out .println(emps. length );

强大的Stream API

Stream 的操作三个步骤

创建 Stream
一个数据源(如:集合、数组),获取一个流
中间操作
一个中间操作链,对数据源的数据进行处理
终止操作(终端操作)
一个终止操作,执行中间操作链,并产生结果 创建

 Stream 的中间操作

多个 中间操作 可以连接起来形成一个 流水线 ,除非流水 线上触发终止操作,否则 中间操作不会执行任何的处理 ! 而在 终止操作时一次性全部处理,称为“惰性求值”

筛选与切片

方法描述
filter(Predicate p)
接收 Lambda , 从流中排除某些元素。
distinct()
筛选,通过流所生成元素的 hashCode() 和 equals() 去
除重复元素
limit(long maxSize)
截断流,使其元素不超过给定数量。
skip(long n)
跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素
不足 n 个,则返回一个空流。与 limit(n) 互补

映射

方法描述
map(Function f)
接收一个函数作为参数,该函数会被应用到每个元
素上,并将其映射成一个新的元素。
mapToDouble(ToDoubleFunction f)
接收一个函数作为参数,该函数会被应用到每个元
素上,产生一个新的 DoubleStream。
mapToInt(ToIntFunction f)
接收一个函数作为参数,该函数会被应用到每个元
素上,产生一个新的 IntStream。
mapToLong(ToLongFunction f)
接收一个函数作为参数,该函数会被应用到每个元
素上,产生一个新的 LongStream。
flatMap(Function f)
接收一个函数作为参数,将流中的每个值都换成另
一个流,然后把所有流连接成一个流

排序

方法描述
sorted()
产生一个新流,其中按自然顺序排序
sorted(Comparator comp)
产生一个新流,其中按比较器顺序排序

Stream 的终止操作

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。

查找与匹配

方法描述
allMatch(Predicate p)
检查是否匹配所有元素
anyMatch(Predicate p)
检查是否至少匹配一个元素
noneMatch(Predicate p)
检查是否没有匹配所有元素
findFirst()
返回第一个元素
findAny()
返回当前流中的任意元素
count()
返回流中元素总数
max(Comparator c)
返回流中最大值
min(Comparator c)
返回流中最小值
forEach(Consumer c)
内部迭代 (使用 Collection 接口需要用户去做迭 代,称为 外部迭代 。相反,Stream API 使用内部 迭代——它帮你把迭代做了)

归约

方法描述
reduce(T iden, BinaryOperator b)
可以将流中元素反复结合起来,得到一个值。 返回 T
reduce(BinaryOperator b)
可以将流中元素反复结合起来,得到一个值。 返回 Optional<T>
备注: map reduce 的连接通常称为 map-reduce 模式,因 Google 用它 来进行网络搜索而出名。

收集

方法描述
collect(Collector c)
将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。但是 Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
方法返回类型作用
toList
List<T>把流中元素收集到List
List<Employee> emps= list.stream().collect(Collectors.toList());
toSet
Set<T>把流中元素收集到Set
Set<Employee> emps= list.stream().collect(Collectors.toSet());
toCollection
Collection<T>把流中元素收集到创建的集合
Collection<Employee>emps=list.stream().collect(Collectors.toCollection(ArrayList::new));
counting
Long计算流中元素的个数
long count = list.stream().collect(Collectors.counting());
summingInt
Integer
对流中元素的整数属性求和
inttotal=list.stream().collect(Collectors.summingInt(Employee::getSalary));
averagingInt
Double
计算流中元素 Integer 属性的平均
doubleavg= list.stream().collect(Collectors.averagingInt(Employee::getSalary));
summarizingInt
IntSummaryStatistics
收集流中 Integer 属性的统计值。
如:平均值
IntSummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
joining
String
连接流中每个字符串
String str= list.stream().map(Employee::getName).collect(Collectors.joining());
maxBy
Optional<T>根据比较器选择最大值
Optional<Emp>max= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
minBy
Optional<T>
根据比较器选择最小值
Optional<Emp> min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
reducing
归约产生的类型
从一个作为累加器的初始值
开始,利用BinaryOperator与
流中元素逐个结合,从而归
约成单个值
inttotal=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));
collectingAndThen
转换函数返回的类型
包裹另一个收集器,对其结
果转换函数
inthow= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
groupingBy
Map<K, List<T>>
根据某属性值对流分组,属
性为 K ,结果为 V
Map<Emp.Status, List<Emp>> map= list.stream()
.collect(Collectors.groupingBy(Employee::getStatus));
partitioningBy
Map<Boolean, List<T>>
根据 true false 进行分区
Map<Boolean,List<Emp>>vd= list.stream().collect(Collectors.partitioningBy(Employee::getManage));


————————————————
课程文件地址:Java8Lambde新特性-Java文档类资源-CSDN下载

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值