JDK8新特性之常见的函数式接口

函数式接口

一、概念

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

二、格式

只要确保接口中有且仅有一个抽象方法即可:
说明:函数式接口要求只有一个抽象方法。但是还可以有默认方法、静态方法,只要只有一个抽象方法就可以。

修饰符 interface 接口名称 {
    public abstract 返回值类型 方法名称(可选参数信息);
}

由于接口当中抽象方法的public abstract是可以省略的,所以定义一个函数式接口很简单:

public interface MyFunctionalInterface {
    //抽象方法只有一个
    void method();
    //可以含有默认方法
    public default void method_1() {
    }
}

三、@FunctionalInterface注解

与@Override注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解:@FunctionalInterface。该注解可用于一个接口的定义上:

@FunctionalInterface
public interface MyFunctionalInterface {
    void method();
}

一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。

问题:为什么要使用该注解来验证一个接口是否是函数式接口呢?
答:因为如果一个接口是函数式接口,可以使用Lambda来简化代码的开发

四、自定义函数式接口(无参无返回值)

对于刚刚定义好的MyFunctionalInterface函数式接口,典型使用场景就是作为方法的参数:

public class Demo04 {
    public static void main(String[] args) {
        show(() -> System.out.println("lambda执行了。。。。"));
    }
    public static void show(MyFunctionalInterface mi) {
        mi.method();// 调用自定义的函数式接口方法
    }
}

四、练习:自定义函数式接口(有参有返回)

请定义一个函数式接口Sumable,内含抽象sum方法,可以将两个int数字相加返回int结果。使用该接口作为方法的参数,并进而通过Lambda来使用它。

@FunctionalInterface
public interface Sumable {
    int sum(int a, int b);
}

public class Demo05 {
    public static void main(String[] args) {
        getSum(150,250,(a,b) -> a + b);
    }
    private static void getSum(int a,int b,Sumable sumable){
        int sum = sumable.sum(a, b);
        System.out.println(sum);
    }
}

方法引用
一、方法引用代码示例

// 函数式接口
@FunctionalInterface
public interface Printable {
    //打印字符串str
    void print(String str);
}

public class Demo01 {
    public static void main(String[] args) {
        // printString(str -> System.out.println(str));
        // 使用方法引用输出
        printString(System.out::println);
    }
    public static void printString(Printable pt) {
        //传递参数
        pt.print("Hello World");

    }
}

二、方法引用符
双冒号::为引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。

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

函数式接口是Lambda的基础,而方法引用是Lambda的孪生兄弟。就是只要可以使用Lambda的地方都可以使用方法引用,没有使用Lambda表达式就不能使用方法引用。

下面这段代码将会调用println方法的不同重载形式,将函数式接口改为int类型的参数:

@FunctionalInterface
public interface PrintableInteger {
    void print(int str);
}

由于上下文变了之后可以自动推导出唯一对应的匹配重载,所以方法引用没有任何变化:

public class Demo01 {
    public static void main(String[] args) {
        // printString(str -> System.out.println(str));
        printString(System.out::println);
    }
    public static void printString(Printable pt) {
        //传递参数
        pt.print(1024);

    }
}

这次方法引用将会自动匹配到println(int)的重载形式。

三、通过对象名引用成员方法
一个类中存在一个成员方法:

public class MethodRefObj {
    public void printUpperCase(String s){
        System.out.println(s.toUpperCase());
    }
}

函数式接口:

@FunctionalInterface
public interface Printable {
    //打印字符串str
    void print(String str);
}

通过对象名引用成员方法

public class Demo01 {
    public static void main(String[] args) {
        MethodRefObj obj = new MethodRefObj();
        // Lambda省略和推到形式
        // show("helloworld",str -> obj.printUpperCase(str));
        // 方法引用
        show("helloworld",obj::printUpperCase);
    }
    //自定义方法
    public static void show(String s, Printable pt) {
        //调用Printable接口中的方法将s变为大写
        pt.print(s);
    }
}

