Bootstrap

java之函数式接口、Lambda表达式和方法引用

介绍

Lambda表达式 (也称为闭包) 是整个 Java 8 发行版中最受期待的在 Java 语言层面上的改变,Lambda 允许把函数作为一个方法的参数,即行为参数化,函数作为参数传递进方法中

它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理。

很多JVM平台上的语言(Groovy、Scala等)从诞生之日就支持Lambda表达式,但是Java开发者没有选择,只能使用匿名内部类代替Lambda表达式。

(参数类型 参数名称)> { 代码语句 }

函数式接口是指一个接口内只有一个抽象方法,若干个 default 方法的接口,一般函数式接口都会使用 @FunctionalInterface 注解进行标注,但是并不是说没有该注解就不是一个函数式接口了,该注解只是用于编译期检查,如果有多个抽象方法,那么编译将不通过。

lambda表达式的作用

Lambda最直观的作用就是使代码变得整洁.。

我们可以对比一下 Lambda 表达式和传统的 Java 对同一个接口的实现。(就是下面的从匿名类到Lambda转换)

这两种写法本质上是等价的。但是显然,Java 8 中的写法更加优雅简洁。并且,由于 Lambda 可以直接赋值给一个变量,我们就可以直接把 Lambda 作为参数传给函数, 而传统的 Java 必须有明确的接口实现的定义,初始化才行。

从匿名类到Lambda转换

package com.demo.lambdaDemo;

public class LambdaDemo {

    public static void main(String[] args) {


        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("我是没有使用Lambda表达式:不简洁");
            }
        };




        Runnable runnable2 = () -> System.out.println("我是使用Lambda表达式:简洁、灵活");

        runnable.run();
        runnable2.run();
    }
}

Lambda表达式语法

Lambda表达式在java语言中引入了一种新的语法元素和操作。这种操作符号为“->”,Lambda操作符或箭头操作符,它将Lambda表达式分割为两部分。
左边:指Lambda表达式的所有参数
右边:指Lambda体,即表示Lambda表达式需要执行的功能。

package com.demo.lambdaDemo;

import java.util.Comparator;
import java.util.function.Consumer;

public class LambdaDemo {

    public static void main(String[] args) {

        // 语法格式一:无参数、无返回值,只需要一个Lambda体
        Runnable runnable = ()-> System.out.println("Lambda表达式:简洁、灵活,优雅永不过时");
        runnable.run();

        // 语法格式二:lambda有一个参数、无返回值
        // Lambda只有一个参数时,可以省略()
        Consumer<String> consumer = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };

        consumer.accept("爱与被爱的区别");

        Consumer<String> consumer2 = s -> System.out.println(s);
        consumer2.accept("接受爱不一定爱对方,爱一定付出真心爱");


        // 语法格式三:Lambda有两个参数时,并且有返回值
        // 当Lambda体只有一条语句的时候,return和{}可以省略掉
        Comparator<Integer> comparator = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 < o2 ? 1 : 0;
            }
        };
        System.out.println(comparator.compare(1,2));


        Comparator<Integer> comparator2 = (o1, o2) -> {return (o1<o2)?1:0;};
        System.out.println(comparator2.compare(1,2));



        // 语法格式四:类型推断:数据类型可以省略,因为编译器可以推断得出,成为“类型推断”
        Consumer<String> consumer3 = (String s) -> System.out.println(s);
        consumer3.accept("Hello World !");

        Consumer<String> consumer4 = (s) -> System.out.println(s);
        consumer4.accept("Hello don't date type !");

    }
}

使用lambda的前提条件

有且仅有一个抽象方法的接口,称为“函数式接口”

  1. 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。 无论是JDK内置的 Runnable 、 Comparator 接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。
  2. 方法中的参数或者局部变量的类型必须是接口。
package xw.lambda;
 
/**
 * @author xw
 *
 */
public class Test01 {
public static void main(String[] args) {
	//方法的参数或者局部变量类型必须为接口才能使用Lambda表达式
	ttt(()->{
		
	});
	
	
	Flyable f=()->{
		System.out.println("这就是Lambda表达式");
	};
	
	
}
public static void ttt(Flyable b) {
	
}
 
}

注意

Lambda表达式可以引用类成员和局部变量,但是会将这些变量隐式得转换成final

函数式接口

函数式接口在Java中是指:有且仅有一个抽象方法的接口

函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。

备注:从应用层面来讲,Java中的Lambda可以看做是匿名内部类的简化格式,但是二者在原理上不同。

@FunctionalInterface

@Override注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface
该注解可用于一个接口的定义上: 一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。
不过, 即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。

package com.lxit.testnginx.testnginx;

@FunctionalInterface
public interface MyFunctionInterface {

    void myMethod();
}

常用函数式接口

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

