This is an entry about what method references are, and how they're meant to replace inner classes and lambdas for some cases. Method references are a new language feature Java 8 introduced, and will be coming to Android with the recent changes to the Jack & Jill compiler. They're also available in Kotlin!
The first thing to understand is that a Single Method Interface (SMI) is an interface with a single method inside. Simple.
interface MyCallback {
void onResult(String result);
}
Functionality wise, all single method interfaces with the same number of parameters fulfil the same purpose, and can be equivalent:
interface MyCallback {
void onResult(String result);
}
interface OnErrorListener {
void onError(Exception e);
}
interface OnClick {
void onClick(Integer position, User userClicked);
}
so they can be generalized using generics:
interface Action1<T> {
void call(T result);
}
interface Action2<T, U> {
void call(T t, U u);
}
...
interface Action7<T, U, V, W, X, Y, Z> {
void call(T t, U u, V v, W w, X x, Y y, Z z);
}
and the same can be done for methods with results:
interface StringCreator(){
String create();
}
interface StringTransformer(){
String create(Integer int);
}
become:
interface Func0<R>(){
R call();
}
interface Func1<T, R>(){
R call(T t);
}
...
interface Action7<T, U, V, W, X, Y, Z, R> {
R call(T t, U u, V v, W w, X x, Y y, Z z);
}
What is the real life application of this? When you're setting a callback or a generation function you used to write:
networkRequester.call(new MyCallback(){
@Override void onResult(String result)
{ /* Your result */ }
}, new OnErrorListener() {
@Override void onResult(Exception e)
{ /* Your error */ }
})
which with lambdas becomes:
networkRequester.request(
(String result) -> { /* Your result */ },
(Exception e) -> { /* Your error */ })
Now, those commented blocks onResult and onError will contain some code that's specific to our solution. Because you don't really want to have code inside an anonymous class and you like unitesting functionality, normally a lambda result is another function call to where execution should follow to. This call is commonly named continuation in the functional world.
networkRequester.request(
(String result) -> { this.toastSuccess(result); },
(Exception e) -> { Log.exception(e); })
Method references live here! They are a language feature from Java and Kotlin that allow you to replace a lambda calling a method by just a reference to that method. To use it you have to use the new ::
operator on where the function lives. For an static it'd be StringUtils::isEmpty()
, for a method you have to use the object or this, like in this::showToast()
or database::storeString()
. Note that you don't have to provide the parameters for the function, you're just saying you're referencing it.
networkRequester.request(
this::toastSuccess(),
Log::exception());
See the difference in line count, readability, and expressing intention from the first example until here! Wow! And because behind the scenes all SMI are equivalent as we stablished above, all methods are compatible with any interfaces as long as they have the same number of parameters. You don't even have to write your own callbacks, as you can use FuncN<T>
and ActionN<T>
instead, or use the new @FunctionalInterface annotation on existing ones.
What cool new things can be done with this paradigm change? If you followed the entry on the Functional toolset, the toolset gives you new tools to interact with functions.
The most obvious one is using RxPartialApplication to make functions with may parameters into functions with fewer. For example, Log.exception(Exception exception)
is not a function on the Android framework, but it can be created in-place by partially applying [Log.e(String tag, String message, Throwable tr)
](https://developer.android.com/reference/android/util/Log.html#e(java.lang.String, java.lang.String, java.lang.Throwable)) with a tag and message.
networkRequester.request(
this::toastSuccess(),
RxPartialActions.apply(Log::e(), "TAG", "Error seen!");
or maybe you want to do multiple actions on success by using RxActions:
networkRequester.request(
RxActions.act(
this::toastSuccess(),
AppAnalytics::onNetworkSuccess()),
RxPartialActions.apply(Log::e(), "TAG");
With these bases we can move forward to the next topic that brings us closer to a better UI layer: how to convert inheritance into composition by using function parameters, and how to design APIs tailor made for your app.
The "Intro to FRP" Series
- Functional libraries for Java
- Advantages of using generic method references, Functions and Actions as parameters
- Transforming APIs based on inheritance to simpler, composable, ones by means of Functions
- Review of Android lifecycle issues with traditional callbacks
- The reactive approach to composable and cancellable callbacks with observable sequences
- The Observable in RxJava as a composable sequence for your existing code