본문 바로가기
멀티캠퍼스 풀스택 과정/Java의 정석

자바의 정석11-4 스트림의 연산(중간연산과 Optioanl<T>)

by 이쟝 2022. 1. 16.

스트림의 연산(중간연산, 최종연산)

중간연산

중간 연산 설명
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<? super T> 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<? super T, ? extends R> mapper)  // Stream<T> - Stream<R>
-> 입력값 <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( ))

 

map 의 역할은 File스트림의 getName( )을 받아서 String스트림으로 변환

 

예) 파일 스트림(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[ ])

 

-> 실제로 원하는 식(스트링의 스트림)

 

map을 썼을 때의 구조

 

flatMap을 썼을 때의 구조

 

예제) 

 

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);      // OptionalInt0을 저장
OptionalInt op2 = OptionalInt.empty( )  // OptionalInt0을 저장(아무 값도 저장 안함)
 
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
		
	}
}