본문 바로가기
java

더 자바(java 8) - Stream, Optional, Date

by 쭈꾸마뇽 2021. 5. 19.

Stream

List<String> names = new ArrayList<>();
names.add("Kang");
names.add("Min");
names.add("Hyeong");
  • Sequence of elements supporting sequential and parallel aggregate operations
  • 데이터를 담고 있는 저장소(컬렉션)이 아니다
  • Funtional in nature, 스트림이 처리하는 데이터 소스를 변경하지 않는다
Stream<String> stringNames1 = names.stream().map(String::toUpperCase);
names.forEach(System.out::println);

names를 stream을 이용해 대문자로 변경하게 했지만 기존의 대에터에는 변화가 없다

  • 스트림으로 처리하는 데이터는 오직 한번만 처리한다
Stream<String> stringNames1 = names.stream().map(String::toUpperCase);
stringNames1.forEach(System.out::println);
stringNames1.forEach(System.out::println);

stream의 데이터를 한번 처리하면 더이상 추가적인 작업을 할 수 없다.

  • 무제한일 수도 있다 -> short circuit 메소드를 사용해서 제한할 수 있다.
  • 중개 오퍼레이션은 근본적으로 lazy하다.
Stream<String> stringNames2 = names.stream().map(s -> {
    System.out.println(s); // 출력이 되지 않음
    return s.toUpperCase();
});

List<String> stringNames3 = names.stream().map(s -> {
    System.out.println(s); // 출력이 됨
    return s.toUpperCase();
}).collect(Collectors.toList()); // 종료형 오퍼레이터를 실행한 이후에는 stream 안에 내용이 처리가 된다.
  • 손쉽게 병렬처리할 수 있다.
List<String> collect = names.parallelStream().map(s -> {
    System.out.println(s + " " + Thread.currentThread().getName());
    return s.toUpperCase();
}).collect(Collectors.toList()); // 병렬처리

System.out.println("=================================================");

List<String> collect2 = names.stream().map(s -> {
    System.out.println(s + " " + Thread.currentThread().getName());
    return s.toUpperCase();
}).collect(Collectors.toList());

parallelStream을 사용하면 main 쓰레드와 추가적인 쓰레드가 함께 동작하지만 stream을 사용하면 main 쓰레드만 일한다

Stream 파이프라인

  • 0 또는 다수의 중개 오퍼레이션과 한개의 종료 오퍼레이션으로 구성한다.
  • Stream의 데이터 소스는 오직 터미널 오퍼레이션을 실행할 때에만 처리한다.

중개 오퍼레이션

  • Stream을 리턴한다.
  • Stateless / Stateful 오퍼레이션으로 더 상세하게 구분할 수도 있다. -> 대부분은 Stateless지만 distinct나 sorted처럼 이전 소스 데이터를 참조해야 하는 오퍼레이션은 Stateful 오퍼레이션이다
  • filter, map, limit, skip, sorted, ...

종료 오퍼레이션

  • Stream을 리턴하지 않는다.
  • collect, allMatch, count, forEach, min, max, ...

Stream API

걸러내기 filter
변경하기 map, flatMap
생성하기 generate, Iterate
제한하기 limit, skip
조건확인 anyMatch, allMatch, nonMatch
개수 새기 count
스트림 데이터를 하나로 뭉치기 reduce, collect, sum, max, min

Stream을 이용한 예제

