17.1.1 函数式编程思想

  • 面向对象的思想:
    • 做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情
  • 函数式编程思想:
    • 只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程
  • Java8 引入了 Lambda 表达式之后,Java也开始支持函数式编程
  • 可以这么说 Lambda 表达式其实就是实现 SAM 接口的语法糖,使得 Java 也算是支持函数式编程的语言 Lambda

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

冗余的匿名内部类

当需要启动一个线程去完成任务时,通常会通过java.lang.Runnable接口来定义任务内容,并使用java.lang.Thread类来启动该线程

  1. public class Demo01Runnable {
  2. public static void main(String[] args) {
  3. // 匿名内部类
  4. Runnable task = new Runnable() {
  5. @Override
  6. public void run() { // 覆盖重写抽象方法
  7. System.out.println("多线程任务执行!");
  8. }
  9. };
  10. new Thread(task).start(); // 启动线程
  11. }
  12. }

本着“一切皆对象”的思想,这种做法是无可厚非的:首先创建一个Runnable接口的匿名内部类对象来指定任务内容,再将其交给一个线程来启动

代码分析:
对于Runnable的匿名内部类用法,可以分析出几点内容:

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

编程思想转换

做什么,而不是谁来做,怎么做
真的希望创建一个匿名内部类对象吗?不,只是为了做这件事情而不得不创建一个对象,真正希望做的事情是:将run方法体内的代码传递给Thread

传递一段代码
这才是真正的目的,而创建对象只是受限于面向对象语法而不得不采取的一种手段方式

那有没有更加简单的办法?如果将关注点从“怎么做”回归到“做什么”的本质上,就会发现只要能够更好地达到目的,过程与形式其实并不重要

生活举例
01-交通方式.png

  • 当需要从北京到上海【这是目的】
  • 可以选择高铁、汽车、骑行或是徒步的【这是形式】
  • 真正目的是到达上海,而如何才能到达上海的形式并不重要,所以一直在探索有没有比高铁更好的方式——搭乘飞机

体验 Lambda 的更优写法

使用 Lambda 语法,上述Runnable接口的匿名内部类写法可以通过更简单的 Lambda 表达式达到等效

  1. public class Demo02LambdaRunnable {
  2. public static void main(String[] args) {
  3. new Thread(() -> System.out.println("多线程任务执行!")).start(); // 启动线程
  4. }
  5. }

这段代码和刚才的执行效果是完全一样的,可以在1.8或更高的编译级别下通
从代码的语义中可以看出:启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定

17.1.2 函数式接口

SAM 接口

  • Single Abstract Method
  • Lambda 表达式其实就是实现SAM接口的语法糖
  • 该接口中只有一个抽象方法需要实现,当然该接口可以包含其他非抽象方法

@FunctionalInterface

  • 其实只要满足“SAM”特征的接口都可以称为函数式接口,都可以使用 Lambda 表达式
  • 最好在声明接口时,加上@FunctionalInterface
  • 一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错

标记了@FunctionalInterface 的函数式接口的有:Runnable,Comparator,FileFilter
image.png

Java8 在 java.util.function 新增了很多函数式接口,主要分为四大类:消费型、供给型、判断型、功能型
基本可以满足的开发需求

自定义函数式接口

只要确保接口中有且仅有一个抽象方法即可

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

接口当中抽象方法的 public abstract 是可以省略的

声明一个计算器Calculator接口,内含抽象方法calc可以对两个 int数字进行计算,并返回结果

  1. public interface Calculator {
  2. int calc(int a, int b);
  3. }

在测试类中,声明一个如下方法

  1. public static void invokeCalc(int a, int b, Calculator calculator) {
  2. int result = calculator.calc(a, b);
  3. System.out.println("结果是:" + result);
  4. }

下面进行测试
使用匿名内部类的方式

  1. public static void main(String[] args) {
  2. invokeCalc(1, 2, new Calculator() {
  3. @Override
  4. public int calc(int a, int b) {
  5. return a + b;
  6. }
  7. });
  8. invokeCalc(1, 2, new Calculator() {
  9. @Override
  10. public int calc(int a, int b) {
  11. return a * b;
  12. }
  13. });
  14. }

