传递一段代码
传递一个函数作为参数,这在C里面很好实现,函数指针嘛,一两个*号&号能解决的问题,C的语言特性具有先天的优势。但是Java呢?我们都知道,Java的函数接收的参数都是对象,可从来没听说过有函数对象这个说法啊。那要怎么整?设想有以下场景,函数executeFunc,它接收一个函数,用这个接受到的函数打印另外一个传入的变量word。文章知识点与官方知识档案匹配,可进一步学习相关知识
static void executeFunc(方法??,String word) {
// todo 用传入的方法打印word变量
}
package main;
interface Wrapper {
void myPrint(String w);
}
class Solution {
static void executeFunc(Wrapper w, String word) {
w.myPrint(word);
}
}
确实,问题解决了。那怎么在主函数调用呢?
public static void main(String[] args) {
executeFunc(new Wrapper() {
@Override
public void myPrint(String w) {
// 个性化拓展,例如在打印之前记录时间什么的
System.out.println(w);
}
}, "Hello Lambda!");
}
看起来有一点麻烦,如果需要用executeFunc的次数多起来的时候,显然就会造成很多“不太必要”的代码了。这里说的不太必要是因为,我们必须满足编译器的需求来进行规范的语法编写,但实际上我们关心的逻辑仅仅是里面那一小段打印的语句:
// 个性化拓展,例如在打印之前记录时间什么的
System.out.println(w);
这就是lambda派上用场的地方了。
Lambda写法
其实如果你在编译器编写以上代码时,你会收到一个灰色的智能提示,让你用lambda重写以上语句。我们应用一下这个修改试试看。
public static void main(String[] args) {
executeFunc(w -> {
// 个性化拓展,例如在打印之前记录时间什么的
System.out.println(w);
}, "Hello Lambda!");
}
我们看到,一大串的new操作,@Override重写被替换成一个传入参数,一对大括号,代码量大大减少。
两种写法的实际操作
// class version 55.0 (55)
// access flags 0x20
class main/Solution {
// compiled from: Solution.java
NESTMEMBER main/Solution$1
// access flags 0x0
INNERCLASS main/Solution$1 null null
// access flags 0x0
<init>()V
L0
LINENUMBER 7 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lmain/Solution; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x8
static executeFunc(Lmain/Wrapper;Ljava/lang/String;)V
L0
LINENUMBER 10 L0
ALOAD 0
ALOAD 1
INVOKEINTERFACE main/Wrapper.myPrint (Ljava/lang/String;)V (itf)
L1
LINENUMBER 11 L1
RETURN
L2
LOCALVARIABLE w Lmain/Wrapper; L0 L2 0
LOCALVARIABLE word Ljava/lang/String; L0 L2 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 15 L0
NEW main/Solution$1
DUP
INVOKESPECIAL main/Solution$1.<init> ()V
LDC "Hello Lambda!"
INVOKESTATIC main/Solution.executeFunc (Lmain/Wrapper;Ljava/lang/String;)V
L1
LINENUMBER 29 L1
RETURN
L2
LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
}
JVM里是不管你内部内部类的,它只认类和类实例化的对象。众所周知,接口是不能实例化的,也就是说,是不能通过直接new的方式新建一个纯接口对象,而是要编写一个类来实现接口,进而实例化这个类。那注意到我们上面的语句
//*****
new Wrapper() {
@Override
public void myPrint(String w) {
// 个性化拓展,例如在打印之前记录时间什么的
System.out.println(w);
}
}
//*****
输出结果
C:jdk11injava.exe....
main.Solution$1
Hello Lambda!
Process finished with exit code 0
Lambda写法的实际操作
// class version 55.0 (55)
// access flags 0x20
class main/Solution {
// compiled from: Solution.java
// access flags 0x19
public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup
// access flags 0x0
<init>()V
L0
LINENUMBER 8 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lmain/Solution; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x8
static executeFunc(Lmain/Wrapper;Ljava/lang/String;)V
L0
LINENUMBER 11 L0
ALOAD 0
ALOAD 1
INVOKEINTERFACE main/Wrapper.myPrint (Ljava/lang/String;)V (itf)
L1
LINENUMBER 12 L1
RETURN
L2
LOCALVARIABLE w Lmain/Wrapper; L0 L2 0
LOCALVARIABLE word Ljava/lang/String; L0 L2 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 26 L0
INVOKEDYNAMIC myPrint()Lmain/Wrapper; [
// handle kind 0x6 : 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;
// arguments:
(Ljava/lang/String;)V,
// handle kind 0x6 : INVOKESTATIC
main/Solution.lambda$main$0(Ljava/lang/String;)V,
(Ljava/lang/String;)V
]
LDC "Hello Lambda!"
INVOKESTATIC main/Solution.executeFunc (Lmain/Wrapper;Ljava/lang/String;)V
L1
LINENUMBER 31 L1
RETURN
L2
LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x100A
private static synthetic lambda$main$0(Ljava/lang/String;)V
L0
LINENUMBER 28 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 0
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
LINENUMBER 29 L1
RETURN
L2
LOCALVARIABLE w Ljava/lang/String; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
}
字节码有点长,但是关键的地方就那么几个,我们仔细看看。首先看到61行的这个地方。
private static synthetic lambda$main$0(Ljava/lang/String;)V
这个看起来很像一个函数的东西,其实就是一个函数,synthetic这个词的意思本来就是“人造的;合成的;综合的;”,作为关键字,它表示该方法由编译器自动生成。简而言之,你把它当做void,就很好阅读了。这是一个无返回值的静态方法,名称都定好啦,叫lambda$main$0,接受一个String类型的参数。里面又有一系列操作,println啥的。诶?仔细想想,是不是很像我们之前写的那个东西?没错,就是那个lambda表达式。
w -> {
// 个性化拓展,例如在打印之前记录时间什么的
System.out.println(w);
}
// 就是上面这货
Itisawayoflogicallygroupingclassesthatareonlyusedinoneplace:Ifaclassisusefultoonlyoneotherclass,thenitislogicaltoembeditinthatclassandkeepthetwotogether.Nestingsuch“helperclasses”makestheirpackagemorestreamlined.Itincreasesencapsulation:Considertwotop-levelclasses,AandB,whereBneedsaccesstomembersofAthatwouldotherwisebedeclaredprivate.ByhidingclassBwithinclassA,A’smemberscanbedeclaredprivateandBcanaccessthem.Inaddition,Bitselfcanbehiddenfromtheoutsideworld.Itcanleadtomorereadableandmaintainablecode:Nestingsmallclasseswithintop-levelclassesplacesthecodeclosertowhereitisused.
大意就是设计方面更科学,逻辑方面更合理,可读性更强之类的。在实践中大伙好像对这些特性不怎么重视,很正常,因为,"传递一段代码"这么简单工作,你跟我讲这些?由于Java的权限机制,内部类可以很方便地访问到外部类的变量,如下代码,在InnerClass的myPrint方法中使用outerPhone不会产生任何错误。
// 内部类很方便
class Solution {
String outerPhone = "13721211111";
private class InnerClass implements Wrapper {
@Override
public void myPrint(String w) {
System.out.println("正在打给"+outerPhone);
}
}
}
说了一大堆两者的区别,总结就是能用lambda就尽量用lambda。不仅好看,而且高效好用。笔者看来,lambda带来的动态表现是革命性的,它让很多开发工作变得简单,可以有更高的抽象程度,Android开发里已经有越来越多的应用例子了。如果你真的爱上了Lambda,那我建议你使用Kotlin作为首选的编程语言,因为Kotlin的Lambda是真真正正地把函数视作了一个对象,底层支持也更完善。
文章为作者独立观点,不代表 股票程序化软件自动交易接口观点