说明:
Lambda表达式格式:(参数) -> {对象.方法名(参数)}
方法引用格式: 对象::方法名
下面两种写法是等效的:

Lambda表达式:s2 -> methodRefObject.printUpperCase(s2)
方法引用:methodRefObject::printUpperCase
(使用方法引用前提:使用对象引用成员方法,要求必须有该对象。)
四、通过类名称引用静态方法
函数式接口:

@FunctionalInterface
public interface Calcable {
    int calc(int num);
}

public class Demo02 {
    public static void main(String[] args) {
        // 使用Lambda表达式
        // method(-5,num -> Math.abs(num));
        // 使用方法引用
        method(-5,Math::abs);
    }
    private static void method(int num,Calcable calcable){
        System.out.println(calcable.calc(num));
    }
}

五、通过super引用成员方法

如果存在继承关系,当Lambda中需要出现super调用时,也可以使用方法引用进行替代。首先是函数式接口:

@FunctionalInterface
public interface Greetable {
    void greet();
}

父类:

public class Fu {
    public void say() {
        System.out.println("Fu 。。。Hello!");
    }
}

子类:

public class Zi extends Fu{
    @Override
    public void say() {
        // lambda写法
        // method(() -> super.say());
        // 方法引用写法
        method(super::say);
    }
    private void method(Greetable lambda) {
        lambda.greet();
        System.out.println("zi.......");
    }
}

测试:

public class Test01 {
    public static void main(String[] args) {
        Zi zi = new Zi();
        zi.say();
    }
}

六、通过this引用成员方法

this代表当前对象,如果需要引用的方法就是当前类中的成员方法,那么可以使用“this::成员方法”的格式来使用方法引用。首先是简单的函数式接口:

@FunctionalInterface
public interface Richable {
    void buy();
}

定义一个类

public class Husband {
    // 定义一个成员方法,供lambda引用
    public void buyHouse(){
        System.out.println("买房子");
    }
    // 函数式接口当参数传递
    private void marry(Richable richable){
        richable.buy();
    }
    // 方法引用
    public void beHappy(){
        // marry(() -> this.buyHouse());
        marry(this::buyHouse);
    }
}

测试

public class Test02 {
    public static void main(String[] args) {
        Husband husband = new Husband();
        husband.beHappy();
    }
}

七、类的构造方法引用

由于构造方法的名称与类名完全一样,并不固定。所以构造方法引用使用类名称::new的格式表示。
首先创建一个Person类:
1)定义一个私有成员变量name;
2)定义满参构造方法和get set 方法;

创建函数式接口:PersonBuilder

@FunctionalInterface
public interface PersonBuilder {
    // 定义一个抽象方法用来创建Person对象
    Person buildPerson(String name);
}

测试

public class Test03 {
    public static void main(String[] args) {
        // lambda省略版
        // printName("王思聪",name -> new Person(name));
        // 方法引用
        printName("迪丽热巴",Person::new);
    }
    public static void printName(String name, PersonBuilder builder) {
        Person p = builder.buildPerson(name);
        System.out.println("姓名:" + p.getName());
    }
}

八、数组的构造器引用

数组也是Object的子类对象,所以同样具有构造器,只是语法稍有不同。
格式:数据类型[]::new
需求:创建一个长度是10的int类型数组。
首先创建函数式接口:

@FunctionalInterface
public interface ArrayBuilder {
    int[] buildArray(int length);// length表示数组的长度
}

测试

public class Test04 {
    public static void main(String[] args) {
        // lambda简化版
        // int[] arr = initArray(10, length -> new int[length]);
        // 方法引用
        int[] arr = initArray(10,int[]::new);
        System.out.println(arr.length);
    }
    public static int[] initArray(int length,ArrayBuilder builder) {
        int[] arr = builder.buildArray(length);
        return arr;
    }
}

九、方法引用的总结

