从Java的lambda表达式加深理解:面向对象、封装、匿名内部类;面向过程(函数式)、闭包、匿名方法

三个Main类:

第一个Main类:
public class Main {
    public static void main(String[] args) {
        Main main = new Main();
        main.test();
    }
    void test() {
	    int i = 2;
	    i++;
	    ((Runnable) () -> {
	        int j=i;
	        System.out.println(j);
	    }).run();
	}
}

这个Main类并不能通过编译,因为这违反了闭包原则:
提示:
Variable used in lambda expression should be final or effectively final
也就是说,java的lambda表达式只能引用外部语境中的不变量,而且自身也不能对其进行修改,所以其实java的lambda表达式只是一个伪闭包实现,因为它不clone外部变量表中的变量到自身的局部变量表,相反地,它对外部的变量提出约束要求 (对程序员的脑洞也提出了约束要求)

第二个Main类:
public class Main2 {
    private int i=2;
    public static void main(String[] args) {
        Main2 main2 = new Main2();
        main2.test();
    }
    void test(){
        ((Runnable) () -> System.out.println(i++)).run();
        main2.i++;
    }
}

那这个例子能不能通过编译并正常运行呢?
答案是能的。
为什么能呢?简单的猜测一二,或许是匿名内部类持有了Main类的引用吧,所以javap -c -p -l Main2,反编译验证一下:

Compiled from "Main2.java"
public class forTest.Main2 {
  private int i;

  public forTest.Main2();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iconst_2
       6: putfield      #2                  // Field i:I
       9: return
    LineNumberTable:
      line 3: 0
      line 4: 4
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      10     0  this   LforTest/Main2;

  public static void main(java.lang.String[]);
    Code:
       0: new           #3                  // class forTest/Main2
       3: dup
       4: invokespecial #4                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #5                  // Method test:()V
      12: return
    LineNumberTable:
      line 6: 0
      line 7: 8
      line 8: 12
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      13     0  args   [Ljava/lang/String;
          8       5     1 main2   LforTest/Main2;

  void test();
    Code:
       0: aload_0
       1: invokedynamic #6,  0              // InvokeDynamic #0:run:(LforTest/Main2;)Ljava/lang/Runnable;
       6: invokeinterface #7,  1            // InterfaceMethod java/lang/Runnable.run:()V
      11: aload_0
      12: dup
      13: getfield      #2                  // Field i:I
      16: iconst_1
      17: iadd
      18: putfield      #2                  // Field i:I
      21: return
    LineNumberTable:
      line 10: 0
      line 11: 11
      line 12: 21
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      22     0  this   LforTest/Main2;

  private void lambda$test$0();  // 注意这段 //
    Code:
       0: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: dup
       5: getfield      #2                  // Field i:I
       8: dup_x1
       9: iconst_1
      10: iadd
      11: putfield      #2                  // Field i:I
      14: invokevirtual #9                  // Method java/io/PrintStream.println:(I)V
      17: return
    LineNumberTable:
      line 10: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      18     0  this   LforTest/Main2;
}

显然,猜测是错误的,因为根本没有预料中的Main2$1.class文件,取而代之的是 lambda$test$0() 这么个匿名方法(函数),这说明lambda表达式并不是简单的java匿名内部类语法糖,而是完全不同的另一种东西——匿名方法(函数),这其实是背离面向对象编程思想的函数式编程思想实现。

第三个Main类:

作为比较,看看传统的匿名内部类写法:

public class Main3 {
    private int i = 0;
    public static void main(String[] args) {
        Main3 main3 = new Main3();
        main3.test();
    }
    void test() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(i++);
            }
        };
        runnable.run();
    }
}

javap -c -p -l Main3$1康一康是什么情况:

Compiled from "Main3.java"
class forTest.Main3$1 implements java.lang.Runnable {
  final forTest.Main3 this$0;

  forTest.Main3$1(forTest.Main3);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:LforTest/Main3;
       5: aload_0
       6: invokespecial #2                  // Method java/lang/Object."<init>":()V
       9: return
    LineNumberTable:
      line 12: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      10     0  this   LforTest/Main3$1;
          0      10     1 this$0   LforTest/Main3;      // 注意这里 //

  public void run();
    Code:
       0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: getfield      #1                  // Field this$0:LforTest/Main3;
       7: invokestatic  #4                  // Method forTest/Main3.access$008:(LforTest/Main3;)I
      10: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
      13: return
    LineNumberTable:
      line 15: 0
      line 16: 13
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      14     0  this   LforTest/Main3$1;
}

这就和在第二个情况中的猜测是一致的,匿名内部类留有Main类的引用。
这是面向对象编程的思想实现,而为什么是持有Main类的引用而不是Main类的字段的引用,则是因为要满足封装。
这三个Main类整理完,我大概就加深了几个重要的概念之间的关系和区别理解:
传统java匿名内部类:面向对象编程思想,封装。
java的lambda表达式(匿名方法(函数)):面向过程编程思想(函数式编程思想),伪闭包。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值