자바의정석 Chapter14. 람다와 스트림
람다식
- 함수형 언어
- 메서드를 하나의 식으로 표현한 것
- 메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로 익명함수라고도 합니다.
- 메서드를 선언하기 위해서는 클래스를 만들고, 객체도 생성해야 했지만 람다식을 사용하면 이러한 과정없이 메서드를 사용할 수 있습니다.
람다식 작성하기
메서드에서 이름과 반환타입을 제거하고 매개변수 선언부와 몸통{} 사이에 -> 를 추가합니다.
[예제]
//일반 메서드
int max(int a, int b) {
return a > b ? a : b;
}
//람다함수
(int a, int b) -> {return a > b ? a : b;}
함수형 인터페이스
- 람다식은 익명 클래스의 객체와 동등합니다.
타입 f = (int a, int b) -> a > b ? a : b; // 참조변수의 타입을 뭘로 해야 할까?
참조변수 f의 타입은 클래스 또는 인터페이스이어야 하며 람다식과 동등한 메서드가 정의 되어 있는 것이어야 합니다.
interfae MyFunction {
public abstract int max(int a, int b);
}
위의 인터페이스를 구현한 익명 클래스의 객체는 다음과 같이 생성할 수 있습니다.
MyFunction f = new MyFunction() {
public int max(int a, int b ) {
return a > b ? a : b;
}
};
int big = f.max(5,3);
위의 인터페이스를 람다식으로 정의 해보겠습니다.
MyFunction f = (int a, int b) -> a > b ? a : b;
int big = f.max(5, 3);
하나의 메서드가 선언된 인터페이스를 정의해서 람다식을 다루는 것은 기존의 자바의 규칙들을 어기지 않으면서 자연스럽습니다.
람다식을 다루기 위한 인터페이스를 함수형 인터페이스 라고 부릅니다.
단, 함수형 인터페이스에서는 오직 하나의 추성 메서드만 정의되어 있어야 한다는 제약이 있기 때문에 @FunctionlInterface 어노테이션을 선언 해 줍니다.
스트림
- 스트림은 컬렉션이던 배열이던 메서드를 동일한 방식으로 다룰 수 있도록 정의 하였습니다.
- 스트림을 이용하면 배열, 컬렉션, 파일에 저장된 데이터를 같은 방식으로 다룰 수 있습니다.
- 스트림은 데이터 소스를 변경하지 않습니다.
- 스트림은 일회용 입니다.
- 스트림은 작업을 내부 반복으로 처리 합니다.
문자열 배열과 같은 내용의 문자열을 젖아하는 List가 있을 때,
String[] strArr = {"a","b","c"}; // 배열
List<String> strList = Arrays.asList(strArr); // 컬렉션
이 두 데이터 소스의 스트림은 다음과 같이 생성 합니다.
Stream<String> arrStream = Arrays.stream(strArr); // 배열 스트림
String<String> listStream = strList.stream(); // 컬렉션 스트림
이 두 스트림으로 데이터 소스의 데이터를 읽어서 정렬하고 화면에 출력하는 방법은 다음과 같습니다.
arrStream.sorted().forEach(System.out:;println);
listStream.sorted().forEach(System.out:;println);
정렬된 결과를 새로운 List에 담아서 반환 하는 방법은 다음과 같습니다.
List<String> sortedList = strStream.sorted().collect(Collectors.toList());
스트림 연산
스트림이 제공하는 연산은 중간 연산과 최종 연산으로 분류 됩니다.
- 중간 연산 - 연산 결과가 스트림인 연산. 스트림에 연속해서 중간 연산할 수 있음
- 최종 연산 - 연산 결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 단 한번만 가능
스트림에 정의된 중간 연산은 다음과 같습니다.
중간연산 | 설 명 |
---|---|
Stream |
중복 제거 |
Stream |
조건에 안 맞는 요소 제외 |
Stream |
스트림의 일부를 잘라낸다. |
Stream |
스트림의 일부를 건너뛴다. |
Stream |
스트림의 요소에 작업수행 |
Stream |
스트림의 요소를 정렬합니다. |
Stream |
위와 동일 |
Stream |
스트림의 요소를 변환한다. |
DoubleStream mapToDouble(ToDoubleFunction<T. mapper) | 위와 동일 |
IntStream mapToInt(ToIntFunction |
위와 동일 |
LongStream mapToLong(ToLongFunction |
위와 동일 |
Stream |
위와 동일 |
DoubleStream flatMapToDouble(Function<T, DoubleStream> m) | 위와 동일 |
IntStream flatMapToInt(Function<T,IntStream> m) | 위와 동일 |
LongStream flatMapToLong(Function<T,LongStream> m) | 위와 동일 |
sorted()
- 지정된 Comparator로 스트림을 정렬할 수 있습니다.
- 지정하지 않을 경우 기본 정렬 기준으로 정렬 합니다.
- 람다식을 사용하여 정렬할 수 있습니다.
Stream
문자열 스트림 정렬 방법 | 출력결과 |
---|---|
strStream.sorted() // 기본정렬 | CCaaabbccdd |
strStream.sorted(Comparator.naturalOrder()) // 기본정렬 | CCaaabbccdd |
strStream.sorted((s1, s2) -> s1.compareTo(s2)); // 람다식 사용 | CCaaabbccdd |
strStream.sorted(String:compareTo); | CCaaabbccdd |
strStream.sorted(Comparator.reverseOrder()) | ddccbaaaCC |
strStream.sorted(Comparator. |
ddccbaaaCC |
strStream.sorted(String.CASE_INSENSITIVE_ORDER) // 대소문자 구분안함 | aaabCCccdd |
strStream.sorted(Comparator.comparing(String::length)) // 길이 순 정렬 | bddCCccaaa |
strStream.sorted(Comparator.comparing(String::length).reversed()) | aaaddCCccb |
BookVo 클래스를 이용한 정렬 사용 방법 및 실행 결과를 확인해 보겠습니다.
public class BookVo {
private int num;
private String title;
private String author;
public BookVo (int num, String title, String author){
this.num = num;
this.title = title;
this.author = author;
}
}
public static void main(String[] args) {
List<BookVo> bookList = new ArrayList<BookVo>();
bookList.add(new BookVo(1, 김, 김);
bookList.add(new BookVo(2, 악, 악);
bookList.add(new BookVo(2, 스, 스);
Stream<BookVo> stream = bookList.stream();
stream.sorted(Comparator.comparing(BookVo::getTitle)).forEach(System.out::println);
stream.sorted(Comparator.comparing(BookVo::getTitle).reversed().thenComparing(BookVo::getAuthor)).forEach(System.out::println);
// 스트림 사용
Stream<BookVo> stream2 = bookList.stream();
bookList = stream2.sorted(Comparator.comparing(BookVo::getTitle)).collect(Collectors.toList());
// 람다 사용
bookList.sort((BookVo o1, BookVo o2) -> { return o1.getTitle().compareTo(o2.getTitle()) < 0 ? 1 : -1;});
// comparator 인터페이스 사용
bookList.sort(new Comparator<BookVo>() {
@Override
public int compare(BookVo o1, BookVo o2) {
String title1 = o1.getTitle();
String title2 = o2.getTitle();
if(title1.compareTo(title2) == 0) {
return 0;
} else if(title1.compareTo(title2) > 0) {
return 1;
}else {
return -1;
}
// TODO Auto-generated method stub
}
});
}
스트림에 정의된 최종 연산은 다음과 같습니다.
최종연산 | 설 명 |
---|---|
void forEach(Consumer<? super T> action | 각 요소에 지정된 작업 수행 |
void forEachOrdered(Consumer<? super T> action) | 위와 동일 |
long count() | 스트림의 요소의 개수 반환 |
Optional |
스트림의 최대값 반환 |
Optional |
스트림의 최소값 반환 |
Optional |
스트림의 아무 요소 하나를 반환 |
Optional |
스트림의 첫 번째 요소를 반환 |
boolean allMatch(Predicate |
주어진 조건을 모두 만족하는지 확인 |
boolean anyMatch(Predicate |
주어진 조건을 하나라도 만족하는지 확인 |
boolean noneMatch(Predicate |
주어진 조건을 모두 만족하지 않는지 확인 |
Object[] toArray() | 스트림의 모든 요소를 배열로 반환 |
A[] toArray(IntFunction<A[]> generator) | 스트림의 모든 요소를 배열로 반환 |
Optional |
스트림의 요소를 하나씩 줄여가면서 계산한다. |
T reduce(T identity, BinaryOperator |
위와 동일 |
U reduce(U identity, BiFunction<U,T,U> accumulator, BinaryOperator combiner) | 위와 동일 |
R collect(Collector<T,A,R> collector) | 스트림의 요소를 수집한다. |
R collect(Supplier |
위외 동일 |
스트림 생성하기
컬렉션
- 컬렉션의 최고 조상인 Collection에 stream()이 정의 되어 있어 컬렉션의 자손인 List, Set을 구현한 컬렉션 클래스들은 스트림을 생성할 수 있습니다.
[생성 방법]
Stream<T> Collection.stream()
[예제1. 컬렉션 클래스 List로 부터 스트림 생성]
List<Integer> list = Arrays.asList(1,2,3,4,5) // 가변인자
Stream<Integer> intStream = list.stream() // list를 소스로 하는 컬렉션 생성
[예제2. 배열로 부터 스트림 생성]
Stream<String> strStream = Stream.of("a", "b", "c");
Stream<String> strStream2 = Arrays.stream(new String[] {"a", "b", "c"});
[예제3. 기본형 배열을로 부터 스트림 생성]
IntStream intStream = IntStream.of(1,2,3);
IntStream intStream2 = Arrays.stream(new String[] {1,2,3});
[예제4. 지정된 디렉토리에 있는 파일의 목록을 반환하는 스트림 생성]
Stream<String> Files.lines(Path path);
Stream<String> lines(); // BufferedReader 클래스의 메서드
Reference
자바의 정석
댓글남기기