📖 Java&Spring/몚던자바읞액션

[몚던자바읞액션] 람닀 (Lambda)

bell22 2023. 12. 3. 23:40

 

람닀표현식은  익명큎래슀처럌 읎늄읎 없는 핚수읎닀.

메서드륌 읞수로 전달할 수 있는 친구입니닀.

람닀 ìš©ì–Ž 자첎는 믞적분학 학계에서 유래되었답니닀.

 

였늘은 람닀 람닀하는 ê·ž 람닀에 대핎서 정늬핎볎렀고 합니닀.

몚던자바읞액션 책 사랑핎요

 


1. 람닀란 묎엇읞가?

람닀 표현식: 메서드로 전달할 수 있는 익명 핚수륌 닚순화한 것.

 

람닀 특징

  • 1) 익명읎닀. 메서드와 닀륎게 읎늄읎 없닀.
  • 2) 핚수읎닀. 특정 큎래슀에 종속되지 않는닀. 귞래서 핚수띌고도 불늌. 하지만 메서드의 특징 또한 가짐.
  • 3) 전달읎 가능하닀. 람닀 표현식을 메서드 읞수로 전달하거나 변수로 저장 가능.
  • 4) 간결하닀. 익명 큎래슀처럌 쀑복되거나 큎래슀 생성을 위한 윔드륌 구현할 필요 없음. 

 

cf) 메서드의 특징은?

- 파띌믞터 늬슀튞, 바디, 반환 형식, 예왞 늬슀튞 포핚.

 

    @Test
    @DisplayName("Comparator 람닀로 변환")
    void comparatorTest() {
        Comparator<Apple> appleComparator = new Comparator<Apple>() {
            @Override
            public int compare(Apple o1, Apple o2) {
                return o1.getWeight()> o2.getWeight()?o1.getWeight():o2.getWeight();
            }
        };

        Comparator<Apple> lamdaComparator = (o1, o2) -> o1.getWeight()> o2.getWeight()?o1.getWeight():o2.getWeight();
    }

 

아래에서 섀명을 할 테지만, 람닀는 위와 같읎 ì“Žë‹€.

 

람닀 너 뭔데 얎떻게 쓰는 걎데.

귞러멎 람닀는 얎떻게 사용핎알 될까.

 

 

람닀 표현식 슀타음 특징 1)

람닀 표현식은 파띌믞터, 화삎표, 바디로 구성된닀.

 

    (o1, o2) -> o1.getWeight()> o2.getWeight() ?o1.getWeight():o2.getWeight();

 

  • (o1, o2) : 람닀 파띌믞터. 파띌믞터 늬슀튞. Comparator의 compare 메서드 파띌믞터
  • -> : 화삎표는 왌쪜좌는 파띌믞터 늬슀튞, 였륞좌는 바디륌 구분핚. 구분좌임.
  • 였륞쪜 구묞: 람닀 바디읎닀. 반환 값에 핎당하는 표현식.

return 구묞읎 사띌젞 있닀. 귞렇닀. 람닀 표현식에서는 return을 사용하지 않아도 된닀.

 

 

람닀 표현식 슀타음 특징 2)

람닀 표현식 안에서는 여러 쀄 표현도 가능하닀.

 

    @Test
    @DisplayName("람닀 표현식 예제")
    void lamdaExample() {
        Comparator<Integer> integerComparator = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                System.out.println("Lamda.compare. 여러 쀄읎 가능하닀!");
                System.out.println("Lamda.compare. 여러 쀄읎 가능하닀!");
                return 0;
            }
        };

        Comparator<Integer> integerComparator2 = (o1, o2) -> {
            System.out.println("Lamda.compare. 여러 쀄읎 가능하닀!");
            System.out.println("Lamda.compare. 여러 쀄읎 가능하닀!");
            return 0;
        };
    }

 

 

람닀 표현식 슀타음 특징 3)

(parameter) -> expression
(parameters) -> { statements; }

 

 

람닀 표현식읎 ê°„ë‹ší•œ 한 쀄읞 겜우에는 쀑ꎄ혞륌 생략할 수 있닀.

 

(Integer i) -> return i; // 틀늌!
(Integer i) -> { return i; } // 맞음!

 

하지만, 윔드 랔록읎 여러 쀄로 읎룚얎젞 있거나 명시적읞 반환을 í•Žì•Œ 하는 겜우에는 쀑ꎄ혞륌 사용하여 윔드 랔록을 정의핎 쀘알 하람닀.


cf) 흐멄 제얎묞 return은 생략할 수 있닀

파읎썬에서의 표현식(expression)읎 있는 묞장에서는 볎통 return읎 생략될 수 있닀.

읎처럌, 표현식을 자동적윌로 추론할 수 있Ʞ 때묞에 명시적읞 return 묞을 사용하지 않아도 람닀 핚수는 표현식의 결곌륌 반환핮 쀀닀.

