33

In Kotlin,the code in an object expression can access variables from the scope that contains it, just like the following code:

fun countClicks(window: JComponent) {
   var clickCount = 0
   var enterCount = 0
   window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        clickCount++
    }

    override fun mouseEntered(e: MouseEvent) {
        enterCount++
    }
   })
}

But why? In Java, it's not allowed to do this, because the life cycle of the object is different from the local variables, so the enterCount or clickCount maybe be invalid when you try to access the object. Can someone tell me how Kotlin does this?

5 Answers 5

43

In Java, you can only capture (effectively) final variables in anonymous classes and lambdas. In Kotlin, you can capture any variable, even if they are mutable.

This is done by wrapping any captured variables in instances of simple wrapper classes. These wrappers just have a single field that contains the captured variables. Since the instances of the wrappers can be final, they can be captured as usual.

So when you do this:

var counter = 0
{ counter++ }()   // definition and immediate invocation, very JavaScript

Something like this happens under the hood:

class Ref<T>(var value: T) // generic wrapper class somewhere in the runtime

val counter = Ref(0);      // wraps an Int of value 0
{ counter.value++ }()      // captures counter and increments its stored value

The actual implementation of the wrapper class is written in Java, and looks like this:

public static final class ObjectRef<T> implements Serializable {
    public T element;

    @Override
    public String toString() {
        return String.valueOf(element);
    }
}

There are also additional wrappers called ByteRef, ShortRef, etc. that wrap the various primitives so that they don't have to be boxed in order to be captured. You can find all the wrapper classes in this file.

Credits go to the Kotlin in Action book which contains the basics of this information, and the example used here.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for your answer,it helps me
I don't understand Why we need wrap variable by a Ref object and then access it from enclosing scope.
We don't need to do this ourselves - the Kotlin compiler does this for us. I was merely explaining how it works behind the scenes.
8

In Kotlin, unlike Java, lambda expressions or anonymous function (as well as local functions and object expressions) can access and modify their closure - variables declared in outer scope. This behavior is as-designed.

Higher order functions and lambdas - Closures

Why Java does not allow this and Kotlin does - capturing closures introduces additional run-time overhead. Java uses simple and fast approach at cost of functionality. Kotlin on the other hand gives you more features - functionality, but it also generates more code behind the scenes to support it.

At the end it is about writing less code to achieve something. If you want to translate above code to Java, it would be more complex.

2 Comments

To add to this, it basically just uses a very simple wrapper class which contains the variable that has to be captured. Since that wrapper instance can be final, it can then be accessed in an inner class / lamda.
@zsmb13 That information would justify separate answer. Capturing closures usually works like that - using wrappers, but since I am not 100% sure how Kotlin exactly does it, I didn't want to include wild guessing in my answer.
5

IF you dump the class by using javap you can found kotlin using a IntRef not a int for lambda accessing the mutable variable out of its scope, since in java variables out of the lambda scope are effectively-final or final which means you can't modify the variable at all, for example:

// Kotlin 
var value = 1; // kotlin compiler will using IntRef for mutable variable
val inc = { value++ }; 
inc();
println(value);// 2;

//Java
IntRef value = new IntRef();
value.element = 1;

Runnable inc=()-> value.element++;
inc.run();
println(value.element);// 2;

the code above are equality. so don't modify the mutable variables out of the lambda scope in multi-threads. it will resulting to the wrong result.

another good usage is you needn't modify the mutable variable out of the lambda scope and want to improve the performance optimization. you can use an additional immutable variable to achieve the performance for the lambda , for example:

var mutable = 1;
val immutable = mutable; // kotlin compiler will using int 

val inc = { immutable + 1 };

1 Comment

If you are using IntelliJ IDEA, the Kotlin plugin has a "decompile to java" view that shows this "magic". It's very useful in cases like this if you want to understand what's actually going on.
5

The concept you're referring to is called "capturing".

There's a trick that can be applied in Java: Wrap the mutable value into a final wrapper such as AtomicReference<T> and the compiler will stop complaining:

AtomicReference<Integer> max = new AtomicReference<>(10);
if (System.currentTimeMillis() % 2 == 0) {
    max.set(11)
}
Predicate<Integer> pred = i -> i * 2 > max.get();

That's basically what's happening in Kotlin as well: If a final variable (val) is being captured, it simply gets copied into the lambda. But if, on the other hand, a mutable variable (var) is being captured, its value is wrapped in an instance of Ref:

class Ref<T>(var value: T)

The Ref variable is final and therefore can be captured without a problem. As a result, the mutable variable can be changed from within the lambda.

1 Comment

Can you please add a link to the documentation?
2

enter image description here With Android Studio 3.2, this nice little message tells you whats going on with projectType var inside the closure.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.