1. 개념

    1. 람다를 활용해 배열의 컬렉션을 함수형으로 간단하게 사용할수 있는 기술
  2. 특징

    1. 원본 데이터 소스를 변경하지 않는다.
    2. 일회용이다.
    3. 최종 연산 전까지 중간 연산을 수행하지 않는다.
      1. 지연 연산
    4. 작업을 내부 반복으로 처리한다.
      1. forEach()는 매개변수에 대입된 람다식을 데이터 소스의 모든 요소에 적용한다.
    5. 병렬 처리가 쉽다
      1. 멀티쓰레드 사용
    6. 기본형 스트림을 제공한다.
      1. Stream<Integer> 대신 IntStream이 제공되어서 오토박싱과 언 박싱 등의 불필요한 과정이 생략되고 숫자의 경우 유용한 메소드를 추가로 제공한다.
  3. 과정

    1. 배열 스트림 : Arrays.stream()

      String [] arr = new String[]{"a","b","c"};
      Stream<String> stream = Arrays.stream(arr);
      
    2. 컬렉션 스트림: .stream()

    List<String> list = Arrays.asList("a", "b" ,"c");
    Stream<String> stream = list.stream()
    
    
    1. Stream.builder()
    Stream<String> builderStream = Stream.<String>builder()
    		.add("a").add("b").add("c")
    		.build();
    
    1. 람다식 Stream.generate(), iterate()
    Stream<String> generatedStream = Stream.generate(() -> "a").limit(3);
    // 생성할 때 스트림의 크기가 정해져있기 않기(무한하기) 때문에 최대 크기를 제한해줘야 한다.
    
    Stream<Integer> iteratedStream = Stream.iterate(0, n -> n + 2).limit(5); //0,2,4,6,8
    
    1. 기본 타입형 스트림
    IntStream intStream = IntStream.range(1, 5);  // [1, 2, 3, 4]
    
    1. 병렬 스트림

      1. parellelStream()
      Stream<String> parallelStream = list.parallelStream();
      
  4. 지연 연산

    1. 말그대로 최종 연산 전까지 중간 연산들이 실제로 실행되지 않는 것
    findMembers.stream()
    	.map(m -> {
    		System.out.println("mapping: " + m.getName());
    		return new MemberDTO(m.getName());
    	}) //중간 연산
    	.collect(Collectors.toList());
    
  5. map은 스트림내 요소들에 대해 함수가 적용된 결과의 새로운 요소로 변환한다.

    1. 예시

      Stream<String> names = Stream.of("kim","lee");
      Stream<Integer> lengths =names.map(s -> s.length()).toList(); //문자열  -> 길이로 변환
      
    2. “kim” → 3 , “lee” → 3, “park” → 4

    3. 즉, map 은 요소를 고르는 게 아니라, 요소를 바꾸는 연산

  6. 중간 연산

    1. Filtering

      1. 스트림 내 요소들을 하나씩 평가해서 걸러내는 작업, if 역할
      List<String> list = Arrays.asList("a","b","c");
      Stream<String> stream = list.stream()
      		.filter(list -> list.contains("a"));
      		// a 가 들어간 요소만 선택 [a]
      
    2. Mapping

      1. 스트림을 원하는 모양의 새로운 스트림으로 변환하고 싶을 때 사용
      Stream<String> stream = list.stream()
      		.map(String:: toUpperCase);
      		//[A, B, C]
      		.map(Integers::parseInt);
      		//문자열 -> 정수로 변환
      
    3. Sorting

      1. 스트림 내 요소들을 정렬하는 작업, Comparator 사용
      Stream<String> stream = list.stream()
      	.sorted()  //[a, b, c] 오름차순
      	.sorted(Comparator.reverseOrder()) //[c, b, a] 내림차순
      	
      List<String> list = Arrays.asList("a","bb","ccc");
      Stream<String> stream = list.stream()
      	.sorted(Comparator.comparingInt(String::length)) // [ccc, bb, a] 문자열 길이 기준 정렬
      
  7. 최종 연산 (결과 만들기)

    1. Calculating

      1. 기본형 타입을 사용하는 경우 스트림 내 요소들로 최소, 최대, 합, 평균 등을 구하는 연산을 수행할 수 있다.
       IntStream stream = list.stream
      	 .count()  //스트림 요소 개수 반환
      	 .sum()  //스트림 요소의 합 반환
      	 .min() //스트림의 최솟값 반환
      	 .max()  // 스트림의 최대값 반환
      	 .average() //스트림의 평균값 반환
      
    2. Reduction

      1. 스트림의 요소를 하나씩 줄여가며 누적연산을 수행
      IntStream stream = IntStream.renge(1,5);
      	.reduce(10, (total, num) -> total + num);
      
    3. Collecting

      1. 스트림 요소를 원하는 자료형으로 변환
      List<Person> members = Arrays.asList(new Person("lee",26),new Person("kim", 23), new Person("park", 23));
      
      // toList() - 리스트로 반환
      members.stream()
      	.map(Person::getLastName)
      	.collect(Collectors.toList());
      	//[lee, kim, park]
      // joining() - 작업 결과를 하나의 스트링으로 이어 붙이기	
      members.stream()
      	.map(Person:: getLastName)
      	.collect(Collectors.joining(delimiter = "+", prefix = "<" , suffix = ">");
      	//<lee+kim+park>
      	
      //groupingBy() - 그룹지어서 Map으로 반환
      members.stream
      	.collect(Collectors.groupingBy(Person::getAge));
      	)
      		// {26 = [Person{lastName="lee",age=26}],
          //  23 = [Person{lastName="kim",age=23},Person{lastName="park",age=23}]}
          
       members.stream
      	 .collect(Collectors.collectingAndThen(Collectors.toSet(),Collections::unmodifiableSet));
      
    4. Matching

      1. 특정 조건을 만족하는 요소가 있는지 체크한 결과를 반환

        1. anyMatch(하나라도 만족하는 요소가 있는지), allMatch(모두 만족하는지), noneMatch(모두 만족하지 않는지)
        List<String> members = Arrays.asList("Lee", "Park", "Hwang");
        boolean matchResult = members.stream()
        													.anyMatch(members -> members.contains("w")); //w를 포함하는 요소가 있는지, TRUE
        
        boolean matchResult = members.stream()
        													.anyMatch(members -> members.length() >= 4 );
        													//모든 요소의 길이가 4 이상인지, False
        boolean matchResult = members.stream()
        													.anyMatch(members -> members.endsWith("t"));
        													//t로 끝나는 요소가 하나도 없는지, true													
        
    5. Iterating

      1. forEach로 스트림을 돌면서 실행되는 작업
      2. 예시
      members.stream()
      	.map(Person :: getName)
      	.forEach(System.out::println);
      	//결과를 출력 (peek는 중간, forEach는 최종)
      
    6. Finding

      1. 스트림에서 하나의 요소를 반환
      2. 예시
      Person person = members.stream()
      											.findAny() //먼저 찾은 요소 하나 반환, 병렬 스트림의 경우 첫번째 요소가 보장되지 않음
      											.findFirst() //첫번째 요소 반환