JAVA8 函数式编程及java.util.function包分析

函数式编程,在java中是一种语法糖实现。

FunctionalInterface注解

Java中通过@FunctionalInterface注解来定义函数,这个注解声明一个接口是由Java语言规范所定义的功能接口

@FunctionalInterface注解在java.util.function包下的所有接口上都有注解;

其所注解的接口在不符合功能函数规范时编译器将会抛出异常;

其注解的接口必需遵守以下规则:

  1. 只能有一个自己的抽象方法
  2. 可以有多个default methods
  3. 可以重写java.lang.Object类的方法

什么是default methods

在java8中接口类可以有默认方法,这些方法可以被接口示例调用:

public interface Human {
    default String lovingHeart() {
        return "爱心";
    }
}

函数式编程需要Lambda语法支持,我们可以自己写一个功能接口实现,同样能被Lambda语法支持——
定义Man函数接口:

@FunctionalInterface
public interface Man {
  void walk();
}

调用Man函数:

Man man = ()->{
  System.out.println("行走了5千米");
};
man.walk();

一个函数基于Lambda语法的最简单的展现形式为:()->{}()里的是函数参数,只有一个参数时()可以省略;{}里用来写逻辑代码的,只有一句代码时{}可以省略:(data)->System.out.println(data);

虽然说这种实现看起来类似于匿名内部类实现:

Man man = new Man() {
  public void walk() {
    System.out.println("行走了5千米");
  }
};
man.walk();

但在底层实现上它其实并不等同于匿名内部类,因为在编译后的class中,匿名内部类会生成一个类实现这个接口:

class com.test.jdk8.function.Test$1 implements com.test.jdk8.function.Man {
  Test$1();
  ...
  public void walk();
  ...
}

Test是类名,$1代表新生成的匿名内部类编号。Test$1代表在Test类下自动生成的类。
这里之所以为Test类名,是因为匿名内部类的实现语句是写在Test类里的。

而lambda语法只是在所在类添加了一个方法:

public class com.test.jdk8.function.Test {
  public Test();
  ...
  private static synthetic void lambda$0();
  ...
}

这个很好理解,因为函数式接口只能有一个自己的抽象方法,所以编译成方法实现会轻量的多。

java.util.function包

所有接口参照

https://blog.csdn.net/zoujiawei6
乍一看接口很多,很难学习。实际上如果我们能探索出其中的规律,就能很轻松地掌握这些接口。
接下来,我会将函数接口的命名分为五种模式和四种修饰词来进行说明。

函数的五种模式

从名称上看,function包下的函数大体可以分为五种模式,暂且称之为Pattern

模式描述
Consumer(消费者)接收值但不返回结果。
Function(函数)接收值并且返回结果。
Predicate(断言)接收值并返回boolean结果。原则上这个boolean应当与传入的值有关。
Supplier(供应者)不接收值但返回结果。
Operator(经营者)接收值并返回同类型结果。

函数的四种修饰

从名称的修饰词上看则有四种修饰类,暂且称之为Modifier

修饰描述
Class(类)表示函数接收此Class类型的参数
To(转换)将接收的类型转值换为另一种类型输出。一般用于修饰Function模式
Unary(一元的)方法只接收一个参数
Binary / Bi(二元的)方法接收两个参数

四种修饰词类型在java.util.function包下的函数名称中的表现形式:

名称ClassToUnaryBinary
简述类对象转换一元的二元的
表现Double / Int / Long / ObjClassToClass/ ToClassUnary / ClassUnaryBinary / Bi / ClassBinary

注:
1、在Class类型的修饰词中,Obj修饰符是Object的缩写。
2、在其它修饰中的Class是指使用Class修饰类型的修饰词(Double / Int / Long / Obj),已用粗体标明。

比如:LongToIntFunction就是ClassToClass类型。

依据模式和修饰推测函数的用途

function包下的接口大致都以 PatternModifiersPattern组成;根据JDK优秀的代码风格,我们只需通过函数的名字就能推测出其用途,比如:

BiConsumer<T,U>
Bi 接收两个参数,Consumer 接收值但不返回结果。
BiConsumer接收两个T和U类型值进行操作,不返回结果。

ToIntBiFunction<T,U>
ToInt 转换为Int类型,Bi 接收两个参数,Function 接收值并返回结果。
接收两个T和U类型值,转换成Int后返回。

