[Java] 람다식이란

zl존석동

·

2022. 1. 6. 19:10


알게 모르게 써오던 람다식..

자바에서의 람다식에 대해 간단하게 공부해보자!

 


 

 

 

 

 

 

Lambda Expression? 

 

람다식은 간단히 말하면 메소드를 하나의 '식' 으로 표현하는 것으로 자바 8에서 나왔다.

 

함수를 간략하고 명확하게 표현할 수 있게 해준다.

 

함수 이름과 리턴이 없기 때문에 익명함수라고도 한다.

 

 

 

람다식 사용 이유?

 

재사용할 일이 없는 기능 구현에 대해서 코드가 간결해진다.

 

따로 메소드를 정의하고 호출해서 사용하지 않고 간결한 식을 바로 활용하기 때문에 가독성생산성이 높아질 수 있다. 

 

병렬처리가 가능하다.

 

불필요한 데이터 생성 없이 기능 그 자체에 집중하고자 할 때 사용한다고 한다.

 

 

 

함수형 프로그래밍에 대해서 나중에 공부를 더 해보아야 겠다.

 

 

 

 

익명함수는 1급객체?

 

1급객체가 될 수 있는 조건

 

1. 변수나 데이터에 할당할 수 있어야 한다.

 

2. 객체의 인자로 넘길 수 있어야 한다.

 

3. 객체의 리턴값으로 리턴할 수 있어야 한다.

 

 

 

자바

void run(){
	System.out.println("hi");
}

// Object A = run();

 

 

 자바스크립트

function run(){
  console.log("he");
}
var a = run();

 

 

일반적인 Java 의 메소드는 1급 객체가 아니다.  메소드를 변수처럼 다룰 수 없다. 

 

하지만 람다식은 메소드의 매개변수로 전달되는게 가능하고 리턴될 수도 있고 변수처럼 다뤄지는 것이 가능하다.

 

 

자바 람다식

  public static void main(String[] args) {
    Exam exam = () -> System.out.println("hi");
  }
}
interface Exam {
  void run();
}

 

 

 

 

인터페이스?

 

순수 함수에 집중해서 사용하겠다는거 잘 알겠는데 왜 인터페이스를 사용할까요?

 

자바는 객체 지향 언어로 모든 함수(메소드) 는 클래스 안에 포함되어야만 합니다.

 

람다식은 변수처럼 사용되는 함수지만 메소드라기 보다는 사실 그 자체가 익명클래스의 객체와 같다고 볼 수 있습니다.

 

    Exam ee = new Exam() {
      @Override
      public void run() {
         System.out.println("hi");
      }
    };
    ee.run();

 

 

객체이니 담을 참조변수가 필요하며 참조형이니 클래스 또는 인터페이스가 되어야 하는 것인데 ..

 

이를 람다식을 이용한 이 함수(1급객체) 라는 것을 일반적인 자바의 함수와 구분지어 사용할 수 있게 하기 위해 

 

자바에서는 단 하나의 추상메소드만 가지고 있는 함수형 인터페이스를 사용한다. 

 

 

메소드의 이름도 없고, 변수의 타입도 없는데 함수를 구현해 사용할 수 있었던 이유는

 

인터페이스에 단 하나 존재하는 추상 메소드와 1:1로 연결되어 구현한 것이기 때문이다.

 

 

 

아래의 코드를 통해 어떤식으로 돌아가는지 재확인해보자

 

    int[] arr = new int[5];
    Arrays.setAll(arr, (i)->(int)(Math.random()*5)+1);

 

i 라는 알 수 없는 매개변수가 사용되고 내부에는 어떤 랜덤 정수값이 구현되어있기만 하다. 

 

setAll() 의 해당 파라미터 부분의 타입을 확인해보자 

 

    public static void setAll(int[] array, IntUnaryOperator generator) {
        Objects.requireNonNull(generator);
        for (int i = 0; i < array.length; i++)
            array[i] = generator.applyAsInt(i);
    }

 

Arrays 의 정수배열 setAll() 메소드는 배열과 IntUnaryOperator 라는 타입이 파라미터로 들어오는 메소드였다.

 

