Home

Back

Contents

Next

Lambda Expressions

Since BeanShell 3.0 you can write lambda expressions - x -> ..., (a, b) -> ..., (Type a) -> ... - and use them as a functional-interface value in Java code, usually without an explicit cast. Method references (Foo::bar) are not supported.

Basic Forms

A lambda body may be a single expression or a block, and its parameter list may be empty, a single untyped identifier, or a parenthesized list of untyped or typed parameters:

Function expr  = x -> x + 1;
Function block = x -> { return x + 1; };

Supplier zeroArg   = () -> 5;
Function oneArg    = x -> x * 2;
BiFunction twoArgs = (a, b) -> a + b;
Function typedArg  = (Integer x) -> x + 10;

expr.apply(1);   // 2
twoArgs.apply(2, 3); // 5

As in the examples above, a lambda can usually be assigned directly to a variable typed with the target functional interface, with no cast needed. This works with ordinary JDK functional interfaces too, so you can pass a lambda straight into APIs like Collection.forEach(), Collections.sort(), or a Stream pipeline:

sum = 0;
Arrays.asList(1, 2, 3).forEach( x -> { sum += x; } );

sorted = new ArrayList(Arrays.asList(3, 1, 2));
Collections.sort( sorted, (a, b) -> a - b );

mapped = Arrays.asList(1, 2, 3).stream()
    .map( x -> x * 2 )
    .collect( Collectors.toList() );

A lambda's value is opaque - it doesn't commit to a particular interface type - until it is converted to one, by cast, assignment, or overload resolution. Until then it can sit in an untyped or Object variable just like any other BeanShell value.

Capturing Variables

A lambda captures its declaring scope by reference, not by snapshotting each variable's value the way Java requires captured locals to be effectively final. This means a later change to a captured variable - a global, a method local, or a scripted object's field - is visible the next time the lambda runs:

counter = 1;
Supplier getCounter = () -> counter;
counter = 2;
getCounter.get(); // 2

Note:
Because BeanShell does not refresh a for-each loop variable per iteration the way Java does, a lambda created inside a loop captures the same variable across iterations, not a fresh one each time:
for ( String s : list )
    list2.add( () -> s );  // every lambda here returns list's LAST element
If you need each lambda to see a different value, assign it to a fresh local inside the loop body first.

Exceptions

An unchecked exception, or a checked exception the target interface's method actually declares, propagates out of a lambda call as itself. Any other checked exception - or a BeanShell script error inside the lambda body - comes out wrapped as bsh.RuntimeEvalError instead.

Limitations

A handful of target interface shapes are rejected when a lambda is converted to them, rather than allowed to fail confusingly later:

A lambda converted to a Serializable interface serializes normally, but deserializing it throws rather than running - the same rule that already applies to serialized 'this' references - because deserialization has to freshly initialize the named interface.


Home

Back

Contents

Next