侧边栏壁纸
博主头像
孔子说JAVA博主等级

成功只是一只沦落在鸡窝里的鹰,成功永远属于自信且有毅力的人!

  • 累计撰写 377 篇文章
  • 累计创建 136 个标签
  • 累计收到 12 条评论

目 录CONTENT

文章目录

java8新特性函数式接口Function使用方法详解

孔子说JAVA
2023-01-11 / 0 评论 / 0 点赞 / 90 阅读 / 22,751 字 / 正在检测是否收录...

之前在讲Lambda表达式的时候提到过,所谓的函数式接口(Functional Interface),就是指在一个接口里面有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,是适用于函数式编程场景的接口。这是Java8的一个新特性,表示函数式的操作。而Java中的函数式编程的体现就是 Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只要接口中只定义了一个抽象方法,那么它就是一个函数式接口。函数式接口可以被隐式转换为 lambda 表达式。

1、函数式接口

1.1 概述

有且仅有一个抽象方法,可以有多个非抽象方法的接口。Java中的函数式编程体现就是Lambda表达式,所以函数式接口适用于Lambda使用的接口。

  • 有且仅有一个抽象方法的接口;
  • 函数式接口里允许定义默认方法,因为默认方法不是抽象方法,其有一个默认实现,所以是符合函数式接口的定义的;
  • 函数式接口里允许定义静态方法,因为静态方法不能是抽象方法,是一个已经实现了的方法,所以是符合函数式接口的定义的;
  • 函数式接口里允许定义java.lang.Object里的public方法,这些方法对于函数式接口来说,不被当成是抽象方法(虽然它们是抽象方法);因为任何一个函数式接口的实现,默认都继承了Object类,包含了来自java.lang.Object里对这些抽象方法的实现;

那么我们该如何检测一个接口是不是函数式接口呢?

Java 8为函数式接口引入了一个新注解@FunctionalInterface,主要用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口定义的时候,编译器会报错。

将 @FunctionalInterface 注解放在接口定义的上方,如果接口是函数式接口,编译通过;如果不符合规范,就会编译报错。

package com.kongzi;

@FunctionalInterface
public interface GreetingService {
   void sayMessage(String message);
}

上述代码在IDE中是可以通过编译的,说明该接口符合函数式接口规范,是一个函数式接口。下面我们使用Lambda表达式来表示该接口的一个实现:

  • 注:JAVA 8 之前一般是用匿名类实现的
package com.kongzi;

public class Demo {
    public static void main(String[] args) {
        GreetingService greetService1 = message -> System.out.println("Hello " + message);
    }
}

提醒:加不加 @FunctionalInterface 对于接口是不是函数式接口没有影响,该注解只是提醒编译器去检查该接口是否仅包含一个抽象方法。

如下代码不会报错:

@FunctionalInterface
interface GreetingService{
   void sayMessage(String message);

	// 默认方法
   default void doSomeMoreWork1(){
      // Method body
   }

	// 默认方法
   default void doSomeMoreWork2(){
      // Method body
   }
   
	// 静态方法
   static void printHello(){
      System.out.println("Hello");
   }
   
   // java.lang.Object里的public方法
   @Override
   boolean equals(Object obj);
}

1.2 作用

  • 是Lambda表达式的使用前提
  • 概念层面,为了表示接口就代表这个抽象方法,所以将名起为函数式接口

1.3 常用的函数式接口

Java8在java.util.function包下预定义了大量的函数数式接口供我们使用。其中常用的接口有以下几个:

  • Function<T,R>(函数型接口)方法:R apply(T t)
  • Supplier(供给型接口)方法:T get()
  • Consumer(消费型接口)方法:void accept(T t)
  • Predicate(断言型接口)方法:boolean test(T t)

JDK 1.8 之前已有的函数式接口:

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

2、函数型接口Function

2.1 接口解析