IntUnaryOperator 타입의 변수가 같이 입력받은 배열의 각 위치에 할당되고 있다??

 

 

IntUnaryOperator 라는 클래스를 확인해보자!

 

@FunctionalInterface
public interface IntUnaryOperator {

    int applyAsInt(int operand);

    default IntUnaryOperator compose(IntUnaryOperator before) {
        Objects.requireNonNull(before);
        return (int v) -> applyAsInt(before.applyAsInt(v));
    }

    static IntUnaryOperator identity() {
        return t -> t;
    }
}

 

IntUnaryOperator

 

int applyAsInt(int operand);

 

라는 하나의 추상메소드만을 가진 함수형 인터페이스였다.

 

그리고 우리는 int operand 라는 파라미터를 i로 넣고  (int)(Math.random()*5)+1) 라는 값을 해당 추상 메소드의 리턴으로 구현해 사용한 것이다.

 

 

위의 IntUnaryOperator 인터페이스 처럼 @FunctionalInterface 어노테이션을 주면

 

하나의 추상클래스만 가지게 강제할 수 있어 함수형 인터페이스라는 것을 확실시 해줄 수 있다.

 

 

 

 

사용법 예시

 

대부분의 경우 파라미터의 타입은 생략 가능하고 

 

파라미터가 하나면 괄호마저 생략할 수 있다.

 

{중괄호} 내부에 문장이 하나라면 중괄호도 생략할 수 있다.

 

 

 

No 매개변수, No 리턴

@FunctionalInterface
interface Flyer {
  public abstract void fly();
}

Flyer f = () -> System.out.println("hi");

 

 

Yes 매개변수, No 리턴

@FunctionalInterface
interface Flyer2 {
  public abstract void fly(int a, String b);
}

// 람다 표현식2 => 파라미터는 있고 리턴이 없는 추상 메소드
Flyer2 f2 = (v, x) -> {
  System.out.println(v + x);
};

 

 

No 매개변수, Yes 리턴

@FunctionalInterface
interface Flyer3 {
  public abstract int fly();
}    
    // 람다 표현식3 -> 파라미터는 없고 리턴이 있는 추상 메소드
Flyer3 ff3 = () -> {
    return 5;
};
    
    // 중괄호 내부에 문장이 하나면 생략가능
Flyer3 ff33 = () -> 5;

 

 

 

Yes 매개변수, Yes 리턴

@FunctionalInterface
interface Flyer4 {
  public abstract int fly(int a, int b);
}
    // 람다 표현식 4 -> 파라미터와 리턴이 모두 있는 추상 메소드
    Flyer4 ff4 = (x, y) -> x + y;
    System.out.println(ff4.fly(3, 15));

 

 

 

Java 에서 제공되는 함수형 인터페이스

 

Java.util.function 패키지에 일반적으로 자주 쓰이는 형식의 메소드를 함수형 인터페이스로 미리 정의되어 있다.

 

매번 새로운 인터페이스를 정의하기 보다는 있는 인터페이스를 활용하는게 메소드 이름도 통일되어 일관성이 있고 재사용성이 좋다.

 

 

Supplier<T> : No 매개변수, Yes 리턴

- 파라미터 없이 반환값 만을 가지는 함수형 인터페이스이다.

- T get() 추상 메소드 하나를 가진다.

 

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

 

 

Consumer<T> : Yes 매개변수, No 리턴

- T를 파라미터로 해서 사용하지만 리턴은 없다.

- void accept(T t) 추상 메소드를 가진다.

 

 

Function<T,R> : Yes 매개변수, Yes 리턴

- T를 매개변수로 받아 처리해 R 타입을 리턴한다.

- R apply(T t) 추상 메소드를 가진다.

 

 

Predicate<T> 

- 매개변수 T를 받아 처리후 Boolean 을 반환한다.

- Boolean test(T t) 추상 메소드를 가진다.

 

 

 

 

Method Reference

 

람다식이 하나의 메소드만 호출하는 경우에는 '메소드 참조' 방법으로 람다식을 간략하게 할 수 있다.

 

 

조건

함수형 인터페이스의 파라미터가 메소드의 파라미터와 타입,개수가 같아야 하고 리턴도 같아야 한다.

 

