ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java 자바] 14. 람다식
    Programming/Java 2022. 9. 14. 00:26
    반응형

    14-1. 람다식이란?

    - 익명 함수를 생성하기 위한 식으로 객체 지향 언어보다는 함수 지향 언어에 가까움

    - "(매개변수) -> {실행코드} " 형태로 작성

    // Runnable 인터페이스의 익명 구현 객체를 생성하는 코드
    Runnable runnable = new Runnable() {
      public void run() {...}
    }
    
    // 람다식을 이용해 표현할 경우
    Runnable runnable = () -> {...};

    14-2. 람다식 기본 문법

    // 기본 형태
    (타입 매개변수, ...)  -> { 실행문; ... }
    
    // int 매개 변수 a 값을 콘솔에 출력할 때
    (int a) -> {System.out.println(a);}
    
    // 매개 변수 타입은 런타임 시 대입값에 따라 자동으로 인식될 수 있어,
    // 람다식에서는 일반적으로 매개변수의 타입을 언급하지 않음
    (a) -> {System.out.println(a);}
    
    // 하나의 매개변수만 있다면 괄호() 생략 가능
    // 하나의 실행문만 있다면 중괄호{} 생략 가능
    a -> System.out.println(a);
    
    // 매개변수가 없을 경우 빈 괄호 사용
    () -> {실행문, ...}
    
    // 중괄호를 실행하고 결과값을 리턴해야 할 경우
    (x, y) -> { return x + y; }
    
    // 중괄호에 return문만 있을 경우, return과 중괄호 생략 가능
    (x, y) -> x + y

    14-3. 타겟 타입과 함수적 인터페이스

    - 람다식은 단순히 메소드를 선언하는 것이 아닌, 메소드를 가지고 있는 객체를 생성

    - 람다식은 인터페이스 변수에 대입되어, 인터페이스의 익명 구현 객체를 생성함

    - 람다식의 타겟 타입: 람다식이 대입될 인터페이스

    인터페이스 변수 = 람다식;

    14-3-1. 함수적 인터페이스 (@FunctionalInterface)

    - 람다식은 하나의 메소드를 정의하기 때문에 두 개 이상의 추상 메소드가 선언된 인터페이스는 람다식을 이용할 수 없음
    => 하나의 추상 메소드가 선언된 인터페이스만이 람다식의 타겟 타입이 될 수 있으며, 이러한 인터페이스를 함수적 인터페이스라 함

    - @FunctionalInterface 어노테이션을 통해 두 개 이상의 추상 메소드가 선언되지 않도록 컴파일러가 체킹해줌

    @FunctionalInterface
    public interface MyFunctionalInterface {
      public void method();
      public void otherMethod();	// 컴파일 오류
    }

    14-3-2. 매개 변수와 리턴값이 없는 람다식

    // MyFunctionalInterface.java
    @FunctionalInterface
    public interface MyFunctionalInterface {
      public void method();
    }
    
    // MyFunctionalInterfaceTest.java
    public class MyFunctionalInterfaceTest {
      public static void main(String[] args) {
        MyFunctionalInterface fi;
        
        // method()의 매개변수가 없으므로 람다식의 매개변수도 없음
        fi = () -> {
          String str = "method call1";
          System.out.println(str);
        };
        fi.method();
        
        fi = () -> { System.out.println("method call2"); };
        fi.method();
        
        fi = () -> System.out.println("method call3");
        fi.method();
      }
    }

    14-3-3. 매개 변수가 있고 리턴값이 없는 람다식

    // MyFunctionalInterface.java
    @FunctionalInterface
    public interface MyFunctionalInterface {
      public void method(int x);
    }
    
    // MyFunctionalInterfaceTest.java
    public class MyFunctionalInterfaceTest {
      public static void main(String[] args) {
        MyFunctionalInterface fi;
        
        fi = (x) -> {
          int result = x * 5;
          System.out.println(result);
        };
        fi.method(2);
        
        fi = (x) -> {System.out.println(x*5);};
        fi.method(2);
        
        fi = x -> System.out.println(x*5);
        fi.method(2);
      }
    }

    14-3-4. 매개 변수가 있고 리턴값이 있는 람다식

    // MyFunctionalInterface.java
    @FunctionalInterface
    public interface MyFunctionalInterface {
      public int method(int x, int y);
    }
    
    // MyFunctionalInterfaceTest.java
    public class MyFunctionalInterfaceTest {
      public static void main(String[] args) {
        MyFunctionalInterface fi;
        
        fi = (x, y) -> {
          int result = x + y;
          return result;
        };
        System.out.println(fi.method(2, 5));
        
        fi = (x, y) -> { return x + y; };
        System.out.println(fi.method(2, 5));
        
        fi = (x, y) -> x + y;
        System.out.println(fi.method(2, 5));
        
        fi = (x, y) -> sum(x, y);
        System.out.println(fi.method(2, 5));
      }
      
      public static int sum (int x, int y) {
        return (x + y);
      }
    }

    14-4. 클래스 멤버와 로컬 변수 사용

    14-4-1. 클래스 멤버 사용

    - 람다식 실행 블록에서는 클래스의 멤버인 필드와 메소드를 제약 사항 없이 사용 가능
    - 단, this 키워드를 사용할 경우 익명 객체의 참조가 아닌 람다식을 실행한 객체의 참조이므로 주의

    // MyFunctionalInterface.java
    public interface MyFunctionalInterface {
      public void method();
    }
    
    // UsingThis.java
    public class UsingThis {
      public int outterField = 10;
      
      class Inner {
        int innerField = 20;
        
        void method() {
          // 람다식
          MyFunctionalInterface fi = () -> {
            System.out.println("outterField: " + outterField);
            // 바깥 객체의 참조를 얻기 위해서는 클래스명.this 사용
            System.out.println("outterField: " + UsingThis.this.outterField + "\n");
            
            System.out.println("innerField: " + innerField);
            System.out.println("innerField: " + this.innerField + "\n");  
          };
          fi.method();
        }
      }
    }
    
    // UsingThisTest.java
    public class UsingThisTest {
      public static void main(String[] args) {
        UsingThis usingThis = new UsingThis();
        UsingThis.Inner inner = usingThis.new Inner();
        inner.method();
      }
    }

    14-4-2. 로컬 변수 사용

    - 람다식은 메소드 내부에서 주로 작성되기에 로컬 익명 구현 객체를 생성시킨다고 할 수 있음
    - 메소드의 매개변수 또는 로컬 변수를 사용하려면 이 두 변수는 final 특성을 가져야 함

    // MyFunctionalInterface.java
    public interface MyFunctionalInterFace {
      public void method();
    }
    
    // UsingLocalVariable.java
    public class UsingLocalVariable {
      void method(int arg) {	// arg는 final특성을 가짐
        int localVar = 40;		// localVar은 final 특성을 가짐
        
        // arg = 31;			// final 특징 때문에 수정 불가
        // localVar = 41;		// final 특징 때문에 수정 불가
        
        // 람다식
        MyFunctionalInterface fi = () -> {
          System.out.println("arg: " + arg);
          System.out.println("localVar: " + localVar + "\n");
        };
        fi.method();
      }
    }
    
    //UsingLocalVariableTest.java
    public class UsingLocalVariableTest {
      public static void main(String[] args) {
        UsingLocalVariable ulv = new UsingLocalVariable();
        ulv.method(20);
      }
    }

    14-5. 표준 API의 함수적 인터페이스

    - 자바 표준 API에서 한 개의 추상 메소드를 가지는 인터페이스는 모두 람다식을 이용해 익명 객체 구현 가능 

    14-5-1. Consumer 함수적 인터페이스

    - 리턴값이 없는 accept() 메소드를 가지고 있음
    - accept() 메소드는 매개값을 받아 소비하는 역할만 함 (사용만 할 뿐 리턴값이 없다)

    - 매개 변수와 타입에 따라 여러 Consumer 인터페이스가 존재함

    인터페이스명 추상 메소드 설명
    Consumer<T> void accept(T t) 객체 T를 받아 소비
    BiConsumer<T> void accept(T t, U u) 객체 T와 U를 받아 소비
    DoubleConsumer void accept(double value) double 값을 받아 소비
    IntConsumer void accept(int value) int 값을 받아 소비
    LongConsumer void accept(long value) long 값을 받아 소비
    ObjDoubleConsumer<T> void accept(T t, double value) 객체 T와 double 값을 받아 소비
    ObjIntConsumer<T> void accept(T t, int value) 객체 T와 int 값을 받아 소비
    ObjLongConsumer<T> void accept(T t, long value) 객체 T와 long 값을 받아 소비
    // ConsumerExample.java
    
    public class ConsumerExample {
      public static void main(String[] args) {
        Consumer<String> consumer = t -> System.out.println(t + "8");
        consumer.accept("Java");
      }
    }

    14-5-2. Supplier 함수적 인터페이스

    - 매개 변수가 없고 리턴값이 있는 getXXX() 메소드를 가지고 있음
    - 메소드는 실행 후 호출한 곳으로 데이터를 리턴(공급)하는 역할을 함
    - get() 메소드가 매개값을 가지지 않으므로 람다식도 ()를 사용

    인터페이스명 추상 메소드 설명
    Supplier<T> T get() T 객체를 리턴
    BooleanSupplier boolean getAsBoolean() boolean 값을 리턴
    DoubleSupplier double getAsDouble() doulbe 값을 리턴
    IntSupplier int getAsInt() int 값을 리턴
    LongSupplier long getAsLong() long 값을 리턴
    // SupplierTest.java
    
    public class SupplierTest {
      public static void main(String[] args) {
        IntSupplier intSupplier = () -> {
          int num = (int) (Math.random() * 6) + 1;
          return num;
        };
        
        int num = intSupplier.getAsInt();
        System.out.println("눈의 수: " + num);
      }
    }

    14-5-3. Function 함수적 인터페이스

    - 매개값과 리턴값이 있는 applyXXX() 메소드를 가지고 있음
    - 매개값을 리턴값으로 매핑(타입 변환) 하는 역할을 함

    // Student.java
    public class Student {
      private String name;
      private int englishScore;
      private int mathScore;
      
      public Student(String name, int englishScore, int mathScore) {
        this.name = name;
        this.englishScore = englishScore;
        this.mathScore = mathScore;
      }
      
      public String getName() {return name;}
      public int getEnglishScore() {return englishScore;}
      public int getMathScore() {return mathScore;}
    }
    
    // FunctionTest.java
    
    public class FunctionTest {
      private static List<Student> list = Arrays.asList(
        new Student("a", 90, 96),
        new Student("b", 95, 93)
      );
      
      public static void printString(Function<Student, String> function) {
        for(Student student: list {
          System.out.print(function.apply(student) + " ");
        }
        System.out.println();
      }
      
      public static void printInt (ToIntFunction<Student> function) {
        for(Student student : list {
          System.out.print(function.applyAsInt(student) + " ");
        }
        System.out.println();
      }
      
      public static void main(String[] args) {
        System.out.println("[학생 이름]");
        printString(t -> t.getName());
        
        System.out.println("[영어 점수]");
        printInt(t -> t.getEnglishScore());
        
        System.out.println("[수학 점수]");
        printInt(t -> t.getMathScore());
      }
    }

    14-5-4. Operator 함수적 인터페이스

    - Function과 동일하게 매개 변수와 리턴값이 있는 applyXXX() 메소드를 가지고 있음

    - 하지만 매개값을 이용해 연산을 수행한 후 동일한 타입으로 리턴값을 제공한다는 점에서 차이

    // OperatorTest.java
    
    public class OperatorTest {
      private static int[] scores = {92, 95, 87};
      
      public static int maxOrMin (IntBinaryOperator operator) {
        int result = scores[0];
        for(int score = scores) {
          result = operator.applyAsInt(result, score);
        }
        return result;
      }
      
      public static void main(String[] args) {
        int max = maxOrMin(
          (a, b) -> {
            if (a >= b) return a;
            else return b;
          }
        );
        
        int min = maxOrMin(
          (a, b) -> {
            if (a <= b) return a;
            else return b;
          }
        );
        System.out.println("최소값 :" + min);
      }
    }
    반응형

    댓글

Designed by Tistory.