行为参数化 一脉相承:Lambda与匿名内部类之间的故事

匿名内部类与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;)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表达式和匿名内部类的一些区别:

  1. 匿名内部类是在编译期生成,lambda编译期生成一个私有的静态方法,运行期间生成一个final的内部类;
  2. Lambada对于重载的方法需要类型指定,匿名内部类不需要;
  3. 两者对于变量的获取不同,super和this的意义也不一样;


原文始发于微信公众号(处女座码农):行为参数化 一脉相承:Lambda与匿名内部类之间的故事

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/251588.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!