add = lambda x, y: x + y
result = add(3, 5)
print(result)  # 출력: 8

 


2. 람닀 사용법

람닀 표현식은 핚수형 읞터페읎슀/핚수 디슀크늜터로 사용할 수 있닀

 

1) 핚수형 읞터페읎슀

자바의 핚수형 읞터페읎슀는 닀양하게 있습니닀. 핚수형 읞터페읎슀는 하나의 추상메서드륌 지정하는 읞터페읎슀입니닀.

자바 API의 핚수형 읞터페읎슀는 Comparator, Runnale, Predicate 등읎 있습니닀.

귞쀑, Predicate 핚수형 읞터페읎슀는 아래와 같읎 생게습니닀람닀.

 

public interface Predicate<T> {
    boolean test (T t);
}

 

 

 

왜 핚수 읞터페읎슀 얘Ʞ가 나왔을까.

람닀 표현식을 핚수형 읞터페읎슀의 읞슀턎슀로 만듀 수 있Ʞ 때묞읎닀.
정확히는 핚수형 읞터페읎슀륌 구현한 큎래슀의 읞슀턎슀로 만듀 수 있닀람닀.

익명 낎부 큎래슀도 구현 가능합니닀.

 

  public static void process(Runnable runnable) {
        runnable.run();
    }

    @Test
    @DisplayName("람닀 표현식윌로 핚수형 읞터페읎슀 전달")
    void functionalInterface() {
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("Lamda.run");
            }
        };
        Runnable runnable2 = () -> System.out.println("Lamda.functionalInterface");

        process(runnable1);
        process(runnable2);
        process(()-> System.out.println("Lamda.functionalInterface 직접 람닀로 전달"));
    }

 

Runnable은 핚수형 읞터페읎슀Ʞ 때묞에, 람닀 표현식윌로 구현읎 가능하닀.

하나의 추상 메서드읞 run() 메서드만 가지고 있Ʞ 때묞읎닀.

 

2) 핚수 디슀크늜터

핚수 디슀크늜터?  람닀 표현식의 시귞니처륌 서술하는 메서드읎닀.

람닀 표현식의 시귞니처? 핚수형 읞터페읎슀의 추상 메서드 시귞니처읎닀.

 

슉, 핚수형 읞터페읎슀의 추상 메서드 시귞니처륌 알멎, ê·ž 읞터페읎슀륌 람닀 표현식윌로 표현할 때

필요한 맀개변수 타입 및 반환 타입을 읎핎할 수 있닀능.

 

추가 섀명핎 볎자멎, 시귞니처는 핚수의 타입읎닀. 람닀가 아닌 음반 메서드에서도 사용하는 ìš©ì–Žë‹€.

람닀 말고, 음반 메서드륌 통핎 시귞니처륌 뎐볎겠닀.

 

public int add(int x, int y) {
    // 메서드 볞묞
}

 

메서드의 시귞니처는 add(int x, int y)읎닀.

읎륌 통핎 메서드의 읎늄읎 "add"읎고 파띌믞터는 두 개의 정수륌 받고, 반환 타입은 정수(int) 임을 알 수 있닀.

 

Runnable myRunnable = () -> {
    // 싀행 윔드
};

 

람닀의 시귞니처 예제도 뎐볎자.

람닀 표현식의 시귞니처는 () -> voidë‹€.

읎것은 파띌믞터륌 받지 않고, 반환값읎 없는 핚수구나~~띌는 것을 시귞니처륌 통핎 알 수 있닀.

 

정늬하자멎, 닀음곌 같닀.

  •  () -> void 표Ʞ:  파띌믞터 늬슀튞가 없윌며 void륌 반환하는 핚수륌 의믞핚.
  • (Apple, Apple) -> int: 두 개의 Apple륌 읞수로 받아 int륌 반환하는 핚수란 뜻읎잖슎~

=> 람닀 표현식은 변수에 할당하거나 핚수형 읞터페읎슀륌 읞수로 받는 메서드로 전달할 수 있음.

읎쯀 되멎, 궁ꞈ핎진닀. 왜 핚수형 읞터페읎슀륌 읞수로 받는 메서드에만 람닀 표현식읎 사용 가능한지.

읎유는 ì–žì–Ž 섀계자듀읎 복잡하지 않고 더 익숙한 방법을 사용할 수 있도록 한닀고 핹.

 

cf) 헷갈렞던 ê±°

  • 핚수 디슀크늜터는 람닀 표현식의 타입을 섀명하Ʞ 위한 개념
  • 싀제로 윔드에서는 핚수 디슀크늜터륌 직접 사용하는 것은 아님!!
  • 람닀 표현식을 통핎 컎파음러가 자동윌로 핚수 디슀크늜터륌 유추하Ʞ 위한 것임.

cf) 메서드의 시귞니처

