[JAVA] STREAM

하이초·2024년 1월 28일

JAVA

목록 보기
7/7
post-thumbnail

1️⃣ STREAM?!

스트림이란 자바 8부터 새로 추가된 기능이며 컬렉션, 배열 등에 저장된 요소를 하나씩 참조하면서 코드를 실행할 수 있는 기능이다

  • 자료의 대상과 관계없이 동일한 연산을 수행
    - 컬렉션, 배열을 대상으로 연산을 수행
    - 자료 처리에 대한 추상화가 구현되었다고 함
  • 한 번 생성하고 사용한 스트림은 재사용 불가
    - 다른 연산을 수행하기 위해서는 스트림을 다시 생성해야 함
  • 스트림 연산은 기존 자료를 변경하지 않음 (read only)
    - 스트림 생성 시 스트림이 사용하는 메모리 공간은 별도로 생성되어, 연산이 수행돼도 기존 자료에 대한 변경은 발생하지 않음
  • 스트림 연산은 중간 연산최종 연산으로 구분 됨
    - 중간 연산과 최종 연산에 대한 구현은 람다식을 활용
  • 순서
    - 스트림 만들기 -> 중간 연산(반복 적용 가능, 연산 결과가 스트림) -> 최종 연산(스트림 요소 소모) -> 결과 리턴
  • 기본형 스트림으로 IntStream, LongStream, DoubleStream 등을 제공
    - 오토박싱 등의 불필요한 과정이 생략됨
    - Stream<Integer> 대신에 IntStream을 사용하는 것이 더 효율적
    - 숫자의 경우 Stream<T> 보다 더 유용한 메서드를 많이 제공한다
    • sum(), average() 등
  • 병렬 처리가 가능(멀티 쓰레드 사용)
    - parallel

2️⃣ 중간 연산

여러 개의 연산이 적용될 수 있다

filter(): 조건에 맞는 요소 추출

Stream<T> filter(Predicate < ? super T> predicate);

// boolean 값을 리턴하는 람다식을 넘겨주고,
// 뽑아져 나오는 데이터에 대해 람다식을 적용하여 true가 리턴되는 데이터만 선별

map(): 요소 변환

<R> Stream<R> map(Function < ? super T, ? extends R> mapper);

// 값을 변환해주는 람다식을 인자로 받음
// 스트림에서 생성된 데이터에 map() 메소드의 인자로 받은 람다식을 적용해 새로운 데이터를 만듬
  • mapToInt 등 각 메소드 호출 가능

sorted(): 요소 정렬

Stream<T> sorted();

// 인자 없이 호출 시 오름차순으로 정렬

Stream<T> sorted(Comparator < ? super T > comparator);

// 만약 정렬할 때 두 값을 비교하는 별도의 로직이 있다면,
// comparator를 sorted() 메소드의 인자로 넘겨줄 수도 있음

peak(): map과 비슷하게 작동하나 새로운 스트림을 생성하지는 않음

Stream<T> peek(Consumer < ? super T > action);

// 그냥 한 번 해본다는 의미로 생성되는 데이터들에 변형을 가하지 않고 그냥 인자로 받은 람다식만 수행

int sum = IntStram.range(1, 10).peek(System.out::println).sum();

// 위와 같이 중간에 확인하고자 할 때 사용하면 좋음

distinct(): 중복 제거

skip(long n): 앞에서부터 n개 건너 뛰기

limit(long maxSize): maxSize 이후 요소 잘라냄

3️⃣ 최종 연산

마지막 한 번만 적용됨

forEach(): stream 요소에 있는 요소를 하나씩 꺼내와 지정된 작업 수행

void forEach(Consumer<? super T> action)

forEachOrdered(): 병렬 스트림의 경우 순서를 유지하며 수행

void forEachOrdered(Consumer<? super T> action)

count(): 요소의 개수

long count()

sum(): 요소들의 합

max(): 최대값

Optional<T> max(Comparator<? super T> comparator)

min(): 최소값

Optional<T> min(Comparator<? super T> comparator)

findAny(): 아무 요소나 하나 반환, 병렬 스트림에 사용

Optional<T> findAny()

findFirst(): 첫번째 요소 반환, 순차 스트림에 사용

Optional<T> findFirst()

allMatch(): 모든 조건을 만족

boolean allMatch(predicate<T> P)

anyMatch(): 조건을 하나라도 만족

boolean anyMatch(Predicate<T> P)

noneMatch(): 모든 조건을 만족하지 않음

boolean noneMatch(Predicate<T> P)

toArray(): 모든 요소를 배열로 반환

Object[] toArray()

A[] toArray(IntFunction<A[]> generator): 특정 타입의 배열로 반환

최종 연산이 호출되어야 중간 연산에 대한 수행이 이루어지고 그 결과가 만들어진다.
중간 연산에 대한 결과를 연산 중간에 알 수 없으며, 이를 지연 연산이라 한다.

3️⃣ 스트림 연산 예시

  • 객체 배열로부터 스트림 생성
