This is inspired by a post on LinkedIn, but I am going way beyond the content in that. It is all about lambdas and capture by reference versus capture by value.
using System;
using System.Collections.Generic;
public class Program
{
public static void Main(string[] args)
{
IList<Func<int>> fa = new List<Func<int>>();
for(int i = 0; i < 5; i++)
{
fa.Add(() => i);
}
foreach(Func<int> f1 in fa)
{
Console.WriteLine(f1());
}
}
}
5 5 5 5 5
may surprise some that are not aware of the capture by reference concept. To make it work as some would expect use:
using System;
using System.Collections.Generic;
public class Program
{
public static void Main(string[] args)
{
IList<Func<int>> fa = new List<Func<int>>();
for(int i = 0; i < 5; i++)
{
int j = i;
fa.Add(() => j);
}
foreach(Func<int> f1 in fa)
{
Console.WriteLine(f1());
}
}
}
0 1 2 3 4
It is documented to work that way, so as such it is OK, but beginners get tricked by this all the time, so I don't think it is good.
Java does not have the problem - Java only allow capture of final or effective final. Then it does not matter how it is passed.
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
public class Lambda1 {
public static void main(String[] args) {
List<Supplier<Integer>> fa = new ArrayList<Supplier<Integer>>();
for(int i = 0; i < 5; i++) {
fa.add(() -> i);
}
for(Supplier<Integer> f1 : fa) {
System.out.println(f1.get());
}
}
}
Lambda1.java:9: error: local variables referenced from a lambda expression must be final or effectively final
fa.add(() -> i);
^
1 error
is a clear error message. And the fix is clearly hinted at:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
public class Lambda2 {
public static void main(String[] args) {
List<Supplier<Integer>> fa = new ArrayList<Supplier<Integer>>();
for(int i = 0; i < 5; i++) {
final int j = i;
fa.add(() -> j);
}
for(Supplier<Integer> f1 : fa) {
System.out.println(f1.get());
}
}
}
0 1 2 3 4
Note that the rule is final or effective final, which means that the final is optional:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
public class Lambda3 {
public static void main(String[] args) {
List<Supplier<Integer>> fa = new ArrayList<Supplier<Integer>>();
for(int i = 0; i < 5; i++) {
int j = i;
fa.add(() -> j);
}
for(Supplier<Integer> f1 : fa) {
System.out.println(f1.get());
}
}
}
0 1 2 3 4
But as soon as the variable is changed then the error comes back:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
public class Lambda4 {
public static void main(String[] args) {
List<Supplier<Integer>> fa = new ArrayList<Supplier<Integer>>();
for(int i = 0; i < 5; i++) {
int j = i;
fa.add(() -> j);
j++;
}
for(Supplier<Integer> f1 : fa) {
System.out.println(f1.get());
}
}
}
Lambda4.java:10: error: local variables referenced from a lambda expression must be final or effectively final
fa.add(() -> j);
^
1 error
I like the Java way. The beginner developer is prevented from making mistakes.
C++ is doing it the C++ way:
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
int main()
{
vector<function<int()>> fa;
for(int i = 0; i < 5; i++)
{
fa.push_back([=]() { return i; });
}
for(function<int()> f1 : fa)
{
cout << f1() << endl;
}
return 0;
}
0 1 2 3 4
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
int main()
{
vector<function<int()>> fa;
for(int i = 0; i < 5; i++)
{
fa.push_back([&]() { return i; });
}
for(function<int()> f1 : fa)
{
cout << f1() << endl;
}
return 0;
}
5 5 5 5 5
I am actually OK with the C++ approach as well. The developer picked a language prioritizing control. And C++ delivered.