메서드의 시귞니처는 메서드륌 식별하는 데 사용되는 정볎륌 나타낞닀.

메서드의 읎늄, 맀개변수의 타입곌 순서, 반환 타입 등을 포핚하는데, 메서드가 혞출될 때 ì–Žë–€ 맀개변수륌 받아듀읎고 ì–Žë–€ 타입의 값을 반환하는지에 대한 정볎륌 ë‹Žê³  있닀.

[ì ‘ê·Œ 제얎자] [반환 타입] 메서드 읎늄(맀개변수 타입1 맀개변수 읎늄1, 맀개변수 타입2 맀개변수 읎늄2, ...)

 

 

  • ì ‘ê·Œ 제얎자(Access Modifier):메서드에 대한 ì ‘ê·Œ 권한. ex) public, private
  • 반환 타입(Return Type): 메서드가 반환하는 값의 데읎터 타입. ex) void
  • 메서드 읎늄(Method Name): 메서드의 읎늄
  • 맀개변수(Parameter): 메서드가 받는 입력 값듀을 정의 

슉, 자바에서 시귞니처는 메서드나 핚수의 읎늄, 맀개변수의 타입, 귞늬고 반환 타입을 통핎 정의되는데,

시귞니처륌 통핎 윔드륌 읎핎하고 유지볎수하는 데 도움읎 된닀.

 

3) 람닀 활용법: 싀행 얎띌욎드 팹턮

싀행 얎띌욎드 팚턎읎란?

싀제 자원을 처늬하는 윔드륌 섀정곌 정늬 두 곌정읎 둘러싌 형태륌 말한닀.

싀행 얎띌욎드 팚턎을 활용한 예제륌 뎐볎자.

 

public String processFile() throws Exception {
        try(
            BufferedReader bufferedReader = new BufferedReader(new FileReader("data.txt"))) {
            return bufferedReader.readLine();
        }
    }

 

읎륌 핚수형 읞터페읎슀와 람닀륌 읎용핎서 전달핎 볎자.

 

@FunctionalInterface
    public interface BufferReaderProcessor {
        String process(BufferedReader bufferedReader) throws IOException;
    }

@Test
    @DisplayName("싀행 얎띌욎드 팹턮")
    void excuteAroundPattern() throws FileNotFoundException {
        // processFile() 핚수의 동작을 파띌믞터화
        //String result = processFile(new BufferedReader(new FileReader("example.txt")));

        // 핚수형 읞터페읎슀로 전달
        String result2 = processFile((BufferedReader br) -> {
            try {
                return br.readLine();
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        });
    }

 

핚수형 읞터페읎슀로 바꿔서 핎뎀닀.

귞러멎 두 쀄을 처늬하는 윔드도 작성 가능하닀.

 

String twoLineResult = processFile((BufferedReader bufferReader) -> bufferReader.readLine() + bufferReader.readLine());

3. 핚수형 읞터페읎슀 사용

핚수형 읞터페읎슀 별도 Ꞁ ì°žê³ !

https://mutpp.tistory.com/entry/%ED%95%A8%EC%88%98%ED%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-Functional-Interface

 

핚수형 읞터페읎슀 (Functional Interface)

Java에서 핚수형 읞터페읎슀는 닚음 추상 메서드(Single Abstract Method, SAM)륌 가진 읞터페읎슀닀. Java 8 읎상에서는 람닀 표현식 및 핚수형 읞터페읎슀륌 지원한닀. 핚수형 읞터페읎슀의 추상 메서드

mutpp.tistory.com

 

쀑요한 점은, 핚수형 읞터페읎슀는 였직 하나의 추상 메서드륌 지정한닀.

귞럌 읎 추상 메서드는 뭐닀? 람닀 표현식의 시귞니처륌 묘사하게 된닀.

 

읎륌 핚수 디슀크늜터띌고 한닀. 얘는 뒀에서 닀시 더 섀명하겠닀.

 

아묎튌! 공통의 핚수 디슀크늜터륌 Ʞ술하는 핚수형 읞터페읎슀 집합읎 있얎알 닀양한 람닀 표현식 사용읎 가능하닀.

자바 8 띌읎람러늬에는 java.util.function 팚킀지로 자바 API의 Comparable, Runnable, Callable 등의 여러 가지 새로욎 핚수형 읞터페읎슀륌 제공한당. 

얘넀륌 얎떻게 사용핎알 하는지 연습하멎 되람닀.

 

1. Predicate

java.util.function.Predicate<T> 읞터페읎슀는 test() 띌는 추상 메서드륌 정의한닀.

boolean 표현식읎 필요할 때 Predicate 읞터페읎슀륌 사용할 수 있닀.

 

    @FunctionalInterface
    public interface Predicate<T> {
        boolean test(T t);
    }

    public <T> List<T> filter(List<T> list, Predicate<T> predicate) {
        List<T> results = new ArrayList<>();
        for (T t : list) {
            if(predicate.test(t)) {
                results.add(t);
            }
        }
        return results;
    }

    @Test
    @DisplayName("Predicate 예제")
    void predicateExample() {
        Predicate<String> emptyStringPredicate = (String s) -> s.isEmpty();
        List<String> stringList = new ArrayList<>();
        stringList.add("TEST");
        stringList.add("TEST2");
        List<String> emptyStringList = filter(stringList, emptyStringPredicate);
        assertEquals(emptyStringList.size(), 0);
    }

 

2. Consumer

java.util.function.Consumeer<T> 읞터페읎슀는 void륌 반환하는 accept 띌는 추상 메서드륌 정의핚.

Consumer의 닚얎의 뜻은 소비자, 소비하닀란 뜻읎잖슎?

ë‹šì–Ž 뜻 귞대로, 반환값도 없고 정말 추상 메서드로 넘ꞎ 동작만 수행핎 쀌.

맀우 ì¿ší•œ 녀석임.

 

    @FunctionalInterface
    public interface Consumer<T> {
        void accept(T t);
    }

    public <T> void printMessage(T printMsg, Consumer<T> consumer) {
        consumer.accept(printMsg);
    }

    @Test
    @DisplayName("Consumer 예제")
    void consumerExample (){
        printMessage(12345, (Integer msg) -> System.out.println("Integer Lamda.consumerExample"));
        printMessage("소비핎버늎테닀", (String msg) -> System.out.println("String Lamda.consumerExample"));
    }

 

위 예제는, 간닚하게 파띌믞터로 넘ꞎ 메시지륌 프며튾 묞윌로 출력하는 예제당

파띌믞터의 타입곌 묎ꎀ하게, 람닀로 넘ꞎ 동작을 성싀히 수행핎 쀀 것을 확읞할 수 있닀.

 

3. Function

java.util.function.Function<T, R> 읞터페읎슀는 T륌 읞수로 받아서 R 객첎륌 반환핮 죌는 추상 메서드 apply륌 정의핚.

입력을 출력윌로 맵핑하는 람닀륌 정의할 때 활용할 수 있닀.

 

    @FunctionalInterface
    public interface Function<T, R> {
        R apply(T t);
    }

    public <T, R> R applyMsg(T msg, Function<T, R> function) {
        return function.apply(msg);
    }

    @Test
    @DisplayName("Function 예제")
    void functionExample() {
        Integer result = applyMsg("안녕하섞요~", (String msg) -> msg.length());
        assertEquals(6, result);
    }

 

바로 요렇게.

위 예제는 파띌믞터로 넘ꞎ 묞자엎의 Ꞟ읎륌 결곌로 받는 예제읎닀.

 


cf) 핚수형 읞터페읎슀 타입

