일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- 깃 연동
- 모두의네트워크
- 깃허브 토큰 인증
- 문자열
- HTTP
- 모두를 위한 딥러닝
- 리액트 네이티브 프로젝트 생성
- 팀플회고
- 정리
- React Native
- 백준 4949번
- 백준
- 데이터베이스
- 지네릭스
- 백준 4358번
- 리액트 네이티브
- 머신러닝
- 딥러닝
- 깃허브 로그인
- 모두의 네트워크
- 깃 터미널 연동
- 백준 4358 자바
- 백준 5525번
- 리액트 네이티브 시작하기
- 스터디
- 네트워크
- SQL
- 모두를위한딥러닝
- 데베
- 자바
- Today
- Total
솜이의 데브로그
Chapter 14 ) 람다와 스트림 본문
출처 : Java의 정석
1. 람다식 (Lambda expression)
- 람다식의 도입으로 인해 자바는 객체지향언어인 동시에 함수형 언어가 되었다.
람다식이란?
- 메서드를 하나의 '식(expression)'으로 표현한 것이다.
- 메서드의 이름과 반환값이 없어지므로, 람다식을 '익명 함수'라고도 한다.
- 메서드의 매개변수로 전달되어지는 것이 가능하고, 메서드의 결과로 반환될 수도 있다.
ex)
int[] arr = new int[5];
Arrays.setAll(arr, (i) -> (int)(Math.random()*5) + 1);
함수형 인터페이스 (Functional Interface)
- 람다식을 다루기 위한 인터페이스
- 단, 함수형 인터페이스에는 오직 하나의 추상 메서드만 정의되어 있어야 한다.
- 그래야 람다식과 인터페이스의 메서드가 1:1로 연결
- static 메서드와 default 메서드의 개수에는 제약 X
- ex) 내가 자주 쓰는 list 정렬 방법
Collections.sort(list, (s1, s2) -> s2.compareTo(s1));
람다식의 타입과 형변환
- 함수형 인터페이스로 람다식을 참조할 수 있는 것일 뿐, 람다식의 타입이 함수형 인터페이스의 타입과 일치하는 것은 아니다.
- 람다식은 익명 객체이고 익명 객체는 타입이 없다.
- 따라서 대입 연산자의 양변의 타입을 일치시키기 위해 형 변환이 필요하다.
- ex)
MyFunction f = (MyFunction) (() -> {});
메서드 참조
- 람다식이 하나의 메서드만 호출하는 경우에는 메서드참조로 람다식을 간략히 할 수 있다.
- ex)
//원래 메서드
Integer wrapper(String s) {
return Integer.parseInt(s);
}
// 람다식
Function<String, Integer> f = (String s) -> Integer.parseInt(s);
//메서드 참조
Function<String, Integer> f = Integer::parseInt;
★ 하나의 메서드만 호출하는 람다식은 '클래스이름::메서드이름' 또는 '참조변수::메서드이름'으로 바꿀 수 있다.
2. 스트림(stream)
스트림이란?
- 데이터소스마다 다른 방식으로 다뤄야한다.
- 자바에서 데이터를 다룰 때, 컬렉션이나 배열에 데이터를 담고 for문이나 Iterator 를 이용했었는데, 이러한 방식은 너무 길고 재사용성이 떨어진다.
- 이러한 문제점들을 해결하기 위해 Stream 을 사용한다.
- 스트림은 데이터소스를 추상화하고, 데이터를 다루는데 자주 사용되는 메서드들을 정의해두었다.
- 데이터소스를 추상화했다는 것은, 데이터 소스가 무엇이든간에 같은 방식으로 다룰 수 있게 되었다는 것과 코드의 재사용성이 높아진다는 것을 의미한다.
예시를 살펴보자
String[] strArr = {"aaa", "ddd", "ccc"};
List<String> strList = Arrays.asList(strArr);
위의 두 배열과 List가 있을 때, 두 데이터소스를 기반으로하는 스트림을 생성하고, 정렬후 화면에 출력하는 코드는 다음과 같다.
(스트림을 쓰지 않은 경우)
Arrays.sort(strArr);
Collections.sort(strList);
for(String str : strArr)
System.out.println(str);
for(String str : strList)
System.out.println(str);
스트림 사용한 경우
Stream<String> strStream1 = strList.stream();
Stream<String> strStream2 = Arrays.stream(strArr);
strStream1.sorted().forEach(System.out::println);
strStream2.sorted().forEach(System.out::println);
훨씬 간결해졌다!
- 스트림은 데이터소스를 변경하지 않는다.
- 데이터소스로부터 데이터를 읽기만할 뿐, 데이터소스를 변경하지 않는다.
- 스트림은 일회용이다.
- 스트림은 작업을 내부 반복으로 처리한다.
- 반복문을 메서드의 내부에 숨길 수 있다는 것을 의미한다.
- forEach()는 스트림에 정의된 메서드 중 하나로 매개변수에 대입된 람다식을 데이터소스의 모든 요소에 적용한다.
중간연산 : 연산 결과가 스트림인 연산. 스트림에 연속해서 중간 연산할 수 있음
ex) distinct, filter, limit, skip, peek, sorted, map, flatMap ...
최종 연산 : 연산결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 단 한번만 가능
ex) forEach, count, max, min, findAny, findFirst, allMatch, anyMatch, toArray, reduce, collect
- 지연된 연산
- 최종연산이 수행되기 전까지는 중간 연산이 수행되지 않는다.
- 즉, 중간 연산을 호출하는 것은 최종 연산이다
- Stream<Integer> 보다는 IntStream 이 효율적
- 병렬 스트림
- parallel() 메서드 호출
스트림 만들기
컬렉션
- Collection에 stream() 이 정의되어 있다.
- 따라서 Collection의 자손인 List와 Set을 구현한 컬렉션 클래스들은 모두 이 메서드로 스트림을 생성할 수 있다.
- stream()은 해당 컬렉션을 소스로 하는 스트림을 반환한다.
Stream<T> Collection.stream()
배열
- Stream<T> Stream.of(T... values)
- Stream<T> Stream.of(T[])
- Stream<T> Arrays.stream(T[])
- Stream<T> Arrays.stream(T[] array, int startInclusive, int endExclusive)
- 예시
- Stream<String> strStream = Arrays.stream(new String[]{"a", "b", "c"});
특정 범위의 정수
- IntStream과 LongStream은 다음과 같이 지정된 범위의 연속된 정수를 스트림으로 생성해서 반환하는 range()와 rangeClosed()를 가지고 있다.
- IntStream.range(int begin, int end) : end 가 범위에 포함되지 않음
- IntStream.rangeClosed(int begin, int end) : end 가 범위에 포함됨
임의의 수
- 무한 스트림 -> limit() 도 같이 사용해 스트림의 크기를 제한해주어야함
- ints()
- longs()
- doubles
- 유한 스트림
- ints(long streamSize)
- longs(long streamSize)
- doubles(long streamSize)
람다식
- iterate()
- generate()
- 위 두가지로 생성된 스트림은 기본형 스트림타이의 참조변수로 다룰 수 없다. 따라서 아래와 같이 써야함
Intstream evenStream = Stream.iterate(0, n->n+2).mapToInt(Integer::valueOf);
Stream<Integer> stream = evenStream.boxed();
스트리의 중간 연산
스트림 자르기
- skip() : 처음 요소들 건너뛰기
- limit() : 스트림 요소 개수 제한
스트림의 요소 걸러내기
- filter() : 주어진 조건에 맞지 않는 요소 걸러냄
- distinct() : 중복된 요소 제거
정렬
- sorted() : 지정된 Comparator로 스트림을 정렬
- Comparator를 지정하지 않으면 스트림 요소의 기본 정렬 기준(Comparable)으로 정렬
예시 : 학생스트림을 반(ban) 별, 성적(totalScore)순, 그리고 이름(name)순으로 정렬하여 출력하려면 다음과 같이 한다.
sutdentStream.sorted(Comparator.comparing(Student::getBan)
.thenComparing(Student::getTotalScore)
.thenComparing(Student::getName)
.forEach(System.out::println);
변환 - map()
- 스트림의 요소에 저장된 값 중에서 원하는 필드만 뽑아내거나 특정 형태로 변환해야할 때
- 매개변수로 T타입을 R타입으로 변환해서 반환하는 함수 지정
Stream<R> map(Function<? super T,? extends R> mapper)
조회 - peek()
- forEach()와 달리 스트림의 요소를 소모하지 않으므로 연산 사이에 여러번 끼워넣어도 문제가 되지 않는다.
- filter()나 map()의 결과를 확인할 때 유용하게 사용될 수 있다.
mapToInt(), mapToLong(), mapToDouble()
- Stream<T> 타입보다 효율적
- intStream은 count()만 지원하는 Stream<T> 와 달리 다른 메서드들 제공
- sum()
- average()
- max()
- min()
flatMap()
- Stream<T[]> 를 Stream<T> 로 변환
스트림의 최종 연산
최종연산은 스트림의 요소를 소모해서 결과를 만들어낸다.
따라서 최종연산 후에는 스트림이 닫히게 되고, 더 이상 사용할 수 없다.
최종연산의 결과는 스트림 요소의 합과 같은 단일 값이거나, 스트림의 요소가 담긴 배열 또는 컬렉션일 수 있다.
- forEach()
- 조건 검사 - allMatch(), anyMatch(), noneMatch(), findFirst(), findAny()
- 통계 - count(), sum(), average(), max(), min()
- 리듀싱 - reduce()
- 처음 두 요소를 가지고 연산한 결과를 가지고 그 다음 요소와 연산한다.
collect()
컬렉터는 Collector 인터페이스를 구현한 것으로, 직접 구현할 수도 있고 미리 작성된 것을 사용할 수도 있다.
collect() : 스트림의 최종 연산, 매개변수로 컬렉터를 필요로 한다.
Collector : 인터페이스, 컬렉터는 이 인터페이스를 구현해야 한다.
Collectors : 클래스, static 메서드로 미리 작성된 컬렉터를 제공한다.
스트림을 컬렉션과 배열로 변환
- toList()
- toSet()
- toMap()
- toCollection()
- toArray()
List<String> names = stuStream.map(Student::getName)
.collect(Collectors.toList());
ArrayList<String> list = names.stream()
.collect(Collectors.toCollection(ArrayList::new));
Map<String, Person> map = personStream
.collect(Collectors.toMap(p->p.getRegId(), p->p));
문자열 결합 - joining()
- 문자열 스트림의 모든 요소를 하나의 문자열로 연결해서 반환
그룹화와 분할 - groupingBy(), partitioningBy()
- 추가로 공부하기..
Collector 구현하기
컬렉터를 작성한다는 것은 Collector 인터페이스를 구현한다는 것을 의미한다.
public interface Collector<T, A, R> {
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
BinaryOperator<A> combiner();
Function<A, R> finisher();
Set<Characteristics> characteristics();
}
- supplier() : 작업 결과를 저장할 공간을 제공
- accumulator() : 스트림의 요소를 수집할 방법을 제공
- combiner() : 두 저장공간을 병합할 방법을 제공 (병렬 스트림)
- finisher() : 결과를 최종적으로 변환할 방법을 제공
스트림의 변환
햐,,, 너무 어렵다 자바...
지금까지 자바는 구구절절 언어라고 생각했는데 그냥 내가 이런것들을 사용할 줄 몰랐던것 뿐..
적절하게 사용하려면 많이 사용해보고 기억해야할 것 같다!!
언제쯤 자바의정석을 읽으면서 다 이해할 수 있을까 ㅠㅡㅠ
이번 챕터에서 더 공부해야 할 것
제네릭스 개념
java.util.function 패키지
predicate
groupingBy(), partitioningBy()
Collectors 인터페이스 구현
'책을 읽자 > Java의 정석' 카테고리의 다른 글
Chapter 12 ) 지네릭스, 열거형, 애너테이션 (0) | 2021.09.17 |
---|---|
Chapter 11 ) 컬렉션 프레임웍 (Collections Framework) (0) | 2021.09.17 |
Chapter 09 ) java.lang 패키지와 그 외 클래스 (0) | 2021.09.11 |
Chapter 08 ) 예외처리 (0) | 2021.09.10 |
Chapter 07 ) 객체지향프로그래밍(2) (0) | 2021.09.08 |