2023.02.27 -2 Java복습
17. 스트림
17.1 스트림이란?
컬렉션 및 배열에 저장된 요소를 반복처리하기 위해서 for문을 이용하거나 iterator(반복자)를 사용햇다.
자바8부터는 또다른 방법으로 컬렉션 및 배열의 요소를 반복처리하기 위해 스트림을 사용할 수 있다.
스트림은 요소들이 하나씩 흘러가면서 처리된다는 의미를 가지고 있다. List컬렉션에서 요소를 반복처리하기 위해 스트림을 이용하면 다음과 같다.
Stream<String> stream = list.stream();
stream.forEach(item -> //item처리);List컬렉션의 stream()메소드로 Stream객체를 얻고 forEach()메소드로 요소를 어떻게 처리할지를 람다식으로 작성한다.
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));
}
}Stream은 iteraotor와 비슷한 반복자이지만 차이점이 있다.
1.내부반복자이므로 처리속도가 빠르고 병렬처리에 효율적이다.
2.람다식으로 다양한 요소처리를 정의할 수 있다.
3.중간처리와 최종처리를 수행하도록 파이프라인을 형성할 수 있다.
17.2 내부반복자
for문과 Iterator은 컬렉션 요소를 컬렉션 바깥쪽으로 반복해서 가져와 처리하는데 이것을 외부 반복자라고 한다.
반면 스트림은 요소처리 방법을 컬렉션 내부로 주입시켜서 요소를 반복처리하는데 이것을 내부반복자라고 한다.
외부반복자일 경우는 컬렉션의 요소를 외부로 가져오는 코드와 처리하는 코드를 모두 개발자 코드가 가지고 있어야한다.
반면 내부 반복자일 경우 개발자 코드에서 제공한 데이터 처리코드(람다식)을 가지고 컬렉션 내부에서 요소를 반복처리한다.
내부반복자는 멀티코어 CPU를 최대한 활용하기 위해 요소들을 분배시켜 병렬 작업을 할 수 있다.
하나씩 처리하는 순차적 외부 반복자보다는 효율적으로 요소를 반복시킬 수 있는 장점이 있다.
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-1
홍길동: main
17.3 중간처리와 최종처리
스트림은 하나이상연결될 수 있다.
오리지널 스트림 뒤에 필터링 중간 스트림이 연결될 수 있고 그 뒤에 매핑 중간스트림이 연결될 수 있다.
이와 같이 스트림이 연결되어 있는 것을 스트림 파이프 라인이라고 한다.
오리지널 스트림과 집계처리 사이의 중간 스트림들은 최종처리를 위해
1.요소를 걸러내거나(필터링)
2.요소를 변환시키거나(매핑)
3.정렬하는 작업을 수행한다.
최종처리는 중간처리에서 정제된요소를
1.반복하거나
2.집계(카운팅, 총합, 평균)작업을 수행한다.
Student객체를 요소로 가지는 컬렉션에서 Student 스트림을 얻고
중간처리를 통해 score스트림으로 변환한 후 최종집계로 평균을 구해보자.
//Student 스트림
Stream<Stream> stStream = list.stream();
//score 스트림
IntStream scoreStream = stStream.mapToInt(student -> student.getScore());
//평균계산
double avg = scoreStream.average().getAsDouble();mapToInt()메소드는 객체를 int값으로 매핑해서 IntStream으로 변환시킨다.
어떤 객체를 int로 매핑할 것인지는 람다식으로 제공해야해서 student객체를 getScore()리턴값으로 매핑한 것이다.
메소드 체이닝을 이용해서 더 간결하게 작성할수 있다.
double avg = list.stream()
.mapToInt(st -> st.getScore())
.average()
.getAsDouble();스트림 파이프라인으로 구성할때 주의할점은 파이프라인 맨끝에는 반드시 최종처리 부분이 있어야한다.
최종처리가 없다면 오리지널 및 중간처리 스트림은 동작하지 않는다.
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> stStream = list.stream();
IntStream scoreStream = stStream.mapToInt(student -> student.getScore());
double avg = scoreStream.average().getAsDouble();
System.out.println("기본:" + avg); // 기본:20.0
// 방법2
double avg2 = list.stream()
.mapToInt(st -> st.getScore())
.average()
.getAsDouble();
System.out.println("메소드체이닝:" + avg2); // 메소드체이닝:20.0
}
}17.4 리소스로부터 스트림얻기
java.util.stream 패키지에는 스트림 인터페이스들이 있다.
BaseStream 인터페이스를 부모로 한 자식 인터페이스들이 있다.
BaseStream
Stream / IntStream / LongStream / DoubleStream
BaseStream에는 모든 스트림에서 사용할 수 있는 공통 메소드들이 정의되어 있다.
Stream은 객체 요소를 처리하는 스트림이고 다른 것들은 각각 기본타입을 처리하는 스트림이다.
이 스트림 인터페이스들의 구현 객체는 다양한 리소스로부터 얻을 수 있다.
주로 컬렉션과 배열에서 얻지만 다른 리소스로부터 스트림 구현 객체를 얻을 수도 있다.

