Java 8 Stream API: Сумма элементов списка с нулевыми элементами

· автор BaRoN · На чтение уйдёт 2 минуты · (390 слов)

Одна из вообще вечных проблем в Java, C++ и куче других языков - это null. Есть языки и без NULL вообще, но с ними мне работать не приходится. Вот, по работе возникла необходимость писать какую-то непонятную хрень, что-то среднее между Access, Excel и каким-нибудь JasperReports. Так-то руки чешутся взяться за Jasper, действительно, но незадача, основной формат на выходе - XLS или XLSX. Так что пишу самостоятельно. В общем, возникла такая ситуация:

Существует таблица X на Y, первые две её строки - заголовок, первый столбец тоже. Последний столбец и последняя строка - гражданин с самой лучшей фамилией Итого, он же John Total. В Excel такая штука решается просто, разве нет? =SUM(B3:B20), и всего делов. Но если сделать что-то подобное в Java, то нас ждёт суровый облом. Я подходил к формированию таблицы примерно таким образом (псевдокод, на самом деле там у нас фактически деревья):

projects = projectRepository.findAll();
projects.forEach(row -> {
	sheetRow = sheet.addRow()
	sheetRow.addCell(0, row.getName());
	sheetRow.addCell(1, row.getValue1());
	sheetRow.addCell(2, row.getValue2());
})

Как посчитать последний столбец? Ну, вроде, относительно просто - row.getValue1() + row.getValue2(). Оказывается, нет, это может вызвать NullPointerException. Но с этим мы ещё легко поборемся, а вот как нам посчитать последнюю строку? Моей первой попыткой стал самый простой способ в духе Java 8:

final int sum1 = projects.stream().mapToInt(Project::getValue1).sum();
final int sum2 = projects.stream().mapToInt(Project::getValue2).sum();

Однако, привет, NullPointerException! Из сложившейся ситуации есть два выхода, одним из которых я пользовался постоянно раньше на Groovy, там это выглядело так:

final sum1 = projects.collect {it.value1}.findAll {it}.sum();

На Java это бы выглядело так:

final int sum1 = projects.stream().map(Project::getValue1).filter(a -> a != null).mapToInt(a -> a).sum();

Но я решил попробовать написать по-другому:

final int sum1 = projects.stream().map(Project::getValue1).reduce((a, b) -> (a == null && b == null) ? 0 : (a == null ? b : (b == null) ? a : a + b)).get();

Честно говоря, пока не знаю, какой из этих способов лучше, хочу их на досуге расковырять и посмотреть, в какой байткод они компилируются и с какой скоростью работают. Теоретически, должны работать с одинаковой скоростью и генерировать примерно одинаковый опкод. Если руки доберутся, потом добавлю на своё исследование ссылку. А пока что пусть пример кода тут полежит, вдруг кому пригодится :).

А вот ещё немножко страхолюдного кода из того же проекта:

final int bi = projects.stream().mapToInt(project -> project.getEntries().stream().mapToInt(Entry::getValue).sum()).sum();

Не удивлюсь, если там ещё и sum().sum().sum() вскорости появятся. Озадачивать меня хитроумными вычислениями успевают сильнее, чем я успеваю их писать.

Полезное