ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Lambda
    기술 면접/Java 2022. 8. 18. 21:08

    람다식이란?

    람다 식이란 Java8에 새로 추가 됐고 함수형 프로그래밍을 위한 첫 단추가 됐다.

    람다식을 사용하는 함수를 람다 함수라 한다. 람다 함수는 익명 함수를 지칭하는 용어이며 함수를 보다 단순하게 표현하기 위해 사용된다.

    람다식을 사용하면 메소드가 어느 특정 클래스에 선언하지 않으며 기능을 수행할 수 있다. 또한 객체처럼 전달되어 요청시 실행할 수 있다.

     

    람다 표현식

     

    기본식

    (매개변수, ...) -> { 실행문 ... }

    (매개변수, ...)는 오른쪽 중괄호 { } 블록을 실행하기 위해 필요한 값을 제공하는 역할을 한다. 매개 변수의 이름은 개발자가 자유롭게 지정할 수 있으며 인자타입도 명시하지 않아도 된다.

     

    -> 기호는 매개 변수를 이용해서 중괄호 { } 바디를 실행한다는 뜻으로 해석하면 된다.

     

    람다식의 종류

    //정상적인 유형
    () -> {}
    () -> 1
    () -> { return 1; }
    
    (int x) -> x+1
    (x) -> x+1
    x -> x+1
    (int x) -> { return x+1; }
    x -> { return x+1; }
    
    (int x, int y) -> x+y
    (x, y) -> x+y
    (x, y) -> { return x+y; }
    
    (String lam) -> lam.length()
    lam -> lam.length()
    
    (Thread lamT) -> { lamT.start(); }
    lamT -> { lamT.start(); }
    
    
    //잘못된 유형 선언된 type과 선언되지 않은 type을 같이 사용 할 수 없다.
    (x, int y) -> x+y
    (x, final y) -> x+y

     

    1. 함수몸체 ({ 실행문 ... }) 가 단일 실행문이면 괄호{}를 생략 할 수 있다.
    2. 함수몸체가 return문으로만 구성되어 있는 경우 괄호{}를 생략 할 수 없다.

     

    람다식 예제

    함수형 인터페이스

    함수형 인터페이스는 구현해야 할 추상 메소드가 하나만 정의된 인터페이스를 가리킨다.

    ‎람다 식은 메서드 매개 변수(Calc)의 매개 변수 타입(int first, int second)과 맞춰야 한다.

     

    함수형 인터페이스 구현에 람다식을 사용하기 전에는 익명 클래스(java 1.1)를 사용했다.

     

    함수형 인터페이스

    @FunctionalInterface
    interface Math {
        public int Calc(int first, int second);
    }

     

    추상 메소드 구현 및 사용

    Math plusLambda = (first, second) -> first + second;
    System.out.println(plusLambda.Calc(4, 2));
    
    Math minusLambda = (first, second) -> first - second;
    System.out.println(minusLambda.Calc(4, 2));

     

    익명 클래스

    	Math plusLambda = (first, second) -> first + second;
            Math plusLambda = new Math() {
                @Override
                public int Calc(int first, int second) {
                    return first + second;
                }
            };
            System.out.println(plusLambda.Calc(4, 2));
    
    //        Math minusLambda = (first, second) -> first - second;
            Math minusLambda = new Math() {
                @Override
                public int Calc(int first, int second) {
                    return first - second;
                }
            };
            System.out.println(minusLambda.Calc(4, 2));

     

    Event Listener/Callback

    람다는 특히 간단한 이벤트 리스너 콜백 함수로 많이 사용된다.

     

    함수형 인터페이스

    public interface OnClickListener {
        void onClick(View var1);
    }

    버튼이 눌렸을 때 실행 되는 onClick 함수를 포함한 onClickListener 인터페이스

     

    인터페이스 포함 클래스

    public void setOnClickListener(@Nullable View.OnClickListener l) {
            throw new RuntimeException("Stub!");
    }

    OnClickListener 추상 메소드를 매개변수로 전달

     

     

    추상 메소드 구현 및 사용

    	btn_signUp.setOnClickListener(view -> {
                Intent intent = new Intent(StartActivity.this, SignUpActivity.class);
                startActivity(intent);
                finish();
            });

     

    익명 클래스

    	btn_signUp.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Intent intent = new Intent(StartActivity.this, SignUpActivity.class);
                    startActivity(intent);
                    finish();
                }
            });

     

    java.util.function

    Java에서 지원하는 함수형 인터페이스

     

    IntFunction<R>

    int 값의 인수를 받아들이고 결과를 생성하는 함수를 나타낸다.

    추상메소드 구현 및 사용

    IntFunction intSum = (x) -> x+1;
    System.out.println(intSum.apply(1));

    익명 클래스

    IntFunction intSum = new IntFunction() {
                @Override
                public Object apply(int value) {
                    return value + 1;
                }
            };
    System.out.println(intSum.apply(1));

     

    BinaryOperator<T>

    같은 타입의 두 피연산자에 대한 연산을 나타내며 피연산자와 같은 타입의 결과를 생성한다.

    추상메소드 구현 및 사용

    BinaryOperator stringSum=(x, y)->x+" "+y;
    System.out.println(stringSum.apply("Hello","world"));

     

    익명 클래스

    BinaryOperator stringSum= new BinaryOperator() {
                @Override
                public Object apply(Object o, Object o2) {
                    return o+" "+o2;
                }
            };
    
    System.out.println(stringSum.apply("Hello","world"));

    실행 결과

    Hello world

     

     

    Stream API

    Stream이란 다양한 데이터를 표준화된 방법으로 다루기 위한 라이브러리이다. 자바 8부터 추가 됐다.

     

    기본 형식

    example.stream().filter(x -> x < 2).count
    

    stream() <- 스트림생성

    filter <- 중간 연산 (스트림 변환) - 연속에서 수행 가능

    count <- 최종 연산 (스트림 사용) - 마지막에 단 한 번만 사용 가능

     

    예제

    IntStream.range(1, 11 )
                .filter(i-> i%2==0)
                .forEach(System.out::println);

     

    익명 클래스

    IntStream.range(1, 11).filter(new IntPredicate() {
                @Override
                public boolean test(int value) {
                    return (value&2) == 0;
                }
            }).forEach(System.out::println);

     

    람다식 과 익명 인터페이스 구현의 차이점?

    람다식(feat. 익명 구현 클래스 vs 람다식) (tistory.com)

    Difference Between Anonymous Inner Class And Lambda Expression In Java 8 | Programmerbay

    오버라이드

    함수형 인터페이스

    @FunctionalInterface
    public interface MyEventConsumer {
        public void consume(Object event);
    }
    

    람다식

    MyEventConsumer consumer = event -> System.out.println(event.toString() + " consumed");
    

    익명 클래스

    MyEventConsumer consumer = new MyEventConsumer() {
    		@Override
        public void consume(Object event){
            System.out.println(event.toString() + " consumed");
        }
    };
    

    익명 인터페이스에서는 람다식으로 표현할 함수를 오버라이드 하기 때문에 재설계가 가능하다.

    재설계

    MyEventConsumer consumer = new MyEventConsumer() {
    		private int eventCount = 0;
    		**@Override**
        public void consume(Object event){
            System.out.println(event.toString() + " consumed " + 
    																				this.eventCount++ + " times.");
        }
    };
    

     

    내부 작동 방식

    익명 클래스

    public class AnonymousClass {
    
        public static void main(String[] args) {
            IntBinaryOperator plus = new IntBinaryOperator() {
                @Override
                public int applyAsInt(int left, int right) {
                    return left + right;
                }
            };
        }
    }
    

    바이트 코드

    public class com/whiteship/white_ship_study/week15/AnonymousClass {
    
      **static INNERCLASS com/whiteship/white_ship_study/week15/AnonymousClass$1 null null**
    
      public static main([Ljava/lang/String;)V
       L0
        LINENUMBER 8 L0
        NEW com/whiteship/white_ship_study/week15/AnonymousClass$1
        DUP
        **INVOKESPECIAL com/whiteship/white_ship_study/week15/AnonymousClass$1.<init> ()V**
        ASTORE 1
       L1
        LINENUMBER 14 L1
        RETURN
       L2
        LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
        LOCALVARIABLE plus Ljava/util/function/IntBinaryOperator; L1 L2 1
        MAXSTACK = 2
        MAXLOCALS = 2
    }
    

    람다

    public class LambdaEx {
    
        private IntBinaryOperator plus() {
            IntBinaryOperator plus = (x, y) -> {
                return x + y;
            };
            return plus;
        }
    }
    

    바이트 코드

    // class version 52.0 (52)
    // access flags 0x21
    public class com/whiteship/white_ship_study/week15/NotAnonymous {
    
      // compiled from: NotAnonymous.java
      // access flags 0x19
      public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup
    
      // access flags 0x2
      private plus()Ljava/util/function/IntBinaryOperator;
       L0
        LINENUMBER 12 L0
        **INVOKEDYNAMIC applyAsInt()Ljava/util/function/IntBinaryOperator;** [
          // handle kind 0x6 : INVOKESTATIC
          java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
          // arguments:
          (II)I, 
          // handle kind 0x6 : INVOKESTATIC
          com/whiteship/white_ship_study/week15/NotAnonymous.lambda$plus$0(II)I, 
          (II)I
        ]
        ASTORE 1
       L1
        LINENUMBER 15 L1
        ALOAD 1
        ARETURN
       L2
        LOCALVARIABLE this Lcom/whiteship/white_ship_study/week15/NotAnonymous; L0 L2 0
        LOCALVARIABLE plus Ljava/util/function/IntBinaryOperator; L1 L2 1
        MAXSTACK = 1
        MAXLOCALS = 2
    
      **// access flags 0x100A
      private static synthetic lambda$plus$0(II)I
        // parameter synthetic  x
        // parameter synthetic  y
       L0
        LINENUMBER 13 L0
        ILOAD 0
        ILOAD 1
        IADD
        IRETURN
       L1
        LOCALVARIABLE x I L0 L1 0
        LOCALVARIABLE y I L0 L1 1
        MAXSTACK = 2
        MAXLOCALS = 2
    }**
    
    

    결론 - 익명 내부 클래스는 새로운 클래스를 생성하지만, 람다는 따로 클래스를 생성하지 않는다. 대신 새로운 메소드를 만든다.

     

    람다의 장단점

    장점

    1. 코드의 간결성 - 람다를 사용하면 불필요한 반복문의 삭제가 가능하며 복잡한 식을 단순하게 표현할 수 있습니다.

    2. 병렬처리 가능 - 멀티쓰레드를 활용하여 병렬처리를 사용 할 수 있습니다. 

    더보기

    병렬 처리코드실행 결과

     

    코드

    IntStream.range(0, 10).**parallel()**.forEach(index -> {
                System.out.println("Starting lambda" + Thread.currentThread().getName() + ",    index=" + index + ", " + new Date());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) { }
            });
    

    실행 결과

    Starting lambdaForkJoinPool.commonPool-worker-1,    index=2, Wed Jul 13 17:46:35 KST 2022
    Starting lambdaForkJoinPool.commonPool-worker-2,    index=8, Wed Jul 13 17:46:35 KST 2022
    Starting lambdaForkJoinPool.commonPool-worker-4,    index=5, Wed Jul 13 17:46:35 KST 2022
    Starting lambdaForkJoinPool.commonPool-worker-6,    index=3, Wed Jul 13 17:46:35 KST 2022
    Starting lambdaForkJoinPool.commonPool-worker-5,    index=4, Wed Jul 13 17:46:35 KST 2022
    Starting lambdaForkJoinPool.commonPool-worker-7,    index=0, Wed Jul 13 17:46:35 KST 2022
    Starting lambdamain,    index=6, Wed Jul 13 17:46:35 KST 2022
    Starting lambdaForkJoinPool.commonPool-worker-3,    index=1, Wed Jul 13 17:46:35 KST 2022
    Starting lambdaForkJoinPool.commonPool-worker-7,    index=7, Wed Jul 13 17:46:36 KST 2022
    Starting lambdaForkJoinPool.commonPool-worker-6,    index=9, Wed Jul 13 17:46:36 KST 2022
    

     

     

    3. 객체화 - 람다 표현식은 함수형 인터페이스의 객체이므로, 람다 표현식을 하나의 변수에 할당한 후 다른 함수에 넘겨줄 수 있다.

    더보기

     

    @FunctionalInterface
    public interface TaskComparator{
    	public boolean compareTasks(int a1, int a2);
    }
    
    
    public class TaskComparatorImpl{
    	public static void main (String[] args){
    		TaskComparator myTaskComparator = (int a1, int a2) -> {return a1 > a2;};
    		boolean result = myTaskCompartor.compareTasks(5,2);
    		System.out.println(result);
    	}
    }

    단점

    1. 불필요하게 너무 사용하게 되면 오히려 가독성을 떨어 뜨릴 수 있습니다.

    Ref: https://jenkov.com/tutorials/java/lambda-expressions.html

    [JAVA] 람다식(Lambda)의 개념 및 사용법 :: 히진쓰의 서버사이드 기술 블로그 (tistory.com)

    [10분 테코톡] 스컬의 람다 - YouTube

    '기술 면접 > Java' 카테고리의 다른 글

    Java에서 Null을 안전하게 사용하기  (0) 2022.08.14
    #1 Garbage Collection  (0) 2022.07.06
Designed by Tistory.