[Java] 람다와 스트림

윤종석·2024년 1월 9일
0

Spring 공부

목록 보기
2/3
post-thumbnail

Lambda

메서드를 ‘하나의 식’으로 표현한 것이다.

메서드의 이름과 반환값이 없어지므로 ‘익명함수’라고도 한다.

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

장점

  • 불필요한 코드를 줄이고, 이해를 도와준다.
  • 람다 표현식은 메소드로 전달할 수 있는 익명함수를 단순화한 코드의 블록이다.
  • 특정 클래스에 종속되지 않으며 함수라는 이름으로 명명한다.
  • 함수 자체를 전달 인자로 쓰거나, 변수에 저장하는 것이 가능하다.

함수형 인터페이스를 타입으로 사용해 람다식을 변수로서 받을 수 있다.

Runnable runnable = new Runnable() {
		@Override
		public void run() {
				// ~
		}
);

Runnable runnable = () -> { ... }; // Lambda

이때 함수형 인터페이스를 @FunctionalInterface를 통해 직접 정의할 수도 있지만, 일반적으로 java.util.function 패키지에 정의된 함수형 인터페이스를 사용한다.

java.util.function 패키지의 기본적인 함수형 인터페이스

함수형 인터페이스메서드반환타입함수 디스크립터설명
java.lang.Runnablerun()void() -> void매개변수x, 반환값x
Supplierget()T() -> T매개변수x, 반환값o
Consumeraccept(T t)voidT -> void매개변수o, 반환값x
Function<T, R>apply(T t)RT -> R매개변수 1개, 반환값o
Predicatetest(T t)booleanT -> boolean매개변수 1개, 반환값o

forEach()는 Consumer 타입의 함수형 인터페이스를 매개변수로 받는다.

참고 영상
https://www.youtube.com/watch?v=u5sLXaWo7tY
https://www.youtube.com/watch?v=cHIKmmWFwng


Stream이란?

Java8 API에서 추가된 람다를 사용하는 기술 중 하나이다.

선언형으로 컬렉션 데이터를 처리할 수 있도록 도와준다.

다양한 데이터 소스를 표준화된 방법으로 다룰 수 있다.

Collection Framework를 통해 관리하는 데이터를 처리하기 위해 주로 사용한다.

List<String> list = Arrays.asList("Lee", "Kim", "Park");
        
// 기존
Iterator<String> it = list.iterator();
while (it.hasNext()) {
		System.out.println(it.next());
}

// stream 활용
list.stream().forEach(name -> System.out.println(name));

Stream API의 최상위 인터페이스는 BaseStream 인터페이스지만 직접 사용하지는 않는다.

Stream 인터페이스는 BaseStream을 상속하는 인터페이스이다.

여러 메소드들을 정의하고 있으며 많은 메소드들의 파라미터에 람다와 메소드 참조가 필요하다.

메소드기능
long count()해당 스트림에 포함된 항목의 수를 반환한다.
Stream concat(Stream, Stream)파라미터로 전달되는 두 개의 스트림을 하나의 스트림으로 반환한다.
R collect(Collector)스트림의 항목들을 컬렉션 타입의 객체로 반환한다.
Stream filter(Predicate)스트림의 항목들을 파라미터의 조건에 따라 필터링하고 결과 항목들을 스트림 형태로 반환한다.
void forEach(Consumer)스트림 항목들을 순회한다.
Optional reduce(BinaryOperator)람다 표현식을 기반으로 데이터를 소모하고 그 결과를 반환한다.
Object toArray()스트림 항목들을 배열 객체로 전환한다.
Stream sorted()스트림 항목들에 대해 정렬하고 이를 스트림으로 반환한다.

Stream 객체를 생성하는 방법

Collection 객체 사용

처리할 데이터가 이미 존재하고, 이를 처리할 때 일반적으로 사용하는 방식이다.

List 인터페이스는 Collection 인터페이스를 상속받고, Collection에는 Stream 객체를 반환하는 stream() default 메소드가 있다.

한번 생성한 Stream은 사용 후 다시 사용할 수 없고, 데이터에 대한 처리가 완료되면 종료된다.

List<String> list = Arrays.asList("Lee", "Kim", "Park");

Stream<String> stream = list.stream();
stream.forEach(name -> System.out.println(name));

Stream.Builder를 사용

데이터가 존재하지 않고, Stream을 만들었을 때, 직접 데이터를 추가해서 사용하고자 할 때 사용한다.