위에서 볞 Predicate, Consumer, Function<T, R> 핚수형 읞터페읎슀는 Ʞ볞형 읞터페읎슀닀.

 

제넀늭(T랑 R) 파띌믞터에는 찞조형!! 만 사용읎 가능하닀. (낎부 구현 때묞에 귞렇닀고 핹)

묌론 자바에서는 Ʞ볞형->찞조형윌로 변환하는 Ʞ능을 제공핚.

 

Ʞ볞형, 찞조형은 아래와 같닀.

  • Ʞ볞형: int, double, byte, char 등
  • 찞조형: Object, Integer, List 등

 

    @Test
    @DisplayName("찞조형을 Ʞ볞형윌로 얞박싱")
    void unboxTest() {
        Integer integer = 100;
        int intValue = integer;
        assertEquals(100, intValue);
    }

    @Test
    @DisplayName("Ʞ볞형을 찞조형윌로 박싱")
    void boxTest() {
        int intValue = 100;
        Integer integer = intValue;
        assertEquals(100, integer);
    }

 

너묎 펾한 Ʞ능읎닀. 였토박싱 읎띌고도 한닀.

귌데 프로귞래뚞가 펾한 것은 뭐닀? 컎퓚터가 고생 쀑읎닀~ 고생 쀑은 뭐닀? 메몚늬륌 더 소비한닀~

 

왜냐멎, 박싱할 때 Ʞ볞형을 감싞서 힙에 저장하는데(랩핑읎띌고 핹), 귞래서 덩치가 컀젞서 메몚늬륌 더 소비한닀.

힙에 저장하게 되멎, 닀시 Ʞ볞형을 가젞올 때도 닀시 한번 메몚늬륌 탐색하는 곌정읎 필요하게 된닀.

 

귞래서, 읎런 였토 박싱 동작을 플하Ʞ 위한 자바 8의 핚수형 읞터페읎슀가 제공된닀.

 

    @FunctionalInterface
    public interface IntPredicate {
        boolean test(int t);
    }

    @Test
    @DisplayName("IntPredicate로 였토박싱 안하Ʞ")
    void intPredicateExample() {
        IntPredicate intPredicate = (int i) -> i+100 > 0;
        Predicate<Integer> integerPredicate = (Integer integer) -> integer+100 > 0;

        assertEquals(true, intPredicate.test(100));
        assertEquals(true, integerPredicate.test(100));
    }

 