使用 lambda 的方式

  1. public static void main(String[] args) {
  2. invokeCalc(1, 2, Integer::sum);
  3. invokeCalc(1, 2, (a, b) -> a - b);
  4. invokeCalc(1, 2, (a, b) -> a * b);
  5. invokeCalc(1, 2, (a, b) -> a / b);
  6. invokeCalc(1, 2, (a, b) -> a % b);
  7. }

消费型接口

消费型接口的抽象方法特点:有形参,但是返回值类型是 void

接口名 抽象方法 描述
Consumer void accept(T t) 接收一个对象用于完成功能
BiConsumer void accept(T t, U u) 接收两个对象用于完成功能
DoubleConsumer void accept(double value) 接收一个double值
IntConsumer void accept(int value) 接收一个int值
LongConsumer void accept(long value) 接收一个long值
ObjDoubleConsumer void accept(T t, double value) 接收一个对象和一个double值
ObjIntConsumer void accept(T t, int value) 接收一个对象和一个int值
ObjLongConsumer void accept(T t, long value) 接收一个对象和一个long值

供给型接口

这类接口的抽象方法特点:无参

接口名 抽象方法 描述
Supplier T get() 返回一个对象
BooleanSupplier boolean getAsBoolean() 返回一个boolean值
DoubleSupplier double getAsDouble() 返回一个double值
IntSupplier int getAsInt() 返回一个int值
LongSupplier long getAsLong() 返回一个long值

判断型接口

这里接口的抽象方法特点:有参,但是返回值类型是 boolean 结果

接口名 抽象方法 描述
Predicate boolean test(T t) 接收一个对象
BiPredicate boolean test(T t, U u) 接收两个对象
DoublePredicate boolean test(double value) 接收一个double值
IntPredicate boolean test(int value) 接收一个int值
LongPredicate boolean test(long value) 接收一个long值

功能型接口

这类接口的抽象方法特点:既有参数又有返回值

接口名 抽象方法 描述
Function R apply(T t) 接收一个T类型对象,返回一个R类型对象结果
UnaryOperator T apply(T t) 接收一个T类型对象,返回一个T类型对象结果
DoubleFunction R apply(double value) 接收一个double值,返回一个R类型对象
IntFunction R apply(int value) 接收一个int值,返回一个R类型对象
LongFunction R apply(long value) 接收一个long值,返回一个R类型对象
ToDoubleFunction double applyAsDouble(T value) 接收一个T类型对象,返回一个double
ToIntFunction int applyAsInt(T value) 接收一个T类型对象,返回一个int
ToLongFunction long applyAsLong(T value) 接收一个T类型对象,返回一个long
DoubleToIntFunction int applyAsInt(double value) 接收一个double值,返回一个int结果
DoubleToLongFunction long applyAsLong(double value) 接收一个double值,返回一个long结果
IntToDoubleFunction double applyAsDouble(int value) 接收一个int值,返回一个double结果
IntToLongFunction long applyAsLong(int value) 接收一个int值,返回一个long结果
LongToDoubleFunction double applyAsDouble(long value) 接收一个long值,返回一个double结果
LongToIntFunction int applyAsInt(long value) 接收一个long值,返回一个int结果
DoubleUnaryOperator double applyAsDouble(double operand) 接收一个double值,返回一个double
IntUnaryOperator int applyAsInt(int operand) 接收一个int值,返回一个int结果
LongUnaryOperator long applyAsLong(long operand) 接收一个long值,返回一个long结果
BiFunction R apply(T t, U u) 接收一个T类型和一个U类型对象,返回一个R类型对象结果
BinaryOperator T apply(T t, T u) 接收两个T类型对象,返回一个T类型对象结果
ToDoubleBiFunction double applyAsDouble(T t, U u) 接收一个T类型和一个U类型对象,返回一个double
ToIntBiFunction int applyAsInt(T t, U u) 接收一个T类型和一个U类型对象,返回一个int
ToLongBiFunction long applyAsLong(T t, U u) 接收一个T类型和一个U类型对象,返回一个long
DoubleBinaryOperator double applyAsDouble(double left, double right) 接收两个double值,返回一个double结果
IntBinaryOperator int applyAsInt(int left, int right) 接收两个int值,返回一个int结果
LongBinaryOperator long applyAsLong(long left, long right) 接收两个long值,返回一个long结果

17.1.3 Lambda 表达式语法

  • Lambda 表达式是用来给【函数式接口】的变量或形参赋值用的
  • 其实本质上, Lambda 表达式是用于实现【函数式接口】的抽象方法

