17. 스트림요소처리

17.7 요소 정렬

순서를 맞추기
메소드는 다음과 같다.

sorted() 기존 스트림이 비교가 가능하면 알아서 정렬할수있음.(Comparable요소 존재)
비교기능이없다면 비교자를 제공해야함.(Comparator에 따라 정렬한 새스트림생성)

17.7.1 Comparable 구현객체의 정렬

public class Xxx implements Comparable{
List list = new ArrayList<>();
Stream stream = list.stream();
Stream orderedStream = stream.sorted();
내림차순 정렬하고 싶다면, 비교자가 없을 때 다음으로 하기 람다식 or Comparator비교자 하기
Stream resverseOrderedStream = stream.sorted(Comparator.reversOrde());

package ch17.sec07.exam01;

public class Student implements Comparable<Student>{
private String name;
private int score;

public Student( String name, int score) {
    this.name = name;
    this.score = score;
}

public String getName() {return name;}
public int getScore() {return score;}

@Override
public int compareTo(Student o) {
    return Integer.compare(score, o.score);
    //내가가진 스코어와 비교대상인 스코어와 비교 작으면 -1 같으면 0 크면 1리턴            
    //if문적는거랑 같음.
}}

package ch17.sec07.exam01;

public class Student implements Comparable<Student>{
private String name;
private int score;

public Student( String name, int score) {
    this.name = name;
    this.score = score;
}

public String getName() {return name;}
public int getScore() {return score;}

@Override
public int compareTo(Student o) {
    return Integer.compare(score, o.score);
    //내가가진 스코어와 비교대상인 스코어와 비교 작으면 -1 같으면 0 크면 1리턴            
    //if문적는거랑 같음.
}}

17.7.2 Comparator 를 이용한 정렬

객체 자체가 비교자를 가지고 있지 않기때문에 사용
sorted((o1, o2) -> {...})
Comparator는 함수형 인터페이스이기 때문에 람다식 작성가능 compare(T o1, T o2)
equals(Object obj)라는 추상메소드도 있지만 Object가 equals를 이미 가지고 있어서 고려대상이 아니게 됨.
만약 o1과 o2가 정수일 경우엔 Integer.compare(o1, o2), 실수일경우에는 Double.compare(o1, o2)를 호출해서 리턴값을 리턴해도 된다.

17.8 요소를 하나씩 처리(루핑)

루핑은 스트림에서 요소를 하나씩 반복해서 가져와 처리하는 것을 말한다.
지금까지 사용한 forEach()와 peek()가있다.
peek()는 중간처리 단게에서 사용하는 것이고 forEach()는 스트림에서 최종처리를 하는것이라는 차이점이 있다.

peek()는 최종처리가 아니기때문에 뒤에 최종처리가 반드시 와야한다.
Consumer<? super T> 소비자라는 뜻을 가진 인터페이스 이걸알아야 처리가능 객체를 소비, 인트값소비, 롱값소비, 더블값소비가 있다.

모든 Consumer는 매개값을 처리하는 accept()메소드를 가지고 있다. 매개값 -> accept()
Consumer<? super T>를 람다식으로 표현하면 다음과같다.
T -> {...} 또는 T-> 실행문;

package ch17.sec08;

import java.util.Arrays;
import java.util.stream.IntStream;

public class LoopingExample {
public static void main(String[] args) {
    int[] intArr = {1,2,3,4,5};

    //잘못 작성한경우
    Arrays.stream(intArr)
    .filter(a -> a%2 ==0)
    .peek(n->System.out.println(n)); // 최종 처리가 없으므로 동작하지 않음.


    //오류발생해서 질문함.
    /*
    IntStream intStream = Arrays.stream(intArr);
    IntStream filterStream = intStream.filter(a -> a%2 == 0);
    IntStream afterpeekStream = filterStream.peek(n->System.out.println(n));
    int total2 = afterpeekStream.sum();
    System.out.println("총합: " + total2 + "\n");
     */

    //중간처리 메소드 peek()를 이용해서 반복처리
    int total = Arrays.stream(intArr)
            .filter(a -> a%2 ==0)
            .peek(n->System.out.println(n))
            .sum();
    System.out.println("총합: " + total + "\n");

    //최종 처리 메소드 forEach()를 이용해서 반복처리
    Arrays.stream(intArr)
    .filter(a -> a%2 ==0)
    .forEach(n->System.out.println(n));

}}

17.9 요소 조건 만족 여부(매칭)

만족하면 true 아니면 false
매칭요소들이 특정조건에 만족하는지 여부를 조사하는 최종 처리 기능이다.
all 전부다 맞아야할경우 any최소한 하나의요소 none 모든 요소가 만족x여야할때

package ch17.sec09;

import java.util.Arrays;
import java.util.stream.IntStream;

public class MatchingExample {
public static void main(String[] args) {
    int[] intArr = { 2, 4 ,6 };

    /*
    IntStream intStream = Arrays.stream(intArr);
    boolean result = intStream.allMatch(a -> a%2==0);
    System.out.println("모두 2의 배수인가? " + result);
    */

    boolean result = Arrays.stream(intArr)
        .allMatch(a -> a%2==0);
    System.out.println("모두 2의 배수인가? " + result);

    result = Arrays.stream(intArr)
        .anyMatch(a -> a%3==0);
    System.out.println("하나라도 3의 배수가 있는가? " + result);

    result = Arrays.stream(intArr)
        .noneMatch(a -> a%3==0);
    System.out.println("3의 배수가 없는가?  " + result);
}}

17.10 요소기본집계

집계는 요소들을 하나의 값으로 만드는 것 최종처리 기능으로 카운팅 합계 평균값 최대값 최소값 등과 같이 하나의 값으로 산출하는 것을 말한다.
대량의 데이터를 가공해서 하나의 값으로 축소하는 리덕션이라고 볼 수 있다.

17.10.1 스트림이 제공하는 기본집계

집계메소드가 리턴하는 OptionalXXX의 객체로 리턴한다.
Optional은 하나의 객체 리턴 OptionalDouble, OptionalInt, OptionalLong클래스를 말한다.

package ch17.sec10;

import java.util.Arrays;
import java.util.stream.IntStream;

public class AggregateExample {
public static void main(String[] args) {
    //정수 배열
    int[] arr = {1, 2, 3, 4, 5};

    //카운팅
    long count = Arrays.stream(arr)
            .filter(n -> n%2==0)
            .count();
    System.out.println("2의 배수 개수: " + count);


    long sum = Arrays.stream(arr)
            .filter(n -> n%2==0)
            .sum();
    System.out.println("2의 배수의 합: " + sum);
    /*
    IntStream intStream = Arrays.stream(arr);
    IntStream filterStream = intStream.filter(n -> n%2==0);
    int total3 = filterStream.sum();
    System.out.println("2의 배수의 합: " + total3 + "\n");
    */

    //평균
    double avg = Arrays.stream(arr)
            .filter(n -> n%2==0)
            .average() //average()최종인데 마지막 아닌이유? OptionalInt로 값을 주기 때문에 이를 처리하기 위해 한줄더!
            .getAsDouble();
    System.out.println("2의 배수의 평균: " + avg);

    //최대값
    int max = Arrays.stream(arr)
            .filter(n -> n%2==0)
            .max()
            .getAsInt();
    System.out.println("최대값: " + max);

    //최소값
    int min = Arrays.stream(arr)
            .filter(n -> n%2==0)
            .min()
            .getAsInt();
    System.out.println("최소값: " + min);

    //첫 번째 요소
    int first = Arrays.stream(arr)
            .filter(n -> n%3==0)
            .findFirst()
            .getAsInt();
    System.out.println("첫 번째 3의 배수: " + first);
}}

17.10.2 Optional 클래스

Optional은 선택적인... 고정된게 아니라 선택할 수 있는 것임.
어떤경우에는 확실한값을 얻지만 어쩔땐 선택적으로 할필요가 잇다.
집계값이 존재하지 않을 경우 디폴트값을 설정하거나 집계값을 처리하는 Consumer를 등록할 수 있다.
필터를 했는데 집계가 존재하지 않을경우
단순히 평균을 구하는게 아니라 평균으로 딴걸 하고싶을때 사용.
orElse 집계값이 없을 경우 디폴트값

컬렉션의 요소는 동적으로 추가되는 경우가 많다. 프로그램실행하면서 요소가 저장
만약 컬렉션에 요소가 존재하지 않으면 집계값을 산출할수 없으므로 NoSuchElementException 예외가 발생한다.
하지만 앞의 표에 언급되어 있는 메소드를 이용하면 예외발생을 막을 수있다.

예를들어 average를 최종처리 에서 사용할 경우 다음 3가지 방법으로 대비가능
1.isPresent() 메소드가 true를 리턴할때만 집계값을 얻는다.
2.orElse()메소드로 집계값이 없을 경우를 대비해 디폴트 값을 정해놓는다.
3.ifPresent() 메소드로 집계값이 있을 경우에만 동작하는 Consumer람다식을 제공한다.

package ch17.sec10;

import java.util.ArrayList;
import java.util.List;
import java.util.OptionalDouble;
import java.util.stream.IntStream;

public class OptionalExample {
public static void main(String[] args) {
    List<Integer> list = new ArrayList< >();

    /*//예외 발생(java.util.NoSuchElementException)
     double avg = list.stream()
         .mapToInt(Integer :: intValue) // e -> return e.intValue() 인스턴스 메소드 Integer(객체) :: intValue
         .average()
         .getAsDouble();
    */

    //방법1
    OptionalDouble optional = list.stream()
        .mapToInt(Integer :: intValue)
        .average();
    if(optional.isPresent()) {
        System.out.println("방법1_평균: " + optional.getAsDouble());
    } else {
        System.out.println("방법1_평균: 0.0");
    }

    //방법2
    double avg = list.stream()
        .mapToInt(Integer :: intValue)
        .average()
        .orElse(0.0);
    System.out.println("방법2_평균: " + avg);

    /*
    IntStream intstream = list.stream().mapToInt(Integer :: intValue);
    double avg2 = intstream.average().orElse(0.0);
    System.out.println("방법2.2_평균: " + avg2);
    */

    //방법3
    list.stream()
        .mapToInt(Integer :: intValue)
        .average()
        .ifPresent(a -> System.out.println("방법3_평균: " + a));
}}

17.11 요소 커스텀 집계

다양한 집계결과물을 만들 수 있도록 reduce()메소드도 제공한다.
reduce: 축소한다 결국 어떤 요소를 처리하면 1개로 줄어들기때문임.


reduce(T identity, BinaryOperator<T> accumulator)
T identity= 이부분은 집계값이 없을때 기본값임. 이걸 사용하면 오류가 안남.
BinaryOperator BiFunction 으로부터 메소드를 물려받음 R apply(T t, U u) 람다식 (t, u) -> {... return r}
스트림에 많은데 두개만 받는데 어떻게 집계? 먼저 두개 = r 이나옴 이 r을 다시 넣고 다시 집계하는 방식
Binary두개를 가지고 하나를 만드는 것.

package ch17.sec11;

public class Student {
private String name;
private int score;

public Student(String name, int score) {
    this.name = name;
    this.score = score;
}

public String getName() { return name; }
public int getScore() { return score; }
}

package ch17.sec11;

import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;

public class ReductionExample {
public static void main(String[] args) {
    List<Student> studentList = Arrays.asList(
            new Student("홍길동", 92),
            new Student("신용권", 95),
            new Student("감자바", 88)
    );        
    //방법1
    int sum1 = studentList.stream()
            .mapToInt(Student :: getScore)
            .sum();
    /*
    IntStream intstream = studentList.stream().mapToInt(Student :: getScore);
    int total = intstream.sum();
    System.out.println("total: " + total);
    */

    //방법2
    int sum2 = studentList.stream()
                    .map(Student :: getScore)
                    .reduce(0, (a, b) -> a+b);
    /*
    IntStream intstream = studentList.stream().mapToInt(Student :: getScore);
    int total = intstream.reduce(0, (a, b) -> a+b);
    System.out.println("total: " + total);
    */

    System.out.println("sum1: " + sum1);
    System.out.println("sum2: " + sum2);
}}

17.12 요소 수집

스트림은 요소들을 필터링 또는 매핑한 후 요소들을 수집하는 최종처리 메소드인 collect() 를 제공한다.
이 메소드를 이용하면 필요한 요소만 컬렉션에 담을 수 잇고 요소들을 그루핑한 후 집계도 할 수 있다.

17.12.1 필터링한 요소 수집

Stream의 collect(Collector<T,A,R> collector) 메소드는 필터링 또는 매핑된 요소들을 새로운 컬렉션에 수집하고 이컬렉션을 리턴한다.
매개값인 Collector은 어떤 요소를 어떤 컬렉션에 수집할 것인지를 결정한다.
타입파라미터의 T는 요소 A 는 누적기(누적을 시키는것)수집을 해야하니 필요함. R은 요소가 저장될 컬렉션


? 은 와일드카드인데 가각ㄱ add put을 사용하기 때문에 자바가 이미 알고 있어서 안넣어도됨.
만약 내가 만든 컬렉션이라면? 그 넣는 방법을 ?에 적어주어야함.
T를 K로 리턴 T를 U로 리턴

package ch17.sec12.exam01;

public class Student {
private String name;
private String sex;
private int score;

public Student(String name, String sex, int score) {
    this.name = name;
    this.sex = sex;
    this.score = score;
}

public String getName() { return name; }
public String getSex() { return sex; }
public int getScore() { return score; }
}

package ch17.sec12.exam01;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CollectExample {
public static void main(String[] args) {
    List<Student> totalList = new ArrayList< >();
    totalList.add(new Student("홍길동", "남", 92));
    totalList.add(new Student("김수영", "여", 87));
    totalList.add(new Student("감자바", "남", 95));
    totalList.add(new Student("오해영", "여", 93));

    //남학생만 묶어 List 생성
    /*List<Student> maleList = totalList.stream()
             .filter(s->s.getSex().equals("남"))
             .collect(Collectors.toList());*/

    List<Student> maleList = totalList.stream()
            .filter(s->s.getSex().equals("남"))
            .toList();

    maleList.stream()
        .forEach(s -> System.out.println(s.getName()));

    System.out.println();

    //학생 이름을 키, Student 점수를 값으로 갖는 Map 생성
    Map<String, Integer> map = totalList.stream()
            .collect(
                Collectors.toMap(
                    s -> s.getName(), //Student 객체에서 키가 될 부분 리턴
                    s -> s.getScore() //Student 객체에서 값이 될 부분 리턴
                )
        );

    System.out.println(map);
}}

17.12.2 요소 그루핑

collect()메소드는 단순히 요소를 수집하느 ㄴ기능 이외에 컬렉션 요소들을 그루핑해서 Map객체를 생성하는 기능도 제공한다.
그룹이름을 키로 그룹이 가지는 값을 리스트로 만듬.
Collectors.groupingBy() 메소드에서 얻은 Collector를 collect()메소드르 호출할때 제공하면된다.


원래요소에서 키가 될부분을 리턴해라 t -> k
리턴 T.?.Map<K.List>>

package ch17.sec12.exam02;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class CollectExample {
public static void main(String[] args) {
    List<Student> totalList = new ArrayList< >();
    totalList.add(new Student("홍길동", "남", 92));
    totalList.add(new Student("김수영", "여", 87));
    totalList.add(new Student("감자바", "남", 95));
    totalList.add(new Student("오해영", "여", 93));

    Map<String, List<Student>> map = totalList.stream() //List<Student>에 성별로 나뉘어짐.
        .collect(
            Collectors.groupingBy(s -> s.getSex())
        );

    List<Student> maleList = map.get("남");
    maleList.stream().forEach(s -> System.out.println(s.getName()));
    System.out.println();

    List<Student> femaleList = map.get("여");
    femaleList.stream().forEach(s -> System.out.println(s.getName()));
}}

17.12.3

남 여를 키로 설정하고 List를 값으로 갔는 Map을 생성하는 코드

