'development/Java'에 해당되는 글 3건

  1. 2017.04.25 [도서] Java8 in Action - 새로운 날짜/시간 API
  2. 2017.04.25 [도서] Java8 in Action - Stream
  3. 2015.06.18 serialize와 serialVersionUID

Java8 in Action 정리


12. 새로운 날짜와 시간 API


기존 날짜 API 문제점
  • Date
    • 1900년을 기준으로 하는 오프셋 
    • 0으로 시작하는 달 인덱스
    • 가변 객체
  • Calendar
    • Date 와의 호환성 유지 및 개선을 위해 나온 클래스
    • 오프셋 이슈는 해결했으나 0으로 시작하는 달 인덱스는 유지됨
    • 가변 객체
  • DateFormat
    • Date 클래스에만 작동
    • 스레드 세이프하지 않음.

새로운 날짜/시간 API
  • LocalDate
    • 시간을 제외한 날짜만 표현하는 불변객체
    • LocalDate today = LocalDate.now()// 시스템시계정보를 이용한 현재 날짜정보 획득

      LocalDate date = LocalDate.of(2016313)// 2016-03-13
      int year = date.getYear()// 2016
      Month month = date.getMonth()// MARCH
      int day = date.getDayOfMonth()// 13
      DayOfWeek dow = date.getDayOfWeek()// SUNDAY
      int len = date.lengthOfMonth()// 31 (3월의 일수)
      boolean leap = date.isLeapYear()// true (윤년)

      // TemporalField를 이용한 값 조회
      int year_ = date.get(ChronoField.YEAR)// 2016
      int month_ = date.get(ChronoField.MONTH_OF_YEAR)// 3
      int day_ = date.get(ChronoField.DAY_OF_MONTH)// 13

      int dayOfYear = date.getDayOfYear()// 73

  • LocalTime
    • 시간정보만을 갖는 객체
    • LocalTime time = LocalTime.of(134520)// 13:45:20
      int hour = time.getHour()// 13
      int minute = time.getMinute()// 45
      int second = time.getSecond()// 20

      LocalTime parseTime = LocalTime.parse("13:45:20");


  • LocalDateTime
    • LocalDate와 LocalTime을 쌍으로 갖는 복합 클래스
    • LocalDate localDate = LocalDate.of(2016Month.MARCH13);
      LocalTime localTime = LocalTime.of(134520);

      LocalDateTime dt1 = LocalDateTime.of(2016Month.MARCH13134520)// 2016-03-13 13:45:20
      LocalDateTime dt2 = LocalDateTime.of(localDatelocalTime);
      LocalDateTime dt3 = localDate.atTime(134520);
      LocalDateTime dt4 = localDate.atTime(localTime);
      LocalDateTime dt5 = localTime.atDate(localDate);

      LocalDate date = dt1.toLocalDate();
      LocalTime time = dt1.toLocalTime();


  • Instant
    • 유닉스 에포크 시간(1970-01-01 00:00:00 UTC)을 기준으로 특정 지점까지의 시간을 초로 표현
    • 나노초의 정밀도를 제공
    • Instant now = Instant.now();

      Instant i1 = Instant.ofEpochSecond(3)// 1970-01-01T00:00:03Z
      Instant i2 = Instant.ofEpochSecond(30)// 1970-01-01T00:00:03Z
      Instant i3 = Instant.ofEpochSecond(21000000000L)// 2초 이후의 1억 나노초(1초), 1970-01-01T00:00:03Z
      Instant i4 = Instant.ofEpochSecond(4-1000000000L)// 4초 이전의 1억 나노초(1초), 1970-01-01T00:00:03Z


  • Duration 과 Period
    • LocalDateTime은 사람이 사용하도록, Instant 는 기계가 사용하도록 만들어진 클래스로 두 인스턴스를 혼합해서 사용할 수 없음
    • Duration 클래스는 초와 나노초로 시간단위를 표현
    • Period는 년, 월, 일로 시간을 표현할 때 사용
    • Duration과 Period 클래스가 공통 제공하는 메서드
      • beetween : 두 시간 사이의 간격을 생성
      • from : 시간 단위로 간격을 생성
      • of : 주어진 구성 요소에서 간격 인스턴스를 생성
      • parse : 문자열을 파싱해서 간격 인스턴스를 생성
      • addTo : 현재값의 복사본을 생성한 후 지정된 Temporal 객체에 추가
      • get : 현재 간격 정보값을 읽음
      • isNegative : 간격이 음수인지 확인
      • isZero : 간격이 0인지 확인
      • minus : 현재값에서 주어진 시간을 뺀 복사본을 생성
      • multipliedBy : 현재값에 주어진 값을 곱한 복사본을 생성
      • negated : 주어진 값의 부호를 반전한 복사본을 생성
      • plus : 현재값에 주어진 시간을 더한 복사본을 생성
      • subtractFrom : 지정된 Temporal 객체에서 간격을 뺌

  • 특정시점을 표현(조정)하는 날짜 시간 클래스의 공통 메서드
    • LocalDate.of(2014, 3, 18).withYear(2011).withDayOfMonth(25).with(ChronoField.MONTH_OF_YEAR, 9);
      • 2014-03-18  >  2011-03-18  >  2011-03-25  >  2011-09-25
    • 공통 메서드
      • from : 주어진 Temporal 객체를 이용해서 클래스의 인스턴스를 생성
      • now : 시스템 시계로 Temporal 객체를 생성
      • of : 주어진 구성요소에서 Temporal 객체의 인스턴스를 생성
      • parse : 문자열을 파싱해서 Temporal 객체를 생성
      • atOffset : 시간대 오프셋과 Temporal 객체를 합침
      • atZone : 시간대와 Temporal 객체를 합침
      • format : 지정된 포매터를 이용해서 Temporal 객체를 문자열로 변환 (Instant 는 지원하지 않음)
      • get : Temporal 객체의 상태를 읽음
      • minus : 특정 시간을 뺀 Temporal 객체의 복사본을 생성
      • plus : 특정시간을 더한 Temporal 객체의 복사본을 생성
      • with : 일부 상태를 바꾼 Temporal 객체의 복사본을 생성
  • TemporalAdjusters
    • 날짜 시간 API를 다양한 상황에서 사용할 수 있도록 한 클래스
      • LocalDate.of(2014, 3, 18).with(nextOrSame(DayOfWeek.SUNDAY)).with(lastDayOfMonth());
        • 2014-03-18  >  2014-03-23 (3/18 포함하여 이후로 처음 나타나는 일요일)  >  2014-03-31 (3월의 마지막 일)
    • TemporalAdjuster 함수형 인터페이스를 통해 커스텀 기능을 제공할 수 있다.
    • TemporalAdjusters 의 팩토리 메서드
      • dayOfWeekInMonth : ‘3월의 둘째 화요일’처럼 서수 요일에 해당하는 날짜를 반환하는 TemporalAdjuster를 반환
      • firstDayOfMonth : 현재 달의 첫 번째 날짜를 반환하는 TemporalAdjuster를 반환
      • firstDayOfNextMonth : 다음 달의 첫 번째 날짜를 반환하는 TemporalAdjuster를 반환
      • firstDayOfNextYear : 내년의 첫 번째 날짜를 반환하는 TemporalAdjuster를 반환
      • firstDayOfYear : 올해의 첫 번째 날짜를 반환하는 TemporalAdjuster를 반환
      • firstInMonth : ‘3월의 첫 번째 화요일’처럼 현재 달의 첫 번째 요일에 해당하는 날짜를 반환하는 TemporalAdjuster를 반환
      • lastDayOfMonth : 현재 달의 마지막 날짜를 반환하는 TemporalAdjuster를 반환
      • next : 현재 날자 이후로 지정한 요일이 처음으로 나타나는 날짜를 반환하는 TemporalAdjuster를 반환 (현재날짜는 포함하지 않음)
      • previous : 현재 날짜 이후로 역으로 날짜를 거슬러 올라가며 지정한 요일이 처음으로 나타나는 날짜를 반환하는 TemporalAdjuster를 반환 (현재날짜 미포함)
      • nextOrSame : 현재 날짜 이후로 지정한 요일이 처음으로 나타나는 날짜를 반환하는 TemporalAdjuster를 반환 (현재날짜 포함)
      • previousOrSame : 현재 날짜 이후로 역으로 날짜를 거슬러 올라가며 지정한 요일이 처음으로 나타나는 날짜를 반환하는 TemporalAdjuster를 반환(현재날짜 포함)
  • DateTimeFormatter
    • 기존 DateFormat과 달리 스레드 세이프한 날짜 시간 포매터 클래스
    • // 패턴을 통한 포매터 생성
      DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
      String formattedDate = LocalDate.of(2014318).format(formatter)// 18/03/2014
      LocalDate date = LocalDate.parse(formattedDateformatter);

      // 지역화된 DateTimeFormatter
      DateTimeFormatter italianFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy"Locale.ITALIAN);
      String formattedDateItalian = LocalDate.of(2014318).format(italianFormatter)// 18. marzo 2014
      LocalDate italianDate = LocalDate.parse(formattedDateItalianitalianFormatter);

      // Builder를 통한 Formatter 생성
      DateTimeFormatter formatterByBuilder = new DateTimeFormatterBuilder()
              .appendText(ChronoField.DAY_OF_MONTH// d
              .appendLiteral(". ").appendText(ChronoField.MONTH_OF_YEAR)
              .appendLiteral(" ").appendText(ChronoField.YEAR)
              .parseCaseInsensitive()
              .toFormatter(Locale.GERMANY);

      String formattedDateByBuilder = LocalDate.of(2014318).format(formatterByBuilder)// 18. Marz 2014

  • ZoneId, ZoneRuls, ZonedDateTime
    • TimeZone을 대체하는 불변 클래스로 서머타임(DST) 같은 복잡한 사항이 자동으로 처리 가능
    • ZoneRuls 클래스에는 약 40개 정도의 시간대가 있으며 ZoneId의 getRules 메서드를 이용하여 해당 시간대 규정 획득 가능
    • ZonedDateTime = LocalDate + LocalTime + ZoneId 로 지정한 시간대에 상대적인 시점을 표현.
    • // 기존 TimeZone 클래스에서 ZoneId 변환
      ZoneId zoneId = TimeZone.getDefault().toZoneId()// "Asia/Seoul"

      // 지역ID는 "지역/도시" 형식으로 이루어지며 IANA Time Zone Database에서 제공하는 지역 집합정보 사용
      ZoneId romeZone = ZoneId.of("Europe/Rome");

      // 지정한 시간대에 상대적인 시점을 표현하는 ZonedDateTime
      ZonedDateTime zdt1 = LocalDate.of(2014318).atStartOfDay(romeZone)// 2014-03-18T00:00+01:00[Europe/Rome]
      ZonedDateTime zdt2 = LocalDateTime.of(20143181345).atZone(romeZone)//  2014-03-18T13:45+01:00[Europe/Rome]
      ZonedDateTime zdt3 = Instant.now().atZone(romeZone)// 2016-03-13T11:58:38.743+01:00[Europe/Rome]

      // ZoneId를 활용한 LocalDateTime과 Instnat간의 변환 (LocalDateTime은 ZoneId를 상속한 ZoneOffset을 변수로 받는다)
      LocalDateTime timeFromInstant = LocalDateTime.ofInstant(Instant.now()romeZone)// 2016-03-13T11:58:38.743
      Instant instantFromDateTime = LocalDateTime.of(20143181345).toInstant(romeZone.getRules().getOffset(timeFromInstant))// zoneOffset = "+01:00" (totalSeconds=3600)


      // ZonedDateTime은 Instnat, LocalDateTime, LocalDate, LocalTime 모두 변환 가능
      Instant instant = zdt1.toInstant()// 2014-03-17T23:00:00Z
      LocalDateTime localDateTime = zdt1.toLocalDateTime()// 2014-03-18T00:00

  • ZoneOffset, OffsetDateTime
    • ZoneOffset : ZoneId를 상속한 클래스. 서머타임을 제대로 처리할 수 없으므로 권장하진 않는다
    • OffsetDateTime : ISO-8601 캘린더 시스템에서 정의하는  UTC/GMT와 오프셋으로 날짜와 시간 표현
    • ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
      OffsetDateTime dateTimeInNewYork = OffsetDateTime.of(LocalDateTime.of(20143181345)newYorkOffset)// 2014-03-18T13:45-05:00

  • 대안 캘린더 시스템
    • ISO-8601 캘린더 시스템은 실질적으로 전세계에서 통용되나 자바8에서는 추가로 4개의 캘린더 시스템을 제공
      • ThaiBuddhistDate : 
      • MinguoDate : 
      • JapaneseDate : 일본력 캘린더 시스템
      • HijrahDate : 이슬람력 캘린더 시스템
    • ChronoLocalDate
      • 임의의 연대기에서 특정 날짜를 표현할 수 있는 기능을 제공하는 인터페이스
      • 자바8에서 추가된 캘린더시스템은 모두 이 인터페이스를 상속하고있어 LocalDate를 이용해 4개의 클래스 인스턴스로 변환이 가능
    • Chronology
      • 캘린더 시스템으로 ofLocale을 이용하여 Chronology의 인스턴스 획득이 가능하다.
    • JapaneseDate japaneseDate = JapaneseDate.from(LocalDate.of(2014318))// Japanese Heisei 26-03-18
      Chronology japaneseChronology = Chronology.ofLocale(Locale.JAPAN);
      ChronoLocalDate now = japaneseChronology.dateNow()// 2016-03-13


'development > Java' 카테고리의 다른 글

[도서] Java8 in Action - Stream  (0) 2017.04.25
serialize와 serialVersionUID  (0) 2015.06.18
Posted by dreamhopp
,

Java8 in Action 정리


4. 스트림 소개


스트림 API
  • 데이터 컬렉션 반복을 선언형으로 처리하고 조립하는 기능
  • 멀티 스레드 코드를 구현하지 않고도 데이터 처리과정을 병렬화하여 스레드와 락 걱정없이 처리 가능
  • 스트림 API는 매우 비싼 연산이다.

스트림
  • 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소

스트림과 컬렉션 차이
  • 데이터를 언제 계산하는가
    • 스트림 : 요청할 때만 요소를 계산하는 고정된 자료구조 (스트림에 요소를 추가/제거 불가)
    • 컬렉션 : 현재 자료구조가 포함하는 모든 값을 메모리에 저장
  • 딱 한번만 탐색할 수 있다.
    • Awways.asList(“1”,”2”).stream().forEach(System.out::println).forEach(System.out::println)
      • 첫번째 forEach에서 데이터가 모두 소비되어 다음 forEach로 전달된 스트림 요소가 존재하지 않음. (파이프라이닝으로 다음 forEach로 전달된 요소가 없음)
  • 반복과 병렬성
    • 외부반복
      • 사용자가 직접 요소를 반복하는 방식 (컬렉션)
      • 병렬성을 사용자가 직접 관리해야 함 (syncronized)
    • 내부반복
      • 반복을 알아서 처리하고 결과 스트림값을 어딘가에 저장
      • 데이터 표현과 하드웨어를 활용한 병렬성 구현을 자동으로 선택

데이터 소스
  • 컬렉션, 배열, I/O 자원 등의 데이터 제공 소스
  • 스트림을 생성하면 자료구조의 순서와 같은 순서의 스트림이 생성된다.

중간연산
  • filter, sorted와 같이 다른 연산과 연결될 수 있는 연산
  • 중간연산을 이용해서 파이프라인을 구성할 수 있으며 어떤 결과도 생성할 수 있음.
  • 게으른 연산 : 중간 연산을 합친 다음에 합쳐진 중간 연산을 최종으로 한번에 처리
    • List<String> names =
      menu.stream()
               .filter(d -> {System.out.println(“filtering” + d.getName()); return d.getCalories() > 300; })
               .map(d -> {System.out.println(“mapping” + d.getName()); return d.getname(); })
               .limit(3).collect(toList());
      System.out.println(names);
    • filtering pork
      mapping pork
      filtering beef
      mapping beef
      filtering chicken
      mapping chicken
      [pork, beef, chicken]
    • 쇼트서킷 : limit을 통해 필터링 된 여러 요소 중 3개만 선택됨
    • 루프 퓨전 : filter, map 등 다른 연산이지만 한 과정으로 병합되어 처리

연산
형식
사용된 함수 디스크립터
설명
filter
Stream<T> filter(Predicate<? super T> predicate)
T -> boolean
Predicate(boolean을 반환하는 함수)를 인수로 받아
일치(true)하는 모든 요소를 포함하는 스트림 반환
distinct
Stream<T> distinct()

고유 요소 필터링 (상태 있는 언바운드)
hashCode, equals 메서드를 통해 고유여부 판별
skip
Stream<T> skip(long n)

요소 건너뛰기 (상태 있는 바운드)
처음 n개 요소를 제외한 스트림을 반환
limit
Stream<T> limit(long maxSize)

스트림 축소 (상태 있는 바운드)
n개 이하의 크기를 갖는 새로운 스트림을 반환
map
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
T -> R
mapToInt, mapToDouble, mapToLong 메서드를 통해 기본형 특화 스트림(IntStream, DoubleStream, LongStream)으로 변환할 수 있다.
객체스트림으로 복원하려면  숫자 스트림의 boxed 메서드를 호출하면 된다.
flatMap
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
T -> Stream<R>
각 배열을 스트림이 아니라 스트림의 콘텐츠로 매핑한다.
1개 이상의 스트림의 각 요소를 하나의 스트림으로 변환
sorted
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)