标注了@FunctionalInterface的接口有很多,其中有个Function接口。Function<T,R> 接口通常用于对参数进行处理,转换(处理逻辑由Lambda表达式实现),然后返回一个新的值。

该接口可以接收一个数据,数据的类型根据泛型指定,然后通过该接口的实现类对象对 该数据进行操作,操作之后返回一个新的数据。

  • 接口名:Function<T,R>
  • 抽象方法:R apply(T):接收一个数据,操作数据之后,返回一个新的数据(根据一个类型的数据得到另一个类型的数据,前者T称为前置条件,后者R称为后置条件)
  • 拓展的非抽象方法: default<V> Function andThen(Function after):返回一个组合函数,首先将该函数应用于输入,然后将after函数应用于结果(先通过调用者对象处理参数,将处理的结果再通过f对象处理,将两个处理的结果进行返回)。
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
    
    /**
     * @return a composed function that first applies the {@code before}
     * function and then applies this function
     */
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    
    /**
     * @return a composed function that first applies this function and then
     * applies the {@code after} function
     */
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
}

从源码可以看出来Function是一个泛型类,其中定义了两个泛型参数 T 和 R ,在Function中,T代表输入参数,R代表返回的结果。也许你很好奇,为什么跟别的java源码不一样,Function 的源码中并没有具体的逻辑呢?

  • 其实这很容易理解,Function 就是一个函数,其作用类似于数学中函数的定义,(x,y)跟<T,R>的作用几乎一致。

image

所以Function中没有具体的操作,具体的操作需要我们去为它指定,因此apply具体返回的结果取决于传入的lambda表达式。

示例如下:

public void test(){
    Function<Integer,Integer> test = i->i+1;
    test.apply(5);
}
/** print:6*/

上例中我们用lambda表达式定义了一个行为使得i自增1,我们使用参数5执行apply方法,最后返回6。

  • 在面向对象编程中,我们定义一组操作首先想到的是定义一个方法,然后指定传入参数,返回我们需要的结果。
  • 而在函数式编程中,即函数式编程的思想是先不去考虑具体的行为,而是先去考虑参数,具体的方法实现后续再设置。
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
}

compose接收一个Function参数,返回时先用传入的逻辑执行apply,然后使用当前Function的apply。

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
}

andThen跟compose正相反,先执行当前的逻辑,再执行传入的逻辑。这样说可能不够直观,可以换个说法:

  • compose等价于B.apply(A.apply(5)),而andThen等价于A.apply(B.apply(5))。

2.2 应用示例

2.2.1 示例1

我们通过给calculate 传入不同的Function,实现了在同一个方法中实现不同的操作。在实际开发中这样可以大大减少很多重复的代码,比如我在实际项目中有个新增用户的功能,但是用户分为VIP和普通用户,且有两种不同的新增逻辑。那么此时我们就可以先写两种不同的逻辑。除此之外,这样还让逻辑与数据分离开来,我们可以实现逻辑的复用。

public class FunctionTest {

    @Test
    public void test1() {
        Function<Numbers, Integer> test1 = i -> i.getN1() - i.getN2();
        Function<Numbers, Integer> test2 = i -> i.getN1() * i.getN2();
        System.out.println(calculate(test1, 2, 2));
        System.out.println(calculate(test2, 2, 2));
    }
    /** 输出结果为:0  4 **/
    
    public Integer calculate(Function<Numbers, Integer> test, Integer number1, Integer number2) {
        Numbers n = new Numbers();
        n.setN1(number1);
        n.setN2(number2);
        return test.apply(n);
    }
}

class Numbers {
    private Integer n1;
    private Integer n2;

    public Integer getN1() {
        return n1;
    }

    public void setN1(Integer n1) {
        this.n1 = n1;
    }

    public Integer getN2() {
        return n2;
    }

    public void setN2(Integer n2) {
        this.n2 = n2;
    }
}

当然实际开发中的逻辑可能很复杂,比如两个方法F1,F2都需要操作AB,但是F1需要A->B,F2方法需要B->A。这样的我们用刚才的方法也可以实现,源码如下:

public void test(){
    Function<Integer,Integer> A=i->i+1;
    Function<Integer,Integer> B=i->i*i;
    System.out.println("F1:"+B.apply(A.apply(5)));
    System.out.println("F2:"+A.apply(B.apply(5)));
}
/** F1:36 */
/** F2:26 */

2.2.2 示例2

public class FunctionTest {

    @Test
    public void test2() {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

        // Predicate<Integer> predicate = n -> true
        // n 是一个参数传递到 Predicate 接口的 test 方法
        // n 如果存在则 test 方法返回 true

        System.out.println("输出所有数据:");

        // 传递参数 n
        eval(list, n -> true);

        // Predicate<Integer> predicate1 = n -> n%2 == 0
        // n 是一个参数传递到 Predicate 接口的 test 方法
        // 如果 n%2 为 0 test 方法返回 true

        System.out.println("输出所有偶数:");
        eval(list, n -> n % 2 == 0);

        // Predicate<Integer> predicate2 = n -> n > 3
        // n 是一个参数传递到 Predicate 接口的 test 方法
        // 如果 n 大于 3 test 方法返回 true

        System.out.println("输出大于 3 的所有数字:");
        eval(list, n -> n > 3);
    }

    public static void eval(List<Integer> list, Predicate<Integer> predicate) {
        for (Integer n : list) {
            if (predicate.test(n)) {
                System.out.println(n + " ");
            }
        }
    }
}

image-1673344107754

2.2.3 示例3

public class FunctionTest {

    @Test
    public void test3() {
        convert("666", (s) -> Integer.parseInt(s));
        convert(666, i -> String.valueOf(i));
        convert("666", s -> Integer.parseInt(s), i -> String.valueOf(i));
    }

    private static void convert(String s, Function<String, Integer> fun) {
        int i = fun.apply(s);
        System.out.println(i);
    }

    private static void convert(Integer i, Function<Integer, String> fun) {
        String s = fun.apply(i);
        System.out.println(s);
    }

    private static void convert(String s, Function<String, Integer> fun1, Function<Integer, String> fun2) {
//        int i=fun1.apply(s);
//        String str=fun2.apply(i);
        String str = fun1.andThen(fun2).apply(s);
        System.out.println(str);
    }
}

上述示例输出 3个 666.

2.2.4 示例4:compose/andThen方法使用

public class FunctionTest {

    @Test
    public void test4() {
        // 使用该方法时  第一点先传递该方法的参数1     【需要处理的数据】
        //             第二点要传递该数据的处理条件  【如何处理】
        System.out.println(strInt("aaa", s -> s.length()));
        System.out.println(strInt("123", s -> Integer.parseInt(s)));
        System.out.println("----------------------------------------------------");

        Function<String, String> fun1 = s -> "hello" + s;
        Function<String, Integer> fun2 = s -> s.length();
        //将 fun1 和 fun2 两种方式结合到一起
        Function<String, Integer> fun3 = fun1.andThen(fun2);
        //接收参数,之后,先使用fun1的方式处理,然后在使用fun2的方式处理,最终处理结果是返回值
        //将 abc 参数先通过 fun1 方式处理,返回值 helloabc。然后交给 fun2 方式处理 返回个数:8
        System.out.println(fun3.apply("abc"));
    }

    //定义一个方法 ,该方法可以接收一个字符串,返回该字符串的个数, 将该字符串转为对应的整数返回
    public static int strInt(String s, Function<String, Integer> fun) {
        return fun.apply(s);
    }
}

image-1673344264034

public void test(){
    Function<Integer,Integer> A=i->i+1;
    Function<Integer,Integer> B=i->i*i;
    System.out.println("F1:"+B.apply(A.apply(5)));
    System.out.println("F1:"+B.compose(A).apply(5));
    System.out.println("F2:"+A.apply(B.apply(5)));
    System.out.println("F2:"+B.andThen(A).apply(5));
}
/** F1:36 */
/** F1:36 */
/** F2:26 */
/** F2:26 */

