17. 스트림 요소처리
17.1 스트림이란
흐름 흘러가는 대상이 객체이다. 흘러간다는 것은 처리가 된다라는 뜻
컬렉션 및 배열에 저장된 요소를 반복 처리하기 위해 for문을 이용하거냐 Iterator(반복자)를 이용했다.
List<String> List = ...;
for(int i = 0 ; i <List.size();i++) {
String item = list.get(i)''
}
Set<String> set = ...;
Iterator(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처리);
stream.forEach(()-> {});
forEach 매개타입이 뭔지알아야 람다식 작성가능
Streanm<T> 제네릭으로 주는게 흘러감 위에선 String이 흘러감.
forEach(Consumer<? super T> action) T이거나 T의 부모!! 제네릭 와일드카드
인터페이스 Consumer의 추상메소드 void accept(T t) 매개변수 1개 T = 요소의 타입
stream.forEach((t)-> {처리내용});
스트림자체가 이 처리하는 과정을 가지고 있지 않기 때문에 람다식을 제공해야 한다.
람다식 작성방법에 따라 처리방법이 달라지고 이를 내부 반복자라고 한다.
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);
});
// stream.forEach(item -> System.out.println(item)); 실행문하나라서 중괄호생략 매개변수하나라서 괄호생략
}}스트림을 왜 사용하나?
stream은 Iteraotr와 비슷한 반복자이지만
1.내부반복자이므로 처리속도가 빠르고 병렬처리에 효율적이다.
2.람다식으로 다양한 요소처리를 정의할수있다.
3.중간처리와 최종처리를 수행하도록 파이프라인을 형성할수있다.
병렬처리
원래는 스레드를 만들어서 했었음.
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();
메소드체이닝 사용해서 처리가능
double avg = list.steram()
.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;}}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 avg = scoreStream.average().getAsDouble();
System.out.println("평균 점수: " + avg2);
*/
//학생 이름만 얻기
list.stream()
.map(student -> student.getName()) //그냥 매핑
.forEach(name -> System.out.print(name + ", "));
System.out.println();
/*
Stream<Student> studentStream = list.stream();
Stream nameStream = studentStream.map(student -> student.getName());
nameStream.forEach(name -> System.out.print(name + ", "));
*/
}}17.4 리소스로부터 스트림 얻기
java.util.stream 패키지에는 스트림 인터페이스들이 있다. BaseStream인터페이스를 부모로한 자식 인터페이스들은 다음 상속관계를 이루고있다.
Stream은 객체 요소를 처리하는 스트림이고, IntStream LongStream DoubleStream은 각각 기본타입을 처리하는 스트림이다.
이 스트림 인터페이스들의 구현객체는 다양한 리소스로부터 얻을 수 있다. 주로 컬렉션과 배열에서 얻지만 리소스로부터 스트림 구현객체를 얻을 수잇다.
API 도큐먼트에서 자세히 보기 가능.
List컬렉션 Set컬렉션
배열 정도는 잘알아두기 Arrays.~
배열에 객체가 들어가있을때 Int값이 들어가잇을때 마다 리턴타입이 다르다.
17.4.1 컬렉션으로부터 스트림 얻기
java.util.Collection 인터페이스는 스트림과 parallelStream() 메소드를 가지고 있기 때문에
자식 인터페이스인 List와 Set인터페이스를 구현한 모든 컬렉션에서 객체 스트림을 얻을 수 있다.
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();
}}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));
/*
list.stream()
.forEach(p -> System.out.println(p));
*/
}}17.4.2 배열로부터 스트림얻기
java.util.Arrays 클래스를 이용해서 다양한 종류의 배열로부터 스트림을 얻을 수잇다.
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();
/*
Arrays.stream(strArray)
.forEach(item -> System.out.print(item + ","));
*/
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() 메소드를 이용하면 특정범위의 정수 스트림을 얻을 수있다.
첫번째 매개값은 시작수이고 두번재 매개값은 끝수인데 끝수포함안하면 전자 하면 후자사용한다.
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 파일로부터 스트림얻기
nio안배운거임..
java.nio.file.Files의 lines()메소드를 이용하면 텍스트 파일의 행단위 스트림을 얻을 수있다.
이는 텍스트파일에서 한행씩 읽고 처리할때 유용하게 사용가능하다.
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키워드 생략가능
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();
/*
Stream<String> listStream = list.stream();
Stream<String> nodisStream = listStream.distinct();
nodisStream.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();
/*
Stream<String> listStream = list.stream();
//String의 startWith메소드 사용 메소드참조 인스턴스메소드 람다식사용한것.
Stream<String> startwithStream = listStream.filter(n -> n.startsWith("신"));
startwithStream.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;
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; }
}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) 객체 요소(포장객체)로 변환하려면 다음과 같은 간편화 메소드를 사용할수도잇다.
다음은 정수스트림을 실수스트림으로 변환하고 기본타입 스트림을 래퍼스트림으로 변환하는 방법
그다지 활용성이 많이 있지는 않은 부분임.
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 intStream2 = Arrays.stream(intArray);
DoubleStream doubleStream = intStream2.asDoubleStream();
doubleStream.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으로만든다.
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();
/*
Stream<String> arrstream = list1.stream();
Stream<String> flat = arrstream.flatMap(data -> Arrays.stream(data.split(" ")));
flat.forEach(word -> System.out.println(word));
System.out.println();
*/
//문자열 숫자 목록 스트림을 숫자 스트림으로 변환
//문자열 두개인 리스트 만듬
List<String> list2 = Arrays.asList("10, 20, 30", "40, 50");
list2.stream() //data에는 한번은 "10, 20, 30" 한번은"40, 50"이 들어옴
.flatMapToInt(data -> {
String[] strArr = data.split(",");
int[] intArr = new int[strArr.length];
//String[]배열을 int[]배열로만듬 strArr의 길이만큼 int배열만듬 배열복사 Stirng을 인트로만들고 복사
//.trim()은 공백을 없애줌
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));
}} 17.7 요소 정렬
순서를 맞추기
메소드는 다음과 같다.
sorted() 기존 스트림이 비교가 가능하면 알아서 정렬할수있음.(Comparable요소 존재)
sorted(Comparator<T>)
비교기능이없다면 비교자를 제공해야함.(Comparator에 따라 정렬한 새스트림생성)
17.7.1 Comparable 구현객체의 정렬
public class Xxx implements Comparable{
List<Xxx> list = new ArrayList<>();
Stream<Xxx> stream = list.stream();
Stream<Xxx> orderedStream = stream.sorted();
내림차순 정렬하고 싶다면, 비교자가 없을 때 다음으로 하기 람다식 or Comparator비교자 하기
Stream<Xxx> resverseOrderedStream = stream.sorted(Comparator.reversOrde());
public class Student implements Comparable<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;}
@Override
public int compareTo(Student o) {
return Integer.compare(score, o.score);
//내가가진 스코어와 비교대상인 스코어와 비교 작으면 -1 같으면 0 크면 1리턴
//if문적는거랑 같음.
}}public class SortingExample {
public static void main(String[] args) {
// 리스트 컬렉션생성
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("홍길동", 30));
studentList.add(new Student("신용권", 10));
studentList.add(new Student("유미선", 20));
//점수를 기준로 오름차순 정렬 새스트림 얻기
/*
메소드 체이닝 패턴 사용x시
Stream<Student> studentStream = studentList.stream();
Stream<Student> orderedStream = studentStream.sorted();
orderedStream.forEach(s-> System.out.println(s.getName() + ": " + s.getScore()));
*/
//obsolete methods on the stack 오류 https://wjddn407.tistory.com/22
studentList.stream()
.sorted()
.forEach(s-> System.out.println(s.getName() + ": " + s.getScore()));
System.out.println();
//점수를 기준으로 내림차순 정렬한 새 스트림 얻기
studentList.stream()
.sorted(Comparator.reverseOrder()) //내림차순은 Comparator의 정적메소드에 이미 있음.
.forEach(s-> System.out.println(s.getName() + ": " + s.getScore()));
}}17.7.2 Comparator 를 이용한 정렬
객체 자체가 비교자를 가지고 있지 않기때문에 사용
sorted((o1, o2) -> {...})
Comparator<T>는 함수형 인터페이스이기 때문에 람다식 작성가능 compare(T o1, T o2)
equals(Object obj)라는 추상메소드도 있지만 Object가 equals를 이미 가지고 있어서 고려대상이 아니게 됨.
만약 o1과 o2가 정수일 경우엔 Integer.compare(o1, o2), 실수일경우에는 Double.compare(o1, o2)를 호출해서 리턴값을 리턴해도 된다.
public class SortingExample {
public static void main(String[] args) {
// 리스트 컬렉션생성
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("홍길동", 30));
studentList.add(new Student("신용권", 10));
studentList.add(new Student("유미선", 20));
//점수를 기준로 오름차순 정렬 새스트림 얻기
//메소드 체이닝 패턴 사용x시
/*
Stream<Student> studentStream = studentList.stream();
Stream<Student> orderedStream = studentStream.sorted((s1, s2) -> Integer.compare(s1.getScore(), s2.getScore()));
orderedStream.forEach(s-> System.out.println(s.getName() + ": " + s.getScore()));
*/
//obsolete methods on the stack 오류 https://wjddn407.tistory.com/22
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()));
}}17.8 요소를 하나씩 처리(루핑)
루핑은 스트림에서 요소를 하나씩 반복해서 가져와 처리하는 것을 말한다.
지금까지 사용한 forEach()와 peek()가있다.
peek()는 중간처리 단게에서 사용하는 것이고 forEach()는 스트림에서 최종처리를 하는것이라는 차이점이 있다.
peek()는 최종처리가 아니기때문에 뒤에 최종처리가 반드시 와야한다.
Consumer<? super T> 소비자라는 뜻을 가진 인터페이스 이걸알아야 처리가능 객체를 소비, 인트값소비, 롱값소비, 더블값소비가 있다.
모든 Consumer는 매개값을 처리하는 accept()메소드를 가지고 있다.
매개값 -> accept()
Consumer<? super T>를 람다식으로 표현하면 다음과같다.
T -> {...} 또는 T-> 실행문;
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)); // 최종 처리가 없으므로 동작하지 않음.
/*
IntStream intStream = Arrays.stream(intArr);
IntStream filterStream = intStream.filter(a -> a%2 == 0);
IntStream afterpeekStream = filterStream.peek(n->System.out.println(n));
int total2 = afterpeekStream.sum();
System.out.println("총합: " + total2 + "\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));
}}17.9 요소 조건 만족 여부(매칭)
만족하면 true 아니면 false
매칭요소들이 특정조건에 만족하는지 여부를 조사하는 '최종 처리 기능'이다.
all 전부다 맞아야할경우 any최소한 하나의요소 none 모든 요소가 만족x여야할때
Predicate<T> test(T t)
public class MatchingExample {
public static void main(String[] args) {
int[] intArr = { 2, 4 ,6 };
/*
IntStream intStream = Arrays.stream(intArr);
boolean result = intStream.allMatch(a -> a%2==0);
System.out.println("모두 2의 배수인가? " + result);
*/
boolean result = Arrays.stream(intArr)
.allMatch(a -> a%2==0);
System.out.println("모두 2의 배수인가? " + result); //true
result = Arrays.stream(intArr)
.anyMatch(a -> a%3==0);
System.out.println("하나라도 3의 배수가 있는가? " + result); //true
result = Arrays.stream(intArr)
.noneMatch(a -> a%3==0);
System.out.println("3의 배수가 없는가? " + result); //false
}}17.10 요소기본집계
집계는 요소들을 하나의 값으로 만드는 것 최종처리 기능으로 카운팅 합계 평균값 최대값 최소값 등과 같이 하나의 값으로 산출하는 것을 말한다.
대량의 데이터를 가공해서 하나의 값으로 축소하는 리덕션이라고 볼 수 있다.
17.10.1 스트림이 제공하는 기본집계
스트림은 카운팅 최대 최소 평균 합계등 처리하는 메소드를 제공한다.
집계메소드가 리턴하는 OptionalXXX의 객체로 리턴한다.
OptionalXXX은 하나의 객체 리턴 OptionalDouble, OptionalInt, OptionalLong클래스를 말한다.
이들은 최종값을 저장하는 객체로 get(), getAsDouble(), getAsInt(), getAsLong()을 호출하면 최종값을 얻을 수잇다.
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() //average()최종인데 마지막 아닌이유? OptionalInt로 값을 주기 때문에 이를 처리하기 위해 한줄더!
.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);
}}17.10.2 Optional 클래스
Optional은 선택적인... 고정된게 아니라 선택할 수 있는 것임.
어떤경우에는 확실한값을 얻지만 어쩔땐 선택적으로 할필요가 잇다.
집계값이 존재하지 않을 경우 디폴트값을 설정하거나 집계값을 처리하는 Consumer를 등록할 수 있다.
1.요소가 하나도 없으면 집계값 존재안함 -> 필터를 했는데 집계가 존재하지 않을경우
요소가없으면 집계를 할수 없기때문이다. 여기에 디폴트값을 줄수잇다.
2.단순히 평균을 구하는게 아니라 평균으로 딴걸 하고싶을때 사용. 집계값으로 다시 소비를하는 Consumer를 등록할 수 있다.
orElse 집계값이 없을 경우 디폴트값
컬렉션의 요소는 동적으로 추가되는 경우가 많다. 프로그램실행하면서 요소가 저장
만약 컬렉션에 요소가 존재하지 않으면 집계값을 산출할수 없으므로 NoSuchElementException 예외가 발생한다.
하지만 앞의 표에 언급되어 있는 메소드를 이용하면 예외발생을 막을 수있다.
예를들어 average를 최종처리 에서 사용할 경우 다음 3가지 방법으로 대비가능
1.isPresent() 메소드가 true를 리턴할때만 집계값을 얻는다.
2.orElse()메소드로 집계값이 없을 경우를 대비해 디폴트 값을 정해놓는다.
3.ifPresent() 메소드로 집계값이 있을 경우에만 동작하는 Consumer(구현객체 람다식)을 제공한다.
public class OptionalExample {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
/*//예외 발생(java.util.NoSuchElementException)
double avg = list.stream()
.mapToInt(Integer :: intValue) // e -> return e.intValue() 인스턴스 메소드 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");
}
/*
IntStream intstream = list.stream().mapToInt(Integer :: intValue);
OptionalDouble optional2 = intstream.average();
//원래는 double avg로 얻는데 값을 모른다고 가정 없으면 average가 안되기때문임.
if(optional2.isPresent()) {
System.out.println("방법1_평균: " + optional2.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);
/*
IntStream intstream = list.stream().mapToInt(Integer :: intValue);
double avg2 = intstream.average().orElse(0.0);
System.out.println("방법2.2_평균: " + avg2);
*/
//방법3
//집계값이 있을 경우에만 나오기때문에 출력이안됨
list.stream()
.mapToInt(Integer :: intValue)
.average()
.ifPresent(a -> System.out.println("방법3_평균: " + a));
}}17.11 요소 커스텀 집계
다양한 집계결과물을 만들 수 있도록 reduce()메소드도 제공한다.
reduce: 축소한다 결국 어떤 요소를 처리하면 1개로 줄어들기때문임.
reduce(T identity, BinaryOperator<T> accumulator)
T identity= 이부분은 집계값이 없을때 기본값임. 이걸 사용하면 오류가 안남.
BinaryOperator BiFunction 으로부터 메소드를 물려받음 두개의 매개값을 받아 하나의 값을 리턴하는 apply()메소드르 가지고있다.
R apply(T t, U u)
람다식 (t, u) -> {... return r} 리턴문만있을경우 중괄호 생력가능 (t, u ) -> 값
스트림에 많은데 두개만 받는데 어떻게 집계? 먼저 두개 요소= r 이나옴 이 r을 다시 넣고 다시 집계하는 방식
Binary두개를 가지고 하나를 만드는 것.
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)
//(s -> s.getScore()) 학생스트림을 정수 스트림으로 바꾼다. 매개변수로 들어온게 참조변수로 사용됨.
//메소드참조로 사용함. getScore인스턴스메소드
.sum();
/*
int sum3 = studentList.stream()
.mapToInt(s -> s.getScore())
.sum();
*/
/*
IntStream intstream = studentList.stream().mapToInt(Student :: getScore);
int total = intstream.sum();
System.out.println("total: " + total);
*/
//방법2
int sum2 = studentList.stream()
.map(Student :: getScore)
.reduce(0, (a, b) -> a+b);
/*
IntStream intstream = studentList.stream().mapToInt(Student :: getScore);
int total = intstream.reduce(0, (a, b) -> a+b);
System.out.println("total: " + total);
*/
System.out.println("sum1: " + sum1);
System.out.println("sum2: " + sum2);
}}17.12 요소 수집
스트림은 요소들을 필터링 또는 매핑한 후 요소들을 수집하는 최종처리 메소드인 collect() 를 제공한다.
이 메소드를 이용하면 필요한 요소만 컬렉션에 담을 수 잇고 요소들을 그루핑한 후 집계도 할 수 있다.
100개의 요소 컬렉션이있다고 가정 근데 내가 원하는 객체가아님 객체의 일부를 별도로 다른 컬렉션에 저장하고싶다.
필터링해서 모으기.
17.12.1 필터링한 요소 수집
Stream의 collect(Collector<T,A,R> collector) 메소드는 필터링 또는 매핑된 요소들을 새로운 컬렉션에 수집하고 이 컬렉션을 리턴한다.
매개값인 Collector은 어떤 요소를 어떤 컬렉션에 수집할 것인지를 결정한다.
타입파라미터의 T는 요소 A 는 누적기(누적을 시키는것)수집을 해야하니 필요함. R은 요소가 저장될 컬렉션 어디에 저장?
? 은 와일드카드인데 각각 add put을 사용하기 때문에 자바가 이미 알고 있어서 안넣어도됨.
만약 내가 만든 컬렉션이라면? 그 넣는 방법을 ?에 적어주어야함.
T를 K로 리턴 T를 U로 리턴
List<Student> maleList = totalList.stream()
.filter(s->s.getSex().equals("남"))
.collect(Collectors.toList());Map<String, Integer> map = totalList.stream()
.collect(
Collectors.toMap(
s -> s.getName(), //Student 객체에서 키가 될 부분 리턴
s -> s.getScore() //Student 객체에서 값이 될 부분 리턴
)
);//java 16부터는 .toList()만 하면됨
List<Student> maleList = totalList.stream()
.filter(s->s.getSex().equals("남"))
.toList();예제 실습 남자만 뽑아서 리스트로
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());*/
//java 16부터 는 .toList()만 하면됨
List<Student> maleList = totalList.stream()
.filter(s->s.getSex().equals("남"))
.toList();
maleList.stream()
.forEach(s -> System.out.println(s.getName()));
System.out.println();
/*
Set<Student> maleSet = totalList.stream()
.filter(s->s.getSex().equals("남"))
.collect(Collectors.toSet());
maleSet.stream().forEach(s -> System.out.println(s.getName()));
*/
/*
Stream<Student> stream = totalList.stream();
Stream<Student> filterStream = stream.filter(s -> s.getSex().equals("남"));
List<Student> maleList2 = filterStream.toList();
maleList2.stream().forEach(s -> System.out.println(s.getName()));
System.out.println();
*/
//학생 이름을 키, Student 점수를 값으로 갖는 Map 생성
Map<String, Integer> map = totalList.stream()
.collect(
Collectors.toMap(
s -> s.getName(), //Student 객체에서 키가 될 부분 리턴
s -> s.getScore() //Student 객체에서 값이 될 부분 리턴
)
);
System.out.println(map);}}남 여 나눠서 맵
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() //List<Student>에 성별로 나뉘어짐.
.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()));
}}그루핑 후 남 여 나누고 평균내기
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);
}}17.13 요소 병렬처리
내부반복자 좋은점
1.외부에서 x 내부처리 시간빠름
2.스트림 여러개에 할당해서 병렬처리를 할 수잇다.
요소가 너무많아서 병렬처리를 한다. 여러개로 분할해서 한다.
무조건 좋은 건아님 요소수가 적으면 오히려 늦어질 수 잇다.
17.13.1 동시성과 병렬성
멀티스레드는 동시성 또는 병렬성으로 실행된다.
동시성이란 하나의 코어에서 작업을 돌아가면서 하는건데 매우 빠르면 동시처럼 보이는 것임.
동시성은 한시점에 하나의 작업만 실행한다. 스래드1 스레드2이런 식인것 마치 여러스레드가 동시에 일하는 것처럼 보이는 것임.
코어의 갯수에는 한계가잇어서 결국 코어 하나에선 여러개를 동시에 실행할수 밖에없다.
병렬성이란 멀티코어를 각각 이용해서 병렬로 실행하는 것을 말한다. 병렬성은 데이터 병렬성과 작업병렬성으로 구분할 수 있다.
데이터병렬성
전체데이터를 분할해서 서브데이터셋으로 만들고 이 서브데이터셋들을 병렬처리해서 작업을 빨리 끝내는 것을 말한다.
자바병렬스트림은 데이터 병렬성을 구현한 것이다.
작업병렬성
서로 다른 작업을 병렬처리하는 것을 말한다.
예는 서버 프로그램이다. 서버는 각각 클라이언트에서 요청한 내용을 개별스레드에서 병렬로 처리한다.
17.13.2 포크조인 프레임워크
자바 병렬스트림은 요소들을 병렬처리하기 위해 포크조인 프레임워크를 사용한다.
포크조인 프레임워크는 포크단계에서 전체 요소들을 서브 요소셋으로 분할하고 각각의 서브 요소셋을 멀티코어에서 병렬로 처리한다.
조인단계에서는 서브 결과를 결합해서 최종결과를 만들어낸다.
우리가 직접하는 것이 아니라 JVM내부에서 알아서 처리하는 것임.
에를들어 4개의 코어를 가진 cpu에서 4개로 분할하고 조인단계에서 결합과저을 거쳐 최종결과를 산출한다.
병렬처리스트림은 포크단계에서 요소를 순서대로 분할하지 않는다. 이해하기 쉽도록 위그림에서는 앞에서부터 차례대로 4등분햇지만
내부적으로는 요소들을 나누는 알고리즘이 있기때문에 개발자가 신경쓸필요는 없다.
포크조인 프레임워크는 병렬처리를 위해 스레드풀을 사용한다. 각각의 코어에서 서브요소셋을 처리하는 것은 작업 스레드가 해야하므로 스레드 관리가 필요하다.
포크조인 프레임워크는 ExecutorService의 구현객체인 ForkJoinPool을 사용해서 작업스레드를 관리한다.
내부적에서 알아서 나누기 때문에 우리는 병렬스트림을 쓰겟다라고 선언만 해주면됨
17.13.3 병렬스트림사용
자바 병렬 스트림을 이용할 경우에는 백그라운드에서 포크조인 프레임워크가 사용되기 때문에 개발자는 매우 쉽게 병렬처리를 할 수 있다.
병렬스트림 다음 두가지 메소드로 얻을 수 있다.
parallelStream() list 또는 set 컬렉션에서 병렬스트림
parallel() 기존의 스트림을 병렬스트림으로 바꾸어서 처리하겠다.
public class ParallelExample {
public static void main(String[] args) {
Random random = new Random();
//1억개의 Integer 저장
List<Integer> scores = new ArrayList< >();
for(int i=0; i<100000000; i++) {
scores.add(random.nextInt(101)); //0~ 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> stream2 = scores.stream();
IntStream intStream = stream2.mapToInt(i -> i.intValue());
Double avg3 = intStream.average().getAsDouble();
*/
//병렬 스트림으로 처리하기
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");
}}17.13.4 병렬처리성능
일반 스트림 순차처리보다 항상 실행성능이 좋다고 판단해서는 안된다.
그전에 병렬처리에 영향을 미치는 다음 3가지요인을 잘살펴보아야한다.
1.요소의 수와 요소당 처리 수간
요소가 적고 요소당 처리시간이 짤븡면 일반스트림이 더 빠를 수잇다.
병렬처리는 포크및 조인단계가 잇고 스레드풀을 생성하는 추가적인 비용이 발생하기 때문임.
2.스트림 소스의 종류
ArrayList와 배열은 인덱스로 요소를 관리하기때문에 포크단계에서 요소를 쉽게 분리할 수 있어 병렬처리 시간이 절약된다.
반면에 HashSet TreeSet은 요소 분리가 쉽지않고 LinkedList역시 링크를 따라가야하므로 요소분리가 쉽지않다. 따라서 이소스들은 상대적으로 병렬처리가 늦다.
3.코어의 수
cpu의 코어의 수가 많으면 많을 수록 병렬스트림의 성능은 좋아진다. 하지만 코어의 수가 적을 경우에는 오히려 일반스트림이 더 빠를 수잇다.
병렬스트림은 스레드 수가 증가하여 동시성이 많이 일어나므로 오히려 느려진다.
2022.12.20 리뷰
스트림은 계속 해서 나오기 때문에 잘 잘알아둘 필요가 있다.
중요한것은 꺾이지 않는 마음
'기초단계 > JAVA' 카테고리의 다른 글
| 2022.12.22 JAVA 복습 네트워크 입출력 (0) | 2022.12.22 |
|---|---|
| 2022.12.21 JAVA 복습 데이터 입출력 (0) | 2022.12.22 |
| 2022.12.20-1 JAVA 복습 람다식 (0) | 2022.12.20 |
| 2022.12.19-1 JAVA 복습 컬렉션 자료구조 (0) | 2022.12.19 |
| 2022.12.17-2 JAVA 복습 멀티 스레드 (2) | 2022.12.18 |