Lambda 表达式语法格式

  1. (形参列表) -> {Lambda体}

说明

  • (形参列表)它就是要赋值的函数式接口的抽象方法的(形参列表),照抄
  • { Lambda 体}就是实现这个抽象方法的方法体
  • ->称为 Lambda 操作符(减号和大于号中间不能有空格,而且必须是英文状态下半角输入方式)

优化

Lambda 表达式可以精简

  • { Lambda 体}中只有一句语句时,可以省略{}{;}
  • { Lambda 体}中只有一句语句时,并且这个语句还是一个 return 语句,那么 return 也可以省略,但是如果{;}没有省略的话,return 是不能省略的
  • (形参列表)的类型可以省略
  • (形参列表)的形参个数只有一个,那么可以把数据类型和 ()一起省略,但是形参名不能省略
  • (形参列表)是空参时,()不能省略

示例代码

  1. public class TestLambdaGrammer {
  2. @Test
  3. public void test1(){
  4. //用Lambda表达式给Runnable接口的形参或变量赋值
  5. /*
  6. * 确定两件事,才能写好lambda表达式
  7. * (1)这个接口的抽象方法长什么样:
  8. * public void run()
  9. * (2)这个抽象方法的实现要干什么事
  10. * 例如:我要打印“hello lambda"
  11. */
  12. Runnable r = () -> {System.out.println("hello lambda");};
  13. }
  14. @Test
  15. public void test2(){
  16. //lambda体省略了{;}
  17. Runnable r = () -> System.out.println("hello lambda");
  18. }
  19. @Test
  20. public void test3(){
  21. String[] arr = {"hello","Hello","java","JAVA"};
  22. //为arr数组排序,但是,想要不区分大小写
  23. /*
  24. * public static <T> void sort(T[] a,Comparator<? super T> c)
  25. * 这里要用Lambda表达式为Comparator类型的形参赋值
  26. *
  27. * 两件事:
  28. * (1)这个接口的抽象方法: int compare(T o1, T o2)
  29. * (2)这个抽象方法要做什么事?
  30. * 例如:这里要对String类型的元素,不区分大小写的比较大小
  31. */
  32. // Arrays.sort(arr, (String o1, String o2) -> {return o1.compareToIgnoreCase(o2);});
  33. //省略了{return ;}
  34. // Arrays.sort(arr, (String o1, String o2) -> o1.compareToIgnoreCase(o2));
  35. //省略了两个String
  36. Arrays.sort(arr, (o1, o2) -> o1.compareToIgnoreCase(o2));
  37. for (String string : arr) {
  38. System.out.println(string);
  39. }
  40. }
  41. @Test
  42. public void test4(){
  43. ArrayList<String> list = new ArrayList<>();
  44. list.add("hello");
  45. list.add("java");
  46. list.add("world");
  47. /*
  48. * JDK1.8给Collection系列的集合,准确的讲是在Iterable接口中,增加了一个默认方法
  49. * default void forEach(Consumer<? super T> action)
  50. * 这个方法是用来遍历集合等的。代替原来的foreach循环的。
  51. *
  52. * 这个方法的形参是Consumer接口类型,它是函数式接口中消费型接口的代表
  53. * 我现在调用这个方法,想要用Lambda表达式为Consumer接口类型形参赋值
  54. *
  55. * 两件事:
  56. * (1)它的抽象方法: void accept(T t)
  57. * (2)抽象方法的实现要完成的事是什么
  58. * 例如:这里要打印这个t
  59. */
  60. // list.forEach((String t) -> {System.out.println(t);});
  61. //省略{;}
  62. // list.forEach((String t) -> System.out.println(t));
  63. //省略String
  64. // list.forEach((t) -> System.out.println(t));
  65. //可以省略形参()
  66. list.forEach(t -> System.out.println(t));
  67. }
  68. }

17.1.4 Lambda 表达式练习

练习1:无参无返回值形式

假如有自定义函数式接口 Call

public interface Call {
    void shout();
}

在测试类中声明一个如下方法

public static void callSomething(Call call){
        call.shout();
    }

在测试类的 main 方法中调用 callSomething 方法,并用 Lambda 表达式为形参 call 赋值

public class TestLambda {
    public static void main(String[] args) {
        callSomething(()->System.out.println("回家吃饭"));
        callSomething(()->System.out.println("我爱你"));
        callSomething(()->System.out.println("滚蛋"));
        callSomething(()->System.out.println("回来"));
    }
    public static void callSomething(Call call){
        call.shout();
    }
}
interface Call {
    void shout();
}

练习2:消费型接口

代码示例:Consumer<T>接口

在 JDK1.8 中 Collection 集合接口的父接口 Iterable 接口中增加了一个默认方法:
public default void forEach(Consumer<? super T> action)遍历Collection集合的每个元素,执行“xxx消费型”操作

在 JDK1.8 中 Map 集合接口中增加了一个默认方法:
public default void forEach(BiConsumer<? super K,? super V> action)遍历 Map 集合的每对映射关系,执行“xxx消费型”操作

案例
创建一个 Collection 系列的集合,添加编程语言,调用 forEach 方法遍历查看
创建一个 Map 系列的集合,添加一些 (key,value) 键值对,例如,添加编程语言排名和语言名称,调用 forEach 方法遍历查看
1564370820279.png

示例代码

    @Test
    public void test1(){
        List<String> list = Arrays.asList("java","c","python","c++","VB","C#");
        list.forEach(s -> System.out.println(s));
    }
    @Test
    public void test2(){
        HashMap<Integer,String> map = new HashMap<>();
        map.put(1, "java");
        map.put(2, "c");
        map.put(3, "python");
        map.put(4, "c++");
        map.put(5, "VB");
        map.put(6, "C#");
        map.forEach((k,v) -> System.out.println(k+"->"+v));
    }

练习3:供给型接口

代码示例:Supplier<T>接口

  • 在 JDK1.8 中增加了 StreamAPI,java.util.stream.Stream是一个数据流
  • 这个类型有一个静态方法:public static <T> Stream<T> generate(Supplier<T> s)可以创建 Stream 的对象
  • 包含一个 forEach 方法可以遍历流中的元素:public void forEach(Consumer<? super T> action)

案例
现在调用 Stream 的 generate 方法,来产生一个流对象,并调用 Math.random() 方法来产生数据,为 Supplier 函数式接口的形参赋值,最后调用 forEach 方法遍历流中的数据查看结果

    @Test
    public void test2(){
        Stream.generate(() -> Math.random()).forEach(num -> System.out.println(num));
    }

练习4:功能型接口

代码示例:Funtion<T,R>接口

在 JDK1.8 时 Map 接口增加了很多方法,例如:
public default void replaceAll(BiFunction<? super K,? super V,? extends V> function)按照 function 指定的操作替换 map 中的 value
public default void forEach(BiConsumer<? super K,? super V> action)遍历 Map 集合的每对映射关系,执行“xxx消费型”操作

案例

  1. 声明一个Employee员工类型,包含编号、姓名、薪资
  2. 添加n个员工对象到一个HashMap<Integer,Employee>集合中,其中员工编号为 key,员工对象为 value
  3. 调用 Map 的 forEach 遍历集合
  4. 调用 Map 的 replaceAll 方法,将其中薪资低于10000元的,薪资设置为10000
  5. 再次调用 Map的 forEach 遍历集合查看结果

Employee 类

class Employee{
    private int id;
    private String name;
    private double salary;
    public Employee(int id, String name, double salary) {
        super();
        this.id = id;
        this.name = name;
        this.salary = salary;
    }
    public Employee() {
        super();
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getSalary() {
        return salary;
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }
    @Override
    public String toString() {
        return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]";
    }

}

测试类

import java.util.HashMap;

public class TestLambda {
    public static void main(String[] args) {
        Map<Integer, Employee> map = new HashMap<>();
        Employee e1 = new Employee(1, "张三", 8000);
        Employee e2 = new Employee(2, "李四", 9000);
        Employee e3 = new Employee(3, "王五", 10000);
        Employee e4 = new Employee(4, "赵六", 11000);
        Employee e5 = new Employee(5, "钱七", 12000);

        map.put(e1.getId(), e1);
        map.put(e2.getId(), e2);
        map.put(e3.getId(), e3);
        map.put(e4.getId(), e4);
        map.put(e5.getId(), e5);

        // foreach
        map.forEach((id, employee) -> System.out.println(id + "->" + employee));

        // replaceAll
        map.replaceAll((id, employee) -> {
            if (employee.getSalary() < 10000) {
                employee.setSalary(10000);
            }
            return employee;
        });

        // forEach
        map.forEach((id, employee) -> System.out.println(id + "->" + employee));
    }
}

