0_ch4n
기계쟁이\n개발자
0_ch4n
0chn.xxx@gmail.com @0ch._.n
전체 방문자
오늘
어제

공지사항

  • All (282)
    • 🖥 CS (21)
      • 네트워크 (12)
      • 운영체제 (3)
      • 자료구조 (2)
      • Web (4)
    • 🧠 Algorithm (185)
      • [C] BOJ (93)
      • [JAVA] Programmers (91)
    • 📚 Study (69)
      • HTML&CSS (19)
      • MySQL (11)
      • JAVA (22)
      • Servlet&JSP (8)
      • Thymeleaf (2)
      • Spring (5)
      • JPA (2)
    • 📖 Book (1)
    • 📃 Certification (6)
      • 정보처리기사 (6)

인기 글

최근 글

최근 댓글

태그

  • CSS
  • 코딩테스트
  • 코테
  • 프로그래머스
  • 카카오
  • Programmers
  • kakao
  • 자바
  • til
  • java

블로그 메뉴

  • 홈
  • 태그
  • 방명록

티스토리

hELLO · Designed By 정상우.
0_ch4n

기계쟁이\n개발자

[JAVA] 람다와 함수형 인터페이스
📚 Study/JAVA

[JAVA] 람다와 함수형 인터페이스

2022. 5. 11. 21:29
반응형

✔️ 람다식(Lambda expression)

  • 메서드를 하나의 식으로 표현한 것으로 익명 클래스, 익명 함수라고도 하며 1급 객체로 취급 받는다
    • 익명 함수(anonymous function) : 메소드의 이름과 반환값이 없고 단 하나의 객체만을 생성할 수 있는 클래스
    • 1급 객체(First-class Object)
      1. 변수나 데이터 구조 안에 담을 수 있다
      2. 파라미터로 전달할 수 있다
      3. 반환값으로 사용할 수 있다
      4. 할당에 사용된 이름과 무관하게 고유한 구별이 가능하다
  • 장점
    • 코드를 간결하게 만들 수 있고 식에 개발자의 의도가 명확히 드러나 가독성이 향상된다
    • 함수를 만드는 과정없이 한 번에 처리할 수 있기에 코딩하는 시간이 줄어든다
    • 병렬 처리, 이벤트 처리 등에 용이하다
  • 단점
    • 람다로 만든 익명 함수는 재사용이 불가능하다
    • 디버깅이 어렵다
    • 함수를 중복 생성 할 수 있으므로 남발하면 코드가 지저분해 질 수 있다
    • 재귀에 부적합하다

 

📌 람다식 작성규칙

  • 공통
    • 이름과 반환타입은 작성하지 않는다
  • Parameter
    • 추론이 가능한 매개변수의 타입은 생략할 수 있다 (두 개 이상인 경우 몇개만 생략하는건 불가능)
    • 선언된 매개변수가 하나인 경우 타입+괄호()를 생략할 수 있다
  • Body {}
    • return문 대신 식(expression)으로 대체할 수 있다 (식의 끝에는 세미콜론(;)을 붙이지 않는다)
    • 괄호{} 안의 문장이 하나인 경우 생략할 수 있다 (return문인 경우엔 같이 생략하거나 괄호{}로 감싸야한다)

 

📌 람다식 작성방법

  • 람다식은 함수형 인터페이스에 선언된 추상 메서드를 익명 클래스로 구현해주는 것이다

 

매개변수가 없고, 리턴값이 없는 람다식

@FunctionalInterface
interface Function {
    void abstractMethod(); //매개변수 X 반환타입 X
}

class Ex {
    public static void main(String[] args) {
        //함수형 인터페이스 선언
        Function func;

        //{} 뒤에 세미콜론 붙여야 함
        func = () -> { System.out.println("매개변수가 없고 리턴값이 없는 람다식"); };
        func.abstractMethod();

        //{}의 실행코드가 1줄이면 생략가능
        func = () -> System.out.println("매개변수가 없고 리턴값이 없는 1줄짜리 람다식");
        func.abstractMethod();
    }
}

 

매개변수가 있고, 리턴값이 없는 람다식

