Reputation:
Please explain the syntax for Java 8's lambda methods.
There are a lot of explanations out there for what lambda functions are, but I can't find a thorough explanation of the syntax, and I am finding it very difficult to learn to replicate the syntax correctly because I don't understand why they're written as they are.
Here's a common case that I run into, courtesy NetBeans:
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new MainAppJFrame();
});
}
So somehow, the following lambda expression is resolving into an anonymous Runnable
object's run() method:
() -> {
// do stuff
}
The ->
is the lambda syntax proper, right? And the curly braces are simply containing the anonymous method code. Are the parentheses an empty argument, because in this case we're creating a Runnable.run()
method?
This is all rather unclear to me. I assume the compiler knows to instantiate an anonymous Runnable
based on the type expected by the SwingUtilities.invokeLater(Runnable)
method? What would happen if there were two SwingUtilities.invokeLater
methods which differ only in parameter list? Obviously there aren't in this specific case, but it's possible elsewhere:
interface ExampleLambdaConsumer {
public void doSomething(Runnable r);
public void doSomething(java.lang.reflect.Method m);
}
class C implements ExampleLambdaConsumer {
// implementations for doSomething methods here
public static void main(String[] args) {
doSomething(() -> {
// lambda method body here
}
}
}
Upvotes: 49
Views: 17833
Reputation: 1108
The syntax is confusing. It drives me around the twist. If it was not for Intellij, correcting me I would get it wrong.
Compiles OK.
class Scratch {
public static void main(String[] args) {
Predicate<Integer> even = integer -> {return (integer%2 == 0);};
}
}
Compiles OK.
class Scratch {
public static void main(String[] args) {
Predicate<Integer> even = integer -> {return integer%2 == 0;};
}
}
Does NOT Compile, since it is not a statement anymore.
class Scratch {
public static void main(String[] args) {
Predicate<Integer> even = integer -> {return integer%2 == 0};
}
}
Does NOT Compile, since it is not a statement anymore and the return statement has been removed.
class Scratch {
public static void main(String[] args) {
Predicate<Integer> even = integer -> {integer%2 == 0};
}
}
Compiles OK.
class Scratch {
public static void main(String[] args) {
Predicate<Integer> even = integer -> integer%2 == 0;
}
}
So moral of the story is, if you use the brackets what is inside must be a statement, and it must return the type expected by the Functional interface single abstract method (i.e. the only one that is not a default).
I actually wrote it more for myself than anything else, cause it was bugging me.
Upvotes: 1
Reputation: 9402
Syntax is:
arguments -> body
where arguments
can be either
()
a single variable if the type of that variable can be inferred from the context
a sequence of variables, with or without types (or since Java 11, with var
), in parentheses.
Examples: (x)
, (x, y)
, (int x, int y)
, (var x, var y)
(Java 11+).
The following are invalid: (int x, y)
, (x, var y)
, (var x, int y)
and body
can be either an expression or a {...}
block with statements. The expression (other than a method or constructor call) is simply returned, i.e. () -> 2
is equivalent to () -> {return 2;}
In case of lambda expressions like () -> f()
(the body is a method or constructor call expression):
if f()
returns void
, they are equivalent to () -> { f(); }
otherwise, they are equivalent to either () -> { f(); }
or () -> { return f(); })
. The compiler infers it from the calling context, but usually it will prefer the latter.
Therefore, if you have two methods: void handle(Supplier<T>)
and void handle(Runnable)
, then:
handle(() -> { return f(); })
and handle(() -> x)
will call the first one,
handle(() -> { f(); }
will call the second one, and
handle(() -> f())
:
if f()
returns void
or a type that is not convertible to T
, then it will call the second one
if f()
returns a type that is convertible to T
, then it will call the first one
The compiler tries to match the type of the lambda to the context. I don't know the exact rules, but the answer to:
What would happen if there were two SwingUtilities.invokeLater methods which differ only in parameter list?
is: it depends on what would be those parameter lists. If the other invokeLater
had also exactly one parameter and that parameter would be of type that is also an interface with one method of type void*()
, well, then it would complain that it cannot figure out which method you mean.
Why are they written as they are? Well, I think it's because syntax in C# and Scala is almost the same (they use =>
rather than ->
).
Upvotes: 63
Reputation: 3720
The syntax is
(parameter_list_here) -> { stuff_to_do; }
The curly braces can be omitted if it's a single expression. The regular parentheses around the parameter list can be omitted if it's a single parameter.
The syntax only works for all functional interfaces. The @FunctionalInterface annotation tells the compiler that you intend to write such an interface and gives a compile error if you do not meet the requirement(s) - for example it must only have 1 overrideable method.
@FunctionalInterface
interface TestInterface {
void dostuff();
}
Runnable is also declared like that. Other interfaces are not, and they cannot be used with lambda functions.
Now that we've made a new functional interface with a method that takes no parameters, how about we test the question you had about "collision" in the signatures?
public class Main {
private void test(Runnable r) {
}
private void test(TestInterface ti) {
}
public static void main(String[] args) {
test(() -> { System.out.println("test");})
}
@FunctionalInterface
interface TestInterface {
void dostuff();
}
}
Result: compile error: ambigouous call to method test.
You see, the compiler/VM(if done runtime) finds the appropriate methods and their parameter list and sees if the parameter is a functional interface and if it is it creates an anonymous implementation of that interface. Technically (in byte code) it's different from an anonymous class, but otherwise identical (you won't see Main$1.class files).
Your example code (courtesy of Netbeans) can also be replaced with
SwingUtilities.invokeLater(MainAppJFrame::new);
Btw. :)
Upvotes: 13
Reputation: 4539
Lambda Expressions are basically adopted in Java 8 to simplify overriding process function as anonymous functions.
They are mere shortcut to Override old java anonymous functions.
Refer following example:
Suppose you have interface A which has only one method declared like below:
interface A{
void print();
}
now with old java style we'll override this in anonymous way like below:
new A() {
@Override
public void print() {
System.out.println("in a print method");
}
};
additionally now with java 8 lambda expression we'll use it like below:
() -> System.out.println("in a print method");
Here we can pass parameters required to method before ->
operator
and then overridden body after ->
operator.
the only more settings that we need to achieve this is that we need to declare interface with @FunctionalInterface as below:
@FunctionalInterface
interface A{
void print();
}
Note: - A lambda expression can only be used for a "functional" interface that has only one non-default method.
Upvotes: 12