읎렇게 사용할 수 있음.

제넀늭 타입을 사용하지 않고 특정 형식을 받는 핚수형 읞터페읎슀읞 것 ㄷㄷ

IntPredicate 말고도 DoublePrediacte, IntConsumer, IntFunction 등의 형식윌로 사용 가능하닀.

두 개의 제넀늭을 사용하는 Function<T, R>은 ToIntFunction 같은 형식윌로도 ì“Žë‹€.

닀륞 핚수형 읞터페읎슀는 닀시 공부핎알겠당.

 

예왞, 람닀, 핚수형 읞터페읎슀

핚수형 읞터페읎슀는 예왞 동작은 처늬하지 않는닀.

귞러멎 예왞륌 얎떻게 처늬하느냐?

 

    @FunctionalInterface
    public interface bufferReader {
        String process(BufferedReader bufferedReader) throws IOException;
    }

    @Test
    @DisplayName("핚수형 읞터페읎슀 예왞 처늬 하Ʞ")
    void exceptionFunctionInterface () {
        Function<BufferedReader, String> function = (BufferedReader bufReader) -> {
            try {
                return bufReader.readLine();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        };
    }

 

try-catch 랔록윌로 감싞서 핎죌멎 됚.


4. 형식 추론

예제로 형식 추론을 핎볎자.

아래와 같은 Comparator 객첎 만드는 윔드가 있닀.

 

Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());

 

읎륌 핚수 디슀크늜터, 시귞니처, 윘텍슀튞로 섀명할 수 있잰.

 

1. 윘텍슀튞

윘텍슀튞는 죌변 환겜에서 람닀가 싀행되는 묞맥읎닀.

여Ʞ서의 람닀 표현식은 Comparator<Apple>륌 정의하고 있닀.

바디에는 compare 메서드의 로직읎 볎읞닀.

읎 람닀 표현식은 Apple 객첎의 묎게륌 Ʞ쀀윌로 비교하는구나~ 띌는 것을 알 수 있닀.

윘텍슀튞로 람닀의 파띌믞터 형식을 알 수 있음. 귞러멎 핚수 디슀크늜터륌 정의할 수 있음!!

 

2. 핚수 디슀크늜터

핚수 디슀크늜터는 (Apple, Apple) -> int임.

읎는 Comparator의 compare 메서드겠구나~ 륌 알 수 있음.

compare 메서드는 두 개의 Apple 객첎륌 비교하고 정수륌 반환할 것 또한 예상 가능핚.

윘텍슀튞로 핚수 디슀크늜터륌 알 수 있고, 컎파음러는 핚수 디슀크늜터로 시귞니처 추론읎 가능핎짐.

귞래서 묞법 생략읎 가능핎짐.

 

3. 시귞니처

람닀 표현식의 시귞니처는 (a1, a2)로 시작하고, 였륞쪜의 a1.getWeight().compareTo(a2.getWeight())는 메서드 볞묞읎닀.

여Ʞ서 (a1, a2)는 맀개변수 목록임을 알 수 있닀.

따띌서 전첎 람닀 표현식의 시귞니처는 (a1, a2) ->가 되시겠닀.

 

깔쌈 요앜

  • 죌얎진 윔드는 Comparator<Apple>륌 정의한닀.
  • 핚수 디슀크늜터는 (Apple, Apple) -> int
  • 람닀 표현식의 시귞니처는 (a1, a2) ->
  • 윘텍슀튞는 compare로 메서드의 로직을 제공하는 Comparator 읞터페읎슀의 구현읎닀.

 


5. 지역 변수 사용

람닀 표현식에서는 자유변수도 사용 가능하닀.

ë‹šì–Žê°€ 좀 얎렀욎데; 귞냥 int a=1띌고 ì“°ë©Ž a가 자유 변수임.

람닀에서 자유 변수륌 사용하멎 람닀 캡쳐링읎띌고 핹.

 

    @Test
    @DisplayName("람닀 캡쳐링")
    void lamdaCapture () {
        int number = 12345;

        Runnable runnable = () -> System.out.println(number);
    }

 

귌데, 람닀에서 지역 변수륌 사용할 때는 죌의할 점읎 있닀.

지역 변수륌 final로 선얞하거나 final로 췚꞉핎알 한닀.

 

    @Test
    @DisplayName("람닀 지역변수는 final 처럌 췚꞉ 되얎알 한닀.")
    void localVarLamdaTest () {
        int a = 10;
        int b = 20;

        Runnable runnable = () -> System.out.println(a+b);

        System.out.println(a);
    }

 

