[Java] 스트림 Stream

yeeyo·2025년 3월 10일

Java

목록 보기
5/5
post-thumbnail

스트림(Stream)

스트림은 컬렉션에 저장된 요소들을 하나씩 순회 하면서 함수형 프로그래밍 방식으로 처리할 수 있는 기능이다.

람다식과 함께 사용할 수 있으며 컬렉션에 들어있는 데이터에 대한 처리를 간결하게 표현할 수 있다.


스트림이 필요한 이유는 다음과 같다.

  • 배열 또는 컬렉션을 함수 여러 개를 사용해서 결과를 쉽게 얻을 수 있다.
  • 람다식을 활용해 코드를 간결하게 표현 가능하다.

우리는 스트림은 크게 생성, 가공, 결과 3가지로 구분할 수 있다.

  1. 생성: 스트림을 생성
  2. 가공: 원하는 결과를 만들기 위해 필터링, 매핑 등의 작업을 수행할 수 있음
  3. 결과: 최종 결과를 만들어 내는 작업


스트림 생성 메소드

stream()

자바에서는 자주 사용하는 배열과 컬렉션 객체에서 stream() 메소드를 지원한다. 우리는 stream() 메소드를 사용하여 스트림을 생성할 수 있다.

public class Application1 {

	public static void main(String[] args) {

		String[] sarr = new String[] {"java", "oracle", "jdbc"};

		//스트림은 사용할 때마다 선언해주어야 한다.
		Stream<String> strStream1 = Arrays.stream(sarr); //배열 스트림 생성
		strStream1.forEach(System.out::println); //스트림을 순회하여 요소들을 출력
		
		Stream<String> strStream2 = Arrays.stream(sarr, 0, 2); //0~1까지 배열 스트림 생성
		strStream2.forEach(System.out::println);
		
		/* 컬렉션 스트림 생성 */
		List<String> stringList = Arrays.asList("html", "css", "javascript");
		
		Stream<String> strStream3 = stringList.stream();
		strStream3.forEach(System.out::println);
		
		/* 컬렉션의 경우 스트림 생성을 생략하고 사용할 수 있다. */
		stringList.forEach(System.out::println);
	}

스트림에 forEach() 메소드를 사용하면 스트림 안에 있는 요소들을 순회하며 출력 해준다.

또한 컬렉션의 경우엔 스트림을 생성하지 않고 forEach()를 사용할 수 있다.


range(), rangeClosed()

range() 는 range(시작 값, 종료 값) 형태로 사용할 수 있으며, 시작 값부터 1씩 증가하는 숫자로 종료 값 전까지 범위의 스트림을 생성 할 수 있다.

rangeClosed 은 rangeClosed(시작 값, 종료 값) 형태로 사용할 수 있으며, 시작 값부터 1씩 증가하는 숫자로 종료 값까지 범위의 스트림을 생성할 수 있다.

public class Application2 {

	public static void main(String[] args) {
	
		IntStream intStream = IntStream.range(5, 10); //5부터 1씩 증가하여 9까지 생성
		intStream.forEach(value -> System.out.print(value + " "));
		System.out.println();
		
		LongStream longStream = LongStream.rangeClosed(5, 10); //5부터 1씩 증가하여 10까지 생성
		longStream.forEach(value -> System.out.print(value + " "));
		System.out.println();
		
		/* 문자열을 intStream으로 변환 */
		IntStream helloworldChars = "hello world".chars(); //문자열이 모두 문자의 아스키코드 값으로 생성됨
		helloworldChars.forEach(v -> System.out.print(v + " "));
		System.out.println();
		
		/* 문자열을 split하여 stream으로 생성 */
		Stream<String> splitStream = Pattern.compile(", ").splitAsStream("html, css, javascript");
		splitStream.forEach(System.out::println);
		
		/* Stream.of()를 이용한 생성 */
		//괄호 안에 있는 데이터를 스트림으로 생성
		Stream<String> stringStream1 = Stream.of("java", "oracle", "jdbc");
		Stream<String> stringStream2 = Stream.of("html", "css", "javascript");

//		stringStream1.forEach(System.out::println);
//		stringStream2.forEach(System.out::println);

		/*  concat()을 이용하여 두 개의 스트림을 동일 타입 스트림으로 합칠 수 있다.
		 *  스트림은 1회성으로 사용 가능하며 이미 열린 스트림은 재사용이 불가능하다 (위에꺼 주석 안하면 IllegalStateException발생함)
		 * */
		Stream<String> concatStream = Stream.concat(stringStream1, stringStream2);
		concatStream.forEach(v -> System.out.print(v + " "));
	}
}


스트림 중계 연산 메소드

filter()

filter()는 스트림의 중계 연산 중 하나이다. 이는 스트림에서 특정 데이터만 걸러내는 메소드이다.

public class Application1 {

