0
点赞
收藏
分享

微信扫一扫

java闭包详解

书坊尚 2021-09-23 阅读 67
日记本

闭包的特性

  • 闭包的价值在于可以作为函数对象或者匿名函数,持有上下文数据,作为第一级对象进行传递和保存。
  • 闭包广泛用于回调函数、函数式编程中。
  • 函数可以访问函数外部的变量,并且与它建立联系,可以修改变量和读取到外部对变量的修改。

java中闭包

在JAVA中,闭包是通过“接口+内部类”实现,JAVA的内部类也可以有匿名内部类。

内部类

在JAVA中,内部类可以访问到外围类的变量、方法或者其它内部类等所有成员,即使它被定义成private了,但是外部类不能访问内部类中的变量。这样通过内部类就可以提供一种代码隐藏和代码组织的机制,并且这些被组织的代码段还可以自由地访 问到包含该内部类的外围上下文环境。

public class DemoClass{
    private int length =0;

    private class InnerClass implements ILog
    {
        @Override
        public void Write(String message) {          
            System.out.println("DemoClass.InnerClass:" + length);
        }
    }

    public ILog logger() {
        return new InnerClass();
    }

    public static void main(String[] args){
        DemoClass demoClass = new DemoClass();
        demoClass.logger().Write("abc");

        //new
        DemoClass dc = new DemoClass();
        InnerClass ic = dc.logger();
        ic.Write("abcde");
    }
}

从上可见,InnerClass是定义在DemoClass内部的一个内部类,而且InnerClass还可以是Private。

局部内部类

局部内部类是指在方法的作用域内定义的的内部类。

public class DemoClass2 {
    private int length =0;

    public ILog logger() {
        //在方法体的作用域中定义此局部内部类
        class InnerClass implements ILog
        {
            @Override
            public void Write(String message) {
                length = message.length();
                System.out.println("DemoClass2.InnerClass:" + length);
            }
        }
        return new InnerClass();
    }
}

因为InnerClass类是定义在logger()方法体之内,所以InnerClass类在方法的外围是不可见的。

匿名内部类

顾名思义,匿名内部类就是匿名、没有名字的内部类,通过匿名内部类可以更加简洁的创建一个内部类。

public class DemoClass3 {
    private int length =0;

    public ILog logger() {
       return new ILog() {
            @Override
            public void Write(String message) {
                  length = message.length();
                  System.out.println("DemoClass3.AnonymousClass:" + length);
            }
       };
    }
}

由此可见,要创建一个匿名内部类,可以new关键字来创建。

  • 格式:new 接口名称(){}
  • 格式:new 接口名称(args…){}

final关键字

闭包所绑定的本地变量必须使用final修饰符,以表示为一个恒定不变的数据,创建后不能被更改。

public class DemoClass4 {
    private int length =0;

    public ILog logger(int level) {//final int level
        //final
        final int logLevel = level+1;

        switch(level)
        {
            case 1:
                return new ILog() {  //多个闭包会共享level,所以要强制level为final
                    @Override
                    public void Write(String message) {
                        length = message.length();
                        System.out.println("DemoClass4.AnonymousClass:InfoLog " + length);
                        System.out.println(logLevel);
                    }
                };    
            default:
            return new ILog() {
                @Override
                public void Write(String message) {
                    length = message.length();
                    System.out.println("DemoClass4.AnonymousClass:ErrorLog " + length);
                    System.out.println(logLevel);
                }
            };

        }
    }

    public static void main(String[] args){
        DemoClass4 demoClass4 = new DemoClass4();
        demoClass4.logger(1).Write("abcefghi");
    }
}

从例子中可以看到,logger方法接受了一个level参数,以表示要写的日志等级,这个level参数如果直接赋给内部类中使用,会导致编译时错误,提示level参数必须为final,这种机制防止了在闭包共享中变量取值错误的问题。解决方法可以像例子一样在方法体内定义一下新的局部变量,标记为final,然后把参数level赋值给它:

  final int logLevel = level ;
  //或者直接参数中添加一个final修饰符:
  public ILog logger(final int level {

实例初始化

匿名类的实例初始化相当于构造器的作用,但不能重载。

    public ILog logger(final int level) throws Exception {

        return new ILog() {
            {
                //实例初始化,不能重载 
                if(level !=1)
                    throw new Exception("日志等级不正确!");
            }

            @Override
            public void Write(String message) {
                length = message.length();
                System.out.println("DemoClass5.AnonymousClass:" + length);
            }
        };
    }

匿名内部类的实例初始化工作可以通过符号 {…} 来标记,可以在匿名内部类实例化时进行一些初始化的工作,但是因为匿名内部类没有名称,所以不能进行重载,如果必须进行重载,只能定义成命名的内部类。

lambda表达式

首先lambda表达式可以使用表达式外的变量,但要求使用的变量是final的,(逻辑上要求,并不强制要求final修饰)

    @FunctionalInterface
    interface fun {
        void f();
    }

    @Setter
    @Getter
    @AllArgsConstructor
    static class MyInter{
        Integer i;
    }

    public static void main(String[] args) {
        Integer a = 1;
        fun fun = ()->System.out.println(a);
        //a=2;
    }

我们使用了变量a,这是没问题的。
但是如果我们再加一行 a =2; 那么就会编译报错,如果我们在lambda里面修改了a也是不行的。
java编译时已经强制要求lambda使用的变量不可修改了,所以final关键字不是必须的。
不能修改那么就达不成闭包的条件。

那可以使用一个对象把变量包起来。

    @FunctionalInterface
    interface fun {
        void f();
    }

    @Setter
    @Getter
    @AllArgsConstructor
    static class MyInter{
        Integer i;
    }

    public static void main(String[] args) {
        MyInter inter = new MyInter(1);
        fun fun = ()->inter.setI(2);
        fun.f();
       System.out.println("lambda修改后:"+inter.getI());
        inter.setI(3);
        System.out.println("直接修改后:"+inter.getI());
    }

我们用一个MyInter对象把变量a封装起来。不修改MyInter对象,而修改它的属性,这样就可以实现了。

闭包的价值

像上面说的,闭包可以为某种功能提供维持某种状态的能力,如数组遍历需要维持index.
举个栗子:打印给定数量的 斐波那契数列


    @FunctionalInterface
    interface fun {
        int f();
    }

    @Setter
    @Getter
    @AllArgsConstructor
    static class MyInter {
        Integer i;
    }

    public static void main(String[] args) {
        print(10);
    }

    public static void print(int n) {
        MyInter last = new MyInter(0);
        MyInter next = new MyInter(1);
        fun fun = () -> {
            int r = next.getI();
            int i = last.getI() + next.getI();
            last.setI(next.getI());
            next.setI(i);
            return r;
        };
        for (int i = 0; i < n; i++) {
            System.out.println(fun.f());
        }
    }

使用闭包就是这么简单。

举报

相关推荐

0 条评论