简体   繁体   中英

Lambdas vs Iterators in Java

When writing Java code, NetBeans often encourages me to convert foreach loops (with iterators) to lambda expressions. Sometimes the resulting code is much cleaner. Other times, the result is less clear than before.

For example, the following uses iterators:

List<String> list = new ArrayList<>();
for (String str : list) {
    if (str.charAt(0) == ' ')) {
        // do something with str
    }
}

And the equivalent uses lambda expressions:

List<String> list = new ArrayList<>();
list.stream().filter((str) -> (str.charAt(0) == ' ')).forEach((str) -> {
    // do something with str
)};

In this case, using lambda expressions results in lengthier code, and uses less intuitive language ( stream , filter , and forEach instead of for and if ). So are there any advantages to using lambdas instead of iterators, when the code isn't cleaner? For example, are there any performance gains?

Iterate over collection and doing "something" with some of it's members is not the best example for using lambdas.

Consider scenario like create result collection of objects filtered by some property, ordered by some other property and do some other "magic" and you'll see that lambdas saves dozens lines of code.

Hard to say if it's easier to read (probably not as lambda is yet another syntax you have to be familiar with) but after all - it's better to read one complex line of code instead creating anonymous/inner comparators. At least in C# lambda is very useful construction.

uses less intuitive language ( stream , filter , and forEach instead of for and if

I don't really find them less intuitive. Moreover, wait for few months, those will be mostly used terms you will hear in Java. In fact, once you are comfortable with lambdas, you'll see how amazingly it cleansifies your code, that uses complex logic inside loops.

So are there any advantages to using lambdas instead of iterators, when the code isn't cleaner? For example, are there any performance gains?

One obvious advantage of streams and lambdas that comes to my mind is, it gives you the power of parallel execution more easily using Stream.parallelStream() . Also internal iteration of streams gives control of how iteration happens to the API. It can choose to evaluate intermediate operations lazily, parallely, sequentially, etc. Moreover, functional programming has it's own advantage. You can pass around logics in the form of lambdas, which used to be done using anonymous classes earlier. That way, some functionality can be easily re-used.

Although there are certain disadvantages too, when comparing with normal for loop. If your loop is modifying some local variable, then you can't get it converted to forEach and lambdas version (at least not directly), because variables used inside lambdas needs to be effectively final .

More details can be found on java.util.stream javadoc.

Just to give you some information about how I think you can write neat lambdas, is for your given code:

List<String> listString = new ArrayList<>();
for (String str : listString) {
    if (str.charAt(0) == ' ') {
        // do something with str
    }
}

I would, convert it to the following:

List<String> listString = new ArrayList<>();
listString.stream()
        .filter(s -> s.charAt(0) == ' ')
        .forEach(/*do something*/);

I find the syntax like this much less intrusive and much clearer. Also, if you are going to need blocks inside your lambda, you are probably doing it wrong. Unless you have a very good reason to do so.

As an example if you would want to print every string on a new line, you could do:

List<String> listString = new ArrayList<>();
listString.stream()
        .filter(s -> s.charAt(0) == ' ')
        .forEach(System.out::println);

Moreover, if you need to store the data in some kind of structure, you want to be using Collectors.* , and you do not want to be doing that inside your forEach , as a silly example we would like to convert it to a List<String> again:

List<String> listString = new ArrayList<>();
List<String> filteredString = listString.stream()
        .filter(s -> s.charAt(0) == ' ')
        .collect(Collectors.toList());

Note that this particular implemention could have been done much easier if you were allowed to modify the original list.

With respect to performance gains, I have written a simple benchmark test for lambda expressions versus iterators. The test gives performance during warmup phase and non warmup phase. Warmup phase is considered for the first 10 iterations, non warmup phase later on. The test runs for 10k iterations and measures the average time. Below is the code:

import java.util.ArrayList;
import java.util.List;

public class LambdaTest {

private static final int COUNT = 10000;
public static void main(String[] args) {

    List<String> str = new ArrayList<String>();
    for (int i =0; i<100000; i++){
        str.add(""+i);
    }
    double iterTotal = 0;
    double lambdaTotal = 0;
    double warmupIterTotal = 0;
    double warmupLambdaTotal = 0;
    for(int j = 0; j < COUNT; j++){
        long start = System.nanoTime();
        for (int i = 0; i< 100000; i++){
            String string = str.get(i);
//              if(string.length() < 5){
//                  System.out.println(string);
//              }
        }
        long end = System.nanoTime();
        if(j>=10){
            iterTotal += end - start;
        }else {
            warmupIterTotal += end - start;
        }
        System.out.println("Output 1 in : "+(end-start)*1.0/1000 +" Us");
        start = System.nanoTime();
        str.forEach((string) -> {
//              if(string.length() < 5){
//                  System.out.println(string);
//              }
        }); 
        end = System.nanoTime();
        if(j>=10){
            lambdaTotal+= end-start;
        }else {
            warmupLambdaTotal += end - start;
        }
        System.out.println("Output 2 in : "+(end-start)*1.0/1000 +" Us");
    }

    System.out.println("Avg Us during warmup using Iter: "+warmupIterTotal/(1000*10));
    System.out.println("Avg Us during warmup using Lambda: "+warmupLambdaTotal/(1000*10));

    System.out.println("Avg Us using Iter: "+iterTotal/(1000*(COUNT-10)));
    System.out.println("Avg Us using Lambda: "+lambdaTotal/(1000*(COUNT-10)));

    }
}

The output of the above code is as below:

Avg Us during warmup using Iter: 1372.8821
Avg Us during warmup using Lambda: 5211.7064
Avg Us using Iter: 373.6436173173173
Avg Us using Lambda: 370.77465015015014

Thus, as we can see the lambda expressions perform poorly in the warmup stages but post-warmup the performance with lambda expressions is quite similar to iterators. This is just a simple test and if you are trying out a complex expression in your application, you can better profile the application and check which one works better in your case.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM