Я часто сталкиваюсь с одной и той же проблемой. Мне нужно подсчитать прогоны лямбды для использования вне лямбды .
Например:
myStream.stream().filter(...).forEach(item -> { ... ; runCount++});
System.out.println("The lambda ran " + runCount + "times");
Проблема в том, что runCount должен быть final
, поэтому он не может быть int
. Это не может быть, Integer
потому что это неизменяемо . Я мог бы сделать это переменной уровня класса (то есть полем), но мне это понадобится только в этом блоке кода.
Я знаю, что есть разные способы, мне просто интересно, какое решение вы предпочитаете для этого?
Вы используете AtomicInteger
ссылку на массив или другой способ?
java
lambda
java-8
java-stream
informatik01
источник
источник
AtomicInteger
.Ответы:
Позвольте мне немного переформатировать ваш пример для обсуждения:
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
apeek
, который передает элементы, а затем подсчитывает их:long runCount = myStream.stream() .filter(...) .peek(item -> { foo(); bar(); }) .count(); System.out.println("The lambda ran " + runCount + " times");
Это немного лучше, но все же немного странно. Причина заключается в том, что
forEach
иpeek
может сделать только свою работу с помощью побочных эффектов. Новый функциональный стиль Java 8 призван избежать побочных эффектов. Мы немного поработали, извлекая приращение счетчика вcount
операцию в потоке. Другими типичными побочными эффектами являются добавление элементов в коллекции. Обычно их можно заменить с помощью коллекторов. Но, не зная, какую реальную работу вы пытаетесь выполнить, я не могу предложить ничего более конкретного.источник
peek
перестает работать, как толькоcount
реализации начинают использовать ярлыки дляSIZED
потоков. Возможно, это никогда не будет проблемой сfilter
ed stream, но может создать большие сюрпризы, если кто-то изменит код позже…final AtomicInteger i = new AtomicInteger(1);
и где-нибудь в вашем лямбде используйтеi.getAndAdd(1)
. Остановись и вспомни, как хорошо былоint i=1; ... i++
раньше.Incrementable
для числовых классов, включаяAtomicInteger
и объявляли операторы,++
чтобы они выглядели причудливыми функциями, нам не потребовалась бы перегрузка операторов, и мы все равно имели бы очень читаемый код.В качестве альтернативы утомительной синхронизации AtomicInteger можно было бы использовать целочисленный массив . Пока ссылка на массив не получает другой назначенный массив (и в этом суть), он может использоваться как конечная переменная, в то время как значения полей могут изменяться произвольно.
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 });
источник
foreach
непосредственно при сборе (без потоков) это достаточно хороший подход. благодаря !!AtomicInteger runCount = 0L; long runCount = myStream.stream() .filter(...) .peek(item -> { foo(); bar(); runCount.incrementAndGet(); }); System.out.println("The lambda ran " + runCount.incrementAndGet() + "times");
источник
runCount
. Я подозреваю, что вы намеревались иметь только один из них, но какой?AtomicInteger
Помог мне , но я бы инициализировать егоnew AtomicInteger(0)
Вы не должны использовать AtomicInteger, вы не должны использовать вещи, если у вас нет действительно веской причины для использования. Причина использования AtomicInteger может заключаться только в разрешении одновременного доступа или тому подобном.
Когда дело доходит до вашей проблемы;
Holder может использоваться для удержания и увеличения его внутри лямбды. И после этого вы можете получить его, вызвав runCount.value
Holder<Integer> runCount = new Holder<>(0); myStream.stream() .filter(...) .forEach(item -> { foo(); bar(); runCount.value++; // now it's work fine! }); System.out.println("The lambda ran " + runCount + " times");
источник
javax.xml.ws.Holder
.Для меня это помогло, надеюсь, кому-то это пригодится:
AtomicInteger runCount = new AtomicInteger(0); myStream.stream().filter(...).forEach(item -> runCount.getAndIncrement()); System.out.println("The lambda ran " + runCount.get() + "times");
getAndIncrement()
В документации Java указано:источник
Другой способ сделать это (полезно, если вы хотите, чтобы ваш счетчик увеличивался только в некоторых случаях, например, если операция была успешной) - это что-то вроде этого, используя
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 имеет подробный обзор.
источник
Если вы не хотите создавать поле, потому что оно вам нужно только локально, вы можете сохранить его в анонимном классе:
int runCount = new Object() { int runCount = 0; { myStream.stream() .filter(...) .peek(x -> runCount++) .forEach(...); } }.runCount;
Странно, я знаю. Но при этом временная переменная не попадает даже в локальную область видимости.
источник
сокращение также работает, вы можете использовать его вот так
источник
Другой альтернативой является использование Apache Commons MutableInt.
MutableInt cnt = new MutableInt(0); myStream.stream() .filter(...) .forEach(item -> { foo(); bar(); cnt.increment(); }); System.out.println("The lambda ran " + cnt.getValue() + " times");
источник
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
источник