17. 스트림요소처리
17.1 스트림이란?
흐름 흘러가는 대상이 객체이다. 흘러간다는 것은 처리가 된다라는 뜻
컬렉션 및 배열에 저장된 요소를 반복 처리학 위해 for문을 이용하거냐 Iterator(반복자)를 이용했다.
List<String> List = ...;
for(int i = 0 ; i <List.size();i++) {
String item = list.get(i)''
}
Set<String> set = ...;
Iteratr(String) iterator = set.iterator();
while(iterator.hasNext()){
String item = iterator.next();
}그러나 이런것은 리스트 바깥쪽에서 처리하는 것임. 리스트라는 컬렉션이 잇고 하나씩 가져와서 처리
Set또한 안에서 처리하는 것이 아니라 하나씩 가져와서 바깥에서 처리하는 것임.
이들을 외부 반복자라고 한다. 바깥에서 하나씩 얻어서 처리하기 때문임
17.2 내부반복자
for문, Iteraotr는 컬렉션의 요소를 컬렉션 바깥쪽으로 바복해 처리하는 것이다.
바깥쪽에서 데이터 처리 작성후 가는거임. 내부는 처리방법만 람다식으로 제공하면 컬렉션 내부 스트림안에서 알아서 처리됨.
외부반복자일경우에는 컬렉션요소를 외부로 가져오는 코드와 처리하ㅡㄴ 코드를 모두 개발자 코드가 가지고 있어야한다.
컬렉션안에 100개잇을 경우 50개만 a스레드 50개는 b스레드에서 처리하고싶을때 매우 번거로운 작업이 된다.
내부반복자를 쓰면 안에서 안에서 자동으로 cpu를 최대한 활용하기 위해 요소들을 분배시켜 병렬작업을 한다. 그래서 효율적임.
스트림 요소처리는 리스트를 통해서 스트림을 얻어내서 리스트안에서 람다식으로 처리
흘러나오는 객체를 람다식으로 처리
Stream<String> stream = list.stream();
stream.forEach(item -> //item처리);
forEach 매개타입이 뭔지알아야 람다식 작성가능
forEach(Consumer<? super T> action) T이거나 T의 부모!! 제네릭 와일드카드
인터페이스 Consumer의 추상메소드 void accept(T t) 매개변수 1개 T = 요소의 타입
스트림자체가 이 처리하는 과정을 가지고 있지 않기 때문에 람다식을 제공해야 한다.
람다식 작성방법에 따라 처리방법이 달라지고 이를 내부 반복자라고 한다.
package ch17.sec01;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.stream.Stream;
public class StreamExample {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("홍길동");
set.add("김길동");
set.add("김자바");
//외부반복자 이용
for(String item : set) {
System.out.println(item);
}
System.out.println();
Iterator<String> iterator = set.iterator();
while(iterator.hasNext()) {
String item = iterator.next();
System.out.println(item);
}
System.out.println();
//내부반복자 이용
Stream<String> stream = set.stream();
stream.forEach((item) -> {System.out.println(item);
});
//forEach 매개타입이 뭔지알아야 람다식 작성가능
// forEach(Consumer<? super T> action) T이거나 T의 부모!! 제네릭 와일드카드
// 인터페이스 Consumer의 추상메소드 void accept(T t) 매개변수 1개 T = 요소의 타입 String
}} 
stream은 Iteraotr와 비슷한 반복자이지만
1.내부반복자이므로 처리속도가 빠르고 병렬처리에 효율적이다.
2.람다식으로 다양한 요소처리를 정의할수있다.
3.중간처리와 최종처리를 수행하도록 파이프라인을 형성할수있다.
병렬처리
원래는 스레드를 만들어서 했었음.
package ch17.sec02;
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> stream = list.parallelStream(); //병렬스트림얻기
stream.forEach(name -> {
Thread thread = Thread.currentThread();
System.out.println(name + ": " + thread.getName());
});
//요소가 많을 수록 효과적임.
}} 
17.3 중간처리와 최종처리
이전 장에서 학습한 것은 바로 람다식을 적용해 최종처리만 했음.
forEach가 최종처리를 하는 것임.
이앞에 중간처리를 넣을 수 있다.
예를들어 남여 -> 남자만 -> 군필자만 ->최종처리
이런 중간처리에는 요소를 걸러내는 '필터링' , 요소를 변환하는 '매핑', 정렬하는 작업을 수행을 한다.
최종처리는 집계(카운팅 총합 평균)작업을 수행한다.
Student스트림
Stream<Student> studentStream = list.stream();
중간처리 score 스트림
IntStream scoreStream = studentStream.mapToInt(student-> student.getScore());
mapIoInt = map매핑한다 Int로 student객체를 getScore()메소드의 리턴값으로 매핑
TOIntFunction applyAsInt(T value) (t) -> {int value = xxxx; return value}
리턴값이 있는 람다식을 사용.
최종처리 평균계산
double avg = scoreStream.average().getAsDouble();
메소드 체이닝 패턴 12.5.2 StringBuilder 참조 하여 생략가능
double avg = list.steram()
.mapToInt(student-> student.getScore())
.average()
.getAsdouble
package ch17.sec03;
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;}
}package ch17.sec03;
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)
);
/*
List<Student> list = List.of(
new Student("홍길동",10),
new Student("신용권",20),
new Student("유미선",30)
);
*/
double avg = list.stream()
.mapToInt(student -> student.getScore())
.average()
.getAsDouble();
System.out.println("평균 점수: " + avg);
/*
//스트림만들기 생략안했을 때
Stream<Student> studentStream = list.stream();
//중간처리
IntStream scoreStream = studentStream.mapToInt(student -> student.getScore());
//최종처리
double avg2 = scoreStream.average().getAsDouble();
System.out.println("평균 점수: " + avg2);
*/
//학생 이름만 얻기
list.stream()
.map(student -> student.getName()) //그냥 매핑
.forEach(name -> System.out.println(name));
}}
17.4 리소스로부터 스트림 얻기
java.util.stream 패키지에는 스트림 인터페이스들이 있다. BaseStream인터페이스를 부모로한 자식 인터페이스들은 다음 상속관계를 이루고있다.
Stream은 객체 요소를 처리하는 스트림이고, IntStream LongStream DoubleStream은 각각 기본타입을 처리하는 스트림이다.
이 스트림 인터페이스들의 구현객체는 다양한 리소스로부터 얻을 수 있다. 주로 컬렉션과 배열에서 얻지만 리소스로부터 스트림 구현객체를 얻을 수잇다.
API 도큐먼트에서 자세히 보기 가능.
List컬렉션 Set컬렉션
배열 정도는 잘알아두기 Arrays.~
17.4.1 컬렉션으로부터 스트림얻기
java.util.Collection 인터페이스는 스트림과 parallelStream() 메소드를 가지고 있기 때문에
자식 인터페이스인 List와 Set인터페이스를 구현한 모든 컬렉션에서 객체 스트림을 얻을 수 있다.
package ch17.sec04.exam01;
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;
}
//getter
public int getPno() { return pno; }
public String getName() { return name; }
public String getCompany() { return company; }
public int getPrice() { return price; }
//이 객체가 어떤걸 가지고 있는지 출력하려고 toString() 메소드 재정의
@Override
public String toString() {
return new StringBuilder()
.append("{")
.append("pno:" + pno + ", ")
.append("name:" + name + ", ")
.append("company:" + company + ", ")
.append("price:" + price)
.append("}")
.toString();
}}package ch17.sec04.exam01;
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++) {
//for문 product 객체 생성
Product product = new Product(i, "상품+i", "멋진 회사", (int) (10000*Math.random())); //0.0<= ...<10000사이 얻기
//product객체 리스트에 담기
list.add(product);
}
// 객체스트림 얻기
Stream<Product> stream = list.stream();
stream.forEach(p -> System.out.println(p));
}} 
17.4.2 배열로부터 스트림 얻기
java.util.Arrays 클래스를 이용해서 다양한 종류의 배열로부터 스트림을 얻을 수잇다.
package ch17.sec04.exam02;
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();
}}
17.4.3 숫자 범위로부터 스트림얻기
IntStream 또는 LongStream의 정적메소드인 range()와 rangeClosed() 메소드를 이요하면 특정범위의 정수 스트림을 얻을 수있다.
첫번째 매개값은 시작수이고 두번재 매개값은 끝수인데 끝수포함안하면 전자 하면 후자사용한다.
package ch17.sec04.exam03;
import java.util.stream.IntStream;
public class StreamExample {
public static int sum;
public static void main(String[] args) {
IntStream stream = IntStream.range(1, 100); //숫자커지면 어지러움 나중가선 병렬로 처리하자.
stream.forEach(a -> sum += a);
System.out.println(sum); //4950
}}17.4.4 파일로부터 스트림얻기
java.nio.file.Files의 lines()메소드를 이용하면 텍스트 파일의 행단위 스트림을 얻을 수있다.
이는 텍스트파일에서 한행씩 읽고 처리할때 유용하게 사용가능하다.
{"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}package ch17.sec04.exam04;
import java.net.URISyntaxException;
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());
//("data.txt").toURI() data.txt파일의 경로(Path)객체얻어서 path객체를 얻어서 넣기
//URI와 URL의 차이 https://www.charlezz.com/?p=44767 참조하기
//위치정보는 유일하기때문에 그것으로 알아오는것임.
Stream<String> stream = Files.lines(path, Charset.defaultCharset()); //JVM기본 문자셋 사용해라(UTF-8)
stream.forEach(line -> System.out.println(line) );
stream.close();
}}
17.5 요소 걸러내기(필터링)
최종처리하기위해 요소를 적절하게 변환시킬 필요가 있다.
필터링 메소드는 distinct()와 filter()가 있다.
컬렉션,배열 오리지널 스트림 -> 필터링 스트림 오리지널 스트림의 요소를 걸러내는 것이다.
distinct() 중복제거
BABA -> distinct() -> BA
filter(Predicate<T>) 조건필터링 매개타입은 요소타입에 따른 함수형 인터페이스이므로 람다식으로 작성가능
CBA -> filter() A-true B-false C-true -> C A
Predicate는 함수형 인터페이스로 다음과 같은 종류가있다.
boolean 요소 조사해서 T, F조사
Predicate는 매개값을 조사한 후 boolean을 리턴하는 test()메소드를 가지고 있다.
매개값 -> test -> boolean
이를 람다식으로 작성하면 T -> {... return true} 또는 T -> true; //리턴문만 있을경우 중괄호와 return키워드 생략가능
package ch17.sec05;
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("신")) //true false리턴
.forEach(n -> System.out.println(n));
System.out.println();
//중복 요소를 먼저 제거하고, 신으로 시작하는 요소만 필터링
//필터를 두번할수있음.
list.stream()
.distinct()
.filter(n -> n.startsWith("신"))
.forEach(n -> System.out.println(n));
}}
17.6 요소변환(매핑)
타입변환이라고 생각하면됨 요소의 타입을 변환해서 새로운 스트림을 만든다.
MapXxx(), asDoubleStream(), asLongStream(), boxed()기본값을 박싱처리 int->Integer등, flatMapXxx()객체를풀어서 여러개의 요소로만듬.
17.6.1 요소를 다른 요소로변환
MapXxx() 요소를 다른요소로 변환한 새로운 스트림을 리턴
B A -> A -C B-D -> D C
메소드의 종류
객체를 기본타입으로 기본타입을 객체로
각 메소드가 사용하는 인터페이스
추상메소드 전부다 외울필요x 어떤 요소를 어떤 요소로 변환할건지만 알아두면 된다.
모든 Function은 매개값을 리턴값으로 매핑하는 applyXxx() 메소드를 가지고 있다.
매개값 -> applyXxx() -> 리턴값
T > {... return R;} 또는 T -> R;
package ch17.sec06.exam01;
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; }
}package ch17.sec06.exam01;
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));
/*
//안풀어쓰면
Stream<Student> studentStream = studentList.stream();
IntStream scoreStream = studentStream.mapToInt(student -> student.getScore() );
scoreStream.forEach(score -> System.out.println(score));
*/
}} 
17.6.2 기본타입간 변환 & 포장객체로의 변환
기본타입간의 변환이거나 기본타입요소를 래퍼(Wrapper) 객체 요소(포장객체)로 변환하려면 다음과 같은 간편화 메소드를 사용할수도잇다.
다음은 정수스트림을 실수스트림으로 변환하고 기본타입 스트림을 래퍼스트림으로 변환하는 방법
그다지 활용성이 많이 있지는 않은 부분임.
package ch17.sec06.exam02;
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())); //포장값 보기
}}17.6.3 요소를 복수개의 요소로 변환
flatMapXxx() 메소드는 하나의 요소를 복수개의 요소로 변환한 새로운 스트림을 리턴
B A -> A - A2A1 B- B2B1 -> B2B1A2A1
데이터가 뭉쳐있는것을 펼친다.
flatMap()메소드의 종류는 다음과 같다.
T -> Stream<R> A를 A2 A1으로만든다.
package ch17.sec06.exam03;
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(" "))) //배열을stream으로 만듬. 공백을 기준으로 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]; //String[]배열을 int[]배열로만듬
for (int i = 0; i < strArr.length; i++) {
intArr[i] = Integer.parseInt(strArr[i].trim());
}
return Arrays.stream(intArr); //Arrays.stream()ㅁ메소드느 주어진 int[]배열을 intStream으로만듬.
})
.forEach(number -> System.out.println(number));
}} 
2022.12.03 리뷰
자신감이 떨어진다. 시작할때는 하나하나 배우면서 즐거웠는데 여길 보든 저길보든 좋은 글이 없다.
매일맞는 길인가 고민하지만 맞는 길인지 중요하지 않다. 해야하는 것이다. 두렵지만 나아가야한다.
중요한 것은 꺾이지 않는 마음..
'기초단계 > JAVA' 카테고리의 다른 글
| 2022.12.06 JAVA 데이터 입출력 (0) | 2022.12.07 |
|---|---|
| 2022.12.05 JAVA 스트림요소처리 (1) | 2022.12.05 |
| 2022.12.02 JAVA 람다식 (0) | 2022.12.02 |
| 2022.12.01 JAVA 컬렉션 자료구조 (0) | 2022.12.02 |
| 2022.11.30-2 JAVA 컬렉션 자료구조 (1) | 2022.11.30 |