@FunctionalInterface
interface Function {
    void abstractMethod(String str); //매개변수 O 반환타입 X
}

class Ex {
    public static void main(String[] args) {
        //함수형 인터페이스와 매개변수로 쓰일 변수 선언
        Function func;

        //{} 뒤에 세미콜론 붙여야 함
        func = (a) -> { System.out.println(a + "매개변수가 있고 리턴값이 없는 람다식"); };
        func.abstractMethod("[매개변수]");

        //{}의 실행코드가 1줄이면 생략가능
        func = (a) -> System.out.println(a + "매개변수가 있고 리턴값이 없는 1줄짜리 람다식");
        func.abstractMethod("[매개변수]");
    }
}

 

매개변수가 없고, 리턴값이 있는 람다식

@FunctionalInterface
interface Function {
    String abstractMethod(); //매개변수 X 반환타입 O
}

class Ex {
    public static void main(String[] args) {
        //함수형 인터페이스 선언
        Function func;

        //{} 뒤에 세미콜론 붙여야 함
        func = () -> { return "매개변수가 없고 리턴값이 있는 람다식"; };
        System.out.println(func.abstractMethod());

        //{}의 실행코드가 return만 있는 경우 생략가능
        func = () -> "매개변수가 없고 리턴값이 있으면서 return만 있는 람다식";
        System.out.println(func.abstractMethod());
    }
}

 

매개변수가 있고, 리턴값이 있는 람다식

@FunctionalInterface
interface Function {
    String abstractMethod(String str); //매개변수 O 반환타입 O
}

class Ex {
    public static void main(String[] args) {
        //함수형 인터페이스 선언
        Function func;

        //{} 뒤에 세미콜론 붙여야 함
        func = (a) -> { return a + "매개변수가 있고 리턴값이 있는 람다식"; };
        System.out.println(func.abstractMethod("[매개변수]"));

        //{}의 실행코드가 return만 있는 경우 생략가능
        func = (a) -> a + "매개변수가 있고 리턴값이 있으면서 return만 있는 람다식";
        System.out.println(func.abstractMethod("[매개변수]"));
    }
}

 

📌 람다식의 타입과 형변환

  • 람다식의 타입은 익명 객체로 컴파일러가 임의로 이름을 정하기 때문에 알 수 없다
  • 람다식은 오직 함수형 인터페이스 타입으로만 형변환이 가능하며 이는 생략이 가능하다
@FunctionalInterface
interface MyFunction { //함수형 인터페이스 생성
    void MyMethod(); //추상 메서드 생성
}

class Ex {
    public static void main(String[] args) {
        MyFunction f = (MyFunction) () -> {}; //람다식을 MyFunction 타입으로 형변환해서 참조
        MyFunction f2 = () -> {}; //형변환 생략해서 참조
        Object obj = (Object) (MyFunction) () -> {}; //다른 타입으로 형변환 하려면 2번 형변환
    }
}

 

📌 참조변수, 매개변수, 반환값을 통해 람다식 주고 받기

  • 람다식을 주고 받기 위해선 어떻게든 함수형 인터페이스 타입의 참조변수를 통해 추상 메서드를 호출해야 한다
@FunctionalInterface
interface MyFunction { //함수형 인터페이스 생성
    void MyMethod(); //추상 메서드 생성
}

class Ex {
    public static void main(String[] args) {
        //f에 참조된 람다식을 MyFunction의 MyMethod로 구현
        MyFunction f = () -> System.out.println("Hello Function");
        f.MyMethod();

        //람다식을 aMethod에게 MyFunction 타입의 인자로 전달해 MyFunction의 MyMethod로 구현
        aMethod(() -> System.out.println("Hello Method"));

        //람다식을 반환 받아 f2로 참조하고 MyFunction의 MyMethod로 구현
        MyFunction f2 = returnMethod();
        f2.MyMethod();
    }

    static void aMethod(MyFunction f) { //람다식을 전달받을 메서드 생성
        f.MyMethod(); //함수형 인터페이스 타입으로 전달받은 인자의 추상 메서드를 호출
    }