메소드기능
void accept(T)스트림 빌터에 데이터를 추가한다.
Stream.Builder add(T)스트림 빌더에 데이터를 추가하고, 스트림을 반환한다.
Stream build()스트림 빌더에 데이터 추가를 종료하고, 스트림을 반환한다.
Stream.Builder<String> builder = Stream.builder();
builder.accept("Kim");
builder.accept("Lee");
builder.accept("Park");

Stream<String> stream = builder.build();
stream.forEach(name -> System.out.println(name));

참고 영상
https://www.youtube.com/watch?v=mUJGw_b6DfI&t=5s
https://www.youtube.com/watch?v=ASQDml4NyKM


Stream 연산

스트림의 연산은 각 연산의 연결을 통해 파이프라인을 구성할 수 있다.

연산 처리는 스트림 객체의 생성, 중간 연산, 최종 연산 단계로 구분할 수 있다.

Collection Data -> Filter -> Sort -> Map -> Collect

중간 연산

filter, map과 같은 중간 연산은 Stream을 반환하고, 연속해서 호출하는 Method Chaining으로 구현 가능하다.

최종 연산이 실행되어야 중간 연산이 처리되므로, 중간 연산들로만 구성된 메소드 체인은 실행되지 않는다.

연산반환 형식연산 인수
filterStreamPredicate
mapStreamFunction<T, R>
limitStream
sortedStreamComparator
distinctStream
peekStreamConsumer
skipStream

필터링

전체 데이터에서 불필요한 데이터를 없에고, 원하는 데이터를 추출하기 위한 과정으로 중간 연산이다.

Stream API의 filter(), distinct()와 같은 메소드를 이용하여 데이터 추출이나 중복 데이터를 제거한다.

이때 중복 데이터를 제거하는 distinct()는 병렬 스트림에서의 성능과, equals() 메소드의 필요성을 고려해야 한다.

정렬

특정 조건에 따라 데이터를 정렬하고, 이를 다시 Stream으로 반환한다.

sorted()를 이용한 정렬을 위해서는 객체들이 Comparable 인터페이스를 구현한 클래스여야 한다.

또한 Comparator 인터페이스를 통해 다른 방식의 정렬도 사용 가능하다.

List<T> list = new ArrayList<>();

// Comparable을 통한 정렬의 경우 인자를 전달받지 않는다
list.stream()
		.sorted()
		.forEach(System.out::println);

// Comparator를 사용한 정렬의 경우 인자를 전달받는다.
list.stream()
		.sorted(Comparator.comparing(T::getThings))
		.forEach(System.out::printlin);

맵핑

스트림이 관리하는 데이터를 다른 형태의 데이터로 반환해준다.

map(), mapToInt(), mapToDouble(), mapToLong() 등이 있다.

Function을 파라미터로 받는다.

List<Curtomers> customers = new ArrayList<>();
customers.add(new Customer("Kim", 33));
customers.add(new Customer("Lee", 28));
customers.add(new Customer("Park", 31));
customers.add(new Customer("Yoon", 25));

List<String> names = customers.stream()
				.map(Customer::getName)
				.collect(Collectors.toList());

최종 연산

연산반환 형식
forEach스트림의 각 요소를 소비하며 람다식을 적용하고, void형을 반환한다.
count스트림 요소의 수를 Long형으로 반환한다.
collectList, Map 형태의 컬렉션을 반환한다.
sum스트림의 모든 요소에 대한 합을 반환한다.
reduce스트림의 요소를 하나씩 줄여가며 연산을 수행하고 결과를 Optional로 반환한다.
allMatch파라미터로 전달되는 람다식 기준으로 데이터가 모두 일치하는지 확인한다.
findFirst스트림 데이터 중 첫번째 데이터를 반환한다.
reduce스트림 데이터 중 임의의 데이터를 반환한다.
List<String> list = Arrays.asList("Kim", "Lee", "Park", "Yoon");
        list.stream()
                .filter(name -> name.length() > 3) // 중간 연산
                .sorted()
                .forEach(System.out::println); // 최종 연산

참고 영상
https://www.youtube.com/watch?v=v7n66yvM8bw&list=TLPQMDkwMTIwMjSH9w8ohdh0rQ&index=2
https://www.youtube.com/watch?v=7cVPlgyx3d8
https://www.youtube.com/watch?v=0ZJr0O1DN8g

profile
공부 중인 학부생

0개의 댓글