김미썸코딩

익명 클래스와 람다식의 차이 본문

공부/Java

익명 클래스와 람다식의 차이

김미썸 2022. 2. 14. 19:45
728x90

* 익명 클래스

추상클래스를 상속하거나 인터페이스를 구현한, 이름없는 클래스

재사용이 필요없는 인스턴스를 생성할 때 사용한다. 

코드가 너무 길어 함수형 프로그래밍 방식에 적합하지 않다.

 

* 람다식

익명 메소드만 전달하여, 인터페이스를 구현한 익명 클래스의 인스턴스를 생성하는 방법

메소드만 전달하지만, 결과적으로 익명 구현 객체를 만들게 된다. 

 

'모던자바인액션'에서 이 익명 클래스를 람다식을 이용해서 간단하게 바꾸는 것을 많이 봤다. 

 

 

 

함수형 프로그래밍

함수형 프로그래밍은 프로그래밍의 패러다임이다. 마치 절차지향 프로그래밍, 객체지향 프로그래밍처럼.

함수형 프로그래밍은 자료 처리를 수학적 함수의 계산으로 취급하고 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임의 하나이다.
- 위키백과

 

함수형 프로그래밍은 선언적 프로그래밍이다. 이와 대조적으로 람다를 지원하기 전의 자바는 완전한 명령형 프로그래밍이었다.

  • 명령형 프로그래밍 : 클래스에서 메서드를 정의하고, 필요할 때 그 메서드를 호출하는 명령하여 동작.
  • 선언적 프로그래밍 : 데이터가 입력으로 주어지고, 데이터가 처리되는 과정(흐름)을 정의하는 것으로 동작.

함수형 프로그래밍을 간단히 알아보자.

함수형 프로그래밍의 조건에는 다음과 같은 것이 있다.

  • 순수 함수.
  • 고차 함수.
  • 익명 함수.

함수형 프로그래밍의 특징에는 다음과 같은 것이 있다.

  • 불변성
  • 참조 투명성
  • 일급 함수 (일급 객체)
  • 게으른 평가

 

함수형 인터페이스

함수형 프로그래밍과 람다를 알아보았다.

함수형 프로그래밍의 람다는 일급 함수로 매개변수, 리턴값, 자료구조의 일부가 될 수 있다 하였다.

 

그럼 이 때, 람다의 타입은 무엇일까 ?

다시말해, 람다라는 함수객체는 어떤 타입의 구현체일까 ?

람다가 있어야 할 곳이 어딘가 ?

 

이것에 대한 대답이 함수형 인터페이스이다.

 

함수형 인터페이스는, 제네릭 <T> 를 타입 파라미터로 가진다.

인터페이스에 정의된 하나의 추상 메서드는, 이후 함수형 인터페이스의 구현체인 람다함수를 실행시킬 메서드이다.

 

함수형 인터페이스의 특징

  • 추상 메서드가 단 하나여야 한다.
    • 추상 메서드가 하나여야, 람다식에서 정보를 추론할 수 있다.
    • 추상 메서드는 하나지만, 도움이되는 static 메서드나, default 메서드들은 가질 수 있다.
  • @FunctionalInterface 로 검증할 수 있다.
    • 에너테이션 챕터에서 살펴보았었따.

 

 

함수형 인터페이스의 종류

java.util.function 에는 자바의 빌트인 함수형 인터페이스가 40여개가 있다.

우리는 앵간하면 제공하는 함수형 인터페이스를 이용하여 프로그래밍할 수 있으며, 필요한 경우 구현해서 쓸 수 있다.

인터페이스명 추상 메소드 설명
Runnable void 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 타입의 반환값 하나 가짐

 

사실 인터페이스의 추상메서드가 하나이고, 반환타입과 파라미터만 맞다면, 어떤 함수형 인터페이스를 쓰든 람다식을 수용할 수 있다. 이와 같은 설계는, 만약 자바에서 새로운 라이브러리와 기능을 제공했을때, 기존 기능을 제약없이 사용가능한 상위호환성을 제공한다.
java.util.function 에는 각 Supplier, Consumer, Predicate, Function 기본 함수형 인터페이스에 대해 파생적인 인터페이스들로 구성되어 있다.

예를 들면, IntConsumer 는 원시타입인 int 를 받도록 되어 있는데, 이를 사용하면 불필요한 박싱으로 인한 객체생성을 하지 않아서 훨씬 효율적이다.

 

 

함수형 인터페이스들의 디폴트, 정적 메서드

default 메서드, static 메서드는 추상메서드가 아니므로 추상메서드가 하나여야 한다는 함수형 인터페이스의 제약과는 상관없이 만들 수 있다. 

한마디로 default, static 메서드가 있어도 추상메서드만 하나면 함수형 인터페이스란 것이다.

 

 

