lambda表达式最全总结

lambda表达式

一、lambda表达式的引入

为什么引入lambda表达式

  • 需求环境:线程类的创建

  • 解决方案:匿名内部类实现

  • 解决方案PLUS:lambda表达式实现

代码:
package com.imooc.test;

/**
 * @ClassName: Demo01
 * @Description:
 * @Author: keke
 * @Date: 2021/4/12
 */
public class Demo01 {

    public static void main(String[] args) {
        // 1.传统模式下,新线程的创建
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("threading..." + Thread.currentThread().getId());
            }
        }).start();

        // 2.jdk8新特性,lambda表达式优化线程模式
        new Thread(() -> {
            System.out.println("lambda threading..." + Thread.currentThread().getId());
        }).start();
    }
    
}

运行结果
threading…13
lambda threading…14

二、lambda表达式基础知识

函数式接口

  • 函数式接口的定义

    1、java类型系统中的接口,只包含一个方法的特殊接口

    2、使用语义化检测注解:@Functionallnterface

  • 函数式接口可以定义的方法

    1.一个抽象方法

    2.静态方法(必须用static修饰)

    3.默认方法(必须用default修饰)

    4.来自Object继承的方法

  • 常见的函数式接口

    java.util.function提供了大量的函数式接口

    1、Predicate 接收参数T对象,返回一个boolean类型结果
    2、Consumer 接收参数T对象,没有返回值
    3、Function 接收参数T对象,返回R对象
    4、Supplier 不接受任何参数,直接通过get()获取指定类型的对象
    5、UnaryOperator 接口参数T对象,执行业务处理后,返回更新后的T对象
    6、BinaryOperator 接口接收两个T对象,执行业务处理后,返回一个T对象

代码:

函数式接口

package com.imooc.service;

/**
 * 消息传输格式化转换接口
 * 函数式接口可以定义的方法:
 * 1.一个抽象方法
 * 2.静态方法(必须用static修饰)
 * 3.默认方法(必须用default修饰)
 * 4.来自Object继承的方法
 */
@FunctionalInterface
public interface IMessageFormat {

    /**
     * 消息转换方法
     * 抽象方法
     * @param message 要转换的消息
     * @param format 转换的格式[xml/json..]
     * @return 返回转换后的数据
     */
    String format(String message, String format);

    String toString(); //来自Object继承的方法

    /**
     * 消息合法性验证方法
     * 静态方法
     * @param msg 要验证的消息
     * @return 返回验证结果
     */
    static boolean verifyMessage(String msg) {
        if (msg != null) {
            return true;
        }
        return false;
    }
}
package com.imooc.service;

/**
 * 用户身份认证标记接口
 */
@FunctionalInterface
public interface IUserCredential {

    /**
     * 通过用户账号,验证用户身份信息的接口
     * @param username 要验证的用户账号
     * @return 返回身份信息[系统管理员、用户管理员、普通用户]
     */
    String verifyUser(String username);

//    boolean test();

    //默认方法
    default String getCredential(String username) {
        // 模拟方法
        if ("admin".equals(username)) {
            return "admin + 系统管理员用户";
        } else if("manager".equals(username)){
            return "manager + 用户管理员用户";
        } else {
            return "commons + 普通会员用户";
        }
    }
}

实现类

package com.imooc.service.impl;

import com.imooc.service.IMessageFormat;

/**
 * @Author keke
 * @Description
 * @Date
 * @Param
 * @return
 **/
public class MessageFormatImpl implements IMessageFormat {
    @Override
    public String format(String message, String format) {
        System.out.println("消息转换...");
        return message;
    }
}
package com.imooc.service.impl;

import com.imooc.service.IUserCredential;

/**
 * @Author keke
 * @Description
 * @Date
 * @Param
 * @return
 **/
public class UserCredentialImpl implements IUserCredential {
    @Override
    public String verifyUser(String username) {
        if ("admin".equals(username)) {
            return "系统管理员";
        } else if("manager".equals(username)) {
            return "用户管理员";
        }
        return "普通会员";
    }
}

测试类

package com.imooc.test;

import com.imooc.service.IUserCredential;

import java.util.UUID;
import java.util.function.*;

/**
 * 需求改动:
 *  所有的用户验证,可以同时获取用户的验证信息[是否认证成功|成功~返回用户|null]
 *
 *  Lambda表达式 基本语法
 */
