Maarten Pennings'
Android development: Closure captured outer variable
fampennings / maarten / android / 03closure

Closure

Download the source of this article.

Introduction

The article I wrote on inline extends ends with the observation the inline classes can access the members of the outer class. I've read around, and the concept is called closures.

This article examines closures. A closure is a subset of (the member variables of) an object, living longer than the object itselve!

Captured outer variables

Let us have a look at the following fragment and classify all variables.

class COuter {
  int v0;
  int v1;
  class CInner {
    int v2;
    int f( int v3 ) {
      int v4= v1+v2+v3;
      return v3;
    }
  }
}

We classify the variables v0..v4 with regard to their role in function f. We start with the easy ones.

The fragment below is a slight variant of the fragment above; all variables are removed except the outer ones. So, v0 and v1 are still outer variables for f, and they are also outer variables for g. Only variable v1 is captured by function f. Note however, that function g not only captures v0, but also v1, through its call to f.

class COuter {
  int v0;
  int v1;
  class CInner {
    int f( ) {
      return v1;
    }
    int g( ) {
      return v0+f();
    }
  }
}

The captured outer variables are tricky. And don't let anyone tell you, you don't need them, because you do.

Common practice, not obscure academics

The blue fragment below is actually a class definition. It is a bit hidden, since it is an anonymous class (defined inline and it has no name). It is also an inner class of the class ButtonClickActivity. And it captures the outer variable But (in red). This is in no way special. Handling button clicks is nearly always done with an inline (anonymous) class, and the handler nearly always needs access to widgets (outer members) of the activity.

public class ButtonClickActivity extends Activity ... {
  Button But;

  OnClickListener ButListener = new OnClickListener() {
    @Override
    public void onClick(View v) {
      if( v==But ) { ... }
    }
  };

  @Override
  public void onCreate(Bundle savedInstanceState) {
    ...
    But= (Button)findViewById(R.id.But);
    But.setOnClickListener(ButListener);
  }

  ...
}

Example

If you think the button click code above (especially the access of But in the listener is natural), stop reading. If you are one of those that tries to image how it works, and you can't, read on.

In order to get away from practical clutter, here is a contrived but clean example.

class Var {

  private int var= 1;

  class Get {
    int get() { return var; }
  }

  class Set {
    void set(int val) { var= val; }
  }

}

Observe that the class Var manages one single (private) variable var. That's all. To access this variable, the class exposes helper classes (not methods as you would expect), namely Get and Set. The Get class has a single method, get, which returns the value of var. Likewise, the Set class has a single method, set, which overwrites the value of var. Clearly, var is a captured outer variable for Get.get() as well as Set.set().

Let us now have a look at using class Var.

public class ClosureActivity extends Activity {
    private static final String TAG = "Closure";

    Var     v1, v2;
    Var.Get g1, g2;
    Var.Set s1, s2;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        v1= new    Var();
        g1= v1.new Get();
        s1= v1.new Set();
        v1= null;
        v2= new    Var();
        g2= v2.new Get();
        s2= v2.new Set();
        v2= null;

        Log.v( TAG, "v2="+g2.get() );
        Log.v( TAG, "v1="+g1.get() );
        s1.set(2);
        Log.v( TAG, "v1="+g1.get() );
        s1.set(3);
        Log.v( TAG, "v1="+g1.get() );
        Log.v( TAG, "v2="+g2.get() );
    }
}

We see that object v1 is assigned a fresh object of type Var. Think of this as allocating a new variable var for v1, since that's the only real thing that a Var does.

Of course we want to play with v1.var, but since the var field is private, we can only do that via the published accessors. In this case, we have accessor classes, so we allocate them: a getter g1 and a setter s1. Observer the funny syntax for the constructor: v1.new Get(). I must admit that I expected new v1.Get(), but that syntax is not ok. Of course Var.new Get() doesn't work, because we need an instance (having a var).

Now comes a tricky part: we throw away (the handle to) object v1 (in red).

Next, we create a new instance, a new variable v2.var. And for this new variable, we create a getter g2 and setter s2. This second variable is also thrown away (v2=null).

The resulting log is as follows. We see that v1 is initially 1 (by the initializer in Var). After calling a method of s1 (namely s1.set(2)), we see that the method of g1 returns a different value; g1.get() returns 2. An after s1.set(3) we see that g1.get() returns 3.

08-31 21:52:04.050: VERBOSE/Closure(11602): v2=1
08-31 21:52:04.050: VERBOSE/Closure(11602): v1=1
08-31 21:52:04.050: VERBOSE/Closure(11602): v1=2
08-31 21:52:04.050: VERBOSE/Closure(11602): v1=3
08-31 21:52:04.050: VERBOSE/Closure(11602): v2=1

The value of v2.var is not changed by all these calls.

Closures

Several languages (e.g. C#, javascript) have the above feature that captured outer variables are kept (once captured) by instances of the inner class. The captured outer variables form the so-called closure of (a method of) the inner class.

Closures can only exist in languages that have dynamic creation of functions. Here, the dynamically created functions are the "methods of the dynalically created instances of the inner class". In C# this is typically exercised with delegates.

Implementation

Every Java compiler has the freedom to implement closures (captured outer variables) in any way that adheres to the above semantics. This section sketches one possibility.

Suppose we have a class with member variables. Suppose the class has an inner class. This makes the member variables of the outer class, outer variables for the inner class. Suppose some of these outer variables are captured by some functions of the inner class.

As usual, the outer class is implemented by a "struct" or "record" storing all the member variables of the outer class. An object variable of the outer class points to this structure, so it has access to all members.

The inner class is also implemented by a struct, in this case storing all member variables of the inner class. Since we assumed that the inner class (has a method that) captures an outer variable, it needs access to this captured variable. This is achieved (by the compiler) by adding a hidden member variable ("closure") that refers to the outer class.

The hidden member variable closure is assigned a reference to an object of the outer class when an object of the inner class is created. This is achieved (by the compiler) by adding an extra (hidden) parameter to the constructor of the inner class. Thats why we have to say v1.new Get() instead of Var.new Get(): we need an outer object (v1), not just the outer class (Var).

When the outer class is destroyed, the reference count of its object struct is decremented. As long as there are objects of inner classes, the reference count of the struct is not zero, so it is kept.


Download the source of this article.

home
closure
Closure




Downloads:
Source





Editor:
Maarten Pennings
Laatste wijziging:
31 aug 2011
File:
error