(T, T) -> int
상태 있는 언바운드
peek
Stream<T> peek(Consumer<? super T> action)
T -> T
자신이 확인한 요소를 파이프라인의 다음연산으로 그대로 전달

최종연산
  • 스트림 파이프라인을 처리해서 스트림이 아닌 결과를 반환하는 연산
  • 보통 최종 연산에 의해 List, Integer, void 등 스트림 이외의 결과가 반환
  • 딱 한번만 탐색할 수 있다.

연산
형식
사용된 함수 디스크립터
설명
anyMatch
boolean anyMatch(Predicate<? super T> predicate)
T -> boolean
프레디케이트가 적어도 한 요소와 일치하는지 확인
boolean 반환
noneMatch
boolean noneMatch(Predicate<? super T> predicate)
T -> boolean
프레디케이트가 모든 요소와 일치하는 요소가 없는지 확인
boolean 반환
allMatch
boolean allMatch(Predicate<? super T> predicate)
T -> boolean
프레디케이트가 모든 요소와 일치하는지 확인
boolean 반환
findAny
Optional<T> findAny()

스트림에서 임의의 요소를 반환
반환값이 없을경우  NPE를 피하기위해 Optional로 반환
findFirst
Optional<T> findFirst()


forEach
void forEach(Consumer<? super T> action)
T -> void
스트림의 각 요소를 소비하면서 람다를 적용.
void를 반환한다.
collect
<R, A> R collect(Collector<? super T, A, R> collector)
<R> R collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner)