package com.demo.lambdaDemo;

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class LambdaDemo {


    public static String getString(Supplier<String> function){
        return function.get();

    }


    public static void consumerString(Consumer<String> consumer,String s){
        consumer.accept(s);

    }


    public static void consumerString(Consumer<String> first,Consumer<String> second, String s) {
        first.andThen(second).accept(s);
    }

    public static Integer functionInteger(Function<String,Integer> function,String s){
        return function.apply(s);
    }


    // 抽象方法 test
    public static boolean predicateType(Predicate<String> predicate,String s){
         return predicate.test(s);
    }


    // 默认方法 and,
    // 同样还有,默认方法 or,negate(取反)
    public static boolean predicateAnd(Predicate<String> predicate,Predicate<String> predicate2, String s){
        return predicate.and(predicate2).test(s);
    }






    public static void main(String[] args) {


        // 1. Supplier供应型接口 无参数无返回值
        // java.util.function.Supplier<T> 接口,它意味着"供给" , 对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
        // 仅包含一个无参的方法: T get() 。    用来获取一个泛型参数指定类型的对象数据。
        System.out.println(getString(()->"测试Supplier接口"));


        // 2. Consumer消费型接口 有参数无返回值
        // java.util.function.Consumer<T> 接口则正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。
        // 抽象方法:void accept(T t) ,意为消费一个指定泛型的数据。基本使用如:
        consumerString(s -> System.out.println(s),"测试Consumer接口");


        // 默认方法 andThen()
        // 如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费一个数据的时候,首先做一个操作,然后再做一个操作,实现组合。
        // 要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是“一步接一步”操作。例如两个步骤组合的情况
        consumerString(s -> System.out.println(s.toLowerCase()),s -> System.out.println(s + "abc"),"ABC");


        // 3. function功能型接口 有参数有返回值
        // java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件, 后者称为后置条件。有进有出,所以称为“函数Function”。
        // Function接口中的抽象方法为:R apply(T t) ,根据类型T的参数获取类型R的结果。使用的场景例如: 将 String 类型转换为 Integer 类型。
        // 也有andThen方法
        System.out.println(functionInteger((s)->Integer.valueOf(s),"666"));


        // 4. Predicate断言型接口 有参数,返回值为布尔值
        // 有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用 java.util.function.Predicate<T> 接口。
        // Predicate 接口中包含一个抽象方法: boolean test(T t) 。用于条件判断的场景
        System.out.println(predicateType(s -> s.length() < 100,"jakfnsdjfk"));

        System.out.println(predicateAnd(s -> s.length() == 3,s -> s.startsWith("a"),"abc"));

	}
}
// Comparator(比较器接口)是老Java中的经典接口, Java 8 在此之上添加了多种默认方法。源代码及使用示例如下
@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}



Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator.compare(p1, p2);             // > 0
comparator.reversed().compare(p1, p2);  // < 0

其他函数式接口

Operator
java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener

BiConsumer<T,U>:代表了一个接受两个输入参数的操作,并且不返回任何结果

BiFunction<T,U,R>:代表了一个接受两个输入参数的方法,并且返回一个结果

BinaryOperator:代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果

BiPredicate<T,U>:代表了一个两个参数的boolean值方法

BooleanSupplier:代表了boolean值结果的提供方

Consumer:代表了接受一个输入参数并且无返回的操作

Function<T,R>:接受一个输入参数,返回一个结果。

Predicate:接受一个输入参数,返回一个布尔值结果。

Supplier:无参数,返回一个结果。


java.util.concurrent.Callable

java.util.Comparator

方法引用

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

方法引用简化

// 1. 方法引用简化
consumerString(s-> System.out.println(s),"冗余的Lambda场景");

consumerString(System.out::println,"方法引用简化");

方法引用符

只能在使用lambda表达式的时候,才能使用方法引用符。

::

符号说明 : 双冒号为方法引用运算符,而它所在的表达式被称为方法引用。

应用场景 : 如果Lambda要表达的函数方案 , 已经存在于某个方法的实现中,那么则可以使用方法引用。

如上例中,System.out对象中有个println(String)方法 , 恰好就是我们所需要的 , 那么对于Consumer接口作为参数,对比下面两种写法,完全等效:

  • Lambda表达式写法:s -> System.out.println(s);
    拿到参数之后经Lambda之手,继而传递System.out.println方法去处理。
  • 方法引用写法:System.out::println 直接让System.out中的println方法来取代Lambda。

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

常见引用方式

package com.demo.lambdaDemo;

import com.demo.reflectDemo.Student;

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class LambdaDemo {


    public static String getString(Supplier<String> function){
        return function.get();

    }


    public static Double getInteger(Supplier<Double> function){
        return function.get();

    }


    public static void consumerString(Consumer<String> consumer,String s){
        consumer.accept(s);

    }


    public static Student createStudent(Function<String,Student> function,String name){

        return function.apply(name);
    }



    public static int[] createArray(Function<Integer,int[]> function,int n){
        return function.apply(n);
    }

    public static void main(String[] args) {


        // 1. 方法引用简化
        consumerString(s-> System.out.println(s),"冗余的Lambda场景");

        consumerString(System.out::println,"方法引用简化");


        // 2. 对象名引用成员方法
        getString("abc"::toUpperCase);


        // 3. 类名引用静态方法
        getInteger(Math::random);


        // 4. 类构造引用
        Student student = createStudent(Student::new, "zs");


        // 5.数组构造引用
        int[] array = createArray(int[]::new, 5);

	}
}

理解历史人物,就是理解现在的人物。

当年明月

部分内容转载自:
https://blog.csdn.net/weixin_40294256/article/details/126338618
https://baijiahao.baidu.com/s?id=1735030804095457450&wfr=spider&for=pc

;