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 !?
Advertisement
Answer
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.