반응형

스트림으로 데이터 수집

  • Collectors 클래스로 컬렉션 만들고 사용하기
  • 하나의 값으로 데이터 스트림 리듀스 하기
  • 특별한 리듀싱 요약 연산
  • 데이터 그룹화와 분할
  • 자신만의 커스텀 컬렉터 개발

컬렉션(Collection), 컬렉터(Collector), collect는 서로 다르다.

6.1 컬렉터란 무엇인가?

Collector 인터페이스 구현은 스트림의 요소를 어떤 식으로 도출할지 지정한다.

6.1.1 고급 리듀싱 기능을 수행하는 컬렉터

스트림에 collect를 호출하면 스트림의 요소에(컬렉터로 파라미터화된) 리듀싱 연산이 수행된다. 즉, 내부적으로 리듀싱 연산이 일어난다.
장점 : collect 로 결과를 수집하는과정을 간단하면서도 유연한 방식으로 정의할 수 있다.

예제 6.1

그림 6.1

6.1.2 미리정의된 컬렉터

Collectors에서 제공하는 메서드의 기능은 크게 세 가지로 구분할 수 있다.

  • 스트림 요소를 하나의 값으로 리듀스하고 요약
  • 요소 그룹화
  • 요소 분할

6.2 리듀싱과 요약

counting 예시

int howManyDishes = menu.stream().collect(counting());
6.2.1 스트림값에서 최댓값과 최솟값 검색

Collectors.maxBy 와 Collectors.minBy 로 스트림의 최대값과 최소값을 구할 수 있다.

Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories);
Optional<Dish> mostCaloriesDish = menu.stream().collect(maxBy(dishCaloriesComparator));
6.2.2 요약연산

Collectors.summinngInt

int totalCalories = menu.stream().collect(summingInt(Dish::getCalories))

그림 6.2 summinngInt 컬렉터 누적과정

그 외 long, double 과 averagingInt 및 summarizingInt 가 있다.

6.2.3 문자열 연결

joining
추출한 모든 문자열을 하나의 문자열로 연결해서 반환

String menuListStr = menu.stream().map(Dish::getName).collect(joining(", "))

6.2.3 범용 리듀싱 요약 연산

reducing

범용 Collectors.reducing을 사용
특화된 summinngInt 등과 같은 메소드를 사용하는 이유는 편의성 및 가독성 때문

int totalCalories = menu.stream()
  .collect(reducing(0, Dish::getCaloreis, (i, j) -> i + j));

reducing은 세 개의 인수를 받는다. (초기값, 합계 함수, 변환 함수)

  • 첫 번째 인수는 리듀싱 연산의 시작값이거나 스트림에 인수가 없을 때는 반환값이다.(숫자 합계에서는 인수가 없을 때 반환하므로 0이 적합하다.)
  • 두 번째 인수는 함수를 받는다.
  • 세 번째 인수는 같은 종류의 두 항목을 하나의 값으로 더하는 BinaryOperator이다.

한 개의 인수를 갖는 reducing

가장 칼로리가 높은 요리 찾는 방법

Optional<Dish> mostCaloireDish = menu.stream().collect(
    reducing((d1, d2) -> d1.getCaloreis() > d2.getCalories() ? d1 : d2)
);

세 개의 인수를 갖는 reducing 메서드에서 첫 번째 인수를 받고, 두 번째 인수에서
자기 자신을 그대로 반환하는 항등함수(identity function)를 두 번째 인수로 받는 상황에 해당한다.

한 개의 인수를 갖는 reducing 컬렉터는 시작값이 없으므로 빈 스트림이 넘겨졌으래 시작값이 설정되지 않아 null을 반환할 수 있으므로
Optional 객체로 만들어 사용해야 한다.

컬렉션 프레임워크 유연성 : 같은 연산도 다양한 방식으로 수행할 수 있다.

int totalCaloires = menu.stream().collect(reducing(0, Dish::getCaloires, Integer::sum)); 
menu.stream().map(Dish::getCalories).reduce(Integer::sum).get(); // 인자가 하나여서 Optional 을 반환하지만 get 으로 값 추출

그룹화

자바8의 함수형을 이용하면 가독성 있는 한 줄의 코드로 그룹화를 구현할 수 있다.
Collectors.groupingBy

/** 
  * 그룹화 groupingBy
  * 생선, 고기 그 밖의 것들로 그룹화 
  */