스트림을 리듀스해서 리스트(toList), 맵(toMap), 정수 형식의 컬렉션을 만든다.
reduce
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
<U> U reduce(U identity,
                 BiFunction<U, ? super T, U> accumulator,
                 BinaryOperator<U> combiner)
(T, T) -> T
상태 있는 바운드(내부 상태의 크기가 한정)
count
long count()

스트림의 요소 개수를 반환한다. long을 반환한다


5. 스트림 활용


flatMap

“HelloWorld".stream().map( w -> w.split(“”) ).flatMap(Arrays::stream).distinct().collect(toList())
  • map( w -> w.split(“”)) : “HelloWorld” => String[] {“H”,”e”,”l”,”l”,”o”,”W”,”o”,”r”,”l”,”d”}
  • disticnt() : String[] {“H”,”e”,”l”,”o”,”W”,”r”,”d”}
  • flatMap(Arrays:stream) : Stream<String[]> => Stream<String>
    • Arrays.stream(T[]) : 배열을 스트림으로 변환 

Optional<T>
  • 값의 존재/부재 여부를 표현하는 컨테이너 클래스
  • null값 반환으로 인한 에러를 피하기 위해 만들어진 기능.
  • 제공 메서드
    • isParent() : Optional이 값을 포함하녀 true, 없으면 false
    • ifPresent(Consumer<T> block) : 값이 있으면 주어진 블록을 실행
    • T get() : 값이 존재하면 값을 반환하고, 없으면 NoSuchElementException을 발생시킴
    • T orElse(T other) : 값이 있으면 값을 반환하고, 없으면 기본값(other)을 반환한다.