我们可以看到上述两个方法的返回值都是一个Function,这样我们就可以使用建造者模式的操作来使用。

B.compose(A).compose(A).andThen(A).apply(5);

3、消费型接口Consumer

Consumer接口也称为消费型接口,它消费的数据的数据类型由泛型指定。

  • 接口名称:Consumer
  • 抽象方法:void accept(T t):对给定的参数执行此操作,消费一个参数数据。
  • 概述:该接口中的方法可以接收一个参数,接收的参数类型由泛型指定,对参数的操作方式根据该接口的实现类决定,不需要返回值。
  • 拓展的非抽象方法:default Consumer andThen(Consumer<? super T> after) :返回一个组合的 Consumer ,按顺序执行该操作,然后执行 after操作。

示例1:

import java.util.function.Consumer;

/**
 * 消费型接口Consumer
 *
 * @Author kongzi
 * @Date 2023/1/11 08:35
 * @Version 1.0
 */
public class ConsumerTest {
    @Test
    public void test1() {
        System.out.println("1、-----------------------------------");
        //想要打印的参数
        printNum("aaa", s -> System.out.println(s));

        System.out.println("2、-----------------------------------");
        //想要打印该参数字符的个数
        printNum("aaa", s -> System.out.println(s.length()));

        System.out.println("3、-----------------------------------");
        //将参数和其他字符串拼接
        printNum("aaa", s -> System.out.println("999" + s));

        System.out.println("4、-----------------------------------");
        Consumer<String> s1 = s -> System.out.println(s);
        Consumer<String> s2 = s -> System.out.println(s.length());
        //将s1 和 s2 结合到一起
        s1.andThen(s2).accept("aaa");
    }

    //方法的功能:打印参数
    // 打印好处:
    //          如果方法要使用一个参数,具体参数的使用方式不确定,或者有很多种使用方式
    //          可以将该参数交给消费型接口去完成该参数的使用
    //          后续使用该方法时,不仅要传递实际参数,还要传递消费型接口的实现类对象
    //          消费型接口的对象如何定义: 根据使用的具体需求来定义
    public static void printNum(String str, Consumer<String> con) {
        con.accept(str);
    }
}

/*
1、-----------------------------------
aaa
2、-----------------------------------
3
3、-----------------------------------
999aaa
4、-----------------------------------
aaa
3
*/

示例2:

import java.util.function.Consumer;

/**
 * 消费型接口Consumer
 *
 * @Author kongzi
 * @Date 2023/1/11 08:35
 * @Version 1.0
 */
public class ConsumerTest {
    @Test
    public void test2() {
        String[] arr = {"唐青枫,20", "曲无忆,21", "离玉堂,22", "叶知秋,23"};
        printInfo(arr,
                (String str) -> {
                    String name = str.split(",")[0];
                    System.out.print("姓名:" + name);
                },
                (String str) -> {
                    int age = Integer.parseInt(str.split(",")[1]);
                    System.out.println(",年龄:" + age);
                });
    }

    private static void printInfo(String[] arr, Consumer<String> con1, Consumer<String> con2) {
        for (int i = 0; i < arr.length; i++) {
            con1.andThen(con2).accept(arr[i]);
        }
    }
}

/*
姓名:唐青枫,年龄:20
姓名:曲无忆,年龄:21
姓名:离玉堂,年龄:22
姓名:叶知秋,年龄:23
*/

4、供给型接口Supplier

Supplier接口也称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据供我们使用。

  • 接口名:Supplier
  • 抽象方法:T get():该方法不需要参数,它会按照某种逻辑(由Lambda表达式实现),返回一个具体的数据
  • 概述:该接口也被称为生产型接口,如果指定了泛型是什么类型,那类中的get方法就会返回一个该类型的一个具体数据。返回的数据,由该接口的实现类对象决定。

