So suppose my application does lots of repetitive work, for example let's say my application checks lots of various Lists if they're empty or not. There are two methods by which I can accomplish this functionality - (there maybe other methods but since my goal is to understand the difference of the two methods and not the functionality itself here we go)
Method 1 - Tradition Method
public boolean isEmptyOrNull(List list)
{
return list != null && !list.isEmpty();
}
Method 2 - Lambda Way
Assuming we have created a functional interface with class name Demo and boolean isEmptyOrNull as the function.
Demo var = list -> list != null && !list.isEmpty();
So each time I wish to check a list I can either use Method 1 or 2 by using isEmptyOrNull(myList) or var.isEmptyOrNull(myList) respectively.
My question is why should I use Method 1 and not Method 2 and vice versa. Is there some performance aspect or some other aspect as to why I should prefer one method over the other !?
Oof, where to start.
Your idea of what null is, is broken.
isEmptyOrNull is a code smell. You shouldn't have this method.
null is a stand-in value that necessarily can mean 'not initialised', because that's built into java itself: Any field that you don't explicitly set will be null. However, it is very common in APIs, even in java.* APIs, that null can also mean 'not found' (such as when you call map.get(someKeyNotInTheMap)), and sometimes also 'irrelevant in this context', such as asking a bootstrapped class for its ClassLoader.
It does not, as a rule, mean 'empty'. That's because there is a perfectly fine non-null value that does a perfect job representing empty. For strings, "" is the empty string, so use that, don't arbitrarily return null instead. For lists, an empty list (as easy to make as List.of()) is what you should be using for empty lists.
Assuming that null semantically means the exact same thing as List.of() is either unneccessary (the source of that list wouldn't be returning null in the first place, thus making that null check unneccessary) or worse, will hide errors: You erroneously interpret 'uninitialized' as 'empty', which is a fine way to have a bug and to have that result your app doing nothing, making it very hard to find the bug. It's much better if a bug loudly announces its presence and does so by pointing exactly at the place in your code where the bug exists, which is why you want an exception instead of a 'do nothing, silently, when that is incorrect' style bug.
Your lambda code does not compile
Unless Demo is a functional interface that has the method boolean isEmptyOrNull(List list);, that is.
The difference
The crucial difference is that a lambda represents a method that you can reference. You can pass the lambda itself around as a parameter.
For example, java.util.TreeSet is an implementation of set that stores all elements you put inside in sorted order by using a tree. It's like building a phonebook: To put "Ms. Bernstein" into the phonebook, you open the book to the middle, check the name there, and if it's 'above' 'Bernstein', look at the middle of the first half. Keep going until you find the place where Bernstein should be inserted; even in a phonebook of a million numbers, this only takes about 20 steps, which is why TreeSet is fast even if you put tons of stuff in there.
The one thing TreeSet needs to do its job is a comparison function: "Given the name 'Maidstone' and 'Bernstein', which one should be listed later in the phone book"? That's all. If you have that function then TreeSet can do its job regardless of the kind of object you store in it.
So let's say you want to make a phone book that first sorts on the length of names, and only then alphabetically.
This requires that you pass the function that decrees which of two names is 'after' the other. Lambdas make this easy:
Comparator<String> decider = (a, b) -> {
if (a.length() < b.length()) return -1;
if (a.length() > b.length()) return +1;
return a.compareTo(b);
};
SortedSet<String> phonebook = new TreeSet<>(decider);
Now try to write this without using lambdas. You won't be able to, as you can't use method names like this. This doesn't work:
public void decider(String a, String b) {
if (a.length() < b.length()) return -1;
if (a.length() > b.length()) return +1;
return a.compareTo(b);
}
public SortedSet<String> makeLengthBook() {
return new TreeSet<String>(decider);
}
There are many reasons that doesn't work, but from a language design point of view: Because in java you can have a method named decider, and also a local variable named decider. You can write this::decider which would work - that's just syntax sugar for (a, b) -> this.decider(a, b); and you should by all means use that where possible.
There is no advantage in your example. Lamdas are often used in Object Streams, e.g for mapping or filtering by defining "adhoc functions" (that's what lambdas are).
Example: You have a list of strings named allStrings that you want to filter
List<String> longStrings = allStrings.stream()
.filter(s -> s.length() > 5) // only keep strings longer than 5
.collect(Collectors.toList()); // collect stream in a new list
Lambdas were added to introduce functional programming in java. It could be used as a shorthand for implementing single method interfaces (Functional interfaces). In the above example you provided, there is no much advantage. But lambdas could be useful in the below scenario:
Before lambdas
public Interface Calc{
int doCalc(int a, int b);
}
public class MyClass{
public void main(String[] args){
Calc x = new Calc() {
#Override
public int doCalc(int a, int b) {
return a + b;
}
};
System.out.println(x.doCalc(2, 3));
}
}
But with lambdas this could be simplified to
public class MyClass{
public void main(String[] args){
BiFunction<Integer, Integer, Integer> doCalc= (a, b) -> a + b;
System.out.println(doCalc.apply(2, 3));
}
}
This is especially helpful for implementing event listeners (in Android), in which case there are a lot of interfaces provided as part of the API with methods like onClick etc. In such cases lambdas could be useful to reduce the code.
Also with Java 8, streams were introduced and lambdas could be passed to filter/map the stream elements. Stream allow more readable code than traditional for loop / if-else in most cases.
Related
I've been watching Douglas Schmidt classes on Parallel Java. He introduces Lambda x method referencing syntax discussion, highlighting how the last one is preferable, as it makes clearer what the code is actually doing, not what the programmer is trying to do with the code, even more than forEach approach.
String[] names = {"first", "Second", "third"};
Arrays.sort(names, (n1,n2) -> n1.compareToIgnoreCase(n2));
Arrays.sort(names, String::compareToIgnoreCase); //preferable
For example, that approach mitigates the chances of programmer making mistakes inside lambda function: passing the wrong argument, inverting arguments order, adding collateral effects, etc.
Then he introduces Functional interfaces, an interface that contains only an abstract method, implementing its own interface runTest with an abstract method factorial():
private static <T> void runTest(Function<T,T> factorial, T n) {
System.out.println(n+ " factorial = " + factorial.apply(n));
}
private static class ParallelStreamFactorial{
static BigInteger factorial(BigInteger n) {
return LongStream
.rangeClosed(1, n.longValue())
.parallel()
.mapToObj(BigInteger::valueOf)
.reduce(BigInteger.ONE, BigInteger::multiply);
}
}
Calling it with the following syntax:
import java.math.BigInteger;
import java.util.function.Function;
import java.util.stream.LongStream;
public static void main(String[] args) {
BigInteger n = BigInteger.valueOf(3);
runTest(ParallelStreamFactorial::factorial, n);
}
The code works and prints
3 factorial = 6
As I'm studying lambdas, I tried to interchange method reference syntax for lambda syntax, and managed to using:
public static void main(String[] args) {
BigInteger n = BigInteger.valueOf(3);
runTest((number)->ParallelStreamFactorial.factorial(number), n);
}
Which also worked.
Then he proceeds to explain built-in interfaces, such as Predicate<T>{boolean test(T t);}, and that's where I got stuck.
I managed to implement a Predicate<Integer> that tests if the integer is bigger than 0 using the three syntaxes:
Instantiating an object myPredicate from a class that implements Predicate<Integer>
Instantiating an object lambdaPredicate from a lambda
Instantiating an object methodReferencePredicatefrom a method reference:
import java.util.function.Function;
import java.util.function.Predicate;
public class MyPredicates {
public static void main(String[] args) {
Predicate<Integer> constructorPredicate = new myPredicate();
System.out.println(constructorPredicate.test(4));
Predicate<Integer> lambdaPredicate = (number)-> number > 0;
System.out.println(lambdaPredicate.test(4));
Predicate<Integer> methodReferencePredicate = myMethodReference::myTest;
System.out.println(methodReferencePredicate.test(4));
}
private static class myPredicate implements Predicate<Integer>{
public boolean test(Integer t) {
return t>0;
}
}
private static class myMethodReference{
public static boolean myTest(Integer t) {
return t>0;
}
}
}
And then calling their .test() methods. They're all three working and printing true.
However I would like to "instantiate and call" everything in a single line, as he did in his example. It seems like his code is inferring the type of the argument passed (I may be wrong) but it's definitely running automatically.
I tried different things:
Predicate<Integer>(myMethodReference::myTest, 4);
Predicate(myMethodReference::myTest, 4);
Predicate<Integer>((number) -> myMethodReference.myTest(number), 4);
Predicate((number) -> myMethodReference.myTest(number), 4);
But none of them work.
They throw:
Syntax error, insert ";" to complete LocalVariableDeclarationStatement
and
The method Predicate(myMethodReference::myTest, int) is undefined for the type MyPredicates
Errors. I also don't even know the name of what he's doing in that single line to properly search better on internet for references.
What's the correct syntax for that, whether by method reference or lambdas?
You've made things far too complicated.
There is no point in lambdas if you want to 'execute them immediately'.
Here is how you run your my test code 'immediately':
System.out.println(number > 4);
Why mess with lambdas? They just make matters confusing here.
The very point of a lambda is two-fold:
A way to transmit code itself to other contexts.
Control flow abstraction.
In java in particular, option 2 is an evil - it makes code ugly, harder to reason about, introduces pointless distractions, and in general should be avoided... unless you're employing it to avoid an even greater evil. That happens plenty - for example, a reasonable 'stream chain' is generally better even though its control flow abstraction. I'd say this:
int total = list.stream()
.filter(x -> x.length() < 5)
.mapToInt(Integer::valueOf)
.sum();
is the lesser evil compared to:
int total = 0;
for (var x : list) {
if (x.length() < 5) continue;
total += Integer.parseInt(x);
}
but it is a pretty close call.
Why is it 'evil'? Because lambdas in java are non transparent in 3 important ways, and this non-transparency is a good thing in the first case, but a bad thing in the second. Specifically, lambdas are not transparent in these ways:
Lambdas cannot change or even read local variables from outer scope unless they are (effectively) final.
Lambdas cannot throw checked exceptions even if the outer scope would handle them (because they catch them or the method you're in declared throws ThatException).
Lambdas cannot do control flow. You can't break, continue, or return from within a lambda to outside of it.
These 3 things are all useful and important things to be doing when you're dealing with basic control flow. Therefore, lambdas should be avoided as you create a bunch of problems and inflexibility by using them... unless you've avoided more complexity and inflexibility of course. It's programming: Nothing is ever easy.
The notion of bundling up code is therefore much more useful, because those non-transparencies turn into upside:
If you take the lambda code and export it to someplace that runs that code much later and in another thread, what does it even mean to modify a local variable at that point? The local variable is long gone (local vars are ordinarily declared on stack and disappear when the method that made them ends. That method has ended; your lambda survived this, and is now running in another context). Do we now start marking local vars as volatile to avoid thead issues? Oof.
The fact that the outer code deals with a checked exception is irrelevant: The lexical scope that was available when you declared the lambda is no longer there, we've long ago moved past it.
Control flow - breaking out of or restarting a loop, or returning from a method. What loop? What method? They have already ended. The code makes no sense.
See? Lambda lack of transparency is in all ways great (because they make no sense), if your lambda is 'travelling'. Hence, lambdas are best used for this, they have no downsides at that point.
Thus, let's talk about travelling lambdas: The very notion is to take code and not execute it. Instead, you hand it off to other code that does whatever it wants. It may run it 2 days from now when someone connects to your web server, using path /foobar. It may run every time someone adds a new entry to a TreeSet in order to figure out where in the tree the item should be placed (that's precisely the fate of the lambda you pass to new TreeSet<X>((a, b) -> compare-a-and-b-here).
Even in control flow situations (which are to be avoided if possible), your lambda still travels, it just travels to place that does immediately ends up using it, but the point of the lambda remains control flow abstraction: You don't run the code in it, you hand your lambda off to something else which will then immediately run that 0 to many times. That's exactly what is happening here:
list.forEach(System.out::println);
I'm taking the code notion of System.out.println(someString), and I don't run it - no, I bundle up that idea in a lambda and then pass this notion to list's forEach method which will then invoke it for me, on every item in the list. As mentioned, this is bad code, because it needlessly uses lambdas in control flow abstraction mdoe which is inferior to just for (var item : list) System.out.println(item);, but it gets the point across.
It just doesn't make sense to want to write a lambda and immediately execute it. Why not just... execute it?
In your example from the book, you don't actually execute the lambda as you make it. You just.. make it, and hand it off to the runTest method, and it runs it. The clue is, runTest is a method (vs your attempts - Predicate is not a method), it's not magical or weird, just.. a method, that so happens to take a Function<A, B> as argument, and the lambda you write so happens to 'fit' - it can be interpreted as an implementation of Function<A, B>, and thus that code compiles and does what it does.
You'd have to do the same thing.
But, if that code is a single-use helper method, then there's no point to the lambda in the first place.
I was reading a not related thread , when I read a comment: Any time I find myself needing a multi-line lambda, I move the lines to a private method and pass the method reference instead of the lambda.
I was asking: which is the correct way to implement this behaviour? With a boolean method as posted in comment, or with predicate?
Example:
let's say I wanna check if a Table is usable, where usable means isClean, isEmpty, hasChair.
class Table{
public boolean hasChair(){...}
public boolean isClean(){...}
public boolean isEmpty(){...}
}
I can implement my filtering test for my list List<Table> tablesList = Arrays.asList(table1,table2,table3,table4); in 2 ways: the first with boolean:
public boolean isUsable(){
return hasChair() && isClean() && isEmpty();
}
And use it with tablesList.stream().filter(Table::isUsable)
The second way is with predicate:
public Predicate<Table> isUsable(){
return table -> table.isEmpty() && table.isClean() && table.hasChair();
}
Usable with tablesList.stream().filter(isUsable())
Which is the correct implementation? Why choose one instead of other? Is there any big difference?
I think you meant for the second example
public static Predicate<Table> isUsable(){
return table -> table.isEmpty() && table.isClean() && table.hasChair();
}
Which might already suggest this form is likely to confuse the reader. Without static you could write table.isUsable() or Table::isUsable but it wouldn't do what you think.
Which is the correct implementation?
I would prefer the Table::isUsable as it can also be used as table.isUsable for an instance.
Why choose one instead of other?
I feel the first example is more natural and less confusing.
The second form is more useful for manipulating Predicates e.g. Predicate.or(Predicate)
Is there any big difference?
In this case, using a Stream is likely to be slower, but more importantly, more likely to confuse.
One advantage of the method returning a Predicate is it could be added to any class e.g. you can't alter Table for some reason.
Having Predicate<Table> isUsable() assumes that you would always require that logic to be used in places that require a Predicate<Table> instance, which is a limitation.
On the other hand, having boolean isUsable() gives you the flexibility of using Table::isUsable where a Predicate<Table> is required, or using Table::isUsable as implementation of some other functional interfaces (that matches the signature of that method) or directly calling t.isUsable() for a specific Table instance. Hence I find this alternative more useful.
static List<Predicate<Table>> predicateList = Arrays.asList(Table::hasChair, Table::isClean);
static boolean isUsable(Table table) {
return predicateList.stream().allMatch(p -> p.test(table));
}
use isUsable:
List<Table> tablesList = ...
Stream<Table> usableTables = tablesList.stream().filter(Table::isUsable);
I have a function, calculate(String A,int B) in legacy code
Double calculate(String A,int B) {
if(A.equals("something")){ return B*1.02; }
if(B.equals("some")) return B*1.0;
else return B;
}
The calculation applied on B depends on the value of A.
In functional style I can break this into:
Function<String, Function<Integer,Double>> strategyA = (a)-> {
if(A.equals("something")) return b -> b*1.02;
if(B.equals("some")) return b -> return b -> b*1.0;
else return b -> b;
}
Then instead of calling calculate(a,b) I would call
strategyA.apply(a).apply(b)
Is the second style better than first one. As per my understanding this involves Strategy pattern and Functional Decomposition and Currying.
If the second approach is indeed better, how would you convince someone?
In Java, the preferred way of delivering a named piece of code is and stays the method. There is no reason to express a method as function, just to have “more functional style”. The reason, why function support was added to Java, is, that you sometimes want to pass a reference to the code to another method or function. In this case, the receiving method defines the required function signature, not the code you’re going to encapsulate.
So your calculate method may be referred to as an ObjIntConsumer<String>, if the receiving method only wants to pass pairs of String and int to it without being interested in the result. Otherwise, it may use a custom functional interface expressing the (String,int) → Double signature. Or when you accept boxing, BiFunction<String,Integer,Double> will do.
Currying allows you to reuse existing interfaces like Function to express functions with multiple arguments, when no builtin interface exists, but given the resulting generic signature, which will appear at least at one place in Java code using such a curried function, the readability suffers a lot, so in most cases, defining a new functional interface will be preferred over currying in most cases…
For other programming languages, having a different syntax and type inference for functional types (or having real function types in the first place, rather than settling on functional interfaces), this will be quite different.
I agree with Holger that in most cases, it does not make sense to write code using functions just for the sake of using functional programming. Functions are just an additional tool that lets you write code such as collection processing in a nicer way.
There is one interesting thing about your example though, which is that you take the String parameter a, then perform some computation and then return another function. This can be sometimes useful if the first operation takes a long time:
Function<String, Function<Integer,Double>> f = (a) -> {
if (some-long-computation(a)) return b -> b*1.02;
if (some-other-long-computation(a)) return b -> return b -> b*1.0;
else return b -> b;
}
When you then invoke f with the String argument, the function will run some-long-computation and some-other-long-computation and return the desired function:
Function<Integer,Double> fast = f.apply("Some input"); // Slow
Double d1 = fast.apply(123); // Fast!
Double d1 = fast.apply(456); // Fast!
If f was an ordinary method, then calling it twice as f("Some input", 123) and
f("Some input", 456) would be slower, because you'd run the expensive computations twice. Of course, this is something you can handle without functional programming too, but it is one place where returning a function actually fits quite nicely.
Why is the second code (the one with the stream) a better solution than the first?
First :
public static void main(String [] args) {
List<Integer> values = Arrays.asList(1,2,3,4,5,6);
int total = 0;
for(int e : values) {
total += e * 2;
}
Second :
System.out.println(total);
System.out.println(
values.stream()
.map(e-> e*2)
.reduce(0, (c, e)-> c + e));
Mutation is changing an object and is one common side effect in programming languages.
A method that has a functional contract will always return the same value to the same arguments and have no other side effects (like storing file, printing, reading). Thus even if you mutate temporary values inside your function it's still pure from the outside. By putting your first example in a function demonstrates it:
public static int squareSum(const List<Integer> values)
{
int total = 0;
for(int e : values) {
total += e * 2; // mutates a local variable
}
return total;
}
A purely functional method doesn't even update local variables. If you put the second version in a function it would be pure:
public static int squareSum(const List<Integer> values)
{
return values.stream()
.map(e-> e*2)
.reduce(0, (c, e)-> c + e);
}
For a person that knows other languages that has long been preferring a functional style map and reduce with lambda is very natural. Both versions are easy to read and easy to test, which is the most important part.
Java has functional classes. java.lang.String is one of them.
Mutation is changing the state of an object, either the list or some custom object.
Your particular code does not cause a mutation of the list either way, so there's no practical benefit here of using lambdas instead of plain old iteration. And, blame me, but I would use the iteration approach in this case.
Some approaches say that whenever you need to modify an object/collection, you need to return a new object/collection with the modified data instead of changing the original one. This is good for collection for example when you concurrently access a collection and it's being changed from another thread.
Of course this could lead to memory leaks, so there are some algorithms for managing memory and mutability for collection i.e. only the changed nodes are stored in another place in memory.
While Royal Bg is right you're not mutating your data in either case, it's not true that there's no advantage to the second version. The second version can be heavily multithreaded without ambiguity.
Since we're not expecting to iterate the list we can put the operations into a heavily multi-threaded context and solve it on a gpu. In the latter one each data point in the collection is multiplied by 2. Then reduced (which means every element is added together), which can be done by a reduction.
There are a number of potential advantages to the latter code not seen in the former. And while neither code element actually mutates, in the second one we are given the very clear contract that the items cannot mutate while that is happening. So we know that it doesn't matter if we iterate the list forwards, backwards, or apply it multithreaded etc. The implementation details can be filled in later. But, only if we know mutation can't happen and streams simply don't allow them.
As it might be clear from the title which approach should we prefer?
Intention is to pass a few method parameters and get something as output. We can pass another parameter and method will update it and method need not to return anything now, method will just update output variable and it will be reflected to the caller.
I am just trying to frame the question through this example.
List<String> result = new ArrayList<String>();
for (int i = 0; i < SOME_NUMBER_N; i++) {
fun(SOME_COLLECTION.get(i), result);
}
// in some other class
public void fun(String s, List<String> result) {
// populates result
}
versus
List<String> result = new ArrayList<String>();
for (int i = 0; i < SOME_NUMBER_N; i++) {
List<String> subResult = fun(SOME_COLLECTION.get(i));
// merges subResult into result
mergeLists(result, subResult);
}
// in some other class
public List<String> fun(String s) {
List<String> res = new ArrayList<String>();
// some processing to populate res
return res;
}
I understand that one passes the reference and another doesn't.
Which one should we prefer (in different situations) and why?
Update: Consider it only for mutable objects.
Returning a value from the function is generally a cleaner way of writing code. Passing a value and modifying it is more C/C++ style due to the nature of creating and destroying pointers.
Developers generally don't expect that their values will be modified by passing it through a function, unless the function explicitly states it modifies the value (and we often skim documentation anyway).
There are exceptions though.
Consider the example of Collections.sort, which does actually do an in place sort of a list. Imagine a list of 1 million items and you are sorting that. Maybe you don't want to create a second list that has another 1 million entries (even though these entries are pointing back to the original).
It is also good practice to favor having immutable objects. Immutable objects cause far fewer problems in most aspects of development (such as threading). So by returning a new object, you are not forcing the parameter to be mutable.
The important part is to be clear about your intentions in the methods. My recommendation is to avoid modifying the parameter when possible since it not the most typical behavior in Java.
You should return it. The second example you provided is the way to go.
First of all, its more clear. When other people read your code, there's no gotcha that they might not notice that the parameter is being modified as output. You can try to name the variables, but when it comes to code readability, its preferable.
The BIG reason why you should return it rather than pass it, is with immutable objects.
Your example, the List, is mutable, so it works okay.
But if you were to try to use a String that way, it would not work.
As strings are immutable, if you pass a string in as a parameter, and then the function were to say:
public void fun(String result){
result = "new string";
}
The value of result that you passed in would not be altered. Instead, the local scope variable 'result' now points to a new string inside of fun, but the result in your calling method still points to the original string.
If you called:
String test = "test";
fun(test);
System.out.println(test);
It will print: "test", not "new string"!
So definitely, it is superior to return. :)
This is more about best practices and your own method to program. I would say if you know this is going to be a one value return type function like:
function IsThisNumberAPrimeNumber{ }
Then you know that this is only going to ever return a boolean. I usually use functions as helper programs and not as large sub procedures. I also apply naming conventions that help dictate what I expect the sub\function will return.
Examples:
GetUserDetailsRecords
GetUsersEmailAddress
IsEmailRegistered
If you look at those 3 names, you can tell the first is going to give you some list or class of multiple user detail records, the second will give you a string value of a email and the third will likely give you a boolean value. If you change the name, you change the meaning, so I would say consider this in addition.
The reason I don't think we understand is that those are two totally different types of actions. Passing a variable to a function is a means of giving a function data. Returning it from the function is a way of passing data out of a function.
If you mean the difference between these two actions:
public void doStuff(int change) {
change = change * 2;
}
and
public void doStuff() {
int change = changeStorage.acquireChange();
change = change * 2;
}
Then the second is generally cleaner, however there are several reasons (security, function visibilty, etc) that can prevent you from passing data this way.
It's also preferable because it makes reusing code easier, as well as making it more modular.
according to guys recommendation and java code convention and also syntax limitation this is a bad idea and makes code harder to understand
BUT you can do it by implementing a reference holder class
public class ReferenceHolder<T>{
public T value;
}
and pass an object of ReferenceHolder into method parameter to be filled or modified by method.
on the other side that method must assign its return into Reference value instead of returning it.
here is the code for getting result of an average method by a ReferenceHolder instead of function return.
public class ReferenceHolderTest {
public static void main(String[] args) {
ReferenceHolder<Double> out = new ReferenceHolder<>();
average(new int[]{1,2,3,4,5,6,7,8},out);
System.out.println(out.value);
}
public static void average(int[] x, ReferenceHolder<Double> out ) {
int sum=0;
for (int a : x) {
sum+=a;
}
out.value=sum/(double)x.length;
}
}
Returning it will keep your code cleaner and cause less coupling between methods/classes.
It is generally preferable to return it.
Specially from a unit testing standpoint. If you are unit testing it
is easier to assert a returned value from a method than verifying if
your object was modified or interacted correctly. (Using
ArgumentCaptor or ArgumentMatcher to assert interactions isn't as
straight forward as a simple return assertion).
Increased code readability. If I see a method that takes 5 object parameters I
might have no immediate way of knowing you plan on modifying one of
those references for future use downstream. Instead if you are returning an
object, I can easily see you ultimately care about the result of that
method's computation.