1. Consumer, Function, Operator 종류 인터페이스의 디폴트메서드

  • andThen()
인터페이스AB = 인터페이스A.andThen(인터페이스B);
최종결과 = 인터페이스AB.method();

인터페이스 A 를 실행후, 반환값을 인터페이스 B의 매개값으로 준다.

Consumer 계열, Operator 계열에 있다.

Consumer 계열은 반환값이 없기 때문에, 단순히 순서를 정해주는 역할을 하게 된다.

  • compose()
인터페이스AB = 인터페이스A.compose(인터페이스B);
최종결과 = 인터페이스AB.method();

위와 반대다. 인터페이스 B 를 먼저 실행 후 반환값을 인터페이스 A의 매개값으로 준다.

Function, Operator 를 뺀 Operator 계열에 있다.

 

 

2. Predicate 계열의 default 메서드

  • and()
predicateAB = predicateA.and(predicateB);
boolean result = predicateAB.method();

&& 와 대응된다.

결과값 둘을 and 연산한 결과를 반환하는 인터페이스를 만든다.

  • or()
predicateAB = predicateA.or(predicateB);
boolean result = predicateAB.method();

|| 와 대응된다.

결과값 둘을 or 연산한 결과를 반환하는 인터페이스를 만든다.

  • negate()
predicateAB = predicateA.negate(predicateB);
boolean result = predicateAB.method();

! 와 대응된다.

결과값 둘을 negate 연산한 결과를 반환하는 인터페이스를 만든다.

 

 

 

3. Predicate 계열의 static 메서드

  • Predicate.isEqual()
Predicate<Object> predicate = Predicate.isEqual(targetObject);
boolean result = predicate.test(sourceObject);

두 객체의 동일성을 검사한다.

위 코드는 Objects.equals(targetObject, sourceObject); 와 같은 역할이다.

 

 

4. BinaryOperator 의 static 메서드

 

  • minBy() : Comparator 객체를 받아 더 작은 객체를 반환한다.
  • maxBy() : Comparator 객체를 받아 더 큰 객체를 반환한다.

 

 

 

함수형 인터페이스와 람다의 활용

많은 예가 있겠지만, 하나의 예를 들자면.

enum 형식에는 abstract 메서드를 통해 상수마다 메서드 몸체를 정의할 수 있었다.

 

abstract 메서드가 아닌, 함수형 인터페이스를 필드로 가지게 하고, 필드에 람다식을 정의하는 방법으로 같은 기능을 구현할 수 있다.

그리고 이 람다식을 실행시키는 메서드를 하나 만들어주면 된다.

 

alkhwa-113.tistory.com/entry/Enum-%ED%99%9C%EC%9A%A9%EB%9E%8C%EB%8B%A4%EC%8B%9D-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0

 

Enum 활용(람다식 사용하기)

3주차 미션이 끝나고 마지막 최종 코딩 테스트를 기다리고 있다. 3주차 미션은 너무 어려웠어서, 코드 리펙토링이 충분히 진행되지 못했다. 그래서 조금 더 코드를 줄이는 방법을 고민한 결과 Enu

alkhwa-113.tistory.com

 

다른 간단한 예시로는 ,

public class FunctionalInterface {

    public static void main(String[] args) {
        BiConsumer<String, String> con = (s1, s2) -> System.out.println(s1 + s2);
        con.accept("Hi", "Java");
    }
}

의 출력값은 HiJava 이다.

 

 

※ 참고

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

 

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

선장님과 함께하는 마지막 자바 스터디입니다. (ㅜ) 자바 스터디 Github github.com/whiteship/live-study whiteship/live-study 온라인 스터디. Contribute to whiteship/live-study development by creating an..

alkhwa-113.tistory.com

 

 

 

익명 구현 클래스 vs 람다식

그렇다면 익명클래스와 람다식의 차이는 무엇일까.

1. 익명 내부 클래스는 새로운 클래스를 생성하지만, 람다는 새로운 메서드를 생성하여 포함한다.

  • 람다는 static 이든, 객체 사용을 위한 non-static 이든, 메서드로 생성된다.
  • 이에 반해 익명 내부 클래스는 새로운 클래스파일이 생성된다.

 

2. 익명 내부 클래스의 this : 새로 생성된 클래스, 람다의 this : 람다식을 포함하는 클래스

  • 위와 완벽하게 일맥상통하는 말이다.
  • 새로운 클래스를 만들기 때문에, 익명 내부클래스에서의 this 는 해당 클래스를 가리킨다.
  • 새로운 클래스를 만드는 게 아니고 그냥 메서드를 만드는 람다는, 메서드 즉 람다가 있는 클래스를 가리킨다.
728x90

'공부 > Java' 카테고리의 다른 글

Thread와 synchronized  (0) 2022.02.14
[JPA] save와 persist차이 (save, persist, merge개념)  (0) 2022.02.07
Comments