2016-11-12 3 views
5

Мне любопытно, как суммировать несколько переменных в потоке java8.java8 stream sum multiple

Integer wCPU = 0; 
Double wnetwork = 0.0; 
Double wMem = 0.0; 

this.slaContractList.forEach(sla -> { 
    wCPU += sla.getNumberOfCPUs(); 
    wnetwork += sla.getNetworkBandwith(); 
    wMem += sla.getMemory(); 
}); 

Однако это не скомпилируется, так как переменная в выражении лямбда должна быть окончательной.

+1

Вы хотите пользовательский коллектор, см на http://stackoverflow.com/questions/37187541/java-8-stream-add-elements-to-list-and-sum/37188002#37188002 – Tunaki

+0

Каковы недостатки взлома? у него, похоже, меньше строк кода, что приятно. –

+1

Вы сказали это ... это взломать. Запустите его параллельно, скорее всего, он не выдает правильный вывод, потому что '+ =', как '++', [не является атомной операцией] (http://stackoverflow.com/questions/25168062/why-is-i -на-атомный). – Tunaki

ответ

7

Предполагая slaContractList список SlaContract объектов, и имеет конструктор SlaContract(numberOfCPUs, networkBandwith, memory) вы можете:

SlaContract sumContract = slaContractList.stream() 
    .reduce(new SlaContract(0, 0.0, 0.0), (sla1, sla2) -> { 
     return new SlaContract(sla1.getNumberOfCPUs() + sla2.getNumberOfCPUs(), sla1.getworkBandwith() + sla2.getworkBandwith(), sla1.getMemory() + sla2.getMemory()); 
    }); 

Double wnetwork = sumContract.getworkBandwith(); 
Double wMem = sumContract.getMemory(); 
Integer wCPU = sumContract.getNumberOfCPUs(); 

То же самое решение, но для простого класса:

Point sumPoint = pointsList.stream() 
    .reduce(new Point(0, 0), (p1, p2) -> { 
     return new Point(p1.x + p2.x, p1.y + p2.y); 
    }); 
+3

'reduce' никогда не должен изменять свой входной параметр во время накопления. Это будет прерываться при параллельном запуске, оно должно всегда возвращать новый объект, содержащий накопление. [См. Также] (http://stackoverflow.com/questions/23869930/is-the-accumulator-of-reduce-in-java-8-allowed-to-modify-its-arguments). Вы хотите использовать 'collect' здесь. – Tunaki

+1

См. Отредактированный ответ. –

6

Попробуйте использовать Stream.reduce и Stream.sum:

Double wnetwork = slaContractList.stream() 
      .mapToDouble(sla -> sla.getNetworkBandwith()) 
      .sum(); 

Double wMem = slaContractList.stream() 
      .mapToDouble(sla -> sla.getMemory()) 
      .sum(); 

Integer wCPU = slaContractList.stream() 
      .mapToInt(sla -> sla.getNumberOfCPUs()) 
      .sum(); 

См https://docs.oracle.com/javase/tutorial/collections/streams/reduction.html

Преимущество использования потока является вариант использования parallelStream() вместо stream(). В некоторых ситуациях он может быть более эффективным, чем простой цикл.

+0

, чтобы вы использовали карту для каждого атрибута? Разве это не медленнее, как один цикл? –

+0

Дело в том, что это в основном поток кортежей. И я считаю, что это будет быстрее, если каждое значение в кортеже будет суммироваться отдельно, но в течение одного прохода цикла. –

+0

Ну, так вы можете суммировать поток. Сравните это решение с другими, не использующими потоки. Проверьте 'parallelStream()' вместо 'stream()' также. –

2

Я хотел бы сделать простой хак, как это:

Integer[] wCPU = new Integer[1]; 
    Double[] wnetwork = new Double[1]; 
    Double[] wMem = new Double[1]; 

    this.slaContractList.forEach(sla -> { 
     wCPU[0] += sla.getNumberOfCPUs(); 
     wnetwork[0] += sla.getNetworkBandwith(); 
     wMem[0] += sla.getMemory(); 
    }); 

Это необязательно иметь final ключевое слово и, как и в Java-они ввели эффективно окончательный концепции. Это означает, что вы назначили только один раз.

+0

Мне нравится ваш хак;) –

+0

Извините за переключение. Но я думаю, что другой ответ лучше/не взломать. –

+0

Это «концептуально» Mutable integer и double, единственная проблема в том, что он не является потокобезопасным, но он работает для простой проблемы с потоками tootu. – pdem

2

Просто, чтобы сделать сумму, я бы использовал операцию суммирования потоков, как и в ответе Лукаса, но для более общего решения для решения «конечной проблемы» вы можете использовать классы java.util.concurrent.atomic. Он предназначен для использования в потоке и является потокобезопасным, поэтому его можно использовать в параллельном потоке.

AtomicInteger wCPU = new AtomicInteger(); 
DoubleAccumulator wnetwork = new DoubleAccumulator(Double::sum,0.d); 
DoubleAccumulator wMem = new DoubleAccumulator(Double::sum,0.d); 

this.slaContractList.forEach(sla -> { 
    wCPU.addAndGet(sla.getNumberOfCPUs()); 
    wnetwork.accumulate(sla.getNetworkBandwith()); 
    wMem.accumulate(sla.getMemory()); 
}); 

Теперь вы видите, что есть два вида реализации: Накопителя и атомная, выбор между этими 2 другим вопросом:

java 8 : Are LongAdder and LongAccumulator preferred to AtomicLong?

+0

Это кажется более чистым, чем создание дополнительного конструктора. –

Смежные вопросы