方法引用是lambda另外一种格式,只是这种格式有一些特点,在特定的情况下才可以使用。
在lambda表达式中,{}里面的内容如果是使用到了其他对象或者是其他类中的功能,这个时候可以采用方法引用简化格式
如果lambda表达式{}中,功能中使用到的方法是某个类的成员方法,这个时候可以改写为 该类对象::方法名
如果lambda表达式{}中,功能中使用到的方法是某个类的静态方法,这个时候可以改写为 类名::静态方法名
如果lambda表达式{}中,功能中使用到的方法是其父类的成员方法,(…)-> super.方法名(…)简写为 super::方法名
如果lambda表达式{}中,功能中使用到的方法是其类的成员方法,(…)-> this.方法名(…)简写为 this::方法名
函数式编程
在兼顾面向对象特性的基础上,Java语言通过Lambda表达式与方法引用等,为开发者打开了函数式编程的大门。

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

public class Demo01 {
    public static void main(String[] args) {
        String msgA = "Hello ";
        String msgB = "World ";
        String msgC = "Java";

        method(1, msgA + msgB + msgC);
    }
    private static void method(int level, String msg) {
        if (level == 1) {
            System.out.println(msg);
        }
    }
}

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

Lambda的写法:首先定义一个函数式接口

@FunctionalInterface
public interface MessageBuilder {  
    String buildMessage();
}

测试

public class Test01 {
    public static void main(String[] args) {
        String strA = "Hello ";
        String strB = "World ";
        String strC = "Java";
        method(1,() -> strA+strB+strC);
    }
    private static void method(int level, MessageBuilder builder) {
        //判断
        if (level == 1) {
            //只有level等于1的时候才会执行这句话,才会执行lambda,然后才能进行拼接
            System.out.println(builder.buildMessage());
        }
    }
}

说明:由于上述使用了Lambda表达式,Lambda表达式是在执行代码:builder.buildMessage() 时才会执行msgA + msgB + msgC。

二、使用Lambda作为参数
Java中的Lambda表达式可以被当作是匿名内部类的另一种体现。如果方法的参数是一个函数式接口类型,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数。

代码示例:函数式接口作为参数
首先定义一个函数式接口

@FunctionalInterface
public interface MySupplier {  
    Object get();
}

测试

public class Demo {
     public static void main(String[] args) {
        //使用lambda
       // printObject(()->{return "Hello";});
        //lambda省略格式
        printObject(()->"Hello");
    }
    private static void printObject(MySupplier supplier) {
        System.out.println(supplier.get());
    }
}

三、使用Lambda作为返回值
如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。
当需要通过一个方法来获取一个java.util.Comparator接口类型的对象作为排序器时:
需求:定义一个字符串数组按照字符串的长度降序排序。

public class Demo02 {
    public static void main(String[] args) {
        //定义一个字符串数组
        String[] arr={"abc","ab","abcds","adef"};
        Arrays.sort(arr,getComparator());
        System.out.println(Arrays.toString(arr));
    }
    //定义一个方法来提供自定义比较器Comparator的对象
    public static Comparator getComparator() {
        //使用Lambda表达式返回Comparator的对象
        return (o1,o2)-> o2.length() - o1.length(); // 因为是降序,所以是o2在前面
    }
}

Lambda的总结:可以理解为1.8之后Lambda的诞生的思想是write less do more(写的少,做的多)。间接可以将Lambda理解为是对匿名内部类的简化,Lambda可以更节省空间和代码量并完成相同的功能。

常用函数式接口
我们以前都是自己定义函数式接口,其实JDK本身提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在java.util.function包中被提供。比如:java.util.function.Supplier

