实例理解Java8新特性中Lambda表达式和函数式接口的使用

目录

  • Lambda表达式
    • 为什么使用Lambda 表达式?
    • Lambda表达式语法
      • 基础语法
      • 语法格式一:无参数,无返回值
        • 示例
      • 语法格式二:有一个参数,并且无返回值
        • 示例
      • 语法格式三:若只有一个参数,小括号可以省略不写
        • 示例
      • 语法格式四:有两个以上的参数,有返回值,并且 Lambda 体中有多条语句
        • 示例
      • 语法格式五:若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写
        • 示例
      • 语法格式六:Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
        • 示例:
      • 类型推断
      • 语法支持
    • 函数式接口
      • 什么是函数式(Functional)接口?
        • 举例
      • 如何理解函数式接口?
      • 自定义函数式接口
        • 示例
        • 使用自定义的函数式接口
      • 作为参数传递Lambda 表达式
      • Java 内置四大核心函数式接口
        • 使用实例
      • 全部函数式接口API
    • 方法引用与构造器引用
      • 方法引用
        • 使用情况
        • 注意事项
        • 使用示例
      • 构造器引用
        • 示例
      • 数组引用
        • 示例

在学习Lambda表达式和函数式接口的时候,发现学习的时候不系统,用的时候总是忘记怎么去使用。所以总结一些实例,便于理解其使用。

Lambda表达式

为什么使用Lambda 表达式?

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

Lambda表达式语法

注:示例中有些使用了函数式接口,可参考下文

基础语法

Java8中引入了一个新的操作符 “->” 该操作符称为箭头操作符或 Lambda 操作符箭头操作符将 Lambda 表达式拆分成两部分:

  • 左侧:Lambda 表达式的参数列表
  • 右侧:Lambda 表达式中所需执行的功能, 即Lambda 体

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

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

示例

@Test
public void test1(){
    //java8之前的写法
    //jdk 1.7 前,内部类引用外部的局部变量必须是 final
    String str = "java8之前的接口内部实现类写法";
    Runnable r = new Runnable() {
        @Override
        public void run () {
            System.out.println(str);
        }
    };
    r.run();

    //java8之后的写法
    String str1 = "测试“无参数,无返回值”的Lambda表达式,实现接口内部实现类写法";
    Runnable r1 = () -> System.out.println(str1);
    r1.run();
}

结果:

java8之前的接口内部实现类写法
测试“无参数,无返回值”的Lambda表达式,实现接口内部实现类写法

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

(x) -> System.out.println(x)

示例

@Test
public void test2(){
    Consumer<String> consumer = (x) -> System.out.println(x);
    consumer.accept("测试“有一个参数,并且无返回值”的lambda表达式");
}

结果:

测试“有一个参数,并且无返回值”的lambda表达式

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

x -> System.out.println(x)

示例

对于上边的示例可以省略括号,如下:

@Test
public void test2(){
    Consumer<String> consumer = x -> System.out.println(x);
    consumer.accept("测试“若只有一个参数,小括号可以省略不写”的lambda表达式");
}

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

(x, y) -> {
    //执行语句
    return [返回值];
};

示例

@Test
public void test3(){
    BiFunction<Integer,Integer,Integer> biFunction = (x,y) -> {
        System.out.println("测试“有两个以上的参数,有返回值,并且 Lambda 体中有多条语句”");
        return x+y;
    };
    Integer sum = biFunction.apply(1, 2);
    System.out.println(sum);
}

结果:

测试“有两个以上的参数,有返回值,并且 Lambda 体中有多条语句”
3

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

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

示例

改写语法格式四的示例

@Test
public void test4 () {
    BiFunction<Integer, Integer, Integer> biFunction = ( x, y ) -> x + y;
    Integer sum = biFunction.apply(1, 2);
    System.out.println(sum);
}

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

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

可以省略为

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

示例:

改写语法格式二的示例

两种写法效果相同

Consumer consumer = (String x ) -> System.out.println(x);
consumer.accept("测试“有一个参数,并且无返回值”的lambda表达式");
Consumer<String> consumer = x -> System.out.println(x);
consumer.accept("测试“有一个参数,并且无返回值”的lambda表达式");

类型推断

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

类型推断不只是在Lambda表达式中运用,在很多地方也有使用到,比如泛型的类型推断:

Map map = new HashMap<>();
List list = new ArrayList<>();

"="号后边的泛型可以省略,用的也是类型推断(在Java7中则不支持,必须进行显示声明)。

小结:

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

语法支持

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

函数式接口:接口中只有一个抽象方法的接口,称为函数式接口。 可以使用注解 @FunctionalInterface 修饰可以检查是否是函数式接口

函数式接口

