스트림의 연산(중간연산, 최종연산)
중간연산
중간 연산 | 설명 | |
Stream<T> distinct( ) | 중복을 제거 | |
Stream<T> filter(Predicate<T> predicate) | 조건에 안 맞는 요소 제외 | |
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<R> DoubleStream IntStream Long Stream Stream<R> DoubleStream IntStream LongStream |
map(Function<T,R> mapper) mapToDouble(ToDoubleFunction<T> mapper) mapToInt(ToIntFunction<T> mapper) mapToLong(ToLongFunction<T> mapper) flatMap(Function<T, Stream<B> mapper) flatMapToDouble(Function<T, DoubleStream> m) flatMapToInt(Function<T, IntStream> m) flatMapToLong(Function<T, LongStream> m) |
스트림의 요소를 반환한다. |
1. 스트림 자르기 – skip( ), limit( )
Stream<T> skip(long n) | 앞에서부터 n개 건너뛰기 |
Stream<T> limit(long maxSize) | maxSize 이후의 요소는 잘라냄 |
IntStream intStream = IntStream.rangeClosed(1, 10) // 12345678910
IntStream.skip(3).limit(5).forEach(System.out::print) // 45678 (3개 건너뛰고 5개 자르기)
2. 스트림의 요소 걸러내기 – filter( ), distinct( )
Stream<T> filter(Predicate<? super T> predicate) | 조건에 맞지 않는 요소 제거(조건식) |
Stream<T> distinct( ) | 중복 제거 |
IntStream intStream = IntStream.of(1,2,2,3,3,3,4,5,5,6);
intStream.distinct( ).forEach(System.out::print); // 123456
IntStream intStream = IntStream.rangeCloded(1,10); // 12345678910
intStream.filter(i->i%2==0).forEach(System.out::print); // 246810
intStream filter(i->i%2==0 && i%3!=0).forEach(System.out::print); // 2의 배수이고 3의 배수 xx
intStream filter(i->i%2!=0).filter(i->i%3!=0).forEach(System.out::print); // 2의 배수 아니고 3의 배수 아니고)
-> 중간연산 이어서 여러 번 쓸 수 있음. &&로 연결해도 되고 filter를 두 번 연속으로 써도 됨
3. 스트림 정렬하기 – sorted( )
- 지정된 Comparator로 스트림을 정렬하는데, Comparator대신 int값을 반환하는 람다식을 사용하는 것도 가능하다.
- Comparator를 지정하지 않으면 스트림 요소의 기본 정렬 기준(Comparable)으로 정렬 가능하다.(스트림의 요소가 Comparable을 구현한 클래스가 아니면 예외 발생)
Stream<T> sorted( ) | 스트림 요소의 기본 정렬(Comparable)로 정렬 |
Stream<T> sorted(Comparator< |
지정된 Comparator로 정렬(정렬 대상과 정렬 기준) |
문자열 스트림을 정렬하는 다양한 방법
문자열 스트림 정렬 방법 | 출력결과 |
strStream.sorted( ) // 기본정렬 strStream.sorted(Comparator.naturalOrder( )) // 기본정렬(기본정렬 메서드) strStream.sorted((s1,s2) -> s1.compareTo(s2)); // 람다식도 가능 strStream.sorted(String::compareTo); // 위의 식과 동일(메서드 참조) |
CCaaabbccdd |
strStream.sorted(Comparator.reverseOrder( )) // 기본 정렬의 역순 strStream.sorted(Comparator.<String>naturalOrder( ).reversed( )) |
ddccbaaaCC |
strStream.sorted(String.CASE_INSENSITIVE_ORDER) // 대소문자 구분 안함 | aaabCCccdd |
strStream.sorted(String.CASE_INSENSITIVE_ORDER.reverse( )) | ddCCccbaaa |
strStream.sorted(Comparator.comparing(String::length)) // 길이 순 정렬 strStream.sorted(Comparator.comparingInt(String::length)) // no 오토박싱 |
bddCCccaaa |
strStream.sorted(Comparator.comparing(String::length).reversed( )) // 길이 순 정렬의 역순 | aaaddCCccb |
static Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator( );
-> CASE-INSENSITIVE_ORDER는 comparator이다!(static)
- Comparator의 comparing( )으로 정렬 기준을 제공
comparing(Function<T, U> keyExtractor) comparing(Function<T, U> keyExtractor, Comparator<U> keyComparator) -> 반환타입이 Comparartor |
studentStream.sorted(Comparator.comparing(Student::getBan)).forEach(System.out::println) // 반별로 정렬
-> 메서드 참조를 람다식으로 (Student s) -> s.getBan( ) static이 아니니까 참조변수 s 필요
- 추가 정렬 기준을 제공할 때는 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);
-> 정렬기준 3개(반별, 총점별, 이름별)
예제) 학생의 성적정보를 요소로 하는 Stream<Student>을 반별로 정렬한 다음에, 총점별 내림차순으로 정렬한다.
import java.util.*;
import java.util.stream.Stream;
public class Ex14_5 {
public static void main(String[] args) {
Stream<Student> studentStream = Stream.of( // Student클래스를 Stream.of으로 Student 객체 스트림 생성
new Student("이자바", 3, 300),
new Student("김자바", 1, 200),
new Student("안자바", 2, 100),
new Student("박자바", 2, 150),
new Student("소자바", 1, 200),
new Student("나자바", 3, 280),
new Student("감자바", 3, 180));
// 람다식으로 변환 (Comparator.comparing((Student s)->s.getBan())
studentStream.sorted(Comparator.comparing(Student::getBan) // 1.반별정렬
.thenComparing(Comparator.naturalOrder())) // 2.기본정렬(내림차순으로 설정)
.forEach(System.out::println); // forEach 최종 연산
}
}
class Student implements Comparable<Student> {
String name;
int ban;
int totalScore;
Student(String name, int ban, int totalScore) { // Student의 생성자 생성
this.name = name;
this.ban = ban;
this.totalScore = totalScore;
}
@Override
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 ;
}
}
<출력값>
[김자바, 1, 200]
[소자바, 1, 100]
[박자바, 2, 150]
[안자바, 2, 100]
[이자바, 3, 300]
[나자바, 3, 280]
[감자바, 3, 180]
학생의 성적 정보를 요소로 하는 Stream<Student>을 반별로 정렬한 다음에, 총점별 내림차순으로 정렬한다. 정렬하는 코드를 짧게 하기 위해서 Comparable을 구현해서 총점별 내림차순 정렬이 Student의 기본 정렬이 되도록 했다.
4. 스트림 변환 – map( )
- 스트림의 요소에 저장된 값 중에서 원하는 필드만 뽑아내거나 특정 형태로 변환해야 할 때 사용하는 것
Stream<R> map(Function< -> 입력값 <T>를 받아서 출력값 < R>을 출력한다. |
-> 매개변수로 T타입을 R타입으로 변환해서 반환하는 함수를 지정해야 한다.
예) File의 스트림에서 파일의 이름만 뽑아서 출력하고 싶을 때, 아래와 같이 map( )을 이용해서 File객체에서 파일의 이름(String)만 간단히 뽑아낼 수 있다.
Stream<File> fileStream = Stream.of(new File(“Ex1.java”), new File(“Ex1”), new File(“Ex1.bak”), new File(“Ex2.bak”), new File(“Ex1.txt”)); Stream<String> filenameStream = fileStream.map(File::getName); filenameStream.forEach(System.out::println); // 스트림의 모든 파일의 이름을 출력 |
-> 메서드 참조(File::getName)를 람다식으로 변환 -> (File f)-> f.getName( ))
예) 파일 스트림(Stream<File>)에서 파일 확장자(대문자)를 중복없이 뽑아내기
fileStream.map(File::getName) // Stream<File> -> Stream<String> .filter(s -> s.indexOf(‘.‘)!=-1) // 확장자가 없는 것은 제외 .map(s->s.subString(s.indexOf(‘.’)+1)) // Stream<String> -> Stream<String> .map(String::toUpperCase) // 모두 대문자로 변환 .distinct( ) // 중복제거 .forEach(System.out::print) // JAVABAKTXT |
map(File::getName) 파일 객체 -> 파일 이름 .filter(s -> s.indexOf(‘.‘)!=-1) 파일 이름(문자열) 확장자가 없으면 -1 반환(없애기) .map(s->s.subString(s.indexOf(‘.’)+1)) 확장자 있는 파일 -> 확장자만 추출 .map(String::toUpperCase) 소문자를 -> 대문자로 |
예제) 파일 확장자만 출력해서 대문자로 변환하고 중복제거!
import java.io.File;
import java.util.stream.Stream;
public class Ex14_6 {
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") };
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) // Stream<File> -> Stream<String>
.filter(s->s.indexOf('.')!=-1) // filter로 확장자가 없는 것은 제외
.map(s->s.substring(s.indexOf('.')+1)) // subString 으로 확장자만 추출
.map(String::toUpperCase) // 모두 대문자로 변환
.distinct().forEach(System.out::print);
}
}
5. 스트림의 요소를 소비하지 않고 엿보기 – peek( )
- 연산과 연산 사이에 올바르게 처리되었는지 확인하기 위해 사용한다.
- forEach( )와 달리 스트림의 요소를 소비하지 않기 때문에 연산 사이에 여러 번 끼워 넣어도 문제가 되지 않는다.
Stream<T> peek(Consumer<? super T> action) | 중간 연산(스트림의 요소를 소비 x) |
void forEach(Consumer<? super T> action) | 최종 연산(스트림을 요소를 소비 o) |
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(“extension=%s%n”,s)) .forEach(System.our::println) |
Stream<File> -> Stream<String> 확장자가 없는 것은 제외 파일명을 출력한다. 확장자만 추출 확장자를 출력한다. 최종연산 스트림을 소비한다. |
-> peek을 디버깅 용도로 사용!
예제) map예제에서 peek만 추가!
import java.io.File;
import java.util.stream.Stream;
public 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") };
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))
.peek(s -> System.out.printf("extension = %s%n", s))
.map(String::toUpperCase)
.distinct()
.forEach(System.out::println);
}
}
<출력문>
Ex1.java
Ex1.bak
Ex2.java
Ex1
Ex1.txt
// 모든 파일의 이름을 출력(위)
// 스트림을 다시 생성해서 사용
filename = Ex1.java
extendsion = java
JAVA
filename = Ex1.bak
extendsion = bak
BAK
filename = Ex2.java
extension = java
filename = Ex1.txt
extendsion = txt
TXT
6. 스트림의 스트림을 스트림으로 변환 – flatMap( )
- 스트림의 요소가 배열이거나 map( )의 연산결과가 배열인 경우, 즉 스트림의 타입이 Stream<T[]>인 경우 flatMap( )을 사용하면 good
Stream<String[] strArrStrm = Stream.of(new String[ ] {“abc”, ”def”, ”ghi” }, new String[ ] {“ABC”, “GRI”, “JKLMN” }); |
-> 스트림의 요소 하나하나가 String 배열!
Stream<Stream<String>> strStrStrm = strArrStrm.map(Arrays::stream); |
-> 위의 식이 아래식으로 바뀌게 됨(그냥 map을 써서 스트림의 스트림이 되버림)
- Arrays::stream 메서드 참조를 람다식으로: (arr)-> Arrays.stream(arr); : 어떤 배열을 주면 Stream으로 변환!
Stream<String> strStrStrm = strArrStrm.flatMap(Array::stream) ; // Arrays.stream(T[ ]) |
-> 실제로 원하는 식(스트링의 스트림)
예제)
import java.util.Arrays;
import java.util.stream.Stream;
public class Ex14_7 {
public static void main(String[] args) {
Stream<String[]> strArrStrm = Stream.of(
new String[] {"abc", "def", "jkl"},
new String[] {"ABC", "GHI", "JKL"}
);
Stream<String> strStrm = strArrStrm.flatMap(Arrays::stream); // flatMap을 사용
strStrm.map(String::toLowerCase) // 스트림의 요소를 모두 소문자로 변경
.distinct() // 스트림의 요소 중복 제거
.sorted() // 정렬
.forEach(System.out::println);
System.out.println();
String[] lineArr = {
"Believe or not It is true", // 단어 하나하나가 스트림의 요소로 만들고 싶음
"Do or do not There is no try"
};
Stream<String> lineStream = Arrays.stream(lineArr);
lineStream.flatMap(line -> Stream.of(line.split(" +"))) //(1)
.map(String::toLowerCase)
.distinct()
.sorted()
.forEach(System.out::println);
}
}
<출력문>
abc
def
ghi
jkl
believe
do
is
it
no
not
or
there
true
try
(1)
-> line.split(“ +”)) : Stirng -> String 배열 생성, (“ +”) -> 정규식으로 하나 이상의 공백을 표현
split으로 문장을 나눠서 요소가 단어인 스트림을 생성하기 위해
Optional<T>
- Optional<T>는 지네릭 클래스로 ‘T타입의 객체’를 감싸는 래퍼클라스
-> Optional 타입의 객체에는 모든 타입의 참조변수를 담을 수 있다.
-> 최종 연산의 결과를 그냥 반환 하는게 아니라 Optional객체에 담아서 반환한다.
-> 이렇게 객체에 담아서 반환하면, 반환된 결과가 null인지 if문으로 확인을 안해도 Optional에 정의된 메서드(객체)를 통해서 간단히 처리 가능
-> null은 직접적으로 다루면 NullPointerException 예외가 발생한다. 그래서 원래 예외를 발생시키지 않기 위해서 if문을 적었지만 Optional에 정의된 메서드를 사용하면 보다 간결하고 안전한 코드를 작성하는 것이 가능!
-Optional<T>객체를 생성하는 다양한 방법 (of, ofNullable)
String str = “abc”; Optional<String> optVal = Optional.of(str); Optional<String> optVal = Optional.of(“abc”); Optional<String> optval = Optional.of(null); // NullPointerException 발생 Optional<String> optVal = Optional.ofNullable(null); // OK |
-> 만일 참조변수의 값이 null일 가능성이 있으면, of( ) 대신에 ofNullable( )을 사용해야 한다.
-> of( )는 매개변수의 값이 null 이면 NullPointerException이 발생하기 때문!
- null대신 빈 Optional<T> 객체를 사용하기
Optional<String> optVal = null; // 널로 초기화. 바람직하지 않음 Optional<String> optVal = Optional.<String>empty( ); // 빈 객체로 초기화 Optional<String> optVal = Optional.empty( ); // <String> 생략가능!! |
ex) Object 배열을 초기화
Object[ ] objArr = null; (바람직하지 않음) <-> Object[ ] objArr = new Object[0]; (바람직함)
- Optional객체의 값 가져오기 – get( ), orElse( ), orElseGet( ), orElseThrow( )
Optional<String> optVal = Optional.of(“abc”); String str1 = optVal.get( ); // optVal에 저장된 값을 반환, nulll이면 예외발생 String str2 = optVal.orElse(“ “); // optVal에 저장된 값이 null일 때는, “ “를 반환 String str3 = optVal.orElseGet(String::new); // 람다식 사용가능 ( ) -> new String( ) String str4 = optVal.orElseThrow(NullPointerException::new); // 널이면 예외발생 |
> orElse( )와 orElseGet( )을 많이 사용!
T orElseGet(Supplier<? extends T> other)
T orElseThrow(Supplier<? extends X> exceptionSupplier)
- isPresent( ) – Optional 객체의 값이 null이면 false, 아니면 true를 반환
if(Optional.ofNullable(str).isPresent( )) { // if(str!=null) { System.out.println(str); } -> 위의 isPresent식을 ifPresent로 작성 /ifPresent(Consumer<T> block)– 널이 아닐 때만 작업수행, 널이면 아무 일도 안함 Optional.ofNullableI(str).ifPresent(System.out::println); |
예제)
import java.util.NoSuchElementException;
import java.util.Optional;
public class OptionalEx {
public static void main(String[] args) {
int[] arr = new int[0];
System.out.println("arr.length = " + arr.length);
Optional<String> opt = Optional.empty();
System.out.println("optArr = " + opt); // optArr = Optional.empty
// System.out.println("optArr.get( )) = " + opt.get()); // NoSuchElementException발생
String str = "";
// .get( )을 사용 try catch 구문 -> 번거로움
try {
str = opt.get();
} catch (NoSuchElementException e) { // Exception e도 가능
str = ""; // 예외가 발생하면 빈문자열 출력
} // 예외발생
System.out.println("str = " + str); // str =
// .orElse( )를 사용
str = opt.orElse(""); // Optional에 저장된 값이 null이면 "반환"
System.out.println("str = " + str); // str =
// .orElseGet( )을 사용 아래 세 코드 모두 같은 코드 "", new String( ), String::new
// str = opt.orElseGet(()->""); // 빈문자열 람다식으로 작성
// str = opt.orElseGet(()->new String());
str = opt.orElseGet(String::new); // 메서드 참조
System.out.println("str = " + str); // str =
-OptionalInt, OptionalLong, OptionalDouble
- 기본형 값을 감싸는 래퍼클래스 (성능 때문에 Optional<T>대신 사용)
public final class OptionalInt { … private final boolean isPresent; // 값이 저장되어 있으면 true, private final int values; // int타입의 변수 |
-OptionalInt의 값 가져오기
Optional클래스 | 값을 반환하는 메서드 |
Optional<T> | T get( ) |
OptionalInt | int getAsInt( ) |
OptionalLong | long getAsLong( ) |
OptionalDouble | double getAsDouble( ) |
- 빈 Optional 객체와의 비교
OptionalInt opt = OptionalInt.of(0); // OptionalInt에 0을 저장 OptionalInt op2 = OptionalInt.empty( ) // OptionalInt에 0을 저장(아무 값도 저장 안함) System.out.println(opt.isPresent( )); // true System.out.println(opt2.isPresent( )); // false // isPresent( )는 객체의 값이 null이면 false 반환 System.out.println(opt.equals(opt2)); // false(value는 같지만 isPresent( )값까지 같아야 true) |
예제)
public class Ex14_8 {
public static void main(String[] args) {
Optional<String> optStr = Optional.of("abcd");
Optional<Integer> optInt = optStr.map(String::length); // (s)->s.length());
System.out.println("optStr = " + optStr.get()); // optStr = abcd
System.out.println("optInt = " + optInt.get()); // optInt = 4
int result1 = Optional.of("123")
.filter(x->x.length() > 0) // fiter()로 문자열의 크기가 0보다 큰지 확인
.map(Integer::parseInt).get(); // 문자열 -> 숫자형
System.out.println("result1 = " + result1); // result1 = 123
int result2 = Optional.of("")
.filter(x->x.length() > 0) // false(빈문자열이기 때문)
.map(Integer::parseInt).orElse(-1); // map()이 false이면, orElse()를 이용해서 -1을 반환
System.out.println("result2 = " + result2); // result2 = -1
Optional.of("456").map(Integer::parseInt)
.ifPresent(x->System.out.printf("result3=%d%n", x)); // result3=456
// ifPresent는 null이 아닐때만 실행
OptionalInt optInt1 = OptionalInt.of(0); // 0을 저장
OptionalInt optInt2 = OptionalInt.empty(); // 빈 객체를 생성
System.out.println(optInt1.isPresent()); // true (0이라는 값이 있다)
System.out.println(optInt2.isPresent()); // false
System.out.println(optInt1.getAsInt()); // 0
// System.out.println(optInt2.getAsInt()); // NoSuchElementException
System.out.println("optInt1 = " + optInt1); // optInt1 = OptionalInt[0]
System.out.println("optInt2 = " + optInt2); // optInt2 = OptionalInt.empty
System.out.println("optInt1.equals(optInt2) ? " + optInt1.equals(optInt2)); // false
}
}
'멀티캠퍼스 풀스택 과정 > Java의 정석' 카테고리의 다른 글
자바의 정석11-6 스트림의 그룹화와 분할(groupingBy, partitioningBy) (0) | 2022.02.02 |
---|---|
자바의 정석11-5 스트림의 연산(최종연산) (0) | 2022.02.02 |
자바의 정석11-3 스트림(Stream), 스트림 생성 (0) | 2022.01.16 |
자바의 정석11-2 java.util.Function 패키지의 함수형 인터페이스와 메서드 참조 (0) | 2022.01.16 |
자바의 정석11-1 람다와 함수형인터페이스 (0) | 2022.01.16 |