public class StreamApp2 {
    public static void main(String[] args) {
        List<OnlineClass> springClasses = new ArrayList<>();
        springClasses.add(new OnlineClass(1, "spring boot", true));
        springClasses.add(new OnlineClass(2, "spring data jpa", true));
        springClasses.add(new OnlineClass(3, "spring mvc", false));
        springClasses.add(new OnlineClass(4, "spring core", false));
        springClasses.add(new OnlineClass(5, "rest api development", false));

        System.out.println("spring 으로 시작 하는 수업");
        springClasses.stream()
                .filter(oc -> oc.getTitle().startsWith("spring"))
                .forEach(oc -> System.out.println(oc.getId()));

        System.out.println("close 되지 않은 수업");
        springClasses.stream()
                .filter(oc -> !oc.isClosed())
//                .filter(Predicate.not(OnlineClass::isClosed))
                .forEach(oc -> System.out.println(oc.getId()));

        System.out.println("수업 이름만 모아서 스트림 만들기");
        springClasses.stream()
                .map(OnlineClass::getTitle)
                .forEach(System.out::println);

        List<OnlineClass> javaClasses = new ArrayList<>();
        javaClasses.add(new OnlineClass(6, "The Java, Test", true));
        javaClasses.add(new OnlineClass(7, "The Java, Code manipulation", true));
        javaClasses.add(new OnlineClass(8, "The Java, 8 to 11", false));

        List<List<OnlineClass>> keesunEvents = new ArrayList<>();
        keesunEvents.add(springClasses);
        keesunEvents.add(javaClasses);

        System.out.println("두 수업 목록에 들어있는 모든 수업 아이디 출력");
        keesunEvents.stream()
                .flatMap(Collection::stream)
                .forEach(oc -> System.out.println(oc.getId()));

        System.out.println("10부터 1씩 증가하는 무제한 스트림 중에서 앞에 10개 빼고 최대 10개 까지만");
        Stream.iterate(10, i -> i + 1)
                .skip(10)
                .limit(10)
                .forEach(System.out::println);

        System.out.println("자바 수업중에 Test가 들어있는 수업이 있는지 확인");
        boolean test = javaClasses.stream()
                .anyMatch(oc -> oc.getTitle().contains("Test"));
        System.out.println(test);

        System.out.println("스프링 수업 중에 제목에 spring이 들어간 제목만 모아서 List로 만들기");
        List<String> spring = springClasses.stream()
                .map(OnlineClass::getTitle)
                .filter(title -> title.contains("spring"))
                .collect(Collectors.toList());
        spring.forEach(System.out::println);
    }
}

Optional

오직 값 한 개가 들어있을 수도 없을 수도 있는 컨테이너

자바 프로그래밍에서 NullPointerException을 종종 보게되는 이유 -> null을 리턴함 && null 체크를 깜박해서

메소드에서 작업중 특별한 상황에서 값을 제대로 리턴할 수 없는 경우 선택할 수 있는 방법

  • 예외를 던진다 -> 비싸다 / stackTrace를 찍기때문
  • null을 리턴한다 -> 비용문제는 없지만 그 코드를 사용하는 클라이언트 코드가 주의해야함
  • Optional을 리턴한다 -> 코드에 명시적으로 빈 값일수도 있다는 걸 알려주고 빈값인 경우데 대한 처리를 강제한다

주의점

  • 리턴값으로만 쓰기를 권장한다 -> 메소드 매개변수 타입, 맵의 키 타입, 인스턴스 필드 타입으로 쓰지 말자
private Optional<Progress> progress2; // 이런건 쓰지 말자
  • Optional을 리턴하는 메소드에서 null을 리턴하지 말자
OnlineClass spring_boot = new OnlineClass(1, "spring boot", false);
Duration studyDuration1 = spring_boot.getProgress().getStudyDuration(); // 에러 발생 -> null이 리턴되기 때문
  • 프리미티브 타입용 Optional은 따로있다. -> OptionalInt, OptionalLong
  • Collection, Map, Stream Array, Optional은 Optional로 감싸지 말 것

Optional API

Optional 만들기 Optional.of(), Optional.ofNullable(), Optional.empty()
Optional에 값이 있는지 없는지 확인 isPresent(), isEmpty()
Optional에 있는 값 가져오기 get()
Optional에 있는 값이 있는 경우 그 값을 가지고 ~~을 하라 ifPresent(Consumer)
Optional에 값이 있느면 가져오고 없는 경우에 ~~을 하라 orElseGet(Supplier)
Optional에 값이 있으면 가져오고 없는 경우에 ~~을 리턴하라 orElse(T)
Optional에 값이 있으면 가져오고 업는 경우에 에러를 던져라 orElseThrow()
Optional에 들어있는 값 걸러내기 Optional filter(Predicate)
Optional에 들어있는 값 변환하기 Optional map(Function),
Optional flatMap(Function): Optional

Date와 Time API

Java 8에 새로운 날짜와 시간 API가 생긴 이유

  • Java 8 이전에 사용하던 Date 클래스는 mutable 하기 때문에 thread safe하지 않다