什么是函数式(Functional)接口?

  • 只包含一个抽象方法的接口,称为函数式接口。
  • 你可以通过Lambda 表达式来创建该接口的对象。(若Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
  • 我们可以在一个接口上使用@FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
  • java.util.function包下定义了Java 8 的丰富的函数式接口

举例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-htUuJFQe-1592729269559)(F:\知识库\Java\Java8新特性\Java8新特性.assets\image-20200620184440255.png)]

如何理解函数式接口?

  • Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,也即java不但可以支持OOP还可以支持OOF(面向函数编程)
  • 在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。
  • 简单的说,**在Java8中,Lambda表达式就是一个函数式接口的实例。**这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
  • 所以以前用匿名实现类表示的现在都可以用Lambda表达式来写

自定义函数式接口

示例

/**
 * 自定义函数式接口
 *  不带泛型
 */
@FunctionalInterface
interface MyFunctional{

    /**
     * 处理整数方法
     * @param num 整数
     * @return 处理后的整数
     */
    Integer handleNumber(Integer num);
}

/**
 * 自定义函数式接口
 *  带泛型
 */
@FunctionalInterface
interface MyFunc<T>{

    /**
     * 处理泛型数据
     * @param t 目标泛型数据
     * @return 处理后的泛型数据
     */
    T handle(T t);
}

使用自定义的函数式接口

@Test
public void test6 () {
    MyFunctional myFunctional = x -> x * x;
    Integer n = myFunctional.handleNumber(4);
    System.out.println("测试不带泛型的自定义函数式接口,结果:" + n);

    MyFunc<String> myFunc = x -> {
        if (x.length() > 5) {
            x=x.substring(0, 5);
        }
        return x;
    };
    String s = myFunc.handle("测试带泛型的自定义函数式接口");
    System.out.println(s);
}

结果:

测试不带泛型的自定义函数式接口,结果:16
测试带泛型

作为参数传递Lambda 表达式

实例(稍后写)

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

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

类型 函数式接口 参数类型 返回类型 包含方法 用途
消费型接口 Consumer T void void accept(T t) 对类型为T的对象应用操作
供给型接口 Supplier T T get() 返回类型为T的对象
函数型接口 Function T R R apply(T t) 对类型为T的对象应用操作,
返回R类型的对象。
断定型接口 Predicate T boolean boolean test(T t) 确定类型为T的对象是否满足某约束,
并返回boolean 值

使用实例

/**
 * 测试四大内置函数式接口
 */
@Test
public void test7 () {
    handleStr("aaBb", x -> {
        System.out.println("---测试消费型函数式接口,处理字符串---");
        x = x.toUpperCase();
        System.out.println(x);
    });

    System.out.println("---利用供给型接口:创建指定数量的随机数,存储到List并返回---");
    List<Integer> list = handleStr(5, () -> (int) (Math.random() * 100));
    for (Integer num : list) {
        System.out.println(num);
    }

    System.out.println("---利用函数型接口接口,处理字符串类型数据(一个参数,有返回值);改写消费型接口的示例---");
    String s = handleStr2("aaBb", x -> x = x.toUpperCase());
    System.out.println(s);

    System.out.println("---利用断言型接口,判断字符串是否符合条件---");
    handleStr3("利用断言型接口,判断字符串是否符合条件", x -> x.length() > 3);
}

/**
 * 利用消费型函数式接口,处理字符串类型数据(一个参数,无返回值)
 */
private void handleStr ( String str, Consumer<String> consumer ) {
    consumer.accept(str);
}

/**
 * 利用供给型接口:创建指定数量的随机数,存储到List并返回
 */
private List<Integer> handleStr ( 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;
}

/**
 * 利用函数型接口接口,处理字符串类型数据(一个参数,有返回值)
 * 改写消费型接口的示例
 */
private String handleStr2 ( String str, Function<String, String> function ) {
    return function.apply(str);
}

/**
 * 利用断言型接口,判断字符串是否符合条件
 */
private void handleStr3 ( String str, Predicate<String> pre ) {
    if(pre.test(str)){
        System.out.println("符合条件");
    }
}

结果:

---测试消费型函数式接口,处理字符串---
AABB
---利用供给型接口:创建指定数量的随机数,存储到List并返回---
80
15
55
83
66
---利用函数型接口接口,处理字符串类型数据(一个参数,有返回值);改写消费型接口的示例---
AABB
---利用断言型接口,判断字符串是否符合条件---
符合条件

全部函数式接口API

这些API实在四大函数式接口的基础上派生出来的,使用方法如出一辙。