findFirst와 findAny
  • 병렬 실행에서는 첫번째 요소를 찾기 어려우므로 요소 반환 순서가 상관없다면 병렬스트림에서는 제약이 적은 findAny를 사용


reduce

reduce(초기값, (초기값 or 이전계산의 결과값, 요소값) -> 반환값이 있는 람다식)
  • int sum = numbers.stream().reduce(0, (a,b) -> a + b);
    • numbers의 모든 요소의 합을 구하는 코드
Optional<T> reduce((T, T) -> T)
  • Optional<Integer> sum = numbers.stream().reduce((a, b) ->  (a + b));
  • 초기값을 받지 않도록 오버로드된 메서드
  • 스트림에 아무런 요소가 존재하지 않는경우 초기값으로 할당할 요소가 존재하지 않아 반환값도 존재하게되지 않으므로 Optional을 반환하도록 설계
  • 기본형 특화 스트림에서 값이 없는경우와 실제 결과값이 초기값과 같은경우를 구분하기 위해 기본형 특화 Optional 클래스도 제공한다.
    • OptionalInt, OptionalDouble, OptionalLong

내부 상태를 갖는 연산
  • 종류 : distinct, skip, limit, sorted, reduce
  • 연산을 처리하려면 모든 요소가 버퍼에 추가되어있어야 하거나 결과값을 다음연산의 초기값으로 상태값을 가지고있어야 처리가능한 연산
  • 연산을 수행하는데 필요한 저장소의 크기가 정해져 있는경우 바운드(bounded), 스트림의 요소갯수에 따라 무한으로 늘어날 수 있는경우 언바운드(unbounded)

