Java Stream 사용법

Jang990·2023년 2월 2일
0

22년 09월 03일에 작성된 글

Optional관련 사용방법을 찾던 도중 Stream과 같은 Optional<T>.map() 메소드를 제공한다 했다. 그런데 Stream이 이해가 안되고 이전에 백준을 풀면서 아래와 같은 코드들도 이해가 안되서 이참에 Stream을 공부해봤다.

Arrays.stream(numberStr.split(" ")).mapToLong(Long::parseLong).toArray()

Stream 사용법

스트림은 다음과 같이 다양한 방식으로 만들 수 있다.

int[] numArr = {0, 1, 2, 3, 4, 5};
List<String> strList = Arrays.asList("a", "b", "c");
Stream<String> stm1 = strList.stream();
Stream<Integer> stm2 = Stream.iterate(0, n->n+2);
IntStream istm = Arrays.stream(numArr);

스트림은 Stream 생성 -> 중간연산 -> 최종연산 으로 진행된다.

중간연산은 연산결과가 스트림인 연산이고 여러번 적용할 수 있다.

최종연산은 연산결과가 스트림이 아니고 스트림의 모든 요소를 소모하여 단 한 번만 적용가능한 연산이다.

진행과정은 다음과 같다.

stream //이미 생성되어있는 스트림 객체
    .distinct().limit(5).sorted() // 중간연산 (중복제거->5개짜르기->정렬)
    .forEach(System.out::println); // 최종연산(출력)

Stream의 특징

스트림은 읽기만(ReadOnly) 할 뿐 원본을 변경하지 않는다.

List<Integer> list = Arrays.asList(3, 2, 5, 4, 2);
List<Integer> sortedList = list.stream()
		.sorted()
		.collect(Collectors.toList());
//원본인 list는 변경되지 않는다.

스트림은 일회용이다. 다음과 같이 재사용할 수 없다.(필요하면 다시 스트림을 생성)

strStream.forEach(System.out::println); // 최종연산 수행
int numOfStr = strStream.count(); // 에러. 스트림이 이미 닫힘

최종 연산 전까지 중간연산이 수행되지 않는다. - 지연된 연산

//로또번호 출력 예시
IntStream intStream = new Random().ints(1,46) // 1~45범위의 무한 스트림 - 지금은 그렇구나 하고 넘어가라
intStream.distinct().limit(6).sorted() 
    .forEach(i->System.out.println(i+","))

스트림은 작업을 내부 반복으로 처리 - 코드 간결

//for(String str : strList)
//    System.out.println(str);
//위와 같은 연산을 아래처럼 수행
stream.forEach(System.out::println);

... 생략

Stream 생성

// 객체 배열 스트림 생성
Stream<String> strSteam1 = Stream.of("a", "b", "c");
Stream<String> strSteam2 = Stream.of(new String[]{"a", "b", "c"});
Stream<String> strSteam3 = Arrays.stream(new String[]{"a", "b", "c"});
Stream<String> strSteam4 = Arrays.stream(new String[]{"a", "b", "c"}, 0, 3); //0~2 즉 a,b,c 가 들어감 

//기본형 배열 스트림 생성
int[] numArr = {0, 1, 2, 3, 4, 5};
IntStream istm1 = Arrays.stream(numArr);
IntStream istm2 = IntStream.of(numArr);

무한 스트림 부분은 건너뛰겠다.



Stream 연산

중간 연산

stream.filter(); // 조건에 안맞는 요소 제외 (중간연산)
stream.distinct(); // 중복제거 (중간연산)
stream.sort(); // 정렬 (중간연산)
stream.limit(5); // 스트림 자르기 (중간연산)
stream.skip(3); // 요소 건너뛰기
stream.map(); // 요소 변환 (중간연산) - 좀 어려움 아래서 살펴봄


//스트림이 1, 2, 3, 4, 5, 6, 7, 8의 요소를 가진다고 가정
// 스트림에서 3개의 요소를 건너뛰고 5개를 자르기 때문에 
// 4,5,6,7,8 스트림이 나온다.
stream.skip(3).limit(5);
//2의 배수만을 가진 스트림이 나온다. 2,4,6,8
stream.filter(i->i%2==0)

최종연산

stream.count(); // 요소 수 세기(최종연산)

//Optional 관련
stream.max(); 
stream.min();

이외에도 다른 중간연산 최종연산이 있다.



Stream.map(), Stream.flatMap()

일단 백준 알고리즘과, Optional쪽에서 자주 사용하는 map() 부분의 원리만 보고 나중에 필요할 때 다시 공부한다.


스트림의 요소 변환하기 - map()

Stream<File> fileStream = Stream.of(new File("Ex1.java"), new File("Ex1"), new File("Ex1.bak"), new File("Ex2.java"), new File("Ex1.txt"));
//Stream<File>에서 File에 이름을 Stream<String>으로 만듦
//Stream<File> -> Stream<String>
Stream<String> fileNameStream = fileStream.map(File::getName);
fileNameStream.forEach(System.out::println);

만약 다음 예시에서 파일 확장자(대문자)를 중복없이 뽑아내고 싶다면 다음과 같은 코드를 작성하면 된다.

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) // 대문자 Stream<String> -> Stream<String>
    .distinct() // 중복제거
    .forEach(System.out::print);

Stream.flatMap()

Stream strArr = Stream.of(new String[]{"abc", "def"},
                        new String[]{"ABC", "DEF"});
Stream<Stream<String>> strStrStrm1 = strArr.map(Arrays::stream); //우리가 원하는 형식이 아니다
// abc, def, ABC, DEF 를 원한다면 아래와 같이 사용하면 된다.
Stream<String> strStrStrm2 = strArr.flatMap(Arrays::stream);


내용출처: https://www.youtube.com/watch?v=7Kyf4mMjbTQ



메소드 참조 ex)Long::parseLong

메소드 참조는 람다 표현식이 단 하나의 메소드만을 호출하는 경우에 해당 람다 표현식에서 불필요한 매개변수를 제거하고 사용할 수 있독 해준다. 즉 아래와 같은 두 코드는 같은 코드이다.

//람다 표현식
(base, exponent) -> Math.pow(base, exponent);
//메소드 참조
Math::pow;

내용출처: http://www.tcpschool.com/java/java_lambda_reference



Stream을 이용하여 int[]형식을 List로 변환

배열을 List로 변환할 때, Arrays.asList() 메소드를 사용하면 된다.

하지만, 배열의 원소가 int와 같은 primitive type인 경우 Arrays.asList()는 좀 다른 결과를 리턴한다.

List<int> 형식의 리스트를 얻기를 원하지만 Array.asList()를 사용하면 다음과 같은 List<int[]> 형식을 반환한다.

// int 배열        
int[] arr = { 1, 2, 3 };

// Arrays.asList()         
List<int[]> intList = Arrays.asList(arr);

// 결과 출력
System.out.println(intList.size()); // 1
System.out.println(intList.get(0)); // I@71bb301
System.out.println(Arrays.toString(intList.get(0)));  // [1, 2, 3]

하지만 다음과 같이 stream을 사용하여 변환할 수 있다.

int[] arr = { 1, 2, 3 };
// int -> List
List<Integer> intList = Arrays.stream(arr)
                        .boxed() // int를 Integer로 변환                
                        .collect(Collectors.toList()); //toSet() 등등이 있음

// List 출력        
System.out.println(intList.size()); // 3
System.out.println(intList); // [1, 2, 3]

출처: https://hianna.tistory.com/552 [어제 오늘 내일:티스토리]

profile
공부한 내용을 적지 말고 이해한 내용을 설명하자

0개의 댓글