    static MyFunction returnMethod() { //람다식을 반환할 메서드 생성
        return () -> { System.out.println("Hello Return"); };
    }
}

 

📌 람다식으로 외부 변수 참조

  • 람다식 내에서 참조하는 지역변수는 final이 붙지 않아도 상수로 간주하므로 값을 변경할 수 없다
  • 외부 지역변수와 람다식의 매개변수는 중복이 불가능하다
@FunctionalInterface
interface MyFunction { //함수형 인터페이스 생성
    void MyMethod(); //추상 메서드 생성
}

class Outer {
    int val = 10;

    class Inner {
        int val = 20;

        void method(int i) { //파라미터 final로 간주해 변경불가
            int val = 30; //지역변수 final로 간주해 변경불가

            MyFunction f = () -> {
                System.out.println(i);
                System.out.println(val);
                System.out.println(this.val);
                System.out.println(Outer.this.val);
            };

            f.MyMethod();
        }
    }
}

class Ex {
    public static void main(String[] args) {
        Outer outer = new Outer(); //Outer 인스턴스화
        Outer.Inner inner = outer.new Inner(); //Inner 인스턴스화

        inner.method(100);
    }
}

 

📌 람다식으로 메서드 참조

class Calculator {
    static int staticSum(int x, int y) {
        return x + y;
    }

    int instanceSum(int x, int y) {
        return x + y;
    }
}

class Student {
    String name;

    Student(String name) {
        this.name = name;
    }
}

class Ex {
    public static void main(String[] args) {
        IntBinaryOperator operator;

        //static 메서드
        operator = (x, y) -> Calculator.staticSum(x, y); //기존
        System.out.println(operator.applyAsInt(1, 3));

        operator = Calculator::staticSum; //메서드 참조
        System.out.println(operator.applyAsInt(2, 5));

        //instance 메서드
        Calculator cal = new Calculator();

        operator = (x, y) -> cal.instanceSum(x, y); //기존
        System.out.println(operator.applyAsInt(4, 3));

        operator = cal::instanceSum; //메서드 참조
        System.out.println(operator.applyAsInt(3, 7));

        //생성자
        Function<String, Student> f = (name) -> new Student(name); //기존
        System.out.println(f.apply("이름").name);

        Function<String, Student> f1 = Student::new; //메서드 참조
        System.out.println(f1.apply("이름").name);

        //매개변수의 메서드 참조
        ToIntBiFunction<String, String> bf = String::compareToIgnoreCase;
        System.out.println(bf.applyAsInt("test", "TEST") == 0 ? "true" : "false");
    }
}

 

✔️ 함수형 인터페이스(Functional Interface)

  • jdk 8부터 인터페이스의 변경점
    • ~ jdk 7 : 추상 메서드, 상수만 정의 가능
    • jdk 8 ~ : 추상 메서드, 상수 + default 메서드, static 메서드 정의 가능
  • jdk 8부터 추가된 람다식은 추상 메서드가 오직 하나인 함수형 인터페이스를 사용한다 (default나 static 상관 X)
  • @FunctionalInterface 어노테이션은 선택사항으로 추상 메서드가 1개인지 컴파일러가 확인해준다

 

📌 java.util.function 패키지

  • 자주 쓰이는 형식의 메서드를 함수형 인터페이스로 미리 정의해 놓은 패키지이다
  • 이 패키지를 활용하면 메서드 이름이 통일되고 재사용성이나 유지보수 측면에서 용이해진다

 

주요 함수형 인터페이스

  • Runnable
@FunctionalInterface
interface Runnable {
    void run();
}

class Ex {
    public static void main(String[] args) {
        Runnable r = () -> System.out.println("run");
        r.run(); //run 출력
    }
}
  • Supplier<T>
@FunctionalInterface
interface Supplier<T> {
    T get();
}

class Ex {
    public static void main(String[] args) {
        Supplier<String> s = () -> "Supplier";
        System.out.println(s.get()); //Supplier 출력
    }
}
  • Consumer<T>
@FunctionalInterface
interface Consumer<T> {
    void accept(T t);
}