Map<Type, List<Dish>> dishesByType = menu.stream().collect(groupingBy(Dish::getType));

groupingBy를 분류 함수(classification function) 이라고 한다.
그림 6.4 - 그룹화로 스트림의 항목을 분류하는 과정

람다 표현식으로도 그룹화 가능

/**
  * 칼로리별로 그룹화
  */
Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream().collect(
  groupingBy(dish -> {
    if(dish.getCalories() <= 400) return CaloricLevel.DIET;
    else if(dish.getCalories() <= 700) return CaloricLevel.NORMAL;
    else return CaloricLevel.FAT;
  }));
// 500칼로리가 넘는 요리만 타입과 종류로 그룹화
Map<Type, List<Dish>> caloricDishesByType = menu.stream().filter(dish -> dish.getCalroies() > 500)
  .collect(groupingBy(Dish::getType));
/**
  * 결과
  * {OTHER=[french fries, pizza], MEAT=[pork, beef]}
  * FISH 라는 키 자체가 사라짐
  * 위 코드의 단점은 위 filter 프레디케이트를 만족하는 값이 없을 경우 키값 자체가 제외되서 맵에 담지 못한다.
  * 해결책 : Collectors 클래스의 정적 팩터리 메서드인 filtering 사용
  */

// 해결
Map<Type, List<Dish>> caloricDishesByType = menu.stream().collect(groupingBy
  (Dish::getType, filtering(dish -> dish.getCalories() > 500, toList())));
// 결과 : {OTHER=[french fries, pizza], MEAT=[pork, beef], FISH=[]}

/** mapping 사용 */
Map<Type, List<String>> dishNamesByType = menu.stream().
  collect(groupingBy(Dish::getType, mapping(Dish::getName, toList())));

/** flatMapping 사용 
  * flatMap은 두 수준의 리스트를 한 수준으로 평면화 할 수 있음 
  * 연산결과를 수집해서 리스트가 아니라 집합으로 그룹화해 중복 태그를 제거한다.
  */
Map<Type, Set<String>> dishNamesByType = 
  menu.stream()
    .collect(groupingBy(Dish::getType, flatMapping(dish -> dishTags.get(dish.getName()).stream(), toSet())));