Stream.of("a", "b", "c");
Stream.of(new String[]{"a", "b", "c"});
Arrays.stream(new String[]{"a", "b", "c"});
Arrays.stream(new String[]{"a", "b", "c"}, startIndex, EndIndex + 1);
  • 람다식 iterate(), generate()
// Stream.iterate(T send, UnaryOperator f); 이전 결과에 종속적
Stream.iterate(0, n -> n + 2); // 0, 2, 4, ... 무한 스트림

// generate는 초기값 지정 x, 이전 결과와 무관, 중간 연산
Stream.generate(Math::random);
Stream.generate(()->1); 
  • 문자열 리스트에서 문자열의 길이가 5 이상인 요소만 출력
sList.stream().filter(s->s.length() >= 5).forEach(s->System.out.println(s));

// filter()로 문자열 중 길이가 5 이상인 요소 추출 -> forEach()로 추출된 문자열 각각에 대해 sout 실행 -> stream에는 자료가 남아있지 않음
// filter 람다식의 리턴값은 true, false로 false면 버려짐
  • 고객 클래스 배열에서 고객 이름만 가져오기
customerList.stream().map(c->c.getName()).forEach(s->System.out.println(s));

// map()로 customer 객체를 customer객체의 getName()함수로 문자열을 변환 -> forEach()로 변환된 문자열 각각에 대해 sout 실행 -> stream에는 자료가 남아있지 않음
// map(file::getName) -> 각각의 파일에서 String Name만 새로운 스트림으로
// map(s->s.subString(3)) string 타입 요소를 짤라서 새로운 스트림으로
  • ArrayList 객체에 stream 생성하고 사용하기
public class ArrayListStreamTest {

	public static void main(String[] args) {
		List<String> sList = new ArrayList<String>();
		sList.add("Tomas");
		sList.add("Edward");
		sList.add("Jack");
		
		Stream<String> stream = sList.stream();
		stream.forEach(s->System.out.print(s + " "));
		System.out.println();
		
		sList.stream().sorted().forEach(s->System.out.print(s+ " "));
		// 문자열 sorted()로 정렬 -> forEach()로 정렬된 문자열 각각에 대해 sout 실행 

		sList.stream().map(s->s.length()).forEach(n->System.out.println(n));
		// map()로 문자열을 길이로 변환 -> forEach()로 변환된 숫자 각각에 대해 sout 실행

		// 기존 stream은 재사용할 수 없으므로, stream() 메소드 호출하여 스트림 다시 생성
	}
}
  • 정수 자료에 대한 연산 예
public class IntArrayStreamTest {

	public static void main(String[] args) {
		int[] arr = {1,2,3,4,5};
		Arrays.stream(arr).forEach(n->System.out.print(n + "\t"));
		// forEach()로 arr에 입력된 숫자 각각에 대해 sout+tab 실행

		System.out.println();					

		int sum  = Arrays.stream(arr).sum();
		System.out.println("sum: " + sum);
		// sum()로 배열 합 구하기		

		List<Integer> list = new ArrayList<Integer>();
		list.add(1);
		list.add(2);
		list.add(3);
		list.add(4);
		list.add(5);
		int sum2 = list.stream().mapToInt(n->n.intValue()).sum();
		System.out.println(sum2);
		// mapToInt()로 Integer wrapper 객체 형에서 int data type으로 변경 -> sum()로 배열 합 구하기
	}

}

4️⃣ 연산 수행에 대한 구현을 할 수 있는 reduce() 연산

  • 정의된 연산이 아닌 프로그래머가 직접 구현한 연산을 적용
T reduce(T identify, BinaryOperator<T> accumulator)
Optional<T> reduce(BinaryOperator<T> accumulator);
  • 최종 연산으로 스트림의 요소를 소모하며 연산 수행
  • 배열의 모든 요소의 합을 구하는 reduce()연산
Arrays.stream(arr).reduce(0, (a,b)->a+b));
  • reduce() 메소드의 두번째 요소로 전달되는 람다식에 따라 다양한 기능을 수행할 수 있음
  • 람다식을 직접 구현하거나 람다식이 긴 경우 BinaryOperator를 구현한 클래스를 사용
class CompareString implements BinaryOperator<String>{

	@Override
	public String apply(String s1, String s2) {
		if (s1.getBytes().length >= s2.getBytes().length) return s1;
		else return s2;
	}
}

public class ReduceTest {

	public static void main(String[] args) {

		String[] greetings = {"안녕하세요~~~", "hello", "Good morning", "반갑습니다^^"};
		
		System.out.println(Arrays.stream(greetings).reduce("", (s1, s2)-> 
		                          {if (s1.getBytes().length >= s2.getBytes().length) 
				                                  return s1;
		                          else return s2;})); 
		// 람다식으로 구현
		
		String str = Arrays.stream(greetings).reduce(new CompareString()).get();

		//BinaryOperator를 구현한 클래스 이용

		System.out.println(str);
		                          
	}
}
  • BineryOpertor의 경우 Optional로 반환되기 때문에 get으로 값을 가져와야 한다.
profile
개발국대가 되는 그 날까지. 지금은 개발 응애.

0개의 댓글