Stream 만들기
  • 값으로 스트림 만들기
    • Stream<T> of(T t)
    • Stream<T> of(T... values)
  • 빈 스트림 만들기
    • Stream<T> empty()
  • 배열로 스트림 만들기
    • Stream<T> stream(T[] array)
  • 파일로 스트림 만들기
    • Stream<String> lines(Path path, Charset cs)
  • 함수로 무한 스트림 만들기
    • 요청할 때마다 주어진 함수를 이용해서 무제한으로 값을 생성할 수 있으며, 보통 limit 함수와 함께 사용해서 무한 실행되지 않도록 한다.
    • Stream<T> iterate(final T seed, final UnaryOperator<T> f)
      • Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println)
      • 상태값이 있는 메서드. 연속된 일련의 값을 만들 때 사용
    • Stream<T> generate(Supplier<T> s)
      • Steam.geneate(Math::random).limit(5).forEach(System.out::println)
      • 상태값이 없는 메서드, 연속되지 않은 값을 만들 때 사용


6. 스트림으로 데이터 수집

Collectors 정적 팩토리 메서드

팩토리메서드
반환형식
사용예제
설명
toList
List<T>
List<Dish> dishes = menuStream.collect(toList());
스트림의 모든 항목을 리스트로 수집
toSet
Set<T>
Set<Dish> dishes = menuStream.collect(toSet());
스트림의 모든 항목을 중복없는 집합으로 수집
toCollection
Collection<T>
Collection<Dish> dishes = menuStream.collect(toCollection(), ArrayList::new);
스트림의 모든 항목을
공급자가 제공하는 컬렉션으로 수집
counting
Long
long howManyDishes = menuStream.collect(counting());
스트림의 항목 수 계산
summingInt
Integer
int totalCalories = menuStream.collect(summingInt(Dish::getCalories));
스트림의 항목에서 정수 프로퍼티 값을 더함
averagingInt
Double
double avgCalories = menuStream.collect(averagingInt(Dish::getCalories));
스트림 항목의 정수 프로퍼티의 평균값 계산
summarizingInt
IntSummaryStatistics
IntSummaryStatistics summary = menuStream.collect(summarizingInt(Dish::getCalories));
// summary = {count=9, sum=4300, min=120, average=477.777778, max = 800}
스트림 내의 항목의 최대, 최소, 합계, 평균 등의 정수 정보 통계를 수집
joining
String
String shortMenu = menuStream.map(Dish::getName).collect(joining(“, “));
스트림의 각 항목에 toString 메서드를 호출한 결과 문자열을 연결
maxBy
Optional<T>
Optional<Dish> fattest = menuStream.collect(maxBy(comparingInt(Dish::getCalories)));
주어진 비교자를 이용해서 스트림의 최대값 요소를 Optional로 감싼 값을 반환.
스트림에 요소가 없는경우 Optional.empty() 반환
minBy
Optional<T>
Optional<Dish> lightest = menuStream.collect(minBy(comparingInt(Dish::getCalories)));
주어진 비교자를 이요해서 스트림의 최소값 요소를 Optional로 감싼 값을 반환
스트림에 요소가 없는경우 Optional.empty() 반환
reducing
리듀싱 연산에서 형식을 결정
int totalCalories = menuStream.collect(reducing(0, Dish::getCalories, Integer::sum));
누적자를 초깃값으로 설정한 다음 BinaryOperator로 스트림의 각 요소를 반복적으로 누적자와 합쳐 스트림을 하나의 값으로 리듀싱
collectingAndThen
변환함수가 형식을 반환
int howManyDishes = menuStream.collect(collectingAndThen(toList(), List::size));
다른 컬렉터를 감싸고 그 결과에 변환 함수를 적용
groupingBy
Map<K, List<T>>
Map<Dish.Type, List<Dish>> dishesByType
= menuStream.collect(groupingBy(Dish::getType), toList());
하나의 프로퍼티값을 기준으로 스트림의 항목을 그룹화하며 기준 프로퍼티값을 결과 맵의 키로 사용
partitioningBy
Map<Boolean, List<T>>
Map<Boolean, List<Dish>> vegetarianDishes
= menuStream.collect(partitioningBy(Dish::isVegetarian));
결과 : {false=[pork, beef], true=[french fries, rice, pizza]}
프레디케이트를 스트림의 각 항목에 적용한 결과로 항목을 분할


