[인프런 워밍업 클럽 0기] BE 3일차 과제

김영훈·2024년 2월 21일
0
post-thumbnail

진도표 3일차와 연결됩니다

우리는 JdbcTemplate을 사용하는 과정에서 익명 클래스람다식이라는 자바 문법을 사용했습니다.
익명 클래스는 자바의 초창기부터 있던 기능이고, 람다식은 자바 8에서 등장한 기능입니다.
다음 키워드를 사용해 몇 가지 블로그 글을 찾아보세요! 아래 질문을 생각하며 공부해보면 좋습니다! 😊


[키워드]
익명 클래스 / 람다 / 함수형 프로그래밍 / @FunctionalInterface / 스트림 API / 메소드 레퍼런스


[질문]

  • 자바의 람다식은 왜 등장했을까?
  • 람다식과 익명 클래스는 어떤 관계가 있을까? - 람다식의 문법은 어떻게 될까?


1. 함수형 프로그래밍과 람다 표현식


1-1. 함수형 프로그래밍이란?

Functional Programming is programming without assignment satements
함수형 프로그래밍은 대입문이 없는 프로그래밍이다.
- Rober C.Martin -

  • 함수형 프로그래밍이란 함수를 일급 객체로 취급하고 이러한 함수들 간의 조합으로 프로그램을 구성하는 프로그래밍 방법론을 의미한다.

  • 아래서 다룰 람다식은 이러한 함수형 프로그래밍을 지원하기 위한 문법 중 하나로, 함수를 간결하고 효율적으로 표현할 수 있는 문법적 표현을 말한다. 자바에서는 메서드를 간결하고 효율적으로 표시하기 위한 문법이 된다.



1-2. 일급 객체?

일급 객체는 프로그래밍 언어론의 개념으로서,
우리가 많이 사용하는 언어 중 Javascript, Python이 일급 객체 언어이고, c, c++이 아니라고 볼 수 있다.
(Java는 람다로 지원)

일급 객체란 무슨 특별한 혜택을 받는다는 뜻이 아니라, 사용할 때 다른 요소들과 아무런 차별이 없다는 것을 뜻한다.

보통 일급 객체는 아래 3가지 조건을 충족한 객체를 일컫는다.

  1. 모든 일급 객체는 변수나 데이터에 담을 수 있어야 한다.

  • 자바의 메소드는 변수에 할당 할 수 없다.
public class Main {

    public static void hello(){
        System.out.println("print Hello!");
    }

    public static void main(String[] args) {
        Object sayHello = hello();//에러발생
    }
}
  • 반면 자바스크립트는 가능하다.
const printHello = function hello(){
        console.log("Hello runners!")
    }
    console.log(printHello)

  1. 모든 일급 객체는 함수의 파라미터로 전달 할 수 있어야 한다.

  • 자바의 메서드는 메서드를 인자로 전달할 수 없다.
public class Main2 {

    public static void hello(){
        System.out.println("print Hello!");
    }
    public static void print(Object My_function){
        My_function();
    }

    public static void main(String[] args) {
        print((Object) hello()); //print 메서드에 hello 메서드를 전달할 수 없다.
    }
}
  • 반면 자바스크립트는 자유롭게 가능하다.
   const hello = function (){
        console.log("print Hello!")
    }
    function print (My_function){
        My_function();
    }

    print(hello);
    //print Hello!
  1. 모든 일급 객체는 함수의 리턴값으로 사용 할 수 있어야 한다.

  • 자바의 메서드는 리턴값으로 메서드를 반환할 수 없다.

  • 반면 자바스크립트는 가능하다.

 const hello = function(){
        console.log("warmingUp Club")
        return function(){
            console.log("Hello runners!")
        }
    }
    const printHello = hello();
    printHello();

이러한 관점에서 자바의 람다식 혹은 익명 클래스는 변수나 매개변수에 사용할 수 있고, 리턴값으로도 사용할 수 있기에 일급 객체의 요건을 충족한다.


1-3. 람다식 이란?

  • 람다식은 위에서 언급한 함수형 프로그래밍을 지원하기 위한 문법중 하나로, 메서드를 하나의 간결한 형태의 함수식으로 표현하는 선언적 프로그래밍의 방법이다.
    원래의 자바는 익명 클래스를 이용하여 익명 구현 객체를 사용할 수 있었다.

    자바 8에서 람다식을 도입하면서 기존 익명클래스로 처리했던 부분을 간소화하고 가독성 있게 처리할 수 있다.
  1. 모든 일급 객체는 변수나 데이터에 담을 수 있어야 한다.