public class App 
{
    public static void main( String[] args ) {

          // 一、接口的静态方法和默认方法
//        // 1. 默认方法
//        IUserCredential ic = new UserCredentialImpl();
//        System.out.println(ic.verifyUser("admin"));
//        System.out.println(ic.getCredential("admin"));
//
//        // 2. 静态方法
//        String msg = "hello world";
//        if (IMessageFormat.verifyMessage(msg)) {
//            IMessageFormat format = new MessageFormatImpl();
//            format.format(msg, "json");
//        }


          // 二、匿名内部类,实现接口的抽象方法
//        IUserCredential ic2 = new IUserCredential() {
//            @Override
//            public String verifyUser(String username) {
//                return "admin".equals(username) ? "管理员":"会员";
//            }
//        };
//
//        System.out.println(ic2.verifyUser("manager"));
//        System.out.println(ic2.verifyUser("admin"));


          // 三、lambda表达式,针对函数式接口的简单实现
//        IUserCredential ic3 = (String username) -> {
//            return "admin".equals(username) ? "lambda管理员":"lambda会员";
//        };

        //简化
        IUserCredential ic3 = username -> "admin".equals(username) ? "lambda管理员":"lambda会员";

        System.out.println(ic3.verifyUser("manager"));
        System.out.println(ic3.verifyUser("admin"));

        System.out.println("========================================");

          // 四、JDK8 提供的常见函数式接口
          // 适用于比较
//        Predicate<String> pre = (String username) -> {
//            return "admin".equals(username);
//        };

        //简化
        Predicate<String> pre = "admin"::equals;

        System.out.println(pre.test("manager"));
        System.out.println(pre.test("admin"));

        System.out.println("========================================");

        //适用于发送消息
        //如果在项目开发过程中需要对某一个类型进行功能性处理并且不需要返回值的话,可以考虑Consumer实现
//        Consumer<String> con = (String message) -> {
//            System.out.println("要发送的消息:" + message);
//            System.out.println("消息发送完成");
//        };

        //简化
        Consumer<String> con = message -> {
            System.out.println("要发送的消息:" + message);
            System.out.println("消息发送完成");
        };

        con.accept("hello 慕课网的学员们..");
        con.accept("imooc lambda expression.");

        System.out.println("========================================");

        //适用于对参数进行处理并返回处理后的参数
//        Function<String, Integer> fun = (String gender) -> {
//            return "male".equals(gender) ? 1:0;
//        };

        //简化
        Function<String, Integer> fun = gender -> "male".equals(gender) ? 1:0;

        System.out.println(fun.apply("male"));
        System.out.println(fun.apply("female"));

        System.out.println("========================================");

        //不接受参数,提供T对象的创建工厂
//        Supplier<String> sup = () -> {
//            return UUID.randomUUID().toString();
//        };

        //简化
        Supplier<String> sup = () -> UUID.randomUUID().toString();

        System.out.println(sup.get());
        System.out.println(sup.get());
        System.out.println(sup.get());

        System.out.println("========================================");

        //前端传递一个图片给后端,后端对图片处理并返回
//        UnaryOperator<String> uo = (String img)-> {
//            img += "[100x200]";
//            return img;
//        };

        //简化
        UnaryOperator<String> uo = img -> img + "[100x200]";

        System.out.println(uo.apply("原图--"));

        System.out.println("========================================");

//        BinaryOperator<Integer> bo = (Integer i1, Integer i2) -> {
//            return i1 > i2 ? i1: i2;
//        };

        //简化
        BinaryOperator<Integer> bo = (i1, i2) -> i1 > i2 ? i1: i2;

        System.out.println(bo.apply(12, 13));

       /**
        * java.util.function提供了大量的函数式接口
        * Predicate 接收参数T对象,返回一个boolean类型结果
        * Consumer 接收参数T对象,没有返回值
        * Function 接收参数T对象,返回R对象
        * Supplier 不接受任何参数,直接通过get()获取指定类型的对象
        * UnaryOperator 接口参数T对象,执行业务处理后,返回更新后的T对象
        * BinaryOperator 接口接收两个T对象,执行业务处理后,返回一个T对象
        */

    }

}

lambda表达式的基本语法

1)声明:就是和lambda表达式绑定的接口类型
2)参数:包含在一对圆括号中,和绑定的接口中的抽象方法中的参数个数及顺序一致。
3)操作符:->
4)执行代码块:包含在一对大括号中,出现在操作符号的右侧
[接口声明] = (参数) -> {执行代码块};

代码:
package com.imooc.test;

/**
 * @ClassName: App2
 * @Description:
 * @Author: keke
 * @Date: 2021/4/11
 */
/*
* lambda表达式的基本语法
* 1)声明:就是和lambda表达式绑定的接口类型
* 2)参数:包含在一对圆括号中,和绑定的接口中的抽象方法中的参数个数及顺序一致。
* 3)操作符:->
* 4)执行代码块:包含在一对大括号中,出现在操作符号的右侧
* [接口声明] = (参数) -> {执行代码块};
* */
public class App2 {

    public static void main(String[] args) {

        ILambda1 i1 = () -> {
            System.out.println("hello imooc!");
            System.out.println("welcome to imooc!");
        };
        i1.test();

        ILambda1 i12 = () -> System.out.println("hello imooc");
        i12.test();

        ILambda2 i2 = (String n, int a) -> {
            System.out.println(n + "say: my year's old is " + a);
        };
        i2.test("jerry", 18);

        ILambda2 i22 = (n, a) -> {
            System.out.println(n + " 说:我今年" + a + "岁了.");
        };
        i22.test("tom", 22);

        ILambda3 i3 = (x, y) -> {
            int z = x + y;
            return z;
        };
        System.out.println(i3.test(11, 22));

        ILambda3 i32 = (x, y) -> x + y;
        System.out.println(i32.test(100, 200));

        /*
        * 总结:
        * 1. lambda表达式,必须和接口进行绑定。
        * 2. lambda表达式的参数,可以附带0个到n个参数,括号中的参数类型可以不用指定,jvm在运行时,会自动根据绑定的抽象方法中电参数进行推导。
        * 3. lambda表达式的返回值,如果代码块只有一行,并且没有大括号,不用写return关键字,单行代码的执行结果,会自动返回。
        *    如果添加了大括号,或者有多行代码,必须通过return关键字返回执行结果。
        *
        * 自我总结:
        * 1.参数可以不写类型
        * 2.参数只有一个时可以不写小括号,参数有多个或没有参数时必须写小括号
        * 3.如果代码块只有一行,且没有返回值,不用写大括号
        * 4.如果代码块只有一行,且有返回值,不用写大括号,且不用写return关键字,如果写了return关键字,必须写大括号
        * 5.如果代码有多行,若无返回值,可以不写大括号,但建议写,便于阅读;若有返回值,必须写大括号
        * */

    }

    // 没有参数,没有返回值的lambda表达式绑定的接口
    interface ILambda1{
        void test();
    }

    // 带有参数,没有返回值的lambda表达式绑定的接口
    interface ILambda2{
        void test(String name, int age);
    }

    // 带有参数,带有返回值的lambda表达式绑定的接口
    interface ILambda3 {
        int test(int x, int y);
    }

}
自我总结:

1.参数可以不写类型
2.参数只有一个时可以不写小括号,参数有多个或没有参数时必须写小括号
3.如果代码块只有一行,且没有返回值,不用写大括号
4.如果代码块只有一行,且有返回值,不用写大括号,且不用写return关键字,如果写了return关键字,必须写大括号
5.如果代码有多行,若无返回值,可以不写大括号,但建议写,便于阅读;若有返回值,必须写大括号

