Java函数式编程的前生今世

随着 Java8 的发布,大家翘首以待的 FP (函数式编程,后文皆以FP简称)终于面世。其实早在 1.7 这个版本就已经准备发布,但是由于还属于 Sun 刚被收购的磨合期所耽误。而 Java8 这个版本也一再延误才终于发布。

早些时候说起 Java ,大家对他的第一印象就是冗长,虽然我们可以通过 IDEA 等工具帮我们解决这些问题,但是可读性差的问题仍无法避免。

于是, lambda 和函数式编程呼之欲出。

Java 中函数是表达式与 lambda 密不可分,而说到 lambda 表达式,又就不得不提及 @FunctionInterface 这个注解,当你点开这个注解,你会发现很多你熟悉的类(Comparator、Runnable等)都使用了这个注解,而这个注解也是 Java 实现函数式编程编程中尤为重要的一环。

在解释这个注解实现原理之前,我们不妨先使用一下 lambda 看看其效果。

//Java8之前
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
});
//Java8之后
Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName()));
//或者你可以使用方法引用
Thread thread1 = new Thread(StreamDemo::sayHello);
private static void sayHello() {
    System.out.println("hello,world");
}

原理

如果是你,你会如何设计这套语法糖的实现呢?

万变不离其宗, lambda 只是语法糖,虽然我们使用 lambda 表达式简化了我们的代码,但是并不意味着我们不需要实现 Runnable ,我们不妨大胆猜测一下:是不是因为 @FunctionalInterface 注解自动帮我们生成了一个 Runnable 的实现?

没错, Java 会在编译时帮我们动态生成一个对象来实现 Runnable ,我们可以通过参数 -Djdk.internal.lambda.dumpProxyClasses 来帮助我们把生成的类输出。

java -classpath your-class-path -Djdk.internal.lambda.dumpProxyClasses com.nineyang.LambdaDemo

通过执行上述代码,我们会发现生成了两个名字比较奇特的类:

Java函数式编程的前生今世

//LambdaDemo$$Lambda$1.class
final class LambdaDemo$$Lambda$1 implements Runnable {
    private LambdaDemo$$Lambda$1() {
    }

    @Hidden
    public void run() {
        LambdaDemo.lambda$main$0();
    }
}

//LambdaDemo$$Lambda$2.class
final class LambdaDemo$$Lambda$2 implements Runnable {
    private LambdaDemo$$Lambda$2() {
    }

    @Hidden
    public void run() {
        LambdaDemo.sayHello();
    }
}

此时又有一个奇奇怪怪的方法出现了, LambdaDemo.sayHello() 我们可以理解,但是 LambdaDemo.lambda$main$0() 是什么鬼?我们可重来没有写过这个方法啊。

我们不妨再猜测一下,是不是因为生成实例的同时,也给我们生成了一个方法呢?既然这个静态方法在我们所写的类中,那我们不妨看看这个类生成的字节码

//命令行执行
javap -p -c target.classes.com.nineyang.LambdaDemo
//截取其中一部分字节码
private static void lambda$main$0();
    Code:
       0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: invokestatic  #10                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
       6: invokevirtual #11                 // Method java/lang/Thread.getName:()Ljava/lang/String;
       9: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      12: return

我想看到这里,已经不需要我再过多解释,大家也能明白其所做的事情是什么了。

不过,也许此时在你心中有了一个疑惑,为什么这些名字这么奇怪?在哪里可以看到生成的规则呢?如果我自定义一个 FunctionInterface 结果会怎样呢?

public class LambdaDemo2 {

    public static void main(String[] args) {
        LambdaPrintFunction lambdaPrintFunction = System.out::println;
        lambdaPrintFunction.print("hello,nine");
    }
}

@FunctionalInterface
interface LambdaPrintFunction {
    void print(String x);
}
//生成的实现类
final class LambdaDemo2$$Lambda$1 implements LambdaPrintFunction {
    private final PrintStream arg$1;

    private LambdaDemo2$$Lambda$1(PrintStream var1) {
        this.arg$1 = var1;
    }

    private static LambdaPrintFunction get$Lambda(PrintStream var0) {
        return new LambdaDemo2$$Lambda$1(var0);
    }

    @Hidden
    public void print(String var1) {
        this.arg$1.println(var1);
    }
}

此时生成的情况会变得稍显复杂,那么生成的规则到底在哪可以看呢?我们不妨带上附加信息后再看看我们的字节码