一、Supplier接口
java.util.function.Supplier接口仅包含一个无参的方法:T get()。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
说明:
1、这里的泛型T表示当我们使用该接口时,传递什么数据类型就是什么数据类型。然后我们通过这个接口中的get方法就可以获取到指定泛型的类型的对象。
2、由于Supplier属于函数式接口,我们可以使用Lambda来实现。在Lambda中可以完成Supplier接口中的get()方法体中的代码。主要完成生产某个类的对象数据的功能。即我们需要使用Lambda返回指定泛型类的对象。
3、其实Supplier接口就是用来生产某个类的对象的。

需求:指定泛型是String类型,获取String类的对象。

public class Demo03 {
    public static void main(String[] args) {
        String s = getString(() -> "helloworld");
        System.out.println("s: " + s);
    }
    public static String getString(Supplier lambda){ //Supplier 已经确定泛型T是String类型
        return lambda.get();
    }
}

说明:上述只是生产String类的对象,那么我们要生产任何类的对象,我们可以修改代码如下所示:

public class Demo03 {
    public static void main(String[] args) {
        String s = getString(() -> "helloworld");
        System.out.println("s: " + s);
        Integer i = getString(() -> 123);
        System.out.println("i: " + i);
    }
    public static  T getString(Supplier lambda){
        return lambda.get();
    }
}

为什么不直接定义:Integer i =123或者int i = 123;
原因:
使用Integer i =123;或者int i=123;这样定义,就把获取对象数据的方式写死了。很不灵活。而使用Supplier接口的方式,我们可以在getObject方法中,除了执行return lambda.get();我们还可以书写其他的代码,完成其他的功能。比如再生产某个对象时,有可能我们还会结合其他的类和对象完成更多的功能。只是我们这里书写的比较简单,简化了业务代码。

二、Consumer接口
java.util.function.Consumer接口和Supplier接口正好相反,它不是用来生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。

抽象方法:accept
Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据类型的对象。
由于accept方法有参数,没有返回值,所以该方法只是用来消费接收到的对象数据。

在使用该接口的时候会确定接口上的泛型,由于这个接口是函数式接口,可以使用Lambda方式来实现。在使用Lambda实现方法体的时候,传递什么数据就对什么数据进行操作,想怎么操作就怎么操作。
代码示例:需求:使用Consumer接口消费String类型的对象数据。

public class ConsumerDemo {
    public static void main(String[] args) {
        String s = "Hello World";
        // 可以消费名称
        consumerString(s, System.out::println);
        // 可以消费长度
        consumerString(s,len -> System.out.println(len.length()));
    }
    //定义一个方法使用接口Consumer来消费字符串对象
    public static void consumerString(String s, Consumer lambda) {
        // 使用lambda调用accept方法来消费字符串s
        lambda.accept(s);
    }
}

通过查看Consumer接口的源代码我们发现,该接口中还有一个默认方法andThen(表示然后的意思)方法。

默认方法:andThen
default Consumer andThen(Consumer after) {}
如果一个方法的参数和返回值全都是Consumer类型,那么就可以实现效果:消费一个数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是Consumer接口中的default方法andThen。下面是JDK的源代码:

default Consumer andThen(Consumer after) {
    Objects.requireNonNull(after);
    return (T t) -> { accept(t); after.accept(t); };
}

说明:
1、java.util.Objects的requireNonNull静态方法将会在参数after为null时主动抛出NullPointerException异常。这省去了重复编写if语句和抛出空指针异常的麻烦。

2、return (T t) -> { accept(t); after.accept(t); };表示先执行第一个对象的accept(t)方法,然后在执行第二个对象after.accept(t);方法

要想实现组合,需要两个或多个Lambda表达式即可,而andThen的语义正是“一步接一步”操作。谁写在前面先执行谁。

代码示例:使用andThen()方法依次求出字符串"hello"的长度,然后将所有小写字母转换为大写字母。

public class AndThenDemo {
    public static void main(String[] args) {
        method("hello",s -> System.out.println(s.length()),
                s -> System.out.println(s.toUpperCase()));
    }

    public static void method(String s, Consumer one, Consumer two){
        one.andThen(two).accept(s);
    }
}