Collector.reducing과 Stream.reduce
  • reducing
    • 도출하려는 결과를 누적하는 컨테이너로 바꾸도록 설계된 메서드
    • 가변 컨테이너 작업의 병렬연산에도 안전하다
  • reduce
    • 두 값을 하나로 도출하는 불변형 연산
    • 병렬로 reducing과 같은 연산을 처리하는경우 불변형이 깨지거나 매번 새로운 리스트를 생성하여 값을 할당해야하므로 성능이 저하될 수 있다.

다수준 그룹화
  • menu.stream().collect(groupingBy(Dish::getType, groupingBy(dish -> {
        if (d.getCalories() <= 400) return CaloricLevel.DIET;
        else if (d.getCalorids() <= 700) return CaloricLevel.NORMAL;
        else return CaloricLevel.FAT;
    })));
    • 결과
      {MEAT={DIET=[chicken], NORMAL=[beef], FAT=[pork]},
       FISH={DIET=[prawns], NORMAL=[salmon]},
       OTHER={DIET=[rice, seasonal fruit], NORMAL=[french fries, pizza]}}
  • menu.stream().collect(groupingBy(Dish::getType, counting()));
    • 결과 : {MEAT=3, FISH=2, OTHER=4}

Collector Interface

public interface Collector<TAR> {
    Supplier<Asupplier();
    BiConsumer<ATaccumulator();
    BinaryOperator<Acombiner();
    Function<ARfinisher();
    Set<Characteristics> characteristics();
   
