函数式接口

595 阅读6分钟

1、函数式接口的概念

函数式接口在java中是指:有且仅有一个抽象方法的接口,当然接口中也可以包含其他的方法(默认,静态,私有)。 函数式接口的定义:

@FunctionalInterface
public interface MyFunctionalInterface {
    //定义一个抽象方法
    public abstract void method();

}
  • 接口中的抽象方法的public abstract可以省略。
  • @FunctionalInterface可以检测接口是否为函数式接口(是编译成功;否编译失败-接口中没有抽象方法或抽象方法个数多于一个)。

2.函数式接口的使用

2.1、作为方法的参数使用

public class Demo01 {
    public static void show(MyFunctionalInterface myFunctionalInterface){
        myFunctionalInterface.method();
    }
    public static void main(String[] args) {
        //调用show方法,方法的参数是一个接口,所以可以传递接口的匿名内部类
        show(new MyFunctionalInterface() {
            @Override
            public void method() {
                System.out.println("匿名内部类重写接口中的抽象方法");
            }
        });
        //调用show方法,方法的参数是一个函数式接口,所以我们可以使用lambda表达式
        show(()->{
            System.out.println("使用lambda表达式重写接口中的抽象方法");
        });
        //简化lambda表达式
        show(()-> System.out.println("使用lambda表达式重写接口中的抽象方法"));
    }
}

2.2、lambda延迟执行

2.2.1、性能浪费的日志案例

public class Demo02 {
    public static void showLog(int level, String log){
        if(level == 1){
            System.out.println(log);
        }
    }

    public static void main(String[] args){
        String msg1 = "hello";
        String msg2 = "world";
        String msg3 = "java";
        showLog(2, msg1 + msg2 + msg3);
    }
}

以上代码存在一些性能浪费的问题,调用showLog方法,是先把字符串拼接好,然后再调用showLog方法,如果传入的日志等级参数不是1,则不会输出拼接口的字符串,因此就白拼接了,存在浪费。

2.2.2、日志优化

@FunctionalInterface
public interface MessageBuilder {
    //定义一个拼接消息的抽象方法,返回拼接后的字符串
    public abstract String builderMessage();
}
public class Demo03Lambda {
    public static void showLog(int level, MessageBuilder mBuilder){
        if(level == 1) {
            System.out.println(mBuilder.builderMessage());
        }
    }
    public static void main(String[] args){
        String msg1 = "hello";
        String msg2 = "world";
        String msg3 = "java";
        /**
         * 使用lambda表达式作为参数传递,仅仅是把参数传递给showLog方法中,
         * 只有满足条件,日志的等级是1,才会调用接口MessageBuilder中的方法,
         * 才会进行字符串的拼接
         */

        showLog(1, ()->  msg1 + msg2 + msg3);
    }
}

2.3、函数式接口作为函数的参数案例

假设有一个startThread方法使用Runnable接口作为参数

public class Demo04Lambda {
    //定义一个方法startThread,方法的参数使用函数式接口Runnable
    public static void startThread(Runnable runnable){
        new Thread(runnable).start();
    }

    public static void main(String[] args){
        //方法的参数是一个接口,可以传递这个接口的匿名内部类
        startThread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });
        //方法的参数是一个函数式接口,可以传递lambda表达式
        startThread(()-> System.out.println(Thread.currentThread().getName()));
    }
}

2.4、函数式接口作为函数的返回值案例

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

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

    public static void main(String[] args){
        //创建一个字符串数组
        String[] arr = {"a", "bbb", "cc", "dddd"};
        //输出排序前的数组 [a, bbb, cc, dddd]
        System.out.println(Arrays.toString(arr));
        //调用Arrays中的sort方法,对字符串数组进行排序
        Arrays.sort(arr, getComparator());
        //输出排序后的数组 [dddd, bbb, cc, a]
        System.out.println(Arrays.toString(arr));
    }
}

3、常用的函数式接口

3.1、Supplier

java.util.function.Supplier接口仅包含一个无参的方法,:T get()。用来获取一个泛型参数执行类型的数据。该接口被称为生产型接口,指定接口的泛型是什么类型,get方法就会生产什么类型的数据。

public class Demo06Lambda {
    //定义一个方法,方法的参数传递Supplier<T>接口,泛型指定String,get方法就会返回一个String
    public static String getString(Supplier<String> supplier){
        return supplier.get();
    }

    public static void main(String[] args) {
        String s = getString(()-> "赵丽颖");
        System.out.println(s);
    }
}

3.1.2、求数组元素最大值案例

使用Supplier接口作为方法参数类型,通过lambda表达式获取数组中的最大值。

public class Demo07Lambda {
    //定义一个方法,获取int类型数组中元素最大值,方法的参数传递Supplier接口,泛型使用Integer
    public static int getMax(Supplier<Integer> supplier) {
        return supplier.get();
    }
    public static void main(String[] args) {
        int[] arr = {1, 4, 211, 6, 8, 11, 111};
        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);
    }
}