示例1:

import org.junit.Test;

import java.util.ArrayList;
import java.util.Random;
import java.util.function.Supplier;

/**
 * 供给型接口Supplier
 *
 * @Author kongzi
 * @Date 2023/1/11 08:45
 * @Version 1.0
 */
public class SupplierTest {

    
    @Test
    public void test1() {
//        String s=getString(()->{
//           return "唐青枫";
//        });
        String s = getString(() -> "唐青枫");
        System.out.println(s);

        Integer i = getInteger(() -> 10);
        System.out.println(i);
    }

    private static String getString(Supplier<String> sup) {
        return sup.get();
    }

    private static Integer getInteger(Supplier<Integer> sup) {
        return sup.get();
    }
}

/*
唐青枫
10
*/

示例2:

import org.junit.Test;

import java.util.ArrayList;
import java.util.Random;
import java.util.function.Supplier;

/**
 * 供给型接口Supplier
 *
 * @Author kongzi
 * @Date 2023/1/11 08:45
 * @Version 1.0
 */
public class SupplierTest {

    @Test
    public void test2() {
        //向集合中添加5个1-100之间的随机数并输出
        System.out.println(getList(() -> new Random().nextInt(100) + 1));
        //向集合中添加5个1-20之间的随机数并输出
        System.out.println(getList(() -> new Random().nextInt(20) + 1));
    }

    //定义一个方法:该方法可以返回一个Integer集合,该集合中的整数范围是:1-100,集合中右五个数据
//              如果需要获取一个数据,但是返回的该数据不确定是谁
//              可以使用供给型接口来使用该数据
//              后续使用该方法时,需要根据调教来给出供给型接口的实现类对象(可以Lambda来实现)
//              使用条件是谁,就怎么定义接口的实现类对象
    public static ArrayList<Integer> getList(Supplier<Integer> sup) {
        Random r = new Random();
        ArrayList<Integer> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            list.add(sup.get());
        }
        return list;
    }
}

/*
[6, 79, 42, 29, 89]
[8, 18, 17, 4, 18]
*/

示例3:

import org.junit.Test;

import java.util.ArrayList;
import java.util.Random;
import java.util.function.Supplier;

/**
 * 供给型接口Supplier
 *
 * @Author kongzi
 * @Date 2023/1/11 08:45
 * @Version 1.0
 */
public class SupplierTest {

    @Test
    public void test3() {
        int[] arr = {1, 9, 2, 7, 5};
        int maxValue = getMax(() -> {
            int max = arr[0];
            for (int i = 1; i < arr.length; i++) {
                if (arr[i] > max) {
                    max = arr[i];
                }
            }
            return max;
        });
        System.out.println(maxValue);
    }

    private static int getMax(Supplier<Integer> sup) {
        return sup.get();
    }
}

/*
9
*/

5、断言型接口Predicate

Predicate接口通常用于判断参数是否满足指定的条件。

  • 接口名:Predicate
  • 抽象方法:boolean test(T t):对数据做出指定的判断
  • 概述:该接口是一个判断接口,接口可以接收一个指定泛型的参数,并根据该接口的实现类对象对该参数做出对应的判断,返回值为boolean类型
  • 拓展的非抽象方法:default Predicate<T> negate():取反。返回一个逻辑的否定,对应逻辑非。
  • default Predicate<T> and(Predicate other):先将参数通过调用者判断真假,再将参数通过p判断真假,全真为真,否则为假。返回一个组合判断,对应短路与。
  • default Predicate<T> or(Predicate other):全假为假,否则为真。返回一个组合判断,对应短路或。

示例1:

import org.junit.Test;

import java.util.ArrayList;
import java.util.function.Predicate;

/**
 * 断言型接口
 *
 * @Author kongzi
 * @Date 2023/1/11 08:53
 * @Version 1.0
 */
public class PredicateTest {

