본문 바로가기
Languages/Java

[Java] 스트림 (stream) (1) 개념과 종류

by 이래지 2022. 1. 5.
728x90
스트림이란? 자바 8부터 추가된 컬렉션(배열 포함)의 저장 요소를 하나씩 참조해서 람다식(함수적 스타일)으로 처리할 수 있도록 해주는 반복자이다.

반복자 스트림


자바 7 이전까지는 List<String> 컬렉션에서 요소를 순차적으로 처리하기 위해 Iterator 반복자를 다음과 같이 사용해왔다.

List<String> list = Arrays.asList("홍길동", "신용권", "김자바");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
	String name = iterator.next();
    System.out.println(name);
}

위의 코드를 stream 을 사용하면 아래와 같이 바꿀 수 있다.

List<String> list = Arrays.asList("홍길동", "신용권", "김자바");
Stream<String> stream = list.stream();
stream.forEach( name -> System.out.println(name) );

컬렉션의 stream() 메소드로 스트림 객체를 얻고 나서 stream.forEach(name -> System.out.println(name)); 메소드를 통해 컬렉션의 요소를 하나씩 콘솔에 출력한다. forEach() 메소드는 다음과 같이 Consumer 함수적 인터페이스 타입의 매개값을 가지므로 컬렉션의 요소를 소비할 코드를 람다식으로 기술할 수 있다.

void forEach(Consumer<T> action)

 

 

스트림의 특징 


Stream은 Iterator와 비슷한 역할을 하는 반복자이지만, 람다식으로 요소 처리 코드를 제공하는 점내부 반복자를 사용하므로 병렬처리가 쉽다는 점 그리고 중간 처리와 최종 처리 작업을 수행하는 점이 차이가 있다.

 

  • 람다식으로 요소 처리 코드를 제공한다.
    Stream이 제공하는 대부분의 요소 처리 메소드는 함수적 인터페이스 매개 타입을 가지기 때문에 람다식 또는 메소드 참조를 이용해서 요소 처리 내용을 매개값으로 전달할 수 있다.
    //요소 처리를 위한 람다식
    Stream<Student> stream = list.stream(); //스트림 얻기
    stream.forEach( s -> { //List컬렉션에서 Student를 가져와 람다식의 매개값으로 제공
    	String name = s.getName();
        int score = s.getScore();
        System.out.println(name + "-" + score);
    });
  • 내부 반복자를 사용하므로 병렬 처리가 쉽다.
    외부 반복자(external iterator)란 개발자가 코드로 직접 컬렉션의 요소를 반복해서 가져오는 코드 패턴이다. index를 이용하는 for문, Iterator를 이용하는 while문 모두 외부 반복자에 속한다.
    내부 반복자(internal iterator)는 컬렉션 내부에서 요소들을 반복시키고, 개발자는 요소당 처리해야 할 코드만 제공하는 코드 패턴을 말한다.
    내부 반복자를 사용하면 얻는 이점 : 개발자는 요소 처리 코드에만 집중할 수 있고, 외부 반복자보다 효율적으로 요소를 반복시킨다.
    스트림은 람다식으로 요소 처리 내용만 전달할 뿐, 반복은 컬렉션 내부에서 일어난다. 무엇보다도 요소의 병렬 처리가 컬렉션 내부에서 처리되므로 효율적
    //순차 처리
    Stream<String> stream = list.stream();
    stream.forEach(ParallelExample :: print); //메소드 참조(s -> ParallelExample.print(s)와 동일)
    
    //병렬 처리
    Stream<String> stream = list.stream();
    parallelStream.forEach(ParallelExample :: print); //메소드 참조​
  • 스트림은 중간 처리와 최종 처리를 할 수 있다.
    스트림은 컬렉션의 요소에 대한 중간 처리와 최종 처리를 수행할 수 있다. 
    중간 처리에서는 매핑, 필터링, 정렬을 수행하고, 최종 처리에서는 반복, 카운팅, 평균, 총합 등의 집계 처리를 수행한다.
    //중간 처리와 최종처리
    //List에 저장되어 있는 Student 객체를 중간 처리에서 score의 필드값으로 매핑하고, 
    //최종처리에서 score평균값 산출
    
    public class MapAndReduceExample {
    	public static void main(String[] args) {
        	List<Student> studentList = Arrays.asList(
            	new Student("홍길동", 10),
                new Student("이래지", 20),
                new Student("주쿼카", 30)
            );
            
            double avg = studentList.stream()
            //중간처리(학생 객체를 점수로 매핑)
            .mapToInt(Student :: getScore)
            //최종처리(평균 점수)
            .average()
            .getAsDouble();
           System.out.println("평균 점수 : " + avg);
        }
    }​

 

스트림의 종류


모든 스트림에서 사용할 수 있는 공통 메소드들은 BaseStream에 정의되어 있으며, Stream은 객체 요소를 처리하는 스트림, IntStream, LongStream, DoubleStream은 각각 기본타입인 int, long, double 요소를 처리하는 스트림이다.

 

1. 컬렉션으로 부터 스트림 얻기

Stream<Student> stream = studentList.stream()
stream.forEach(s -> System.out.println(s.getName()))

 

 

2. 배열로부터 스트림 얻기

String[] strArray = {"가", "나", "다"};
Stream<String> stream = Arrays.stream(strArray); //문자열 
int[] intArray = {1, 2, 3};
IntStream intStream = Arrays.stream(intArray);

 

3. 숫자 범위로부터 스트림 얻기

IntStream stream = IntStream.rangeClosed(1, 100);
1부터 100까지 갖는 IntStream을 리턴한다.
rangedClosed(int startInclusive, int endExclusive)
=> 첫번째 매개값부터, 두번째 매개값까지 순차적으로 리턴하는 IntStream 리턴
range(int startInclusive, int endInclusive)
=> rangeClosed와 같지만 두 번째 매개값은 포함하지 않는다.

 

4. 파일로부터 스트림 얻기 

Files의 메소드인 lines(), BufferedReader의 메소드인 lines()를 이용하여 문자 파일의 내용을 스트림을 통해 읽는다.

public static void main(String[] args) throws IOException {
	//파일 내용을 소스로 하는 스트림
    Path path = Paths.get("src/stream/list.txt");
    
    //Files.lines() 메소드 이용
    Stream<String> stream = Files.lines(path, Charset.defaultCharset()); //운영체제 기본 문자셋
    stream.forEach(System.out :: println);
    System.out.println();
    
    //BufferedReader의 lines() 메소드 이용
    File file = path.toFile();
    FileReader fileReader = new FileReader(file);
    BufferedReader br = new BufferedReader(fileReader);
    stream = br.lines();
    stream.forEach(System.out :: println);
}

위 소스를 실행하면 list.txt안의 데이터가 두 번 console에 찍힌다.

 

5. 디렉토리로부터 스트림 얻기

Files의 메소드인 list()를 이용하여 디렉토리 내용을 스트림을 통해 읽는다.

public static void main(String[] args) throws IOException {
	Path path = Paths.get("c://");
    Stream<Path> stream = Files.list(path);
    stream.forEach(p -> System.out.println(p.getFileName()));
}

 

댓글