练习:格式化打印信息
下面的字符串数组当中存有多条信息,请按照格式“姓名:XX。性别:XX。”的格式将信息打印出来。要求将打印姓名的动作作为第一个Consumer接口的Lambda实例,将打印性别的动作作为第二个Consumer接口的Lambda实例,将两个Consumer接口按照顺序“拼接”到一起。

public class DemoConsumer {
    public static void main(String[] args) {
        String[] arr = {"迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男"};
        printInfo(s -> System.out.print("姓名:" + s.split(",")[0]),
                s -> System.out.println("。性别:" + s.split(",")[1] + "。"),arr);
    }
    private static void printInfo(Consumer one, Consumer two, String[] array) {
        for (String s : array) { // s:迪丽热巴,女  古力娜扎,女  马尔扎哈,男
            one.andThen(two).accept(s); // 姓名:迪丽热巴。性别:女。
        }
    }
}

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

抽象方法:test
Predicate接口中包含一个抽象方法:boolean test(T t)。
说明:这个接口就是一个条件判断接口。判断传递的泛型类型的数据是否符合判断条件,而条件的具体代码由Lambda表达式来实现。

需求:判断指定的字符串 “ajahsgs” 中是否含有a字符。

public class PredicateDemo {
    public static void main(String[] args) {
        String name="ajahsgs";
        boolean boo = checkString(name, s -> s.contains("a"));
        System.out.println("boo = " + boo);
    }
    public static boolean checkString(String s, Predicate lambda) {
        return lambda.test(s);
    }
}

默认方法:and
既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个Predicate条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用default方法and。其JDK源码为:

default Predicate and(Predicate other) {
    Objects.requireNonNull(other);//判断接口对象是否为空
    return (t) -> test(t) && other.test(t);
}

需求:如果要判断一个字符串既要包含字母“a”,并且长度大于5。

public class PredicateDemo02 {
    public static void main(String[] args) {
        String name = "ajahsgs";
        boolean boo = checkString(name, s -> s.contains("a"), s -> s.length() > 5);
        System.out.println("boo : " + boo);
    }
    public static boolean checkString(String s, Predicate one, Predicate two) {
        return one.and(two).test(s);
    }
}

默认方法:or
与and类似,默认方法or实现逻辑关系中的“或”。JDK源码为:

default Predicate or(Predicate other) {
    Objects.requireNonNull(other);
    return (t) -> test(t) || other.test(t);
}

如果希望实现逻辑“一个字符串包含字母a或者长度大于5”,那么代码只需要将“and”修改为“or”名称即可,其他都不变:

public class PredicateDemo02 {
    public static void main(String[] args) {
        String name = "aja";
        boolean boo = checkString(name, s -> s.contains("a"), s -> s.length() > 5);
        System.out.println("boo : " + boo);
    }
    public static boolean checkString(String s, Predicate one, Predicate two) {
        return one.or(two).test(s);
    }
}

默认方法:negate
“与”、“或”已经了解了,剩下的“非”(取反)也很简单。默认方法negate的JDK源代码为:

default Predicate negate() {
    return (t) -> !test(t);
}

从实现中很容易看出,它是执行了test方法之后,对结果boolean值进行“!”取反而已。一定要在test方法调用之前调用negate方法,就是先调用negate方法,在调用test方法。正如and和or方法一样:
negate()方法演示如下:

public class PredicateDemo02 {
    public static void main(String[] args) {
        String name = "bjb";
        boolean boo = checkString(name, s -> s.contains("a"));
        System.out.println("boo : " + boo);
    }
    public static boolean checkString(String s, Predicate one) {
        return one.negate().test(s);
    }
}

练习:集合信息筛选

/*
    数组当中有多条“姓名+性别”的信息如下,请通过Predicate接口的拼装将符合要求的字符串筛选到集合ArrayList中,
    需要同时满足两个条件:
    1. 必须为女生;
    2. 姓名为4个字。
 */
