Java 函數語言程式設計的前生今世

Nine發表於2020-04-18

隨著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

通過執行上述程式碼,我們會發現生成了兩個名字比較奇特的類:
2020-04-17T14:23:20.png

//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的篇幅內容息息相關,但是由於篇幅有限,這裡便不再贅述。

歡迎關注我的公眾號,每週至少一篇比較有深度的原創文章:

Java函數語言程式設計的前生今世

本作品採用《CC 協議》,轉載必須註明作者和本文連結

Nine

相關文章