6.3.2 다수준 그룹화
menu.stream().collect(
        groupingBy(Dish::getType,
            groupingBy((Dish dish) -> {
              if (dish.getCalories() <= 400) {
                return CaloricLevel.DIET;
              }
              else if (dish.getCalories() <= 700) {
                return CaloricLevel.NORMAL;
              }
              else {
                return CaloricLevel.FAT;
              }
            })
        )

groupingBy

groupingBy(x)는 사실 groupingBy(x, toList())의 축약형이다.

  • 요리의 종류를 분류하는 컬렉터로 메뉴에서 가장 높은 칼로리를 가진 요리를 찾는 프로그램
Map<Type, Optional<Dish>> mostCaloricByType = menu.stream().collect(groupingBy(Dish::getType, maxBy(comparingInt(Dish::getCaloires))));
6.3.3 서브그룹으로 데이터 수집
Map<Dish.TYPE, Long> typeCount = menu.stream().collect(groupingBy(Dish::getType, counting()))

컬렉터 결과를 다른 형식에 적용하기

collectingAndThen

  • 팩토리 메서드 collectingAndThen은 적용할 컬렉터와 변환 함수를 인수로 받아 다른 컬렉터를 반환한다.

  • 분류함수로 여러 서브스트림을 만든 뒤 각각 리듀싱 요약 가능

    Map<Type, Dish> mostCaloricByType = 
    menu.stream()
      .collect(groupingBy(Dish::getType, // 분류함수
                          collectingAndThen(maxBy(comparingInt(Dish::getCaloreis)), // 감싸인 컬렉터 
                          Optional::get)); // 변환함수 -- Optional 에 포함된 값을 추출
    

그림 6.6 여러 컬렉터를 중첩한 효과

  • 각 요리 형식에 존재하는 모든 CaloricLevel 값을 알고 싶은 경우(groupingBy와 mapping 사용)
Map<Type, Set> caloricLevelByType =  
menu.stream().collect(  
groupingBy(Dish::getType, 
            mapping(dish -> { // dish를 CaloricLevel 로 매핑  
                        if(dish.getCalories() <= 400) return CaloricLevel.DIET;  
                        else if(dish.getCalories() <= 700 return CaloricLevel.NORMAL;  
                        else return CaloricLevel.FAT; 
                    },  
                    toSet() )
                     )
           ); // 리스트가 아닌 집합으로 반환

6.4 분할(partitioningBy)

분할은 분할 함수(partitioning function)이라 불리는 프레디케이트를 분류 함수로 사용하는 특수한 그룹화 기능이다. 분할 함수는 불리언을 반환하므로 맵의 키 형식은 Boolean이다.

분할의 장점은 참, 거짓 두 가지 요소의 스트림 리스트를 모두 유지한다.

  • 모든 요리를 채식과 아닌 요리로 분류

    Map<Boolean, List> partitionedMenu =  
    menu.stream().collect(partitioningBy(Dish::isVegetarian));
    List vegetarianDishes = partitionedMenu.get(true); // 파티셔닝 사용
    List vegetarianDishes2 = menu.stream().filter(Dish::isVegetarian).collct(toList()); // 필터사용
  • 분할함수 안에 그룹함수를 사용

    menu.stream().collect(partitioningBy(Dish::isVegetarian, groupingBy(Dish::getType)))
    
  • 채식과 채식이 아닌 요리에서 가장 칼로리가 높은 음식 찾기

  • 분할함수 안에서서 값 비교

    Map<Boolean, Dish> mostCaloricPartitioneByVegetarian =  
    menu.stream().collect(  
                      partitioningBy(Dish::isVegetarain, 
                                     collectingAndThen(maxBy(comparingInt(Dish::getCalories)), 
                                     Optional::get)));
    
6.4.2 숫자를 소수와 비소수로 분할하기

6.5 Collector 인터페이스

public interface Collector<T, A, R> {  
    Supplier supplier();  
    BiConsumer<A, T> accumulator();  
    BinaryOperator combiner();  
    Function<A, R> finisher();  
    Set<Collector.Characteristics> characteristics();  
}
  • T는 수집될 스트림 항목의 제네릭 형식
  • A는 누적자, 즉 수집 과정에서 중간 결과를 누적하는 객체의 형식
  • R은 수집 연산 결과 객체의 형식(항상 그런 것은 아니지만 대개 컬렌션 형식)

characteristics 은 collect 메소드가 어떤 최적화를 이용해서 리듀싱 연산을 수행할 것인지를 결정하도록 돕는 힌트 특성 집합 제공

supplier 메소드: 새로운 결과 컨테이너 만들기

supplier 메소드는 빈 결과로 이루어진 Supplier를 반환해야 한다.

accumulator 메소드: 결과 컨테이너에 요소 추가하기

accumulator 메소드는 리듀싱 연산을 수행하는 함수를 반환

finisher 메소드: 최종 변환값을 결과 컨테이너로 적용하기

finisher 메소드는 스트림 탐색을 끝내고 누적자 객체를 최종 결과로 변환하면서 누적 과정을 끝낼 때 호출할 함수를 반환해야 한다.

combiner 메소드: 두 결과 컨테이너 병합

combiner 는 스트림의 서로 다른 서브파트를 병렬로 처리할 떄 누적자가 이 결과를 어떻게 처리 할지 정의한다.

Characteristics 메소드

Characteristics 는 스트림을 병렬로 리듀스할지 병렬로 리듀스한다면 어떤 최적화를 선택해야 할지 힌트 제공
UNORDERED : 리듀싱 결과가 누적순서에 영향 받지 않을때
CONCURRENT : 병렬 리듀싱을 수행할 수 있다. 데이터 소스가 정렬되어 있지 않은 상황에서만 병렬 리듀싱 가능(순서 무의미)
IDENTIFY_FINISH : 리듀싱 과정의 최정결과 - 생략가능

menu.stream().collect(Collectors.toList());  
\-> menu.stream().collect(new ToListCollector());

기존 코드의 toList 는 팩토리지만 ToListCollector 는 new 로 인스턴스화한다.

반응형
반응형

https://www.hanbit.co.kr/support/supplement_survey.html?pcode=B4926602499

 

한빛출판네트워크

출판사, IT전문서, 대학교재, 경제경영, 어린이/유아, MAKE, 실용/여행, 전자책, 인터넷 강의

www.hanbit.co.kr

예제 소스 파일

 

 

https://fliphtml5.com/hkuy/hoja

 

모던 자바 인 액션 : 람다, 스트림, 함수형, 리액티브 프로그래밍으로 새로워진 자바 마스터하기

Related x

fliphtml5.com

영어 PDF

https://livebook.manning.com/book/modern-java-in-action/about-this-book/

 

About this book · Modern Java in Action: Lambdas, streams, reactive and functional programming

 

livebook.manning.com

 

반응형
반응형

Modern Java In Action 정리

Modern Java In Action을 읽고 내용을 정리해본다.

5장 스트림 활용

필터링

  • filter() 메서드는 Predicate<T>를 인자로 일치하는 모든 요소를 포함하는 스트림을 반환한다.

    @Test
    public void 스트림_filter(){
      List<Integer> numbers = Arrays.asList(1, 2, 3, 1, 2, 4);
      numbers.stream()
             .filter(i -> i % 2 == 0) // 짝수만 필터링
             .distinct() // 중복요소 제거, hashCode와 equals로 결정된다.
             .forEach(System.out::println); // 출력
    }
  • distinct(), skip(n), limit(n) 와 같이 사용되어 스트림을 축소할 수 있다. 직관적으로 동작이 메서드명에 나타나므로 따로 정리를 하지 않는다.

  • 자바9에서 추가된 takeWhile(), dropWhile() 를 활용하면 기본 filter에서 추가 동작을 지정할 수 있다.
    (이미 정렬된 상태에서 유용하게 적용이 가능하다.)

@Test
public void 스트림_takeWhile_dropWhile(){
    // 메뉴 컬렉션을 칼로리를 기준으로 오른차순 정렬
    menu.sort(Comparator.comparing(Dish::getCalories));
    menu.stream().forEach((dish -> System.out.print(dish.getName() + "(" + dish.getCalories() + ") ")));

    // takeWhile() : 조건에 만족할 때까지 스트림을 반환, false시 반복 중단
    List<Dish> lowCalDish = menu.stream()
            .takeWhile(dish -> dish.getCalories() < 450)
            .collect(Collectors.toList());

    System.out.println("\n450 칼로리 미만 ------");
    lowCalDish.stream().forEach((dish -> System.out.print(dish.getName() + "(" + dish.getCalories() + ") ")));

    // dropWhile() : 조건에 만족하는 요소까지 버림, true부터 스트림을 반환
    List<Dish> highCalDish = menu.stream()
            .dropWhile(dish -> dish.getCalories() < 450)
            .collect(Collectors.toList());

    System.out.println("\n450 칼로리 이상 ------");
    highCalDish.stream().forEach((dish -> System.out.print(dish.getName() + "(" + dish.getCalories() + ") ")));
}
chicken(400) salmon(450) french fries(530) beef(700) pork(800) 
450 칼로리 미만 ------
chicken(400) 
450 칼로리 이상 ------
salmon(450) french fries(530) beef(700) pork(800) 

매핑

  • 스트림API는 스트림의 요소에서 다른 요소로 변환할 수 있는 map()과 flatMap() 메서드를 제공한다.
    <R> Stream<R> map(Function<? super T, ? extends R> var1);
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> var1);
  • 매핑 메서드들은 스트림의 한 요소(T 타입)에서 맵핑되는 새로운 타입(R 타입)의 요소를 만들어 반환한다.
  • 아래 예시에서는 map(Function<String, Integer>) 를 이용하여 Integer를 요소로 갖는 스트림을 생성하고 이를 List로 반환했다.
@Test
public void map_테스트(){
    List<String> strs = List.of("하이", "헬로우");

    // 문자열 길이를 요소로 같는 리스트 생성
    List<Integer> lengths = strs.stream()
            .map(String::length)
            .collect(Collectors.toList());
}
  • map()은 스트림의 요소별로 1대1로 반환하기 때문에 이해가 쉽지만 flatMap()은 다른 동작을 한다.

  • map()과 flatMap()에 파라미터로 주는 Function 인터페이스를 보면 반환형이 다른것을 볼 수 있다.

    • map() : Function<? super T, ? extends R>
    • flatMap() : Function<? super T, ? extends Stream<? extends R>>
  • flatMap의 파라미터인 Function 인터페이스의 구현체는 반환하는 객체의 타입이 Stream 타입이고, flatMap은 Stream 그 자체가 아닌 Stream의 컨텐츠로 매핑한다. 즉, map과 다르게 flatMap은 하나의 평면화된 스트림을 반환한다.

  • 차이를 볼 수 있게 예제 코드를 확인한다.
    문자열 리스트에서 중복을 제거한 한글자가 요소인 리스트를 구한다.

    @Test
    public void flatMap_테스트(){
       List<String> strs = List.of("HI", "HELLO");
    
       List<String> distinctStrs = strs.stream()
               .map(s -> s.split("")) // Stream<String[]>
    //          .map(sArr -> Arrays.stream(sArr)) // Stream<Stream<String>>
               .flatMap(sArr -> Arrays.stream(sArr)) // Stream<String>
               .distinct()
               .collect(Collectors.toList());
    
       distinctStrs.forEach(System.out::println); // H I E L O
    }

sArr -> Arrays.stream(sArr) 의 실행결과인 Stream 에 대해

  • map은 Stream<Stream>을 리턴

![ModernJava3_0]({{ "/assets/img/202002/ModernJava3_0.png" | relative_url }})

  • flatMap은 Stream의 컨텐츠인 String으로 하나의 평면화면 Stream을 리턴했다.
    distinct()도 의도한 대로 글자당 중복을 제거하도록 동작한다.

![ModernJava3_1]({{ "/assets/img/202002/ModernJava3_1.png" | relative_url }})

  • 이 밖에도 flatMap은 아래와 같이 변환을 지원하므로 자세한 내용은 Mkyong flatMap Example 의 내용을 참고하자
    Stream<String[]>        -> flatMap ->   Stream<String>
    Stream<Set<String>>        -> flatMap ->   Stream<String>
    Stream<List<String>>    -> flatMap ->   Stream<String>
    Stream<List<Object>>    -> flatMap ->   Stream<Object>

검색과 매칭

  • 스트림API는 특정 속성이 데이터 집합에 있는지 여부를 검색하는 allMatch, anyMatch, noneMatch, findFirst, findAny 등 다양한 유틸리티 메서드를 제공한다.
@Test
public void 검색과_매칭(){
    List<String> strs = List.of("HI", "HELLO");

    boolean isAllStartWithH = strs.stream()
                                  .allMatch(s -> s.startsWith("H"));
    System.out.println("모두 H로 시작합니다. : " + isAllStartWithH);

    boolean isOneEndWithO = strs.stream()
                                .anyMatch(s -> s.endsWith("O"));
    System.out.println("한 요소 이상이 O로 끝납니다. : " + isOneEndWithO);

    boolean isNoneEndWithK = strs.stream()
                                 .noneMatch(s -> s.endsWith("K"));
    System.out.println("모두 K로 끝나지 않습니다. : " + isNoneEndWithK);

    Optional<String> first = strs.stream().findFirst();
    System.out.println("첫번째 요소는(First) : " + first.get());


    Optional<String> any = strs.stream().findAny();
    System.out.println("첫번째 요소는(Any) : " + any.get());
}
  • findFirst와 findAny가 모두 필요한 이유는 병렬성 때문이다. 요소의 반환순서가 상관없다면 병렬 스트림에서는 제약이 적은 findAny를 사용한다.

리듀싱

  • 최종 결과값이 나올 때까지 스트림의 요소를 반복적으로 처리하는 연산을 리듀싱 연산이라고 한다.

  • 리듀싱 연산은 reduce 메서드로 수행할 수 있으며 사용은 예제코드를 참고한다.

    @Test
    public void reduce(){
      int[] nums = new int[]{1, 2, 3, 4, 5};
    
      // 초기값을 사용하여 reduce 연산
      int sumWithInitVal = Arrays.stream(nums)
                                 .reduce(0, Integer::sum);
      System.out.println("합계 : " + sumWithInitVal); // 15
    
      // 초기값 없이 reduce 연산
      OptionalInt optSum = Arrays.stream(nums)
                                 .reduce(Integer::sum);
      System.out.println("합계 : " + optSum.getAsInt()); // 15
    
      // 초기값이 없기 때문에 reduce 연산시 결과값이 없을 수 있다.
      OptionalInt optSum2 = Arrays.stream(new int[]{})
                                  .reduce(Integer::sum);
      System.out.println("합계 : " + optSum2.orElse(0)); // 0
    }
반응형
반응형

Modern Java In Action 정리

Modern Java In Action을 읽고 내용을 정리해본다.

4장 스트림 소개

스트림(Stream)

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

  • 자바8에 추가된 기능으로 스트림을 이용하면 선언형으로 코드를 구현하여 컬렉션 데이터를 처리할 수 있다.
    선언형으로 구현한다는 것은 for 루프나 if 조건문 등의 제어 블록을 사용하지 않고 동작을 지정하는 것이다.
    → 실제 구현은 신경쓰지 않고 사용하는 SQL를 생각하자.
  • 스트림에서 제공하는 filter, sorted, map, collect 같은 메서드들은 특정 스레딩 모델에 제한되지 않고 스레드와 락을 걱정할 필요없이 편리하게 데이터 병렬처리를 가능하게 해준다.

스트림 기본 구현

  • 기본구현에 사용할 Dish 클래스

    class Dish{
      private final String name;
      private final boolean vegetarian;
      private final int calories;
      private final Type type;
    
      public Dish(String name, boolean vegetarian, int calories, Type type) {
          this.name = name;
          this.vegetarian = vegetarian;
          this.calories = calories;
          this.type = type;
      }
    
      public String getName() {
          return name;
      }
    
      public boolean isVegetarian() {
          return vegetarian;
      }
    
      public int getCalories() {
          return calories;
      }
    
      public Type getType() {
          return type;
      }
    
      enum Type {
          MEAT, FISH, OTHER
      }
    }
  • 스트림 구현에 사용할 Dish 컬렉션

    List<Dish> menu = Arrays.asList(
      new Dish("pork", false, 800, Dish.Type.MEAT),
      new Dish("beef", false, 700, Dish.Type.MEAT),
      new Dish("chicken", false, 400, Dish.Type.MEAT),
      new Dish("french fries", true, 530, Dish.Type.OTHER),
      new Dish("salmon", false, 450, Dish.Type.FISH)
    );
  • 스트림 기본구현

    @Test
    public void 스트림_기본구현(){
      List<String> threeHighCaloricDishNames =
          menu.stream() // 컬렉션에서 스트림(Stream<Dish>)을 가져온다.
              .filter(dish -> dish.getCalories() > 300) // 해당 조건의 요소만 추출한다.
              .map(Dish::getName) // 이름(String) 속성을 스트림(Stream<String)으로 가져온다.
              .limit(3) // 3개를 제외하고 truncate한다.
              .collect(Collectors.toList()); // 스트림을 컬렉션(리스트)로 변환한다.
    
      System.out.println(threeHighCaloricDishNames); // [pork, beef, chicken]
    }
  • filter, map 메서드는 인자로 Functional Interface 인스턴스를 받기 때문에 람다표현식이나, 메서드참조로 간결하게 코딩이 가능하다. 또한 메서드들은 실행 결과로 스트림을 리턴하기 때문에 파이프라인 형태가 된다.
    → 빌더 패턴과 유사하다.

  • 서로 연결이 가능한 filter, map, limit는 중간연산이며, collect는 스트림을 닫는 최종연산이다.

스트림과 컬렉션

  • 컬렉션은 DVD에 저장된 영화에 비유할 수 있다. 모든 데이터(영화 내용 전부)가 메모리(DVD)에 저장되어 있다.
    컬렉션에는 계산된 결과물이 저장되어 있으며, 주 관심사는 특정요소에 접근하여 값을 계산하거나 치환한다.
  • 스트림은 인터넷으로 스트리밍하는 영화에 비유할 수 있다.
    영화 전체를 모두 받는 것이 아니라 미리 몇 프레임만 내려받아 재생이 가능할 수 있다. 스트림은 이론적으로 요청할 때만 요소를 계산하는 고정된 자료구조다.
  • 스트림은 한번만 탐색이 가능하다.
    이미 소비된 스트림을 사용하려 하면 Exception이 발생하므로 다시 생성하여 사용해야 한다.
  • 컬렉션은 데이터를 순회하기 위해서는 루프문을 이용해 명시적으로 반복해야 한다. → 외부반복
  • 스트림은 반복을 알아서 처리(중간값 저장, 최적화, 병렬성 구현 등) 한다. → 내부반복
    @Test
    public void 스트림_내부반복(){
      // forEach 메서드에서 반복되며 명시적으로 루프문이 필요없다.
      menu.stream()
          .forEach(dish -> System.out.println(dish.getName()));
    }

스트림은 게으른(lazy) 연산을 지원

  • 스트림 처리시 한 요소는 연결된 모든 파이프라인을 타고나서 다음 요소가 처리된다.

    @Test
    public void 스트림_게으른_연산(){
      List<String> threeHighCaloricDishNames =
          menu.stream()
              .filter((dish) -> {
                  System.out.println("filtering :: " + dish.getName());
                  return dish.getCalories() > 300;
              })
              .map((dish) -> {
                  System.out.println("mapping :: " + dish.getName());
                  return dish.getName();
              })
              .limit(3)
              .collect(Collectors.toList());
    
      System.out.println(threeHighCaloricDishNames); // [pork, beef, chicken]
    }
  • 콘솔출력으로 스트림 처리 순서를 보면 한 요소씩 filter → map → limit → collect 파이프라인을 타는것을 볼 수 있다. 중간 연산들(filter → map → limit)을 합친 다음에 최종 연산(collect)에서 한 번에 처리한다.

    filtering :: pork
    mapping :: pork
    filtering :: beef
    mapping :: beef
    filtering :: chicken
    mapping :: chicken
    [pork, beef, chicken]
반응형
반응형

Mkyong

자바 예제소스만 버젼별로 많이 올려주시는데, 이거로만 튜토리얼 공부해도 될 정도로 다양한 내용 예제 소스로 영문으로만 정리되어 있습니다.

https://mkyong.com/

 

Mkyong.com - Learn Java and Spring.

Focus on Core Java and Spring Frameworks, with simple examples, code snippets, and tutorials.

mkyong.com

 

javable

자바 관련 컨텐츠로 좋은 코드관련해서 헷갈릴만한 내용들이 많이 있습니다.
우아한형제들 기술블로그랑 관련 있어보이네요.

https://woowacourse.github.io/javable/

 

Home

우아한테크코스 코드리뷰 모음 공간

woowacourse.github.io

 

제이 블로그

자바 관련 컨텐츠가 많습니다. 자바 뿐만 아니라 자바를 쓰면서 사용할 수 있는 devops 관련된 기술 블로그 또한 많습니다.

https://pjh3749.tistory.com/

 

JayTech의 기술 블로그

개발, 알고리즘 등 기술 관련 블로그입니다. 추가로 저의 일상, 미국 생활이야기, 여행 관련 글도 씁니다.

pjh3749.tistory.com

velopert 블로그

https://velog.io/@velopert

 

velopert (Minjun Kim) - velog

2019.log 벌써 2019년도 끝났다. 쓸 이야기가 많고 생각을 정리할 것도 많아서 결국 2020년 1월 1일에 릴리즈하고 말았다! 역시, 회고록은 12월 초부터 작성하기 시작해야 한다. 내년엔 꼭 회고록을 부�

velog.io

 

Outsider's Dev Story

이 분도 여러가지 주제로 꾸준히 블로그 하시는 분이네요.
IT 뉴스도 유익한 정보가 많습니다.

https://blog.outsider.ne.kr/

 

기술 뉴스 #149 : 20-05-01 :: Outsider's Dev Story

# 웹개발 관련 * **[await의 함정, 숨은 병목을 찾자](https://jaeheon.kr/161)** : `async`, `await`를 사용하면 비동기 코드를 동기처럼 읽기 쉽게 작성할 수 있지만 동시에 실행해도 되는 코드도 `await`로 인...

blog.outsider.ne.kr

 

유명하신분들 블로그 천천히 업로드 예정입니다.

반응형

'서비스평가 및 사용' 카테고리의 다른 글

[heroku] 시작하기 java 배포  (0) 2021.04.29
Buy me a coffee 블로그로 후원받기  (0) 2020.05.26
Github gist 사용해보기  (0) 2020.05.22
반응형

Select SQL 실행순서(오라클 SQL 기준)

 

1) FROM table(s) [alias]

- FROM 절에 사용된 테이블을 인식하여 데이터 딕셔너리에서 관련된 정보들을 파악

 

2) [WHERE condition(s)]

