乐趣区

乐字节Java8核心特性实战之函数式接口

大家好,上一篇小乐给大家讲述了《乐字节 -Java8 核心特性 -Lambda 表达式》,点击回顾。接下来继续:Java8 核心特性之函数式接口。

什么时候可以使用 Lambda?通常 Lambda 表达式是用在函数式接口上使用的。从 Java8 开始引入了函数式接口,其说明比较简单: 函数式接口 (Functional Interface) 就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。java8 引入 @FunctionalInterface 注解声明该接口是一个函数式接口。

一、语法

  • 抽象方法有且仅有一个
  • 接口使用 @FunctionalInterface 注解进行标注
  • 接口中可以存在默认方法和静态方法实现

如下形式:

/**
 * 定义函数式接口
 * 接口上标注 @FunctionalInterface 注解
 */
@FunctionalInterface
public interface ICollectionService {
    /**
     * 定义打印方法
     */
    void print();}

在 Java8 以前,已有大量函数式接口形式的接口(接口中只存在一个抽象方法),只是没有强制声明。例如 java.lang.Runnable,java.util.concurrent.Callable,java.security.PrivilegedAction,java.io.FileFilter 等,Java8 新增加的函数接口在 java.util.function 包下,它包含了很多类,用来支持 Java 的 函数式编程,该包中的函数式接口如下:

序号 接口 & 描述
1 BiConsumer<T,U>代表了一个接受两个输入参数的操作,并且不返回任何结果
2 BiFunction<T,U,R>代表了一个接受两个输入参数的方法,并且返回一个结果
3 BinaryOperator<T>代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果
4 BiPredicate<T,U>代表了一个两个参数的 boolean 值方法
5 BooleanSupplier代表了 boolean 值结果的提供方
6 Consumer<T>代表了接受一个输入参数并且无返回的操作
7 DoubleBinaryOperator代表了作用于两个 double 值操作符的操作,并且返回了一个 double 值的结果。
8 DoubleConsumer代表一个接受 double 值参数的操作,并且不返回结果。
9 DoubleFunction<R>代表接受一个 double 值参数的方法,并且返回结果
10 DoublePredicate代表一个拥有 double 值参数的 boolean 值方法
11 DoubleSupplier代表一个 double 值结构的提供方
12 DoubleToIntFunction接受一个 double 类型输入,返回一个 int 类型结果。
13 DoubleToLongFunction接受一个 double 类型输入,返回一个 long 类型结果
14 DoubleUnaryOperator接受一个参数同为类型 double, 返回值类型也为 double。
15 Function<T,R>接受一个输入参数,返回一个结果。
16 IntBinaryOperator接受两个参数同为类型 int, 返回值类型也为 int。
17 IntConsumer接受一个 int 类型的输入参数,无返回值。
18 IntFunction<R>接受一个 int 类型输入参数,返回一个结果。
19 IntPredicate:接受一个 int 输入参数,返回一个布尔值的结果。
20 IntSupplier无参数,返回一个 int 类型结果。
21 IntToDoubleFunction接受一个 int 类型输入,返回一个 double 类型结果。
22 IntToLongFunction接受一个 int 类型输入,返回一个 long 类型结果。
23 IntUnaryOperator接受一个参数同为类型 int, 返回值类型也为 int。
24 LongBinaryOperator接受两个参数同为类型 long, 返回值类型也为 long。
25 LongConsumer接受一个 long 类型的输入参数,无返回值。
26 LongFunction<R>接受一个 long 类型输入参数,返回一个结果。
27 LongPredicate R 接受一个 long 输入参数,返回一个布尔值类型结果。
28 LongSupplier无参数,返回一个结果 long 类型的值。
29 LongToDoubleFunction接受一个 long 类型输入,返回一个 double 类型结果。
30 LongToIntFunction接受一个 long 类型输入,返回一个 int 类型结果。
31 LongUnaryOperator接受一个参数同为类型 long, 返回值类型也为 long。
32 ObjDoubleConsumer<T>接受一个 object 类型和一个 double 类型的输入参数,无返回值。
33 ObjIntConsumer<T>接受一个 object 类型和一个 int 类型的输入参数,无返回值。
34 ObjLongConsumer<T>接受一个 object 类型和一个 long 类型的输入参数,无返回值。
35 Predicate<T>接受一个输入参数,返回一个布尔值结果。
36 Supplier<T>无参数,返回一个结果。
37 ToDoubleBiFunction<T,U>接受两个输入参数,返回一个 double 类型结果
38 ToDoubleFunction<T>接受一个输入参数,返回一个 double 类型结果
39 ToIntBiFunction<T,U>接受两个输入参数,返回一个 int 类型结果。
40 ToIntFunction<T>接受一个输入参数,返回一个 int 类型结果。
41 ToLongBiFunction<T,U>接受两个输入参数,返回一个 long 类型结果。
42 ToLongFunction<T>接受一个输入参数,返回一个 long 类型结果。
43 UnaryOperator<T>接受一个参数为类型 T, 返回值类型也为 T。

对于 Java8 中提供的这么多函数式接口,开发中常用的函数式接口有以下几个 Predicate,Consumer,Function,Supplier。

二、函数式接口实例

1、Predicate

java.util.function.Predicate<T> 接口定义了一个名叫 test 的抽象方法,它接受泛型 T 对象,并返回一个 boolean 值。在对类型 T 进行断言判断时,可以使用这个接口。通常称为断言型接口。

  • 字符串判空
 Predicate<String> p01=(str)->str.isEmpty()||str.trim().isEmpty();
     /**
      * 测试传入的字符串是否为空
      */
    System.out.println(p01.test(""));
    System.out.println(p01.test(" "));
    System.out.println(p01.test("admin"));
  • 用户合法性校验

