【java】java语言之闭包

一、为什么需要闭包

  • 在《Think in java》中有这样一句话:使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
  • 内部类最大的优点就在于它能够非常好的解决多重继承的问题
  • 闭包的价值在于可以作为函数对象或者匿名函数,持有上下文数据,作为第一级对象进行传递和保存。
  • 闭包广泛用于回调函数、函数式编程中。

特性:

      1、内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立。

      2、在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。

      3、创建内部类对象的时刻并不依赖于外围类对象的创建。

      4、内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。

      5、内部类提供了更好的封装,除了该外围类,其他类都不能访问。

二、JAVA中的闭包

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

1、成员内部类

在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");

        // 调用方式一
        DemoClass dc = new DemoClass();
        InnerClass ic = dc.logger();
        ic.Write("abcde");

        // 调用方式二
        DemoClass.InnerClass innerClass = demoClass.new InnerClass();
        innerClass.write("使用.new也可以实例化成员内部类");
    }
}


public interface ILog {
    void writer(String message);
} 

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

如何通过this显式引用外围类的变量? {外围类名}.this.{变量名称}

DemoClass.this.length = message.length();

2、局部内部类

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

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();
    }
}


public interface ILog {
    void writer(String message);
} 

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

3、匿名内部类

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

public class DemoClass3 {
    private int length =0;

    // ILog是另外定义的一个接口
    public ILog logger() {
       return new ILog() {
            @Override
            public void write(String message) {
                  length = message.length();
                  System.out.println("DemoClass3.AnonymousClass:" + length);
            }
       };
    }
}


public interface ILog {
    void writer(String message);
} 

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

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

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

4、静态内部类

  1. 非静态内部类中不允许定义静态成员
  2. 外部类的静态成员不可以直接使用非静态内部类
  3. 静态内部类,不能访问外部类的实例成员,只能访问外部类的类成员
public class EmpTest {
    private Integer id;
    private Integer empLevel;
    private String mapingOrderLevel;


    //外部类私有的构造方法
    private EmpTest(Builder builder) {
        setId(builder.id);
        setEmpLevel(builder.empLevel);
        setMapingOrderLevel(builder.mapingOrderLevel);
    }

    //对外提供初始化EmpTest类的唯一接口,通过这个方法,获得内部类的实例
    public static Builder newBuilder() {
        return new Builder();
    }

    //静态内部类:Builder
    public static final class Builder {
        private Integer id;
        private Integer empLevel;
        private String mapingOrderLevel;

        public Builder() {
        }

        public Builder id(Integer val) {
            id = val;
            return this;
        }

        public Builder empLevel(Integer val) {
            empLevel = val;
            return this;
        }

        public Builder mapingOrderLevel(String val) {
            mapingOrderLevel = val;
            return this;
        }

        //通过内部类的build方法,实例化外部类,并给其实例各个字段赋值
        public EmpTest build() {
            return new EmpTest(this);
        }

    }

    public Integer getId() {
        return id;
    }

    // get,set方法省略。。。。。

}

 调用:

EmpTest.newBuilder().empLevel(getRandom(5)).empNo("Emp_"+i).id(i).mapingOrderLevel(getRandomChar()).orderNumLimit(getRandom(20)).build()

 讲解:

  1. EmpTest中 定义了一个Builder的静态内部类,还定义了一个newBuilder的公共静态方法,上边讲过加了static关键字,那么就是类相关,所以我不需要实例化外部类,直接使用外部类就可以调用newBuilder的方法
  2. Builder的实例在调用各个内部类中的方法,给Builder的字段赋值
  3. 最后调用build()方法,实例化EmpTest,它的构造方法需要builder的对象,所以将builder的对象传入,通过builder的字段值给EmpTest的对象赋值

5、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() {
                    @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");
    }

}


public interface ILog {
    void writer(String message);
} 

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

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

6、实例初始化

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

    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);
            }
        };
    }



public interface ILog {
    void writer(String message);
} 

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

三、闭包的问题。

  1. 让某些对象的生命周期加长。
  2. 让自由变量的生命周期变长,延长至回调函数执行完毕。
  3. 闭包共享。
  4. final关键字
interface Action
{
    void Run();
}

public class ShareClosure {

    List<Action> list = new ArrayList<Action>();

    public void Input()
    {
        for(int i=0;i<10;i++)
        {
            final int copy = i;
            list.add(new Action() {    
                @Override
                public void Run() {
                    System.out.println(copy);
                }
            });
        }
    }

    public void Output()
    {
        for(Action a : list){a.Run();}
    }

    public static void main(String[] args) {
        ShareClosure sc = new ShareClosure();
        sc.Input();
        sc.Output();

    }

}

这个例子创建一个接口列表List ,先向列表中创建 i 个匿名内部类new Action(),然后通过for遍历读出。

因为 i 变量在各个匿名内部类中使用,这里产生了闭包共享,java编译器会强制要求传入匿名内部类中的变量添加final

关键字,所以这里final int copy = i;需要做一个内存拷贝,否则编译不过。(在c#中没有强制要求会导致列有被遍历时

始终会取 i 最大值,这是因为延迟执行引起的)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章