匿名内部类与Lambda表达式的初衷是相同的:行为参数化,换句话说就是可以将方法作为参数进行一个传递。
1. 两者的执行流程
基于匿名内部类方式:
//使用匿名内部类的 java文件
public class LambdaTest {
public static void main(String[] args) throws Exception {
print(new Switch() {
public String switchLowerCase(String str) {
return str.toLowerCase();
}
});
}
public static void print(Switch switcher) {
String str = "Hello World";
str = switcher.switchLowerCase(str);
System.out.println(str);
}
interface Switch {
String switchLowerCase(String str);
}
}
//生成了一个匿名类的class文件, 这是在编译期生成的
final class LambdaTest$1 implements LambdaTest.Switch {
LambdaTest$1() {
}
public String switchLowerCase(String str) {
return str.toLowerCase();
}
}
基于当前的main方法运行后生成的字节码文件:
//java文件
public class LambdaTest {
public static void main(String[] args) throws Exception {
print((str) -> str.toLowerCase());
}
public static void print(Switch switcher) {
String str = "Hello World";
str = switcher.switchLowerCase(str);
System.out.println(str);
}
interface Switch {
String switchLowerCase(String str);
}
}
//java 字节码指令文件:
public class com/dev/wizard/lambda/LambdaTest {
// compiled from: LambdaTest.java
// access flags 0x608
static abstract INNERCLASS com/dev/wizard/lambda/LambdaTest$Switch com/dev/wizard/lambda/LambdaTest Switch
// access flags 0x19
public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lcom/dev/wizard/lambda/LambdaTest; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V throws java/lang/Exception
// parameter args
L0
LINENUMBER 6 L0
INVOKEDYNAMIC switchLowerCase()Lcom/dev/wizard/lambda/LambdaTest$Switch; [
// 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;)Ljava/lang/String;,
// handle kind 0x6 : INVOKESTATIC
com/dev/wizard/lambda/LambdaTest.lambda$main$0(Ljava/lang/String;)Ljava/lang/String;,
(Ljava/lang/String;)Ljava/lang/String;
]
INVOKESTATIC com/dev/wizard/lambda/LambdaTest.print (Lcom/dev/wizard/lambda/LambdaTest$Switch;)V
L1
LINENUMBER 7 L1
RETURN
L2
LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static print(Lcom/dev/wizard/lambda/LambdaTest$Switch;)V
// parameter switcher
L0
LINENUMBER 10 L0
LDC "Hello World"
ASTORE 1
L1
LINENUMBER 11 L1
ALOAD 0
ALOAD 1
INVOKEINTERFACE com/dev/wizard/lambda/LambdaTest$Switch.switchLowerCase (Ljava/lang/String;)Ljava/lang/String; (itf)
ASTORE 1
L2
LINENUMBER 12 L2
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L3
LINENUMBER 13 L3
RETURN
L4
LOCALVARIABLE switcher Lcom/dev/wizard/lambda/LambdaTest$Switch; L0 L4 0
LOCALVARIABLE str Ljava/lang/String; L1 L4 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x100A
//这里生成了一个新的静态方法。 命名规则:lambda${该lambda所在的方法名}${调用的lambda的顺序}
private static synthetic lambda$main$0(Ljava/lang/String;)Ljava/lang/String;
// parameter synthetic str
L0
LINENUMBER 6 L0
ALOAD 0
INVOKEVIRTUAL java/lang/String.toLowerCase ()Ljava/lang/String;
ARETURN
L1
LOCALVARIABLE str Ljava/lang/String; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
}
为了让运行期间输出类的字节码文件,我们需要添加启动参数-Djdk.internal.lambda.dumpProxyClasses
//运行期间生成的
final class LambdaTest$$Lambda$1 implements LambdaTest.Switch {
private LambdaTest$$Lambda$1() {
}
@Hidden
public String switchLowerCase(String var1) {
return LambdaTest.lambda$main$0(var1);
}
}
//字节码指令文件
final synthetic class com/dev/wizard/lambda/LambdaTest$$Lambda$1 implements com/dev/wizard/lambda/LambdaTest$Switch {
// access flags 0x2
private <init>()V
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public switchLowerCase(Ljava/lang/String;)Ljava/lang/String;
@Ljava/lang/invoke/LambdaForm$Hidden;()
ALOAD 1
INVOKESTATIC com/dev/wizard/lambda/LambdaTest.lambda$main$0 (Ljava/lang/String;)Ljava/lang/String;
ARETURN
MAXSTACK = 1
MAXLOCALS = 2
}
两者虽然本质都生成了匿名内部类,但是生成的时期不同。匿名类部类是在编译期产生的,lambda是在运行期产生的final内部类,编译期只产生了一个私有的静态方法。
2.类型识别
public class LambdaDemo {
public static void test(MyFirstFunction function){
function.execute("MyFirstFunction");
}
public static void test(MySecondFunction function){
function.execute("MySecondFunction");
}
public static void main(String[] args) {
test(new MyFirstFunction() {
@Override
public void execute(String value) {
System.out.println("value + " + value);
}
});
//这里使用了类型转换
test((MySecondFunction) value -> {
System.out.println("value + " + value);
});
}
}
对于匿名内部类和lambda而言,正如上述test的方法重载的情况,前者在使用new出一个对象的时候已经确定了类型,确定了我传的是MyFirstFunction的对象; 但是对于后者而言,如果只写 value -> {System.out.println("value + " + value);}
对于test的两个方法都可以使用,这样一来就不知道调用哪一个方法了,所以类型转换的目的是为了确定调用的方法。
3.变量使用
匿名内部类和lambda表达式的super和this是不同的,前者是针对匿名类自身,后者是这个lambda所在的类;
匿名内部类可以屏蔽包含类的变量,Lambda不能(编译报错)
int number = 33;
test(new MyFirstFunction() {
final int number = 55;
@Override
public void execute(String value) {
System.out.println(number);
}
});
test((MySecondFunction) value -> {
// final int number = 44; 编译报错
System.out.println(number);
});
上述对于匿名内部类的结果是55, lambda自然是33了。如果将匿名内部类中的final int number = 55;
去掉,匿名内部类输出为33。也就是匿名内部类中优先选择自己的变量。
4.总结
本文主要讲述了Lambda表达式和匿名内部类的一些区别:
-
匿名内部类是在编译期生成,lambda编译期生成一个私有的静态方法,运行期间生成一个final的内部类; -
Lambada对于重载的方法需要类型指定,匿名内部类不需要; -
两者对于变量的获取不同,super和this的意义也不一样;
原文始发于微信公众号(处女座码农):行为参数化 一脉相承:Lambda与匿名内部类之间的故事
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/251588.html