文章目录

  • 函数式接口
  • 一、函数式接口
  • 1. **概念**
  • 2. 函数式接口的使用
  • 二、函数式编程
  • 1. Lambda的延迟执行
  • 2. 性能浪费的日志案例
  • 3. 使用Lambda表达式优化日志案例
  • 三、常用函数式接口
  • 1. Supplier接口
  • 2. Consumer接口
  • 3. Predicate接口
  • 4. Function接口


函数式接口

一、函数式接口

1. 概念

  • **函数式接口:**有且仅有一个抽象方法的接口,称之为函数式接口。
  • 接口中可以包含其他方法:默认、静态、私有。
  • @FunctionalInterface注解
  • 作用:可以检测接口是否是一个函数式接口
  • 是:编译成功
  • 否:编译失败(接口中没有抽象方法或抽象方法的个数 )
  • 格式:
@FunctionalInterface
public interface MyFunctionalInterface {
  //定义一个抽象方法
  public abstract void method();
}

2. 函数式接口的使用

  • 一般可作为方法的参数和返回值类型。
//接口
public interface MyFunctionalInterface {
    public abstract void method();
}

//接口的实现类
public class MyFunctionalInterfaceImpl implements MyFunctionalInterface {
    @Override
    public void method() {
        System.out.println("使用接口的实现类重写接口的方法");
    }
}

//测试类
public class Test {
    //函数式接口作为方法的参数
    public static void show(MyFunctionalInterface myFunctionalInterface) {
        myFunctionalInterface.method();
    }

    public static void main(String[] args) {
        //使用接口的实现类作为参数
        show(new MyFunctionalInterfaceImpl());

        //使用匿名内部类作为参数
        show(new MyFunctionalInterface() {
            @Override
            public void method() {
                System.out.println("使用匿名内部类重写接口的方法");
            }
        });

        //使用lambda表达式作为参数
        show(()->{
            System.out.println("使用lambda表达式重写接口的方法");
        });

        //使用简化的lambda表达式作为参数
        show(()-> System.out.println("使用简化的lambda表达式重写接口的方法"));
    }
}

二、函数式编程

1. Lambda的延迟执行

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

2. 性能浪费的日志案例

/*
  以下代码存在一些性能浪费的问题:
  调用showLog方法,传递的第二个参数是一个拼接好的字符串,
  先把字符串拼接好,然后调用showLog方法,
  如果showLog方法中第一个参数传递的日志等级不是1级,
  那么就不会输出拼接后的字符串,
  这样字符串就白白拼接了,造成了性能浪费。
*/

public class Logger {
    public static void showLog(int level, String message) {
        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);
        showLog(2,msg1+msg2+msg3);
    }
}

3. 使用Lambda表达式优化日志案例

  • Lambda的特点:延迟执行。
  • Lambda的使用前提:必须存在函数式接口。
/*
	使用Lambda表达式作为参数传递,仅仅是把参数传递到showLog方法中,
	只有满足条件(日志的等级为1),
    才会调用接口MessageBuilder中的方法builderMessage,
    才会进行字符串的拼接;
  如果条件不满足(日志的等级不为1),
  	那么接口MessageBuilder中的方法builderMessage不会执行,
  	所以拼接字符串的代码也不会执行;
  所以不会存在性能的浪费。
*/

public class Lambda {
    public static void showLog(int level, MessageBuilder message) {
        if (level == 1) {
            System.out.println(message.builderMessage());
        }
    }

    public static void main(String[] args) {
        String msg1 = "Hello";
        String msg2 = "World";
        String msg3 = "Java";

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

        showLog(2,()->{
            return msg1+msg2+msg3;
        });
    }
}

三、常用函数式接口

1. Supplier接口

  • java.util.function.Supplier<T>:接口仅包含一个无参的方法T get(),用来获取一个泛型参数指定类型的对象数据。
  • Supplier<T>接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据。
import java.util.function.Supplier;

public class Test {
    //生产一个String类型的数据
    public static String getString(Supplier<String> supplier) {
        return supplier.get();
    }

    public static void main(String[] args) {
        //使用Lambda表达式
        String str1 = getString(()->{
            return "Hello,World!";
        });
        System.out.println(str1);
        
        //使用简化的Lambda表达式
        String str2 = getString(()->"Hello,World!");
        System.out.println(str2);

    }
}
  • 练习:求数组元素最大值
/*
	使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值
*/
import java.util.function.Supplier;