class Ex {
    public static void main(String[] args) {
        Consumer<String> c = (str) -> System.out.println(str);
        c.accept("Consumer"); //Consumer 출력
    }
}
  • Function<T, R>
@FunctionalInterface
interface Function<T, R> {
    R apply(T t);
}

class Ex {
    public static void main(String[] args) {
        Function<Integer, String> f = i -> "입력 받은 숫자 : " + i;
        System.out.println(f.apply(3)); //입력 받은 숫자 : 3 출력
    }
}
  • Predicate<T>
@FunctionalInterface
interface Predicate<T> {
    boolean test(T t);
}

class Ex {
    public static void main(String[] args) {
        Predicate<String> p = (str) -> str == "test";
        System.out.println(p.test("test")); //true 출력
    }
}

 

매개변수가 두 개인 함수형 인터페이스

  • BiConsumer<T, U>
@FunctionalInterface
interface BiConsumer<T, U> {
    void accept(T t, U u);
}

class Ex {
    public static void main(String[] args) {
        BiConsumer<String, Integer> bc = (String str, Integer itg) -> 
                System.out.println("String : " + str + ", Integer : " + itg);
        bc.accept("테스트", 3); //String : 테스트, Integer : 3 출력
    }
}
  • BiPredicate<T, U>
@FunctionalInterface
interface BiPredicate<T, U> {
    boolean test(T t, U u);
}

class Ex {
    public static void main(String[] args) {
        BiPredicate<Integer, Integer> bp = (Integer itg, Integer itg2) -> itg == itg2;
        System.out.println(bp.test(1, 1)); //true 출력
    }
}
  • BiFunction<T, U, R>
@FunctionalInterface
interface BiFunction<T, U, R> {
    R apply(T t, U u);
}

class Ex {
    public static void main(String[] args) {
        BiFunction<Integer, Integer, String> bf = (Integer itg, Integer itg2) ->
                itg + "+" + itg2 + "=" + (itg+itg2);
        System.out.println(bf.apply(3, 5)); //3+5=8 출력
    }
}

 

UnaryOperator와 BinaryOperator

  • Function의 자손들로 매개변수의 타입과 반환타입이 일치하는 점을 제외하곤 Function과 같다

 

컬렉션 프레임워크와 함수형 인터페이스

  • 컬렉션 프레임워크 인터페이스에 추가된 디폴트 메서드 중 함수형 인터페이스를 사용하는 메서드의 목록이다

 

기본형 특화 함수형 인터페이스

  • 함수형 인터페이스는 제네릭 타입을 사용하기 때문에 오토박싱을 통해 래퍼 클래스를 이용한다
  • 하지만 기본형을 사용하는게 훨씬 효율적이므로 이 인터페이스를 통해 박싱의 한 과정을 덜어준다

 

함수형 인터페이스의 합성

//f.andthen(g) : f -> g
default <V> Function<T,V>	andThen(Function<? super R,? extends V> after)
//f.compose(g) : g -> f
default <V> Function<V,R>	compose(Function<? super V,? extends T> before)
//f.identity() : f -> f
static <T> Function<T,T>	identity() //항등 함수

 

함수형 인터페이스의 논리연산

//And
default Predicate<T>	and(Predicate<? super T> other)
//Or
default Predicate<T>	or(Predicate<? super T> other)
//Predicate<T> notP = p.negate(); : !p
default Predicate<T>	negate()
//boolean result = Predicate.isEqual(str1).test(str2); : str == str
static <T> Predicate<T>	isEqual(Object targetRef)

 

📄 참고

자바의 정석 3rd Edition

반응형
저작자표시 (새창열림)

'📚 Study > JAVA' 카테고리의 다른 글

[JAVA] 입출력과 스트림  (0) 2022.05.13
[JAVA] 스트림(Stream)  (0) 2022.05.12
[JAVA] 스레드  (0) 2022.05.11
[JAVA] 어노테이션(Annotation)  (0) 2022.05.09
[JAVA] 열거형(enums)  (0) 2022.05.09
    0_ch4n
    0_ch4n
    while(true) { study(); }

    티스토리툴바