- WHERE절에서 조건에 맞는 데이터를 추출

 

3) [GROUP BY column(s)]

- GROUP BY절이 추가되면 GROUP BY절에 사용된 항목별로 데이터의 정렬이 일어남

 

4) [HAVING condition(s)]

- HAVING 절은 GROUP BY절로 정렬이 된 데이터를 대상으로 조건을 정의.

 

5) SELECT {*, column(s) [alias],...}

- 대부분의 RDBMS가 ROW(로우)기준 저장구조입니다. SELECT이전까지 원하지 않는 칼럼까지도 데이터베이스의 메모리에 저장.

 

6 [ORDER BY column(s) [alias] [DESC],.....];

- ORDER BY절이 가장 나중에 실행.

- SELECT절에서 선택되지 않은 칼럼이나 연산도 데이터베이스의 메모리에 저장되어있으므로 ORDER BY절에서 사용

 

 

정리하자면 다음과 같다.

 

1.FROM 절에서 테이블의 목록을 가져옴
2.WHERE 절에서 검색 조건에 불일치 하는 행 제외
3.GROUP BY 절에서 명시된 행의 값을 그룹화
4.HAVING 절이 GROUP BY 절의 결과 행 중 검색 조건에 불일치 하는 행 제외
5.SELECT 절에서 명시된 열을 정리 
6.ORDER BY 절에서 열을 기준으로 출력할 대상을 정렬 후 출력 

 