 Map<String, List<Student>> map = totalList.stream()
        .collect(
            Collectors.groupingBy(s -> s.getSex())
            //학생을 받아서 학생의 성을 리턴
        );

String을 키로 가지고 리스트를 값으로 얻는 것
그루핑 한거로 따로 평균점수 구하기같은 것 하려는 것임.
그루핑한것으로 집계하기.

Collectors.groupingBy()메소드에 두번째 매개값인 집계값을 얻을수잇다.

Map<String, Double> map = totalList.stream() //키 : 성별
        .collect(
        Collectors.groupingBy(
            s -> s.getSex(), //그루핑
        Collectors.averagingDouble(s->s.getScore()) //집계
            )
        );

예제실습

package ch17.sec12.exam03;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class CollectExample {
public static void main(String[] args) {
    List<Student> totalList = new ArrayList<>();
    totalList.add(new Student("홍길동", "남", 92));
    totalList.add(new Student("김수영", "여", 87));
    totalList.add(new Student("감자바", "남", 95));
    totalList.add(new Student("오해영", "여", 93));

    Map<String, Double> map = totalList.stream()
            .collect(
                Collectors.groupingBy(
                    s -> s.getSex(),
                    Collectors.averagingDouble(s->s.getScore())
                )
            );

    System.out.println(map);
}}

17.13 요소 병렬처리

내부반복자 좋은점 외부에서 x 내부처리 시간빠름 스트림 여러개에 할당해서 병렬처리를 할 수잇다.
요소가 너무많아서 병렬처리를 한다. 여러개로 분할해서 한다.
무조건 좋은 건아님 요소수가 적으면 오히려 늦어질 수 잇다.

17.13.1 동시성과 병렬성

멀티스레드는 동시성 또는 병렬성으로 실행된다.
동시성이란 하나의 코어에서 작업을 돌아감녀서 하는건데 매우 빠르면 동시처럼 보이는 것임.
동시성은 한시점에 하나의 작업만 실행한다. 스래드1 스레드2이런 식인것
병렬성이란 멀티코어를 각가이용해서 병렬로 실행하는 것을 말한다. 병렬성은 데이터 병렬성과 작업병렬성으로 구분할 수 있다.
데이터병렬성
전체데이터를 분할해서 서브데이터셋으로 만들고 이 서브데이터셋들을 병렬처리해서 작업을 빨리 끝내느 것을 말한다.
작업병렬성
서로 다른 작업을 병렬처리하는 것을 말한다.
예는 서버 프로그램이다. 서버는 각각 클라이언트에서 요청한 내용을 개별스레드에서 병렬로 처리한다.

17.13.2 포크조인 프레임워크

자바 병렬스트림은 요소들을 병렬처리하기 위해 포크조인 프레임워크를 사용한다. 포크조인 프레임워크는 포크단계에서 전체 요소들을 서브 요소셋으로 분할하고
각각의 서브 요소셋을 멀티코어에서 병렬로 처리한다. 조인단계에서는 서브 결과를 결합회서 최종결과를 만들어낸다.
JVM내부에서 알아서 처리하는 것임.

병렬처리스트림은 포크단계에서 요소를 순서대로 분할하지 않는다. 이해하기 쉽도록 위그림에서는 앞에서부터 차례대로 4등분햇지만
내부적으로는 요소들을 나누는 알고리즘이 있기때문에 개발자가 신경쓸필요는 없다.
포크조인 프레임워크는 병렬처리를 위해 스레드풀을 사용한다. 각각의 코어에서 서브요소셋을 처리하는 것은 작업 스레드가 해야하므로 스레드 관리가 필요하다.
포크조인 프레임워크는 ExecutorService의 구현객체인 ForkJoinPool을 사용해서 작업스레드를 관리한다.

17.13.3 병렬 스트림사용

자바 병렬 스트림을 이용할 경우에는 백그라운드에서 포크조인 프레임워크가 사용되기 때문에 개발자는 매우 쉽게 병렬처리를 할 수 있다.
병렬스트림 다음 두가지 메소드로 얻을 수 있다.

package ch17.sec13;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.Stream;

public class ParallelExample {
public static void main(String[] args) {
    Random random = new Random();

    //1억개의 Integer 저장
    List<Integer> scores = new ArrayList< >();
    for(int i=0; i<100000000; i++) {
        scores.add(random.nextInt(101)); //0~ 101미만의 임의 정수 리턴
    }

    double avg = 0.0;
    long startTime = 0;
    long endTime = 0;
    long time = 0; //끝시간 - 시작시간으로 걸린시간 구하기

    //일반 스트림으로 처리하기
    Stream<Integer> stream = scores.stream();
    startTime = System.nanoTime();
    avg = stream
            .mapToInt(i -> i.intValue())
            .average()
            .getAsDouble();
    endTime = System.nanoTime();
    time = endTime - startTime;
    System.out.println("avg: " + avg + ", 일반 스트림 처리 시간: " + time + "ns");

    //병렬 스트림으로 처리하기
    Stream<Integer> parallelStream = scores.parallelStream();
    startTime = System.nanoTime();
    avg = parallelStream
            .mapToInt(i -> i.intValue())
            .average()
            .getAsDouble();
    endTime = System.nanoTime();
    time = endTime - startTime;
    System.out.println("avg: " + avg + ", 병렬 스트림 처리 시간: " + time + "ns");
}}

17.13.4 병렬처리성능

일반 스트림 순차처리보다 항상 실행성능이 좋다고 판단해서는 안된다.
그전에 병렬처리에 영향을 미치는 다음 3가지요인을 잘살펴보아야한다.

요소의 수와 요소당 처리 수간
요소가 적고 요소당 처리시간이 짤븡면 일반스트림이 더 빠를 수잇다.
병렬처리는 포크및 조인단계가 잇고 스레드풀을 생성하는 추가적인 비용이 발생하기 때문임.

스트림 소스의 종류
ArrayList와 배열은 인덱스로 요소를 관리하기때문에 포크단계에서 요소를 쉽게 분리할 수 있어 병렬처리 시간이 절약된다.
반면에 HashSet TreeSet은 요소 분리가 쉽지않고 LinkedList역시 링크를 따라가야하므로 요소분리가 쉽지않다. 따라서 이소스들은 상대적으로 병렬처리가 늦다.

코어의 수
cpu의 코어의 수가 많으면 많을 수록 병렬스트림의 성능은 좋아진다. 하지만 코어의 수가 적을 경우에는 오히려 일반스트림이 더 빠를 수잇다.
병렬스트림은 스레드 수가 증가하여 동시성이 많이 일어나므로 오히려 느려진다.

2022.12.05 리뷰

마지막 국비지원 면접을 봤다.
꼭 됬으면 좋겠다. 화이팅!
중요한 것은 꺾이지 않는 마음

'기초단계 > JAVA' 카테고리의 다른 글

2022.12.07 JAVA 데이터 입출력  (1) 2022.12.07
2022.12.06 JAVA 데이터 입출력  (0) 2022.12.07
2022.12.03 JAVA 스트림요소처리  (1) 2022.12.03
2022.12.02 JAVA 람다식  (0) 2022.12.02
2022.12.01 JAVA 컬렉션 자료구조  (0) 2022.12.02

+ Recent posts