编译:ImportNew/覃佑桦
dzone.com/articles/modifying-variable-inside-lambda
本文将讨论如何使用 Java AtomicInteger 和数组修改 Lambda 表达式中的变量值。
有时会遇到需要在 Lambda 表达式中修改变量的情况。然而,当尝试修改时会报告编译错误:
Lambda 表达式中的变量必须是 final 或 effective final 变量。
让我们通过一个例子了解,什么时候需要修改 Lambda 表达式 中的变量值。
Romzo 在 FedEx 工作。FedEx 采用可变大小预付容器(第一件物品大小 + 6),具体结果取决于加入容器的第一件物品重量。假设加入的第一件物品重量是5,那么容器可以容纳单件物品重量至多为5 + 6,即11。
给定物品重量列表为1,1,2,3,21,7,12,14,21,计算最少需要多少个集装箱。
解答:
货品重量分别为:{1, 2, 3, 21, 7, 12, 14, 21}。列表可分为3个容器:{1,2,3,7}、{12,14} 和 {21,21}。
每个容器中的货品重量 < 最小货品重量 + 6 单位重量。
说明
- 第一个容器可容纳重量分别为1,2、3和7的物品(最大重量为7,即1+ 6)
- 第二个容器容纳物品重量为12,14。(最大重量18,即12 + 6 = 18)
- 第三个容器存放物品重量为21,21。(最大重量为27,即21 + 6 = 27)
不使用 Lambda 表达式:
public static void main(String[] args) {
int[] items = {1, 2, 3, 21, 7, 12, 14, 21};
int count = 1;
Arrays.sort(items); // 按照重量升序排列
int currentWeight = items[0];
for (int weight : items) {
if (!(weight <= currentWeight + 6)) {
count++;
currentWeight = weight;
}
}
System.out.println(count);
}
在尝试用 lambda 表达式和 stream 方案时,发现更新 lambda 表达式中的 count 变量有问题。
无法更新声明为 final 的字段:
public static void main(String[] args) {
final int count = 1; // Lambda 內部变量必须标记为 final 或实际作为 final 使用
final int t = 0; // Lambda 內部变量必须标记为 final 或实际作为 final 使用
int[] A = {1, 2, 3, 21, 7, 12, 14, 21};
int k = 6;
Arrays.sort(A);
IntStream.rangeClosed(0, A.length - 1).boxed().map(x -> {
if (!(A[t] + k >= A[x])) {
count++; // 由于 count 是 final 变量, 不能修改
t = x; // 由于 tcount 是 final 变量, 不能修改
}
return count;
}).collect(Collectors.toList());
System.out.println(count);
}
Java 8语言规范§15.27.2提出了上述问题:
使用任何 lambda 表达式外部声明的局部变量、形式参数或异常参数
必须声明为 final或 effective final 变量(§4.12.4),
否则会报告编译错误。
要克服这个问题有3种方案:
- 使静态变量
- 使用数组
- AtomicInteger
1.如果把变量 count 和 t 变为 static,在多线程环境中会发生读写冲突问题。
假如可以确认除当前正在使用线程外没有其它线程,这么干没有问题。最好避免使用静态变量。
static int count = 1;
static int t = 0;
public static void main(String[] args) {
int[] A = {1, 2, 3, 21, 7, 12, 14, 21};
int k = 6;
Arrays.sort(A);
IntStream.rangeClosed(0, A.length - 1).boxed().map(x -> {
if (!(A[t] + k >= A[x])) {
count++;
t = x;
}
return count;
}).collect(Collectors.toList());
System.out.println(count);
}
2.用数组取代静态变量。这种方案充分利用了数组的优势。请记住,只有 lamdba 表达式外面的变量必须为 final;这个方法其实绕过了 lambda 的限制。
public static void main(String[] args) {
final int[] count = {0}; // final
final int[] t = {0}; // final
int[] A = {1, 2, 3, 21, 7, 12, 14, 21};
int k = 6;
Arrays.sort(A);
IntStream.rangeClosed(0, A.length - 1).boxed().map(x -> {
if (!(A[t[0]] + k >= A[x])) {
count[0] = count[0] + 1; // 保证外部变量为 final 的前提下可以工作
t[0] = x; // 保证外部变量为 final 的前提下可以工作
}
return count;
}).collect(Collectors.toList());
System.out.println(count[0] + 1);
}
3.使用 AtomicInteger。这样可以确保 int 值会以原子操作执行,从而确保代码线程安全。它提供的 方法包括getAndIncrement()、set()、addAndGet()等,可以按原子操作执行。
public static void main(String[] args) {
int[] A = {1, 2, 3, 21, 7, 12, 14, 21};
int k = 6;
AtomicInteger count = new AtomicInteger(0); // 实际上为 final
final AtomicInteger t = new AtomicInteger(0); // final
Arrays.sort(A);
IntStream.rangeClosed(0, A.length - 1).boxed().map(x -> {
if (!(A[t.intValue()] + k >= A[x])) {
count.incrementAndGet();
t.set(x);
}
return count;
}).collect(Collectors.toList());
System.out.println(count.incrementAndGet());
}
final 与 Effective final 的区别:
final 或 effectively final 变量一旦初始化就不能更改,因此不能再循环中或者内部类里初始化 。
final 变量:
final int x;
x = 3;
Effectively final 变量:
int x;
x = 4; // 永远不修改变量值, 实际等价于 final
变量前使用关键字为 final 变量, 不加关键字但永远不修改变量值为实际上的 final 变量。