반응형
반응형

포인트컷 표현식

execution() : 가장 대표적이고 강력한 지시자로, 접근제어자, 리턴 타입, 타입 패턴, 메서드, 파라미터 타입, 예외 타입 등을 조합해서 메서드까지 선택가능한 가장 정교한 포인트컷을 만들수 있다.
execution([수식어] 리턴타입 [클래스이름].이름(파라미터)

execution(public Integer com.edu.aop.*.*(*))
 - com.edu.aop 패키지에 속해있고, 파라미터가 1개인 모든 메서드

execution(* com.edu..*.get*(..))
 - com.edu 패키지 및 하위 패키지에 속해있고, 이름이 get으로 시작하는 파라미터가 0개 이상인 모든 메서드 

execution(* com.edu.aop..*Service.*(..))
 - com.edu.aop 패키지 및 하위 패키지에 속해있고, 이름이 Service르 끝나는 인터페이스의 파라미터가 0개 이상인 모든 메서드

execution(* com.edu.aop.BoardService.*(..))
 - com.edu.aop.BoardService 인터페이스에 속한 파마리터가 0개 이상인 모든 메서드

execution(* some*(*, *))
 - 메서드 이름이 some으로 시작하고 파라미터가 2개인 모든 메서드

within() : 타입 패턴만을 이용하여 조인포인트를 정의한다.

within(com.edu.aop.SomeService)
 - com.edu.aop.SomeService 인터페이스의 모든 메서드

within(com.edu.aop.*)
 - com.edu.aop 패키지의 모든 메서드

within(com.edu.aop..*)
 - com.edu.aop 패키지 및 하위 패키지의 모든 메서드

this : 빈 오브텍트의 타입의 조인포인트를 정의한다.
target : 대상객체의 타입 비교를 이용한 조인포인트를 정의한다.
args : 메서드의 파라미터 타입만을 이용하여 조인포인트를 정의한다.
@target : 특정 어노테이션이 정의된 객체를 찾는 조인포인트를 정의한다.
@args : 특정 어노테이션을 파라미터로 받는 오브젝트를 찾는 조인포인트를 정의한다.
@within : @target과 유사하게 특정 어노테이션이 정의된 객체를 찾는데, 선택될 조인포인트 메서드는 타겟 클래스에서 선언이 되어있어야 한다.
@annotation : 조인 포인트 메서드에 특정 어노테이션을 찾는 조인포인트를 정의한다.

포인트컷의 조합식에는 or, and, not 3가지를 사용할 수 있으며 각각 ||, &&, !으로 표현할 수 있음.

execution과 @annotation을 주로 사용

반응형
반응형
 @Scheduled(cron="*/30 * * * * *")

시간 설정 @scheduled(cron=" ") * 리눅스 crontab 과 같은 설정방법

@Scheduled(cron="0 0 02 * * ?") // 매일 새벽2시에 실행
@Scheduled(cron="0 0 02 2,20 * ?") // 매월 2일,20일 새벽2시에 실행

참고문헌

https://kanetami.tistory.com/entry/Schedule-Spring-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8A%A4%EC%BC%80%EC%A5%B4-%EC%84%A4%EC%A0%95%EB%B2%95-CronTab

반응형

+ Recent posts