练习5:判断型接口

代码示例:Predicate<T>接口

JDK1.8 时,Collecton 接口增加了一下方法,其中一个如下:
public default boolean removeIf(Predicate<? super E> filter) :用于删除集合中满足 filter 指定的条件判断的数据
public default void forEach(Consumer<? super T> action):遍历 Collection 集合的每个元素,执行“xxx消费型”操作

案例

  1. 添加一些字符串到一个Collection集合中
  2. 调用 forEach 遍历集合
  3. 调用 removeIf 方法,删除其中字符串的长度<5的
  4. 再次调用forEach遍历集合 ```java

import java.util.ArrayList;

public class TestLambda { public static void main(String[] args) { ArrayList list = new ArrayList<>(); list.add(“hello”); list.add(“java”); list.add(“polo”); list.add(“ok”);

    // forEach
    list.forEach(System.out::println);

    // removeIf
    list.removeIf(s -> StringUtils.containsIgnoreCase(s, "o"));

    // forEach
    list.forEach(System.out::println);
}

}


<a name="b30043fb"></a>
#### 练习6:判断型接口
**案例**

- 声明一个 Employee 员工类型,包含编号、姓名、性别,年龄,薪资
- 声明一个 EmployeeSerice 员工管理类,包含一个 ArrayList 集合的属性 all,在 EmployeeSerice 的构造器中,创建一些员工对象,为 all 集合初始化
- 在 EmployeeSerice 员工管理类中,声明一个方法:`ArrayList get(Predicate p)`,即将满足 p 指定的条件的员工,添加到一个新的 ArrayList 集合中返回
- 在测试类中创建 EmployeeSerice 员工管理类的对象,并调用 get 方法,分别获取:
   - 所有员工对象
   - 所有年龄超过35的员工
   - 所有薪资高于15000的女员工
   - 所有编号是偶数的员工
   - 名字是“张三”的员工
   - 年龄超过25,薪资低于10000的男员工

**Employee类**
```java
public class Employee{
    private int id;
    private String name;
    private char gender;
    private int age;
    private double salary;

    public Employee(int id, String name, char gender, int age, double salary) {
        super();
        this.id = id;
        this.name = name;
        this.gender = gender;
        this.age = age;
        this.salary = salary;
    }
    public Employee() {
        super();
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getSalary() {
        return salary;
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }
    @Override
    public String toString() {
        return "Employee [id=" + id + ", name=" + name + ", gender=" + gender + ", age=" + age + ", salary=" + salary
                + "]";
    }
}

员工管理类

class EmployeeService{
    private ArrayList<Employee> all;
    public EmployeeService(){
        all = new ArrayList<Employee>();
        all.add(new Employee(1, "张三", '男', 33, 8000));
        all.add(new Employee(2, "翠花", '女', 23, 18000));
        all.add(new Employee(3, "无能", '男', 46, 8000));
        all.add(new Employee(4, "李四", '女', 23, 9000));
        all.add(new Employee(5, "老王", '男', 23, 15000));
        all.add(new Employee(6, "大嘴", '男', 23, 11000));
    }
    public List<Employee> get(Predicate<Employee> p) {
        ArrayList<Employee> result = new ArrayList<>();
        for (Employees emp : all) {
            if (p.test(emp)) {
                result.add(emp);
            }
        }
        System.out.println();
        return result;
    }
}

测试类

public class TestLambda {
    public static void main(String[] args) {
        EmployeeService es = new EmployeeService();

        EmployeeSerice employeeSerice = new EmployeeSerice();
        employeeSerice.get(emp -> true).forEach(System.out::print);
        employeeSerice.get(emp -> emp.getAge() > 35).forEach(System.out::print);
        employeeSerice.get(emp -> emp.getSalary() > 15000 && emp.getGender() == '女').forEach(System.out::print);
        employeeSerice.get(emp -> emp.getId() % 2 == 0).forEach(System.out::print);
        employeeSerice.get(emp -> StringUtils.equals(emp.getName(), "张三")).forEach(System.out::print);
        employeeSerice.get(emp -> emp.getAge() > 25 && emp.getGender() == '男' && emp.getSalary() < 10000).forEach(System.out::print);
    }
}

17.1.3 方法引用与构造器引用