예시

// 기존
Function<String , Integer> f = (s) -> Integer.parseInt(s);

// 메소드 참조 방식
//	람다식과 내부 메소드의 파라미터가 생략되는 형태이다.
Function<String , Integer> f = Integer::parseInt;

 

 

 

Static Method

 

람다식
(x) -> ClassName.method(x)
 
메소드 참조 방식
ClassName::method

 

@FunctionalInterface
interface MyInterface<T,V>{
  T hi(T t, V v);
}

// static method 에서의 참조    
    //before
    MyInterface<Integer , Integer> ss = (x,y) -> StaticClass.sum(x, y);
    
    //after
    ss = StaticClass::sum;
    ss.hi(5, 15);

 

 

 

 

인스턴스 메소드

 

람다식
(x) -> obj.method(x)

메소드 참조 방식
obj::method

 

class InstanceClass {
  public int sum(int x, int y) {
    return x + y;
  }
}

// 인스턴스 메소드에서의 참조
    InstanceClass ic = new InstanceClass();
    
    //before
    MyInterface<Integer , Integer> ii = (x,y) -> ic.sum(x, y); 
    
    //after
    ii = ic::sum;
    ii.hi(5, 15);

 

 

 

생성자

 

생성자를 호출하는 람다식도 메소드 참조로 변환할 수 있다,

 

public class MethodRefConstructExam {

  public static void main(String[] args) {
    
    // 인자 없는 생성자
      // 람다식
    Supplier<MyClassA> s = () -> new MyClassA();
      // 메소드 참조 방식
    s = MyClassA::new; 
    
    // 인자 있는 생성자
    // 알아서 필요한 매개변수에 맞춰서 사용해야한다.
    
      // 람다식
    BiFunction<String, Integer, MyClassA> bf = (x,y) -> new MyClassA(x,y);
    bf.apply("김갑환", 25);
    
      // 메소드 참조 방식
    bf = MyClassA::new;
    bf.apply("최번개", 19);
  }
}

class MyClassA {
  String name;
  int age;
  public MyClassA() {}
  public MyClassA(String name, int age) {
    this.name = name;
    this.age = age;
  }
}

 

 

배열도 가능하다.

 

    // 배열
      // 람다식
    Function<Integer, int[]> f = x -> new int[x];
      // 메소드 참조 방식
    f = int[]::new;

 

메소드 참조는 람다식을 Static 사용하듯 해준다.

 

 

 


 

 

람다식Stream API 에서 사용되는데

웃긴게 본인은 Stream API는 많이 사용하는데 람다식에 대해 이번에 처음 알게 되었다. 

(사실 Stream API 도 잘 모르는 것 같다. 다시 공부해야함! )

 

이번에 간단하게 공부해본 람다식을 바탕으로 

다음 기회에 Stream API 에 대해서 공부해보고자 한다!

 

 

ref

 

 

 

[Java] 람다식이란?

이 글은 "자바 온라인 스터디"에서 공부한 내용을 정리하였습니다. 람다식이란? 람다식이란 함수명을 선언하고 사용하는 것이 아닌 식별자 없이 실행가능한 함수입니다. 절차형 프로그래밍, 객

math-coding.tistory.com

 

1급 객체(First-class citizen) 란? with Kotlin

Function Programming 을 공부 하다보면 1급 객체란 말을 많이 언급됩니다. 그래서 1급 객체 (First-class citizen)란 무엇인가를 공부도하고 정리해 공유 하고자 합니다.

medium.com

 

Java의 정석 기초편

교육현장에서 뒤쳐지는 학생들을 위해 쓰고, 학생들에게 직접 검증받고 호평받은 책. 코딩을 처음 배우는 사람도 자바를 쉽게 배울수 있게 도와준다.

book.naver.com

 

 

'Java' 카테고리의 다른 글

[Java] 예외란 뭘까  (0) 2022.01.10
[Java] Abstract , Interface  (0) 2022.01.09
[Java] 중첩 클래스  (0) 2022.01.05
[Java] 자바는 Call by Value (feat. C++)  (0) 2022.01.04
[Java] 자바 메모리 구조와 변수 타입  (0) 2021.12.22