lambda表达式的变量捕获

package com.imooc.test;


/**
 * @Author keke
 * @Description 匿名内部类的变量捕获和lambda表达式的变量捕获作比较
 * @Date
 * @Param
 * @return
 **/
public class App3 {

    String s1 = "全局变量";

    // 1. 匿名内部类型中对于变量的访问
    public void testInnerClass() {
        String s2 = "局部变量";

        new Thread(new Runnable() {
            String s3 = "内部变量";
            @Override
            public void run() {
                // 访问全局变量
//                System.out.println(this.s1); // 报错,this关键字表示是当前内部类型的对象
//                System.out.println(this.s2); // 报错
                System.out.println(this.s3); // 正确
                System.out.println(s1);

                // 访问局部变量
                System.out.println(s2);
//                s2 = "hello"; //不能对局部变量[默认为final]进行数据的修改

                // 访问内部变量
                System.out.println(s3);
                System.out.println(this.s3);

            }
        }).start();
    }

    // 2. lambda表达式变量捕获
    public void testLambda() {
        String s2 = "局部变量lambda";

        new Thread(() -> {
            String s3 = "内部变量lambda";

            // 访问全局变量
            System.out.println(this.s1); // this关键字,表示的就是所属方法所在类型的对象

            // 访问局部变量
            System.out.println(s2);
//            s2 = "hello"; // 不能进行数据修改,默认推导变量的修饰符:final

            // 访问内部变量
            System.out.println(s3);
            s3 = "labmda 内部变量直接修改";
//            System.out.println(this.s3); // this关键字,表示的就是所属方法所在类型的对象,即App对象
            System.out.println(s3);
        }).start();
    }

    /*
    * 总结:
    * lambda表达式变量操作优化了匿名内部类中this关键字,不再独立建立对象作用域,表达式本身就是所属对象的一部分,语法意义上使用更加简洁
    * */
    public static void main(String[] args) {
        App3 app = new App3();
//        app.testInnerClass();

        app.testLambda();

    }
}

lambda表达式类型检查

package com.imooc.test;

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

/**
 * @Author keke
 * @Description lambda表达式类型检查
 * @Date
 * @Param
 * @return
 **/
public class App4 {

    public static void test(MyInterface<String, List> inter) {
        List<String> list = inter.strategy("hello", new ArrayList());
        System.out.println(list);
    }

    public static void main(String[] args) {
        test(new MyInterface<String, List>() {
            @Override
            public List strategy(String s, List list) {
                list.add(s);
                return list;
            }
        });

        test((x, y) -> {
            y.add(x);
            return y;
//            x.add(y);
//            return x;
        });

        /*
        * (x,y)->{..} --> test(param) --> param==MyInterface --> lambda表达式 --> MyInterface类型
        * 这个就是对于lambda表达式的类型检查,MyInterface接口就是lambda表达式的目标类型(target typing)
        * (x,y)->{..} --> MyInterface.strategy(T r, R r) --> MyInterface<String, List> inter
        * --> T==String R==List --> lambda --> (x, y) == strategy(T t , R r) --> x==T==String  y==R==List
        * 这个就是lambda表达式参数的类型检查
        * */

    }
}

@FunctionalInterface
interface MyInterface<T, R> {
    R strategy(T t, R r);
}

方法重载对于lambda表达式的影响

package com.imooc.test;

/**
 * @Author keke
 * @Description 方法重载对于lambda表达式的影响
 * @Date
 * @Param
 * @return
 **/
public class App5 {

    public static void main(String[] args) {
        App5 app = new App5();

        app.lambdaMethod(new Param1() {
            @Override
            public void outInfo(String info) {
                System.out.println(info);
            }
        });

        System.out.println("=================================");

        app.lambdaMethod(new Param2() {
            @Override
            public void outInfo(String info) {
                System.out.println(info);
            }
        });

        /*
        * lambda表达式存在类型检查-> 自动推导lambda表达式的目标类型
        * lambdaMethod() -> 方法 -> 重载方法
        *        -> Param1  函数式接口
        *        -> Param2  函数式接口
        *        调用方法 -> 传递Lambda表达式 -> 自动推导 -> Param1 | Param2
        * 结论:当方法重载的参数都是函数式接口,并且两函数式接口中的抽象方法参数和返回值类型都一样时,lambda表达式
        * 校验不通过,只能用匿名内部类
        * 抽象方法返回值类型或者参数不一样时可以校验通过
        * */

//        app.lambdaMethod(info -> System.out.println(info));

    }

    interface Param1 {
        void outInfo(String info);
    }

    interface Param2 {
        void outInfo(String info);
    }

    // 定义重载的方法
    public void lambdaMethod(Param1 param) {
        param.outInfo("hello param1 imooc!");
    }
    public void lambdaMethod(Param2 param) {
        param.outInfo("hello param2 imooc");
    }

}

lambda表达式的底层原理

  • lambda表达式底层解析运行原理

​ lambda表达式在JVM底层解析成私有静态方法和匿名内部类,通过实现接口的匿名内部类中接口方法调用静态实现方法,完成lambda表达式的执行

  • 具体操作

​ 1、创建类App.java(注意:不能带包名,不然后面命令可能报错)

public class App {

   public static void main(String [] args) {
      IMarkUp mu = (message) -> System.out.println(message);
      mu.markUp("lambda!");
   }

}

interface IMarkUp {
   void markUp(String msg);
}

​ 2、进入类所在的根目录

​ 3、执行javac App.java:编译java文件,生成class文件

​ 4、执行javap -p App.class:查看class文件

public class App {
  public App();
  public static void main(java.lang.String[]);
  private static void lambda$main$0(java.lang.String); //lambda表达式生成的代码
}

lambda表达式生成的代码实际就是

private static void lambda$main$0(java.lang.String message) {
    System.out.println(message);
}

​ 5、执行java -Djdk.internal.lambda.dumpProxyClasses 类名:生成底层class文件App$$Lambda$1.class