public class Test {
    public static int getMax(Supplier<Integer> supplier) {
        return supplier.get();
    }

    public static void main(String[] args) {
        int[] arr = {1,-5,7,2,10,3};

        int maxValue = getMax(()->{
            int max = arr[0];
            for (int i = 1; i < arr.length; i++) {
                if (max < arr[i]) {
                    max = arr[i];
                }
            }
            return max;
        });
        System.out.println(maxValue);
    }
}

2. Consumer接口

  • java.util.function.Consumer<T>接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。
  • Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据。至于如何消费(使用),需要自定义。
import java.util.function.Consumer;

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

    public static void main(String[] args) {
        //输出字符串
        method("zhuguli",(String name)->{
            System.out.println(name); // zhuguli
        });

        //翻转字符串
        method("zhuguli",(String name)->{
            String str = new StringBuffer(name).reverse().toString();
            System.out.println(str); // iluguhz
        });
    }
}
  • 默认方法:andThen
  • 作用:需要两个或多个Consumer接口,可以把两个或多个Consumer接口组合到一起,再对数据进行消费。
  • 例如:
Consumer<String> consumer1;
Consumer<String> consumer2;
String str;
consumer1.accept(str);
consumer2.accept(str);

//使用andThen
consumer1.andThen(consumer2).accept(str);
  • consumer1.andThen(consumer2).accept(str);谁写在前边谁先消费。
import java.util.function.Consumer;

public class Test {
    public static void method(String str, Consumer<String> consumer1, Consumer<String> consumer2){
        consumer1.andThen(consumer2).accept(str);
    }

    public static void main(String[] args) {
        String str = "Hello";
        method(str,(String s)->{
            System.out.println(s.toUpperCase()); // HELLO
        },(String s)->{
            System.out.println(s.toLowerCase()); // hello
        });
    }
}
  • 练习:格式化打印信息
/*
	字符串数组中存有多条信息,请按照格式”姓名:xx,年龄:xx。“的格式将信息打印出来。
	要求:
		将打印姓名的动作作为第一个Consumer接口的Lambda实例;
		将打印年龄的动作作为第二个Consumer接口的Lambda实例;
		将两个Consumer接口按照顺序”拼接“到一起。
*/
import java.util.function.Consumer;

public class Test {
    public static void printInfo(String[] arr, Consumer<String> consumer1, Consumer<String> consumer2){
        for (String str : arr) {
            consumer1.andThen(consumer2).accept(str);
        }
    }

    public static void main(String[] args) {
        String[] arr = {"朱古力,18", "猪猪侠,20", "猪猪猪,22"};
        printInfo(arr, (String str)->{
            String name = str.split(",")[0];
            System.out.print("姓名:" + name + ",");
        }, (String str)->{
            String age = str.split(",")[1];
            System.out.println("年龄:" + age + "。");
        });
    }
}

3. Predicate接口

  • java.util.function.Predicate<T>接口
  • 作用:对某种数据类型的数据进行判断,结果返回一个boolean值。
  • 抽象方法:boolean test(T t)
  • 作用:用来对指定的数据类型数据进行判断的方法。
  • 结果:
  • 符合条件,返回true
  • 不符合条件,返回false
import java.util.function.Predicate;

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

    public static void main(String[] args) {
        //检测字符串的长度是否大于3
        String str = "hello";
        boolean bool = checkString(str,(String string)->{
            return string.length() > 3;
        });
        System.out.println(bool);  // true
    }
}
  • 默认方法:and
  • 作用:表示并且关系,用于连接两个判断条件
/*
	定义一个方法,方法的参数:
		一个字符串;
		一个用于判断字符串的长度是否大于5;
		一个用于判断字符串中是否包含a
		必须同时满足两个条件
*/
import java.util.function.Predicate;

public class Test02 {
    public static boolean checkString(String str, Predicate<String> pre1, Predicate<String> pre2){
        return pre1.and(pre2).test(str); // 等价于 return pre1.test(str) && pre2.test(str);
    }

    public static void main(String[] args) {
        String str = "abcdef";
        boolean bool = checkString(str, (String string)->{
            return string.length() > 5;
        }, (String string)->{
            return str.contains("a");
        });
        System.out.println(bool);   // true
    }
}
  • 默认方法:or
  • 作用:表示关系,用于连接两个判断条件
