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: JCompo
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.
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 };
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.
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.
With Android Studio 3.2, this nice little message tells you whats going on with projectType
var inside the closure.