接口静态方法完成手机号合法校验功能,方法返回函数式接口 Predicate

public interface MyStringInter {public final  String checkPhone= "^((13[0-9])|(14[5,7,9])|(15([0-3]|[5-9]))|(16[0-9])" +
            "|(17[0,1,3,5,6,7,8])|(18[0-9])|(19[8|9]))\\d{8}$";
    /**
     * 用户手机格式合法性
     *     返回 L 函数式接口 Predicate 的实现  Lambda 表达式
     * @return
     */
    static Predicate<String> checkPhone(){return (e)-> {return Pattern.compile(checkPhone).matcher(e).matches();};
    }
}

2、Consumer

java.util.function.Consumer<T> 接口定义了一个名叫 accept 的抽象方法,它接受泛型 T,没有返回值(void)。如果需要访问类型 T 的对象,并对其执行某些操作,可以使用这个接口,通常称为消费型接口。

  • 热销商品展示
/**
  热销商品测试数据
*/
Goods g01=new Goods(1,"iPad 2018 款",3000,180, BigDecimal.valueOf(2300));
Goods g02=new Goods(6,"小米平板 4",5000,600, BigDecimal.valueOf(1900));
Goods g03=new Goods(9,"微软 Surface Pro 6",100,50, BigDecimal.valueOf(8500));
Goods g04=new Goods(20,"华为 荣耀平板 5",1600,480, BigDecimal.valueOf(1500));
List<Goods> goods= Arrays.asList(g01,g02,g03,g04);


//Consumer 实现集合数据输出 Lambda 替代匿名函数 实现 Consumer 接口
 goods.forEach(g->{System.out.println(g);
 });

3、Function

​ java.util.function.Function<T, R> 接口定义了一个叫作 apply 的方法,它接受一个泛型 T 的对象,并返回一个泛型 R 的对象。如果需要定义一个 Lambda,将输入的信息映射到输出,可以使用这个接口(比如提取苹果的重量,或把字符串映射为它的长度), 通常称为功能型接口。

  • 用户密码 Base64 编码
// 实现用户密码 Base64 加密操作
Function<String,String> f01=(password)->Base64.getEncoder().encodeToString(password.getBytes());
// 输出加密后的字符串
System.out.println(f01.apply("123456"));

4、Supplier

java.util.function.Supplier<T> 接口定义了一个 get 的抽象方法,它没有参数,返回一个泛型 T 的对象,这类似于一个工厂方法, 通常称为功能型接口。

  • 外部 Properties 文件读取
public static Properties readFile(String fileName) {Supplier<Properties> supplier = () -> {
            try {InputStream is = TestCase04.class.getClassLoader().getResourceAsStream(fileName);
                Properties prop = new Properties();
                prop.load(is);
                return prop;
            } catch (IOException e) {e.printStackTrace();
                return null;
            }
        };
        return supplier.get();}

三、高阶函数

Java8 中函数式接口中方法允许函数接口作为方法形参传入,同时方法的结果为函数接口, 从而实现链式调用操作,就像俄罗斯套娃那样,当把套娃一个个打开时,发现还有一个同样的小套娃在里面,最终发现最里面的一个也是一个完整的套娃玩具,此时的高阶函数是不是跟套娃有着惊人的相似之处呢。

  • 多页面转发
String action = "";
Predicate<String> p01 = (a) -> StringUtils.isBlank(a);
/**
* 如果 action 为空  或 index  或 main 转发到网站主页面
* 链式判断 方法结果仍然为一个函数
*/
if (p01.or((a) -> a.equals("index")).or((a) -> a.equals("main")).test(action)) {System.out.println("网站主页面...");
} else {System.out.println("其他页面...");
}
  • 多条件排序

这里以商品数据为例,按商品销量、评论排序,如果销量一致 按照商品评论数排序

/**
* 实际开发数据通常从数据库获取
* 这里使用测试数据
*/
Goods g01=new Goods(1,"小米 9",1789,200, BigDecimal.valueOf(2500));
Goods g02=new Goods(2,"华为 Mate20",5000,3000, BigDecimal.valueOf(7000));
Goods g03=new Goods(3,"OPPO R17",2000,2827, BigDecimal.valueOf(1500));
Goods g04=new Goods(4,"魅族 Note9",2000,1600, BigDecimal.valueOf(1600));
Goods g05=new Goods(5,"一加 6T",8000,5000, BigDecimal.valueOf(3500));
List<Goods> goods= Arrays.asList(g01,g02,g03,g04,g05);


// 销量 与 评论排序 高阶函数使用
Comparator<Goods> comparator = (g1,g2)->g1.getSale()-g2.getSale();
goods.sort(comparator.thenComparing(Comparator.comparing(g3 -> g3.getComment())));
goods.forEach((g)->System.out.println(g));

高阶函数应用场景较多(这里查看源码相关高阶函数方法), 如 Optinal 接口 filter、map、orElseGet 等方法,Stream 流操作等 基本都会用到 Predicate,Consumer,Supplier,Function 等接口。

四、函数式接口优势与应用场景

函数式接口的引入,结合 Lambda 的使用,消除的匿名函数繁琐的代码,使得代码结构简洁、紧凑,第二点就是函数式接口中使用高阶函数,可以很方便的实现链式调用,代码清晰简洁,同时引入的一种新的开发思想 - 函数式编程,对于开发者来说只需要关注函数的规则设计实现即可。

对于函数式接口应用,后续介绍到的 Optinal、Stream 相关方法对于数据处理的使用频率较高,同时也是构成函数式编程的核心内容。

感谢大家欣赏小乐带来的 Java8 核心特性之函数式接口,接下来还会更多 Java8-Java12 核心技术讲解,请关注 乐字节 如需要视频课程,请搜索 乐字节腾讯课堂

退出移动版