    enum Characteristics {
        CONCURRENTUNORDEREDIDENTITY_FINISH
    }
}

  1. supplier
    • accumulator에서 사용할 빈 누적자 인스턴스를 만드는 함수
  2. accumulator
    • 리듀싱 연산을 수행하는 함수(void return) 반환
    • supplier가 생성한 누적자 인스턴스를 받아 함수의 상태값을 저장하고 반환값은 void가 된다. (내부탐색만 가능하며 계속 상태가 바뀌므로 어떤값일지 추적 불가)
  3. combiner
    • 마지막으로 리듀싱 연산에서 사용할 함수를 반환
    • 스트림의 서로 다른 서브파트를 병렬로 처리할 때 누적자가 이 결과를 어떻게 처리할지 정의
    • 병렬 처리시 스트림을 분할해야 하는지 정의하는 조건이 거짓으로 바뀌기 전까지 원래 스트림을 재귀적으로 분할하며,
      일반적으로 프로세싱 코어의 개수 이하의 병렬작업이 효율적이다.(p214)
  4. finisher
    • 누적과정을 끝낼 때 호출할 함수를 반환
    • 누적자 객체가 이미 최종결과인 경우 변환과정이 필요없는 항등함수(Function.identity())를 반환하면 된다.
  5. characteristics
    • 스트림을 병렬로 리듀스할 것인지, 병렬로 리듀스 할 경우 어떤 최적화를 선택해야할지 힌트를 제공
    • UNORDERED
      • 리듀싱 결과는 스트림 요소의 방문순서나 누적 순서에 영향을 받지 않는다.
    • CONCURRENT
      • 다중 스레드에서 accumulator 함수를 동시에 호출할 수 있으며 이 컬렉터는 스트림의 병렬 리듀싱을 수행할 수 있다.
      • UNORDERED를 함께 설정하지 않았다면 데이터 소스의 순서가 무의미한 상황에서만 병렬 리듀싱을 수행할 수 있다.
    • IDENTITY_FINISH
      • 리듀싱 과정의 최종 결과로 누적자 객체를 바로 사용할 수 있으며, 누적자 객체를  A -> R로 안전하게 형변환 할 수 있다.


7. 병렬 데이터 처리와 성능

Stream.parallel()
  • 스트림 자체에는 아무 변화도 일어나지 않는다
  • parallel을 호출하면 내부적으로 이후 연산이 병렬로 수행해야 함을 의미하는 불린 플래그가 설정된다.

Stream.sequential()
  • 병렬 스트림을 순차 스트림으로 바꿀 수 있다.
  • sequential을 호출하면 이후 연산이 순차적으로 수행해야 함을 의미하는 불린 플래그가 설정된다.

병렬 스트림에서 사용하는 스레드 풀 설정
  • 병렬스트림은 내부적으로 ForkJoinPool을 사용
  • ForkJoinPool은 기본적으로 Runtime.getRuntime().availableProcessors()가 반환하는 값에 상응하는 스레드를 사용(가상 프로세서를 포함한 프로세서 수)

병렬 스트림 사용시 주의해야할 점
  • 자료구조 변화에 따른 auto boxing으로 인한 성능저하
    • 기본형 특화 스트림을 사용하여 auto boxing 회피
  • 병렬실행을 위한 재귀적인 청크 분할, 스레드 할당, 머지 등 작업을 위한 비용과 성능개선비용의 관계
    • 성능측정을 통한 병렬 스트림 사용시의 이점 확인
    • 소량의 데이터의 경우 병렬화 과정으로 인한 비용이 더 크게 발생할 수 있다.
    • 최종연산의 병합과정 비용보다 병렬 스트림으로 얻은 성능의 이익이 더 큰지 확인
  • 순서에 의존하는 limit, findFirst 등 연산은 병렬스트림에서 성능이 저하됨.
  • 공유된 가변상태 변수로 인한 로직 오류 발생
  • 효율적으로 분할 가능한 자료구조로 스트림이 구성되었는지 확인
    • 분해성
      • 훌륭함 : ArrayList, IntStream.range
      • 좋음 : HashSet, TreeSet
      • 나쁨 : LinkedList, Stream.iterate

