코딩 기록들

[Java Stream Programming] 3. 스트림 Stream 본문

카테고리 없음

[Java Stream Programming] 3. 스트림 Stream

코딩펭귄 2024. 2. 7. 17:17

스트림이란?

- 컬렉션, 배열의 저장요소를 하나씩 참조해서 람다식(함수적 스타일) 으로 처리할 수 있도록 해주는 반복자

- 병렬처리를 지원하는 내부반복자

- 컬렉션, 배열을 스트림으로 변환하여 여러가지 형태의 처리를 할 수 있음

*병렬처리 : 쓰레드가 동시에 움직이는것 - ex) 온라인게임

- 반복문을 이용하던 기존의 컬렉션을 스트림으로 처리할 수 있음 -> 반복문을 사용하지 않고 컬렉션을 제어할 수 있음

-- 내부반복자 : for을쓰지않아도, 가지고있는 데이터의 수 만큼 반복한다

스트림의 종류

- BaseStream을 부모로 하는 Stream, IntStream, LongStream, DoubleStream (자식 인터페이스들)이 상속관계를 이룸

- Stream : 객체 요소를 처리하는 스트림, 

- IntStream, LongStream, DoubleStream : 각각 int, long, double요소를 처리하는 스트림

 

스트림을 사용하는 이유?

1. 정렬, 필터링, 맵핑 등을 통해 지저분하고 중복되던 반복문 코드가 사라짐

2. 데이터를 제어하는 주체가 RDB 에서 JAVA옮겨짐 (데이터가 늘어남에 따라 데이터를 정제하는 RDB 능력의 한계가 드러났고, 비정형 데이터를 RDB 에서 정제할 수 없음)

- RDB의 역할을 Java의 Stream이 대신 처리함

 

- 스트림을 쓰는 이후로는 for, whlie 안씀 .forEach로 표현

- 스트림 : 반환시키는 타입으로 제네릭이 정해짐

 

public 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;
    } 
    @Override 
    public String toString() { 
        return name;
    } 
    public enum Type { 
        MEAT, FISH, OTHER
    }
}
import java.util.Arrays;
import java.util.List;

public interface DishData {

	public List<Dish> menu = Arrays.asList(  //이렇게하면 하단에 있는것들이 전부 menu에 담김
							// Arrays.asList( 대신 List.of( 써도 됨(자바10, 버전차이)
            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("rice", true, 350, Dish.Type.OTHER), 
            new Dish("season fruit", true, 120, Dish.Type.OTHER), 
            new Dish("pizza", false, 550, Dish.Type.OTHER), 
            new Dish("prawns", false, 300, Dish.Type.FISH), 
            new Dish("salmon", false, 450, Dish.Type.FISH));
}

위와 같은 Dish, DishData 파일이 있을때, 

저칼로리 음식만 필터링 한 후
칼로리 순
으로 정렬,출력하는 예제
(menu 리스트안에 음식정보가 들어있음)
1. 기존컬렉션의 사용예제
List<Dish> lowCaloricDishs = new ArrayList<>();
// 누적자로 요소 필터링
for (Dish d: menu) {
    if (d.getCalories() < 400) {
        lowCaloricDishs.add(d);
    }
}

// 익명클래스로 요리 정렬
Collections.sort(lowCaloricDishs, new Comparator<Dish>() {
    public int compare(Dish d1, Dish d2) {
        return Integer.compare(d1.getCalories(), d2.getCalories());
    }
});

// 정렬된 리스트 처리하며 요리이름 선택
List<String> lowCalroricDishesName = new ArrayList<>();
for (Dish d: lowCaloricDishs) {
    lowCalroricDishesName.add(d.getName());
}
기존 코드를 스트림으로 변경
List<String> lowCaloricDishesName = 
            menu.stream()
                // 400 칼로리 이하의 요리 선택
                .filter(d -> d.getCalories() < 400) 
                // 칼로리로 요리 정렬 (오름차순 정렬)
                .sorted(Comparator.comparing(Dish::getCalories))
                // 요리명 추출
                .map(Dish::getName) //dish = dish.getName을 줄인형태
                // 모든 요리명을 리스트에 저장
                .collect(Collectors.toList());

 