date.setTime(1032412412434L);  // 시간을 다시 설정함
  • 클래스 이름이 명확하지 않다 -> Date인데 시간까지 다룬다
Date date = new Date();
System.out.println(date);
System.out.println(date.getTime()); // Date인데 time을 가져옴...

  • 버그가 발생할 여지가 많다 -> 타입 안정성이 없고 월이 0부터 시작..
Calendar birthDate1 = new GregorianCalendar(1992, 9, 26); // 1992년 10월 26일, type safe하지 않다
Calendar birthDate2 = new GregorianCalendar(1992, Calendar.OCTOBER, 26);
System.out.println(birthDate2.getTime()); // date가 나옴...

  • 날자 시간 처리가 복잡한 애플리케이션에서는 보통 Joda Time을 쓰곤 했다.

주요 API

  • 기계용 시간과 사람용 시간으로 나눌 수 있다.
  • 기계용 시간은 EPOCK(1970-01-01 00:00:00)부터 현재까지의 타임스탬프를 표현한다
  • 사람용 시간은 연,월,일,시,분,초 등을 표현한다
  • 타임스탬프는 Instant를 사용한다
  • 특정 날짜(LocalDate), 시간(LocalTime), 일시(LocalDateTime)를 사용 할 수 있다
  • 기간을 표현할 때는 Duration(시간 기반)과 Period(날짜 기반)를 사용 할 수 있다
  • DateTimeFormatter를 사용해서 일시를 특정한 문자열로 포매팅 할 수 있다.

Date와 Time API

지금 이 순간을 기계 시간으로 표현

Instant instant = Instant.now();
System.out.println(instant);  // 기준시 UTC, GMT
System.out.println(instant.atZone(ZoneId.of("UTC")));  // 기준시 UTC, GMT

ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
System.out.println(zonedDateTime);

인류용 일시를 표현하는 방법

LocalDateTime now = LocalDateTime.now();  // 내 local에 맞게 설정 -> 만약 서버를 다른 지역에 올리면 다른 시간이 뜰거임
System.out.println(now);

ZonedDateTime nowInKorea = ZonedDateTime.now(ZoneId.of("Asia/Seoul"));
System.out.println(nowInKorea);

LocalDate today = LocalDate.now();
LocalDate thisYearBirthday = LocalDate.of(2021, Month.OCTOBER, 26); // 특정 일시를 리턴

기간을 표현하는 방법

Period period = Period.between(today, thisYearBirthday);
System.out.println(period.getDays());

Period until = today.until(thisYearBirthday);
System.out.println(until.get(ChronoUnit.DAYS));

파싱, 포매팅

LocalDateTime dateTime = LocalDateTime.now();
DateTimeFormatter MMddyyyy = DateTimeFormatter.ofPattern("MM/dd/yyyy");
System.out.println(dateTime.format(MMddyyyy));

LocalDate parse = LocalDate.parse("10/26/1992", MMddyyyy);
System.out.println(parse);

레거시 API 지원

  • GregorianCalendar와 Date 타입의 인스턴스를 Instant나 ZonedDateTime으로 변환 가능
  • java.util.Timezone에서 java.time.ZonedId로 상호 변환 가능
GregorianCalendar gregorianCalendar = new GregorianCalendar();
ZonedDateTime dateTime1 = gregorianCalendar.toInstant().atZone(ZoneId.systemDefault());
System.out.println(dateTime1);
GregorianCalendar from = GregorianCalendar.from(dateTime1);
System.out.println(from.getTimeZone().getDisplayName());

ZoneId zoneId = TimeZone.getTimeZone("PST").toZoneId();
TimeZone timeZone = TimeZone.getTimeZone(zoneId);
System.out.println(timeZone.getDisplayName());


참고자료

 

더 자바, Java 8 - 인프런 | 강의

자바 8에 추가된 기능들은 자바가 제공하는 API는 물론이고 스프링 같은 제 3의 라이브러리 및 프레임워크에서도 널리 사용되고 있습니다. 이 시대의 자바 개발자라면 반드시 알아야 합니다. 이

www.inflearn.com

 

댓글