Motivation
Method reference is something I always tried to get into. However, always felt a little bit too overwhelming for me when I tried to get into it, because unlike lambda (which also took a great deal to get used to), method reference comes in different flavours (static method reference, instance method reference..)
I am now a tiny bit confident that I understand about method reference. So I'll share exactly how to get into method reference with examples. Of course, it's assumed that you know lambda (as method reference really comes after lambda)
1 Unbound method reference
Understanding unbound method reference took me a while, and for a good reason. In the end, it all comes down to this:
Say instance a is of type A, b is of type B, ...
We can replace the lambda expression `(a,b,c,d,...)->a.method(b,c,d,...)` with A::method
This stackoverflow post clearly puts it, so does Java 8 in Action.
First place that I've used method reference is Student::getName. This is used instead of s->s.getName(), meaning that whatever is passed in as a parameter to map's function is used to invoke .getName() on it. It makes sense because Student#getName() is an instance method, meaning that there has to be some student instance that you invoke .getName() on. Conveniently, since the map accepts a function that looks like s ->{//return something}, s is used as the instance to invoke getName() on.
However, the second use of instance method reference is confusing - String::compareToIgnoreCase. This is clearly an instance method declared in String, so you normally use it like this: (a,b) -> a.compareToIgnoreCase(b). In other words, compareToIgnoreCase has to be invoked on a string instance!
In the previous case, it was quite clear which instance was to be invoked of getName, since there's only one parameter. But in this case, there are two possibilities, since .sorted will pass two parameters to the function we supply. By the way, we know it can't just randomly be used like String.compareToIgnoreCase(a,b) because it's not how it was defined (it's not even a static method).
Here's where the rule shines: the first parameter is always used as the instance that the method is invoked on, and all the other parameters are passed in as parameters.
compareToIgnoreCase takes one String instance, and is invoked on one String instance.
Stream#sorted() expects a comparator functional interface, that passes 2 parameters - call it a, b. Then, Stream#sorted(String::compareToIgnoreCase) can be understood like this:
1. compareToIgnoreCase is an instance method of String, so will be invoked on 'a'
2. compareToIgnoreCase requires one parameter, and 'b' is passed in as the parameter
That's it!
2 Bound method reference
This is less general than unbound method reference, because instead of using one of the parameters as the object for method invocation, we supply an object to invoke the method on - and all the parameters are simply passed in as parameters to that method.
The following uses bound method reference:
For reference, here's method declaration of instanceMethodCompareDifficulty(), which is an instance method of DomainUtil:
Bound method reference usage here is pretty easy to understand if you understood Unbound method reference. Here's how I'd parse it:
1. We are using DomainUtils#instanceMethodCompareDifficulty, which is an instance method. so we need an object of DomainUtils to invoke this method on.
2. Lucky for us, we have new DomainUtils() as the instance to invoke instanceMethodCompareDifficulty() on.
3. instanceMethodCompareDifficulty takes two parameters, which is passed from Stream#sorted() (remember? Stream#sorted it passes two arguments to the lambda so that lambda that takes two parameters can process them)
3. Static method reference
Static method reference is perhaps the easiest one, because all the arguments that is passed in are simply used as parameters for the method. That's because we don't need to supply it an instance to invoke a method on.
Here's an example, where Course.Difficulty::staticCompare is the static method reference.
Here's the method declaration for Course.Difficulty#staticCompare
Here's how to understand .sorted(Course.Difficulty::staticCompare)
1. .sorted((a,b)->Course.Difficulty.staticCompare(a,b)) is original lambda form, which is equivalent to the .sorted(Course.Difficulty::staticCompare).
2. essentially, a,b which are parameters are simply passed to the method invocation parameters. Method is invoked in static context (Course.Difficulty)
When to use method reference
Method reference makes code readable and succinct with even less verbosity than lambda. (a,b)->a.compareTo(b) can be written as String::compareTo. However, here 'a,b' weren't that meaningful but they could've been meaningful. In that case, it's better to leave those parameters visible to the reader, so lambda is preferred in that case. And sometimes class name can be huge, which can clutter the code readability. In any case, readability determines the call.
'Book > Effective Java' 카테고리의 다른 글
[Effective Java] Item 45: Use streams judiciously (0) | 2021.11.13 |
---|---|
[Effective Java] Item 42: Prefer lambda to anonymous classes (0) | 2021.11.07 |
[Effective Java] Item 38: emulate extensible enums with interfaces (0) | 2021.11.06 |
[Effective Java] Item 37: Using EnumMap as a way to group items by enum (0) | 2021.11.05 |