[Java] : Stream

dohyoungK·2024년 5월 28일
0

면접 스크립트

목록 보기
30/30
post-thumbnail

[Java] : Stream


Stream이란?

Java 8부터 추가된 기술인 Stream API는 ‘일련의 데이터의 흐름’을 표준화된 방법으로 쉽게 처리할 수 있도록 지원하는 ‘클래스의 집합(패키지)’이다.

오라클 공식 문서에서는 Stream 패키지를 ‘요소들의 Stream에 함수형 연산(람다함수를 통한 연산)을 지원하는 클래스’라고 정의하고 있다.

즉, Java의 Stream을 이용하면 일련의 데이터를 함수형 연산을 통해 표준화된 방법으로 쉽게 가공, 처리할 수 있다.

Stream 장점

Stream 사용 이전에는 데이터 처리에 주로 Collection을 이용했다.

Stream은 Collection과 기존 for문을 사용해 데이터를 처리할 때와 비교했을 때 코드가 훨씬 간결해지고 명료해져 코드의 가독성유지보수성을 높여준다.

또한 Stream은 병렬처리를 지원하고 있기 때문에 대용량의 데이터를 효율적으로 처리 가능하다.

class Fruit {
    String name; // 이름
    String origin; // 원산지
    Integer price; // 가격
    Integer amount; // 재고

    public Fruit(String name, String origin, Integer price, Integer amount) {
        this.name = name;
        this.origin =  origin;
        this.price = price;
        this.amount = amount;
    }
}

List<Fruit> fruits = Arrays.asList(
        new Fruit("cherry", "A", 2000, 3),
        new Fruit("banana", "C", 3500, 5),
        new Fruit("avocado", "B", 3000, 0),
        new Fruit("blueberry", "B", 2000, 1),
        new Fruit("apple", "A", 3000, 3),
        new Fruit("coconut", "B", 3500, 6),
        new Fruit("tomato", "C", 3000, 0));

위와 같은 상황의 과일 리스트에서 과일을 원산지 별로 묶어 이름순 정렬한 리스트를 구하고, 원산지별 과일을 3개씩 구매했을 때의 가격 총합을 구하고 싶을 때 코드를 보자.

// 기존 방식
void findFruitsFor () {
    Map<String, List<Fruit>> fruitByOrigin = new HashMap<>();

	// 원산지별 나누기 
    for (Fruit fruit : fruits) {
        List<Fruit> value = fruitByOrigin.getOrDefault(fruit.origin, new ArrayList<>());
        value.add(fruit);
        fruitByOrigin.put(fruit.origin, value);
    }

	// 원산지별 나눈 과일 리스트 정렬
    fruitByOrigin.forEach((key, value) -> {
        System.out.println("원산지: " + key);
        Collections.sort(value, new Comparator<Fruit>() {
            @Override
            public int compare(Fruit o1, Fruit o2) {
                return o1.name.compareTo(o2.name);
            }});

		// 원산지별 과일 가격 총합 구하기
        int totalPrice = 0;
        for (Fruit fruit : value) {
            System.out.println(fruit.name);
            totalPrice += fruit.price * Math.min(3, fruit.amount);
        }
        System.out.println("가격 총합: " + totalPrice);
        System.out.println();
    });
}
void findFruitsStream () {
// Stream 사용
	
    // 원산지별 과일 나누고 정렬하기
    Map<String, List<Fruit>> fruitByOrigin = fruits.stream()
            .collect(groupingBy(fruit -> fruit.origin, collectingAndThen(Collectors.toList(), list -> {
                Collections.sort(list, new Comparator<Fruit>() {
                    @Override
                    public int compare(Fruit o1, Fruit o2) {
                        return o1.name.compareTo(o2.name);
                    }
                });
                return list;
            })));

	// 원산지별 과일 가격 총합 구하기
    Map<String, Integer> totalPrice = fruits.stream()
            .collect(groupingBy(fruit -> fruit.origin, summingInt(fruit -> fruit.price * Math.min(3, fruit.amount))));

    fruitByOrigin.forEach((key, value) -> {
        System.out.println("원산지: " + key);
        value.forEach(fruit -> System.out.println(fruit.name));
        System.out.println();
    });
    System.out.println(totalPrice);
}