/*
	定义一个方法,方法的参数:
		一个字符串;
		一个用于判断字符串的长度是否大于5;
		一个用于判断字符串中是否包含a
		必须同时满足两个条件
*/
import java.util.function.Predicate;

public class Test03 {
    public static boolean checkString(String str, Predicate<String> pre1, Predicate<String> pre2){
        return pre1.or(pre2).test(str); // 等价于 return pre1.test(str) || pre2.test(str);
    }

    public static void main(String[] args) {
        String str = "bcdefg";
        boolean bool = checkString(str, (String string)->{
            return string.length() > 5;
        }, (String string)->{
            return str.contains("a");
        });
        System.out.println(bool);   // true
    }
}
  • 默认方法:negate
  • 作用:表示取反
/*
	定义一个方法,方法的参数:
		一个字符串;
		一个用于判断字符串的长度是否大于5;
*/
import java.util.function.Predicate;

public class Test04 {
    public static boolean checkString(String str, Predicate<String> pre){
        return pre.negate().test(str); // 等价于 return !pre1.test(str) ;
    }

    public static void main(String[] args) {
        String str = "bcdefg";
        boolean bool = checkString(str, (String string)->{
            return string.length() > 5;
        });
        System.out.println(bool);   // false
    }
}
  • 练习:集合信息筛选
/*
	数组当中有多条“姓名+性别”的信息如下:
	String[] array = {"迪丽热巴,女","古力娜扎,女","马尔扎哈,男","赵丽颖,女"};
	请通过Predicate接口的拼装将符合要求的字符串筛选到集合ArrayList中,
	需要同时满足两个条件:
		必须为女生
		姓名为四个字
*/
import java.util.ArrayList;
import java.util.function.Predicate;

public class Test {
    public static ArrayList<String> checkArray(String[] arr, Predicate<String> pre1, Predicate<String> pre2) {
        ArrayList<String> list = new ArrayList<>();
        for (String s : arr) {
            if (pre1.and(pre2).test(s)) {
                list.add(s);
            }
        }
        return list;
    }

    public static void main(String[] args) {
        String[] array = {"迪丽热巴,女","古力娜扎,女","马尔扎哈,男","赵丽颖,女"};

        ArrayList<String> list = checkArray(array,(String string)->{
            return string.split(",")[1].equals("女");
        },(String string)->{
            return string.split(",")[0].length() == 4;
        });

        for (String s : list) {
            System.out.println(s);
        }
    }
}

4. Function接口

  • java.util.function.Function<T,R>接口:用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
  • 抽象方法:R apply(T t),根据类型T的参数获取类型R的结果。
//将String类型转换为Integer类型
import java.util.function.Function;

public class Test01 {
    public static void change(String str, Function<String, Integer> func) {
        Integer in = func.apply(str);
        System.out.println(in + 10);     // 30
    }

    public static void main(String[] args) {
        String s = "20";
        change(s, (String str)->{
            return Integer.parseInt(s);
        });
    }
}
  • 默认方法:andThen用来进行组合操作。
/*
	把String类型的“123”,转换为Integer类型,把转换的结果加10;
	把增加之后的Integer类型的数据,转换为String类型
*/
import java.util.function.Function;

public class Test02 {
    public static void change(String str, Function<String,Integer> func1, Function<Integer,String> func2){
        String s = func1.andThen(func2).apply(str);
        System.out.println(s);   // "133"
    }

    public static void main(String[] args) {
        String str = "123";
        change(str,(String s)->{
            return Integer.parseInt(s) + 10;
        },(Integer i)->{
            return i + "";
        });
    }
}
  • 练习:自定义函数模型拼接
/*
	请使用Function进行函数模型的拼接,按照顺序需要执行的多个函数操作为:
		String str = "朱古力,22";
  1.将字符串截取数字部分,得到字符串;
  2.将上一步的字符串转换为int类型的数字;
  3.将上一步的int数字累加100,得到结果int数字。
*/
import java.util.function.Function;

public class Test {
    public static void change(String str, Function<String,String> func1,
                              Function<String,Integer> func2,
                              Function<Integer,Integer> func3 ) {
        int i = func1.andThen(func2).andThen(func3).apply(str);
        System.out.println(i);
    }

    public static void main(String[] args) {
        String str = "朱古力,22";
        change(str,(String s)->{
            return s.split(",")[1];
        },(String s)->{
            return Integer.parseInt(s);
        },(Integer i)->{
            return i + 100;
        });
    }
}