    @Test
    public void test1() {
        ArrayList<String> st = new ArrayList<>();
        st.add("aac");
        st.add("bbc");
        st.add("ccc");

        //在使用该方法时,不仅需要给出需要处理的参数,也要给出判断条件
        //一旦条件给定之后,将来判断的结果就会随着条件的不同而改变
        System.out.println(is(st, s -> s.startsWith("a")));
        System.out.println(is(st, s -> s.length() == 2));
        System.out.println("--------------------------------------------");
        Predicate<Integer> pre1 = s -> s >= 10 && s <= 100;
        Predicate<Integer> pre2 = s -> s >= 20 && s <= 80;
        //连接两个判断方式:全真为真,否则为假 ------ &
        System.out.println(pre1.and(pre2));
        //连接两个判断方式:全假为假,否则为真 ------ |
        System.out.println(pre1.or(pre2));
        //对某个结果取反
        System.out.println(pre1.negate());
    }

    //定义一个方法:  接受一个集合,判断该集合中的内容是否以a开头,如果全部都是,则返回真,否则返回假
    //                         判断字符串每个内容,长度是否满足都为2
    //             如果需要对数据进行判断,但是判断的规则不同,可以将需要判断的数据交给断言型接口去做
    public static boolean is(ArrayList<String> list, Predicate<String> pre) {
        for (String s : list) {
            if (!pre.test(s)) {
                return false;
            }
        }
        return true;
    }
}

/*
false
false
--------------------------------------------
java.util.function.Predicate$$Lambda$5/88579647@66133adc
java.util.function.Predicate$$Lambda$6/2080166188@42f30e0a
java.util.function.Predicate$$Lambda$7/1528637575@1c2c22f3
*/

示例2:

import org.junit.Test;

import java.util.ArrayList;
import java.util.function.Predicate;

/**
 * 断言型接口
 *
 * @Author kongzi
 * @Date 2023/1/11 08:53
 * @Version 1.0
 */
public class PredicateTest {

    @Test
    public void test2() {
        //Lambda表达式
//        boolean b=checkString("Hello",(String str)->{
//            return str.length()>8;
//        });
        boolean b = checkString("Hello", str -> str.length() > 8);
        System.out.println(b);
        System.out.println("****************************");
        b = checkString1("Hello", str -> str.length() > 8);
        System.out.println(b);
    }

    private static boolean checkString(String str, Predicate<String> pre) {
        return pre.test(str);
    }

    private static boolean checkString1(String str, Predicate<String> pre) {
//        return !pre.test(str);
        //上一句等价于
        return pre.negate().test(str);
    }
}

/*
false
****************************
true
*/

示例3:

import org.junit.Test;

import java.util.ArrayList;
import java.util.function.Predicate;

/**
 * 断言型接口
 *
 * @Author kongzi
 * @Date 2023/1/11 08:53
 * @Version 1.0
 */
public class PredicateTest {

    @Test
    public void test3() {
        boolean b1 = checkString("hello", s -> s.length() > 8, s -> s.length() < 15);
        boolean b2 = checkString("helloworld", s -> s.length() > 8, s -> s.length() < 15);
        System.out.println(b1);
        System.out.println(b2);
    }

    private static boolean checkString(String str, Predicate<String> pre1, Predicate<String> pre2) {
//        boolean b1=pre1.test(str);
//        boolean b2=pre2.test(str);
//        boolean b=b1 && b2;
//        return b;
        //上述等价于
        return pre1.and(pre2).test(str);//判断两个条件和

        //对应的有
//      return pre1.or(pre2).test(str);//判断两个条件或
    }
}

/*
false
true
*/

示例4:筛选满足条件的数据: 姓名长度>2且年龄>20

import org.junit.Test;

import java.util.ArrayList;
import java.util.function.Predicate;

/**
 * 断言型接口
 *
 * @Author kongzi
 * @Date 2023/1/11 08:53
 * @Version 1.0
 */
