Java 8: Предпочтительный способ подсчета итераций лямбда?
Я часто сталкиваюсь с той же проблемой. Мне нужно подсчитать пробеги лямбды для использования за пределами лямбды. Например:.
myStream.stream().filter(...).forEach(item->{ ... ; runCount++);
System.out.println("The lambda ran "+runCount+"times");
Проблема заключается в том, что runCount должен быть окончательным, поэтому он не может быть int. Это не может быть целым, потому что это неизменное. Я мог бы сделать это переменной уровня класса (т.е. Поле), но мне нужно будет только в этом блоке кода.
Я знаю, что есть разные способы, мне просто интересно, каково ваше предпочтительное решение для этого? Вы используете AtomicInteger или ссылку на массив или каким-либо другим способом?
Ответы
Ответ 1
Позвольте немного переформатировать ваш пример для обсуждения:
long runCount = 0L;
myStream.stream()
.filter(...)
.forEach(item -> {
foo();
bar();
runCount++; // doesn't work
});
System.out.println("The lambda ran " + runCount + " times");
Если вам действительно нужно увеличить счетчик из лямбда, типичный способ сделать это - сделать счетчик AtomicInteger
или AtomicLong
, а затем вызвать один из методов инкремента на нем.
Можно использовать одноэлементный массив int
или long
, но это будет иметь условия гонки, если поток выполняется параллельно.
Но обратите внимание, что поток заканчивается на forEach
, что означает, что нет возвращаемого значения. Вы можете изменить forEach
на a peek
, который передает элементы через, а затем подсчитывает их:
long runCount = myStream.stream()
.filter(...)
.peek(item -> {
foo();
bar();
})
.count();
System.out.println("The lambda ran " + runCount + " times");
Это несколько лучше, но все же немного странно. Причина в том, что forEach
и peek
могут выполнять свою работу только с помощью побочных эффектов. Новый функциональный стиль Java 8 заключается в том, чтобы избежать побочных эффектов. Мы немного это сделали, извлекая приращение счетчика в операцию count
в потоке. Другими типичными побочными эффектами являются добавление предметов в коллекции. Обычно их можно заменить с помощью коллекционеров. Но, не зная, какую фактическую работу вы пытаетесь сделать, я не могу предложить ничего более конкретного.
Ответ 2
В качестве альтернативы синхронизации с AtomicInteger можно использовать массив integer. Пока ссылка на массив не получает другой массив, назначенный (и что точка), он может использоваться как переменная final, а значения полей может произвольно изменять.
int[] iarr = {0}; // final not neccessary here if no other array is assigned
stringList.forEach(item -> {
iarr[0]++;
// iarr = {1}; Error if iarr gets other array assigned
});
Ответ 3
AtomicInteger runCount = 0L;
long runCount = myStream.stream()
.filter(...)
.peek(item -> {
foo();
bar();
runCount.incrementAndGet();
});
System.out.println("The lambda ran " + runCount.incrementAndGet() + "times");
Ответ 4
Другой способ сделать это (полезно, если вы хотите, чтобы ваш счетчик увеличивался только в некоторых случаях, например, если операция прошла успешно), это что-то вроде этого, используя mapToInt()
и sum()
:
int count = myStream.stream()
.filter(...)
.mapToInt(item -> {
foo();
if (bar()){
return 1;
} else {
return 0;
})
.sum();
System.out.println("The lambda ran " + count + "times");
Как отметил Стюарт Маркс, это все еще несколько странно, потому что это не позволяет полностью избежать побочных эффектов (в зависимости от того, что делают foo()
и bar()
).
И еще один способ приращения переменной в лямбда-выражении, доступной вне ее, - это использование переменной класса:
public class MyClass {
private int myCount;
// Constructor, other methods here
void myMethod(){
// does something to get myStream
myCount = 0;
myStream.stream()
.filter(...)
.forEach(item->{
foo();
myCount++;
});
}
}
В этом примере использование переменной класса для счетчика в одном методе, вероятно, не имеет смысла, поэтому я предостерегаю против этого, если нет веской причины для этого. Сохранение final
переменных класса, если это возможно, может быть полезным с точки зрения безопасности потоков и т.д. (См. Http://www.javapractices.com/topic/TopicAction.do?Id=23 для обсуждения использования final
).
Чтобы получить лучшее представление о том, почему лямбды работают так, как они работают, https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood подробно рассмотрим.
Ответ 5
Если вы не хотите создавать поле, потому что оно вам нужно только локально, вы можете сохранить его в анонимном классе:
int runCount = new Object() {
int runCount = 0;
{
myStream.stream()
.filter(...)
.peek(x -> runCount++)
.forEach(...);
}
}.runCount;
Странно, я знаю. Но он сохраняет временную переменную вне локальной области видимости.
Ответ 6
Для меня это помогло, надеюсь, кто-то найдет это полезным:
AtomicInteger runCount= new AtomicInteger(0);
myStream.stream().filter(...).forEach(item->{ ... ; runCount.getAndIncrement(););
System.out.println("The lambda ran "+runCount.get()+"times");
getAndIncrement() Документация Java гласит:
Атомно увеличивает текущее значение с эффектами памяти, как указано в VarHandle.getAndAdd. Эквивалент getAndAdd (1).
Ответ 7
Вы не должны использовать AtomicInteger, вы не должны использовать вещи, если у вас нет действительно веской причины для использования. И причиной использования AtomicInteger может быть только разрешение одновременного доступа или тому подобное.
Когда дело доходит до вашей проблемы;
Держатель можно использовать для удержания и увеличения его внутри лямбды. И после того, как вы можете получить его, позвонив runCount.value
Holder<Integer> runCount = new Holder<>(0);
myStream.stream()
.filter(...)
.forEach(item -> {
foo();
bar();
runCount.value++; // now it work fine!
});
System.out.println("The lambda ran " + runCount + " times");
Ответ 8
Снижение также работает, вы можете использовать его следующим образом
myStream.stream().filter(...).reduce((item, sum) -> sum += item);
Ответ 9
AtomicInteger runCount = new AtomicInteger(0);
elements.stream()
//...
.peek(runCount.incrementAndGet())
.collect(Collectors.toList());
// runCount.get() should have the num of times lambda code was executed