람다(Lamda)

조용근·2024년 1월 23일

자바 정리

목록 보기
14/21

람다식

(매개변수) -> {실행문};
  • 자바 메서드를 간결한 함수식으로 표현한 것.
    기존에 메서드를 선언할려면 클래스를 정의해야 했다. 람다식은 메서드 이름과 반환값 생략이 가능하고,자바 코드가 간결해져서 가독성이 좋다.
(int a) -> { System.out.println(a); } // 매개 변수 a의 값을 출력
  • 매개변수를 이용해서 {}를 실행한다는 뜻
(a) -> { System.out.println(a); }
  • 람다식에서는 매개변수 타입을 따로 적지 않는다.(대입값에 따라 자동으로 인식)
a -> System.out.println(a);
  • 하나의 매개변수만 존재한다면, 괄호를 생략할 수 있다.
  • 하나의 실행문만 존재한다면, 중괄호도 생략할 수 있다.
(x, y) -> { return x + y; }
  • 위와 같이 중괄호에 return문만 존재한다면, return문을 생략할 수 있다.
(x, y) -> x + y //중괄호 생략
//기존 메서드
int add(int a, int b) {
	return a+b
}
//람다식
(a,b) -> a+b;

//예문1
int add(int x, int y) {
    return x + y;
}

// 위의 메서드를 람다 표현식을 이용해 아래와 같이 단축 시킬수 있다. (메서드 반환 타입, 메서드 이름 생략)
(int x, int y) -> {
	return x + y;
};

// 매개변수 타입도 생략 할 수 있다.
(x, y) -> {
	return x + y;
};

// 함수에 리턴문 한줄만 있을 경우 더욱 더 단축 시킬 수 있다. (중괄호, return 생략)
(x, y) -> x + y;

타입을 생략해도 오류가 안 나는 이유는 컴파일러가 생략한 타입 위치를 추론해서 동작하기 때문이다.

예문2
interface MyFunction {
	void print(String str);
}

public class Main {
    public static void main(String[] args) {
        MyFunction myfunc = (str) -> System.out.println(str);
        myfunc.print("Hello World");
    }
}

MyFunction myfunc = (str) -> System.out.println(str);
myfunc.print("Hello World");
부분을 보면, 함수식을 변수에 대입해서 변수에서 함수를 호출해서 사용한 것을 확인 할 수 있다. 즉, 자바는 메서드를 단독으로 선언하는 것이 아니라, 항상 클래스의 구성 멤버로 선언하기 때문에 메서드를 가지고 있는 객체를 생성해 낸다고 볼 수 있다.

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

위 식을 통해, 람다식은 인터페이스의 익명 구현 객체를 생성한다는 뜻이다.
인터페이스는 직접 객체화를 할 수 없으므로, 반드시 구현 클래스가 필요하다. 람다식은 익명 구현 클래스를 생석하고 동시에 객체화한다.
이 때, 인터페이스의 종류에 따라 람다식의 작성 방법이 달라지는데, 람다식이 대입되는 인터페이스를 람다식의 target type이라고 한다.

Functional Interface

하나의 추상 메소드가 선언된 인터페이스라면 람다식이 가능하다!!!!
단, 두 개 이상의 추상 메소드가 선언된 인터페이스는 람다식을 이용해서 구현 객체를 생성할 수 없다.
@FunctionalInterface 어노테이션을 써주면, 두 개이상의 메서드 선언시 컴파일 오류를 발생시킨다.

@FunctionalInterface
public interface MyFunctionalInterface {
		public void method();
		public void otherMethod(); // 컴파일 오류 발생
}
  • 위 어노테이션을 반드시 쓸 필요는 없다.
  • 하나의 추상 메서드만 있으면 된다.

본래 list를 만들고, 각각의 요소에 대해서 반복문을 통해 접근한다.
하지만, 람다식을 이용하면, 아래 3가지 방법으로 간편하게 접근이 가능하다.

package javaplus.lamda;

import java.util.ArrayList;
import java.util.List;