javap -c -p -v target.classes.com.nineyang.LambdaDemo2
SourceFile: "LambdaDemo2.java"
InnerClasses:
     public static final #56= #55 of #61; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #29 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #30 (Ljava/lang/String;)V
      #31 invokevirtual java/io/PrintStream.println:(Ljava/lang/String;)V
      #30 (Ljava/lang/String;)V

我们拿到最后的附加信息之后,发现又一个非常有用的提示: invokestatic java/lang/invoke/LambdaMetafactory.metafactory

//LambdaMetafactory.metafactory方法
    public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType)
            throws LambdaConversionException {
        AbstractValidatingLambdaMetafactory mf;
        mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                             invokedName, samMethodType,
                                             implMethod, instantiatedMethodType,
                                             false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
        mf.validateMetafactoryArgs();
        return mf.buildCallSite();
    }

该方法会帮助调度 InnerClassLambdaMetafactory 来我们生成实现类,而 InnerClassLambdaMetafactory 中,就有了我们所需要的参数相关的生成规则。

public InnerClassLambdaMetafactory(MethodHandles.Lookup caller,
                                       MethodType invokedType,
                                       String samMethodName,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType,
                                       boolean isSerializable,
                                       Class<?>[] markerInterfaces,
                                       MethodType[] additionalBridges)
            throws LambdaConversionException {
        super(caller, invokedType, samMethodName, samMethodType,
              implMethod, instantiatedMethodType,
              isSerializable, markerInterfaces, additionalBridges);
        implMethodClassName = implDefiningClass.getName().replace('.', '/');
        implMethodName = implInfo.getName();
        implMethodDesc = implMethodType.toMethodDescriptorString();
        implMethodReturnClass = (implKind == MethodHandleInfo.REF_newInvokeSpecial)
                ? implDefiningClass
                : implMethodType.returnType();
        constructorType = invokedType.changeReturnType(Void.TYPE);
        lambdaClassName = targetClass.getName().replace('.', '/') + "$$Lambda$" + counter.incrementAndGet();
        cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        int parameterCount = invokedType.parameterCount();
        if (parameterCount > 0) {
            argNames = new String[parameterCount];
            argDescs = new String[parameterCount];
            for (int i = 0; i < parameterCount; i++) {
                argNames[i] = "arg$" + (i + 1);
                argDescs[i] = BytecodeDescriptor.unparse(invokedType.parameterType(i));
            }
        } else {
            argNames = argDescs = EMPTY_STRING_ARRAY;
        }
    }

好了,这就是 lambda 表达式的基本原理。前面我们用到了自定义的函数式编程,不过坦率地讲,我们在实际工作中用的非常少,因为 Java 已经帮我们定义好了我们可能会用到的一些接口。

四大函数式编程接口

Consumer

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);

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

Consumer 是对数据源的操作,没有返回值,同时提供了 andThen 方法来帮助我们链式调用。这里需要说明的是,注解 @FunctionalInterface 是可以有默认方法和静态方法的,但是, 接口实现只能有一个

public class ConsumerDemo {
    public static void main(String[] args) {
        Consumer<String> consumer = (name) -> System.out.println("hello," + name);
        consumer.accept("nineyang");
    }
}

Predicate

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

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

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

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

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

Predicate ,顾名思义,用于判定表达式执行的结果,返回值是 boolean

public class PredicateDemo {
    public static void main(String[] args) {
        Predicate<Integer> predicate = (x) -> x > 0;
        boolean result = predicate.test(10);
    }
}

Function

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

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

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

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

Function ,可以执行一段逻辑,比如用于转化类型,将一种类型转换为另外一种。

public class FunctionDemo {

    public static void main(String[] args) {
        Function<String,Integer> function = Integer::valueOf;
        Integer result = function.apply("10");
    }
}

Supplier

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

Supplier ,用于获取结果。

public class SupplierDemo {
    public static void main(String[] args) {
        Supplier<String> supplier = () -> "hello,nineyang";
        System.out.println(supplier.get());
    }
}

这就是 Java 中最常用的四大函数式编程的接口了,其他的也基本上是围绕这四种来展开的,这也与我们接下来关于 Stream 的篇幅内容息息相关,但是由于篇幅有限,这里便不再赘述。

本文由nine 创作,采用 知识共享署名4.0 国际许可协议进行许可

本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名

最后编辑时间为: Apr 17, 2020 at 11:54 pm

原文 

https://www.hellonine.top/index.php/archives/121/

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Java函数式编程的前生今世

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址