java 8 읎전에는 final int a = 10; 처럌 final로 선얞을 핎쀘알 했지만, java 8에서는 선얞은 생략읎 가능하닀. (읎륌 effectively final 읎띌고 핹)

위 예제는 a, b 지역 변수가 한 번만 값읎 변겜되고 싀질적윌로 final로 동작하Ʞ 때묞에 람닀 묞에서 였류가 발생하지 않는닀.

하지만 값에 변겜읎 음얎나멎 사용할 수가 없습넀닀.

 

 @Test
    @DisplayName("람닀 지역변수는 final 처럌 췚꞉ 되얎알 한닀.")
    void localVarLamdaTest () {
        int a = 10;
        int b = 20;

        a = 30;

        Runnable runnable = () -> System.out.println(a+b); // Error!!

        System.out.println(a);
    }

 

에러가 발생한닀.

 

왜 귞럎까?

읎유는, 람닀 표현식읎 큎로저륌 형성할 때 핎당 변수의 값을 저장하Ʞ 때묞읎닀.


 

cf) 큎로저

윔드 랔록 안에서 자유롭게 접귌할 수 있는 왞부 범위의 변수읎닀.

 

람닀는 왞부 변수의 값을 복사핎서 사용한닀. 값만!! 복사한닀.

큎로저는 자신읎 정의된 범위(scope)에서 왞부 변수에 접귌할 수 있는데, 읎륌 위핎 람닀는 핎당 변수의 값을 final 또는 effectively final로 만듀얎 복사한닀.

복사륌 통핎 람닀 표현식은 ê·ž 값을 고정된 상태로 유지하며 사용읎 가능하람닀.

따띌서 람닀가 사용될 때의 변수 값곌 람닀가 싀행될 때의 변수 값읎 항상 같게 된닀.

 

귞럌 뭐가 좋윌냐. 슀레드 안전성읎나 예잡 가능한 동작읎 볎장됩니닀.

닀륞 슀레드에서 읎 변수의 값을 변겜 못하게 í•Žì„œ 예잡 가능한 결곌륌 얻도록 볎장하는 것임.

만앜 람닀가 변수의 ì°žì¡°ê°€ 아니띌 값을 직접 변겜한닀멎 묞제가 발생할 수 있을 것임..

 

정늬하자멎, 람닀 표현식읎 슀레드 안전성곌 예잡 가능한 동작을 볎장하Ʞ 위핎 읎런 제앜을 뒀고

final 또는 effectively final 변수륌 사용핚윌로썚 람닀 표현식읎 큎로저로 동작할 때 변수의 값읎 변하지 않아 예상치 못한 동작을 방지할 수 있닀람닀~


6. 메서드 ì°žì¡°

메서드 ì°žì¡° 방식윌로 람닀처럌 사용할 수 있닀.

메서드 찞조는 윔드륌 간결하게 만듀고 가독성을 향상하는 데 도움읎 되는 Java 8의 Ʞ능 쀑 하나읎닀.

묌론 읎거도 하나의 메서드만 혞출할 때 가능하닀.

원래 우늬가 아는 람닀 표현식을 예시로 듀얎볎멎 닀음곌 같닀.

 

List<String> words = Arrays.asList("apple", "banana", "orange");

// 람닀 표현식을 사용하여 묞자엎의 Ꞟ읎륌 반환
List<Integer> lengths = words.stream()
                            .map(s -> s.length())
                            .collect(Collectors.toList());

 

읎륌 메서드 ì°žì¡°ë¡œ 바꿔볎멎?

 

List<String> words = Arrays.asList("apple", "banana", "orange");

// 메서드 찞조륌 사용하여 묞자엎의 Ꞟ읎륌 반환
List<Integer> lengths = words.stream()
                            .map(String::length)
                            .collect(Collectors.toList());

 

짠-! String::length 윔드가 바뀌었닀.

나는 람닀도 처음 사용할 때는 ì°ž 많읎도 쀄여뒀넀.띌고 생각했는데, 메서드 찞조륌 볎니, 자바 섀계자듀읎 더 쀄읎고 싶얎 했구나... 싶었닀.

 

메서드 ì°žì¡°ê°€ 쀑요한 읎유는 크게 4가지닀.

  1. 가독성 향상: 메서드 찞조는 윔드륌 간결하게 만듀얎 가독성을 향상한닀. 특히 람닀 표현식읎 메서드륌 혞출하는 ê°„ë‹ší•œ 겜우에 핎당합니닀. 불필요한 람닀 표현식을 쀄읎고, 윔드의 의도륌 더 명확하게 전달할 수 있음
  2. 재사용성 슝가: 메서드 찞조륌 사용하멎 Ʞ졎의 메서드륌 닀륞 윘텍슀튞에서 재사용할 수 있습니닀. 윔드륌 작성할 때 특정 메서드륌 가늬킀는 것윌로, 핎당 메서드의 로직을 변겜하지 않고도 새로욎 Ʞ능을 도입할 수 있습니닀.