ObjIntConsumer<T>
Obj 接收Obj参数,Int 接收Int参数,Consumer 接收值但不返回结果。
接受对象值和int值进行操作,不返回结果

现在,试着推敲下列函数的用途:
IntBinaryOperator
DoublePredicate
ObjIntConsumer<T>
ToIntBiFunction<T,U>
Supplier<T>

函数方法

函数方法分布于不同的接口中,但所有类中的同名方法都是一个概念。所以,我们只需要去了解这些方法的概念即可。

compose方法

返回一个组合的函数,使得传入的函数在当前函数之前运行。如果传入的函数有返回值,则运行传入的这个函数,并将运行结果当做参数值传入当前函数中。

所谓的运行,就是运行函数接口那唯一的抽象方法。

Function中的compose方法源码:

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

这个方法会调用befor函数的唯一方法apply

before.apply(v)

再将返回值当做函数传入返回的函数的apply方法:

apply(before.apply(v))

最后形成一个函数返回:

return (V v) -> apply(before.apply(v));

举个例子,在调用当前apply方法前,会先调用传入的函数:

Function<String, String> function = (x)->{
    System.out.println("本体");
    return x;
};
function.compose((x)->{
    System.out.println("前");
    return String.valueOf(x);
}).apply("传入值");

andThen方法

返回一个组合的函数,使得传入的函数在当前函数之后运行。如果当前函数有返回值,则将当前函数的运行结果当做参数值传入这个被传入的函数中。

Consumer中的方法源码,传入函数无返回值:

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

Function中的方法源码,传入函数有返回值:

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

identity方法

identity方法将传入参数当做返回参数返回
identity方法作用于有返回值的函数。
identity是一个静态方法。

Function中的方法源码:

static <T> Function<T, T> identity() {
   return t -> t;
}

假设有一个Int类型传参,那么上面代码等价于:

Function<Integer, Integer> function = (x)->{
   return x;
};

当调用运行函数时,仅仅只是返回传入结果
PS:总感觉这个方法像吃饱了没事干,谁能告诉我怎么回事吗?

BinaryOperator函数特有的minBy和maxBy方法

minBy在传入的两个参数中取最小值
maxBy在传入的两个参数中取最大值

BinaryOperator接收两个参数并返回同类型结果。它继承自BiFunction接口,同时只有两个静态方法:

	// 取最小值
    public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
    }
    
    // 取最大值
    public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
    }

在传入的Comparator函数中只要进行减法计算(x1 - x2)即可比较大小,负数证明x1 < x2,零说明x1 = x2,正数证明x1 > x2

我们来演示一下如何将2和1进行比较,分别输出最大值和最小值:

Comparator<Integer> comparator = (x1,x2)->{
    return x1 - x2;
};
BinaryOperator<Integer> binaryOperator = BinaryOperator.maxBy(comparator);
System.out.println("max " + binaryOperator.apply(2, 1));

binaryOperator = BinaryOperator.minBy(comparator);
System.out.println("min " + binaryOperator.apply(2, 1));

输出结果:

max 2
min 1

Predicate模式里的方法

and、negate、or方法大致分布于Predicate和名称以Predicate结尾的接口中,用于对boolean值进行操作
而isEqual是Predicate接口的独有方法。

isEqual方法

isEqual是一个静态方法。返回一个Predicate函数对象,用于判断传入值是否与运行函数时传入的值一致。两个空对象会被认为是相等的:null==null

static <T> Predicate<T> isEqual(Object targetRef) {
    return (null == targetRef)
            ? Objects::isNull
            : object -> targetRef.equals(object);
}

上面代码中,Objects::isNull是方法引用语法。是调用Objects类的isNull方法判断传入的值是否为null,并在返回或赋值时封装成一个函数,这样写或许更加直观一点:

Predicate predicate = Objects::isNull;

示例,判断a、b两个对象是否相等:

Object a = new Object();
Object b = new Object();
Predicate<Object> predicate = Predicate.isEqual(a);
boolean result = predicate.test(b);
System.out.println(result);

返回结果:

false
and方法

将other函数与当前函数的运行结果进行&&运算:

default Predicate<T> and(Predicate<? super T> other) {
    Objects.requireNonNull(other);
    return (t) -> test(t) && other.test(t);
}
or方法

将other函数与当前函数的运行结果进行||运算:

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
negate

将当前函数的运行结果进行取反!

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

——END 感谢观看

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值