요구사항
- 특정 시간 Column에서 누락이 발생할 경우 해당 시간대에 얼마나 누락이 발생되었는지 확인 필요
- 시작 시간과 끝 시간이 누락될 경우 해당 시간에서 얼마나 누락이 발생하였는지 확인 필요
- 전체가 누락될 경우 누락된 전체 시간 확인 필요
- 해당 데이터는 List 형태로 제공
기능 정리
- 연속적이지 않은 시간대와 해당 시간에 얼마나 누락이 발생하였는지 확인 필요
- 연속적인 기준은 초/분/시간이며 해당 로직 구성 필요
- 시작 시간이 List 첫번째 인덱스에 없을 경우, 마지막 시간이 List 마지막 인덱스에 없을 경우 추가
- 해당 시간에 얼마나 누락이 발생하였는지 확인 필요
기능 구현
선택한 방법은 전체를 순환하며 n 값과 n+1의 값을 비교 (n == (n+1) + 특정 시간(1초, 1분 ...))
시간 복잡도: $$O(nlog_n)$$
전체 코드
Enum (TimeUnit.java)
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.function.BiFunction;
public enum TimeUnit {
SECOND(LocalDateTime::plusSeconds, (duration, ignored) -> duration.getSeconds()),
MINUTE(LocalDateTime::plusMinutes, (duration, ignored) -> duration.toMinutes()),
HOUR(LocalDateTime::plusHours, (duration, ignored) -> duration.toHours()),
DAY(LocalDateTime::plusDays, (duration, ignored) -> duration.toDays());
private final BiFunction<LocalDateTime, Long, LocalDateTime> plusFunction;
private final BiFunction<Duration, Long, Long> durationFunction;
TimeUnit(
BiFunction<LocalDateTime, Long, LocalDateTime> plusFunction,
BiFunction<Duration, Long, Long> durationFunction) {
this.plusFunction = plusFunction;
this.durationFunction = durationFunction;
}
public LocalDateTime plusFunction(LocalDateTime time, Long seconds) {
return plusFunction.apply(time, seconds);
}
public Long durationFunction(Duration duration, Long seconds) {
return durationFunction.apply(duration, seconds);
}
}
Class (TimeFinder.java)
import lombok.extern.slf4j.Slf4j;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@Slf4j
public class TimeFinder {
private final List<LocalDateTime> timeStamps;
private final Long timeDuration;
private final TimeUnit timeUnit;
private static final Long SECOND = 1L;
private static final Long MINUTE = SECOND * 60L;
private static final Long HOUR = MINUTE * 60L;
private static final Long DAY = HOUR * 24L;
private TimeFinder(List<LocalDateTime> timeStamps, Long timeBranch) {
validate(timeStamps, timeBranch);
this.timeStamps = new LinkedList<>(timeStamps);
this.timeUnit = determineTimeUnit(timeBranch);
this.timeDuration = determineDurationTime(timeBranch);
}
private TimeFinder(List<LocalDateTime> timeStamps, Long timeBranch, LocalDateTime startTime, LocalDateTime endTime) {
this.timeUnit = determineTimeUnit(timeBranch);
this.timeDuration = determineDurationTime(timeBranch);
this.timeStamps = checkRangeTimeReturnTimeStamps(timeStamps, startTime, endTime);
validate(this.timeStamps, timeBranch);
}
/**
* 전체가 누락될 경우 전체 누락 시 가장 처음 시간과 마지막 시간을 처리하기 위한 로직 추가
* 반환 값은 전체 시간 List<LocalDateTime>
*
* @param timeStamps 누락을 확인할 원본 시간
* @param startTime 누락을 확인할 첫 시간
* @param endTime 누락을 확인할 마지막 시간
* @return List<LocalDateTime>
*/
private List<LocalDateTime> checkRangeTimeReturnTimeStamps(
List<LocalDateTime> timeStamps,
LocalDateTime startTime,
LocalDateTime endTime) {
LinkedList<LocalDateTime> result = new LinkedList<>(timeStamps);
LocalDateTime firstTime = this.timeUnit.plusFunction(startTime, (this.timeDuration) * -1);
LocalDateTime lastTime = this.timeUnit.plusFunction(endTime, this.timeDuration);
if(!(result.getFirst().equals(startTime))) {
result.addFirst(firstTime);
}
if(!(result.getLast().equals(endTime))) {
result.addLast(lastTime);
}
// 전체 누락일 경우 timeStamps 초기화
if(result.isEmpty()) {
log.warn("[Stats omission] Omit all row time ");
timeStamps = new ArrayList<>();
timeStamps.add(0, firstTime);
timeStamps.add(1, lastTime);
}
return new ArrayList<>(result);
}
public static Map<LocalDateTime, Long> findHole(
List<LocalDateTime> timeStamps,
Long timeBranch,
LocalDateTime start,
LocalDateTime end) {
Map<LocalDateTime, Long> result = new LinkedHashMap<>();
try {
TimeFinder timeFinder = new TimeFinder(timeStamps, timeBranch, start, end);
result = timeFinder.findNonConsecutiveTimes();
} catch (IllegalArgumentException e) {
log.error(e.getLocalizedMessage());
}
return result;
}
/**
* Map<LocalDateTime, Long> 반환
* 시간/분/초 단위로 누락된 시간의 첫 시간과 해당 Count 확인
*
* @return Map<LocalDateTime, Long> : 처음 누락된 시간(LocalDateTime), 시간 단위 별 Count(Long)
*/
private Map<LocalDateTime, Long> findNonConsecutiveTimes() {
int maxTraversal = timeStamps.size() - 1;
// 누락 부분 확인(filter())
// 첫 누락 시간(plusFunction())
// 누락 Count(durationFunction())
Map<LocalDateTime, Long> result = IntStream.range(0, maxTraversal)
.filter(i ->
!timeUnit.plusFunction(timeStamps.get(i), timeDuration)
.equals(timeStamps.get(i + 1))
)
.boxed()
.collect(Collectors.toMap(
i ->
timeUnit.plusFunction(timeStamps.get(i), timeDuration),
i ->
timeUnit.durationFunction(Duration.between(timeStamps.get(i), timeStamps.get(i+1)), timeDuration) - 1
));
// Time 정렬 후 반환
// LinkedHashMap : 삽입이 많이 발생하여 선택
return result.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(time, count) -> time, LinkedHashMap::new
));
}
private static void validate(List<LocalDateTime> timeStamps, Long timeBranch) {
validateTimeBranchIsPositive(timeBranch);
validateTimeBranchIsPositiveTime(timeBranch);
validateTimeStampsIsNull(timeStamps);
}
private static void validateTimeStampsIsNull(List<LocalDateTime> timeStamps) {
if (timeStamps == null || timeStamps.isEmpty()) {
throw new NullPointerException("[Somission] Timestamps is null");
}
}
private static void validateTimeBranchIsPositive(Long timeBranch) {
if (timeBranch <= 0) {
throw new IllegalArgumentException("[omission] Invalid timeBranch value (Stat)");
}
}
private static void validateTimeBranchIsPositiveTime(Long timeBranch) {
if (isMinute(timeBranch)) {
if (Math.floorMod(timeBranch, MINUTE) != 0) {
throw new IllegalArgumentException("[omission] Time branch is negative (Minute)");
}
}
if (isHour(timeBranch)) {
if (Math.floorMod(timeBranch, HOUR) != 0) {
throw new IllegalArgumentException("[omission] Time branch is negative (Hour)");
}
}
if (isDay(timeBranch)) {
if (Math.floorMod(timeBranch, DAY) != 0) {
throw new IllegalArgumentException("[omission] Time branch is negative (Day)");
}
}
}
/**
* timeBranch 따라 Plus, Duration between 함수 결정
*
* @param timeBranch 비교할 시간의 기준(단위: 초)
* @return TimeUnit 시간의 기준에 따라 TimeUnit 반환
*/
private static TimeUnit determineTimeUnit(Long timeBranch) {
if (isMinute(timeBranch)) {
return TimeUnit.MINUTE;
}
if (isHour(timeBranch)) {
return TimeUnit.HOUR;
}
if (isDay(timeBranch)) {
return TimeUnit.DAY;
}
return TimeUnit.SECOND;
}
/**
* timeBranch 크기에 따라 초/분/시의 기준 값 결정
*
* @param timeBranch 비교할 시간의 기준(단위: 초)
* @return Long 기준이 되는 시간(단위: 초/분/시)
*/
private static Long determineDurationTime(Long timeBranch) {
if (isMinute(timeBranch)) {
return Math.floorDiv(timeBranch, MINUTE);
}
if (isHour(timeBranch)) {
return Math.floorDiv(timeBranch, HOUR);
}
if (isDay(timeBranch)) {
return Math.floorDiv(timeBranch, DAY);
}
return timeBranch;
}
private static boolean isMinute(Long timeBranch) {
return timeBranch >= MINUTE && timeBranch <= HOUR;
}
private static boolean isHour(Long timeBranch) {
return timeBranch >= HOUR && timeBranch <= DAY;
}
private static boolean isDay(Long timeBranch) {
return timeBranch >= DAY;
}
}
누락 시간 확인
아래 설명을 통하여 전체를 확인.
For문으로 구현
전체 코드
private static Map<LocalDateTime, Long> findConsecutiveNumbers(List<LocalDateTime> timestamps) {
Map<LocalDateTime, Long> result = new LinkedHashMap<>();
for (int i = 0; i < timestamps.size() - 1; i++) {
if (!timestamps.contains(timestamps.get(i).plusMinutes(1))) {
LocalDateTime start = timestamps.get(i);
LocalDateTime end = timestamps.get(i + 1);
Long count = Duration.between(start, end).toMinutes() - 1;
result.put(start.plusMinutes(1), count);
}
}
return result;
}
상세 설명
- LinkedHashMap
삽입이 많이 이뤄지므로 LinkedHashMap으로 진행
또, 자원이 공유되지 않으므로 멀티 쓰레드를 고려하지 않아도 됨.(ConcurrentHashMap을 고려할 필요 없음)
Map<LocalDateTime, Long> result = new LinkedHashMap<>();
- 0 부터 N-1까지 탐색진행
for (int i = 0; i < timestamps.size() - 1; i++)
timeStamps[i]
와timeStamps[i+1]
를 비교하여 연속적이지 않은 경우 추가 로직 진행
if (!timestamps.contains(timestamps.get(i).plusMinutes(1)))
- 연속적이지 않은 시간대 추가
start.plusMinutes(1)
예를 들어 0분, 1분, 4분 ... 순으로 List에 있었을 경우 2분에 총 2개의 누락이 발생List[1]
이 1분List[2]
가 4분이며 두개를 비교할 때 i == 1,List[1] + 1분의 값
으로 저장
LocalDateTime start = timestamps.get(i);
result.put(start.plusMinutes(1), count);
- 해당 시간 얼마나 누락되었는지 확인
Duration.between(start, end).toMinutes() - 1
1분과 4분 사이는 3분이며 총 2분이 누락되었으므로 시간차이의 -1을 저장.
Long count = Duration.between(start, end).toMinutes() - 1;
result.put(start.plusMinutes(1), count);
Collection API로 구현
Collection API도 로직은 크게 변하지 않았으며 코드의 줄이 감소함.
private static Map<LocalDateTime, Long> findConsecutiveNumbers(List<LocalDateTime> timestamps) {
Map<LocalDateTime, Long> result = new LinkedHashMap<>();
IntStream.range(0, timestamps.size() - 1)
.filter(i -> !timestamps.get(i).plusMinutes(1).equals(timestamps.get(i + 1)))
.forEach(i -> {
LocalDateTime start = timestamps.get(i);
LocalDateTime end = timestamps.get(i + 1);
long count = Duration.between(start, end).toMinutes() - 1;
result.put(start.plusMinutes(1), count);
});
return result;
}
- For문은
IntStream.range(0, timestamps.size() - 1)
로 구현 - if문은
filter(i -> !timestamps.get(i).plusMinutes(1).equals(timestamps.get(i + 1)))
로 구현 - 누락된 시간대와 누락된 수는 아래 코드 참고
forEach(i -> { LocalDateTime start = timestamps.get(i); LocalDateTime end = timestamps.get(i + 1); long count = Duration.between(start, end).toMinutes() - 1; result.put(start.plusMinutes(1), count); });
결과 값이 정렬되지 않아 정렬 로직 추가
private static Map<LocalDateTime, Long> findConsecutiveNumbers(List<LocalDateTime> timestamps) {
Map<LocalDateTime, Long> result = new LinkedHashMap<>();
int maxTraversal = timestamps.size() - 1;
IntStream.range(0, maxTraversal)
.filter(i -> !timestamps.get(i).plusMinutes(1).equals(timestamps.get(i + 1)))
.forEach(i -> {
LocalDateTime start = timestamps.get(i);
LocalDateTime end = timestamps.get(i + 1);
long count = Duration.between(start, end).toMinutes() - 1;
result.put(start.plusMinutes(1), count);
});
return result.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(time, count) -> time, LinkedHashMap::new
));
}
result는 LocalDateTime 기준으로 정렬이 필요하여 sorted(Map.Entry.comparingByKey())
로 정렬 진행
정렬된 이후 해당 HashMap을 반환해야 함
- 반환되는 HashMap은 탐색이 필요 없으며 삽입만 많이 이뤄지므로 LinkedHashMap으로 선언)
(time, count) -> time, LinkedHashMap::new
중 LinkedHashMap::new
BiFunction으로 중복 최소화
해당 코드는 분만 확인이 가능한 상태.
- 초/분/시를 비교하기 위해서는 아래 함수만 변경되며 나머지는 중복
plusSeconds()
,plusMinutes()
,plusHours()
getSeconds()
,toMinutes()
,toHours()
중복 코드를 최소화하기 위하여 BiFunction으로 구현 진행- 함수 사용은 복잡하지 않으며 로직도 단순하여 BiFunction을 사용하여도 가독성을 해치지 않을 것으로 판단
전체 코드
/**
* Map<LocalDateTime, Long> 반환
* 시간/분/초 단위로 누락된 시간의 첫 시간과 해당 Count 확인
*
* @param timestamps 전체 시간
* @param plusFunction LocalDateTime::plusHours, LocalDateTime::plusSeconds, LocalDateTime::plusMinutes
* @param betweenFunction Duration::toHours, Duration::toMinutes, Duration::getSeconds
* @return Map<LocalDateTime, Long> : 처음 누락된 시간(LocalDateTime), 시간 단위 별 Count(Long)
*/
private Map<LocalDateTime, Long> findNonConsecutiveTimes(
List<LocalDateTime> timestamps,
BiFunction<LocalDateTime, Long, LocalDateTime> plusFunction,
BiFunction<Duration, Long, Long> betweenFunction,
Long durationTime) {
if(timestamps == null || timestamps.isEmpty()) {
throw new IllegalArgumentException("Empty time (Stat)");
}
if(durationTime == 0) {
throw new IllegalArgumentException("durationTime is zero(Stat)");
}
int maxTraversal = timestamps.size() - 1;
Map<LocalDateTime, Long> result = IntStream.range(0, maxTraversal)
.filter(i -> !plusFunction.apply(timestamps.get(i), durationTime).equals(timestamps.get(i + 1)))
.boxed()
.collect(Collectors.toMap(
i -> plusFunction.apply(timestamps.get(i), durationTime),
i -> (
betweenFunction.apply(Duration.between(timestamps.get(i), timestamps.get(i + 1)), durationTime)
) - 1
));
return result.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(time, count) -> time, LinkedHashMap::new
));
}
누락이 언제 발생하였으며 해당 Count를 저장할 임시 변수
Map<LocalDateTime, Long> result
Map<LocalDateTime, Long> result = IntStream.range(0, maxTraversal) .filter(i -> !plusFunction.apply(timestamps.get(i), durationTime).equals(timestamps.get(i + 1))) .boxed() .collect(Collectors.toMap( i -> plusFunction.apply(timestamps.get(i), durationTime), i -> ( betweenFunction.apply(Duration.between(timestamps.get(i), timestamps.get(i + 1)), durationTime) ) - 1 ));
저장한 임시변수를 정렬된 이후 반환
return result.entrySet().stream() .sorted(Map.Entry.comparingByKey()) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (time, count) -> time, LinkedHashMap::new ));
입력받은 시간이 초/분/시 중 어디에 해당하는지 확인
private static final Long SECOND = 1L; private static final Long MINUTE = 60L; private static final Long HOUR = 3600L; private static final Long DAY = 86400L; private Map<LocalDateTime, Long> findHole(List<LocalDateTime> timestamps, Long timeBranch) { Map<LocalDateTime, Long> result; BiFunction<LocalDateTime, Long, LocalDateTime> plusFunction = LocalDateTime::plusSeconds; BiFunction<Duration, Long, Long> toFunction = (duration, ignored) -> duration.getSeconds(); long durationTime = timeBranch; if (timeBranch >= MINUTE) { if(Math.floorMod(timeBranch, MINUTE) != 0) { throw new IllegalArgumentException(""); } plusFunction = LocalDateTime::plusMinutes; toFunction = (duration, ignored) -> duration.toMinutes(); durationTime = Math.floorDiv(timeBranch, MINUTE); } if (timeBranch >= HOUR) { if(Math.floorMod(timeBranch, HOUR) != 0) { throw new IllegalArgumentException(""); } plusFunction = LocalDateTime::plusHours; toFunction = (duration, ignored) -> duration.toHours(); durationTime = Math.floorDiv(timeBranch, HOUR); } if(timeBranch >= DAY) { if(Math.floorMod(timeBranch, DAY) != 0) { throw new IllegalArgumentException(); } plusFunction = LocalDateTime::plusDays; toFunction = (duration, ignored) -> duration.toDays(); durationTime = Math.floorDiv(timeBranch, DAY); } result = findNonConsecutiveTimes(timestamps, plusFunction, toFunction, durationTime); return result; }
해당 기능을 테스트하기 위한 테스트 함수
전체 코드
import automation.ops.util.TimeFinder;
import org.junit.jupiter.api.Test;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static org.assertj.core.api.Assertions.assertThat;
public class ConsecutiveTest {
private static final Long SECOND = 1L;
private static final Long MINUTE = 60L;
private static final Long HOUR = 3600L;
private static final Long DAY = 86400L;
@Test
void findConsecutiveMinutes() {
List<LocalDateTime> minutesTimestamps = Arrays.asList(
LocalDateTime.parse("2023-08-30T00:12:00"),
LocalDateTime.parse("2023-08-30T00:13:00"),
LocalDateTime.parse("2023-08-30T00:14:00"),
LocalDateTime.parse("2023-08-30T00:24:00"),
LocalDateTime.parse("2023-08-30T00:25:00"),
LocalDateTime.parse("2023-08-30T00:26:00"),
LocalDateTime.parse("2023-08-30T00:28:00")
);
List<LocalDateTime> secondTimestamps = Arrays.asList(
LocalDateTime.parse("2023-08-30T00:10:01"),
LocalDateTime.parse("2023-08-30T00:10:02"),
LocalDateTime.parse("2023-08-30T00:10:04"),
LocalDateTime.parse("2023-08-30T00:10:05"),
LocalDateTime.parse("2023-08-30T00:10:07"),
LocalDateTime.parse("2023-08-30T00:10:13"),
LocalDateTime.parse("2023-08-30T00:10:15")
);
Map<LocalDateTime, Long> resultMinutes = findHole(minutesTimestamps, MINUTE);
Map<LocalDateTime, Long> resultSeconds = findHole(secondTimestamps, SECOND);
assertThat(resultMinutes.size())
.isEqualTo(2);
assertThat(resultMinutes.keySet())
.contains(LocalDateTime.parse("2023-08-30T00:15"));
assertThat(resultMinutes.get(LocalDateTime.parse("2023-08-30T00:15")))
.isEqualTo(9L);
assertThat(resultMinutes.keySet())
.contains(LocalDateTime.parse("2023-08-30T00:27"));
assertThat(resultMinutes.get(LocalDateTime.parse("2023-08-30T00:27")))
.isEqualTo(1L);
assertThat(resultSeconds.size())
.isEqualTo(4);
assertThat(resultSeconds.keySet())
.contains(LocalDateTime.parse("2023-08-30T00:10:03"));
assertThat(resultSeconds.get(LocalDateTime.parse("2023-08-30T00:10:03")))
.isEqualTo(1L);
assertThat(resultSeconds.keySet())
.contains(LocalDateTime.parse("2023-08-30T00:10:06"));
assertThat(resultSeconds.get(LocalDateTime.parse("2023-08-30T00:10:06")))
.isEqualTo(1L);
assertThat(resultSeconds.get(LocalDateTime.parse("2023-08-30T00:10:08")))
.isEqualTo(5L);
assertThat(resultSeconds.keySet())
.contains(LocalDateTime.parse("2023-08-30T00:10:14"));
assertThat(resultSeconds.get(LocalDateTime.parse("2023-08-30T00:10:14")))
.isEqualTo(1L);
}
private Map<LocalDateTime, Long> findHole(List<LocalDateTime> timestamps, Long timeBranch) {
Map<LocalDateTime, Long> result;
BiFunction<LocalDateTime, Long, LocalDateTime> plusFunction = LocalDateTime::plusSeconds;
BiFunction<Duration, Long, Long> toFunction = (duration, ignored) -> duration.getSeconds();
long durationTime = timeBranch;
if (timeBranch >= MINUTE) {
if(Math.floorMod(timeBranch, MINUTE) != 0) {
throw new IllegalArgumentException("");
}
plusFunction = LocalDateTime::plusMinutes;
toFunction = (duration, ignored) -> duration.toMinutes();
durationTime = Math.floorDiv(timeBranch, MINUTE);
}
if (timeBranch >= HOUR) {
if(Math.floorMod(timeBranch, HOUR) != 0) {
throw new IllegalArgumentException("");
}
plusFunction = LocalDateTime::plusHours;
toFunction = (duration, ignored) -> duration.toHours();
durationTime = Math.floorDiv(timeBranch, HOUR);
}
if(timeBranch >= DAY) {
if(Math.floorMod(timeBranch, DAY) != 0) {
throw new IllegalArgumentException();
}
plusFunction = LocalDateTime::plusDays;
toFunction = (duration, ignored) -> duration.toDays();
durationTime = Math.floorDiv(timeBranch, DAY);
}
result = findNonConsecutiveTimes(timestamps, plusFunction, toFunction, durationTime);
return result;
}
/**
* Map<LocalDateTime, Long> 반환
* 시간/분/초 단위로 누락된 시간의 첫 시간과 해당 Count 확인
*
* @param timestamps 전체 시간
* @param plusFunction LocalDateTime::plusHours, LocalDateTime::plusSeconds, LocalDateTime::plusMinutes
* @param betweenFunction Duration::toHours, Duration::toMinutes, Duration::getSeconds
* @return Map<LocalDateTime, Long> : 처음 누락된 시간(LocalDateTime), 시간 단위 별 Count(Long)
*/
private Map<LocalDateTime, Long> findNonConsecutiveTimes(
List<LocalDateTime> timestamps,
BiFunction<LocalDateTime, Long, LocalDateTime> plusFunction,
BiFunction<Duration, Long, Long> betweenFunction,
Long durationTime) {
if(timestamps == null || timestamps.isEmpty()) {
throw new IllegalArgumentException("Empty time (Stat)");
}
if(durationTime == 0) {
throw new IllegalArgumentException("durationTime is zero(Stat)");
}
int maxTraversal = timestamps.size() - 1;
Map<LocalDateTime, Long> result = IntStream.range(0, maxTraversal)
.filter(i -> !plusFunction.apply(timestamps.get(i), durationTime).equals(timestamps.get(i + 1)))
.boxed()
.collect(Collectors.toMap(
i -> plusFunction.apply(timestamps.get(i), durationTime),
i -> (
betweenFunction.apply(Duration.between(timestamps.get(i), timestamps.get(i + 1)), durationTime)
) - 1
));
return result.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(time, count) -> time, LinkedHashMap::new
));
}
'개발공부 > JAVA' 카테고리의 다른 글
convert java List to json array String (0) | 2021.12.21 |
---|---|
Java Objecto To Json (0) | 2021.12.20 |