Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[모던 자바 인 액션] 2주차 #4

Open
so3500 opened this issue Jun 30, 2023 · 2 comments
Open

[모던 자바 인 액션] 2주차 #4

so3500 opened this issue Jun 30, 2023 · 2 comments
Assignees
Labels
SummerCoding 땅울림 여름코딩 스터디 모던 자바 인 액션

Comments

@so3500
Copy link
Contributor

so3500 commented Jun 30, 2023

스터디 날짜

2023.07.07 9-10

내용

챕터3. 람다 표현식
챕터4. 스트림 소개

공유

최승위

  • TBD

이성온

정민교

@so3500 so3500 added SummerCoding 땅울림 여름코딩 스터디 모던 자바 인 액션 labels Jun 30, 2023
@so3500
Copy link
Contributor Author

so3500 commented Jul 7, 2023

2주차 람다 표현식

💡 람다 표현식과 함수형 인터페이스를 살펴본다 상황에 맞는 함수형 인터페이스를 떠올릴 수 있도록 한다

람다 표현식

람다란? 메서드로 전달할 수 있는 익명 함수를 단순화한 것

람다 표현식에는 이름은 없지만, 파라미터 리스트, 바디, 반환 형식, 발생할 수 있는 예외 리스트는 가질 수 있다.

// 람다 표현식 예시
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
    람다 파라미터      화살표              람다 바디

람다는 함수형 인터페이스를 기대하는 곳에서 사용할 수 있다.

함수형 인터페이스의 추상 메서드 시그니쳐 == 함수 디스크립터

자주 사용하는 함수는 아래와 같다. java.util.function

  • 가장 많이 사용 ⇒ Predicate, Function, Consumer
  • 그 외 ⇒ Supplier, UnaryOperator, BinaryOperator, BiFunction, …

Predicate // T → boolean

추상 메서드 test 정의

제네릭 형식 T의 객체를 인수로 받아 불리언 반환

@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
}

Function<T, R> // T → void

추상메서드 apply 정의

제네릭 형식 T를 인수로 받아서 제네릭 형식 R 객체를 반환

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
}

// example
Function<Integer, Integer> square = x -> x * x;
int result = square.apply(2); // 4 

Consumer // () → T

추상 메서드 accept 정의

제네릭 형식 T 객체를 받아서 void 를 반환

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

Supplier // () → R

추상 메서드 get 정의

인수를 받지않고 제네릭 형식 T를 반환

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

UnaryOperator // T → T

제네릭 형식 T 객체를 받아서 T를 반환

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {

    /**
     * Returns a unary operator that always returns its input argument.
     *
     * @param <T> the type of the input and output of the operator
     * @return a unary operator that always returns its input argument
     */
    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }

// example
UnaryOperator<String> CREATE_USER_KEY = userId -> "prefix" + userId + "_" + LocalDateTime.now().toString();
String userId = "myId";
String userKey = CREATE_USER_KEY.apply(userId); // prefix_myId_2023xxxx