3.2、Consumer

java.util.function.Consumer接口与Supplier接口相反,它消费数据,数据类型由泛型执行,接口中只有一个抽象方法accept(T t),表示消费一个指定泛型的数据。 定义一个方法,参数传递Consumer接口,泛型使用String,Consumer接口进行消费字符串。

public class Demo08Lambda {
    public static void consumerStr(String name, Consumer<String> consumer) {
        consumer.accept(name);
    }

    public static void main(String[] args) {
        //调用consumerStr方法,传递字符串姓名,方法的另一个参数是一个函数式接口,可以传递lambda表达式
        consumerStr("古天乐", name -> {
            //消费方式:把字符串进行反转
            String revName = new StringBuffer(name).reverse().toString();
            System.out.println(revName);
        });
    }
}

3.2.1、默认方法andThen

可以把两个Consumer接口组合到一起,再对数据进行消费。

public class Demo09Lambda {
    public static void consumerStr(String s, Consumer<String> con1, Consumer<String> con2){
        //con1.accept(s);
        //con2.accept(s);
        //使用andThen方法,把两个Consumer接口连接到一起再消费
        con1.andThen(con2).accept(s);//con1连接con2,先执行con1消费数据,再执行con2消费数据
    }
    
    public static void main(String[] args) {
        consumerStr("Test", s -> System.out.println(s.toLowerCase()), s -> System.out.println(s.toUpperCase()));
    }
}

3.2.1.1、格式化打印案例

字符串数组中存在多条信息,按照"姓名":xx,"性别":xx,打印数据。

public class Demo10Lambda {
    public static void formatString(String s, Consumer<String> con1, Consumer<String> con2){
        con1.andThen(con2).accept(s);
    }
    public static void main(String[] args){
        String[] arrs = {"张三,男","李四,男", "王五,女"};
        for (String arr: arrs){
            formatString(arr, s->{
               String name = s.split(",")[0];
               System.out.print("姓名:"+name);
            }, s->{
                String sex = s.split(",")[1];
                System.out.print(",性别:"+sex);
            });
            System.out.println();
        }
    }
}

3.3、Predicate

对某种数据类型的数据进行判断,结果返回一个boolean值,该接口包含一个抽象方法:test(T t),用来对指定数据类型的数据进行判断,符合条件返回true;不符合条件,返回false。

public class Demo11Lambda {
    public static boolean checkString(String s, Predicate<String> predicate){
        return predicate.test(s);
    }

    public static void main(String[] args) {
        String s = "abcdfges";
        boolean isGreaterThanFive = checkString(s, str -> str.length()>5);
        System.out.println(isGreaterThanFive);
    }
}

3.3.1、默认方法and

Predicate接口中有一个方法and,表示并且关系,用于连接两个判断条件,两个判断条件均为true时,记结果为true。

public class Demo12Lambda {
    public static boolean checkString(String s, Predicate<String> pre1, Predicate<String> pre2){
        return pre1.and(pre2).test(s);
    }

    public static void main(String[] args) {
        String s = "abcdewesdf";
        boolean isGreaterThanFiveAndContainsA = checkString(s, str->s.length() > 5, str-> s.contains("a"));
        System.out.println(isGreaterThanFiveAndContainsA);
    }
}

3.3.2、默认方法or

Predicate接口中有一个方法and,表示并且关系,用于连接两个判断条件,两个判断条件有一个为true时,记结果为true。

public class Demo12Lambda {
    public static boolean checkString(String s, Predicate<String> pre1, Predicate<String> pre2){
        return pre1.or(pre2).test(s);
    }

    public static void main(String[] args) {
        String s = "aaa";
        boolean isGreaterThanFiveAndContainsA = checkString(s, str->s.length() > 5, str-> s.contains("a"));
        System.out.println(isGreaterThanFiveAndContainsA);
    }
}

4、Function

java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一数据类型的数据,前者称为前置条件,后者称为后置条件,Function接口中最重要的抽象方法为R apply(T t),根据类型T的参数返回R类型的数据。

4.1、将String类型转换为Integer类型

public class Demo13Lambda {
    public static int changeString2Int(String num, Function<String, Integer> function){
        return function.apply(num);
    }

    public static void main(String[] args) {
        int num = changeString2Int("14", str-> Integer.parseInt(str));
        System.out.println(num);
    }
}

4.2、默认方法andThen

可以把两个Function接口组合到一起,再对数据进行处理。

public class Demo14Lambda {
    public static void change(String s, Function<String, Integer> fun1, Function<Integer, String> fun2) {
        String s1 = fun1.andThen(fun2).apply(s);
        System.out.println(s1);
    }

    public static void main(String[] args) {
        change("123", s -> Integer.parseInt(s), num -> num + "");
    }
}