#java #java-8 #java-stream #refactoring
#java #java-8 #java-stream #рефакторинг
Вопрос:
Я реализовал следующий класс DateBucket
, у которого есть вызываемый метод bucketize
. Этот метод возвращает список DateBucket
того, где каждый DateBucket
элемент списка находится между датами fromDate
и toDate
и использует bucketSize
и bucketSizeUnit
в качестве шагов для установки значений переменных from
и to
DateBucket
.
class DateBucket {
final Instant from;
final Instant to;
public static List<DateBucket> bucketize(
ZonedDateTime fromDate,
ZonedDateTime toDate,
int bucketSize,
ChronoUnit bucketSizeUnit
) {
List<DateBucket> buckets = new ArrayList<>();
boolean reachedDate = false;
for (int i = 0; !reachedDate; i ) {
ZonedDateTime minDate = fromDate.plus(i * bucketSize, bucketSizeUnit);
ZonedDateTime maxDate = fromDate.plus((i 1) * bucketSize, bucketSizeUnit);
reachedDate = toDate.isBefore(maxDate);
buckets.add(new DateBucket(minDate.toInstant(), maxDate.toInstant()));
}
return buckets;
}
}
В качестве примера следующий ввод:
fromDate -> 2020-12-07 00:00:00
toDate -> 2020-12-07 00:00:03
bucketSize -> 1
bucketSizeUnit -> ChronoUnit.SECONDS
Возвращает следующий DateBucket
список:
0. DateBucket(from = 2020-12-07 05:00:00, to = 2020-12-07 05:00:01)
1. DateBucket(from = 2020-12-07 05:00:01, to = 2020-12-07 05:00:02)
2. DateBucket(from = 2020-12-07 05:00:02, to = 2020-12-07 05:00:03)
3. DateBucket(from = 2020-12-07 05:00:03, to = 2020-12-07 05:00:04)
Итак, как я могу реорганизовать bucketize
метод, используя ту же логику, но с потоками Java 8?
Комментарии:
1. Обратите внимание, что вы создаете каждый объект ZonedDateTime и Instant дважды. Но вы можете сделать то же самое с таким потоком, как
return LongStream.rangeClosed(0, fromDate.until(toDate, bucketSizeUnit)) .mapToObj(l -> new DateBucket(fromDate.plus(l, bucketSizeUnit).toInstant(), fromDate.plus(l 1, bucketSizeUnit).toInstant())) .collect(Collectors.toList());
.
Ответ №1:
Вы могли бы сделать:
public static List<DateBucket> bucketize(
ZonedDateTime fromDate,
ZonedDateTime toDate,
int bucketSize,
ChronoUnit bucketSizeUnit
) {
var result = new ArrayList<DateBucket>();
IntStream.range(0, Integer.MAX_VALUE)
.mapToObj(i -> fromDate.plus(i * bucketSize, bucketSizeUnit))
.takeWhile(d -> d.isBefore(toDate))
.map(d -> d.toInstant())
.reduce((min, max) -> {
result.add(new DateBucket(min, max));
return max;
});
return resu<
}
Хотя эту функцию сокращения можно считать злоупотреблением stream API (предполагается, что функции сокращения не имеют состояния и ассоциативны, чего у нас нет, но в этом случае это должно быть безопасно, поскольку мы знаем, что поток является последовательным и, следовательно, будет повторяться по порядку), stream API не кажетсяпредложить более простой API для объединения смежных элементов потока.
Тем не менее, я бы, вероятно, предпочел вашу императивную реализацию.
Ответ №2:
Суть в том, что вы хотите передать значения, необходимые для создания сегментов, а затем map
и collect
их. Подойдет поток ZonedDateTimes
, и вы можете использовать a Spliterator
для создания потока. Похоже, это одна из возможных реализаций, которая является базовой и не поддерживает распараллеливание, но, похоже, работает.
public class ZonedDateTimeSpliterator implements Spliterator<ZonedDateTime> {
private final ZonedDateTime fromDate;
private final ZonedDateTime toDate;
private final int bucketSize;
private final ChronoUnit bucketSizeUnit;
private ZonedDateTime currentDate;
public ZonedDateTimeSpliterator(ZonedDateTime fromDate, ZonedDateTime toDate, int bucketSize,
ChronoUnit bucketSizeUnit) {
this.fromDate = fromDate;
this.toDate = toDate;
this.bucketSize = bucketSize;
this.bucketSizeUnit = bucketSizeUnit;
currentDate = fromDate.truncatedTo(bucketSizeUnit);
}
public void forEachRemaining(Consumer<? super ZonedDateTime> action) {
while (currentDate.plus(bucketSize, bucketSizeUnit).isBefore(toDate)) {
getNext(action);
}
}
private void getNext(Consumer<? super ZonedDateTime> action) {
action.accept(currentDate);
currentDate = currentDate.plus(bucketSize, bucketSizeUnit);
}
@Override
public boolean tryAdvance(Consumer<? super ZonedDateTime> action) {
if (currentDate.plus(bucketSize, bucketSizeUnit).isBefore(toDate)) {
getNext(action);
return true;
} else // cannot advance
return false;
}
@Override
public Spliterator<ZonedDateTime> trySplit() {
// implement for parallel processing
return null;
}
@Override
public long estimateSize() {
return Duration.between(fromDate.truncatedTo(bucketSizeUnit), toDate.truncatedTo(bucketSizeUnit))
.get(bucketSizeUnit);
}
@Override
public int characteristics() {
return ORDERED | SIZED | IMMUTABLE;
}
}
Затем вы просто stream
указываете значения, map
сегменты и collect
список.
private List<DateBucket> doThing(ZonedDateTime fromDate, ZonedDateTime toDate, int bucketSize,
ChronoUnit bucketSizeUnit) {
return StreamSupport
.stream(() -> new ZonedDateTimeSpliterator(fromDate, toDate, bucketSize, bucketSizeUnit),
Spliterator.ORDERED | Spliterator.SIZED | Spliterator.IMMUTABLE, false)
.map(currentDate -> new DateBucket(currentDate.toInstant(),
currentDate.plus(bucketSize, bucketSizeUnit).toInstant()))
.collect(Collectors.toList());
}
Комментарии:
1. Вы могли бы использовать простой
LongStream
like в комментарии к вопросу или реализоватьSpliterator
подобное, предложенное здесь. ASpliterator
позволит вам перейти к распараллеливанию, если вы хотите, хотя я не думаю, что этот пример оправдывает это. В основном я посмотрел на вопрос и подумал: «Вау, это можно сделать с помощью разделителя». У меня не так много (читай: нет) практики реализацииSpliterators
, поэтому я использовал этот ответ как возможность попрактиковаться. Выбор читателей.
Ответ №3:
Вы можете варьировать количество требуемых сегментов, заранее рассчитав это число с помощью TemporalUnit#between
:
public static List<DateBucket> bucketize(ZonedDateTime fromDate, ZonedDateTime toDate,
int bucketSize, ChronoUnit bucketSizeUnit) {
return LongStream.range(0, bucketSizeUnit.between(fromDate, toDate)/bucketSize 1)
.mapToObj( nb -> new DateBucket(
fromDate.plus(nb * bucketSize, bucketSizeUnit).toInstant(),
fromDate.plus((nb 1) * bucketSize, bucketSizeUnit).toInstant()))
.collect(Collectors.toList());
}