​ 6、执行javap -p App$$Lambda$1.class:查看class文件

final class App$$Lambda$1 implements IMarkUp {
  private App$$Lambda$1();
  public void markUp(java.lang.String);
}

实际就是

final class App$$Lambda$1 implements IMarkUp {
  private App$$Lambda$1() {
      
  }
  public void markUp(java.lang.String message) {
      App.lambda$main$0(message);
  }
}

综合以上,执行的代码就是

new App$$Lambda$1().markUp("lambda");

三、lambda表达式在集合中的应用

方法引用

前提条件:实现接口的入参,返回值类型和接口的抽象方法一致
1.静态方法引用的使用
类型名称.方法名称() -> 类型名称::方法名称
2.实例方法引用的使用
创建类型对应的一个对象 -> 对象引用::实例方法名称
3.构造方法引用的使用
构造方法返回的对象类型::new

代码
package com.damu;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.*;

/**
 * @Author keke
 * @Description
 * @Date
 * @Param
 * @return
 **/
 /**
  * 前提条件:实现接口的入参,返回值类型和接口的抽象方法一致
  * 1.静态方法引用的使用
  * 类型名称.方法名称() -> 类型名称::方法名称
  * 2.实例方法引用的使用
  * 创建类型对应的一个对象 -> 对象引用::实例方法名称
  * 3.构造方法引用的使用
  * 构造方法返回的对象类型::new
  **/
@Data
@AllArgsConstructor
@NoArgsConstructor
class Person {
    private String name;
    private String gender;
    private int age;

    // 静态方法引用
    public static int compareByAge(Person p1, Person p2) {
        return p1.getAge() - p2.getAge();
    }
}

class PersonUtil {
    // 实例方法引用
    public int compareByName(Person p1, Person p2) {
        return p1.getName().hashCode() - p2.getName().hashCode();
    }
}

@FunctionalInterface
interface IPerson {
    // 抽象方法,通过指定类型的构造方法初始化对象数据
    Person initPerson(String name, String gender, int age);
}

class PersonCon {
    public static Person testPersonCon(IPerson iPerson) {
        return iPerson.initPerson("array", "女", 18);
    }
}

public class Test {
    public static void main(String[] args) {
        // 存储Person对象的列表
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("shuke", "男", 29));
        personList.add(new Person("tom", "男", 16));
        personList.add(new Person("jerry", "男", 20));
        personList.add(new Person("beita", "女", 30));

        // 1.匿名内部类实现
//        Collections.sort(personList, new Comparator<Person>() {
//            @Override
//            public int compare(Person o1, Person o2) {
//                return o1.getAge() - o2.getAge(); // o1-o2:升序   o2-o1:降序
//            }
//        });
//        System.out.println(personList);

        // 2.lambda表达式实现
//        Collections.sort(personList, (p1, p2) -> p1.getAge() - p2.getAge());
//        System.out.println(personList);

        // 3.静态方法引用实现
//        Collections.sort(personList, Person::compareByAge);
//        System.out.println(personList);

        // 4.实例方法引用
        PersonUtil pu = new PersonUtil();
//        Collections.sort(personList, (p1, p2) -> p1.getName().hashCode() - p2.getName().hashCode());
//        Collections.sort(personList, pu::compareByName);
//        System.out.println("tom".hashCode());
//        System.out.println("jerry".hashCode());
//        personList.forEach(System.out::println);

        // 5.构造方法引用,要求:绑定函数式接口,且函数式接口为构造方法构造的对象
//        IPerson p1 = Person::new;
//        Person person = p1.initPerson("tom", "男", 18);
//        System.out.println(person);

        /*String name = PersonCon.testPersonCon(new IPerson() {
            @Override
            public Person initPerson(String name, String gender, int age) {
                return new Person(name, gender, age);
            }
        }).getName();*/

        //因为Person类中的全参构造方法和IPerson接口中的返回值类型和参数类型及入参一致,即可使用构造方法引用
        //Person::new相当于直接重写了IPerson类中的initPerson方法,重写方法的返回值和入参即为Person的全参构造方法的返回值和入参
        String name = PersonCon.testPersonCon(Person::new).getName();
        System.out.println(name);
    }
}

Stream概述

介绍

Stream 是用函数式编程方式在集合类上进行复杂操作的工具,其集成了Java 8中的众多新特性之一的聚合操作,开发者可以更容易地使用Lambda表达式,并且更方便地实现对集合的查找、遍历、过滤以及常见计算等。

代码
package com.imooc.test;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;

public class Test2 {

    public static void main(String[] args) {

        // 1.添加测试数据,存储多个账号的列表
        List<String> accounts = new ArrayList<>();
        accounts.add("tom");
        accounts.add("jerry");
        accounts.add("shuke");
        accounts.add("beita");
        accounts.add("damu");

        // 业务要求:长度大于等于3的为有效账号
        // 1. 业务实现  增强for循环
        List<String> lista = new ArrayList<>();
        for (String account : accounts) {
            if (account.length() >= 3) {
                lista.add(account);
            }
        }
        System.out.println(lista);

        // 2. 增强for循环底层规则  迭代器
        List<String> listb = new ArrayList<>();
        Iterator<String> it = accounts.iterator();
        while(it.hasNext()) {
            String account = it.next();
            if(account.length() >= 3) {
                listb.add(account);
            }
        }
        System.out.println(listb);

        // 3. stream结合lambda表达式完成业务处理
        List<String> listc = accounts.stream().filter(account->account.length() >= 3).collect(Collectors.toList());
        System.out.println(listc);
    }
}

Stream常见操作API介绍

1、聚合操作:对批量数据进行操作

​ 例如:获取指定用户的平均消费额,获取指定店铺中最便宜的商品

2、stream的处理流程

​ 1.获取数据源
​ 2.对数据源进行数据转换
​ 3.执行操作,获取结果

3、获取Stream对象

