Подтвердить что ты не робот

Java 8: Предпочтительный способ подсчета итераций лямбда?

Я часто сталкиваюсь с той же проблемой. Мне нужно подсчитать пробеги лямбды для использования за пределами лямбды. Например:.

myStream.stream().filter(...).forEach(item->{ ... ; runCount++);
System.out.println("The lambda ran "+runCount+"times");

Проблема заключается в том, что runCount должен быть окончательным, поэтому он не может быть int. Это не может быть целым, потому что это неизменное. Я мог бы сделать это переменной уровня класса (т.е. Поле), но мне нужно будет только в этом блоке кода. Я знаю, что есть разные способы, мне просто интересно, каково ваше предпочтительное решение для этого? Вы используете AtomicInteger или ссылку на массив или каким-либо другим способом?

4b9b3361

Ответ 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