public class PredicateTest {

    // 筛选满足条件的数据: 姓名长度>2且年龄>20
    @Test
    public void test4() {
        String[] arr = {"唐青枫,20", "慕晴,21", "曲无忆,22", "孔雀,23", "离玉堂,25"};
        //姓名长度>2且年龄>20
        ArrayList<String> arrayList = myFilter(arr, str -> str.split(",")[0].length() > 2,
                str -> Integer.parseInt(str.split(",")[1]) > 20);
        for (String str : arrayList) {
            System.out.println(str);
        }
    }

    private static ArrayList<String> myFilter(String[] arr, Predicate<String> pre1, Predicate<String> pre2) {
        ArrayList<String> arrayList = new ArrayList<String>();
        for (int i = 0; i < arr.length; i++) {
            if (pre1.and(pre2).test(arr[i])) {
                arrayList.add(arr[i]);
            }
        }
        return arrayList;
    }
}

/*
曲无忆,22
离玉堂,25
*/

5、自定义函数式接口

5.1 自定义函数式接口

package com.kz.example.common.jdk8.function;

/**
 * 自定义函数式接口
 *
 * @Author kongzi
 * @Date 2023/1/11 08:35
 * @Version 1.0
 */
@FunctionalInterface
public interface MyFunctionalInterface {
    // 定义一个抽象方法
    void method();
}

5.2 自定义函数式实现类

package com.kz.example.common.jdk8.function;

/**
 * 自定义函数式接口实现类
 *
 * @Author kongzi
 * @Date 2023/1/11 09:08
 * @Version 1.0
 */
public class MyFunctionalInterfaceImpl implements MyFunctionalInterface {
    @Override
    public void method() {
        System.out.println("重写了接口中的抽象方法");
    }
}

5.3 自定义函数式接口测试类

package com.kz.example.common.jdk8.function;

import org.junit.Test;

/**
 * 自定义函数式接口测试类
 *
 * @Author kongzi
 * @Date 2023/1/11 09:10
 * @Version 1.0
 */
public class MyFunctionalInterfaceTest {
    public static void show(MyFunctionalInterface myInter) {
        myInter.method();
    }

    @Test
    public void test1() {
        //调用show方法,方法的参数是一个接口,可以传递接口的实现类对象
        show(new MyFunctionalInterfaceImpl());

        //调用show方法,方法的参数是一个接口,可以传递接口的匿名内部类
        show(new MyFunctionalInterface() {
            @Override
            public void method() {
                System.out.println("使用匿名内部类重写接口中的抽象方法");
            }
        });

        //调用show方法,方法的参数是一个接口,可以使用Lambda表达式
        show(() -> {
            System.out.println("使用Lambda表达式重写接口中的抽象方法");
        });

        //简化Lambda表达式
        show(() -> System.out.println("使用简化的Lambda表达式重写接口中的抽象方法"));
    }
}

测试结果:

重写了接口中的抽象方法
使用匿名内部类重写接口中的抽象方法
使用Lambda表达式重写接口中的抽象方法
使用简化的Lambda表达式重写接口中的抽象方法

5.4 自定义函数式接口及测试类

/**
 * 抛异常接口
 **/
@FunctionalInterface
public interface ThrowExceptionFunction {
    /**
     * 抛出异常信息
     *
     * @param message 异常信息
     * @return void
     **/
    void throwMessage(String message);
}
import org.junit.Test;

/**
 * 创建一个isTure方法,方法的返回值为函数式接口-ThrowExceptionFunction
 *
 * @Author kongzi
 * @Date 2023/1/11 09:28
 * @Version 1.0
 */
public class ThrowExceptionFunctionTest {

    /**
     * 如果参数为true抛出异常
     *
     * @param b
     * @return com.kz.example.common.jdk8.function.ThrowExceptionFunction
     **/
    public static ThrowExceptionFunction isTure(boolean b) {

        return (errorMessage) -> {
            if (b) {
                throw new RuntimeException(errorMessage);
            }
        };
    }