​ 1.从集合或者数组中获取[**]
​ Collection.stream(),如accounts.stream()
​ Collection.parallelStream():获取支持并发的stream对象
​ Arrays.stream(T t)

​ 2.BufferReader
​ BufferReader.lines()-> stream()

​ 3.静态工厂
​ java.util.stream.IntStream.range()…
​ java.nio.file.Files.walk()…

​ 4.自定构建
​ java.util.Spliterator

​ 5.更多的方式…
​ Random.ints()
​ Pattern.splitAsStream()…

4、中间操作API{intermediate}

​ 操作结果是一个Stream,中间操作可以有一个或者多个连续的中间操作,需要注意的是,中间操作只记录操作方式,不做具体执 行,直到结束操作发生时,才做数据的最终执行。
​ 中间操作:就是业务逻辑处理。
​ 中间操作过程:无状态:数据处理时,不受前置中间操作的影响。
​ map/filter/peek/parallel/sequential/unordered
​ 有状态:数据处理时,受到前置中间操作的影响。
​ distinct/sorted/limit/skip

5、终结操作|结束操作{Terminal}

需要注意:一个Stream对象,只能有一个Terminal操作,这个操作一旦发生,就会真实处理数据,生成对应的处理结果。
终结操作:非短路操作:当前的Stream对象必须处理完集合中所有 数据,才能得到处理结果。
forEach/forEachOrdered/toArray/reduce/collect/min/max/count/iterator
短路操作(Short-circuiting):当前的Stream对象在处理过程中,一旦满足某个条件,就可以得到结果。
anyMatch/allMatch/noneMatch/findFirst/findAny等
适用场景:无限大的Stream -> 有限大的Stream。

Stream操作集合中的数据

package com.imooc.test;

import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * @ClassName: Test5
 * @Description: Stream操作集合中的数据
 * @Author: keke
 * @Date: 2021/4/13
 */
public class Test5 {

    public static void main(String[] args) {
        //1.批量数据获取Stream对象
        //多个数据
        Stream<String> stream = Stream.of("admin", "tom", "keke");

        //数组
        String[] strArrays = new String[] {"xueqi", "biyao"};
        Stream<String> stream2 = Arrays.stream(strArrays);

        //列表
        List<String> list = new ArrayList<>();
        list.add("少林");
        list.add("武当");
        list.add("青城");
        list.add("峨眉");
        Stream<String> stream3 = list.stream();

        //集合
        Set<String> set = new HashSet<>();
        set.add("少林罗汉拳");
        set.add("武当长拳");
        set.add("青城剑法");
        Stream<String> stream4 = set.stream();

        //Map
        Map<String, Integer> map = new HashMap<>();
        map.put("tom", 1000);
        map.put("jerry", 1200);
        map.put("shuke", 1000);
        Stream<String> stream5 = map.keySet().stream();

        //2.Stream对象对于基本数据类型的功能封装
        //目前有这几个int / long / double
        /*
        * forEach(System.out::println)解释:
        * 调用的是void forEach(IntConsumer action);方法,IntConsumer是一个函数式接口,抽象方法为void accept(int value);
        * System.out是一个PrintStream对象,它里面存在一个成员方法public void println(int x) {}
        * 这两个方法均无返回值、方法参数类型个数一致,即为实例方法引用的调用
        * 关于x哪来的问题:这就回到lambda表达式的基础了,就是调用forEach(System.out::println)方法的IntStream对象中的一个个数据
        * 需要注意的是IntStream.of方法返回的是一个IntPipeline对象
        * */
//        IntStream.of(new int[]{10, 20, 30}).forEach(System.out::println); //在底层只做了一次拆箱和装箱
//        IntStream.range(1, 5).forEach(System.out::println); //包含起始数据,不包含结束数据
//        IntStream.rangeClosed(1, 5).forEach(System.out::println); //即包含起始数据,也包含结束数据

        //3.Stream对象 --> 转换得到指定的数据类型
        //数组
        /*
        * toArray(String[]::new)解释:即调用<A> A[] toArray(IntFunction<A[]> generator);方法
        * IntFunction为一个函数接口,抽象方法为R apply(int value);
        * String[]::new表示的构造方法
        * TODO 暂时不知道怎么回事
        * */
//        String[] strings = stream.toArray(String[]::new);//将stream对象转换为String数组
//        for (String s : strings) {
//            System.out.println(s);
//        }

        //字符串
        //注意:若上面已经将stream对象转换为String数组,则下面转换会报错,因为一个stream对象,只能有一个Terminal操作
//        String str = stream.collect(Collectors.joining()); //将stream对象转换为String对象
//        System.out.println(str); //admintomkeke

        //列表
//        List<String> listx = stream.collect(Collectors.toList());
//        System.out.println(listx);

        //集合
//        Set<String> setx = stream.collect(Collectors.toSet());
//        System.out.println(setx);

        //Map
//        Map<String, String> mapx = stream.collect(Collectors.toMap(x->x, y->"value"+y));
//        System.out.println(mapx);

        //4.Stream中常见的API操作
        List<String> accountList = new ArrayList<>();
        accountList.add("songjiang");
        accountList.add("lujunyi");
        accountList.add("wuyong");
        accountList.add("luzhishen");
        accountList.add("likui");

        //map中间操作,map()方法接收一个Functional接口
//        accountList = accountList.stream().map(x -> "梁山好汉:" + x).collect(Collectors.toList());

        //fiter() 添加过滤条件,过滤符合条件的用户
        //如果是复杂业务可以自定义一个Stream<T> filter(Predicate<? super T> predicate);方法中Predicate的实现类进行操作
//        accountList = accountList.stream().filter(x -> x.length() > 7).collect(Collectors.toList());

        //foreach增强型循环,支持方法引用操作
//        accountList.forEach(x -> System.out.println("forEach->" + x));
//        accountList.forEach(x -> System.out.println("forEach->" + x));
//        accountList.forEach(x -> System.out.println("forEach->" + x));
        /*
        * 如果需要对集合中数据进行多次迭代操作,即可进行多次foreach操作,但是迭代过程就会出现冗余,此时对于数据的处理器非常不友好
        * 解决方法:peek()中间操作,迭代数据完成数据的一次处理过程
        * */
//        accountList.stream().peek(x -> System.out.println("peek 1:" + x)) //第一次迭代
//                .peek(x -> System.out.println("peek 2:" + x)) //第二次迭代
//                .forEach(System.out::println); //第三次迭代


        //5.Stream中对于数字运算的支持
        List<Integer> integerList = new ArrayList<>();
        integerList.add(20);
        integerList.add(19);
        integerList.add(7);
        integerList.add(8);
        integerList.add(86);
        integerList.add(11);
        integerList.add(3);
        integerList.add(20);

        //skip() 中间操作,有状态(即这个中间操作一旦发生对后续操作有一定的影响),跳过部分数据
//        integerList.stream().skip(3).forEach(System.out::println);

        //limit() 中间操作,有状态,限制输出数据量
//        integerList.stream().skip(3).limit(2).forEach(System.out::println);

        //distinct() 中间操作,有状态,剔除重复的数据
//        integerList.stream().distinct().forEach(System.out::println);

        //sorted() 中间操作,有状态,排序
//        integerList.stream().sorted().forEach(System.out::println); //默认从小到大
//        integerList.stream().sorted((x,y) -> y-x).forEach(System.out::println);

        //max() 获取最大值
        Optional<Integer> max = integerList.stream().max((x, y) -> x - y);
        System.out.println(max.get());

        //min() 获取最小值
        Optional<Integer> min = integerList.stream().min((x, y) -> x - y);
        System.out.println(min.get());

        //reduce() 合并处理数据
        Optional<Integer> result = integerList.stream().reduce((sum, x) -> x + sum);
        System.out.println(result.get());

    }

}