public class Lamda {
    public static void main(String[] args) {
        Consumer<String> printMessage = (AnyString) -> System.out.println(AnyString);
        printMessage.accept("Hello WarmingUp Club!");
    }
}
⚠️ Consumer는 뭘까?
자바가 제공하는 대표적인 FunctionalInterface중에 하나로, 자세한 내용은 아래에서 다룰 예정이다.
  1. 모든 일급 객체는 함수의 파라미터로 전달 할 수 있어야 한다.

public class Lamda2 {

    public static void print(Consumer<String> anyMessage, String str){
        anyMessage.accept(str);
    }
    
    public static void main(String[] args) {
        print((inflearn) -> System.out.println(inflearn), "Hello, inflearn");
    }
}
  1. 모든 일급 객체는 함수의 리턴값으로 사용 할 수 있어야 한다.

public class Lamda3 {

    public static Consumer<String> printAnyMessage(){
         return System.out::println; //메서드 레퍼런스
    }

    public static void main(String[] args) {
        Consumer<String> MyMessage = printAnyMessage();
        MyMessage.accept("Hello! runners!");
    }

}

메서드 레퍼런스란?

메서드 레퍼런스는 실행하려는 람다식의 메서드를 참조해서 파라미터의 정보, 반환 타입을 추론한 뒤
람다식에서 선언이 불필요한 부분을 제거하고 람다식을 더 간결하게 표현해 주는 문법이다.

public class Lamda3 {
    public static Consumer<String> printAnyMessage(){
        return message -> System.out.println(message);
    }
    public static void main(String[] args) {
        Consumer<String> MyMessage = printAnyMessage();
        MyMessage.accept("Hello! runners!");
    }
}

기존의 return message -> System.out.println(message) 부분을 보면
반환값 없이 전달받은 message 인자를 그대로 받아서 System클래스의 출력 메서드를 호출하고 있다.
이를 메서드 레퍼런스를 이용하여 정리하면 이렇게된다.

public class Lamda3 {
    public static Consumer<String> printAnyMessage(){
        return System.out::println;
    }
    public static void main(String[] args) {
        Consumer<String> MyMessage = printAnyMessage();
        MyMessage.accept("Hello! runners!");
    }
}

1-4. 익명객체(익명클래스)란?

  • 익명이란 말 그대로 이름이 없는 클래스이며, 프로그램에서 일회용으로 사용되고 버려지는 객체이다.
    만약 어떤 메서드에서 부모 클래스의 자원을 상속받아 재정의하여 사용할 자식 클래스가
    한번만 사용되고 버려진다면, 굳이 상단에 클래스를 정의하기보다는, 지역 변수처럼 익명 클래스로
    정의하고 스택이 끝나면 삭제되도록 하는 것이 유지보수나 프로그램 메모리면에서 이점을 얻을 수 있다.
class WarmingUp{
    public String study(){
        return "공부를 열심히 한다.";
    }
}

public class AnonymousClass1 {
    public static void main(String[] args) {
        WarmingUp runners = new WarmingUp(){
        @Override 
        public String study(){
            return "익명 클래스에 대해 공부한다.";
        } // 이미 정의되어 있는 클래스를 일회성으로 재정의하여 사용
        };
        System.out.println(runners.study()); //익명 클래스 사용
    }
}

즉, 익명 클래스는 재사용할 필요가 없는 일회성 클래스를 굳이 정의하고 생성하는 것이 비효율적이기 때문에, 익명 클래스를 통해 코드를 줄이는 일종의 기법이라고 말 할 수 있다.

  • 인터페이스 익명 구현 객체
    위에서 익명 클래스는 일회성 오버라이딩용 이라고 학습한 바 있는데,
    이러한 특징이 추상화 구조인 인터페이스와 잘 맞물려 익명 구현 객체로 선언해서 사용하면 매우 시너지가 좋다.

interface Runners {
    public String study();
    public String homework();
}

public class AnonymousClass2 {
    public static void main(String[] args) {
        Runners student = new Runners() {
            @Override
            public String study() {
                return "인터페이스 익명 구현 객체를 공부해요";
            }

            @Override
            public String homework() {
                return "3일차 과제를 해요";
            }
        };
        System.out.println(student.study()); //인터페이스 익명 구현 객체를 공부해요
        System.out.println(student.homework()); // 3일차 과제를 해요
    }
}