Fork/Join Framework
(병렬작업의 분할/작업/머지 등을 컨트롤 하고싶은경우 직접 구현가능 - p238~245)

  • 작업 훔치기
    • ForkJoinPool에서 각 스레드는 자신에게 할당된 태스크를 포함하는 이중연결 리스트를 참조하면서 작업이 끝날때 마다 큐의 HEAD 에서 다른 태스크를 가져와 작업을 처리
    • 특정 스레드가 먼저 작업이 완료되었을 ㄷ경우 다른 스레드 큐의 TAIL 에서 작업을 훔쳐와 모든 큐가 빌 때 까지 이 작업을 반복하여 스레드간 작업부하를 비슷한 수준으로 유지
  • Spliterator
    • 자바 8은 컬렉션 프레임워크에 포함된 모든 자료구조에 대해 사용할 수 있는 디폴트 Spliterator 구현을 제공
    • 제공 메서드
      • tryAdvance : 일반적인 Iterator 동작과 동일
      • trySplit : 스트림을 재귀적으로 분할하며 모든 trySplit 결과가 null이면 분할과정이 종료된다.
        • Spliterator의 일부 요소(자신이 반환한 요소)를 분할해서 두번째 Spliterator를 생성하는 메서드
        • estimateSize 메서드로 탐색해야할 요소 수 정보를 제공할 수 있다.(정확성 X)
      • characteristics : Spliterator 자체의 특성 집합을 포함하는 int를 반환하며, 이 특성을 이용해서 Spliterator를 더 잘 제어하고 최적화 할 수 있다
        • ORDERED, DISTINCT, SORTED, SIZE, NONNULL, IMMUTABLE, CONCURRENT, SUBSIZED


'development > Java' 카테고리의 다른 글

[도서] Java8 in Action - 새로운 날짜/시간 API  (0) 2017.04.25
serialize와 serialVersionUID  (0) 2015.06.18
Posted by dreamhopp
,

http://cafe.naver.com/swnara/369
http://kevinx64.net/107


개발하다보면 종종 serialVersionUID를 보게된다. 이값은 어디에 사용하는것일까 궁금해서 찾아보게 되었다.


Serialize (직렬화)

객체를 파일로 저장하거나, 혹은 다른 사람에서 전송하기 위해서는 직렬화(serialize) 및 역직렬화(deserialize)가 필요하다.
직렬화란 메모리나 저장공간에 담을 수 있도록 일렬로 만드는 것을 의미하며, 직렬화를 해야만 객체를 저장하거나 통신할 수 있다.

Serializable 인터페이스는 구현 메소드, 필드가 없는 껍데기 인터페이스로 
JAVA에서는 이 인터페이스를 구현하여 직렬화가 가능한 객체임을 인식한다.
Serializable을 구현한 객체를 저장하면 해당 class에 정의된 private을 포함한 모든 필드와 값, super class의 모든 필드 및 값이 저장된다.


SerialVersionUID

Serializable 인터페이스를 구현하게되면 IDE에서 serialVersionUID를 생성하도록 warning을 표시한다. 이 SerialVersionUID는 무엇에 쓰이는것일까? 그리고 해당 값을 선언하지 않으면 어떻게 되는것일까?

http://kevinx64.net/107 에서 이 의문에 대한 답을 찾을 수 있었다.

 직렬화 가능(Serializable) 클래스가 serialVersionUID를 명시적으로 선언하지 않는 경우, 직렬화 런타임은 「Java(TM) 객체 직렬화 스펙」에서 설명하고 있듯이, 클래스의 다양한 측면에 근거해, 클래스의 serialVersionUID의 Default 값를 계산한다. 다만, 모든 직렬화 가능 클래스가 serialVersionUID를 명시적으로 선언하는 것을 강하게 추천한다. Default의 serialVersionUID 계산이, 컴파일러의 구현에 따라서, 다를 가능성이 있는 클래스의 영향을 받기 쉽고, 직렬화 복원 중에 예기치 않은 InvalidClassException 가 발생할 가능성이 있기 때문이다. 따라서, java 컴파일러의 구현이 달라도 serialVersionUID의 일관성을 확보로 하려면, 직렬화 가능 클래스가 serialVersionUID를 명시적으로 선언하지 않으면 안된다. 또, serialVersionUID 의 명시적인 선언에서는 private 수식자를 사용하는 것을 적극 추천한다. 이러한 선언은 직접적으로 선언하는 클래스에게만 적용되게 하기 위한 것이다. private으로 선언하면, serialVersionUID 필드를 상속되는 멤버와 같이 사용하지 않는다.

SerialVersionUID는 객체 직렬화 시에 동일한 Class 명을 갖고있더라도 실제로 동일한 class가 맞는지 검사하기 위해 version을 지정하는것이며, 이 값을 명시적으로 지정하지 않으면 JVM은 default로 serialVersionUID를 계산하여 사용한다.
여러가지 근거를 통해 자동으로 serialVersionUID를 계산해 낼 때 환경적인 사유 혹은 불특정한 이유로 인해 이 값이 다르게 계산될 수도 있으며, 이 경우 InvalidClassException이 발생할 수 있으므로 명시적으로 선언해주는것을 권장한다.


Transient

직렬화 시에 대상에서 제외하고 싶은 경우 변수 선언 앞에 transient를 선언하면 직렬화 대상에서 제외가 된다.

'development > Java' 카테고리의 다른 글

[도서] Java8 in Action - 새로운 날짜/시간 API  (0) 2017.04.25
[도서] Java8 in Action - Stream  (0) 2017.04.25
Posted by dreamhopp
,