UnaryOperator<String> == Function<String, String>
  • 두 가지 조건으로 불리언을 반환해야 한다면?

    BiPredicate<L, R> // (T, U) → boolean

    제네릭 형식 T와 U의 객체를 인수로 받아 불리언 반환

    @FunctionalInterface
    public interface BiPredicate<T, U> {
    
        /**
         * Evaluates this predicate on the given arguments.
         *
         * @param t the first input argument
         * @param u the second input argument
         * @return {@code true} if the input arguments match the predicate,
         * otherwise {@code false}
         */
        boolean test(T t, U u);
  • 두 가지 객체를 받아서 소비해야 한다면?

    BiConsumer<T, U> // (T, U) → void

    추상 메서드 accept 정의

    제네릭 형식 T 와 U 객체를 받아서 void 를 반환

    @FunctionalInterface
    public interface BiConsumer<T, U> {
    
        /**
         * Performs this operation on the given arguments.
         *
         * @param t the first input argument
         * @param u the second input argument
         */
        void accept(T t, U u);
  • 두 가지 객체 T, U를 받아서 R을 반환하고 싶다면?

    BiFunction<T, U, R> // (T, U) → R

    추상 메서드 apply 정의

    제네릭 형식 T 와 U 객체를 받아서 R 을 반환

    @FunctionalInterface
    public interface BiFunction<T, U, R> {
    
        /**
         * Applies this function to the given arguments.
         *
         * @param t the first function argument
         * @param u the second function argument
         * @return the function result
         */
        R apply(T t, U u);

람다 표현식의 조합

람다 표현식은 조합이 가능하다.

  • Comparator 조합
    • reversed : 주어진 비교자의 순서를 뒤바꿈
  • Predicate 조합
    • and : A 이면서 B
    • or : A 또는 B
  • Function 조합
    • andThen : A 이후에 B
    • compose : B 이후에 A

@jeongminkyo
Copy link

스트림 vs 컬렉션

컬렉션이란

컬렉션은 현재 자료구조가 포함하는 모든 값을 메모리에 저장하는 자료구조이다.

스트림이란

스트림(Stream)은 자바 8 API에 새로 추가된 기능으로써 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소(Sequence of elements)로 정의할 수 있다.

스트림(Stream)의 특징

  • 선언형

    더 간결하고 가독성이 좋아진다.

  • 조립할 수 있음

    부분의 스트림 연산은 스트림 연산끼리 연결해서 커다란 파이프 라인을 구성할 수 있도록 스트림 자신을 반환한다. 그 덕분에 layziness(게으름), short-circuiting(쇼트서킷) 같은 최적화도 얻을 수 있다.

  • 병렬화

    성능이 좋아진다. 스레드와 락을 걱정할 필요가 없다.

그래서 둘 차이는 뭔데?

  1. 데이터를 언제 계산하느냐

컬렉션 = DVD

DVD는 영상 전체 데이터를 CD에 모두 담고 있는 것처럼 컬렉션은 현재 자료구조가 포함하는 모든 값을 메모리에 저장한다. 

즉, 컬렉션의 모든 요소는 컬렉션에 추가하기 전에 계산되어야 한다.

스트림 = 인터넷 스트리밍

스트림은 사용자가 필요로 하는 몇 부분만 미리 내려받는 스트리밍 서비스와 비슷하다.

스트림은 이론적으로 요청할 때만 요소를 계산하는 고정된 자료구조이다. 사용자가 요청하는 값만 추출하는 것이 스트림의 핵심이다.

  1. 외부 반복과 내부 반복

컬렉션과 스트림은 데이터 반복 처리 방법에서 차이가 있다. 컬렉션은 외부 반복을 사용한다. 외부 반복이란 사용자가 for-each 등을 사용해서 직접 요소를 반복하는 것을 말한다. 스트림은 내부 반복을 사용한다.

외부 반복

  • 명시적으로 컬렉션 항목을 하나씩 가져와서 처리함

  • 병렬성을 스스로 관리해야 함

    List<String> names = new ArrayList<>();
    for (Dish dish : menu) {
      names.add(dish.getName());
    }
    

내부 반복

  • 작업을 투명하게 병렬로 처리할 수 있음

  • 더 최적화된 다양한 순서로 처리할 수 있음

  • filter 나 map 같이 반복을 숨겨주는 연산 리스트가 미리 정의되어 있어야 함

    List<String> names = menu.stream()
                            .map(Dish::getName)
                            .collect(toList()); // 파이프라인을 실행한다.
    
  1. 스트림은 딱 한 번만 탐색이 가능하다

반복자와 마찬가지로 스트림도 단 한번만 탐색할 수 있다. 탐색된 스트림의 요소는 소비된다. 반복자와 마찬가지로 한번 탐색한 요소를 다시 탐색하려면 초기 데이터 소스에서 새로운 스트림을 만들어야한다.

List<String> title = Arrays.asList("kim", "seo", "hae");
Stream<String> s = title.stream();
s.forEach(System.out::println);
s.forEach(System.out::println); // java.lang.IllegalStateException : 스트림이 이미 소비되었거나 닫힘

위 코드에서 보듯이, 스트림은 단 한번만 소비할 수 있다.
만약 한 번의 소비 이후에 다시한번 호출한다면 java.lang.IllegalStateException 에러가 발생한다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
SummerCoding 땅울림 여름코딩 스터디 모던 자바 인 액션
Projects
None yet
Development

No branches or pull requests

3 participants