序号 接口 & 描述
1 BiConsumer 代表了一个接受两个输入参数的操作,并且不返回任何结果
2 BiFunction 代表了一个接受两个输入参数的方法,并且返回一个结果
3 BinaryOperator 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果
4 BiPredicate 代表了一个两个参数的boolean值方法
5 BooleanSupplier 代表了boolean值结果的提供方
6 Consumer 代表了接受一个输入参数并且无返回的操作
7 DoubleBinaryOperator代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。
8 DoubleConsumer代表一个接受double值参数的操作,并且不返回结果。
9 DoubleFunction 代表接受一个double值参数的方法,并且返回结果
10 DoublePredicate代表一个拥有double值参数的boolean值方法
11 DoubleSupplier代表一个double值结构的提供方
12 DoubleToIntFunction接受一个double类型输入,返回一个int类型结果。
13 DoubleToLongFunction接受一个double类型输入,返回一个long类型结果
14 DoubleUnaryOperator接受一个参数同为类型double,返回值类型也为double 。
15 Function 接受一个输入参数,返回一个结果。
16 IntBinaryOperator接受两个参数同为类型int,返回值类型也为int 。
17 IntConsumer接受一个int类型的输入参数,无返回值 。
18 IntFunction 接受一个int类型输入参数,返回一个结果 。
19 IntPredicate接受一个int输入参数,返回一个布尔值的结果。
20 IntSupplier无参数,返回一个int类型结果。
21 IntToDoubleFunction接受一个int类型输入,返回一个double类型结果 。
22 IntToLongFunction接受一个int类型输入,返回一个long类型结果。
23 IntUnaryOperator接受一个参数同为类型int,返回值类型也为int 。
24 LongBinaryOperator接受两个参数同为类型long,返回值类型也为long。
25 LongConsumer接受一个long类型的输入参数,无返回值。
26 LongFunction 接受一个long类型输入参数,返回一个结果。
27 LongPredicateR接受一个long输入参数,返回一个布尔值类型结果。
28 LongSupplier无参数,返回一个结果long类型的值。
29 LongToDoubleFunction接受一个long类型输入,返回一个double类型结果。
30 LongToIntFunction接受一个long类型输入,返回一个int类型结果。
31 LongUnaryOperator接受一个参数同为类型long,返回值类型也为long。
32 ObjDoubleConsumer 接受一个object类型和一个double类型的输入参数,无返回值。
33 ObjIntConsumer 接受一个object类型和一个int类型的输入参数,无返回值。
34 ObjLongConsumer 接受一个object类型和一个long类型的输入参数,无返回值。
35 Predicate 接受一个输入参数,返回一个布尔值结果。
36 Supplier 无参数,返回一个结果。
37 ToDoubleBiFunction 接受两个输入参数,返回一个double类型结果
38 ToDoubleFunction 接受一个输入参数,返回一个double类型结果
39 ToIntBiFunction 接受两个输入参数,返回一个int类型结果。
40 ToIntFunction 接受一个输入参数,返回一个int类型结果。
41 ToLongBiFunction 接受两个输入参数,返回一个long类型结果。
42 ToLongFunction 接受一个输入参数,返回一个long类型结果。
43 UnaryOperator 接受一个参数为类型T,返回值类型也为T。

方法引用与构造器引用

方法引用

当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!)
方法引用:使用操作符“::”将方法名和对象或类的名字分隔开来。

使用情况

如下三种主要使用情况:

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

注意事项

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

使用示例

@Test
public void test8 () {
    System.out.println("---方法引用  对象的引用 :: 实例方法名---");
    Student student = new Student("小明", 22, Student.Gender.MAN);
    Supplier<String> supplier = () -> student.getName();
    //使用方法引用,可以进行如下改写
    Supplier<String> supplier2 = student::getName;
    String s = supplier.get();
    String s2 = supplier2.get();
    System.out.println(s + " " + s2 + " " + "是否相同:" + s.equals(s2));

    System.out.println("---方法引用  类名 :: 静态方法名---");
    Comparator<Integer> com = ( x, y ) -> Integer.compare(x, y);
    //改写
    Comparator<Integer> com2 = Integer::compare;
    com2.compare(1, 2);

    System.out.println("---方法引用 类名 :: 实例方法名 ---");
    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"));
}

构造器引用

格式:ClassName::new
与函数式接口相结合,自动与函数式接口中方法兼容。

注意事项:

可以把构造器引用赋值给定义的方法,与构造器参数列表要与接口中抽象方法的参数列表一致!

示例

@Test
public void test9 () {
    System.out.println("---测试构造器引用---");
    Function<String, String> fun = String::new;
    String s = fun.apply("测试");
    System.out.println(s);

    //使用无参构造器引用,创建对象
    Supplier<Student> student = Student::new;
}

数组引用

格式:type[] :: new

示例

@Test
public void test10() {
    System.out.println("---测试数组引用---");
    Function<Integer, String[]> fun = String[]::new;
    String[] strs = fun.apply(10);
    System.out.println(strs.length);
}

你可能感兴趣的:(Java基础)