no image
[Java] 백준 11866번 문제(요세푸스 문제 0)
문제설명 소스코드 import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.LinkedList; import java.util.Queue; import java.util.StringTokenizer; public class Main { public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); int N = Integer.parseInt(st..
2023.08.08
no image
[Java] 백준 12789번 문제 (도키도키 간식드리미)
문제설명 소스코드 import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.LinkedList; import java.util.Queue; import java.util.Stack; import java.util.StringTokenizer; public class Main { public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int N = Integer.parseInt(br.readLine()); Stack stack = new S..
2023.08.08
no image
[Java] 바이트 & 문자 입출력 스트림
이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 입출력 스트림 자바는 입력 스트림과 출력 스트림을 통해 데이터를 입출력한다. 스트림은 단 방향으로 데이터가 흐르는 것을 말한다. 입력 스트림 : 입력 장치 -> 프로그램 출력 스트림 : 프로그램 -> 출력 장치 프로그램을 기준으로 데이터가 들어오면 입력스트림, 데이터가 나가면 출력 스트림이 된다. 프로그램이 다른 프로그램과 데이터를 교환하려면 양쪽 모두 입력 스트림과 출력 스트림이 필요하다. 어떤 데이터를 입출력하느냐에 따라 스트림은 아래의 두 종류로 구분할 수 있다. 바이트 스트림 : 그림, 멀티미디어, 문자 등 모든 종류의 데이터를 입출력할 때 사용 문자 스트림 : 문자만 입출력할..
2023.08.08
no image
[Java] 백준 9012번 문제 (괄호)
문제설명 소스코드 import java.util.Scanner; import java.util.Stack; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int T = sc.nextInt(); for(int i = 0; i < T; ++i) { boolean ans = func(sc.next()); //nextLine()이 아니라 next()를 사용 if(ans == true) System.out.println("YES"); else System.out.println("NO"); } } public static boolean func(String str) { Stack stac..
2023.08.07
no image
[Java] 스트림(정렬, 루핑, 매칭, 기본 집계, 커스텀 집계, 수집, 병렬 처리)
이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 요소 정렬 정렬은 요소를 오름차순 또는 내림차순으로 정렬하는 중간 처리 기능이다. 요소를 정렬하는 메소드는 아래와 같다. Comparable 구현 객체의 정렬 스트림의 요소가 객체일 경우 객체가 Comparable을 구현하고 있어야만 sorted() 메소드를 사용하여 정렬할 수 있다. 만약 내림차순으로 정렬하고 싶다면 Comparator.reverseOrder() 메소드가 리턴하는 Comparator를 매개값으로 제공하면 된다. 아래의 더보기를 누르면 예제 코드를 볼 수 있다. 더보기 Student.java public class Student implements Comparable ..
2023.08.07
no image
[Java] 백준 28278번 문제 (스택 2)
문제설명 소스코드 import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.Stack; import java.util.StringTokenizer; public class Main { public static void main(String[] args) throws Exception{ BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringBuilder sb = new StringBuilder(); Stack stack = new Stack(); //스택 생성 StringTokenizer st; int N = Integer.par..
2023.08.06
no image
[Java] 백준 10773번 문제 (제로)
문제설명 소스코드 import java.util.Stack; import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); Stack stack = new Stack(); //스택 생성 int K = sc.nextInt(); for(int i = 0; i < K; i++) { int input = sc.nextInt(); if(input == 0) stack.pop(); //0이면 팝 else stack.push(input); //0이 아니면 푸시 } int sum = 0; for(int i = 0; i < stack.size(); ++i) { s..
2023.08.06
no image
[Java] 스트림(스트림 개념, 스트림 얻기, 필터링, 매핑)
이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 스트림이란 Java 8부터 컬렉션 및 배열의 요소를 반복 처리하기 위해 스트림을 사용할 수 있다. 스트림은 요소들이 하나씩 흘러가면서 처리된다는 의미를 가지고 있다. Stream은 Iterator와 비슷한 반복자이지만, 아래와 같은 차이점을 가지고 있다. - 내부 반복자이므로 처리 속도가 빠르고 병렬 처리에 효율적이다. (내부에서 멀티 스레딩으로 처리) - 람다식으로 다양한 요소 처리를 정의할 수 있다. - 중간 처리와 최종 처리를 수행하도록 파이프 라인을 형성할 수 있다.(필터링 후 원하는 데이터만 추출 및 가공) List 컬렉션의 stream() 메소드로 Stream 객체를 얻고,..
2023.08.06

문제설명

 

 

소스코드

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;
public class Main {
	public static void main(String[] args) throws Exception
	{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		int N = Integer.parseInt(st.nextToken());
		int K = Integer.parseInt(st.nextToken());
		Queue<Integer> tmp = new LinkedList<>();
		Queue<Integer> ans = new LinkedList<>();
		for(int i = 1; i <= N; ++i) tmp.add(i);
		while(!tmp.isEmpty())
		{
			for(int i = 1; i < K; ++i) tmp.add(tmp.poll());
			ans.add(tmp.poll());
		}
		System.out.print("<");
		while(ans.size() > 1)System.out.print(ans.poll() + ", ");
		System.out.print(ans.poll() + ">");
	}
}

 

설명

핵심 코드는 아래의 코드이다.

for(int i = 1; i < K; ++i) tmp.add(tmp.poll());
ans.add(tmp.poll());

만약 K = 3이라면 1 2 3 4 5 6 7 중에서 맨 앞 두 개를 잘라내기 후 붙여넣는다. -> 3 4 5 6 7 1 2

이후 맨 앞의 3을 ans큐에 넣는다.

이 행동을 tmp 큐가 비워질 때 까지 반복한다.

tmp 1 2 3 4 5 6 7 ans null
4 5 6 7 1 2 3
7 1 2 4 5 3 6
4 5 7 1 3 6 2
1 4 5 3 6 2 7
1 4 3 6 2 7 5
1 3 6 2 7 5 1
null 3 6 2 7 5 1 4

 

그 다음 ans 큐의 모든 원소를 출력하면 된다.

문제설명

 

소스코드

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
import java.util.StringTokenizer;
public class Main {
	public static void main(String[] args) throws Exception
	{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int N = Integer.parseInt(br.readLine());
		Stack<Integer> stack = new Stack<>();
		Queue<Integer> queue = new LinkedList<>();
		StringTokenizer st = new StringTokenizer(br.readLine(), " ");
		for(int i = 0; i < N; ++i) queue.add(Integer.parseInt(st.nextToken()));
		int seq = 1;
		while(!queue.isEmpty()) //큐가 비어있지 않다면
		{
			if(queue.peek() == seq) //큐 맨앞과 순서가 맞다면
			{
				queue.poll(); //큐 맨앞 숫자를 제거
				++seq;
			}
			else if(!stack.isEmpty() && stack.lastElement() == seq) //스택 꼭대기와 순서가 맞다면
			{
				stack.pop(); //스택 꼭대기 숫자를 제거
				++seq;
			}
			else stack.push(queue.poll()); //스택 꼭대기와 순서가 다르다면, 큐 맨앞을 스택에 저장
		}
		while(!stack.isEmpty()) //스택이 비어있지 않다면
		{
			if(stack.lastElement() == seq) //스택 꼭대기와 순서가 맞다면
			{
				stack.pop(); //스택 꼭대기를 제거
				++seq;
			}
			else 
			{
				System.out.println("Sad");// 스택 꼭대기와 순서가 맞지 않다면 Sad
				System.exit(0);
			}
		}
		System.out.println("Nice");
	}
}

 

설명

  • 첫 번째 while문에서 큐의 맨 앞부분과, 스택의 꼭대기의 순서를 조사한다.
    순서가 맞다면 제거를 하고, 틀리다면 해당 큐를 스택에 넣는다.
    이런 반복을 큐가 empty상태가 될 때 까지 반복한다.
  • 두 번째 while문에서 스택의 꼭대기와 순서를 조사한다.
    순서가 맞다면 스택 꼭대기를 제거하고, 틀리다면 Sad를 출력한다.

이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 


입출력 스트림

자바는 입력 스트림과 출력 스트림을 통해 데이터를 입출력한다.

스트림은 단 방향으로 데이터가 흐르는 것을 말한다.

  • 입력 스트림 : 입력 장치 -> 프로그램
  • 출력 스트림 : 프로그램 -> 출력 장치

프로그램을 기준으로 데이터가 들어오면 입력스트림, 데이터가 나가면 출력 스트림이 된다.

프로그램이 다른 프로그램과 데이터를 교환하려면 양쪽 모두 입력 스트림과 출력 스트림이 필요하다.

 

어떤 데이터를 입출력하느냐에 따라 스트림은 아래의 두 종류로 구분할 수 있다.

  • 바이트 스트림 : 그림, 멀티미디어, 문자 등 모든 종류의 데이터를 입출력할 때 사용
  • 문자 스트림 : 문자만 입출력할 때 사용
두 스트림 모두 바이트를 전송한다.
또한 바이트 스트림도 문자를 입출력할 수 있지만 문자로 변환하는 과정이 추가적으로 필요하다.

 

자바는 데이터 입출력과 관련된 라이브러리를 java.io 패키지에서 제공하고 있다.

java.io 패키지는 바이트 스트림과 문자 스트림을 아래와 같이 이름으로 구분해서 제공한다.

출처 : 이것이 자바다 유튜브 동영상 강의

바이트 입출력 스트림의 최상위 클래스는 InputStream과 OutputStream이다.

이 클래스를 상속받는 자식 클래스에는 접미사로 InputStream과 OutputStream이 붙는다.

 

문자 입출력 스트림의 최상위 클래스는 Reader와 Writer이다.

이 클래스를 상속받는 하위 클래스에는 접미사로 Reader 또는 Writer가 붙는다.

 

예를 들어 이미지와 같은 바이너리 파일의 입출력 스트림 클래스는 FileInputStream과 FileOutputStream

예를 들어 텍스트 파일의 입출력 스트림 클래스는 FileReader와 FileWriter이다.

출처 : 이것이 자바다 유튜브 동영상 강의

 


 

바이트 출력 스트림

OutputStream은 바이트 출력 스트림의 최상위 클래스로 추상 클래스이다.

모든 바이트 출력 스트림 클래스는 이 OutputStream 클래스를 상속받아서 만들어진다.

출처 : 이것이 자바다 유튜브 동영상 강의

OutputStream 클래스에는 모든 바이트 출력 스트림이 기본적으로 가져야 할 메소드가 정의되어 있다.

출처 : 이것이 자바다 유튜브 동영상 강의

 

1 byte 출력

write(int b) 메소드는 매개값 int(4 byte)에서 끝 1 byte만 출력한다.

매개변수가 int 타입이므로 4 바이트를 모두 보내는 것은 아니다.

따라서 byte의 범위인 -128~127 이외의 수가 들어오면 끝의 한 바이트를 제외한 값은 날라간다.

출처 : 이것이 자바다 유튜브 동영상 강의

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class WriteExample {
	public static void main(String[] args) {
		try {
			OutputStream os = new FileOutputStream("C:/Temp/test1.db"); //폴더는 있어야하고, 파일은 없으면 생성함
				
           		 //1 바이트씩 출력(1 바이트씩 파일에 저장됨)
			byte a = 10;
			byte b = 20;
			byte c = 30;
			
			os.write(a);
			os.write(b);
			os.write(c);
			
			os.flush(); //내부 버퍼에 있는 바이트를 출력하고 버퍼를 비움
			os.close(); //출력 스트림을 닫음, 버퍼를 출력하고 비우는 기능도 있음
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

FileOutputStream 생성자는 주어진 파일을 생성할 수 없으면 IOException을 발생시킨다.

write(), flush(), close() 모두 예외처리를 해주어야 한다.

OutputStream은 내부에 작은 버퍼를 가지고 있다.
write() 메소드가 호출되면 버퍼에 바이트를 우선 저장하고, 버퍼가 차면 순서대로 바이트를 출력한다.
flush() 메소드는 내부 버퍼에 잔류하는 모든 바이트를 출력하고 버퍼를 비우는 역할을 한다.
출력 스트림을 더 이상 사용하지 않을 때에는 close() 메소드를 호출해서 출력 스트림이 사용했던 메모리를 해제해야 한다.
아니면 리소스 자동 닫기로 반드시 스트림을 닫아야 한다.

FileOutputStream은 두 번째 매개값에 true를 주면 새로운 내용을 뒤에 추가시키는 것이고(이어 쓰기), false를 주면 처음부터 다시 쓰게 된다.

 


 

바이트 배열 출력

보통 바이트 배열을 통째로 출력하는 경우가 많다.

write(byte[] b) 메소드는 매개값으로 주어진 배열의 모든 바이트를 출력한다.

출처 : 이것이 자바다 유튜브 동영상 강의

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class WriteExample {
	public static void main(String[] args) {
		try {
			OutputStream os = new FileOutputStream("C:/Temp/test2.db");

			byte[] array = { 10, 20, 30 };

			os.write(array); //배열의 모든 바이트를 출력

			os.flush(); //버퍼에 있는 바이트를 출력하고 버퍼를 비움
			os.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

 

만약 배열의 일부분을 출력하고 싶다면 write(byte[] b, int off, int len) 메소드를 사용하면 된다.

이 메소드는 b[off]부터 len개의 바이트를 출력한다.

출처 : 이것이 자바다 유튜브 동영상 강의

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
	
public class WriteExample {
	public static void main(String[] args) {
		try {
			OutputStream os = new FileOutputStream("C:/Temp/test3.db");

			byte[] array = { 10, 20, 30, 40, 50 };

			os.write(array, 1, 3);

			os.flush();
			os.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

 

 


 

바이트 입력 스트림

InputStream은 바이트 입력 스트림의 최상위 클래스로, 추상 클래스이다.

모든 바이트 입력 스트림은 InputStream 클래스를 상속받아 만들어진다.

출처 : 이것이 자바다 유튜브 동영상 강의

InputStream 클래스에는 바이트 입력 스트림이 기본적으로 가져야 할 메소드가 정의되어 있다.

출처 : 이것이 자바다 유튜브 동영상 강의

 

1 byte 읽기

read() 메소드는 입력 스트림으로부터 1 byte를 읽고 intI(4 byte) 타입으로 리턴한다.

따라서 4byte중 끝 1 byte에만 데이터가 들어 있다.

 

예를 들어 입력 스트림에서 5개의 바이트가 들어온다면 아래와 같이 read() 메소드로 1 바이트씩 5번 읽을 수 있다.

출처 : 이것이 자바다 유튜브 동영상 강의

 

더 이상 입력 스트림으로부터 바이트를 읽을 수 없다면 read() 메소드는 -1을 리턴한다.

 

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class ReadExample {
	public static void main(String[] args) {
		try {
			InputStream is = new FileInputStream("C:/Temp/test1.db");

			while(true) {
				int data = is.read(); //한 바이트씩 읽어옴
				if(data == -1) break; //-1을 받으면 탈출
				System.out.println(data);
			}
			
			is.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
/*
10
20
30
*/

FileInputStream 생성자는 주어진 파일이 존재하지 않을 경우 FileNotFoundException을 발생시킨다.

read(), close() 메소드에서 IOException이 발생할 수 있으므로 두 가지 예외를 모두 처리해야 한다.

 IOException의 자식 자식 클래스가 FileNotFoundException이므로 IOException만 예외 처리해 주어도 된다.

 


 

바이트 배열로 읽기

read(byte[] b) 메소드는 입력 스트림으로부터 주어진 배열의 길이만큼 바이트를 읽고 배열에 저장한 다음 읽은 바이트 수를 리턴한다.

예를 들어 입력 스트림에 5개의 바이트가 들어오면 아래와 같이 길이 3인 배열로 두 번 읽을 수 있다.

출처 : 이것이 자바다 유튜브 동영상 강의

read(byte[] b) 역시 입력 스트림으로부터 바이트를 더 이상 읽을 수 없다면 -1을 리턴한다.

 

많은 양의 바이트를 읽을 때는 read(byte[] b) 메소드를 사용하는 것이 좋다.

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class ReadExample {
	public static void main(String[] args) {
		try {
			InputStream is = new FileInputStream("C:/Temp/test2.db");
			
			byte[] data = new byte[100];

			while(true) {
				int num = is.read(data); //최대 100바이트를 읽고, 읽은 바이트는 data에 저장, 읽은 수는 리턴
				if(num == -1) break;

				for(int i=0; i<num; i++) {
					System.out.println(data[i]);
				}
			}

			is.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
/*
10
20
30
*/

 

파일 복사 예제

아래의 예제는 파일 복사이다.

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class CopyExample {
	public static void main(String[] args) throws Exception {
		String originalFileName = "C:/Temp/test.jpg"; //복사할 파일
		String targetFileName = "C:/Temp/test2.jpg"; //복사될 파일의 이름과 경로 지정
		
                //입출력 스트림 생성
		InputStream is = new FileInputStream(originalFileName);
		OutputStream os = new FileOutputStream(targetFileName);
		
		byte[] data = new byte[1024];
		while(true) {
			int num = is.read(data);
			if(num == -1) break;
			os.write(data, 0, num);
		}
			
		os.flush();
		os.close();
		is.close();
		
		System.out.println("복사가 잘 되었습니다.");
	}
}
/*
복사가 잘 되었습니다.
*/

test.jpg의 크기는 41.4KB(=42393.6 byte)이고, data 배열의 크기가 1024 이므로 for문을 42번 돌게 된다.
또한 0.4 byte(약 400 byte)는 num이 400정도가 되므로 os.write(data, 0, 400) 으로 처리된다.

복사가 잘 된 것을 확인할 수 있다.

 

Java 9부터 좀 더 편리하게 입력 스트림에서 출력 스트림으로 바이트를 복사하는 transferTo() 메소드가 InputStream에 추가되었다.
byte[] data = new byte[1024];
    while(true) {
        int num = is.read(data);
        if(num == -1) break;
        os.write(data, 0, num);
    }
//////위 코드를 아래의 한줄로 대체 가능
is.transferTo(os);​

 

 


 

문자 입출력 스트림

문자 입출력 스트림으로 Reader와 Writer가 있다.

입출력되는 단위가 문자인 것을 제외하고는 바이트 입출력 스트림과 사용 방법은 동일하다.

 

문자 출력

Writer는 문자 출력 스트림의 최상위 클래스로, 추상 클래스이다.

모든 문자 출력 스트림 클래스는 Writer 클래스를 상속받아서 만들어진다.

출처 : 이것이 자바다 유튜브 동영상 강의

 

Writer 클래스에는 모든 문자 출력 스트림이 기본적으로 가져야 할 메소드가 정의되어 있다.

출처 : 이것이 자바다 유튜브 동영상 강의

Writer는 OutputStream과 사용 방법은 동일하지만, 출력 단위가 문자(char)이다.

또한 문자열을 출력하는 writer(String str) 메소드를 추가로 제공한다.

 

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class WriteExample {
	public static void main(String[] args) {
		try {
			//문자 기반 출력 스트림 생성
			Writer writer = new FileWriter("C:/Temp/test.txt");

			//1 문자씩 출력
			char a = 'A';
			writer.write(a);
			char b = 'B';
			writer.write(b);

			//char 배열 출력
			char[] arr = { 'C', 'D', 'E' };
			writer.write(arr);
           		 //writer.write(arr, 0, arr.length); //위와 같은 코드

			//문자열 출력
			writer.write("FGH");
			
			//버퍼에 잔류하고 있는 문자들을 출력하고, 버퍼를 비움
			writer.flush();
			
			//출력 스트림을 닫고 메모리 해제
			writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

 


 

문자 읽기

Reader는 문자 입력 스트림의 최상위 클래스로, 추상 클래스이다.

모든 문자 입력 스트림 클래스는 Reader 클래스를 상속받아서 만들어진다.

출처 : 이것이 자바다 유튜브 동영상 강의

Reader 클래스에는 문자 입력 스트림이 기본적으로 가져야 할 메소드가 정의되어 있다.

출처 : 이것이 자바다 유튜브 동영상 강의

 

public class ReadExample {
	public static void main(String[] args) {
		try {
			Reader reader = null;

			//1 문자씩 읽기
			reader = new FileReader("C:/Temp/test.txt"); //텍스트 파일로부터 문자 입력 스트림 생성
			while(true) {
				int data = reader.read();
				if(data == -1) break;
				System.out.print((char)data);
			}
			reader.close();
			System.out.println();

			//문자 배열로 읽기
			reader = new FileReader("C:/Temp/test.txt"); //텍스트 파일로부터 문자 입력 스트림 생성
			char[] data = new char[100]; // 읽은 문자를 저장할 배열 생성
			while(true) {
				int num = reader.read(data); //읽은 문자는 배열에 저장, 읽은 문자 수는 리턴
				if(num == -1) break;
				for(int i=0; i<num; i++) { //읽은 문자 수만큼 출력
					System.out.print(data[i]);
				}
			}
			reader.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

문제설명

 

소스코드

import java.util.Scanner;
import java.util.Stack;
public class Main {
	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int T = sc.nextInt();
		for(int i = 0; i < T; ++i)
		{
			boolean ans = func(sc.next()); //nextLine()이 아니라 next()를 사용
			if(ans == true) System.out.println("YES");
			else System.out.println("NO");
		}
	}
	public static boolean func(String str)
	{
		Stack<Character> stack = new Stack<>();
		for (int i = 0; i < str.length(); ++i)
		{
			char tmp = str.charAt(i);
			if (tmp == '(') stack.push(tmp); //'(' 가 들어오면 push
			else if (stack.empty()) return false; //')' 가 들어오면 pop을 해야하는데 스택이 비었다면 VPS가 아니다.
			else stack.pop(); //')' 가 들어오면 pop
		}
		if (stack.empty()) return true; //'('의 수만큼 ')'의 수가 정상적으로 빠졌다면 스택은 비어있어야 한다.
		else return false;
	}
}

 

설명

  • 기본적으로 '('의 수와 ')'의 수가 같아야 한다.
  • 함수는 문자열을 검사하여 '('가 들어오면 푸시를하고, ')'가 들어오면 팝을 한다.
  • ')'가 들어와서 팝을 해야하는데, 스택이 비어있다면 VPS가 아니다.
  • 문자열을 모두 돌고 스택이 비어있다면 '('의 수와 ')'의 수가 같은 것이다.

이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 


요소 정렬

정렬은 요소를 오름차순 또는 내림차순으로 정렬하는 중간 처리 기능이다.

요소를 정렬하는 메소드는 아래와 같다.

출처 : 이것은 자바다 유튜브 동영상 강의

 

Comparable 구현 객체의 정렬

스트림의 요소가 객체일 경우 객체가 Comparable을 구현하고 있어야만 sorted() 메소드를 사용하여 정렬할 수 있다.

만약 내림차순으로 정렬하고 싶다면 Comparator.reverseOrder() 메소드가 리턴하는 Comparator를 매개값으로 제공하면 된다.

 

아래의 더보기를 누르면 예제 코드를 볼 수 있다.

더보기

Student.java

public class Student implements Comparable<Student> { //Comparable 구현 클래스
	private String name;
	private int score;

	public Student(String name, int score) {
		this.name = name;
		this.score = score;
	}

	public String getName() { return name; }
	public int getScore() { return score; }

	@Override
	public int compareTo(Student o) { //Comparable 인터페이스 메소드 구현
		return Integer.compare(score, o.score);
	}
}

 

SortingExample.java

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class SortingExample {
	public static void main(String[] args) {
		//List 컬렉션 생성
		List<Student> studentList = new ArrayList<>();
		studentList.add(new Student("홍길동", 30));
		studentList.add(new Student("신용권", 10));
		studentList.add(new Student("유미선", 20));
		
		//점수를 기준으로 오름차순으로 정렬한 새 스트림 얻기
		studentList.stream()
			.sorted( )
			.forEach(s -> System.out.println(s.getName() + ": " + s.getScore()));	
		System.out.println();
		
		//점수를 기준으로 내림차순으로 정렬한 새 스트림 얻기
		studentList.stream()
		.sorted(Comparator.reverseOrder())
		.forEach(s -> System.out.println(s.getName() + ": " + s.getScore()));	
	}
}
/*
신용권: 10
유미선: 20
홍길동: 30

홍길동: 30
유미선: 20
신용권: 10
*/

 


 

Comparator를 이용한 정렬

요소 객체가 Comparable을 구현하고 있지 않다면, 비교자를 제공하면 요소를 정렬시킬 수 있다.

비교자는 Comparator 인터페이스를 구현한 객체를 말하는데, 명시적인 클래스로 구현할 수도 있지만 간단하게 람다식으로 작성할 수도 있다.
sorted((o1, o2) -> { ... })

중괄호 안에는 o1이 o2보다 작으면 음수, 같으면 0, 크면 양수를 리턴하도록 작성하면 된다.

 

아래의 더보기를 누르면 예제 코드를 볼 수 있다.

더보기

Student.java

public class Student {
	private String name;
	private int score;

	public Student(String name, int score) {
		this.name = name;
		this.score = score;
	}

	public String getName() { return name; }
	public int getScore() { return score; }
}

 

SortingExample.java

import java.util.ArrayList;
import java.util.List;

public class SortingExample {
	public static void main(String[] args) {
		//List 컬렉션 생성
		List<Student> studentList = new ArrayList<>();
		studentList.add(new Student("홍길동", 30));
		studentList.add(new Student("신용권", 10));
		studentList.add(new Student("유미선", 20));
		
		//점수를 기준으로 오름차순으로 정렬한 새 스트림 얻기
		studentList.stream()
			.sorted((s1, s2) -> Integer.compare(s1.getScore(), s2.getScore()))
			.forEach(s -> System.out.println(s.getName() + ": " + s.getScore()));	
		System.out.println();
		
		//점수를 기준으로 내림차순으로 정렬한 새 스트림 얻기
        //매개값을 반대로 주면 내림차순 정렬
		studentList.stream()
			.sorted((s1, s2) -> Integer.compare(s2.getScore(), s1.getScore()))
			.forEach(s -> System.out.println(s.getName() + ": " + s.getScore()));	
	}
}
/*
신용권: 10
유미선: 20
홍길동: 30

홍길동: 30
유미선: 20
신용권: 10
*/

 


 

요소를 하나씩 처리(루핑)

루핑은 스트림에서 요소를 하나씩 반복해서 가져와 처리하는 것을 말한다.

루핑 메소드에는 peek()와 forEach()가 있다.

출처 : 이것은 자바다 유튜브 동영상 강의

 

peek()와 forEach()는 동일하게 요소를 루핑 하지만 peek()은 중간 처리 메소드이고, forEach()는 최종 처리 메소드이다.

따라서 peek()은 최종 처리가 뒤에 붙지 않으면 동작하지 않는다.

 

매개타입 Consumer는 함수형 인터페이스로, 아래와 같은 종류가 있다.

출처 : 이것은 자바다 유튜브 동영상 강의

모든 Consumer는 매개값을 처리(소비)하는 accept() 메소드를 가지고 있다.

 

Consumer<? super T>를 람다식으로 표현하면 아래와 같다.

T -> {...}
또는
T -> 실행문;

 

아래의 더보기를 누르면 예제 코드를 볼 수 있다.

더보기

LoopingExample.java

import java.util.Arrays;
	
public class LoopingExample {
	public static void main(String[] args) {
		int[] intArr = { 1, 2, 3, 4, 5 };

		//잘못 작성한 경우
		Arrays.stream(intArr)
			.filter(a -> a%2==0)
			.peek(n -> System.out.println(n)); //최종 처리가 없으므로 동작하지 않음

		//중간 처리 메소드 peek()을 이용해서 반복 처리
		int total = Arrays.stream(intArr)
			.filter(a -> a%2==0)
			.peek(n -> System.out.println(n)) //동작함
			.sum(); //최종 처리
		System.out.println("총합: " + total + "\n");

		//최종 처리 메소드 forEach()를 이용해서 반복 처리
		Arrays.stream(intArr)
			.filter(a -> a%2==0)
			.forEach(n -> System.out.println(n)); //최종 처리이므로 동작함
	}
}
/*
2
4
총합: 6

2
4
*/

 


 

요소 조건 만족 여부(매칭)

매칭은 요소들이 특정 조건에 만족하는지 여부를 조사하는 최종 처리 기능이다.

매칭과 관련된 메소드는 아래와 같다.

출처 : 이것은 자바다 유튜브 동영상 강의

allMatch(), anyMatch(), noneMatch() 메소드는 매개값으로 주어진 Predicate가 리턴하는 값에 따라 true 또는 false 를 리턴한다.

 

아래의 더보기를 누르면 예제 코드를 볼 수 있다.

더보기

MatchingExample.java

import java.util.Arrays;

public class MatchingExample {
	public static void main(String[] args) {
		int[] intArr = { 2, 4 ,6 };
		
		boolean result = Arrays.stream(intArr)
			.allMatch(a -> a%2==0);
		System.out.println("모두 2의 배수인가? " + result);
		
		result = Arrays.stream(intArr)
			.anyMatch(a -> a%3==0);
		System.out.println("하나라도 3의 배수가 있는가? " + result);
		
		result = Arrays.stream(intArr)
			.noneMatch(a -> a%3==0);
		System.out.println("3의 배수가 없는가?  " + result);
	}
}
/*
모두 2의 배수인가? true
하나라도 3의 배수가 있는가? true
3의 배수가 없는가?  false
*/

 


 

요소 기본 집계

집계는 최종 처리 기능으로 요소들을 처리해서 카운팅, 합계, 평균값, 최대값, 최소값등과 같이 하나의 값으로 산출하는 것을 말한다.

즉, 대량의 데이터를 가공해서 하나의 값으로 축소하는 리덕션(Reduction)이라고 볼 수 있다.

 

스트림이 제공하는 기본 집계

스트림은 카운팅, 최대, 최소, 평균, 합계 등을 처리하는 아래와 같은 최종 처리 메소드를 제공한다.

출처 : 이것은 자바다 유튜브 동영상 강의

집계 메소드가 리턴하는 OptionalXXX는 Optional, OptionalDouble, OptionalInt, OptionalLong 클래스를 말한다.

이들은 최종값을 저장하는 객체로 get(), getAsDouble(), getAsIntI(), getAsLong()을 호출하면 최종값을 얻을 수 있다.

 

아래의 더보기를 누르면 예제 코드를 볼 수 있다.

더보기

AggregateExample.java

import java.util.Arrays;

public class AggregateExample {
	public static void main(String[] args) {
		//정수 배열
		int[] arr = {1, 2, 3, 4, 5};

		//카운팅
		long count = Arrays.stream(arr)
				.filter(n -> n%2==0)
				.count();
		System.out.println("2의 배수 개수: " + count);

		//총합
		long sum = Arrays.stream(arr)
				.filter(n -> n%2==0)
				.sum();
		System.out.println("2의 배수의 합: " + sum);

		//평균
		double avg = Arrays.stream(arr)
				.filter(n -> n%2==0)
				.average()
				.getAsDouble();
		System.out.println("2의 배수의 평균: " + avg);

		//최대값
		int max = Arrays.stream(arr)
				.filter(n -> n%2==0)
				.max()
				.getAsInt();
		System.out.println("최대값: " + max);
		
		//최소값
		int min = Arrays.stream(arr)
				.filter(n -> n%2==0)
				.min()
				.getAsInt();
		System.out.println("최소값: " + min);
		
		//첫 번째 요소
		int first = Arrays.stream(arr)
				.filter(n -> n%3==0)
				.findFirst()
				.getAsInt();
		System.out.println("첫 번째 3의 배수: " + first);
	}
}
/*
2의 배수 개수: 2
2의 배수의 합: 6
2의 배수의 평균: 3.0
최대값: 4
최소값: 2
첫 번째 3의 배수: 3
*/

 


Optional 클래스

Optional, OptionalDouble, OptionalInt, OptionalLong 클래스는 단순히 집계값만 저장하는 것이 아니다.

집계값이 존재하지 않을 경우 디폴트 값을 설정하거나 집계값을 처리하는 Consumer를 등록할 수 있다.

 

아래는 Optional 클래스가 제공하는 메소드이다.

출처 : 이것은 자바다 유튜브 동영상 강의

 

컬렉션의 요소는 동적으로 추가되는 경우가 많다.

만약 컬렉션에서 요소가 존재하지 않으면 집계 값을 산출할 수 없으므로 예외가 발생한다.

하지만 위 표에 있는 메소드를 이용하면 예외 발생을 막을 수 있다.

 

예를 들어 평균을 구하는 average를 최종 처리에서 사용할 경우, 아래와 같이 3가지 방법으로 요소(집계값)가 없는 경우를 대비할 수 있다.

 

1. isPresent() 메소드가 true를 리턴할 때만 집계값을 얻는다.

OptionalDouble optional = list.stream()
    .mapToInt(Integer :: intValue)
    .average();
if(optional.isPresent()) {
    System.out.println("방법1_평균: " + optional.getAsDouble());
} else {
    System.out.println("방법1_평균: 0.0");
}

 

2. orElse() 메소드로 집계값이 없을 경우를 대비해서 디폴트 값을 정해놓는다.

double avg = list.stream()
    .mapToInt(Integer :: intValue)
    .average()
    .orElse(0.0);
System.out.println("방법2_평균: " + avg);

 

3. ifPresent() 메소드로 집계값이 있을 경우에만 동작하는 Consumer 람다식을 제공한다.

stream
    .average()
    .ifPresent(a -> System.out.println("방법3_평균: " + a));
    //집계값이 없다면 average() 메소드를 실행하지 않음

 

아래의 더보기를 누르면 예제 코드를 볼 수 있다.

더보기

OptionalExample.java

import java.util.ArrayList;
import java.util.List;
import java.util.OptionalDouble;

public class OptionalExample {
	public static void main(String[] args) {
		List<Integer> list = new ArrayList< >();

		/*//예외 발생(java.util.NoSuchElementException)
 		double avg = list.stream()
 			.mapToInt(Integer :: intValue)
 			.average()
 			.getAsDouble();
		*/

		//방법1
		OptionalDouble optional = list.stream()
			.mapToInt(Integer :: intValue)
			.average();
		if(optional.isPresent()) {
			System.out.println("방법1_평균: " + optional.getAsDouble());
		} else {
			System.out.println("방법1_평균: 0.0");
		}

		//방법2
		double avg = list.stream()
			.mapToInt(Integer :: intValue)
			.average()
			.orElse(0.0);
		System.out.println("방법2_평균: " + avg);
		
		//방법3
		list.stream()
			.mapToInt(Integer :: intValue)
			.average()
			.ifPresent(a -> System.out.println("방법3_평균: " + a));
	}
}
/*
방법1_평균: 0.0
방법2_평균: 0.0
*/

 


 

요소 커스텀 집계

스트림은 기본 집계 메소드 외에 다양한 집계 결과물을 만들 수 있도록 reduce() 메소드도 제공한다.

출처 : 이것은 자바다 유튜브 동영상 강의

 

매개값은 BinaryOperator는 함수형 인터페이스이다.

BinaryOperator는 두 개의 매개값을 받아 하나의 값을 리턴하는 apply() 메소드를 가지고 있기 때문에 람다식을 작성할 수 있다.

스트림 요소 중 두 개를 받아서 모든 객체를 차례대로 처리한다.

 

reduce() 는 스트림에 요소가 없을 경우 예외가 발생하지만, identity 매개값이 주어지면 이 값을 디폴트 값으로 리턴한다.

identity 매개값을 넣어주면 해당 타입을 리턴하기 때문에, get() 관련 메소드를 따로 호출할 필요가 없다.

 

아래의 더보기를 누르면 예제 코드를 볼 수 있다.

더보기

Student.java

public class Student {
	private String name;
	private int score;

	public Student(String name, int score) {
		this.name = name;
		this.score = score;
	}

	public String getName() { return name; }
	public int getScore() { return score; }
}

 

ReductionExample.java

import java.util.Arrays;
import java.util.List;

public class ReductionExample {
	public static void main(String[] args) {
		List<Student> studentList = Arrays.asList(
            new Student("홍길동", 92),
            new Student("신용권", 95),
            new Student("감자바", 88)
		);		
		//방법1
		int sum1 = studentList.stream()
                .mapToInt(Student :: getScore)
                .sum();		
		//방법2
		int sum2 = studentList.stream()
                .map(Student :: getScore)
                .reduce(0, (a, b) -> a+b);
		
		System.out.println("sum1: " + sum1);
		System.out.println("sum2: " + sum2);
	}
}
/*
sum1: 275
sum2: 275
*/

 

 


 

요소 수집

스트림은 요소들을 필터링 또는 매핑한 후 요소들을 수집하는 최종 처리 메소드인 collect()를 제공한다.

이 메소드를 이용하면 필요한 요소만 컬렉션에 담을 수 있고, 요소들을 그룹핑한 후에 집계도 할 수 있다.

 

필터링한 요소 수집

Stream의 collect(Collector<T, A, R> collector) 메소드는 필터링 또는 매핑된 요소들을 새로운 컬렉션에 수집하고, 이 컬렉션을 리턴한다.

매개값인 Collector는 어떤 요소를 어떤 컬렉션에 수집할 것인지를 결정한다.

리턴 타입 메소드(매개변수) 인터페이스
R collect(Collector<T, A, R> collector) Stream

 

타입 파라미터

  • T : 요소
  • A : accumulator (누적기)
  • R : 요소가 저장될 컬렉션

풀어서 해석하면 T 요소를 A 누적기가 R에 저장한다는 의미이다.

List, Set, Map 등이 R에 들어가면 별도의 A를 제공할 필요가 없다. 

 

Collector의 구현 객체는 아래와 같이 Collectors 클래스의 정적 메소드로 얻을 수 있다.

출처 : 이것은 자바다 유튜브 동영상 강의

 

아래는 Student 스트림에서 남학생만 필터링해서 별도의 List로 생성하는 코드이다.

List<Student> maleList = totalList.stream()
            .filter(s->s.getSex().equals("남"))
            .toList();

 

아래는 Student 스트림에서 이름을 키로, 점수를 값으로 갖는 Map 컬렉션을 생성하는 코드이다.

Map<String, Integer> map = totalList.stream()
            .collect(
                Collectors.toMap(
                    s -> s.getName(), //Student 객체에서 키가 될 부분 리턴
                    s -> s.getScore() //Student 객체에서 값이 될 부분 리턴
                )
        );

 

Java 16부터는 좀 더 편리하게 요소 스트림에서 List 컬렉션을 얻을 수 있다.

스트림에서 바로 toList() 메소드를 아래와 같이 사용하면 된다.

List<Student> maleList = totalList.stream()
            .filter(s->s.getSex().equals("남"))
            .toList();

 

아래의 더보기를 누르면 예제 코드를 볼 수 있다.

더보기

Student.java

public class Student {
	private String name;
	private String sex;
	private int score;

	public Student(String name, String sex, int score) {
		this.name = name;
		this.sex = sex;
		this.score = score;
	}

	public String getName() { return name; }
	public String getSex() { return sex; }
	public int getScore() { return score; }
}

 

CollectExample.java

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
	
public class CollectExample {
	public static void main(String[] args) {
		List<Student> totalList = new ArrayList< >();
		totalList.add(new Student("홍길동", "남", 92));
		totalList.add(new Student("김수영", "여", 87));
		totalList.add(new Student("감자바", "남", 95));
		totalList.add(new Student("오해영", "여", 93));
		
		//남학생만 묶어 List 생성
		/*List<Student> maleList = totalList.stream()
		 		.filter(s->s.getSex().equals("남"))
		 		.collect(Collectors.toList());*/
			
		List<Student> maleList = totalList.stream()
				.filter(s->s.getSex().equals("남"))
				.toList();
		
		maleList.stream()
			.forEach(s -> System.out.println(s.getName()));
		
		System.out.println();
		
		//학생 이름을 키, 학생의 점수를 값으로 갖는 Map 생성
		Map<String, Integer> map = totalList.stream()
				.collect(
					Collectors.toMap(
						s -> s.getName(), //Student 객체에서 키가 될 부분 리턴
						s -> s.getScore() //Student 객체에서 값이 될 부분 리턴
					)
			);
			
		System.out.println(map);
	}
}
/*
홍길동
감자바

{오해영=93, 홍길동=92, 감자바=95, 김수영=87}
*/

 


 

요소 그룹핑

Collect() 메소드는 단순히 요소를 수집하는 기능 이외에 컬렉션의 요소들을 그룹핑해서 Map 객체를 생성하는 기능도 제공한다.

Collectors.groupingBy() 메소드에서 얻은 Collector를 collect() 메소드를 호출할 때 제공하면 된다.

리턴 타입 메소드
Collector<T, ?, Map<K, List<T>>> groupingBy(Function<T, K> classifier)

 

groupingBy()는 Function을 이용해서 T, K로 매핑하고, K를 키로해서 List<T>를 값으로 갖는 Map 컬렉션을 생성한다.

 

아래는 남과 여를 키로 설정하고 List<Student>를 값으로 갖는 Map을 생성하는 코드이다.

Map<String, List<Student>> map = totalList.stream()
            .collect(
                Collectors.groupingBy( s -> s.getSex() ) //그룹핑 키 리턴
            );

 

 

아래의 더보기를 누르면 예제 코드를 볼 수 있다.

더보기

Student.java

public class Student {
	private String name;
	private String sex;
	private int score;

	public Student(String name, String sex, int score) {
		this.name = name;
		this.sex = sex;
		this.score = score;
	}

	public String getName() { return name; }
	public String getSex() { return sex; }
	public int getScore() { return score; }
}

 

CollectExample.java

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class CollectExample {
	public static void main(String[] args) {
		List<Student> totalList = new ArrayList< >();
		totalList.add(new Student("홍길동", "남", 92));
		totalList.add(new Student("김수영", "여", 87));
		totalList.add(new Student("감자바", "남", 95));
		totalList.add(new Student("오해영", "여", 93));
		
		Map<String, List<Student>> map = totalList.stream()
			.collect(
				Collectors.groupingBy(s -> s.getSex())
			);
			
		List<Student> maleList = map.get("남");
		maleList.stream().forEach(s -> System.out.println(s.getName()));
		System.out.println();
		
		List<Student> femaleList = map.get("여");
		femaleList.stream().forEach(s -> System.out.println(s.getName()));
	}
}
/*
홍길동
감자바

김수영
오해영
*/

 

Collectors.groupingBy() 메소드는 그룹핑 후 매핑 및 집계 (평균, 카운팅, 연결, 최대, 최소, 합계)를 수행할 수 있도록 두 번째 매개값인 Collector를 가질 수 있다.

 

아래는 두 번째 매개값으로 사용될 Collector를 얻을 수 있는 Collectors의 정적 메소드이다.

출처 : 이것은 자바다 유튜브 동영상 강의

 

아래는 학생들을 성별로 그룹핑하고 각각의 평균 점수를 구해서 Map으로 얻는 코드이다.

Map<String, Double> map = totalList.stream()
        .collect(
            Collectors.groupingBy(
                s -> s.getSex(),
                Collectors.averagingDouble(s->s.getScore())
            )
        );

 

아래의 더보기를 누르면 예제 코드를 볼 수 있다.

더보기

Student.java

public class Student {
	private String name;
	private String sex;
	private int score;

	public Student(String name, String sex, int score) {
		this.name = name;
		this.sex = sex;
		this.score = score;
	}

	public String getName() { return name; }
	public String getSex() { return sex; }
	public int getScore() { return score; }
}

 

CollectExample.java

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class CollectExample {
	public static void main(String[] args) {
		List<Student> totalList = new ArrayList<>();
		totalList.add(new Student("홍길동", "남", 92));
		totalList.add(new Student("김수영", "여", 87));
		totalList.add(new Student("감자바", "남", 95));
		totalList.add(new Student("오해영", "여", 93));
		
		Map<String, Double> map = totalList.stream()
				.collect(
					Collectors.groupingBy(
						s -> s.getSex(),
						Collectors.averagingDouble(s->s.getScore())
					)
				);
		
		System.out.println(map);
	}
}
/*
{남=93.5, 여=90.0}
*/

 

 


 

요소 병렬 처리

요소 병렬 처리란 멀티 코어 CPU 환경에서 전체 요소를 분할해서 각각의 코어가 병렬적으로 처리하는 것을 말한다.

요소 병렬 처리의 목적은 작업 처리 시간을 줄이는 것에 있다.

자바는 요소 병렬 처리를 위해 병렬 스트림을 제공한다.

 

동시성과 병렬성

멀티 스레드는 동시성 또는 병렬성으로 실행된다.

  • 동시성 : 멀티 작업을 위해 멀티 스레드가 하나의 코어에서 번갈아 가며 실행
  • 병렬성 : 멀티 작업을 위해 멀티 코어를 각각 이용해서 병렬로 실행

출처 : 이것은 자바다 유튜브 동영상 강의

동시성은 한 시점에 하나의 작업만 실행한다.

병렬성은 한 시점에 여러 개의 작업을 병렬로 실행하기 때문에 동시성보다는 좋은 성능을 낸다.

 

병렬성은 아래와 같이 두 가지로 나뉜다.

  • 데이터 병렬성
  • 작업 병렬성

 

데이터 병렬성
데이터 병렬성은 전체 데이터를 분할해서 서브 데이터셋으로 만들고 이 서브 데이터셋들을 병렬 처리해서 작업을 빨리 끝내는 것을 말한다.
서브 데이터셋은 멀티 코어의 수만큼 쪼개어 각각의 데이터들을 분리된 스레드에서 병렬처리 한다.
자바 병렬 스트림은 데이터 병렬성을 구현한 것이다.

 

작업 병렬성
작업 병렬성은 서로 다른 작업을 병렬 처리하는 것을 말한다.
작업 병렬성의 대표적인 예는 서버 프로그램이다.
서버는 각각의 클라이언트에서 요청한 내용을 개별 스레드에서 병렬로 처리한다.

 


 

포크조인 프레임워크

자바 병렬 스트림은 요소들을 병렬처리하기 위해 포크 조인 프레임워크를 사용한다.

포크조인 프레임워크는 포크 단계에서 전체 요소들을 서브 요소셋으로 분할하고, 각각의 서브 요소셋을 멀티 코어에서 병렬로 처리한다.

조인 단계에서는 서브 결과를 결합해서 최종 결과를 만들어낸다.

 

예를 들어 쿼드 코어 CPU에서 병렬 스트림으로 요소들을 처리할 경우 먼저 포크 단계에서 스트림의 전체 요소들을 4개의 서브 요소셋으로 분할한다.

그리고 각각의 서브 요소셋을 개별 코어에서 처리하고, 조인 단계에서는 3번의 결합 과정을 거쳐 최종 결과를 산출한다.

출처 : 이것은 자바다 유튜브 동영상 강의

병렬 처리 스트림은 포크 단계에서 요소를 순서대로 분할하지 않는다.

이해하기 쉽도록 위 그림에서는 앞에서부터 차례대로 4등분 했지만, 내부적으로 요소들을 나누는 알고리즘이 있다.

 

포크조인 프레임워크는 병렬 처리를 위해 스레드풀을 사용한다. 각각의 코어에서 서브 요소셋을 처리하는 것은 작업 스레드가 해야 하므로 스레드 관리가 필요하다.

 

포크조인 프레임워크는 ExecutorService의 구현 객체인 ForkJoinPool을 사용해서 작업 스레드를 관리한다.


병렬 스트림 사용

자바 병렬 스트림을 이용할 경우에는 백그라운드에서 포크조인 프레임워크가 사용되기 때문에 개발자는 매우 쉽게 병렬 처리를 할 수 있다.

 

병렬 스트림은 아래의 두 가지 메소드로 얻을 수 있다.

출처 : 이것은 자바다 유튜브 동영상 강의

parallelStream() 메소드는 컬렉션(List, Set)으로부터 병렬 스트림을 바로 리턴한다.

parallel()메소드는 기존 스트림을 병렬 처리 스트림으로 변환한다.

 

아래의 예제는 1억개의 점수에 대한 평균을 얻을 때 일반 스트림과 병렬 스트림의 처리 시간을 측정한 예제이다.

더보기를 누르면 볼 수 있다.

더보기

ParallelExample.java

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.Stream;

public class ParallelExample {
	public static void main(String[] args) {
		Random random = new Random();

		List<Integer> scores = new ArrayList< >();
		for(int i=0; i<100000000; i++) {
			scores.add(random.nextInt(101));
		}

		double avg = 0.0;
		long startTime = 0;
		long endTime = 0;
		long time = 0;

		Stream<Integer> stream = scores.stream();
		startTime = System.nanoTime();
		avg = stream
				.mapToInt(i -> i.intValue())
				.average()
				.getAsDouble();
		endTime = System.nanoTime();
		time = endTime - startTime;
		System.out.println("avg: " + avg + ", 일반 스트림 처리 시간: " + time + "ns");
		
		Stream<Integer> parallelStream = scores.parallelStream();
		startTime = System.nanoTime();
		avg = parallelStream
				.mapToInt(i -> i.intValue())
				.average()
				.getAsDouble();
		endTime = System.nanoTime();
		time = endTime - startTime;
		System.out.println("avg: " + avg + ", 병렬 스트림 처리 시간: " + time + "ns");
	}
}
/*
avg: 49.99819842, 일반 스트림 처리 시간: 82815600ns
avg: 49.99819842, 병렬 스트림 처리 시간: 45988500ns
*/

 


 

병렬 처리 성능

병령 처리가 순차 처리보다 항상 실행 성능이 좋지는 않다.

병렬 처리에 영향을 미치는 아래의 3가지 요인을 잘 살펴보아야 한다.

 

요소의 수와 요소당 처리 시간

  • 컬렉션에 전체 요소의 수가 적고 요소당 처리 시간이 짧으면 일반 스트림이 병렬 스트림보다 빠를 수 있다.
  • 병렬 처리는 포크 및 조인 단계가 있고, 스레드 풀을 생성하는 추가적인 시간이 발생하기 때문이다.

 

스트림 소스의 종류

  • ArrayList와 배열은 인덱스로 요소를 관리하기 때문에 포크 단계에서 요소를 쉽게 분리할 수 있어 병렬 처리 시간이 절약된다.
  • 반면에 HashSet, TreeSet은 요소 분리가 쉽지 않고, LinkedList 또한 링크를 따라가야 하므로 요소 분리가 쉽지 않다.
  • 따라서 이 소스들은 상대적으로 병렬 처리가 늦다.

 

코어의 수

  • CPU의 코어의 수가 많으면 많을수록 병렬 스트림의 성능은 좋아진다.
  • 하지만 코어의 수가 적을 경우에는 일반 스트림이 더 빠를 수 있다.
  • 병렬 스트림은 스레드 수가 증가하여 동시성이 많이 일어나므로 오히려 느려진다.

문제설명

 

소스코드

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Stack;
import java.util.StringTokenizer;
public class Main {
	public static void main(String[] args) throws Exception{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringBuilder sb = new StringBuilder();
		Stack<Integer> stack = new Stack<Integer>(); //스택 생성
		StringTokenizer st;
		int N = Integer.parseInt(br.readLine());
		for(int i = 0; i < N; i++)
		{
			st = new StringTokenizer(br.readLine());
			String command = st.nextToken();
			if(command.equals("1"))stack.push(Integer.parseInt(st.nextToken()));
			else if(command.equals("2"))
			{
				if(!stack.isEmpty())
				{
					sb.append(stack.lastElement()).append("\n");
					stack.pop();
				}
				else sb.append(-1).append("\n");
			}
			else if(command.equals("3")) sb.append(stack.size()).append("\n");
			else if(command.equals("4")) 
				if(stack.isEmpty()) sb.append(1).append("\n");
				else sb.append(0).append("\n");
			else if(command.equals("5"))
				if(stack.isEmpty())sb.append(-1).append("\n");
				else sb.append(stack.lastElement()).append("\n");
		}
		br.close();
		System.out.println(sb);
	}
}

 

설명

  • 컬렉션 프레임워크인 Stack를 사용하였다.
  • Scanner를 사용하면 시간초과가 발생하므로 BufferReader를 사용한다.
    System.out.println() 보다는 StringBuilder를 사용한다.

문제설명

 

소스코드

import java.util.Stack;
import java.util.Scanner;
public class Main {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		Stack<Integer> stack = new Stack<Integer>(); //스택 생성
		int K = sc.nextInt();
		for(int i = 0; i < K; i++) {
			int input = sc.nextInt();
			if(input == 0) stack.pop(); //0이면 팝
			else stack.push(input); //0이 아니면 푸시
		}
		int sum = 0;
		for(int i = 0; i < stack.size(); ++i) {
			sum += stack.elementAt(i); //스택의 모든 원소를 더함
		}
		System.out.println(sum);		
	}
}

 

설명

  • 컬렉션 프레임워크의 Stack을 사용하였다.

이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 


스트림이란

Java 8부터 컬렉션 및 배열의 요소를 반복 처리하기 위해 스트림을 사용할 수 있다.

스트림은 요소들이 하나씩 흘러가면서 처리된다는 의미를 가지고 있다.

 

Stream은 Iterator와 비슷한 반복자이지만, 아래와 같은 차이점을 가지고 있다.

- 내부 반복자이므로 처리 속도가 빠르고 병렬 처리에 효율적이다. (내부에서 멀티 스레딩으로 처리)
- 람다식으로 다양한 요소 처리를 정의할 수 있다.
- 중간 처리와 최종 처리를 수행하도록 파이프 라인을 형성할 수 있다.(필터링 후 원하는 데이터만 추출 및 가공)

 

List 컬렉션의 stream() 메소드로 Stream 객체를 얻고, forEach() 메소드로 요소를 어떻게 처리할지를 람다식으로 제공한다.

import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;

public class StreamExample {
	public static void main(String[] args) {
		//Set 컬렉션 생성
		Set<String> set = new HashSet< >();
		set.add("홍길동");
		set.add("신용권");
		set.add("감자바");

		//Stream을 이용한 요소 반복 처리
		Stream<String> stream = set.stream();
		stream.forEach( name -> System.out.println(name) ); //forEach()에 람다식 제공
	}
}
/*
홍길동
신용권
감자바
*/

 


 

내부 반복자

  • 외부 반복자 : for문과 Iterator는 컬렉션의 요소를 컬렉션 바깥쪽으로 반복해서 가져와 처리한다.
  • 내부 반복자 : 스트림은 요소 처리 방법을 컬렉션 내부로 주입 시켜서 요소를 반복 처리한다.

출처 : 이것이 자바다 유튜브 동영상 강의

내부 반복자는 멀티 코어 CPU를 최대한 활용하기 위해 요소들을 분배시켜 병렬 작업을 할 수 있다.

하나씩 처리하는 순차적 외부 반복자보다는 효율적으로 요소를 반복시킬 수 있는 장점이 있다.

 

내부 반복자는 멀티 스레딩 환경에서도 공유 자원을 안전하게 처리한다.
예를 들어, 공유 객체가 1000개이고 내부 반복자가 스레드를 2개 생성했다면, 각각 500개씩 분담 해서 처리한다. 

 

아래의 예제는 List 컬렉션의 내부 반복자를 이용해서 병렬 처리하는 방법을 보여준다.

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
	
public class ParallelStreamExample {
	public static void main(String[] args) {
		//List 컬렉션 생성
		List<String> list = new ArrayList< >();
		list.add("홍길동");
		list.add("신용권");
		list.add("감자바");
		list.add("람다식");
		list.add("박병렬");

		//병렬 처리
		Stream<String> parallelStream = list.parallelStream(); //병렬 스트림 얻기
		parallelStream.forEach( name -> {
			System.out.println(name + ": " + Thread.currentThread().getName()); //처리하는 스레드 이름 출력
		} );
	}
}
/*
감자바: main
박병렬: main
람다식: main
홍길동: ForkJoinPool.commonPool-worker-2
신용권: ForkJoinPool.commonPool-worker-1
*/

 

 


 

중간 처리와 최종 처리

 

스트림은 하나 이상 연결될 수 있다.

아래의 그림을 보면 컬렉션의 오리지널 스트림 뒤에 필터링 중간 스트림이 연결 되고, 그 뒤에 매핑 중간 스트림이 연결된다.

이와 같이 스트림이 연결되어 있는 것을 스트림 파이프 라인이라고 한다.

출처 : 이것이 자바다 유튜브 동영상 강의

  • 중간 처리 과정 : 최종 처리를 위해 요소를 필터링 하거나 요소를 변환(매핑), 정렬 하는 작업을 수행
  • 최종 처리 과정 : 중간 처리에서 정제된 요소들을 반복하거나, 집계(카운팅, 총합, 평균 등) 작업을 한다.

 

아래의 그림은 이름과 점수를 가지는 Studet 객체 스트림에서 중간 처리를 통해 score 스트림으로 변환한 후 최종 집계 처리로 score 평균을 구하는 과정을 나타낸다.

출처 : 이것이 자바다 유튜브 동영상 강의

//방법 1
Stream<Student> studentStream = list.stream();

//중간 처리(학생 객체를 점수로 매핑)
IntStream scoreStream = studentStream.mapToInt(student -> student.getScore());

//최종 처리(평균 점수)
double avg = scoreStream.average().getAsDouble();

 

//방법 2 (메소드 체이닝)
double avg = list.stream()
            .mapToInt(student -> student.getScore()) //매핑
            .average() //평균(최종 처리)
            .getAsDouble(); //더블 타입으로 변환 (최종 처리)

 

파이프라인의 맨 끝에는 반드시 최종 처리 부분이 있어야 한다.
최종 처리가 없다면 오리지널 및 중간 처리 스트림은 동작하지 않는다.
최종 처리가 된 스트림은 재사용이 안된다. 따라서 해당 스트림에 추가 작업이 필요하면 스트림을 새로 생성해야 한다.

 

자세한 예제는 아래의 더보기를 클릭하면 볼 수 있다.

더보기
public class Student {
	private String name;
	private int score;
	
	public Student (String name, int score) {
		this.name = name;
		this.score = score;
	}

	public String getName() { return name; }
	public int getScore() { return score; }
}

 

import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class StreamPipeLineExample {
	public static void main(String[] args) {
		List<Student> list = Arrays.asList(
				new Student("홍길동", 10),
				new Student("신용권", 20),
				new Student("유미선", 30)
				);

		//방법1
 		Stream<Student> studentStream = list.stream();
 		//중간 처리(학생 객체를 점수로 매핑)
 		IntStream scoreStream = studentStream.mapToInt(student -> student.getScore());
		//최종 처리(평균 점수)
 		double avg = scoreStream.average().getAsDouble();
 		System.out.println("평균 점수: " + avg);

		//방법2
		avg = list.stream()
				.mapToInt(student -> student.getScore()) //중간 처리(학생 객체를 점수로 매핑)
				.average() //최종 처리(평균 점수)
				.getAsDouble();
		System.out.println("평균 점수: " + avg);
		
	}
}
/*
평균 점수: 20.0
평균 점수: 20.0
*/

 

 


 

리소스로부터 스트림 얻기

java.util.stream 패키지에는 스트림 인터페이스들이 있다.

BaseStream 인터페이스를 부모로 한 자식 인터페이스들은 아래와 같은 상속 관계를 이루고 있다.

출처 : 이것이 자바다 유튜브 동영상 강의

이 스트림 인터페이스들의 구현 객체는 다양한 리소스로부터 얻을 수 있다.

주로 컬렉션과 배열에서 얻지만, 아래와 같은 리소스로부터 스트림 구현 객체를 얻을 수도 있다.

 

출처 : 이것이 자바다 유튜브 동영상 강의

 

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

java.util.Collection 인터페이스는 스트림과 parallelStream() 메소드를 가지고 있기 때문에 자식 인터페이스인 List와 Set 인터페이스를 구현한 모든 컬렉션에서 객체 스트림을 얻을 수 있다.

 

Product.java

public class Product {
	private int pno;
	private String name;
	private String company;
	private int price;
	
	public Product(int pno, String name, String company, int price) {
		this.pno = pno;
		this.name = name;
		this.company = company;
		this.price = price;
	}

	public int getPno() { return pno; }
	public String getName() { return name; }
	public String getCompany() { return company; }
	public int getPrice() { return price; }
	
	@Override
	public String toString() {
		return new StringBuilder()
				.append("{")
				.append("pno:" + pno + ", ")
				.append("name:" + name + ", ")
				.append("company:" + company + ", ")
				.append("price:" + price)
				.append("}")
				.toString();
	}
}

 

StreamExample.java

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamExample {
	public static void main(String[] args) {
		//List 컬렉션 생성
		List<Product> list = new ArrayList<>();
		for(int i=1; i<=5; i++) {
			Product product = new Product(i, "상품"+i, "멋진회사", (int)(10000*Math.random()));
			list.add(product);
		}
		
		//객체 스트림 얻기
		Stream<Product> stream = list.stream();
		stream.forEach(p -> System.out.println(p));
	}
}
/*
{pno:1, name:상품1, company:멋진회사, price:3764}
{pno:2, name:상품2, company:멋진회사, price:5358}
{pno:3, name:상품3, company:멋진회사, price:5351}
{pno:4, name:상품4, company:멋진회사, price:8885}
{pno:5, name:상품5, company:멋진회사, price:6178}
*/

 


배열로부터 스트림 얻기

java.util.Arrays 클래스를 이용하면 다양한 종류의 배열로부터 스트림을 얻을 수 있다.

 

StreamExample.java

import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class StreamExample {
	public static void main(String[] args) {
		String[] strArray = { "홍길동", "신용권", "김미나"};
		Stream<String> strStream = Arrays.stream(strArray);
		strStream.forEach(item -> System.out.print(item + ","));
		System.out.println();
		
		int[] intArray = { 1, 2, 3, 4, 5 };
		IntStream intStream = Arrays.stream(intArray);
		intStream.forEach(item -> System.out.print(item + ","));
		System.out.println();
	}
}
/*
홍길동,신용권,김미나,
1,2,3,4,5,
*/

 


 

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

IntStream 또는 LongStream의 정적 메소드인 range()와 rangeClosed() 메소드를 이용하면 특정 범위의 정수 스트림을 얻을 수 있다.

첫 번째 매개값은 시작 수이고 두 번째 매개값은 끝 수인데, 끝 수를 포함하지 않으면 range(), 포함하면 rangeClosed()를 사용한다.

StreamExample.java

import java.util.stream.IntStream;

public class StreamExample {
	public static int sum;

	public static void main(String[] args) {
		IntStream stream = IntStream.rangeClosed(1, 100);
		stream.forEach(a -> sum += a);
		System.out.println("총합: " + sum);
	}
}
//총합: 5050

 


 

파일로부터 스트림 얻기

java.nio.file.Files의 lines() 메소드를 이용하면 텍스트 파일의 행 단위 스트림을 얻을 수 있다.

 

data.txt

{"pno":1, "name":"상품1", "company":"멋진회사", "price":1558}
{"pno":2, "name":"상품2", "company":"멋진회사", "price":4671}
{"pno":3, "name":"상품3", "company":"멋진회사", "price":470}
{"pno":4, "name":"상품4", "company":"멋진회사", "price":9584}
{"pno":5, "name":"상품5", "company":"멋진회사", "price":6868}

 

StreamExample.java

import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class StreamExample {
	public static void main(String[] args) throws Exception {
		Path path = Paths.get(StreamExample.class.getResource("data.txt").toURI()); //StreamExample 클래스 기준으로 data.txt 상대경로를 얻고 URI 객체를 리턴
		Stream<String> stream = Files.lines(path, Charset.defaultCharset()); //스트림을 얻음, 문자셋 지정
		stream.forEach(line -> System.out.println(line) );
		stream.close();
	}
}
/*
{"pno":1, "name":"상품1", "company":"멋진회사", "price":1558}
{"pno":2, "name":"상품2", "company":"멋진회사", "price":4671}
{"pno":3, "name":"상품3", "company":"멋진회사", "price":470}
{"pno":4, "name":"상품4", "company":"멋진회사", "price":9584}
{"pno":5, "name":"상품5", "company":"멋진회사", "price":6868}
*/
URI와 URL
URI (Uniform Resource Identifier) : 리소스 “자원 자체”를 식별하는 고유한 문자열 시퀀스
URL (Uniform Resource Locator) : 자원(리소스)의 “위치” 를 나타내기 위한 규약

 


 

요소 걸러내기 (필터링)

필터링은 요소를 걸러내는 중간 처리 기능이다.

필터링은 요소를 걸러내는 중간 처리 기능이다.

필터링 메소드에는 disinct()와 filter()가 있다.

출처 : 이것이 자바다 유튜브 동영상 강의

 

disinct()

  • 요소의 중복을 제거
  • 객체 스트림일 경우, equals() 메소드의 리턴 값이 true이면 동일한 요소로 판단
  • IntStream, LongStream, DoubleStream은 같은 값일 경우 중복을 제거

출처 : 이것이 자바다 유튜브 동영상 강의

 

filter()

  • 매개값으로 주어진 Predicate가 false를 리턴하는 요소는 스트림에서 제거한다.

출처 : 이것이 자바다 유튜브 동영상 강의

  • Predicate는 함수형 인터페이스로, 아래와 같은 종류가 있다.

출처 : 이것이 자바다 유튜브 동영상 강의

  • 모든 Predicate는 매개값을 조사한 후 boolean을 리턴하는 test() 메소드를 가지고 있다.

출처 : 이것이 자바다 유튜브 동영상 강의

Predicate<T>를 람다식으로 표현하면 아래와 같다.

T -> {...return true}
또는
T -> true; // 리턴문만 있을 경우 다른거 다 생략 가능

 

사용 예제

FilteringExample.java

import java.util.ArrayList;
import java.util.List;

public class FilteringExample {
	public static void main(String[] args) {
		//List 컬렉션 생성
		List<String> list = new ArrayList<>();
		list.add("홍길동"); 	list.add("신용권");
		list.add("감자바");		list.add("신용권");		list.add("신민철");
		
		//중복 요소 제거
		list.stream()
			.distinct()
			.forEach(n -> System.out.println(n));
		System.out.println();
		
		//신으로 시작하는 요소만 필터링
		list.stream()
			.filter(n -> n.startsWith("신"))
			.forEach(n -> System.out.println(n));
		System.out.println();
		
		//중복 요소를 먼저 제거하고, 신으로 시작하는 요소만 필터링
		list.stream()
			.distinct()
			.filter(n -> n.startsWith("신"))
			.forEach(n -> System.out.println(n));		
	}
}
/*
홍길동
신용권
감자바
신민철

신용권
신용권
신민철

신용권
신민철
*/

 

요소 변환 (매핑)

매핑은 스트림의 요소를 다른 요소로 변환하는 중간 처리 기능이다.(타입 변환)

 

요소를 다른 요소로 변환

mapXxx() 메소드는 요소를 다른 요소로 변환한 새로운 스트림을 리턴한다.

출처 : 이것이 자바다 유튜브 동영상 강의

 

mapXxx() 메소드의 종류는 아래와 같다.

출처 : 이것이 자바다 유튜브 동영상 강의

 

매개타입인 Function은 함수형 인터페이스로, 아래와 같은 종류가 있다.

출처 : 이것이 자바다 유튜브 동영상 강의

 

모든 Function은 매개값을 리턴값으로 매핑(변환)하는 applyXxx() 메소드를 가지고 있다.

출처 : 이것이 자바다 유튜브 동영상 강의

 

Function <T, R>을 람다식으로 표현하면 아래와 같다.

T -> {...return R}
또는
T -> R; // 리턴문만 있을 경우 다른거 다 생략 가능

 

사용 예제

Student.java

public class Student {
	private String name;
	private int score;

	public Student(String name, int score) {
		this.name = name;
		this.score = score;
	}

	public String getName() { return name; }
	public int getScore() { return score; }
}

 

MapExample.java

import java.util.ArrayList;
import java.util.List;

public class MapExample {
	public static void main(String[] args) {
		//List 컬렉션 생성
		List<Student> studentList = new ArrayList<>();
		studentList.add(new Student("홍길동", 85));
		studentList.add(new Student("홍길동", 92));
		studentList.add(new Student("홍길동", 87));
		
		//Student를 score 스트림으로 변환
		studentList.stream()
			.mapToInt(s -> s.getScore())
			.forEach(score -> System.out.println(score));
	}
}
/*
85
92
87
*/

 

기본 타입 간의 변환이거나 기본 타입 요소를 Wrapper 객체 요소로 변환하려면 아래와 같은 간편화 메소드를 사용할 수도 있다.

출처 : 이것이 자바다 유튜브 동영상 강의

 

사용 예제

MapExample.java

import java.util.Arrays;
import java.util.stream.IntStream;

public class MapExample {
	public static void main(String[] args) {
		int[] intArray = { 1, 2, 3, 4, 5};
		
		IntStream intStream = Arrays.stream(intArray);
		intStream
			.asDoubleStream()
			.forEach(d -> System.out.println(d));
			
		System.out.println();
		
		intStream = Arrays.stream(intArray);
		intStream
			.boxed()
			.forEach(obj -> System.out.println(obj.intValue()));
	}
}
/*
1.0
2.0
3.0
4.0
5.0

1
2
3
4
5
*/

 


요소를 복수 개의 요소로 변환

flatMapXxx() 메소드는 하나의 요소를 복수 개의 요소들로 변환한 새로운 스트림을 리턴한다.

출처 : 이것이 자바다 유튜브 동영상 강의

 

flatMap() 메소드의 종류는 아래와 같다.

출처 : 이것이 자바다 유튜브 동영상 강의

 

사용예제

FlatMappingExample.java

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class FlatMappingExample {
	public static void main(String[] args) {
		//문장 스트림을 단어 스트림으로 변환
		List<String> list1 = new ArrayList< >();
		list1.add("this is java");
		list1.add("i am a best developer");
		list1.stream().
		flatMap(data -> Arrays.stream(data.split(" ")))
		.forEach(word -> System.out.println(word));
		
		System.out.println();
		
		//문자열 숫자 목록 스트림을 숫자 스트림으로 변환
		List<String> list2 = Arrays.asList("10, 20, 30", "40, 50");
		list2.stream()
		.flatMapToInt(data -> {
			String[] strArr = data.split(",");
			int[] intArr = new int[strArr.length];
			for (int i = 0; i < strArr.length; i++) {
				intArr[i] = Integer.parseInt(strArr[i].trim());
			}
			return Arrays.stream(intArr);
		})
		.forEach(number -> System.out.println(number));
	}
}
/*
this
is
java
i
am
a
best
developer

10
20
30
40
50
*/