conanan's blog conanan's blog
首页
关于
  • 分类
  • 标签
  • 归档
  • Java
  • Java Web
  • 工具

    • Maven
  • MySQL
  • Redis
  • Git
  • Vim
  • Nginx
  • Docker
GitHub

Evan Xu

前端界的小学生
首页
关于
  • 分类
  • 标签
  • 归档
  • Java
  • Java Web
  • 工具

    • Maven
  • MySQL
  • Redis
  • Git
  • Vim
  • Nginx
  • Docker
GitHub
  • 基础

  • API

    • 4 API-1 字符串
    • 4 API-2 时间
    • 4 API-3 时间-old
    • 4 API-4 比较器
    • 4 API-5 精确数据类型
    • 4 API-6 Math
    • 4 API-7 Random
    • 4 API-8 System
    • 4 API-9 Object
    • 4 API-习题
    • 13 Lambda
      • 函数式接口
      • 函数式编程
      • Lambda 的延迟执行
        • 性能浪费的日志案例
        • 体验 Lambda 的更优写法
      • 常用的函数式接口
        • Supplier—生产型接口
        • Consumer—消费型接口
        • Predicate—判断
        • Function
      • 方法引用
        • 引用符::
        • 对象::非静态方法
        • 类名::静态方法
        • 类名::非静态方法
        • 通过 super 引用成员方法
        • 通过 this 引用成员方法
      • 构造器引用
      • 数组的构造器引用
      • 习题
        • Comparator 使用 🔥
        • 函数式接口 1 🔥
        • 函数式接口 2 🔥
        • Pedicate 接口使用 🔥
        • Function 接口使用 🔥
    • 14 Stream API
    • 15 Optional
  • Container

  • 多线程

  • 16 9,10,11新特性
  • 17 Test
  • 18 设计原则&设计模式
  • JDBC
  • Java
  • API
conanan
2020-12-14

13 Lambda

# Lambda

在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么及结果,而不是以什么形式做。

面向对象的思想:做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情.

函数式编程思想:只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程

  • 冗余的 Runnable 代码分析

    • Thread 类需要 Runnable 接口作为参数,其中的抽象 run 方法是用来指定线程任务内容的核心;
    • 为了指定 run 的方法体,不得不需要 Runnable 接口的实现类;
    • 为了省去定义一个 RunnableImpl 实现类的麻烦,不得不使用匿名内部类;
    • 必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;
    • 而实际上,似乎只有方法体才是关键所在。
  • 匿名内部类的好处与弊端

    • 一方面,匿名内部类可以帮我们省去实现类的定义;
    • 另一方面,匿名内部类的语法——确实太复杂了!
  • 编程思想转换

    做什么,而不是怎么做,只要能够更好地达到目的,过程与形式其实并不重要。2014 年 3 月 Oracle 所发布的 Java 8 中,加入了 Lambda 表达式的重量级新特性,为我们打开了新世界的大门。

# 函数式接口

函数式接口在 Java 中是指:有且仅有一个抽象方法的接口。可以适用于 Lambda 使用的接口,只有确保接口为函数式接口,Java 中的 Lambda 才能顺利地进行推导。

备注:“语法糖”是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的 for-each 语法,其实底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,Java 中的 Lambda 可以被当做是匿名内部类的“语法糖”,但是二者在原理上是不同的。

格式

修饰符 interface 接口名称 {
    /*public abstract*/ 返回值类型 方法名称(可选参数信息);
    // 其他非抽象方法内容,默认方法,静态方法,私有方法
}
1
2
3
4

@FunctionalInterface注解

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

