Одна из вообще вечных проблем в 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() вскорости появятся. Озадачивать меня хитроумными вычислениями успевают сильнее, чем я успеваю их писать.