0

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 !?

2
  • 1
    This is probably obvious, but in case you need to use the functionality in different scenarios, using 1) would save you writing out the lambda each time and make the code more readable. Commented Dec 16, 2020 at 13:21
  • Just imagine using the stream API without lambdas or method references, would be a nightmare and an unreadable mess... Commented Dec 16, 2020 at 13:35

3 Answers 3

3

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.

Sign up to request clarification or add additional context in comments.

1 Comment

"Your idea of what null is, is broken." - Thanks for explaining in so much details but yes I was aware of that and that's why I mentioned don't go into the functionality :P "Your lambda code does not compile" - It will obviously not since we need to declare the functional interface first & I've mentioned it as well. "The difference" - Much appreciated that gave me a good use case of lambdas and cleared my main question. Thanks
1

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

Comments

1

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.

2 Comments

Thanks for the explanation. Appreciate it but I wanted to know use case of lambdas other than making the code look cooler. For ex - filter streams etc and not doing a+b in a single line.
@theunluckyone I just mentioned that only at the end. Would update my answer.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.