public class LambdaTypeExam {
   public static void main(String[] args) {
       List<String>list = new ArrayList<>();
       list.add("Java");
       list.add("C");
       list.add("React");

       for (String s : list) {
           System.out.println(s);
       }
       System.out.println();

       list.stream().forEach((String s) -> System.out.println(s));
       list.stream().forEach(s -> System.out.println(s));
       list.stream().forEach(System.out::println);
       //람다 표현식에서 list에 해당하는 것을 가져올 것이고, 해당 타입이 String이라고 추론이 가능하기 떄문에 String을 생략해도 된다.

   }
}

람다식 작성방법

1. 매개 변수와 리턴값이 없는 람다식

@FunctionalInterface
public interface MyFunctionalInterface {
    public void method();
}

위와 같은 경우에는 아래와 같이 람다식을 작성한다.

MyFunctionalInterface fi = () -> { ... }

호출할 경우에는 아래와 같이 호출한다.

fi.method();
public class MyFinctionalInterfaceExample {
    public static void main(String[] args) {
        MyFunctionalInterface fi;

        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();
    }
}

2. 매개 변수가 있는 람다식

@FunctionalInterface
public interface MyFunctionalInterface {
		public void method(int x);
}

위와 같은 경우에는 아래와 같이 람다식을 작성한다.

MyFunctionalInterface fi = (x) -> { ... } 또는 x -> { ... }

호출할 경우에는 아래와 같이 호출한다.

fi.method(5);
public class MyFinctionalInterfaceExample {
    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);
    }
}

3. 리턴값이 있는 람다식

//매개 변수와 리턴값이 있는 추상 메서드를 가진 인터페이스
@FunctionalInterface
public interface MyFunctionalInterface {
		public int method(int x, int y);
        		
}

위의 인터페이스는 아래와 같이 람다식을 적는다.

MyFunctionalInterface fi = (x,y) -> { ...; return 값; }

람다식 호출은 아래와 같이 한다.

int result = fi.method(2, 5);
public class MyFinctionalInterfaceExample {
    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);
        }
    }
}

Capturing

람다 함수가 정의된 위치에서 외부 변수를 참조하는 것을 말한다.
1.지역 변수(변경 불가능/읽기 가능)
2.인스턴스 변수
3.정적 변수
이 중에서 지역 변수만 변경이 불가능하고, 나머지는 람다 내부에서 읽기/쓰기 모두 가능하다.

Method Reference

Method Reference(메서드 참조)

  • 람다식에서 불필요한 매개변수를 제거하는 것이 목적이다.
(left, right) -> Math.max(left, right);

위 코드는 두 개의 값을 받아서 큰 수를 리턴하는 Math 클래스의 max() 함수를 호출하는 코드이다.
이는 단순히 두 개의 매개변수 값을 함수에 전달하는 것이기 때문에, 아래와 같이 간소화할 수 있다.

Math::max;

Method Reference는 람다식처럼 인터페이스의 익명 구현 객체로 생성되기 때문에, 인터페이스의 추상 메서드가 어떤 매개변수이며, 리턴 타입이 무엇이냐에 따라 달라지게 된다.

1. 정적 메서드와 인스턴스 메서드 참조

//정적 메서드

클래스 :: 메소드
//참조 메서드

참조변수 :: 메소드

2. 매개변수의 메서드 참조

(a, b) -> { a.instanceMethod(b); }

a 매개변수의 함수를 호출해서 b 매개변수를 instanceMethod의 매개변수 값으로 사용한다.

이를 MethodReference로 바꾸면 이렇다.

클래스 :: instanceMethod

a 클래스 이름 뒤에 ::를 붙이고 메서드 이름을 적으면 된다.

3. 생성자 참조(=객체 생성)

(a, b) -> { return new 클래스(a,b); }

위 코드는 객체를 생성하고 리턴하도록 구성된 람다식이다.

클래스 :: new

이는 위와 같이 생성자 참조로 대치된다.
//생성자가 오버로딩되어 여러 개 있는 경우, 컴파일러는 functional Interface의 추상 메서드와 동일한 매개 변수의 type과 개수를 가지고 있는 생성자를 찾아 실행한다.
생성자가 존재하지 않는다면, 컴파일 오류가 발생한다.

출처: https://github.com/yeGenieee/java-live-study/blob/main/[15]Java%20Live%20Study.md

profile
Today I Learn

0개의 댓글