	public static void main(String[] args) {

		IntStream intStream = IntStream.range(0, 10); //0부터 9까지 스트림 생성
		intStream.filter(i -> (i % 2) == 0).forEach(i -> System.out.print(i + " "));
		//filter를 이용하여 조건을 준다. 
		//해당 조건에 일치하는 값만 출력할 수 있다.
		//출력: 0 2 4 6 8
	}
}

map()

map()은 스트림에 들어있는 데이터를 특정 람다식을 통해 데이터를 가공하고 새로운 스트림에 담아 주는 역할을 한다.

public class Application2 {

	public static void main(String[] args) {
	
		IntStream intStream = IntStream.range(1, 10); //1부터 9까지 스트림 생성
		intStream
			.filter(i -> i % 2 == 0) //짝수만 필터링
			.map(i -> i * 5) //map으로 각 요소에 접근 가능. 각 요소에 5씩 곱하겠다는 의미
			.forEach(result -> System.out.print(result + " ")); //스트림의 각 요소를 result에 할당하고 출력하겠다는 의미

	}
}

flatMap()

flatMap()은 중첩 구조를 한 단계 제거하고 단일 컬렉션으로 만들어준다. 이러한 작업을 플래트닝(flattening)이라고 한다.

public class Application3 {
	
	public static void main(String[] args) {

		List<List<String>> list = Arrays.asList( //리스트 안에 리스트가 들어가게 됨
					Arrays.asList("JAVA", "SPRING", "SPRINGBOOT"), //내부리스트1
					Arrays.asList("java", "spring", "springboot")	//내부리스트2
				);

		System.out.println("list = " + list);
		
		List<String> flatList = list.stream() //스트림 생성
									.flatMap(Collection::stream) //중첩된 내부 리스트를 하나의 리스트로 만들어줌
									.collect(Collectors.toList()); //그 결과 값을 다시 리스트 형태로 만들어줌
		
		System.out.println("flatList = " + flatList);
	}
}

sorted()

sorted()는 인자가 없이도 호출이 가능한데, 인자가 없으면 오름차순으로 자동 정렬된다.

public class Application4 {

	public static void main(String[] args) {

		List<Integer> integerList = IntStream.of(5, 10, 99, 2, 1, 35) //스트림 생성
	            .boxed() //박싱을 이용해 int를 Integer로 바꿔줌
	            .sorted() //스트림의 요소를 오름차순으로 정렬
	            .collect(Collectors.toList()); //결과 값을 리스트 형태로 반환

	    System.out.println("integerList = " + integerList); 
	}
}


스트림의 최종 연산 메소드

calculating

우리는 스트림에서 최소,최대,총합 등과 같은 연산을 메소드로 손쉽게 구할 수 있다.

public class Application1 {

	public static void main(String[] args) {

		long count = IntStream.range(1, 10).count(); //1~9까지 개수가 몇 개인지
	    long sum = IntStream.range(1, 10).sum(); //1~9까지 합이 몇 인지
	    
	    System.out.println("count : " + count);
	    System.out.println("sum : " + sum);

		//OptionalInt : 결과가 있을 수도 있고 없을 수도 있을 때 결과 값을 안전하게 받아올 수 있게 해준다.
	    OptionalInt max = IntStream.range(1, 10).max();
	    OptionalInt min = IntStream.range(1, 10).min();
	    
	    System.out.println("max : " + max);
	    System.out.println("min : " + min);

	    int oddSum = IntStream
			    		.range(1, 10) //1~9
			            .filter(i -> i % 2 == 1) //홀수만 필터링
			            .sum();  //필터링 된 결과를 모두 합해준다.
	    System.out.println("oddSum : " + oddSum);
	}
}

스트림을 생성하고 바로 count(), sum(), max(), min() 등의 메소드를 같이 호출해주면 원하는 연산 결과 값을 바로 구할 수 있다.

위 코드에서 OptionalInt 는 int값이 존재하는 경우와 존재하지 않는 경우를 명확하게 구분할 수 있게 해주며, 이를 통해 더 안전하게 코드를 작성할 수 있게 도와준다.


reduce()

reduce() 는 스트림에 있는 데이터들의 총합을 계산해준다.

public class Application2 {