# 函数式编程

  • Lambda 表达式的标准格式为

    (参数类型 参数名称) ‐> { 代码语句 }
    
    1
    • 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
    • -> 是新引入的语法格式,代表指向动作。
    • 大括号内的语法与传统方法体要求基本一致。
  • 无参数返回值

    new Thread( () ‐>  System.out.println("多线程任务执行!")).start());
    
    1
  • 有参数和返回值

    Arrays.sort(students,(s1, s2)->{ //参数类型可以从students推导出,so可以省略
        return s1.getAge()-s2.getAge();
    });
    
    1
    2
    3
    Collections.sort(students, (o1, o2) -> o1.getAge()-o2.getAge()); //{}内只有一行表达式,可以省略{}和return
    
    1
    Arrays.sort(students, Comparator.comparingInt(Student::getAge)); //更简洁的写法,但{}中语句多时不能简写
    
    1
  • 可推导可省略:Lambda 强调的是“做什么”而不是“怎么做”,凡可以根据上下文推导得知的信息,都可以省略。

    • 小括号内参数的类型可以省略
    • 如果小括号内有且仅有一个参,则小括号可以省略
    • 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return 关键字及语句分号。
  • Lambda 的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:

    • 使用 Lambda 必须具有函数式接口,其本质就是一个函数式接口的实例

      无论是 JDK 内置的 Runnable 、 Comparator 接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用 Lambda。

    • 使用 Lambda 必须具有上下文类型推断

      方法的参数或局部变量类型必须为Lambda 对应的接口类型,才能使用 Lambda 作为该接口的实例。

    • 若 Lambda 表达式抛出一个受检异常(即非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明

# Lambda 的延迟执行

有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而 Lambda 表达式是延迟执行的,这正好可以 作为解决方案,提升性能。

# 性能浪费的日志案例

public static void main(String[] args) throws FileNotFoundException {
    String a = "hello";
    String b = "world";
    log(1,a+b);
}
public static void log(int level,String msg) {
    if (level==1) {
        System.out.println(msg);
    }
}
1
2
3
4
5
6
7
8
9
10

无论级别是否满足要求,作为log 方法的第二个参数,三个字符串一定会首先被拼接并传入方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。

备注:SLF4J是应用非常广泛的日志框架,它在记录日志时为了解决这种性能浪费的问题,并不推荐首先进行字符串的拼接,而是将字符串的若干部分作为可变参数传入方法中,仅在日志级别满足要求的情况下才会进行字符串拼接。例如: LOGGER.debug("变量{}的取值为{}。", "os", "macOS") ,其中的大括号 {} 为占位符。如果满足日志级别要求,则会将“os”和“macOS”两个字符串依次拼接到大括号的位置;否则不会进行字 符串拼接。这也是一种可行解决方案,但 Lambda 可以做到更好

# 体验 Lambda 的更优写法

函数式接口

@FunctionalInterface
public interface MessageBuilder {
    String buildMessage();
}
1
2
3
4

然后对 log 方法进行改造

public static void main(String[] args) throws FileNotFoundException {
    String a = "hello";
    String b = "world";
    log(1, () -> a + b );
}
public static void log(int level,MessageBuilder mb) {
    if (level==1) {
        System.out.println(mb.buildMessage());
    }
}
1
2
3
4
5
6
7
8
9
10

只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接。

证明:只需在 lambda 大括号中打印一个语句,并让log方法传递的参数改为 2,发现打印语句不执行

# 常用的函数式接口

JDK 提供了大量常用的函数式接口以丰富 Lambda 的典型使用场景,它们主要在**java.util.function**中提供。

java.lang.Runnable、java.util.Comparator不再上述包中,但也是函数式接口

如下函数式接口强调的是状态的改变!不在乎具体实现细节!具体实现交给 Lambda 来完成!

Java 内置四大核心函数式接口

image-20191105234610241

其他接口

image-20191105234644979

# Supplier—生产型接口

java.util.function.Supplier<T>用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的 Lambda 表达式需要“对外提供”一个符合泛型类型的对象数据。这个接口也称为生产型接口。

  • 抽象方法 T get()

    //求数组元素最大值
    public static void main(String[] args) {
      int[] arr = {1, 22, 55, 333, 66};
      int m = getMax(arr,() -> {
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
          if (arr[i] > max)
            max = arr[i];
        }
        return max;
      });
      System.out.println(m);
    }
    
    public static int getMax(int[] arr,Supplier<Integer> supplier) {
      return supplier.get();
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

# Consumer—消费型接口

java.util.function.Consumer<T>接口则正好与Supplier接口相反,它不是生产一个数据。消费型接口。

  • 抽象方法void accept(T t)

    意为消费一个指定泛型的数据。

    public static void main(String[] args) {
        String msg = "牛逼";
        consumeString(msg,s ‐> {
            //怎么处里消费随意
        }
    }
    
    public static void consumeString(String msg, Consumer<String> consumer) {
        consumer.accept(msg);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  • 默认方法default Consumer<T> andThen(Consumer<T> c)

    如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作, 然后再做一个操作,实现组合,哪个写前面则先消费。返回值是本身,可以用链式编程,三个、四个操作都行。这个方法就是 Consumer 接口中的 default 方法 andThen 。

    public static void main(String[] args) {
        /**
         * 下面的字符串数组当中存有多条信息,请按照格式“ 姓名:XX;性别:XX。 ”的格式将信息打印出来。
         * 要求将打印姓名的动作作为第一个Consumer接口的Lambda实例,将打印性别的动作作为第二个Consumer接口的Lambda实例,
         */
        String[] arr = {"迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男"};
        consumerString(arr,
                       s -> System.out.print("姓名:" + s.split(",")[0] + ";"),
                       s -> System.out.println("性别:" + s.split(",")[1] + "。")
        );
    
    }
    
    public static void consumerString(String[] msg, Consumer<String> consumer1, Consumer<String> consumer2) {
        for (String s : msg) {
            consumer1.andThen(consumer2).accept(s);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

# Predicate—判断

有时候我们需要对某种类型的数据进行判断,从而得到一个 boolean 值结果。这时可以使用 java.util.function.Predicate<T>接口。

  • 抽象方法: boolean test(T t)

    //判断字符串长度是否大于3
    public static void main(String[] args) {
        String msg = "hello";
        boolean b = panduan(msg, s -> s.length() > 3);
        System.out.println(b);
    
    }
    public static boolean panduan(String s, Predicate<String> predicate){
        return predicate.test(s);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  • 默认方法:Predicate<T> and(Predicate<? super T> other) ,与

  • 默认方法:Predicate<T> or(Predicate<? super T> other) ,或

  • 默认方法:Predicate<T> negate() ,非

  • 静态方法:static <T> Predicate<T> isEqual(Object targetRef),相等,根据Objects.equals(O,O)

    System.out.println(Predicate.isEqual("test").test("test"));//true,返回Predicate实例,之后也可以用其他方法
    
    1
    public static void main(String[] args) {
        String msg = "hello";
        boolean b = panduan(msg,s -> s.length() > 3,s -> s.contains("w"));
        System.out.println(b);
    
    }
    public static boolean panduan(String s, Predicate<String> p1,Predicate<String> p2){
        return p1.and(p2).test(s); //and
        //return p1.or(p2).test(s); //or
        //return p1.negate().and(p2).test(s); //negate
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static void main(String[] args) {
        /**
             * 数组当中有多条“姓名+性别”的信息如下,请通过 Predicate 接口的拼装将符合要求的字符串筛选到集合 ArrayList 中
             * 需要同时满足两个条件:1. 必须为女生; 2. 姓名为4个字。
             */
        String[] array = {"迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女"};
        ArrayList<String> list = toList(array,
                                        s -> s.split(",")[0].length() == 4,
                                        s -> "女".equals(s.split(",")[1])
        );
        System.out.println(list);
    }
    
    public static ArrayList<String> toList(String[] arr, Predicate<String> p1, Predicate<String> p2) {
        ArrayList<String> list = new ArrayList<>();
        for (String s : arr) {
            if (p1.and(p2).test(s))
                list.add(s);
        }
        return list;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

# Function

java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件, 后者称为后置条件。

  • 抽象方法: R apply(T t):根据类型 T 的参数获取类型 R 的结果。如String转Integer

  • 默认方法:Function<T,V> andThen(Function<? super R,? extends V> after),用来组合操作

    public static void main(String[] args) {
        /**
             * 第一个操作是将字符串解析成为int数字,
             * 第二个操作是乘以10并转为字符串。两个操作通过 andThen 按照前后顺序组合到了一 起。
             */
        String msg = "1234";
        String string = change(msg, s -> Integer.valueOf(s), s -> (s * 10)+"" /*或String.valueOf()*/);
        System.out.println(string);
    }
    
    public static String change(String msg, Function<String, Integer> f1, Function<Integer, String> f2) {
        return f1.andThen(f2).apply(msg);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public static void main(String[] args) {
        String str = "赵丽颖,20";
        //1. 将字符串截取数字年龄部分,得到字符串;
        //2. 将上一步的字符串转换成为int类型的数字;
        //3. 将上一步的int数字累加100,得到结果int数字
        int change = change(str,
                            s -> s.split(",")[1],
                            s -> Integer.parseInt(s),
                            i -> i + 100
        );
        System.out.println(change);
    }
    
    public static int change(String s, Function<String, String> f1,
                             Function<String, Integer> f2, Function<Integer, Integer> f3) {
        return f1.andThen(f2).andThen(f3).apply(s);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

# 方法引用

在使用 Lambda 表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿什么参数做什么操作。那么考虑 一种情况:如果我们在 Lambda 中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑?

# 引用符::

双冒号:: 为引用运算符,而它所在的表达式被称为方法引用。如果 Lambda 要表达的函数方案(方法体)已经存在于某个方法的实现中(出入参一致仅代表对象::非静态方法和类名::静态方法),那么则可以通过双冒号来引用该方法作为 Lambda 的替代者。

语义分析

System.out对象中有一个重载的 println(String) 方法恰好就是我们所需要的。那么对于 printString 方法的函数式接口参数,对比下面两种写法,完全等效:

  • Lambda 表达式写法: s -> System.out.println(s);
  • 方法引用写法: System.out::println

第一种语义是指:拿到参数之后经 Lambda 之手,继而传递给 System.out.println 方法去处理。

第二种等效写法的语义是指:直接让 System.out 中的 println 方法来取代 Lambda

注:Lambda 中传递的参数 一定是方法引用中的那个方法可以接收的类型,否则会抛出异常

推导与省略

如果使用 Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式——它们都将被自动推导。而如果使用方法引用,也是同样可以根据上下文进行推导。

函数式接口是 Lambda 的基础,而方法引用是 Lambda 的孪生兄弟。

# 对象::非静态方法

与上例相同。对象已经存在,方法也存在,可以使用对象名::方法名替代 lambda

//Consumer中的void accept(T t)
//PrintStream中的void println(T t)
@Test
public void test1() {
  Consumer<String> con1 = str -> System.out.println(str);
  con1.accept("北京");

  System.out.println("*******************");
  PrintStream ps = System.out;
  Consumer<String> con2 = ps::println;
  con2.accept("beijing");
}

//Supplier中的T get()
//Employee中的String getName()
@Test
public void test2() {
  Employee emp = new Employee(1001,"Tom",23,5600);

  Supplier<String> sup1 = () -> emp.getName();
  System.out.println(sup1.get());

  System.out.println("*******************");
  Supplier<String> sup2 = emp::getName;
  System.out.println(sup2.get());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 类名::静态方法

类已经存在,静态方法也存在,可以使用类名::静态方法替代 lambda

public interface Calcable {
    int calc(int num);
}
// -------------------------------
public static void main(String[] args) {
    method(‐10, Math::abs);
}
private static void method(int num, Calcable lambda) {
    System.out.println(lambda.calc(num));
}
1
2
3
4
5
6
7
8
9
10
//Comparator中的int compare(T t1,T t2)
//Integer中的int compare(T t1,T t2)
@Test
public void test3() {
  Comparator<Integer> com1 = (t1,t2) -> Integer.compare(t1,t2);
  System.out.println(com1.compare(12,21));

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

  Comparator<Integer> com2 = Integer::compare;
  System.out.println(com2.compare(12,3));

}

//Function中的R apply(T t)
//Math中的Long round(Double d)
@Test
public void test4() {
  Function<Double,Long> func = new Function<Double, Long>() {
    @Override
    public Long apply(Double d) {
      return Math.round(d);
    }
  };

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

  Function<Double,Long> func1 = d -> Math.round(d);
  System.out.println(func1.apply(12.3));

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

  Function<Double,Long> func2 = Math::round;
  System.out.println(func2.apply(12.6));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 类名::非静态方法

当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数(或无参数)时,可以使用类名::非静态方法替代 lambda

# 通过 super 引用成员方法

如果存在继承关系,当 Lambda 中需要出现 super 调用时,也可以使用方法引用进行替代。

@FunctionalInterface
public interface Greetable {
    void greet();
}
1
2
3
4
public class Human {
    public void sayHello() {
        System.out.println("Hello!");
    }
}
1
2
3
4
5
public class Man extends Human {
    @Override
    public void sayHello() {
        System.out.println("大家好,我是Man!");
    }
    //定义方法method,参数传递Greetable接口
    public void method(Greetable g){
        g.greet();
    }
    public void show(){ //在main中调用此方法时,会打印Hello!
        //method(()‐>super.sayHello());
        method(super::sayHello);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 通过 this 引用成员方法

this 代表当前对象,如果需要引用的方法就是当前类中的成员方法,那么可以使用this::成员方法的格式来使用方法引用

@FunctionalInterface
public interface Richable {
    void buy();
}
1
2
3
4
public class Husband {
    private void buyHouse() {
        System.out.println("买套房子");
    }
    private void marry(Richable lambda) {
        lambda.buy();
    }
    public void beHappy() { //main中调用此方法,会打印 买套房子
        //marry(() ‐> this.buyHouse());
        marry(this::buyHouse);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 构造器引用

和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致,抽象方法的返回值类型即为构造器所属的类的类型。由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用类名称::new 的格式表示

public class Person {
    private String name;
    ...
}
// ------------------
@FunctionalInterface
public interface PersonBuilder {
    Person buildPerson(String name);
}
// ------------------
public class Demo {
    public static void printName(String name, PersonBuilder builder) {
        System.out.println(builder.buildPerson(name).getName());
    }
    public static void main(String[] args) {
        //printName("赵丽颖", name ‐> new Person(name));
        printName("赵丽颖", Person::new);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//Supplier中的T get()
//Employee的空参构造器:Employee()
@Test
public void test1(){

  Supplier<Employee> sup = new Supplier<Employee>() {
    @Override
    public Employee get() {
      return new Employee();
    }
  };
  System.out.println("*******************");

  Supplier<Employee>  sup1 = () -> new Employee();
  System.out.println(sup1.get());

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

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

//Function中的R apply(T t)
@Test
public void test2(){
  Function<Integer,Employee> func1 = id -> new Employee(id);
  Employee employee = func1.apply(1001);
  System.out.println(employee);

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

  Function<Integer,Employee> func2 = Employee :: new;
  Employee employee1 = func2.apply(1002);
  System.out.println(employee1);

}

//BiFunction中的R apply(T t,U u)
@Test
public void test3(){
  BiFunction<Integer,String,Employee> func1 = (id,name) -> new Employee(id,name);
  System.out.println(func1.apply(1001,"Tom"));

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

  BiFunction<Integer,String,Employee> func2 = Employee :: new;
  System.out.println(func2.apply(1002,"Tom"));

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 数组的构造器引用

数组也是Object 的子类对象,所以同样具有构造器,只是语法稍有不同

@FunctionalInterface
public interface ArrayBuilder {
    int[] buildArray(int length);
}
public class Demo {
    private static int[] initArray(int length, ArrayBuilder builder) {
        return builder.buildArray(length);
    }
    public static void main(String[] args) {
        //int[] array = initArray(10, length ‐> new int[length]);
        int[] array = initArray(10, int[]::new);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
//Function中的R apply(T t)
@Test
public void test4(){
  Function<Integer,String[]> func1 = length -> new String[length];
  String[] arr1 = func1.apply(5);
  System.out.println(Arrays.toString(arr1));

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

  Function<Integer,String[]> func2 = String[] :: new;
  String[] arr2 = func2.apply(10);
  System.out.println(Arrays.toString(arr2));

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 习题

Lambda 接口在 Java 中只能用于方法的参数!!!

# Comparator 使用 🔥

使用 Collections.sort() 方法, 推荐直接使用 Stream,通过定制排序比较两个 Employee (先按年龄比,年龄相同按姓名比), 使用 Lambda 表达式作为参数传递。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {

	private int id;
	private String name;
	private int age;
	private double salary;

}
1
2
3
4
5
6
7
8
9
10
11
public class LambdaTest {

    List<Employee> emps = Arrays.asList(
        new Employee(101, "张三", 18, 9999.99),
        new Employee(102, "李四", 59, 6666.66),
        new Employee(103, "王五", 18, 3333.33),
        new Employee(104, "赵六", 8, 7777.77),
        new Employee(105, "田七", 18, 5555.55)
    );

    @Test
    public void test1() {
        emps.stream().sorted((e1, e2) -> {
            if (e1.getAge() != e2.getAge()) {
                return Integer.compare(e1.getAge(), e2.getAge());
            } else {
                return e1.getName().compareTo(e2.getName());
            }
        }).forEach(System.out::println);

        /*Collections.sort(emps, (e1, e2) -> {
            if (e1.getAge() != e2.getAge()) {
                return Integer.compare(e1.getAge(), e2.getAge());
            } else {
                return e1.getName().compareTo(e2.getName());
            }
        });*/

        // Collections.sort 会提示直接使用如下方式
        /*emps.sort((e1, e2) -> {
            if (e1.getAge() != e2.getAge()) {
                return Integer.compare(e1.getAge(), e2.getAge());
            } else {
                return e1.getName().compareTo(e2.getName());
            }
        });*/
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 函数式接口 1 🔥

  1. 声明函数式接口,接口中声明抽象方法:String getValue(String str);
  2. 声明类 LambdaTest,类中编写方法使用接口作为参数,将一个字符串转换成大写,并作为方法的返回值。
  3. 再将一个字符串的第 2 个到第 4 个索引位置进行截取子串。
@FunctionalInterface
public interface MyFunctional1 {
    String getValue(String str);
}
1
2
3
4
@Test
public void test2(){
    String s = "abcdEfg";

    String s1 = strHandler(s, String::toUpperCase);
    System.out.println(s1);

    String s2 = strHandler(s, str -> str.substring(2, 5));
    System.out.println(s2);
}

/**
 * 用于处理字符串
 */
private String strHandler(String str, MyFunctional1 func){
    return func.getValue(str);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 函数式接口 2 🔥

  1. 声明一个带两个泛型的函数式接口,泛型类型为<T,R> : T 为参数,R 为返回值
  2. 接口中声明对应抽象方法
  3. 在 LambdaTest 类中声明方法,使用接口作为参数,计算两个 long 型参数的和
  4. 再计算两个 long 型参数的乘积
@FunctionalInterface
public interface MyFunctional2<T, R> {
    R calc(T t1, T t2);
}
1
2
3
4
@Test
void test3() {
    long l = calcSum(2, 3, Long::sum);
    System.out.println(l);

    long ll = calcSum(2, 3, (l1, l2) -> l1 * l2);
    System.out.println(ll);
}

/**
 * 用于处理两个long数的计算
 */
private long calcSum(long l1, long l2, MyFunctional2<Long,Long> func){
    return func.calc(l1, l2);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# Pedicate 接口使用 🔥

Integer[] arr = {-12345, 9999, 520, 0, -38, -7758520, 941213};

Predicate<Integer> p1 = i -> i >= 0; //使用lambda表达式创建Predicate对象p1,p1能判断整数是否是自然数
Predicate<Integer> p2 = i -> Math.abs(i) > 100; //使用lambda表达式创建Predicate对象p2,p2能判断整数的绝对值是否大于100
Predicate<Integer> p3 = i -> i % 2 == 0; //使用lambda表达式创建Predicate对象p3,p3能判断整数是否是偶数

int count1 = 0;
int count2 = 0;
int count3 = 0;
int count4 = 0;
for (Integer i : arr) {
    if (p1.test(i))
        count1++;
    if (p1.negate().test(i))
        count2++;
    if (p2.and(p3).test(i))
        count3++;
    if (p1.negate().or(p3).test(i))
        count4++;
}
System.out.println("自然数个数:"+count1);
System.out.println("负整数个数:"+count2);
System.out.println("绝对值大于100的偶数的个数:"+count3);
System.out.println("负整数或偶数的数的个数:"+count4);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# Function 接口使用 🔥

/**
         * 1.	使用lambda表达式分别将以下功能封装到Function对象中
         * a)	求Integer类型ArrayList中所有元素的平均数
         * b)	将Map<String,Integer>中value存到ArrayList<Integer>中
         */
Function<ArrayList<Integer>, Integer> avg = list -> {
    int sum = 0;
    for (Integer i : list) {
        sum += i;
    }
    return sum / list.size();
};

Function<Map<String, Integer>, ArrayList<Integer>> change = map -> {
    ArrayList<Integer> list = new ArrayList<>();
    map.entrySet().stream().forEach(s -> list.add(s.getValue()));//或用values,addAll
    return list;
};

HashMap<String, Integer> students = new HashMap<>();
students.put("岑小村", 59);
students.put("谷天洛", 82);
students.put("渣渣辉", 98);
students.put("蓝小月", 65);
students.put("皮几万", 70);

int avg_score = change.andThen(avg).apply(students);
System.out.println(avg_score);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
编辑
上次更新: 2021/06/21, 15:45:42
4 API-习题
14 Stream API

← 4 API-习题 14 Stream API→

最近更新
01
线程生命周期
07-06
02
线程安全理论
06-24
03
并发简史
06-24
更多文章>
Theme by Vdoing | Copyright © 2019-2021 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×