    @Test
    public void test1() {
        ThrowExceptionFunctionTest.isTure(true).throwMessage("我要抛出异常拉");
    }
}

测试结果:

java.lang.RuntimeException: 我要抛出异常拉

	at com.kz.example.common.jdk8.function.ThrowExceptionFunctionTest.lambda$isTure$0(ThrowExceptionFunctionTest.java:24)
	at com.kz.example.common.jdk8.function.ThrowExceptionFunctionTest$$Lambda$1/1709537756.throwMessage(Unknown Source)
	at com.kz.example.common.jdk8.function.ThrowExceptionFunctionTest.test1(ThrowExceptionFunctionTest.java:31)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
	at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
	at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)

6、Lambda的延迟执行

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

6.1 性能浪费的日志案例

日志可以帮助我们快速的定位问题,记录程序运行过程中的情况,以便项目的监控和优化。


public class DemoLogger {
    //定义一个根据日志的等级,显示日志信息的方法
    public static void showlog(int level, String message) {
        //对日志等级判断,等于1时输出日志信息
        if(level == 1) {
            System.out.println(message);
        }
    }

    public static void main(String[] args) {
        //定义三个日志信息
        String msg1 =  "Hello";
        String msg2 =  "World";
        String msg3 =  "Java";
        showlog(1,msg1+msg2+msg3);
    }
}

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

6.2 Lambda优化日志案例

public class Demo02Lambda {
    public static void showlog(int level, MessageBuilder mb) {
        if(level == 1) {
            System.out.println(mb.builderMessage());
        }
    }

    public static void main(String[] args) {
        String msg1 =  "Hello";
        String msg2 =  "World";
        String msg3 =  "Java";
        showlog(1,()->{
        	System.out.println("Lambda执行!");
            return msg1+msg2+msg3;
        });
    }
}

使用 Lambda 表达式作为参数传递时,仅仅是把参数传递到showlog方法中,只有满足日志等级是1级的条件,才会调用接口MessageBuilder中的方法builderMessage,再进行字符串拼接,如果不满足日志等级为1,接口MessageBuilder中的方法就不会执行,拼接字符串的代码也不会执行,所以就不会存在性能上的浪费。

image-1673399962504

image-1673399980620

实际上使用内部类也可以达到同样的效果,只是将代码操作延迟到了另外一个对象当中通过调用方法来完成。而是否调用其所在方法是在条件判断之后才执行的。

7、使用Lambda作为参数和返回值

例如 java.lang.Runnable 接口就是一个函数式接口,假设有一个 startThread 方法使用该接口作为参数,那么就可以使用Lambda进行传参。这种情况其实和 Thread 类的构造方法参数为 Runnable 没有本质区别。

public class Demo03Runnable {
    public static void startThread(Runnable r) {
        new Thread(r).start();
    }

    public static void main(String[] args) {
        startThread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了。");
            }
        });

        startThread(()-> System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了。"));
    }
}

image-1673400122586

如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。当需要通过一个方法来获取一个 java.util.Comparator 接口类型的对象作为排序器时,就可以调该方法获取。

import java.util.Arrays;
import java.util.Comparator;

public class Demo04Comparator {
    public static Comparator<String> getComparator() {
//        return new Comparator<String>() {
//            @Override
//            public int compare(String o1, String o2) {
//                return o2.length()-o1.length();
//            }
//        };

//        return (String o1, String o2)->{
//            return o2.length()-o1.length();
//        };

        return (o1,o2)->o2.length()-o1.length();
    }

    public static void main(String[] args) {
        String[] arr = {"aaa","b","cccccc","ddddddddd"};
        System.out.println("排序前:"+Arrays.toString(arr));
        Arrays.sort(arr,getComparator());
        System.out.println("排序后:"+Arrays.toString(arr));
    }
}
0

评论区