본문 바로가기

프로그래밍/JAVA

[JAVA 이론] 람다와 스트림

람다식

람다식이란?

- 람다식은 메서드를 하나의 식으로 표현한 것이다.

- 메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로, 람다식을 '익명 함수'라고도 한다.

 

람다식 작성하기

메서드의 이름과 반환타입을 제거하고 매개변수 선언부와 몸통 사이에 '->'를 추가한다.

//기본 메서드
반환타입 메서드이름(매개변수 선언){
	문장들
}

//람다식
(매개변수 선언) -> { 
	문장들 
}

 

람다식은 반환값이 있는 경우, return문 대신 식으로 대신할 수 있다. 식의 연산결과가 자동적으로 반환값이 된다. 이때는 문장이 아닌 식이므로 끝에 ';'를 붙히지 않는다.

또한, 람다식에 선언된 매개변수의 타입은 추론이 가능한 경우 생략 할 수 있다. 이때 매개변수 중 어느 하나의 타입만 생략하는 것은 허용되지 않는다. 람다식에서 반환 타입이 없는 이유도 항상 추론이 가능하기 때문인다.

//기본 메서드
int max(int a, int b){
	return a > b > ? a : b;
}

//리턴 생략 람다식
(int a, int b) -> a > b ? a : b

//매개변수 생략 람다식
(a, b) -> a > b ? a : b

 

람다식에서 선언된 매개변수가 하나뿐인 경우에는 괄호()를 생략 할 수 있다. 단, 매개변수의 타입이 있으면 괄호()를 생략할 수 없다.

마찬가지고 괄호{} 안의 문장이 하나일 때는 괄호{}를 생략할 수 있다. 그러나 괄호{} 안의 문장이 return문일 경우 생략할 수 없다.

//() 생략
a -> a * a     //OK
int a -> a * a //error

//{} 생략
(int a, int b) -> a * a;         //OK
(int a, int b) -> return a * a;  //error

 

함수형 인터페이스

단 하나의 추상 메서드만 선언된 인터페이스를 함수형 인터페이스라고 한다.

람다식의 참조형을 위해서는 함수형인터페이스가 필요하다.

@FunctionalInterface
interface MyFunction{
	void myMethod();
}

 

람다식은 익명 클래스의 객체와 동등하다

//람다식
(int a, int b) -> a > b ? a : b

//일반 메소드
new Object(){
	int myMethod(int a, int b){
		return a > b ? a : b;
	}
}

//익명 클래스
Myfunction f = new MyFunction{
			public int myMethod(int a, int b){
			return a > b ? a : b;
			}
		};

 

참조변수 없이 직접 람다식을 매개변수로 지정하는 것도 가능하다.

//매개변수의 타입이 MyFunction인 메서드
static void execute(MyFunction f) {
	f.run();
}
	
//반환형의 타입이 MyFunction인 메서드
static MyFunction getMyFunction(){
	MyFunction f= () -> System.out.println("f3.run()");
	return f;
}

//람다식으로 MyFunction을 구현
MyFunction f1 = () -> System.out.println("f1.run()");

//익명 클래스로 구현
MyFunction f2 = new MyFunction() {
	public void run() {
		System.out.println("f2.run()");
	}
};

 

람다식은 함수형 인터페이스와 타입이 일치하는 것은 아니다. 람다식은 익명 객체이고 익명 객체는 타입이 없다. 따라서 타입의 형을 일치 시키기 위해서는 형변환이 필요하다. 또한, 람다식은 Object타입으로 형변환 할 수 없다. 굳이 Object타입으로 형변환하려면, 먼저 함수형 인터페이스로 변환한 후 Object타입으로 변환해야 한다.

MyFunction f = (Myfunction)(() -> {});
Object     f = (Object((Myfunction)(() -> {}));

 

java.util.function패키지

java.util.function패키지에 일반적으로 자주 쓰이는 형식의 메서드를 함수형 인터페이스로 미리 정의해 놓았다.

함수형 인터페이스 메서드 설명
java.lang.Runnable void run() 매개변수도 없고, 반환값도 없음
Supplier<T> T get() 매개변수는 없고, 반환값만 있음
Consumer<T> void accept(T t) 매개 변수만 있고, 반환값이 없음
Function<T, R> R apply(T t) 일반적인 함수, 하나의 매개변수를 받아서 결과를 반환
Predicate<T> boolean test(T t) 조건식을 표현하는데 사용됨. 매개변수는 하나, 반환 타입은 boolean

 

매개변수가 두개인 함수형 인터페이스

함수형 인터페이스 메서드 설명
BiConsumer<T, U> void accept(T t, U u) 두개의 매개변수만 있고, 반환값이 없음
BiPredicate<T, U> boolean test(T t, U u) 조건식을 표현하는데 사용됨, 매개변수는 둘, 반환값은 boolean
BiFunction<T,U,R> R apply(T t, U u) 두 개의 매개변수를 받아서 하나의 결과를 반환

3개 이상의 매개변수를 갖는 함수형 인터페이스가 필요하다면  직접 만들어서 써야한다.

 

UnaryOperator와 BinaryOperator

매개변수와 반환타입이 모두 일치한다.

함수형 인터페이스 메서드 설명
UnaryOperator<T> T apply(T t) Function의 자손, Function과 달리 매개변수와 결과의 타입이 같다
BinaryOperator<T> T apply(T t, T t) BiFunction의 자손, BiFunction과 달리 매개변수와 결과의 타입이 같다

 

컬렉션 프레임웍과 함수형 인터페이스

함수형 인터페이스 메서드 설명
Collection boolean removeIf(Predicate<E> filter) 조건에 맞는 요소를 삭제
List void replaceAll(UnaryOperator<E> operator) 모든 요소를 변환하여 대체
Iterable void forEach(Consumer<T> action) 모든 요소에 작업 action을 수행
Map V compute(K key, BiFunction<K,V,V> f) 지정된 키에 값에 작업 f를 수행
V computeIfAbsent(K key, Function<K,V> f) 키가 없으면, 작업 f 수행 후 추가
V computeIfPresent(K key, V value, BiFunction<V,V,V> f) 지정된 키가 있을 때, 작업 f를 수행
V merge(K key, V value, BiFunction<V,V,V> f) 모든 요소에 병합 작업 f를 수행
void forEach(BiConsumer<K,V> action) 모든 요소에 작업 action을 수행
void replaceAll(BiFunction<K,V,V> f) 모든 요소에 치환작업 f를 수행

 

기본형을 사용하는 함수형 인터페이스

함수형 인터페이스 메서드 설명
DoubleToIntFunction int applyAsint(double) AToBFunction은 입력이 A타입 출력이 B타입
ToIntFunction<T> int applyAsInt(T value) ToBFunction은 출력이 B타입이고, 입력은 지네릭타입
IntFunction<R> R apply(T t, U u) AFunction은 입력이 A타입이고, 출력은 지네릭 타입
ObjIntConsumer<T> void accept(T t, U u) objAFunction은 입력이 T, A타입이고 출력은 없다

 

Function의 합성과 Predicate의 결합

//Function
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after)
default <V> Function<V, R> compose(Function<? super V, ? extends T> before)
static <T> Function<T, T> identity()

//Predicate
default Predicate<T> and(Predicate<? super T> other)
default Predicate<T> or(Predicate<? super T> other)
default Predicate<T> negate()
static <T> Predicate<T> isEqual(Object targetRef)

 

메서드 참조

람다식이 하나의 메서드만 호출하는 겨우에는 메서드 참조라는 방법으로 람다식을 간략히 할 수 있다.

하나의 메서드만 호출하는 람다식은 '클래스이름::메서드이름' 또는 '참조변수::메서드이름'으로 바꿀 수 있다.

//람다식
Function<String, Integer> f = (String s) -> Integer.parseInt(s)

//메서드 참조
Function<String, Integer> f = Integer::parseInt

 

스트림

스트림이란?

데이터 소스를 다루는 방식을 표준화 한것으로, 스트림은 데이터 소스를 추상화하고 데이터를 다루는데 자주 사용되는 메서드들을 정의해 놓았다.

 

스트림의 특징

- 스트림은 데이터 소스를 변경하지 않는다.

- 스트림은 일회용이다.

- 스트림은 작업을 내부 반복으로 처리한다.

 

스트림의 연산

스트림이 제공하는 다양한 연산을 이용해서 복잡한 작업들을 간단히 처리할 수 있다. 마치 데이터베이스에 SELECT문으로 질의(쿼리, query)하는 것과 같은 느낌이다.

//스트림의 두가지 연산
중간 연산 : 연산 결과가 스트림인 연산, 스트림에 연속해서 중간 연산할 수 있음
최종 연산 : 연산 결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 단 한번만 가능

 

중간 연산

연산 설명
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>          map(Function<T, R>  mapper)
DoubleStream   maptoDouble(ToDoubleFunction<T> mapper)
IntStream             mapToInt(ToIntFunction<T> mapper)
LongStream        mapToLong(ToLongFunction<T> mapper)

Stream<R>          flatMap(Function<T, Stream<R>>  mapper)
DoubleStream  flatMaptoDouble(Function<T, DoubleStream> m)
IntStream            flatMapToInt(Function<T, IntStream> m)
LongStream        flatMapToLong(Function<T, LongStream> m)
스트림의 요소를 변환한다

 

최종 연산

연산 설명
void forEach(Consumer<? super T> action)
void forEachOrdered(Consumer<? super T> action)
각 요소에 지정된 작업 수행
long count() 스트림의 요소의 개수 반환
Optional<T> max(Comparator<? super T> comparator)
Optional<T> min(Comparator<? super T> comparator)
스트림의 최대값/최소값을 반환
Optional<T> findAny()  //아무거나 하나
Optional<T> findFirst() //첫 번째 요소
스트림의 요소 하나를 반환
boolean allMatch(Predicate<T> p)      //모두 만족하는지
boolean anyMatch(Predicate<T> p)   //하나라도 만족하는지
boolean noneMatch(Predicate<T> p) //모두 만족하지 않는지
주어진 조건을 모든 요소가 만족시키는지, 만족시키지 않는지 확인
Object[] toArray()
A[] toArray(IntFuction<A[]> generator)
스트림의 모든 요소를 배열로 반환
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
U reduce(U identity, BiFunction<U, T, T> accumulator, BinaryOperator<U> conbiner)
스트림의 요소를 하나씩 줄여가면서(리듀싱) 계산한다.
R collect(Collector<T,A,R> collerctor)
R collect(Supplier<R> supplier, BiConsumer<R, T> accmulator, Biconsumer<R, R> conbiner)
스트림의 요소를 수집한다. 
주로 요소를 그룹화하거나 분할한 결과를 컬렉션에 담아 반환하는데 사용된다.

 

지연된 연산

스트림 연산에서 한 가지 중요한 점은 최종 연산이 수행되기 전까지는 중간 연산이 수행 되지 않는다.

 

병렬 스트림

스트림으로 데이터를 다룰 때의 장점 중 하나가 병렬 처리가 쉽다는 것이다. 그저 parallel()이라는 메서드를 호출해서 병렬로 연산을 수행하도록 지식하면 된다.

int sum = strStream.parallel() //strStream을 병렬 스트림으로 전환
		    .mapToLint(s -> s.length)
                    .sum();

 

스트림 만들기

스트림의 소스가 될  수 있는 대상은 배열, 컬렉션, 임의의 수 등 다양하다.

 

- 컬렉션

컬렉션의 최고 조상인 Collection에 stream()이 정의되어 있다. 그래서 Collection의 자손인 List와 Set을 구현한 컬렉션 클래스들은 모두 이 메서드로 스트림을 생성할 수 있다.

Stream<T> Collection.stream()

//예시
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> intStream = list.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)

//기본형 스트림
IntStream, LongStream, DoubleStream

 

- 특정 범위의 정수

IntStream과 LongStream은 다음과 같이 지정된 범위의 연속된 정수를 스트림으로 생성해서 반환하는 range()와 rangeClosed()를 가지고 있다.

IntStream  IntStream.range(int begin, int end)
IntStream  IntStream.rangeClosed(int begin, int end)

 

- 임의의 수

난수를 생성하는데 사용하는 Random클레스에는 난수로 이루워지 스트림 타입을 반환하는 메서드가 있다.

IntStream          ints()

LongStream      longs()

DoubleStream  doubles()

IntStream       intStream = new Random().ints();   //무한 스트림
IntStream.limit(5).forEach(System.out::println);   //유한 스트림

 

- 람다식[iterate(), generate()]

Stream클래스의 iterate()와 generate()는 람다식을 매개변수로 받아서, 이 람다식에 의해 계산되는 값들을 요소로 하는 무한 스트림을 생성한다.

//seed로 지정된 값부터 시작해서, 람다식 f에 의해 계산된 결과를 다시 seed값으로 해서 계산을 반복
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f) 
Strean<Integer> evenStream = Stream.iterate(0, n -> n+2); //0, 2, 4, 6, ...

//람다식에 의해 계산되는 값을 요소로 하는 무한 스트림을 생성, 이전 결과를 이용하지 않음
static <T> Stream<T> generate(Supplier<T> s)
Stream<Double> randomStream = Stream.generate(Math::Random);

 

- 파일

java.nio.file.Files는 파일을 다루는데 필요한 유용한 메서드들을 제공하는데, list()는 지정된 디렉토리(dir)에 있는 파일의 목록을 소스로 하는 스트림을 생성해서 반환한다.

Stream<Path>   Files.list(Path dir)
Stream<String> Files.lines(Path path) //줄 단위로 읽어오기

- 빈스트림

요소가 하나도 없는 비어있는 스트림을 생성할 수도 있다. 스트림에 연산을 수행한 결과가 하나도 없을 때, null 보다 빈 스트림을 반환하는 것이 낫다.

Stream emptyStream = Stream.empty();

 

- 두 스트림의 연결

Stream의 static 메서드인 concat()을 사용하면, 두 스트림을 하나로 연결할 수 있다.

Stream.concat(Stream<T>, Stream<T>)

 

스트림의 중간 연산

스트림 자르기 - skip(), limit()

skip(n)은 처음 n새의 요소를 건너뛰고, limit(m)는 스트림의 요소를 m개로 제한한다.

Stream<T> skip(long n)
Stream<T> limit(long maxSize)

 

스트림의 요소 걸러내기 -filter(), distinct()

distinct()는 스트림에서 중복된 요소들을 제거하고, filter()는 주어진 조건(Predicate)에 맞지 않는 요소를 걸러낸다.

Stream<T> filter(Predicate<? super T> predicate)
Stream<T> distinct()

 

정렬 -sorted()

스트림을 정렬할 때는 sorted()를 사용하면 된다.

Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
문자열 스트림 정렬 방법 출력결과
strStream.sorted()                                                                  //기본정렬
strStream.sorted(Comparator.naturalOrder())           //기본정렬
strStream.sorted((s1, s2) -> s1.comparaTo(s2) )         //람다식도 가능
strStream.sorted(String::compareTe)                            //위의 문장과 동일
CCaaabccdd
strStream.sorted(Comparator.reverseOrder())          //기본 정렬의 역순
strStream.sorted(Comparator.<String>maturalOrder().reversed())
ddccbaaaCC
strStream.sorted(String.CASE_INSENSITIVE_ORDER) //대소문자 구분안함 aaabCCccdd
strStream.sorted(String.CASE_INSENSITIVE_ORDER.reversed)  ddCCccbaaa
strStream.sorted(Comparator.comparing(String::length))          //길이 순 정렬
strStream.sorted(Comparator.comparingInt(String::length))      //no  오토방식
bddCCccaaa
strStream.sorted(Comparator.comparing(String::length).reversed())  aaaddCCccb

 

변환 -map()

스트림의 요소에 저장된 값 중에서 원하는 필드만 뽑아내거나 특정 형태로 변환해야 할때 사용한다.

Stream<R> map(Function<? super T, ? extends R> mapper)

//map()으로 Stream<File>을 Stream<String>으로 변환
Stream<String> filenameStream = fileStream.map(File::getName);
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)                  //중복 제거
   .forEach(System.out::print);

 

조회 -peek()

연산과 연산 사이에 올바르게 처리되었는지 확인하고 싶을 때 사용한다.

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)                         
   .forEach(System.out::print);

 

mapToInt(), mapToLong(), mapToDouble()

map()은 연산의 결과로 Stream<T> 타입의 스트림을 반환하는데, 스트림의 요소를 숫자로 변환하는 경우 IntStream과 같은 이본형 스트림으로 변환하는 것이 더 유용할 수 있다.

DoubleStream  mapToDouble(ToDoubleFunction<? super T> mapper)
IntStream     mapToInt(ToIntFunction<? super T> mapper)
LongStream    mapToLong(ToLongFunction<? super T> mapper)

//예시
IntStream studentScoreStream = studentStream.mapToInt(Student::getTotalScore);
int allTotalScore = studentScoreStream.sum(); //sum, average, max, min 등이 있다

//mapRoObj - IntStream을 Stream<String>으로 변환

 

flatMap() -Stream<T[]>를 Stream<T>로 변환

스트림의 요소가 배열이거나 map()의 연산결과가 배열인 경우, 즉 스트림의 타입이 Stream<T[]>인 경우, Stream<T>로 다루기 위해 사용한다.

//map(), flatMap() 비교
Stream<Stream<String>> strStrStrm = strArrStrm.map(Arrays::stream);
Stream<String>         strStrm    = strArrStrm.flatMap(Arrays::stream);

 

Optional<T>와 OptionalInt

Optional<T>은 지네릭 클래스로 "T 타입의 객체"를 감싸는 래퍼 클래스이다. 그래서 Optional타입의 객체에는 모든 참조변수를 담을 수 있다.

public final class Optional<T>{
	private final T value; // T 타입의 참조변수
    		...
}

최조 연산의 결과를 그냥 반환하는게 아니라 Optional객체에 담아서 반환하는 것이다. 이처럼 객체에 담아서 반환하면 null 체크를 위한 if문 없이 NullPointException을 방지할 수 있다.

 

- Optional객체 생성하기

Optional객체를 생성할 때는 of() 또는 ofNullable()을 사용한다.

//Optional 초기화
Optional<String> optVal = null  //널로 초기화는 가능하나 권장하지 안흠
Optional<String> optVal = Optional.<String>empty();

//예외
Optional<String> optVal = Optional.of(null);  //NullPointerException발생
Optional<String> optVal = Optional.ofNullable(null);

//사용예시
String str = "abc";
optional<String> optVal = Optional.of(str);
optional<String> optVal = Optional.of("abc");
optional<String> optVal = Optional.of(new String("abc"));

 

- Optional객체의 값 가져오기

Optional<String> optVal = null;
String str1 = optVal.get();       //optVal에 저장된 값을 반환. null이면 예외발생
String str2 = optVal.orElse("");  //optVal에 저장된 값이 null일 때는, ""를 반한

//null을 대체할 값을 반환하는 람다식을 지정
T orElseGet(Suppllier< ? extends T> other)
String str3 = optVal.orElseGet(String::new); //() -> new String()과 동일

//null일 때 지정된 예외를 발생시킴
T orElseThrow(Supplier<? extends X> exceptionSupllier)
String str4 = optVal.orElseThrow(NullPointerException::new); //null이면 예외발생

isPresent(): Optional객첵의 값이 null이면 false를, 아니면 true를 반환

 

스트림의 최종 연산

최종 연산은 스트림의 요소를 소모해서 결과를 만들어낸다. 그래서 최종 연산후에는 스트림이 닫히게 되고 더 이상 사용할 수 없다.

 

forEach()

스트림의 요소를 출력하는 용도로 많이 사용된다.

void forEach(Consumer<? super T> action)

 

조건 검사 - allMatch(), anyMatch(), noneMatcth(), findFirst(), findAny()

연산 설명
boolean allMatch(Predicate<? super T> predicate) 모든 요소가 일치하면 true
boolean anyMatch(Predicate<? super T> predicate) 하나라도 일치하면 true
boolean noneMatch(Predicate<? super T> predicate) 하나도 일치하지 않으면 true
//스트림의 요소 중에서 조건에 일치하는 첫번째 것을 반환
Optional<Student> stu = stuStream.filter(s -> s.getTotalScore() <= 100).findFirst()

//아무거나 하나 반환
Optional<Student> stu = paralleStream.filter(s -> s.getTotalScore() <= 100).findAny()

 

리듀싱 - reduce()

스트림의 요소를 줄요나가면서 연산을 수행하고 최종결과를 반환한다. 처음 두 요소를 가지고 연산한 결과를 가지고 그 다음 요소와 연산한다.

Optional<T> reduce(BinaryOperator<T> accumulator)

T reduce(T identity, BinaryOperator<T> accumulator)
U reduce(U identity, BiFunction<U, T, U> accumulator, BinaryOperator<U> combiner)

//예시
int count = intStream.reduce(0, (a, b) -> a + 1);
int sum   = intStream.reduce(0, (a, b) -> a + b);
int max   = intStream.reduce(Integer.MIN_VALUE, (a, b) -> a > b ? a : b);
int min   = intStream.reduce(Integer.MAX_VALUE, (a, b) -> a < b ? a : b);

 

collect()

collect()는 스트림의 요소를 수집하는 최정 연산이다. collect()가 스트림의 요소를 수집하려면, 어떻게 수집할 것인가에 대한 방법이 정의되어 있어야 하는데, 이 방법을 정의한 것이 컬렉터이다.