Stream을 사용했을 때의 코드가 훨씬 간결하고 가독성이 높은 것을 확인할 수 있다.

Stream 과정

데이터를 처리하기 위해서는 데이터를 생성하고, 생성된 데이터를 가공해 필요한 형태로 변환한 후, 결과를 소비해야 한다.

따라서 Stream은 데이터 처리를 위한 API이므로 생성 -> 가공 -> 소비의 과정으로 구성되어 있다.

1. 생성

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

  • 컬렉션 스트림 : .stream()

  • Stream.builder()

Stream<String> builderStream = Stream.<String>builder()
    .add("a").add("b").add("c")
    .build(); 
  • Stream.generate(), iterate()
Stream<String> generatedStream = Stream.generate(() -> "a").liimit(3);
// 생성할 때 스트림의 크기가 정해져있지 않기(무한하기)때문에 최대 크기를 제한해줘야 한다.

Stream<Integer> iteratedStream = Stream.iterate(0, n->n+2).limit(5);
//0,2,4,6,8
  • 기본 타입형 스트림
IntStream intStream = IntStream.range(1, 5); // [1, 2, 3, 4]
  • 병렬 스트림
Stream<String> parallelStream = list.parallelStream();

2. 가공

Stream<T> skip(long n) // n개의 요소를 건너뛴다.
Stream<T> limit(long maxSize) // 스트림의 요소를 maxSize개로 제한한다.

Stream<T> filter(Predicate<? super T? predicate) // 조건(Predicate)에 맞지 않는 요소를 걸러낸다.
Stream<T> distinct() // 스트림에서 중복된 요소를 제거한다.

Stream<T> sorted(Comparator<? super T> comparator) // 지정된 Comparator에 따라 정렬한다.

Stream<R> map(Function<? super T, ? extends R> mapper) // 스트림의 요소를 특정 형태로 변환한다.

// Stream<T> 타입의 스트림을 기본형 스트림으로 변환한다.
IntStream mapToInt(ToIntFunction<? super T> mapper)
LongStream mapToLong(ToLongFunction<? super T> mapper)
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)

3. 소비

IntStream stream = list.stream()
	.count() 	// 스트림 요소 개수 반환 
    .sum()		// 스트림 요소의 합 반환
    .min()		// 스트림 최소값 반환
    .max()		// 스트림 최대값 반환
    .average()	// 스트림 평균값 반환
-----------------------------------------------------------------------------------------
// reduce - 스트림의 요소를 하나씩 줄여가며 누적연산 수행
IntStream.range(1,5)
	.reduce(10, (total,num)->total+num);
    //reduce(초기값, (누적 변수,요소)->수행문)
    // 10 + 1+2+3+4+5 = 25

-----------------------------------------------------------------------------------------
// collect - 스트림을 원하는 자료형으로 변환
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());
    
// 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}]}
    
//collectingAndThen() - collecting 이후 추가 작업 수행
members.stream()
	.collect(Collectors.collectingAndThen (Collectors.toSet(),
    									   Collections::unmodifiableSet));
	//Set으로 collect한 후 수정불가한 set으로 변환하는 작업 실행
    
-----------------------------------------------------------------------------------------
List<String> members = Arrays.asList("Lee", "Park", "Hwang");
boolean matchResult = members.stream()
						.anyMatch(members->members.contains("w")); //w를 포함하는 요소가 있는지, True

boolean matchResult = members.stream()
						.allMatch(members->members.length() >= 4); //모든 요소의 길이가 4 이상인지, False

boolean matchResult = members.stream()
						.noneMatch(members->members.endsWith("t")); //t로 끝나는 요소가 하나도 없는지, True

0개의 댓글