public class PredicateDemo03 {
    public static void main(String[] args) {
        String[] array = {"迪丽热巴,女","古力娜扎,女","马尔扎哈,男","赵丽颖,女"};
        List list = getList(array, s -> s.split(",")[1].equals("女"), s -> s.split(",")[0].length() == 4);
        System.out.println(list);
    }
    //自定义方法
    public static List getList(String[] arr, Predicate one, Predicate two)
    {
        //创建一个空的集合
        ArrayList list = new ArrayList<>();
        //遍历数组
        for (String s : arr) {
            //s表示取出的每一个数据
            boolean boo = one.and(two).test(s);//如果boo为true,说明满足两个条件
            //判断,满足两个条件,就将s添加到list集合中
            if (boo) {
                //说明满足两个条件
                list.add(s);
            }
        }
        //将list返回给调用者
        return list;
    }
}

四、Function接口
java.util.function.Function接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件,有进有出。

抽象方法:apply
Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。
说明:
Function接口其实是一个转换接口,具有转换功能,可以将传入的T类型数据转换为R类型数据。转换的操作由Lambda表达式来处理,就是根据类型T作为参数的数据获取R类型的数据。

示例:将String类型转换为Integer类型。“123”------>转换为123。

public class FunctionDemo {
    public static void main(String[] args) {
        String s = "123";
        int i = method(s, Integer::parseInt);
        System.out.println(i);
    }
    //定义方法通过接口Function将字符串转换为整数
    public static int method(String s,Function lambda) {
        return lambda.apply(s);
    }
}

默认方法:andThen

Function接口中也有一个默认的andThen方法,用来进行组合操作。JDK源代码如:

default  Function andThen(Function after) {
    Objects.requireNonNull(after);//判断是否为空
    return (T t) -> after.apply(apply(t));//先执行apply(t),在执行after.apply()
}

该方法同样用于“先做什么,再做什么”的场景,和Consumer中的andThen差不多:
需求:将一个字符串"10"先转换位整数10,然后在将转换后的数字10乘以10,并输出结果。

public class Demo011 {
    public static void main(String[] args) {
        //调用方法
        method("10", Integer::parseInt, i-> i = i*10);
    }
    //定义方法
    public static void method(String s, Function one, Function two) {
        //调用方法
        int result = one.andThen(two).apply(s);
        //输出结果
        System.out.println("result = " + result);
    }
}

请注意,Function的前置条件泛型和后置条件泛型可以相同。

练习:自定义函数模型拼接
请使用Function进行函数模型的拼接,按照顺序需要执行的多个函数操作为:
给定字符串:String str = “赵丽颖,20”;

将字符串截取数字年龄部分,得到字符串;Function
将上一步的字符串转换成为int类型的数字;Function
将上一步的int数字累加100,得到结果int数字。Function

public class DemoFunction {
    public static void main(String[] args) {
        String str = "赵丽颖,20";
        int age = getAgeNum(str, s -> s.split(",")[1],
                            Integer::parseInt,
                            n -> n += 100);
        System.out.println(age);
    }
    private static int getAgeNum(String str, Function one,
                                 Function two,
                                 Function three) {
        //这里先执行one.apply() ,然后是two.apply() 最后是three.apply()
        return one.andThen(two).andThen(three).apply(str);
    }
}

五、总结:延迟方法与终结方法
在上述学习到的多个常用函数式接口当中,方法可以分成两种:

延迟方法:只是在拼接Lambda函数模型的方法,并不立即执行得到结果。
终结方法:根据拼好的Lambda函数模型,立即执行得到结果值的方法。
通常情况下,这些常用的函数式接口中唯一的抽象方法为终结方法,而默认方法为延迟方法。但这并不是绝对的。下面的表格中进行了方法分类的整理:

20190609140106385.png

你可能感兴趣的:(JDK8新特性之常见的函数式接口)