스트림은 코드를 블로단위로 관리 / 처리함

 

이런 코드가 있다면, 

List<String> threeHighCaloricDishNames = 
            menu.stream()
                // 300 칼로리 이상의 요리 선택
                .filter(d -> d.getCalories() > 300) 
                // 요리명 추출
                .map(Dish::getName)
                // 선착순 3개만 선택
                .limit(3)
                // 모든 요리명을 리스트에 저장
                .collect(Collectors.toList());

 

스트림은 이렇게 처리함

 

어떤 인터페이스를 요구하는지 암기할것

List<String> lowCaloricDishesName =

//List<Dish> -> Stream<Dish>로 변경시킴

menu.stream()

 

//filter : 조건에 만족하는 데이터검색

//filter(Predicate<Dish>)

//Predicate역할: dish를받아서 boolean으로 반환. true가 반환된것만 새로운 Stream으로 생성됨

.filter(dish -> dish.getCalories() < 400) //filter가 새롭게 만들어준 새로운 Stream에 boolean연산결과가 들어감

 

//sorted : 정렬시킴(정렬기준 주기) : 데이터 오름차순 정렬(칼로리기준)

//sorted(Comparater<Dish>)

//comparator -> compare(Dish1, Dish2)

// -> dish1.getCalories() - dish2.getCalories() : 오름차순

// -> dish2.getCalories() - dish1.getCalories() : 내림차순

// -> Comparator.comparing(Dish::getCalories) : 오름차순

.sorted(Comparator.comparing(Dish::getCalories))

//이걸람다로 바꿔본다면,(위 코드와 아래 코드 1줄씩은 완벽히 동일)

// .sorted((dish1, dish2) -> dish1.getCalories() - dish2.getCalories())

// .sorted(Comparator.comparing(dish -> dish.getCalories()))

 

//여기서 말하는 map : 데이터변경!! map(데이터 변경시키고 싶은 식 적어줌)

// map(Function<Dish, 반환되는 타입>)

.map(Dish::getName) // Dish -> Name : 즉, 음식의 이름 ex) "salmon" 만 남는다

// .map(dish -> dish.getName()) //윗줄 코드와동일

 

//현재까지 처리된 Stream데이터에서 상위2개는 제외한다

.skip(2)

 

//현재까지 처리된 String데이터에서 가장 상위데이터 3개만 가져온다

.limit(3)

 

//List로 변경

//최종함수 : Stream을 일반클래스 혹은 Primitive Type으로 반환.

//Collectors.toList() -> Stream을 List로 변경.

//이 때, Stream의 제네릭이 List에도 동일하게 전달

//Steram<String> ==> List<String>

.collect(Collectors.toList());

lowCaloricDishesName //type : string

//Stream 혹은 List의 데이터를 반복하며 Lambda를 수행

//forEach(Consumer<String>)

//Consumer -> String을 파라미터로 받아 VOID를 반환

.forEach(System.out::println); //lowCaloricDishesName를 반복하며 system 수행해라

// .forEach(menuName -> Sytem.out.println(menuName)); //윗줄코드와 동일

 

.stream() : main이 혼자서, 한번에 다 처리

.parallelStream() : 각각 할당받을 쓰레드 만큼의 처리가 이루어짐(자기영역안에서, 각각 할당받은 쓰레드들이 Filtering-sorted-map 처리 -> 마지막에 toList 하면 각각 처리한 데이터들을 하나로 묶어서 리스트로 보내줌). 하지만, 데이터가 매우 클 경우 발생할 수 있는 문제 : '정렬'이 안되어있을수 있음.  --> 병렬스트림을 쓸 때는 sort 하지 않는것이 유리함

 

 

스트림 연산의 종류

- 최종연산이 실행되기 전까지, 중간 연산 각각은 실행되지 않음

- 최종연산이 실행될 때, 중간연산이 실행됨 (코드는 위에서부터 아래로 쓰지만, 실제 실행은 위->아래가 아님)

- 최종연산이 실행되어야, 그제서야 중간연산을 시키고, 그 다음 collect(최종연산)를 하는것!)