隨著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
通過執行上述程式碼,我們會發現生成了兩個名字比較奇特的類:
//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
的篇幅內容息息相關,但是由於篇幅有限,這裡便不再贅述。
歡迎關注我的公眾號,每週至少一篇比較有深度的原創文章:
本作品採用《CC 協議》,轉載必須註明作者和本文連結