-> 읎걎, 람닀 표현식도 귞런 ê±° 아니알? 할 수 있닀. 귌데 메서드 찞조는 람닀륌 가독성 있게 사용하렀는 거니까 넘얎가죌삌

  1. 핚수형 프로귞래밍 지원: 메서드 찞조는 핚수형 프로귞래밍의 핵심 Ʞ능 쀑 하나입니닀. ê²°êµ­ 람닀의 좋은 점읎 메서드 찞조의 장점곌 겹친닀. ㅋ 메서드 찞조는 읎러한 핚수형 슀타음을 쉜게 표현할 수 있도록 도와쀀닀.
  2. 컎파음러 최적화: 음부 겜우(ꌭ 귞런 걎 아님!!)에는 메서드 찞조륌 사용하멎 컎파음러가 더 횚윚적읞 윔드륌 생성할 수 있습니닀. 읎는 성능을 향상하는 데 도움읎 될 수 있닀고 한닀. 쀄읎 짧아젞서 귞렇닀는 얘Ʞ도 있고...

묎튌, 간결한 묞법곌 높은 가독성윌로 읞핎 메서드 찞조는 Java 8에서 도입된 핚수형 프로귞래밍 Ʞ능 쀑 하나로 많읎 쓰읞닀고 한닀.

쀑요한 것은 가독성읎 좋아진닀는 ê±°-!

 

1. 메서드 ì°žì¡° 만드는 방법

3가지 방법읎 있음.

 

1) 정적 메서드 ì°žì¡° (Static Method Reference)

큎래슀읎늄::정적메서드로 표현

예륌 듀얎, Math 큎래슀의 정적 메서드읞 abs륌 찞조하는 겜우: Math::abs 같은~

 

2) 닀양한 형식의 읞슀턎슀 메서드 ì°žì¡°

찞조변수::읞슀턎슀메서드로 표현

예륌 듀얎, 묞자엎의 length 메서드륌 찞조하는 겜우: String::length

 

3) êž°ì¡Ž 객첎의 읞슀턎슀 메서드 ì°žì¡°

객첎찞조::읞슀턎슀메서드로 표현합니닀.

예륌 듀얎, 특정 객첎의 메서드륌 찞조하는 겜우: object::instanceMethod

3가지 방법윌로 작성핎 볎멎 읎렇게 ì“ž 수 있닎.

 

    class StringConcatenator {
        public void concatenate(String word) {
            System.out.print(word + " ");
        }
    }

    @Test
    @DisplayName("메서드 ì°žì¡°")
    void staticMethodTest() {
        // 1. 정적 메서드 ì°žì¡°
        List<String> singer = Arrays.asList("아읎유", "레드벚벳", "QWER");
        singer.forEach(System.out::println);  // System.out읎띌는 큎래슀의 정적 메서드 println ì°žì¡°

        // 2. 닀양한 형식의 읞슀턎슀 메서드 ì°žì¡°
        singer.sort(String::compareToIgnoreCase);  // String 큎래슀의 읞슀턎슀 메서드 compareToIgnoreCase ì°žì¡°

        // 3. êž°ì¡Ž 객첎의 읞슀턎슀 메서드 ì°žì¡°
        StringConcatenator concatenator = new StringConcatenator();
        singer.forEach(concatenator::concatenate);  // StringConcatenator 객첎의 읞슀턎슀 메서드 concatenate ì°žì¡°
    }

 

컎파음러는 람닀 표현식 검사하던 방식곌 비슷하게, 메서드 ì°žì¡°ê°€ 핚수형 읞터페읎슀와 혞환되는지 확읞한닀.

 

2. 생성자 ì°žì¡°

ë‚Žê°€ 제음 얌륞 윔드 늬팩토링 핎볎고 싶닀고 생각한 부분..

위에서 뎀던 핚수형 읞터페읎슀 Ʞ억나는감?

시귞니처가 같은 핚수형 읞터페읎슀륌 사용핎서 생성자륌 만듀 수 있닀.

 

  @Test
    @DisplayName("생성자 ì°žì¡°")
    void constructorReference() {
        // 람닀 표현식윌로 Apple 객첎 생성자 만듀Ʞ
        Supplier<Apple> appleSupplier1 = () -> new Apple();

        // 생성자 ì°žì¡°ë¡œ 읎렇게 사용할 수 있닀
        Supplier<Apple> appleSupplier2 = Apple::new;

        // Supplier의 get윌로 객첎 만듀 수도 있닀
        Apple apple = appleSupplier2.get();

        BiFunction<Color, Integer, Apple> biFunction = Apple::new;
        Apple apple1 = biFunction.apply(Color.RED, 20);
    }

 

읞슀턎슀화하지 않고도 생성자로 객첎륌 만듀 수 있닀.


7. 람닀, 메서드 ì°žì¡° 활용하Ʞ

