이전까지 데이터를 다룰 때,
① 컬렉션이나 배열에 데이터를 담고 원하는 결과를 얻기 위해 for, iterator를 이용
-> 너무 코드가 길고 재사용성↓
② 데이터 소스마다 다른 방식으로 다뤄야함
-> Collection이나 Iterator와 같은 인터페이스로 표준화했지만 (조상)
-> 결국 각 컬렉션 클래스에는 같은 기능의 메서드가 중복 정의(오버라이딩)
-> Collection.sort() / Arrays.sort()
✔️ 이러한 문제점들을 해결하는 것이 '스트림(stream)'
EX> 문자열 배열과, 같은 내용을 저장한 List
String[] strArr ={"aaa","bbb","ccc"};
List<String> strList=Arrays.asList(strArr);
stream 스트림을 이용한 경우는 아래와 같다.
Stream<String> strStream1= strList.stream();//List->stream
Stream<String> strStream2 = Arrays.stream(strArr);//Array->stream
이 데이터를 정렬하고 읽는 방법은 아래와 같다.
strStream1.sorted().forEach(System.out::println);
strStream2.sorted().forEach(System.out::println);//메서드 참조가 이용됨
데이터 소스를 읽기만 한다. 변경할 수 없다.
그리고 필요하다면 정렬된 결괄르 컬렉션이나 배열에 담아서 반환
List<String> sortedList = strStream.sorted().collect(Collectors.toList());
한번 사용되면 다시 생성 (Iterator처럼)
strStream1.sorted().forEach(System.out::println);
int numOfStr = strStream1.count();//에러 이미 닫혔음.
우리가 앞서 보았던 forEach()도 for이라는 반복문을 메소드 내부에 숨기는 내부반복 메서드이다.
for( String str: strList){
System.out.println(str);
}
⬇️ 스트림의 forEach()
stream.forEach(Sytem.out::println);
내부에 for 반복문을 숨긴 것을 알아볼 수 있다.
자세히 forEach()를 살펴보자
void forEach(Consumer<? super T> action){
Objects.requireNonNull(action);
for(T t:str){
action.accept(T);
}
}
람다식을 매개변수로 받는다.
중간 연산 : 연산 결과가 스트림인 연산. 스트림에 연속해서 중간 연산을 할 수 있음.
최종 연산 : 연산 결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 단 한번만 가능stream.distinct().limit(5).sorted().forEach(System.out::println); // 중간 연산 : distinct().limit(5).sorted() // 최종 연산 : forEach()
중간 연산 | 설명 |
---|---|
Stream<T> distince() | 중복을 제거 |
Stream<T> filter(Predicate<T> predicat) | 조건에 안맞는 요소 제외 |
Stream<T> limit (long maxSize) | 스트림의 일부를 잘라낸다 |
Stream<T> skip(long n) | 스트림의 일부를 건너뛴다 |
Stream<T> peek(Consumer<T> action) | 스트림의 요소에 작업수행 |
Stream<T> sorted() Stream<T> sorted(Comparator<T> comparator) | 스트림의 요소를 정렬한다. |
Stream<T> map(Function<T,R> mapper) DoubleStream mapToDouble(ToDoubleFunction<T> mapper) IntStream mapToInt(ToIntFunction<T> mapper) LongStream mapToLong(ToLongFunction<T> mapper) | |
Stream<R> flatMap(Function<T,Stream<R>> mapper) DoubleStream flatmapToDouble(Function<T,DoubleStream> m) IntStream flatmapToInt(Function<T,InrStream> m) LongStream flatmapToLong(Function<T,LongStream> m) | 스트림의 요소를 반환 |
최종연 | 설명 |
---|---|
void forEach(Consumer<? super T> action) void forEachOrdered(Consumer<? super T> action) | 각 요소에 지정된 작업을 수행 |
long count() | 스트림의 요소의 개수 반환 |
Optional<T> max(Comparator<? super T> comparator) Optional<T> min(Comparator<? super T> comparator) | 스트림의 최대값/최소값을 반환 |
Optional<T> findAny()//아무거나 하나 findFirst()//첫 번째 요소 | 스트림의 요소를 하나 반환 |
boolean allMatch(Predicate<T> p)//모두 만족하는지 boolean anyMatch(Predicate<T> p)//하나라도 만족하는지 boolean nonMatch(Predicate<T> p)//모두 만족하지 않는지 | 주어진 조건을 모든 요소가 만족시키는지, 만족시키지 않는지 확인 |
Object[] toArray() A[] toArray(IntFunction<A[]> generator) | 스트림의 모든 요소를 배열로 반환 |
Optional<T> reduce(BinaryOperator<T> accumulator) T reduce(T identity, BinaryOperator accumulator) U reduce(U identity, BiFunction <U,T,U> accumulator,BinaryOperator combiner) | 스트림의 요소를 하나씩 줄여가면서 계산한다. |
R collect(Collector<T,A,R> collector) R collector(Supplier<R> supplier, BiConsumer<R,T> accumulator, BiConsumer<R,R> combiner) | 스트림의 요소를 수집한다. 주로 요소를 그룹화하거나 분할한 결과를 컬렉션에 담아 반환하는데 사용된다. |
이 메서드를 정리해보니 백준 알고리즘 문제를 풀 때 잘 사용해야겠다.
최종 연산 수행 전까지는 중간 연산이 수행되지 않는다.
최종연산이 수행되어야 비로소 스츠림의 요소들이 중간 연산을 거쳐 최종연산에서 소모
지네릭 타입을 지정하는 Stream보다는 IntStream과 같이 기본형으로 다루는 스트림이 더 효율적이다.
우리는 앞서 fork&join을 통해서 하나의 작업을 작은 단위로 나눠서 동시에 처리가 가능하도록 하는 프레임웍을 배웠다. 스트림에서도 이 프레임을 웍을 이용해서 자동적으로 연산을 병렬로 수행한다.
기본적으로는 병렬로 처리하지 않지만 parallel()이라는 메서드를 호출해서 병렬로 수행하며 이를 취소할 때 sequential()을 호출하여 취소한다.
sequetial()이 기본값이기 때문에 처음에는 호출하지 않고 parallel()을 취소할 때만 호출한다.
int sum = strStream.parallel()
.mapToInt(s->s.length())
.sum();
스트림의 소스 : 배열, 컬렉션, 임의의 수 등 다양
컬렉션의 최고 조상인 Collection에는 stream()이 정의
따라서 그 아래 자손들은 이 메서드를 이용하여 스트림을 생성
Stream<T> Collection.stream()
//List로부터 스트림 생성 List<Integer> list = Arrays.asList(1,2,3,4,5); Stream<Integer> intStream =list.stream();
배열로부터 스트림을 생성하는 방법
Stream<String> strStream = Stream.of("a","b","c"); //가변인자
Stream<String> strStream2 =Stream.of(new String[] {"a","b","c"});
Stream<String> strStream3 = Arrays.stream(new String[]{"a","b","c"})
Stream<String> strStream4 = Arrays.stream(new String[]{"a","b","c"},0, 3);
기본현 배열을 소스로 하는 스트림
IntStream Stream.of(int... values) //가변인자
IntStream Stream.of(int[])
IntStream Arrays.stream(int[])
IntStream Arrays.stream(int[] array,int startInclusive, int endExclusive)
IntStream과 LongStream의 경우
지정된 범위의 연속된 정수를 스트림으로 생성해서 반환하는 메서드를 가지고 있다.
IntStream IntStream.range(int begin, int end)//끝에 범위 포함 안됨
IntStream IntStream.rangeClosed(int begin, int end)// 끝에 포함
Random클래스에 정의된 난수를 생성하되 stream으로 반환하는 메서드들
IntStream ints()
LongStream longs()
DoubleStream doubles()
이 메서드의 이름에 맞게 해당 타입의 난수들을 무한의 스트림으로 만들기 때문에
✖️ 유한 스트림으로 만들어 줄 limit()도 같이 사용
IntStream intStream = new Random().ints();//static메서드가 아니기 때문에
//객체 생성 후 .ints() 무한 스트림
intStream.limit(5).forEach(System.out::println);
유한 스트림으로 난수를 생성하는 메서드
IntStream ints(long streamSize)
LongStream longs(long streamSize)
DoubleStream doubles(long streamSize)IntStream intStream = newRandom().ints(5);
지정된 범위의 난수를 발생시키는 스트림을 얻는 메서드
IntStream ints(int begin, int end)
IntStream ints(long streamSize, int begin, int end)
end는 범위에 포함되지 않는다.
✔️ 이 메서드는 람다식을 매개변수로 받는다.
✔️ 람다식의 의해 계산한 값을 요소로 하는 무한 스트림을 생성한다.
static <T> Stream<T> iterate(T seed, UnaryOperator f)
static<T> Stream<T> generate(Supplier<T> s)
Stream<Integer> evenStream = stream.iterate(0,n->n+2);//0,2,4,6...
Stream<Double> randomStream = stream.generate(Math::random);
Stream<Double> randomStream = stream.generate(()->1);
✔️ 기본형 스트림 타입의 참조변수로 다룰 수 없음
IntStream evenStream = stream.iterate(0,n->n+2); //에러
DoubleStream randomStream = stream.generate(Math::random);//에러
따라서 아래와 같이 mapToInt()와 같은 메서드로 변환
IntStream evenStream = stream.iterate(0,n->n+2).mapToInt(Integer::valueOf);
Stream<Integer> stream = evenStream.boxed();
stream<Integer> --- mapToInt ---> IntStream
stream<Integer> <--- boxed() --- IntStream
java.nio.file.Files의 list() 메서드 : 디렉토리에 있는 파일 목록을 소스로 하는 스트림을 반환
Stream<Path> Files.list(Path dir)
요소가 하나도 빈 스트림 생성
Stream emptyStream = Stream.empty();
Stream의 static메서드 concat() : 두 스트림을 하나로 연결
String[] str1 = {"123","456","789"};
String[] str2 = {"ABC","abc","DEF"};
Stream<String> str1 = Stream.of(str1);
Stream<String> str2 = Stream.of(str2);
Stream<String> str3 = Stream.concat(str1,str2);
Stream<T> skip(long n) // 건너뜀
Stream<T> limit(long maxSize) // 길이 제한
기본형 스트림
IntStream skip(long n)
IntStream limit(long maxSize)
Stream filter(Predicate<? super T> predicate)
- 주어진 조건에 맞지 않은 요소 걸러내기
IntStream intStream = IntStream.rangeClosed(1,10); intStream.filter(i->i%2==0 && i !=4).forEach(System.out::println);
Stream distinct()
- 중복된 요소를 제거
IntStream intStream = IntStream.of(1,2,3,4,5,5,6,6,6,7); intStream.distinct().forEach(System.out::println()); //1234567
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
① Comarator로 스트림 정렬
② ①번 대신 int값을 반환하는 람다식
③ 지정하지 않으면 기본 정렬 기준 단, 스트림의 요소가 Comparable을 구현한 클래스
문자열 스트림 정렬 방법 | 출력결과 |
---|---|
strStream.sorted()//기본정렬 strStream.sorted(Comparator.naturalOrder())//기본정렬 strStream.sorted((s1,s2)->s1.ompareTo(s2));//람다식도 가능 strStream.sorted(String::compareTo)//위와 같은 문장 | CCaabbccdd |
strStream.sorted(Comparator.reverseOrder())//기본 정렬의 역순 strStream.sorted(Comparator.naturalOrder().reversed()) | ddccbaaaCC |
strStream.sorted(String.CASE_INSENSITIVE_ORDER)//대소문자 구분안함 | aaabCCccdd |
strStream.sorted(String.CASE_INSENSITIVE_ORDER.reversed()) | ddCCccbaaa |
strStream.sorted(Comparator.comparing(String::length))//길이 순 정렬 strStream.sorted(Comparator.comparingInt(String::length)) //no오토박싱 | bddCCccaaa |
strStream.sorted(Comparator.comparing(String::length).reversed()) | aaaddCCccd |
🔍 문자열 스트림을 정렬하는 다양한 방법
✔️ 가장 기본적인 메서드인 comparing()을 알아보자
comparing()메서드는 Comparator의 static메서드이며
comparing(Function<T,U> keyExtractor) //①
comparing(Function<T,U> keyExtractor, Comparator<U> keyComparator)//②
②번은 스트림의 요소가 Comparator을 구현하지 않은 경우, 추가적인 매개변수로 정 렬기준을 따로 정해준다.
비교대상이 기본형 스트림인 경우
comparingInt(ToIntFunction<T,U> keyExtractor)
comparingLong(ToLongFunction<T,U> keyExtractor)
comparingDouble(ToDoubleFunction<T,U> keyExtractor)
정렬 조건을 추가할 때는 Comparator의 디폴드 메서드인 thenComparing()메서드를 이용한다.
thenComparing(Comparator<T> other)
thenComparing(Function<T,U> keyExtractor)
thenComparing(Function<T,U> keyExtractor, Comparator<U> keycomp)
//반별>점수별>이름별(사전)
studentStream.sorted(Comparator.comparing(Student::getBan)
.thenComparing(Student::getTotalScore)
.thenComparing(Student::getName))
.forEach(System.out::println);
사실 나는 이번 챕터를 공부하여 Comparator과 Comparable의 차이를 정확히 이해할 수 있었다.
좀 흘리듯 말하자면
Comparator은 익명함수처럼 그 자리에서 여러번 정의되는 것...
그니까 어떤 자료형을 정의 하는 클래스가 인터페이스 Comparable를 구현하고 있다. 그 자료형 클래스에는 오름차순으로 정렬되는 기본적인 compareTo()메서드도 오버라이딩 되어 있을 것이다.
하지만 Comparator의 compare()메서드는 자료형 안에 정의되는 것이 아닌 그 기본적인 정렬이 아닌 사용자가 정의하여 자료형 안에 소속되는 메서드로 넣지 않고 원할 때 그 자리에서 정의하는 것이다!
참고 블로그 : https://blog.naver.com/vail131/222194933384
import java.util.*;
import java.util.stream.*;
class StreamEx1 {
public static void main(String[] args) {
Stream<Student> studentStream = Stream.of(
new Student("이자바",3,300),
new Student("김자바",1,200),
new Student("안자바",2,100),
new Student("박자바",2,150),
new Student("소자바",1,200),
new Student("나자바",3,290),
new Student("김자바",3,180)
);
studentStream.sorted(Comparator.comparing(Student::getBan)//반별
.thenComparing(Comparator.naturalOrder())) // 점수별 내림 차순
.forEach(System.out::println);
}
}
class Student implements Comparable<Student>{//자료형에 정의되는 기본정렬 Comparable
String name;
int ban;
int totalScore;
Student(String name, int ban, int totalScore) {
this.name = name;
this.ban = ban;
this.totalScore = totalScore;
}
public String toString() {
return String.format("[%s,%d,%d]",name,ban,totalScore);
}
String getName() {return name;}
int getBan() {return ban;}
int getTotalScore() {return totalScore;}
public int compareTo(Student s) { // 기본 내림차순
return s.totalScore -this.totalScore; //양수 바꾸기
}
}
✔️ 스트림 요소에 저장된 값 중에서 원하는 필드만 뽑아내거나
✔️ 특정 형태로 변환해야 할 때
Stream map(Function<? super T,? extends R> mapper)
Stream<File> fileStream= Stream.of(new File("Ex1.java"),new File("Ex1.bak"),new File("Ex2.java"), new File("Ex1"),new File("Ex1.txt"),new File("Ex3.jpg")); //File->String map()을 이용 Stream<String> filenameStream = fileStream.map(File::getName); filenameStream.forEach(System.out::println);
<->forEach() : 스트림이 소모됨
fileStream.map(File::getName)
.filter(s->s.indexOf('.')!=-1)
.peek(s->System.out.printf("filename=%s%n",s))
.map(s->s.substring(s.indexOf('.')+1))
.peek(s->System.out.printf("substring=%s%n",s))
.map(String::toUpperCase)
.distinct()
.peek(s->System.out.printf("중복제거=%s%n",s))
.forEach(System.out::println);
이렇게 중간에 넣어도 소모되지 않는다.
예제를 살펴보자
import java.io.*;
import java.util.stream.*;
class StreamEx2 {
public static void main(String[] args) {
File[] fileArr = {new File("Ex1.java"),new File("Ex1.bak"),new File("Ex2.java"),
new File("Ex1"),new File("Ex1.txt"),new File("Ex3.jpg")};
Stream<File> fileStream = Stream.of(fileArr);
//map()으로 Stream<File>->Stream<String>으로 변환
Stream<String> filenameStream = fileStream.map(File::getName);
filenameStream.forEach(System.out::println);//스트림 소모됨
fileStream = Stream.of(fileArr);//다시 생성
fileStream.map(File::getName)
.filter(s->s.indexOf('.')!=-1) //.이 들어간 인덱스의 위치가 맨 끝이 아닌 요소만
.peek(s->System.out.printf("filename=%s%n",s))
.map(s->s.substring(s.indexOf('.')+1))//.이후부터의 string생성
.peek(s->System.out.printf("substring=%s%n",s))
.map(String::toUpperCase)
.distinct()
.peek(s->System.out.printf("중복제거=%s%n",s))
.forEach(System.out::println);
System.out.println();
}
}
✔️ Stream<T> 타입의 스트림을 기본형 스트림으로 변환할 때 사용
DoubleStream mapToDouble(ToDoubleFunction<? suuper T> mapper)
IntStream mapToInt(ToIntFunction<? suuper T> mapper)
LongStream mapToLong(ToLongFunction<? suuper T> mapper)Stream<Integer> studentScoreStream = studentStream.map(Student::getTotalScore)
IntStream studentScoreIntStream = studentStream.mapToInt(Student::getTotalScore) int allTotalScore = studentScoreIntStream.sum()
✔️ Stream<T>는 count()만 지원 그러나 기본형 스트림은 아래와 같이 숫자를 다루는데 편리한 메서드들을 제공
int sum()
OptionalDouble averate()
OptionalInt max()
OptionalInt min()
최종연산이기 때문에 호출 후에 스트림이 닫힘
중복 호출을 원할 때는
summaryStatistics()메서드 이용IntSummaryStatistics stat = scoreStream.summaryStatistics(); long totalCount = stat.getCount(); long totalScore = stat.getSum(); double avgScore = stat.getAverage(); int minScore = stat.getMin(); int maxScore = stat.getMax();
✔️ Stream<T>➡️mapToInt()➡️IntStream
IntStream➡️boxed()➡️Stream<Integer>
단, IntStream➡️mapToObj()➡️Stream<String> 다른 타입의 stream_
import java.util.*;
import java.util.stream.*;
class StreamEx1 {
public static void main(String[] args) {
Student[] stuArr= {
new Student("이자바",3,300),
new Student("김자바",1,200),
new Student("안자바",2,100),
new Student("박자바",2,150),
new Student("소자바",1,200),
new Student("나자바",3,290),
new Student("김자바",3,180)
};
Stream<Student> stuStream = Stream.of(stuArr);
stuStream.sorted(Comparator.comparing(Student::getBan)//반별
.thenComparing(Comparator.naturalOrder())) // 점수별 내림 차순
.forEach(System.out::println); //Stream소모
stuStream = Stream.of(stuArr);//다시 생성
IntStream stuScoreStream = stuStream.mapToInt(Student::getTotalScore);//기본형 스트림으로 변환하는
IntSummaryStatistics stat = stuScoreStream.summaryStatistics();//최종연산에도 소모되지 않도록
System.out.println("count="+stat.getCount());
System.out.println("sum="+stat.getSum());
System.out.printf("average=%.2f%n",stat.getAverage());
System.out.println("min="+stat.getMin());
System.out.println("max="+stat.getMax());
}
}
class Student implements Comparable<Student>{//자료형에 정의되는 기본정렬 Comparable
String name;
int ban;
int totalScore;
Student(String name, int ban, int totalScore) {
this.name = name;
this.ban = ban;
this.totalScore = totalScore;
}
public String toString() {
return String.format("[%s,%d,%d]",name,ban,totalScore).toString() ;
}
String getName() {return name;}
int getBan() {return ban;}
int getTotalScore() {return totalScore;}
public int compareTo(Student s) { // 기본 내림차순
return s.totalScore -this.totalScore; //양수 바꾸기
}
}
Stream의 요소가 배열인 경우
import java.util.*;
import java.util.stream.*;
class StreamEx4 {
public static void main(String[] args) {
//1번째 배열을 요소로 하는 stream의 각 요소를 stream으로 만들기
Stream<String[]> strArrStrm = Stream.of( //stream 요소 자체가 배열인
new String[] {"abc","def","jkl"},
new String[] {"ABC","GHI","JKL"}
);
//Stream<Stream<String>> strStremStrem = strArr.map(Arrays::stream);
Stream<String> strStrm = strArrStrm.flatMap(Arrays::stream);//Arrays.stream()
strStrm.map(String::toLowerCase)//소문자
.distinct()//중복제거
.sorted()//정렬
.forEach(System.out::println); //출력하고 소모
System.out.println();
//2번째 문장을 요소로 하는 stream을 단어별의 stream으로 만들기
String[] lineArr = {
"Believe or not It is true",
"Do or do not There is no try"
};
Stream<String> lineStream = Arrays.stream(lineArr); //stream생성
lineStream.flatMap(line->Stream.of(line.split(" +"))) //띄어쓰기를 구분하여 stream요소 생성
.map(String::toLowerCase) //소문자
.distinct() //중복 제거
.sorted() //정렬
.forEach(System.out::println); //출력하고 소모
System.out.println();
//3번째 stream안의 stream을 stream<T>으로 만들 때
// map으로 stream 안의 stream을 배열로 만들 후
// flatMap으로 그 배열의 요소를 각각 stream으로 만들 것
Stream<String> strStrm1 = Stream.of("AAA","ABC","bBb","Dd"); //String의 요소를 갖는 stream
Stream<String> strStrm2 = Stream.of("bbb","aaa","ccc","dd");
Stream<Stream<String>> strStrmStrm =Stream.of(strStrm1,strStrm2); //두개의 stream을 합치기 즉 스트림 안의 스트림
Stream<String> strStream = strStrmStrm
.map(s->s.toArray(String[]:: new)) //스트림 안의 스트림을 String 배열로 마들기
.flatMap(Arrays::stream); //배열 안의 요소들 stream으로 만들기
strStream.map(String::toLowerCase)
.distinct()
.forEach(System.out::println);
}
}
import java.util.*;
public class OptionalEx1 {
public static void main(String[] args) {
//Optional 객체 생성
Optional <String> optStr = Optional.of("1234");
Optional<Integer> optInt = optStr.map(String::length); //s->s.length()
//get()함수를 통해서 저장된 값 가져오기
System.out.println("optStr="+optStr.get());
System.out.println("optInt="+optInt.get());
//null이 발생하여 예외가 발생하는 경우 get() ->orElse()
int result1 = Optional.of("1234")
.filter(x->x.length()>0)
.map(Integer::parseInt).get();
int result2 = Optional.of("")
.filter(x->x.length()>0)
.map(Integer::parseInt).orElse(-1);
System.out.println("result1 ="+result1);
System.out.println("result2 ="+result2);
//ifPresent() null이면 아무일도 안함, 값이 있다면 람다식
Optional.of("456").map(Integer::parseInt)
.ifPresent(x->System.out.printf("result3=%d%n",x));
//0과 빈객체의 차이
OptionalInt optInt1 = OptionalInt.of(0);//0저장
OptionalInt optInt2 = OptionalInt.empty();//null
//isPresent() null이면 false, 값이 있다면 true
System.out.println(optInt1.isPresent());
System.out.println(optInt2.isPresent());
//기본형Optional의 get메서드의 형태
System.out.println(optInt1.getAsInt());
System.out.println("optInt1="+optInt1);
System.out.println("optInt2="+optInt2);
System.out.println("optInt1.equals(optInt2)?"+optInt1.equals(optInt2));
//참보 뱐수의 값이 null일 가능성이 있는 경우 Optional의 겍체 생성은
//of() 대신에 ofNullable()을 사용
Optional<String> opt = Optional.ofNullable(null);
Optional<String> opt2 = Optional.empty();
System.out.println("opt ="+opt);
System.out.println("opt2 ="+opt2);
System.out.println("opt.equals(opt2)?"+opt.equals(opt2));
//optStrToInt 메서드 정의
int result3 = optStrToInt(Optional.of("123"),0);
int result4 = optStrToInt(Optional.of(""),0);
System.out.println("result3="+result3);
System.out.println("result4="+result4);
}
static int optStrToInt(Optional<String> optStr , int defaultValue) {
try {
return optStr.map(Integer:: parseInt).get();
}catch(Exception e) {
return defaultValue;
}
}
}
<->peek() : 스트림 소모하지 않고 결과를 만들어낸다.
void forEach(Consumer<? super T> action)
import java.util.*;
import java.util.stream.*;
class StreamEx5 {
public static void main(String[] args) {
String[] strArr = {
"Inheritance","Java" ,"Lambda","stream","OptionalDouble","IntStream","count","sum"
};
//스트림 생성
Stream.of(strArr).forEach(System.out::println);
//조건 noneMatch : 조건을 만족하는 요소가 하나도 없는가?
boolean noEmptyStr = Stream.of(strArr).noneMatch(s->s.length()==0);
//조건 findFirst() 첫번째 요소 주로 filter()와 같이 사용됨
//s로 시작하는 첫번째 요소 반환
Optional<String> sWord = Stream.of(strArr)
.filter(s->s.charAt(0)=='s').findFirst();
System.out.println("noEmptyStr="+noEmptyStr);
System.out.println("sWord="+sWord.get());
//mapToInt를 이용하여 Stream<String[]>->IntString
IntStream intStream1= Stream.of(strArr).mapToInt(String::length);
IntStream intStream2= Stream.of(strArr).mapToInt(String::length);
IntStream intStream3= Stream.of(strArr).mapToInt(String::length);
IntStream intStream4= Stream.of(strArr).mapToInt(String::length);
int count = intStream1.reduce(0,(a,b)->a+1);//count()
int sum = intStream2.reduce(0,(a,b)->a+b);//sum()
//람다식을 메서드 차조로
OptionalInt max = intStream3.reduce(Integer::max);
OptionalInt min = intStream4.reduce(Integer::min);
System.out.println("count="+count);
System.out.println("sum="+sum);
System.out.println("max="+max.getAsInt());
System.out.println("min="+min.getAsInt());
}
}
import java.util.*;
import java.util.stream.*;
import static java.util.stream.Collectors.*;//static 메서드 호출시 'Collectors.' 생략
class StreamEx1 {
public static void main(String[] args) {
Student[] stuArr= {
new Student("이자바",3,300),
new Student("김자바",1,200),
new Student("안자바",2,100),
new Student("박자바",2,150),
new Student("소자바",1,200),
new Student("나자바",3,290),
new Student("사자바",3,180)
};
//stream->컬렉션과 배열로 변환
List<String> names = Stream.of(stuArr).map(Student::getName)
.collect(Collectors.toList());
System.out.println(names);
//스트림->배열로
Student[] stuArr2= Stream.of(stuArr).toArray(Student[]::new);
for(Student s : stuArr2)
System.out.println(s);
// 스트림->Map<String,Student>로 변환. 학생이름이 key
Map<String,Student> stuMap = Stream.of(stuArr)
.collect(Collectors.toMap(s->s.getName(),p->p));
for(String name : stuMap.keySet())
System.out.println(name+"-"+stuMap.get(name));
long count = Stream.of(stuArr).collect(counting());
long totalScore = Stream.of(stuArr)
.collect(summingInt(Student::getTotalScore));
System.out.println("count="+count);
System.out.println("totalScore="+totalScore);
//map()->reduce()
totalScore =Stream.of(stuArr)
.collect(reducing(0,Student::getTotalScore, Integer::sum));
System.out.println("totalScore="+totalScore);
Optional<Student> topStudent = Stream.of(stuArr)
.collect(maxBy(Comparator.comparingInt(Student::getTotalScore)));
System.out.println("topStudent="+topStudent.get());
IntSummaryStatistics stat = Stream.of(stuArr)
.collect(summarizingInt(Student::getTotalScore));
System.out.println(stat);
String stuNames = Stream.of(stuArr).map(Student::getName)
.collect(joining(",","{","}"));
System.out.println(stuNames);
}
}
class Student implements Comparable<Student>{//자료형에 정의되는 기본정렬 Comparable
String name;
int ban;
int totalScore;
Student(String name, int ban, int totalScore) {
this.name = name;
this.ban = ban;
this.totalScore = totalScore;
}
public String toString() {
return String.format("[%s,%d,%d]",name,ban,totalScore).toString() ;
}
String getName() {return name;}
int getBan() {return ban;}
int getTotalScore() {return totalScore;}
public int compareTo(Student s) { // 기본 내림차순
return s.totalScore -this.totalScore; //양수 바꾸기
}
}
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
import static java.util.stream.Collectors.*;
import static java.util.Comparator.*;
class Student2 {
String name;
boolean isMale;
int hak;
int ban;
int score;
Student2(String name, boolean isMale, int hak, int ban, int score){
this.name = name;
this.isMale = isMale;
this.hak = hak;
this.ban = ban ;
this.score = score;
}
String getName() {return name;}
boolean isMale() {return isMale;}
int getHak() {return hak;}
int getBan() {return ban;}
int getScore() {return score;}
public String toString() {
return String.format("[ %s ,%s,%d학년 %d반,%3d점]",
name,isMale? "남":"여",hak,ban,score);
}
enum Level {HIGH, MID, LOW}
}
class StreamEx7 {
public static void main(String[] args) {
Student2[] stuArr = {
new Student2("나자바",true,1,1,300),
new Student2("김자바",false,1,1,250),
new Student2("김자바",true,1,1,200),
new Student2("이지미",false,1,2,150),
new Student2("남자바",true,1,2,100),
new Student2("안지미",false,1,2,50),
new Student2("황지미",false,1,3,100),
new Student2("강지미",false,1,3,150),
new Student2("이자바",true,1,3,200),
new Student2("나자바",true,2,1,300),
new Student2("김지미",false,2,1,250),
new Student2("김자바",true,2,1,250),
new Student2("이지미",false,2,2,150),
new Student2("남자바",true,2,2,100),
new Student2("안지미",false,2,2,100),
new Student2("황지미",false,2,3,100),
new Student2("강지미",false,2,3,150),
new Student2("이자바",true,2,3,100)
};
System.out.printf("1. 단순분할(성별로 분할)%n");
Map<Boolean, List<Student2>> stuBySex = Stream.of(stuArr)
.collect(partitioningBy(Student2::isMale));
System.out.println("map<성별, List<Student>="+stuBySex);
List<Student2> maleStudent = stuBySex.get(true);
List<Student2> femaleStudent = stuBySex.get(false);
for(Student2 s : maleStudent) System.out.println(s);
for(Student2 s: femaleStudent) System.out.println(s);
System.out.printf("%n2. 단순분할 + 통계(성별 학생수)%n");
Map<Boolean,Long> stuNumBySex = Stream.of(stuArr)
.collect(partitioningBy(Student2::isMale,counting()));
System.out.println("남학생 수 : "+stuNumBySex.get(true));
System.out.println("여학생 수 : "+stuNumBySex.get(true));
System.out.printf("%n3. 단순불할 + 통계 (성별 1등)%n");
Map<Boolean,Optional<Student2>> topScoreBySex = Stream.of(stuArr)
.collect(partitioningBy(Student2::isMale,maxBy(comparingInt(Student2::getScore))
));
System.out.println("남학생 1등 :"+topScoreBySex.get(true));
System.out.println("여학생 1등 :"+topScoreBySex.get(false));
Map<Boolean, Student2> topScoreBySex2 = Stream.of(stuArr)
.collect(partitioningBy(Student2::isMale,
collectingAndThen(
maxBy(comparingInt(Student2::getScore)), Optional ::get
)
));
System.out.println("남학생 1등 :"+ topScoreBySex2.get(true));
System.out.println("남학생 1등 :"+ topScoreBySex2.get(false));
System.out.printf("%n4. 다중분할(성별 불합격자, 100점 이하)%n");
Map<Boolean, Map<Boolean, List<Student2>>> failedStuBySex =
Stream.of(stuArr).collect(partitioningBy(Student2::isMale,partitioningBy(s->s.getScore()<=100))
);
List<Student2> failedMaleStu = failedStuBySex.get(true).get(true);
List<Student2> failedFemaleStu = failedStuBySex. get(false).get(true);
for(Student2 s : failedMaleStu) System.out.println(s);
for(Student2 s : failedFemaleStu) System.out.println(s);
}
}
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
import static java.util.stream.Collectors.*;
import static java.util.Comparator.*;
class Student2 {
String name;
boolean isMale;
int hak;
int ban;
int score;
Student2(String name, boolean isMale, int hak, int ban, int score){
this.name = name;
this.isMale = isMale;
this.hak = hak;
this.ban = ban ;
this.score = score;
}
String getName() {return name;}
boolean isMale() {return isMale;}
int getHak() {return hak;}
int getBan() {return ban;}
int getScore() {return score;}
public String toString() {
return String.format("[ %s ,%s,%d학년 %d반,%3d점]",
name,isMale? "남":"여",hak,ban,score);
}
enum Level {HIGH, MID, LOW}
}
class StreamEx7 {
public static void main(String[] args) {
Student2[] stuArr = {
new Student2("나자바",true,1,1,300),
new Student2("김자바",false,1,1,250),
new Student2("김자바",true,1,1,200),
new Student2("이지미",false,1,2,150),
new Student2("남자바",true,1,2,100),
new Student2("안지미",false,1,2,50),
new Student2("황지미",false,1,3,100),
new Student2("강지미",false,1,3,150),
new Student2("이자바",true,1,3,200),
new Student2("나자바",true,2,1,300),
new Student2("김지미",false,2,1,250),
new Student2("김자바",true,2,1,250),
new Student2("이지미",false,2,2,150),
new Student2("남자바",true,2,2,100),
new Student2("안지미",false,2,2,100),
new Student2("황지미",false,2,3,100),
new Student2("강지미",false,2,3,150),
new Student2("이자바",true,2,3,100)
};
System.out.printf("1. 단순그룹화(반별로 그룹화)%n");
Map<Integer, List<Student2>> stuByBan = Stream.of(stuArr)
.collect(groupingBy(Student2::getBan));
for(List<Student2> ban : stuByBan.values()) {
for (Student2 s : ban) {
System.out.println(s);
}System.out.println();
}
System.out.printf("%n2. 단순그룹화(성적별로 그룹화)%n");
Map<Student2.Level,List<Student2>> stuByLevel = Stream.of(stuArr)
.collect(groupingBy(s->{
if(s.getScore() >= 200) return Student2.Level.HIGH;
else if(s.getScore() >=100) return Student2.Level.MID;
else return Student2.Level.LOW; }
));
TreeSet<Student2.Level> keySet = new TreeSet<>(stuByLevel.keySet());
for(Student2.Level key : keySet) {
System.out.println("["+key+"]");
for(Student2 s: stuByLevel.get(key)) {
System.out.println(s);
System.out.println();
}
}
System.out.printf("%n3. 단순그룹화 + 통계(성적별-> 학생수)%n");
Map<Student2.Level, Long> stuCntByLevel = Stream.of(stuArr)
.collect(groupingBy(s->{
if(s.getScore() >= 200) return Student2.Level.HIGH;
else if(s.getScore() >=100) return Student2.Level.MID;
else return Student2.Level.LOW;},counting())
);
for(Student2.Level key : stuCntByLevel.keySet())
System.out.printf("[%s] - %d명, ",key, stuCntByLevel.get(key));
System.out.println();
System.out.printf("%n4. 다중그룹화(학년별, 반별)");
//1-2-Student List
Map<Integer,Map<Integer, List<Student2>>> stuByHakAndBan =
Stream.of(stuArr)
.collect(groupingBy(Student2::getHak,
groupingBy(Student2::getBan)
));
for(Map<Integer,List<Student2>> hak : stuByHakAndBan.values() ) {
for(List<Student2> ban : hak.values()) {
System.out.println();
for(Student2 s : ban)
System.out.println(s);
}
}
System.out.printf("%n5. 다중그룹화 + 통계 (학년별, 반별 1등) %n");
// 학년 별로 아래 Map으로 존재
//1-2-Student 1등인 사람 하나
//하나로 연결되는 경우에는 collecitongAndThen (어떤 최종 연산자),Optional::get
Map<Integer,Map<Integer,Student2>> topStuByHakAndBan =
Stream.of(stuArr)
.collect(groupingBy(Student2::getHak,
groupingBy(Student2::getBan,
collectingAndThen(
maxBy(comparingInt(Student2::getScore))
, Optional::get
)
)
));
for(Map<Integer, Student2> ban : topStuByHakAndBan.values()) {
for(Student2 s : ban.values())
System.out.println(s);
}
System.out.printf("%n6. 다중그룹화 + 통계(학년별, 반별 성적그룹)%n");
Map<String, Set<Student2.Level>> stuByScoreGroup = Stream.of(stuArr)
.collect(groupingBy(s->s.getHak()+"-"+s.getBan(),
mapping(s->{
if(s.getScore()>=200) return Student2.Level.HIGH;
else if(s.getScore() >=100) return Student2.Level.MID;
else return Student2.Level.LOW;
},toSet())
));
Set<String> keySet2 = stuByScoreGroup.keySet();
for(String key : keySet2) {
System.out.println("["+key+"]"+stuByScoreGroup.get(key));
}
}
}
위에서는 Collectors 클래스에서 제공하는 컬렉터를 사용하는 방법에 대해서 배웠다.
✔️ 이번에는 Collector 인터페이스를 구현하여 컬렉터를 직접 작성해보자.
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
class CollectorEx1 {
public static void main(String[] args) {
String[] strArr = {"aaa","bbb","ccc"};
Stream<String> strStream = Stream.of(strArr);
String result = strStream.collect(new ConcatCollector());
System.out.println(Arrays.toString(strArr));
System.out.println("result="+result);
}
}
// 문자열을 하나로 결합해서 String으로 반환하는
class ConcatCollector implements Collector<String, StringBuilder, String> {
@Override
public Supplier<StringBuilder> supplier() {
return () -> new StringBuilder();
}
@Override
public BiConsumer<StringBuilder, String> accumulator(){
return (sb,s)->sb.append(s);
}
@Override
public Function<StringBuilder, String> finisher() {
return sb->sb.toString();
}
@Override
public BinaryOperator<StringBuilder> combiner() {
return (sb,sb2)->sb.append(sb2);
}
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
}