익명 클래스 기법은 길고 복잡한 자바 문법을 간결하게 하는 것에 초점을 둔다.
그래서 람다식과 매우 잘 어울리며 람다식을 이용하면 익명 구현 객체의 코드를 획기적으로 줄일 수 있다.

interface Calculator{ 
    int calculator(int a, int b);
}
public class AnonymousLamda {
    public static void main(String[] args) {
    
      Calculator anonymous = new Calculator() { // 익명클래스만 사용
          @Override
          public int calculator(int a, int b) {
              return a + b;
          }
      };
      //-> 람다를 사용하여 줄이기 
      Calculator lamda = (a,b) -> {
          return a + b;
      };
      //-> 더 줄이기
      Calculator shortlamda = (a,b) -> a + b;
      //-> + 메서드 레퍼런스
      Calculator methodReference = Integer::sum;
  }
}

2. @FunctionalInterface



2-1. 함수형 인터페이스란?

  • 함수를 1급 객체처럼 다룰 수 있게 해주는 어노테이션
  • 인터페이스에 선언하여 단 하나의 추상 메소드만을 갖도록 제한하는 역할
  • 람다식으로 순수 함수를 선언할 수 있게 되었지만, Java는 기본적으로 객체지향 언어이기 때문에
    순수 함수와 일반 함수를 다르게 취급하고 있으며, 이를 구분하기 위해 함수형 인터페이스가 등장하게 되었다.
    함수형 인터페이스를 사용하는 이유는 Java의 람다식이 함수형 인터페이스를 반환하기 때문이다.

2-2. 함수형 인터페이스를 구현하는 방법

1개의 추상 메서드를 선언하고, 위에 @FunctionalInterface 어노테이션을 붙여주면된다.

@FunctionalInterface
interface MyFunctionalInterface{
    int sum(int a, int b);
}
public class functionalInterface1 {
    public static void main(String[] args) {
        MyFunctionalInterface Myfunction = (a, b) -> a + b;
        System.out.println(Myfunction.sum(15,17));
        // 
    }
}

⚠️ 주의

  • 람다식으로 생성된 순수 함수는 함수형 인터페이스로만 선언이 가능하다.
  • @FunctionalInterface가 해당 인터페이스가 1개의 추상 메서드만을 갖도록 제한하기 때문에
    (default 메서드와 static 메서드 제외)
    여러 개의 추상 메서드를 선언하면 컴파일 에러가 발생한다.

2-3. 함수형 인터페이스의 종류

java.util.function 에는 각 Supplier, Consumer, Predicate, Function 기본 함수형 인터페이스에 대해 파생적인 인터페이스가 40여개가 있다.

인터페이스명추상 메소드설명
Runnablevoid run()기본적인 인터페이스, 매개변수와 반환값 없음
Supplier<T>T get()매개변수 없음, 제네릭타입 반환값 가짐
Consumer<T>void accept()제네릭 매개변수 하나, 반환값 없음(void)
Predicate<T>boolean test()제네릭 매개변수 하나, Boolean 반환값 하나
Function<T,R>R apply(T t)제네릭 매개변수 하나와 다른 제네릭 반환값하나
Comparator<T>int compare(T o1, T o2)같은 제네릭 타입 매개변수 두개를 받고,Integer 반환값 하나 가짐, 객체간 비교를 위핸 compare를 위한 인터페이스
BiConsumer<T,U>void accept(T t, U u)서로다른 제네릭 매개변수 두개를 받고 반환값 없음
BiFunction<T,U,R>R apply(T t, U u)서로 다른 제네릭 매개변수 두개를 받고 다른 제네릭 타입의 반환값 하나
BiPridicate<T,U>boolean test(T t, U u)서로 다른 제네릭 타입의 매개변수 2개를 받고Boolean 타입의 반환값 하나 가짐

요약

  • 자바의 람다식은 왜 등장했을까?

    자바의 람다식은 기존 익명 클래스로 함수형 인터페이스를 구현하려면 코드가 길어지고 가동성이 떨어지는 문제가 있었기에 익명 클래스로 구현하던 함수형 프로그래밍을 지원하기 위해 등장하였다.

  • 람다식과 익명 클래스는 어떤 관계가 있을까?

    익명 클래스를 사용하여 함수형 인터페이스를 구현할 때, 람다식을 통해 훨씬 간단하고 직관적으로 작성할 수 있다.

Reference

0개의 댓글