Иногда мы сталкиваемся с ситуацией, в которой мы хотим изменить переменную внутри лямбда-выражения, но когда мы пытаемся это сделать, мы получаем ошибку во время компиляции, говорящую:
Переменная внутри Lambda должна быть Final или Effective Final.
Давайте рассмотрим пример задачи, в которой нам нужно было бы изменить переменную внутри лямбда-выражения .
Ромзо работает в FedEx. FedEx использует предварительные контейнеры переменного размера (размер первого элемента + 6 ), в зависимости от первого элемента, вставленного в него. Предположим, мы вставили первый элемент весом 5, тогда контейнер может принимать элементы до веса 5 + 6 , то есть 11.
Нам выдаются предметы 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)
Вам также может понравиться:
Лямбда-выражения в Java 8 .
Одно решение без лямбды:
Джава
xxxxxxxxxx
1
public static void main(String[] args) {
2
int [] items = {1, 2, 3, 21, 7, 12, 14, 21};
3
int count = 1;
4
Arrays.sort(items); //Sort the items ascending by weight
5
int currentWeight = items[0];
6
for(int weight : items)
7
{
8
if(!(weight <= currentWeight+6))
9
{
10
count++;
11
currentWeight = weight;
12
}
13
}
14
System.out.println(count);
15
}
Теперь, когда мы пытаемся решить вышеуказанную проблему с помощью лямбды и потока, мы беспокоимся об обновлении переменной count внутри нашей лямбды.
Но мы не можем обновить поле, объявленное final:
Джава
xxxxxxxxxx
1
public static void main(String[] args) {
2
final int count = 0; //variable inside lambda must be final or effective final
3
final int t = 0; //variable inside lambda must be final or effective final
4
int[] A = {1, 2, 3, 21, 7, 12, 14, 21};
5
int k = 6;
6
Arrays.sort(A);
7
IntStream.rangeClosed(0, A.length - 1).boxed().map(x -> {
8
if (! (A[t] + k >= A[x])) {
9
count++; //cannot do this as the count variable is final
10
t = x; //cannot do this as the count variable is final
11
}
12
return count;
13
}).collect(Collectors.toList());
14
System.out.println(count + 1);
15
}
Вышеуказанная проблема возникает из-за спецификации языка Java 8, §15.27.2 который говорит:
Любая локальная переменная, формальный параметр или параметр исключения, используемые, но не объявленные в лямбда-выражении,
должны быть либо объявлены окончательными, либо фактически окончательными (§4.12.4) ,
либо при попытке использования возникает ошибка времени компиляции.
Чтобы преодолеть это, мы можем предложить 3 возможных решения:
1. Делаем переменную статической.
2. Используйте массив
3. Атомное целое число
1. Когда мы создаем переменную count
и , тогда возникает некоторая огромная проблема конфликтующих чтения / записи, если мы работаем в многопоточной среде.t
static
Но если вы уверены, что нет других потоков, кроме той, над которой вы работаете в данный момент, вы можете использовать этот метод. Но использования статической переменной следует избегать.
Джава
xxxxxxxxxx
1
static int count = 0;
2
static int t = 0;
3
public static void main(String[] args) {
4
int[] A = {1, 2, 3, 21, 7, 12, 14, 21};
5
int k = 6;
6
Arrays.sort(A);
7
IntStream.rangeClosed(0, A.length - 1).boxed().map(x -> {
8
if (! (A[t] + k >= A[x])) {
9
count++;
10
t = x;
11
}
12
return count;
13
}).collect(Collectors.toList());
14
System.out.println(count + 1);
15
}
2. Использование array
вместо static
переменных. Это использует преимущество структуры данных массива, имея в виду, что только внешняя переменная внутри lamdba должна быть конечной; здесь мы как-то обманули лямбду.
Джава
xxxxxxxxxx
1
public static void main(String[] args) {
2
final int [] count = {0}; //final
3
final int [] t = {0}; //final
4
int[] A = {1, 2, 3, 21, 7, 12, 14, 21};
5
int k = 6;
6
Arrays.sort(A);
7
IntStream.rangeClosed(0, A.length - 1).boxed().map(x -> {
8
if (! (A[t[0]] + k >= A[x])) {
9
count[0]=count[0]+1; // works, as we trick the lambda keeping the external variable as final
10
t[0] = x; //works, as we trick the lambda keeping the external variable as final
11
}
12
return count;
13
}).collect(Collectors.toList());
14
System.out.println(count[0] + 1);
15
}
3. Использование AtomicInteger
. Это гарантирует, что значение int обновляется атомарно, что делает код потокобезопасным. Он имеет встроенные методы , такие как getAndIncrement()
, set()
, addAndGet()
и т.д., которые могут быть выполнены атомарно.
Джава
xxxxxxxxxx
1
public static void main(String[] args) {
2
int[] A = {1, 2, 3, 21, 7, 12, 14, 21};
3
int k = 6;
4
AtomicInteger count = new AtomicInteger(0); //Effective Final
5
final AtomicInteger t = new AtomicInteger(0); //Final
6
Arrays.sort(A);
7
IntStream.rangeClosed(0, A.length - 1).boxed().map(x -> {
8
if (! (A[t.intValue()] + k >= A[x])) {
9
count.incrementAndGet();
10
t.set(x);
11
}
12
return count;
13
}).collect(Collectors.toList());
14
System.out.println(count.incrementAndGet());
15
}
Разница между Финалом и Эффективным Финалом:
Переменная является окончательной или фактически конечной, когда она инициализируется один раз и никогда не мутирует в своем классе владельца; мы не можем инициализировать его в циклах или внутренних классах.
Финал :
Джава
xxxxxxxxxx
1
final int x;
2
x = 3;
Эффективно финал:
Джава
xxxxxxxxxx
1
int x;
2
x = 4; //value is never changed ever, so kind of make=ing it effectively final.
3
A keyword before a variable makes it final and no final keyword before a variable meke it effectively final provided we never change its value.