17.4.1 컬렉션으로부터 스트림 얻기
java.util.Collection인터페이스는 스트림과 parallelStream메소드를 가지고 있어서
자식 인터페이스인 list와 set 인터페이스를 구현한 모든 컬렉션에서 객체 스트림을 얻을 수 있다.
public class SteamExample {
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));
}
}17.4.2 배열로부터 스트림 얻기
java.util.Arrays 클래스를 사용하면 다양한 종류의 배열로부터 스트림을 얻을 수 있다.
public class StreamExample {
public static void main(String[] args) {
String[] strArr = { "홍길동", "신용권", "김미나" };
Stream<String> strStream = Arrays.stream(strArr);
strStream.forEach(item -> System.out.println(item + ", "));
System.out.println();
int[] intArr = { 1, 2, 3, 4, 5 };
IntStream intStream = Arrays.stream(intArr);
intStream.forEach(item -> System.out.println(item + ", "));
System.out.println();
}
}17.4.3 숫자 범위로부터 스트림 얻기
IntStream 또는 LongStream의 정적 메소드인 range()와 ragneClosed() 메소드를 이용하면 특정 범위의 정수스트림을 얻을 수 있다.
첫 번째 매개값은 시작 수 이고 두번째 매개값은 끝수이다.
끝수를 포함하지 않으면 range(), 포함하면 rangeCloased()를 사용한다.
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);
}
}-> 외부반복자인 for문과 속도를 비교해봣을때 for문이 더빨랐다.
내부반복자가 효율적이더라도 더 느릴 수도 있다.
17.4.4 파일로부터 스트림 얻기
java.nio.file.Files의 lines()메소드를 이용하면 텍스트파일의 행단위 스트림을 얻을 수 있다
텍스트 파일에서 한행씩 읽고 처리할때 유용하게 사용할 수 있다.
public class StreamExample {
public static void main(String[] args) throws Exception {
//data.txt 의 파일 경로 객체 얻기
Path path = Paths.get(StreamExample.class.getResource("data.txt").toURI());
//Path로부터 파일을 열고 한행 씩 읽으면서 문자열 스트림 생성 기본 UTF-8문자셋으로 읽음
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}
17.5 요소 걸러내기(필터링)
필터링은 요소를 걸러내는 중간 처리 기능이다. 필터링 메소드에는 distinct()와 filter()가 있다.
distinct() | 중복제거
filter(Predicate<T>)| 조건필터링
filter(intPredicate)| 매개차입은 요소타입에 따른
long,double | 함수형 인터페이스므로 람다식으로 작성가능
distinct() 메소드는 요소의 중복을 제거한다. 객체 스트림일 경우 eqauls()메소드의 리턴값이 true이면 동일한 요소로 판단한다
Predicate는 함수형 인터페이스로
추상메소드 boolean test(T t)를 가지고 있다.
모든 Predicate는 매개값을 조사한후 boolean을 리턴하는 test메소드를 가지고 있다.
Predicate<T>를 람다식으로 표현하면
T -> {... return true} 또는 T -> true; // retrun문만 있을 경우 중괄호 retrun키워드 생략가능
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));
System.out.println();
}
}17.6 요소변환(매핑)
매핑은 스트림의 요소를 다른 요소로 변환하는 중간처리 기능이다.
매핑메소드는 MapXXX(), asDoubleStream(), asLongStream(), boxed(), flatMapxxx() 등이있다.
17.6.1 요소를 다른 요소로 변환
Mapxxx() 메소드는 요소를 다른 요소로 변환한 새로운 스트림을 리턴한다.
메소드 종류는 다음과 같다.
매개타입인 Function은 함수형 인터페이스이다
모든 Function은 매개값을 리턴값으로 매핑(변환)하는 applyXX()메소드를 가지고 잇다.
매개값 -> applyxxx() -> 리턴값
public class MapExample {
public static void main(String[] args) {
// list컬렉션 생성
List<Student> stList = new ArrayList<>();
stList.add(new Student("홍길동", 85));
stList.add(new Student("홍길동", 92));
stList.add(new Student("홍길동", 87));
// score스트림으로 변환
stList.stream()
.mapToInt(s -> s.getScore())
.forEach(score -> System.out.println(score));
}
}기본 타입간의 변환이나 기본타입 요소를 래퍼 객체 요소로 변환하려면 다음과 같은 간편화 메소드를 사용할 수도 있다.
LongStream | asLongStream() |int ->long
DoubleStream | asDoubleStream() |int -> double long -> double
Stream<Integer>| boxed() |int-> Integer
Long, Double |long -> Long double -> Double
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()));
}
}17.6.2 요소를 복수 개의 요소로 변환
flatMapXXX() 메소드는 하나의 요소를 복수의 요소들로 변환한 새로운 스트림을 리턴한다.
메소드의 종류는 다음과 같다.
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));
}
}2023.02.27 후기
객체 지향파트를 한번더 보니 이해를 더 쉽게 할 수 있어서 좋다. 혼자서 설계할때 어느정도 고려를하면서 할 수 잇을 것 같다.
스트림의 경우에는 자주 사용하지 않아 까먹게 되는 것들 중 하나인 것같다. 람다식->스트림->문자입출력으로 이어지는 이 과정을 지금 반복해서 보고 어려운 부분이 있다면 추후 국비 학원 교육시에 질문하는 것으로 완성할 수 있을 것 같다. 일단 도구함에 넣어주는 것이 중요하다. 사용할 수 있다면 한번씩사용해보는 것도 좋은 것 같다.