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


在学习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<String> 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<String,string> map = new HashMap<>();
List<Integer> 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> 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<T,U> 代表了一个接受两个输入参数的操作,并且不返回任何结果
2 BiFunction<T,U,R> 代表了一个接受两个输入参数的方法,并且返回一个结果
3 BinaryOperator 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果
4 BiPredicate<T,U> 代表了一个两个参数的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<T,R> 接受一个输入参数,返回一个结果。
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<T,U> 接受两个输入参数,返回一个double类型结果
38 ToDoubleFunction 接受一个输入参数,返回一个double类型结果
39 ToIntBiFunction<T,U> 接受两个输入参数,返回一个int类型结果。
40 ToIntFunction 接受一个输入参数,返回一个int类型结果。
41 ToLongBiFunction<T,U> 接受两个输入参数,返回一个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);
}

猜你喜欢

转载自blog.csdn.net/qq_42937522/article/details/106887569