1 닚계) sort 메서드에 정렬 전략 전달하Ʞ

void sort(Comparator<? super E> c)

 

sort 핚수의 시귞니처는 읎렇게 생게닀.

 

    public class AppleComparator implements Comparator<Apple> {

        @Override
        public int compare(Apple o1, Apple o2) {
            return o1.getWeight() > o2.getWeight() ? o1.getWeight() : o2.getWeight();
        }
    }

    @Test
    @DisplayName("sort 전략 전달하Ʞ")
    void sortTest() {
        List<Apple> appleList = new ArrayList<>();
        appleList.sort(new AppleComparator());
    }

 

sort 동작을 파띌믞터 화한 예제읎닀. 읎렇게 Comparator 객첎로 전략을 전달하멎 닀양한 전략을 전달할 수 있닀.

 

2 닚계) 익명 큎래슀 사용

만앜 위 윔드의 Comparator 큎래슀륌 한 번만 사용한닀멎 큎래슀로 구현하는 것볎닀는 익명 큎래슀로 작성하멎 간닚하닀.

 

    @Test
    @DisplayName("익명 큎래슀로 구현하Ʞ")
    void anonymousClassTest() {
        List<Apple> appleList = new ArrayList<>();
        //appleList.sort(new AppleComparator());

        appleList.sort(new Comparator<Apple>() {
            @Override
            public int compare(Apple o1, Apple o2) {
                return o1.getWeight() > o2.getWeight() ? o1.getWeight() : o2.getWeight();
            }
        });
    }

 

 

3닚계) 람닀 표현식 사용하Ʞ

윔드륌 닀읎얎튞시쌜볎자.

 

Comparator 핚수의 디슀크늜터는? (T,T) -> int

귞러멎 Apple Comparator 핚수 디슀크늜터는? (Apple, Apple) -> int

    @Test
    @DisplayName("람닀로 구현하Ʞ")
    void lamdaTest() {
        List<Apple> appleList = new ArrayList<>();

        // 람닀 표현식 사용하멎 읎렇게!
        appleList.sort((Apple o1, Apple o2) -> o1.getWeight() > o2.getWeight() ? o1.getWeight() : o2.getWeight());
        // 자바 컎파음러는 람닀 표현식의 컚텍슀튞륌 볎고, 파띌믞터 형식을 추론할 수 있닀.
        appleList.sort((o1, o2) -> o1.getWeight() > o2.getWeight() ? o1.getWeight() : o2.getWeight());
    }

 

더 가독성을 향상하Ʞ 위핎서 Comparator 객첎의 comparing 메서드륌 사용할 수 있닀.

Comparator는 ê°ì²Žë¡œ ë§Œë“œëŠ” Function í•šìˆ˜ë¥Œ ìžìˆ˜ë¡œ ë°›ëŠ” ì •ì  ë©”서드읞 comparing을 í¬í•ší•˜ê³  ìžˆë‹€.

 

static <T,U extends Comparable<? super U>>
Comparator<T> comparing(Function<? super T,? extends U> keyExtractor)


타입 T에서 ë¹„교 ê°€ëŠ¥í•œ ì •ë ¬ í‚€ë¥Œ ì¶”출하는 í•šìˆ˜ë¥Œ ë°›ì•„, í•Žë‹¹ ì •ë ¬ í‚€ë¡œ ê°ì²Ž T륌 ë¹„교하는 Comparator<T>륌 ë°˜í™˜í•Ž 쀀닀.

    @Test
    @DisplayName("람닀로 구현하Ʞ")
    void lamda2Test() {
        List<Apple> appleList = new ArrayList<>();

        Comparator<Apple> comparator = Comparator.comparing((Apple apple)-> apple.getWeight());
        appleList.sort(Comparator.comparing(apple -> apple.getWeight()));
    }

 

귞래서 요렇게 바꿔쀄 수 있닀.

 

4닚계) 메서드 ì°žì¡° 사용하Ʞ

마지막 닚계는 메서드 ì°žì¡°ë¡œ 바꿔볎자.

 

 @Test
    @DisplayName("메서드 ì°žì¡°ë¡œ 구현하Ʞ")
    void methodTest() {
        List<Apple> appleList = new ArrayList<>();
        appleList.sort(Comparator.comparing(Apple::getWeight));
    }

 


 

요앜

  • 람닀 표현식은 윔드륌 간결하게 표현하Ʞ 위한 자바 8에 추가된 묞법읎닀.
  • 람닀 표현식은 익명 핚수의 한 종류읎닀. 읎늄은 없지만 파띌믞터 늬슀튞/바디/반환 형식을 가진닀.
  • 핚수형 읞터페읎슀는 하나의 추상 메서드륌 가지는 읞터페읎슀닀.
  • 람닀 표현식 전첎가 핚수형 읞터페읎슀의 읞슀턎슀가 된닀.