四、lambda表达式在实际生产中的应用

lambda表达式重构项目

介绍

假设以后项目设计非常完善,可能在各种业务场景中用到大量设计模式,无论是工厂模式、策略模式、适配器模式等,都会提供类似这个项目的函数式接口完成完整功能的对接以及业务功能的耦合,在这种情况下,就可以使用lambda表达式封装数据接口,将数据业务和数据逻辑上移,交给业务调用者进行处理,作为数据提供者,只需要管理好数据接口即可,实现整个业务功能中数据逻辑的处理方式以及业务处理方式的一些耦合,lambda提供很好的处理方式,以此项目作为演示。

图解

在这里插入图片描述

代码

函数式接口

package com.imooc.dao;

/**
 * ORM查询策略
 */
@FunctionalInterface
public interface IStrategy<T> {
    /**
     * 测试方法
     * @param t 要测试的对象
     * @return 返回测试结果
     */
    Boolean test(T t);
}

pojo

package com.imooc.pojo;

/**
 * 部门类
 * @author 大牧
 * @version 1.0.0
 */
//@Data
//@AllArgsConstructor
//@NoArgsConstructor
//@Accessors(chain = true)
public class Department<AllArgsConstructor> {
    private Long        deptNo;     //  部门编号
    private String      deptName;   //  部门名称
    private String      location;   //  部门所在地区

    public Long getDeptNo() {
        return deptNo;
    }

    public void setDeptNo(Long deptNo) {
        this.deptNo = deptNo;
    }

    public String getDeptName() {
        return deptName;
    }

    public void setDeptName(String deptName) {
        this.deptName = deptName;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }
}

dao

package com.imooc.dao;

import java.sql.SQLException;
import java.util.List;

/**
 * 数据ORM接口
 * @author 大牧
 * @version V1.0
 */
public interface BaseDAO<T> {

    /**
     * 增加对象数据
     * @param t 要增加的对象
     * @return 增加操作影响的记录数
     * @throws SQLException 可能会抛出SQLException异常
     */
    Integer add(T t) throws SQLException;

    /**
     * 根据编号删除对象数据
     * @param id 要删除的对象编号
     * @return 返回删除操作影响的记录数
     * @throws SQLException 可能会抛出SQLException异常
     */
    Integer delete(String id) throws SQLException;

    /**
     * 修改对象数据
     * @param t 更新后的对象数据
     * @return 返回更新操作影响的记录数
     * @throws SQLException 可能会抛出SQLException异常
     */
    Integer update(T t) throws SQLException;

    /**
     * 根据编号查询T对象
     * @param id 编号
     * @return 返回查询到的单个对象
     * @throws SQLException 可能会抛出SQLException异常
     */
    T findById(String id) throws SQLException;

    /**
     * 查询所有T数据
     * @return 返回查询到的T数据
     * @throws SQLException 可能会抛出SQLException异常
     */
    List<T> findAll() throws SQLException;

    /**
     * 按照指定策略查询数据
     * @param strategy 查询策略
     * @return 返回查询到符合条件的T数据
     * @throws SQLException 可能会抛出SQLException异常
     */
    List<T> findByStrategy(IStrategy strategy) throws SQLException;
}
package com.imooc.dao.impl;

import com.imooc.dao.BaseDAO;
import com.imooc.dao.IStrategy;
import com.imooc.dao.mapper.DeptMapper;
import com.imooc.pojo.Department;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

@Repository
public class DeptDAO implements BaseDAO<Department> {

    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private DeptMapper mapper;
    private List<Department> list;

    @Override
    public Integer add(Department department) throws SQLException {
        return jdbcTemplate.update("insert into department(deptname, location) values(?, ?)",
                department.getDeptName(), department.getLocation());
    }

    @Override
    public Integer delete(String id) throws SQLException {
        return jdbcTemplate.update("delete from department where deptno = ?", id);
    }

    @Override
    public Integer update(Department department) throws SQLException {
        return jdbcTemplate.update("update department set deptname = ?, location = ? where deptno = ?",
                department.getDeptName(),
                department.getLocation(),
                department.getDeptNo());
    }

    @Override
    public Department findById(String id) throws SQLException {
        return jdbcTemplate.queryForObject("select * from department where deptno = ?", mapper, id);
    }