collect()  스트림의 최종연산, 매개변수로 컬렉터를 필요라한다.
Collector  인터페이스, 컬렉터는 이 인터페이스를 구현해야한다.
Collectors 클래스, static메서드로 미리 작성된 컬렉터를 제공한다.

//구현방법
Object collect(Collector collector) //Collector를 구현한 클래스의 객체를 매개변수로
Object collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner)

 

스트림을 컬렉션과 배열로 변환 - 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);

 

통계 - counting(), summingInt(), averagingInt(), maxBy(), minBy()

long count = stuStream.count();
long count = stuStream.collect(counting());

long totalScroe = stuStream.mapToInt(Student::getTotalScore).sum();
long totalScore = stuStream.collect(summingInt(Student::getTotalScore));

OptionalInt topScore = studentStream.mapToInt(Student::getTotalScore).max();

Optional<Student> topStudent = stuStream
			.max(Comparator.comparingInt(Student::getTotalScore));
Optional<Student> topStudent = stuStream
			.collect(maxBy(Comparator.comparingInt(Student::getTotalScore));

IntSummaryStatistics stat = stuStream
			.mapToInt(Student::getTotalScore).summaryStatistics();
IntSummaryStatistics stat = stuStream
			.collect(summarizingInt(Student::getTotalScore));

 

리듀싱 - reducing()

Collector reducing(BinarayOperator<T> op)
Collector reducing(T identitiy, BinaryOperator<T> op)
Collector reducing(U identity, Funtion<T,U> mapper, BinarayOperator<U> op)

 

문자열 결합 - joining()

문자열 스트림의 모든 요소를 하나의 문자열로 연결해서 반환한다. 구분자를 지정해줄 수도 있고, 접두사와 접미사도 지정가능하다.

String studentNames = stuStream.map(Student::getName).collect(joining());
String studentNames = stuStream.map(Student::getName).collect(joining(","));
String studentNames = stuStream.map(Student::getName).collect(joining(",","[", "]"));

 

그룹화와 분할 - groupingBy(), partitioningBy()

그룹화는 스트림의 요소를 특정 기준으로 그룹화하는 것을 의미하고, 분할은 스트림의 요소를 두 가지, 지정된 조건에 일치하는 그룹과 일치하지 않는 그룹으로의 분하을 의미한다. groupingBy()는 스트림의 요소를 Function으로, partitioningBy()는 Predicatie로 분류한다.

Collector groupingBy(Function classifier)
Colleotor groupingBy(Function classifier, Collector downstream)
Collector groupingBy(Function classifier, Supplier mapFactory, Collectoer downStream)

Collector partitioningBy(Predicate predicate)
Collector partitioningBy(Predicate predicate, Collector downStream)

 

Collector구현하기

public interface Collector<T, A, R>{
	Supplier<A>         supplier();
	BiConsumer<A, T>    accumulator();
	BinaryOperator<A>   combiner();
	Function<A, R>      finisher();
    
	Set<Characteristics> characteristics(); //컬렉터의 특성이 담긴 Set을 반환
		...
}

 

직접 구현해야하는 것은 위의 5갸 메서드인데, characteristics()를 제외하면 모두 반환타입이 함수형 인터페이스이다. 즉, 4새의 람다식을 작성하면 되는 것이다.

Supplier()      작업 결과를 저장할 공간을 제공
accumulator()   스트림의 요소를 수집(collect)할 방법을 제공
combiner()      두 저장공간을 병합할 방법을 제공(병렬 스트림)
finisher()      결과를 최종적으로 변환할 방법을 제공

 

characteristics()는 컬렉터가 수행하는 작업의 속성에 대한 정보를 제공하기 위한 것이다.

Characteristics.CONCURRENT        병렬로 처리할 수 있는 작업
Characteristics.UNORDERED         스트림의 요소의 순서가 유지될 필요가 없는 작업
Characteristics.IDENTITY_FINISH   finisher()가 항등 함수인 작업

 

'프로그래밍 > JAVA' 카테고리의 다른 글

[JAVA 이론] 네트워킹  (0) 2022.08.15
[JAVA 이론] 입출력 I/O  (0) 2022.07.16
[JAVA 이론] 쓰레드  (0) 2022.03.16
[JAVA 이론] Generics, Enumeration, Annotation  (0) 2022.03.13
[JAVA 이론] Collections Framework  (0) 2022.02.27