  • Lambda 表达式是可以简化函数式接口的变量与形参赋值的语法
  • 而方法引用和构造器引用是为了简化 Lambda 表达式的
  • 当 Lambda 表达式满足一些特殊的情况时,还可以再简化

特殊情况

Lambda 体只有一句语句,并且是通过调用一个对象/类现有的方法来完成的,如:

  • System.out 对象,调用 println() 方法来完成 Lambda 体
  • Math 类,调用 random() 静态方法来完成 Lambda 体

并且 Lambda 表达式的形参正好是给该方法的实参,如:

  • (t) -> System.out.println(t),都是只有一个参数
  • () -> Math.random() ,都是无参

方法引用

方法引用的语法格式:

实例对象名::实例方法
类名::静态方法
类名::实例方法

说明

  • ::称为方法引用操作符(两个:中间不能有空格,而且必须英文状态下半角输入)
  • Lambda 表达式的形参列表,全部在 Lambda 体中使用上了,要么是作为调用方法的对象,要么是作为方法的实参
  • 在整个 Lambda 体中没有额外的数据

    
      @Test
      public void test4(){
          // 不能简化方法引用,因为"hello lambda"这个无法省略
          // Runnable r = () -> System.out.println("hello lambda");
    
          // 打印空行
          Runnable r = System.out::println;
          // 等价写法
          Runnable r1 = () -> System.out.println();
      }
    
      @Test
      public void test3(){
          String[] arr = {"Hello","java","chai"};
          // Arrays.sort(arr, (s1,s2) -> s1.compareToIgnoreCase(s2));
          // 用方法引用简化
          /*
           * Lambda表达式的形参,第一个(例如:s1)
           * 正好是调用方法的对象,剩下的形参(例如:s2)正好是给这个方法的实参
           */
          Arrays.sort(arr, String::compareToIgnoreCase);
      }
    
      @Test
      public void test2(){
          // Stream<Double> stream = Stream.generate(() -> Math.random());
    
          // 用方法引用简化
          Stream<Double> stream = Stream.generate(Math::random);
      }
    
      @Test
      public void test1(){
          List<Integer> list = Arrays.asList(1,3,4,8,9);
    
          //list.forEach(t -> System.out.println(t));
    
          //用方法再简化
          list.forEach(System.out::println);
      }
    

构造器引用

  • 当 Lambda 表达式是创建一个对象,并且满足 Lambda 表达式形参,正好是给创建这个对象的构造器的实参列表
  • 当 Lambda 表达式是创建一个数组对象,并且满足 Lambda 表达式形参,正好是给创建这个数组对象的长度

构造器引用的语法格式

  • 类名::new
  • 数组类型名::new

示例代码

public class TestMethodReference {
    @Test
    public void teset04() {
        Stream<Integer> stream = Stream.of(1,2,3);
        Stream<int[]> map = stream.map(int[]::new);
    }


    //这个方法是模仿HashMap中,把你指定的数组的长度纠正为2的n次方的代码
    //createArray()的作用是,创建一个长度为2的n次方的数组
    public <R> R[] createArray(Function<Integer,R[]> fun,int length){
        int n = length - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        length = n < 0 ? 1 : n + 1;
        return fun.apply(length);
    }

    @Test
    public void test3(){
        /*
         * Function是一个函数式接口,可以用Lambda表达式赋值
         * Function<T,R>的抽象方法   R apply(T t)
         * 
         * createArray这个方法中用的是Function<Integer,R[]> fun。说明T类型已经指定为Integer
         * 说明
         */
        // Function<Integer,String[]> f = (Integer len) -> new String[len];

        //因为Lambda体是在创建一个数组对象完成的,而且Lambda表达式的形参正好是创建数组用的长度
        //通过构造器引用省略
        Function<Integer,String[]> f = String[]::new;
        String[] array = createArray(f, 10);

        System.out.println(array.length);//16
    }


    @Test
    public void teset02() {
        Stream<String> stream = Stream.of("1.0","2.3","4.4");

        // Stream<BigDecimal> stream2 = stream.map(num -> new BigDecimal(num));

        Stream<BigDecimal> stream2 = stream.map(BigDecimal::new);
    }

    @Test
    public void test1(){
        // Supplier<String> s = () -> new String();//通过供给型接口,提供一个空字符串对象

        //构造器引用
        Supplier<String> s = String::new;//通过供给型接口,提供一个空字符串对象
    }

}