    @Override
    public List<Department> findAll() throws SQLException {
        return jdbcTemplate.query("select * from department", mapper);
    }

    @Override
    public List<Department> findByStrategy(IStrategy strategy) throws SQLException {
        List<Department> tempList = new ArrayList<>();
        list = this.findAll(); //可以通过缓存将查询所有的数据放入缓存不必再次查询数据库
        //通过传入的strategy策略进行过滤,返回符合条件的Department
        for (Department department : list) {
            if (strategy.test(department)) {
                tempList.add(department);
            }
        }
        return tempList;
    }
}

service

package com.imooc.service;

import com.imooc.dao.IStrategy;
import com.imooc.dao.impl.DeptDAO;
import com.imooc.pojo.Department;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.sql.SQLException;
import java.util.List;

/**
 * 部门业务受理类
 * @author 大牧
 * @version 1.0.0
 */
@Service
public class DeptService {
    @Autowired
    private DeptDAO deptDAO;
    private List<Department> list;

    /**
     * 查询所有部门数据
     * @return 返回所有的部门数据
     */
    public List<Department> getAllDepartment() {
        try {
            list = deptDAO.findAll();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return list;
    }

    /**
     * 根据部门名称查询部门
     */
    public List<Department> getDepartmentByName(String name) {
        try {
            list = deptDAO.findByStrategy(new IStrategy<Department>() {
                @Override
                public Boolean test(Department department) {
                    return department.getDeptName().contains(name);
                }
            });
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return list;
    }

    /**
     * 根据地区获取部门
     * @param location 地区名称
     * @return 查询到的部门
     */
    public List<Department> getDepartmentByLocation(String location) {
        try {
            //匿名内部类实现
            list = deptDAO.findByStrategy(new IStrategy<Department>() {
                @Override
                public Boolean test(Department department) {
                    return department.getLocation().contains(location);
                }
            });
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return list;
    }

    /**
     * @Author keke
     * @Description lambda表达式封装查询部门
     * @Date
     * @Param
     * @return java.util.List<com.imooc.pojo.Department>
     **/
    public List<Department> getDepartmentByLambda(IStrategy<Department> strategy) {
        List<Department> list = null;
        try {
            list = deptDAO.findByStrategy(strategy);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return list;
    }

}

controller

package com.imooc.controller;

import com.imooc.pojo.Department;
import com.imooc.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/dept")
public class DeptController {

    @Autowired
    private DeptService deptService;

    @GetMapping("/hello")
    public String hello() {
        return "hello imooc!";
    }

    @GetMapping("/list")
    public List<Department> getAllDepartment() {
        return deptService.getAllDepartment();
    }

    @GetMapping("/list/deptname/{dname}")
    public List<Department> getDepartmentByName(@PathVariable String dname) {
//        return deptService.getDepartmentByName(dname);
        return deptService.getDepartmentByLambda(department->department.getDeptName().contains(dname));
    }

    @GetMapping("/list/location/{location}")
    public List<Department> getDepartmentByLocation(@PathVariable String location) {
//        return deptService.getDepartmentByLocation(location);
        return deptService.getDepartmentByLambda(department -> department.getLocation().contains(location));
    }
}

lambda和Stream性能问题

package com.imooc.performance;

import java.util.*;

public class Test {

    public static void main(String[] args) {

        Random random = new Random();
//        // 1. 基本数据类型:测试获取最大的整数
//        List<Integer> integerList = new ArrayList<Integer>();
//
//        for(int i = 0; i < 1000000; i++) {
//            integerList.add(random.nextInt(Integer.MAX_VALUE));
//        }
//
//        // 1) stream
//        testStream(integerList);
//        // 2) parallelStream(并行stream)
//        testParallelStream(integerList);
//        // 3) 普通for
//        testForloop(integerList);
//        // 4) 增强型for
//        testStrongForloop(integerList);
//        // 5) 迭代器
//        testIterator(integerList);

        //结论:普通for、增强型for、迭代器性能差不多;
        //stream流的方式在处理基础数据的时候不如普通for、增强型for、迭代器;
        //parallelStream方式基本接近普通for、增强型for、迭代器

        // 2. 复杂数据类型:对象
        List<Product> productList = new ArrayList<>();
        for(int i = 0; i < 1000000; i++) {
            productList.add(new Product("pro" + i, i, random.nextInt(Integer.MAX_VALUE)));
        }

        // 调用执行
        testProductStream(productList);
        testProductParallelStream(productList);
        testProductForloop(productList);
        testProductStrongForloop(productList);
        testProductIterator(productList);

        //结论:普通for、增强型for、迭代器性能差不多;
        //stream流的方式在处理基础数据的时候不如普通for、增强型for、迭代器;
        //parallelStream方式基本接近甚至超过普通for、增强型for、迭代器

        /*
        * 最终结论:对于简单数据的迭代处理,可以直接通过普通for、增强型for、迭代器进行操作
        * 如果在性能上有一定的要求可以使用parallelStream方式
        * 对于复杂的对象的处理操作,可以使用stream流的方式、parallelStream方式替代普通for、增强型for、迭代器
        * 如果在性能上有一定的要求可以使用parallelStream方式,parallelStream方式在多线程环境在更能发挥优势
        * */

    }

    public static void testStream(List<Integer> list) {
        long start = System.currentTimeMillis();

        Optional optional = list.stream().max(Integer::compare);
        System.out.println(optional.get());

        long end = System.currentTimeMillis();
        System.out.println("testStream:" + (end - start) + "ms");
    }

    public static void testParallelStream(List<Integer> list) {
        long start = System.currentTimeMillis();

        Optional optional = list.parallelStream().max(Integer::compare);
        System.out.println(optional.get());

        long end = System.currentTimeMillis();
        System.out.println("testParallelStream:" + (end - start) + "ms");
    }

    public static void testForloop(List<Integer> list) {
        long start = System.currentTimeMillis();

        int max = Integer.MIN_VALUE;
        for(int i = 0; i < list.size(); i++) {
            int current = list.get(i);
            if (current > max) {
                max = current;
            }
        }
        System.out.println(max);

        long end = System.currentTimeMillis();
        System.out.println("testForloop:" + (end - start) + "ms");
    }

    public static void testStrongForloop(List<Integer> list) {
        long start = System.currentTimeMillis();

        int max = Integer.MIN_VALUE;
        for (Integer integer : list) {
            if(integer > max) {
                max = integer;
            }
        }
        System.out.println(max);

        long end = System.currentTimeMillis();
        System.out.println("testStrongForloop:" + (end - start) + "ms");
    }

    public static void testIterator(List<Integer> list) {
        long start = System.currentTimeMillis();

        Iterator<Integer> it = list.iterator();
        int max = it.next();

        while(it.hasNext()) {
            int current = it.next();
            if(current > max) {
                max = current;
            }
        }
        System.out.println(max);

        long end = System.currentTimeMillis();
        System.out.println("testIterator:" + (end - start) + "ms");
    }


    public static void testProductStream(List<Product> list) {
        long start = System.currentTimeMillis();

        Optional optional = list.stream().max((p1, p2)-> p1.hot - p2.hot);
        System.out.println(optional.get());

        long end = System.currentTimeMillis();
        System.out.println("testProductStream:" + (end - start) + "ms");
    }

    public static void testProductParallelStream(List<Product> list) {
        long start = System.currentTimeMillis();

        Optional optional = list.stream().max((p1, p2)-> p1.hot - p2.hot);
        System.out.println(optional.get());

        long end = System.currentTimeMillis();
        System.out.println("testProductParallelStream:" + (end - start) + "ms");
    }

    public static void testProductForloop(List<Product> list) {
        long start = System.currentTimeMillis();

        Product maxHot = list.get(0);
        for(int i = 0; i < list.size(); i++) {
            Product current = list.get(i);
            if (current.hot > maxHot.hot) {
                maxHot = current;
            }
        }
        System.out.println(maxHot);

        long end = System.currentTimeMillis();
        System.out.println("testProductForloop:" + (end - start) + "ms");
    }

    public static void testProductStrongForloop(List<Product> list) {
        long start = System.currentTimeMillis();

        Product maxHot = list.get(0);
        for (Product product : list) {
            if(product.hot > maxHot.hot) {
                maxHot = product;
            }
        }
        System.out.println(maxHot);

        long end = System.currentTimeMillis();
        System.out.println("testProductStrongForloop:" + (end - start) + "ms");
    }

    public static void testProductIterator(List<Product> list) {
        long start = System.currentTimeMillis();

        Iterator<Product> it = list.iterator();
        Product maxHot = it.next();

        while(it.hasNext()) {
            Product current = it.next();
            if (current.hot > maxHot.hot) {
                maxHot = current;
            }
        }
        System.out.println(maxHot);

        long end = System.currentTimeMillis();
        System.out.println("testProductIterator:" + (end - start) + "ms");
    }

}

class Product {
    String name;    // 名称
    Integer stock;  // 库存
    Integer hot;    // 热度

    public Product(String name, Integer stock, Integer hot) {
        this.name = name;
        this.stock = stock;
        this.hot = hot;
    }
}

线程安全问题

package com.imooc.performance;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Author keke
 * @Description 通过整数列表的数据复制来测试并行stream线程是否安全
 * @Date
 * @Param
 * @return
 **/

public class Test2 {

    public static void main(String[] args) {
        // 整数列表
        List<Integer> lists = new ArrayList<Integer>();
        // 增加数据
        for (int i = 0; i < 1000000; i++){
            lists.add(i);
        }

        System.out.println("原有集合长度:" + lists.size());

        // 串行Stream
        List<Integer> list2 = new ArrayList<>();
        lists.stream().forEach(x->list2.add(x));
        System.out.println("串行Stream:" + list2.size());

        // 并行Stream
        List<Integer> list3 = new ArrayList<>();
        lists.parallelStream().forEach(x-> list3.add(x));
        System.out.println("并行Stream:" + list3.size());

        /*
        * 结论:并行Stream线程是不安全的,导致数据出现丢失,相同的程序如果多次运行,甚至会出现多个线程访问同样的数据导致数据溢出的情况
        * 官方解释:因为并行Stream在操作过程中使用的collections本身就不是线程安全的,所以在多线程操作的情况下,有可能会因为多个线程的
        * 处理过程导致一些数据不一致的错误,在处理过程中,collections集合本身提供了一些线程同步块,通过线程同步块可以完成对于数据的同
        * 步处理,另外,如果使用这种方式会引起线程竞争的问题,如果想避免线程竞争的问题,聚合操作和parallelStream结合到一起能让我们得
        * 到一个在非线程安全的情况下对于数据处理的过程,但是要求在操作过程中,对于非线程安全的集合中的数据不能修改
        * stream中的API文档也对非线程安全进行规约:foreach()方法:官方在底层实现foreach操作时,为了提升并行Stream的性能并没有考虑它
        * 线程同步的问题,但是官方提供的一切其他的终端操作,比如reduce()、collect()等当并行执行时,可以实例化,填充和合并多个中间结果,
        * 以便保持可变数据结构的隔离。因此,即使与非线程安全的数据结构(例如:ArrayList)并行执行,并行还原也不需要额外的同步
        * 最终结论:所以当我们在操作并行Stream出现非线程安全时,可以通过自定义编码添加线程锁的方式;或者使用stream的官方API提供的collect()
        * 终端操作来完成并行Stream的处理过程,操作如下。但是在更多的场景中,如果我们遇到多线程问题,会直接使用线程安全的集合来贵方数据源
        * */

        List<Integer> list4 = lists.parallelStream().collect(Collectors.toList());
        System.out.println("并行StreamPlus:" + list4.size());
    }
}

运行结果

原有集合长度:1000000
串行Stream:1000000
并行Stream:464095
并行StreamPlus:1000000

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值