Every once in a while we all run across something that it takes a while to get our head around. For me (at least with Java 8) it was some of the syntax regarding the use of method references in functional programming. I didn’t have the slightest problem using lambdas, for example, but I kind of blocked on some cases of how to write code that accepted them – particularly when I wanted to use method references instead of “vanilla lambda expressions.” For me, at least, part of the problem was determining the signature for the method that was going to accept the lambda or method reference. So now that I finally have it sorted out in my head, I’m writing it up so that you don’t have to go through what I did.
Code for this post can be found on GitHub.
So, as you’ve probably already figured out, Java 8 lets you use method references as “syntactic sugar” for lambda expressions. You provide the method references, and Java (essentially) creates the lambda for you. In the Oracle documentation, Oracle states that there are four kinds of method references:
- Reference to a constructor. For this, the reference syntax is
ClassName::new
- Reference to a static method. For this, the reference syntax is
ClassName::methodName
- Reference to an instance method of a particular object. For this, the reference syntax is
objectReference::methodName
- Reference to an instance method of an arbitrary object of a particular type. For this, the reference syntax is
ClassName::methodName
– the same as for a static method
So let’s suppose that you’re writing some code that’s going to use method references such as these. What is the syntax you need to use to declare the lambda object that your code is going to accept? That, as well as the “equivalent lambda expression” for a method reference is the subject of this post.
Reference to a Constructor
So what is a constructor? It’s something you call that returns an object.
So let’s see examples in action:
public class MethodNameConstructor { private long createdAt; public MethodNameConstructor() { createdAt = System.currentTimeMillis(); } public MethodNameConstructor(long createdAt) { this.createdAt = createdAt; } public long getCreatedAt() { return createdAt; } public static void main(String[] args) { useSupplier(() -> new MethodNameConstructor()); useSupplier(MethodNameConstructor::new); useFunction((x) -> new MethodNameConstructor(x), 123L); useFunction(MethodNameConstructor::new, 123L); useSupplier(() -> new MethodNameConstructor(123L)); } public static void useSupplier(Supplier<MethodNameConstructor> supplier) { MethodNameConstructor object = supplier.get(); System.out.println("Got a MethodNameConstructor with createdAt " + object.getCreatedAt()); } public static void useFunction(Function<Long, MethodNameConstructor> function, long argument) { MethodNameConstructor object = function.apply(argument); System.out.println("Built a MethodNameConstructor with createdAt " + object.getCreatedAt()); } }
The class MethodNameConstructor
has two constructors – one that takes no arguments, and one that takes one argument. Let’s take the more common case of a zero-argument constructor first. Since the object passed in to our code isn’t going to take any arguments and is going to return an object, it is a Supplier
. As you can see from Lines 22 and 23, the method reference MethodNameConstructor::new
is identical to an explicit lambda that takes nothing in – indicated by ()
, which is sometimes called the “hamburger” – and returns an object.
If, on the other hand, the constructor is going to take a value which our functional-interface-using code is going to provide, then you’re not dealing with a Supplier
any more. The fact that you have to pass in a value means you’re now dealing with a Function
, which is a functional interface that takes a single argument and returns a value. (There is an Oracle-supplied BiFunction
that takes two arguments, and you can write functional interfaces for ones that take more if you want.) Hence, on lines 25 and 26, where we’re going to be supplying the argument to the constructor, we use a method that takes a Function
instead of Supplier
. Note, however, that on line 26 we’re using the exact same syntax – MethodNameConstructor::new
that we used on line 23, even though we’re now referring to a different constructor. Java is smart enough to figure this out when it builds the actual lambdas.
Finally, of course, if the constructor call needs an argument but we’re not going to provide it ourself, then we have to fall back to an explicit lambda expression as shown on line 28, and make whoever is calling our function turn the constructor into a Supplier
. The constructor still needs an argument, but the functional interface does not.
Reference to a static method
A reference to a static method looks very much like a reference to a constructor – it uses the syntax ClassName::methodName
. (new
is, effectively, a synthetic static method representing a constructor.) Here again, the nature of the functional interface that you will declare depends on the number of arguments you will be providing to the functional interface and whether or not the functional interface returns a value.
public class MethodNameStatic { public static void zeroInZeroOut() { System.out.println("Called zeroInZeroOut"); } public static void oneInZeroOut(String a) { System.out.println("Called oneInZeroOut with " + a); } public static void twoInZeroOut(String a, String b) { System.out.println("Called twoInZeroOut with " + a + " and " + b); } public static String zeroInOneOut() { return "zeroInOneOut"; } public static String oneInOneOut(String input) { return input.toUpperCase(); } public static String twoInOneOut(String input1, String input2) { return input1 + input2; } public static void main(String[] args) { useRunnable(() -> MethodNameStatic.zeroInZeroOut()); useRunnable(MethodNameStatic::zeroInZeroOut); useConsumer((x)->{MethodNameStatic.oneInZeroOut(x);}, "input"); useConsumer(MethodNameStatic::oneInZeroOut, "input"); useBiConsumer((x,y)->{MethodNameStatic.twoInZeroOut(x,y);}, "input1", "input2"); useBiConsumer(MethodNameStatic::twoInZeroOut, "input1", "input2"); useSupplier(() -> MethodNameStatic.zeroInOneOut()); useSupplier(MethodNameStatic::zeroInOneOut); useFunction((x) -> MethodNameStatic.oneInOneOut(x), "abc"); useFunction(MethodNameStatic::oneInOneOut, "abc"); useBiFunction((x,y)->MethodNameStatic.twoInOneOut(x,y), "abc", "def"); useBiFunction(MethodNameStatic::twoInOneOut, "abc", "def"); } public static void useConsumer(Consumer<String> consumer, String input) { consumer.accept(input); } public static void useBiConsumer(BiConsumer<String, String> consumer, String input1, String input2) { consumer.accept(input1, input2); } public static void useSupplier(Supplier<String> supplier) { System.out.println("Supplier returned " + supplier.get()); } public static void useFunction(Function<String, String> function, String input) { System.out.println("Calling function with '" + input + "' returned '" + function.apply(input) + "'"); } public static void useBiFunction(BiFunction<String, String, String> function, String input1, String input2) { System.out.println("Calling function with '" + input1 + "' and '" + input2 + "' returned '" + function.apply(input1, input2) + "'"); } public static void useRunnable(Runnable x) { x.run(); } }
Here, the functional interfaces are all pretty straightforward:
- A functional interface where you provide one value and get nothing back is a
Consumer
. - A functional interface where you provide two values and get nothing back is a
BiConsumer
. - A functional interface where you provide nothing and get a value back is a
Supplier
. - A functional interface where you provide one value and get something back is a
Function
. - A functional interface where you provide two values and get something back is a
BiFunction
. - The only one that might seem a little tricky is if you provide nothing and get nothing back. Oracle didn’t provide a new interface for this when they created Java 8. Instead, they realized that
Runnable
already met this criteria, so they enhancedRunnable
so that it is also a functional interface.
Reference to an instance method of a particular object
In the previous two sections, we’ve been dealing with methods that aren’t specific to a particular object. If you want to refer to instance methods of a specific, known object, the syntax changes – it now becomes objectReference::methodName
. Other than this, things are pretty similar to the case where we’re passing references to static method names.
public class MethodNameExplicit { public void zeroInZeroOut() { System.out.println("Called zeroInZeroOut"); } public void oneInZeroOut(String a) { System.out.println("Called oneInZeroOut with " + a); } public void twoInZeroOut(String a, String b) { System.out.println("Called twoInZeroOut with " + a + " and " + b); } public String zeroInOneOut() { return "zeroInOneOut"; } public String oneInOneOut(String input) { return input.toUpperCase(); } public String twoInOneOut(String input1, String input2) { return input1 + input2; } public static void main(String[] args) { MethodNameExplicit object = new MethodNameExplicit(); useConsumer((x)->object.oneInZeroOut(x), "input"); useConsumer(object::oneInZeroOut, "input"); useBiConsumer((x,y)->object.twoInZeroOut(x,y), "input1", "input2"); useBiConsumer(object::twoInZeroOut, "input1", "input2"); useSupplier(() -> object.zeroInOneOut()); useSupplier(object::zeroInOneOut); useFunction((x) -> object.oneInOneOut(x), "abc"); useFunction(object::oneInOneOut, "abc"); useBiFunction((x,y)->object.twoInOneOut(x,y), "abc", "def"); useBiFunction(object::twoInOneOut, "abc", "def"); useRunnable(() -> object.zeroInZeroOut()); useRunnable(object::zeroInZeroOut); } public static void useConsumer(Consumer<String> consumer, String input) { consumer.accept(input); } public static void useBiConsumer(BiConsumer<String, String> consumer, String input1, String input2) { consumer.accept(input1, input2); } public static void useSupplier(Supplier<String> supplier) { System.out.println("Supplier returned " + supplier.get()); } public static void useFunction(Function<String, String> function, String input) { System.out.println("Calling function with '" + input + "' returned '" + function.apply(input) + "'"); } public static void useBiFunction(BiFunction<String, String, String> function, String input1, String input2) { System.out.println("Calling function with '" + input1 + "' and '" + input2 + "' returned '" + function.apply(input1, input2) + "'"); } public static void useRunnable(Runnable x) { x.run(); } }
One thing to note here is that, technically, what is generated is slightly different. In our previous two cases, the lambdas or method names didn’t rely on the existence of any particular object. Here, however, the lambda code and the corresponding method reference are bound to a specific object. Thus, if you want to get somewhat picky about it, these represent “closures” rather than “lambdas,” since the objects that implement the functional interfaces capture the object reference.
Reference to an instance method of an arbitrary object of a particular type
This was the one that took me a little extra work to wrap my head around. Unlike the previous case, what we’re saying here is “I want to call this method on an object, but I don’t necessarily have the specific object around as yet – I’ll provide that object later.” For this, the reference syntax is ClassName::methodName
– the same as for a static method.
The catch behind this is that taking this approach changes the “shape” of the functional interface. Let’s take the case of a simple setter – a method that takes one input and returns nothing. At first glance, this would appear to be a Consumer
. What you have to remember, however, is that when you speak of a Consumer
you’re not speaking of the method reference, you’re speaking of the lambda code itself, which is a different object. If you don’t know the object at the time you build the lambda, then a lambda that’s going to call a setter needs two inputs – one the value being passed to the setter, and one which identifies the object on which the setter will be called. Thus, as compared to the previous case, all the functional interfaces must get an additional argument.
Thus, in the code below:
Runnable
changes toConsumer
Consumer
changes toBiConsumer
BiConsumer
changes toTriConsumer
Supplier
changes toFunction
Function
changes toBiFunction
BiFunction
changes toTriFunction
TriConsumer
? TriFunction
? No such animal, I hear you saying. Not in the standard Java packages, but it’s very easy to define your own, which the following code does.
public class MethodNameGeneric { public void zeroInZeroOut() { System.out.println("Called zeroInZeroOut"); } public void oneInZeroOut(String a) { System.out.println("Called oneInZeroOut with " + a); } public void twoInZeroOut(String a, String b) { System.out.println("Called twoInZeroOut with " + a + " and " + b); } public String zeroInOneOut() { return "zeroInOneOut"; } public String oneInOneOut(String input) { return input.toUpperCase(); } public String twoInOneOut(String input1, String input2) { return input1 + input2; } public static void main(String[] args) { MethodNameGeneric object = new MethodNameGeneric(); useConsumer((x) -> x.zeroInZeroOut(), object); useConsumer(MethodNameGeneric::zeroInZeroOut, object); useBiConsumer((x, y) -> x.oneInZeroOut(y), object, "input"); useBiConsumer(MethodNameGeneric::oneInZeroOut, object, "input"); useTriConsumer((x, y, z) -> x.twoInZeroOut(y, z), object, "input1", "input2"); useTriConsumer(MethodNameGeneric::twoInZeroOut, object, "input1", "input2"); useFunction((x) -> x.zeroInOneOut(), object); useFunction(MethodNameGeneric::zeroInOneOut, object); useBiFunction((x, y) -> x.oneInOneOut(y), object, "def"); useBiFunction(MethodNameGeneric::oneInOneOut, object, "def"); useTriFunction((x, y, z) -> x.twoInOneOut(y, z), object, "def", "ghi"); useTriFunction(MethodNameGeneric::twoInOneOut, object, "def", "ghi"); } public static void useConsumer(Consumer<MethodNameGeneric> consumer, MethodNameGeneric object) { consumer.accept(object); } public static void useBiConsumer( BiConsumer<MethodNameGeneric, String> consumer, MethodNameGeneric object, String input2) { consumer.accept(object, input2); } public static void useTriConsumer( TriConsumer<MethodNameGeneric, String, String> consumer, MethodNameGeneric object, String input1, String input2) { consumer.accept(object, input1, input2); } public static void useFunction( Function<MethodNameGeneric, String> function, MethodNameGeneric object) { System.out.println("Calling function returned '" + function.apply(object) + "'"); } public static void useBiFunction( BiFunction<MethodNameGeneric, String, String> function, MethodNameGeneric object, String input) { System.out.println("Calling function with '" + input + "' returned '" + function.apply(object, input) + "'"); } public static void useTriFunction( TriFunction<MethodNameGeneric, String, String, String> function, MethodNameGeneric object, String input1, String input2) { System.out .println("Calling function with '" + input1 + "' and '" + input2 + "' returned '" + function.apply(object, input1, input2) + "'"); } @FunctionalInterface public static interface TriConsumer<T, R, S> { void accept(T t, R r, S s); } @FunctionalInterface public static interface TriFunction<T, R, S, U> { U apply(T t, R r, S s); } }
I should also point out that one of the other items it took me a while to grok was the fact that the method name in the functional interfaces has nothing to do with the method name being passed in. Thus, I’m creating a TriConsumer
using the method reference MethodNameGeneric::twoInZeroOut
, however the “active” function name for a TriConsumer
is accept
. What you have to remember is that Java is generating “wrapper code” for you, and it is this wrapper code that functions like TriConsumer
are calling. If we didn’t have lambdas and method references, then we’d have had to replace MethodNameGeneric::twoInZeroOut
with something like:
new TriConsumer<MethodNameGeneric, String, String>() { void accept(MethodNameGeneric t, String r, String s) { t.twoInZeroOut(r, s); } }
This is the code that Java effectively generates for us. For those curious, the line
useTriConsumer(MethodNameGeneric::twoInZeroOut, object, "input1", "input2");
ends up looking something like this under the hood:
useTriConsumer((TriConsumer<MethodNameGeneric, String, String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V, twoInZeroOut(java.lang.String java.lang.String ), (Lcom/silverbaytech/blog/lambdaPlay/methodNames/MethodNameGeneric;Ljava/lang/String;Ljava/lang/String;)V)(), object, "input1", "input2");
The LambdaMetafactory
class takes care of building the object that’s actually passed to useTriConsumer
. The gobbledygook in the middle are signature strings that Java uses to specify what methods look like.
A Practical Use
Obviously, lambdas and functional programming gets used a lot with streams, sorting, searching and so forth. But there are plenty of other places where this can be useful as well. As one example, consider the problem of parsing object data out of some external format. Depending on what you’re parsing, there can be a lot of repetitive code. One way that can be made somewhat more concise is to separate the process of extracting a value from the process of putting the value where it needs to end up.
Let’s take a very simple example – we have a standard Java Properties
object, possibly loaded from a file, and we want to pull various items out of it into an object.
Here’s our sample object:
public class SampleBean { private String stringItem1; private String stringItem2; private Integer integerItem1; private Integer integerItem2; public SampleBean() { } public String getStringItem1() { return stringItem1; } public void setStringItem1(String stringItem1) { this.stringItem1 = stringItem1; } public String getStringItem2() { return stringItem2; } public void setStringItem2(String stringItem2) { this.stringItem2 = stringItem2; } public Integer getIntegerItem1() { return integerItem1; } public void setIntegerItem1(Integer integerItem1) { this.integerItem1 = integerItem1; } public Integer getIntegerItem2() { return integerItem2; } public void setIntegerItem2(Integer integerItem2) { this.integerItem2 = integerItem2; } }
Nothing special. What we want to do is to be able to pull items out of the Properties
file, convert them to the appropriate format, and store them into the bean. Then we’ll add a method to the bean that looks like this:
public void load(Properties properties) { LambdaParser.transferString(this, SampleBean::setStringItem1, properties, "s1"); LambdaParser.transferString(this, SampleBean::setStringItem2, properties, "s2"); LambdaParser.transferInteger(this, SampleBean::setIntegerItem1, properties, "i1"); LambdaParser.transferInteger(this, SampleBean::setIntegerItem2, properties, "i2"); }
Basically, the bean knows what property names correspond to what methods, and it’s basically asking for things like “find the property named i1
, convert it to an Integer
and then set it on me using the method setIntegerItem1
.” Using functional programming, we can define the two methods above as:
public class LambdaParser { public static <T> void transferString(T object, BiConsumer<T,String> setter, Properties source, String name) { setter.accept(object, source.getProperty(name)); } public static <T> void transferInteger(T object, BiConsumer<T,Integer> setter, Properties source, String name) { setter.accept(object, Integer.parseInt(source.getProperty(name))); } }
Now, of course, this is an overly simplified example, and the value of using the functional programming interface over just writing methods like getIntegerFromProperties
might not justify the slight obtuseness of the code. But this approach is extensible to calling methods with arbitrary numbers of parameters. As an example, suppose we add
public void setPrice(BigDecimal amount, String currency) { this.amount = amount; this.currency = currency; } public BigDecimal getAmount() { return amount; } public String getCurrency() { return currency; }
to our bean. We can then add another parser method
public static <T> void transferPrice(T object, TriConsumer<T,BigDecimal,String> setter, Properties source, String name) { BigDecimal amount = new BigDecimal(source.getProperty(name + ".amount")); String currency = source.getProperty(name + ".currency"); setter.accept(object, amount, currency); }
and another line to our load
method
LambdaParser.transferPrice(this, SampleBean::setPrice, properties, "price");
Thus, using method references doesn’t have to be restricted to simple cases like using map
in stream processing – it can be a very flexible way of “hooking together” generic code, or reducing the amount of DRY (Don’t Repeat Yourself) in code.
The post Java 8 Lambdas and Method References is from the Silver Bay Technologies Blog.