	public static void main(String[] args) {
	
	    OptionalInt reduceOneParam = IntStream.range(1, 4)     // 1, 2, 3
			                                    .reduce((a, b) -> {
			                                        return Integer.sum(a, b);
			                                    });
			
	    System.out.println("reduceOneParam = " + reduceOneParam.getAsInt());
	}
}

reduce()는 누적연산을 해주게 되는데,

예를 들어 1,2,3 이 들어있을 때, a에는 1, b에는 2가 들어가 3이 나오게 된다.
이때 나온 3이 다시 a로 들어가고 b에는 스트림에 있던 3이 들어가며 결과 값이 6이 나온다.

이런식으로 누적하여 합을 계산해 준다.

위 코드에서 getAsInt()는 OptionalInt 클래스에서 제공하는 메소드로,

OptionalInt로 인스턴스를 생성하여 값을 받아왔기 때문에 만약 OptionalInt 객체가 값을 가지고 있는 경우에 getAsInt() 메소드를 사용하면 그 값을 반환 시켜주게 된다.


collect()

collect()는 Collector 타입을 받아서 처리하는데, 해당 메소드를 통해 컬렉션을 출력으로 받을 수 있다.

public class Application3 {

	public static void main(String[] args) {

		List<Member> memberList = Arrays.asList( //3개의 멤버리스트 생성
	            new Member("test01", "testName01"),
	            new Member("test02", "testName02"),
	            new Member("test03", "testName03")
	    );
	    
	    //getMemberName()은 Member 클래스에 정의한 메소드
	    List<String> collectorCollection = memberList
												.stream() //스트림 생성
                                                .map(Member::getMemberName) //Member 클래스에 있는 멤버 이름을 반환받는다
                                                .collect(Collectors.toList()); //그 결과를 리스트로 다시 반환받는다.

	    System.out.println("collectorCollection = " + collectorCollection);
	    
	    String str = memberList
				    	.stream() //스트림 생성
		                .map(Member::getMemberName)
		                .collect(Collectors.joining()); //joining(): 구분자 없이 하나로 이어붙이겠다.

	    System.out.println("str = " + str);
	    
	    String str2 = memberList
						.stream()
		                .map(Member::getMemberName)
		                .collect(Collectors.joining(" || ", "**", "**")); // 구분자를 주고, 앞과 뒤에 접두사,접미사를 각각 붙여주겠다는 의미

	    System.out.println("str2 = " + str2);
	}

위 코드에서 joining()은 결과 출력값을 구분자 없이 하나로 이어붙이는 메소드이다.

joining(" || ", "**", "**")) → 이렇게 매개변수를 넘겨줄 수도 있다.

구분자를 주고, 앞과 뒤에 접두사, 접미사를 각각 붙여주겠다는 의미이다.


match

match는 anyMatch(), allMatch(), noneMatch() 이렇게 3가지 종류가 있다.

결과로는 true, false 를 리턴한다.

  • anyMatch() : 하나라도 조건을 만족하는 값이 있는지
  • allMatch() : 모든 조건을 만족하는지
  • noneMatch() : 모든 조건을 만족하지 않는지
public class Application4 {

	public static void main(String[] args) {

		List<String> stringList = Arrays.asList("Java", "Spring", "SpringBoot");

	    boolean anyMatch = stringList.stream()
	                                .anyMatch(str -> str.contains("p")); //p를 포함하고 있는지
	    boolean allMatch = stringList.stream()
	                                .allMatch(str -> str.length() > 4); //문자열의 길이가 모두 4인지
	    boolean noneMatch = stringList.stream()
	                                .noneMatch(str -> str.contains("c")); //c를 포함하고 있는지

	    System.out.println("anyMatch = " + anyMatch); //true
	    System.out.println("allMatch = " + allMatch); //false
	    System.out.println("noneMatch = " + noneMatch); //true
	}
}
profile
하나씩 천천히

0개의 댓글