자바 람다

민율·2021년 6월 21일
0
post-thumbnail

람다 ( 람다 표현식 ) ⇒ 익명함수

특징

이름이 없다는 것 ⇒ 함수명이 없다는 것

어떤 특정 클래스에 종속적이지 않다.

람다표현식을 메서드 인수로 전달, 변수로 저장할 수 있다. 즉, 추상메소드를 인스턴스화

  • 책을 읽다보면 변수에 람다를 할당했다는 문구가 많이 나옴

람다식 사용 목적

함수형 인터페이스가 가지고 있는 메소드를 간편하게 즉흥적으로 구현해서 사용하는 것이 목적
익명 클래스 등의 판같이 박힌 코드를 구현할 필요가 없다는 점

함수형 인터페이스 : 하나의 추상 메서드를 지정하는 인터페이스

여기서 뜬금없이 람다가 나오다가 함수형 인터페이스가 나온이유 :

  • Java8에서는 함수를 일급객체처럼 다룰 수 있게 함수형 인터페이스를 제공
  • 즉, 람다로 함수형 인터페이스를 구현한 것을 인스턴스화.
@FunctionalInterface
interface TestInterface {
     void test();
}

public class LambdaTest {
public static void main(String[] args) {
       TestInterface instance = () -> System.out.println("Test !"); // 변수 인스턴스에 할당
                                                                    // 인스턴스화를 했기에
       instance.test(); // 메소드를 호출
    }
}
  • 람다 시그니처를 서술 하는 메소드를 함수 디스크립터. 모양이라고 생각하면 쉽다.
void test()  이부분을 디스크립터로 표현하자면 void -> void
int test(boolean) 이것을 디스크립터로 표현하자면 boolean -> int
  • 함수형 인터페이스의 추상메서드와 같은 모양을 갖는다는 사실을 기억하는 것이 좋다.
  • 람다로 함수형 인터페이스를 구현한 클래스의 인스턴스'를 할당받는 것.
TestInterface instance = () -> System.out.println("Test !"); // 할당
@FunctionalInterface
interface TestInterface {
     void test();
}

public void testProcess(TestInterface instance){ // 할당받음
   instance.test();  // 할당받고 test 호출
}

testProcess( ()-> System.out("HI") ); // 파라메터로 람다를 넘김 인스턴스화

즉, 중간 요약하자면

  1. 함수형 인터페이스는 추상메소드를 1개가진 인터페이스다.

  2. 당연히 그 1개의 메소드를 구현을 해야하고 그 메소드를 구현한 것이 람다

  3. 람다로 인스턴스로 취급하고 일급객체처럼 넘길 수 있다.

저는 이렇게 외웁니다.

  • Class -> 함수 인터페이스
  • new Class -> () -> {} 람다

함수형 인터페이스를 보고 함수 디스크립터를 먼저 고려하는것이 가장 좋다

@FuncationalInterface
public interface Test{
    boolean test(String s);
}

-> 디스크립터 : (String) -> boolean

이렇게 디스크립터를 먼저 따고 람다식으로 표현을 해주면서 일급객체처럼 인스턴스를 할당해주는 것이 가장 좋은 방향성인 인듯

예륻 들면 Comsumer 를 해보자

@FuntionalInterface
public interface Consumer<T>{
   void accept(T t);
}

그럼 여기서 디스크립터를 따면

// 1
(Integer i) -> void
// 2
public <T> void forEach(List<T> list, Consumer<T> c) { // <---- 파라메터 2번째
   for(T t: list){
      c.accept(t);  
   }
}
// 3
forEach(
   Arrays.asList(1,2,3,4,5), (Integer i) -> System.out.println(i)  // 인스턴스화
);

박싱 vs 언박싱 → 함수형인터페이스 이점

제네릭 파라미터에는 참조형만 쓸 수 있다 ( Byte, Integer, Object ,List )

기본형 -> 참조형 변환하는 기능 : 박싱

참조형 -> 기본형 변환하는 기능 : 언박싱

박싱한 값 : 기본형을 감싸는 wrapper 즉, 힙에 저장되고 메모리를 더 소비하여 기본형을 가져올 때도 메모리를 탐색하는 과정이 필요.

List<Integer> list = new ArrayList<>();
for (int i = 300; i< 400; ++i){
   list.add(i);
}

즉 결론은 박싱하면 메모리 소비라는 단점이 있는데 '오토박싱'이 일어나지 않겠끔 하기 위해서 자바 8에서는 기본형을 입출력하는 상황에서 '오토박싱'을 피할수 있도록 특별한 버전의 함수형 인터페이스를 제공함.

형식검사

@FunctionalInterface
public interface Predicate<T> {
      boolean test(T t);
}

Predicate 의 디스크립터 : T -> boolean

람다로 함수형 인터페이스의 인스턴스를 만들수 있다.

filter(inventory, Predicate<Apple> p) // 대상형식
List<Apple> heavierThan150g = filter(inventory, (Apple a) -> a.getWeitht() > 150)

이제까지 했던 시그니처를 맞추고 했던 일련의 과정들이 '형식검사'고 '대상 형식'이 람다표현식과 같으면 형식검사가 성공적으로 완료.

자바 컴파일러가 이러한 과정을 이용해서 추론한다.

그러면

allable<Integer> c = () -> 42;
PriviliegedAction<Integer> p = () -> 42;

두 추상메소드의 디스크립터가 같다는 것은 다른 함수형 인터페이스지만, 같은 형식의 함수형 인터페이스의 인스턴스를 할당 받을 수 있다는 점!

execute(()->{})

명확하지가 않다. 왜냐하면

@FunctionalInterface
public interface Runnable {
    void run();
}
@FunctionalInterface
interface Action{
    void act();
}

Action, Runnable의 추상메서드에 각 메서드의 시그니처가 같아요.

execute(Runable runable)
execute(Action<T> action)

두개의 메소드로 해석을 할수 있음.

그럼 좀 더 명시적으로하기위해서 밑 예시와 같은 캐스트가 가능하다는 것

execute((Action) () -> {})
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());

전자는 형식을 추론, 후자는 형식추론 x

개발자가 스스로 가독성을 향상시킬수 있는지 생각하고 결정해야하는 문제

자유변수와 지역변수

항상 파라미터로 넘겨진 변수로만 예시로 봤다면 밖에서도 자유롭게

Runnable r = () -> System.out.println(number)

이런식으로 익명함수처럼 쓸수 있는것이 자유변수다.

그러면 이렇게 자유롭게 움직일 수 있으면

다른 인스턴스 변수, 정적 변수가 람다표현식에 할당이 될 수 있다.

final int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
..
 //  final이 아닌데도 컴파일 에러

2개의 스레드가 있는데, 람다가 스레드에 실행되고 변수를 할당한 스레드가 사라지면 변수 할당이 해제되었는데도 람다를 실행하는 스레드에서는 해당 변수에 접근하려하기 때문에 이러한 제약조건이 생긴 것.그럼 Heap 영역에 할당된 인스턴스 변수는 ? 복사가 아닌 참조기 때문에 이러한 제약조건은 없다.

메서드 참조

메서드참조는 특정 메서드만을 호출하는 람다의 축약형

' 가독성을 높일 수 있다' 는 장점을 가졌고 실제로 호출하는 것이 아니므로 괄호가 없다.

또 다른 장점으로는 기존의 메소드 구현을 재사용하고 직접 전달할 수 있다.

표현 방법
(Apple apple) -> apple.getWeight()
Apple::getWeight -> Apple클래스에 정의된 getWeight의 메서드 참조

아직까지 나의 생각은 축약을 해서 깔끔하고 간결하고 보기 좋게 만드는 면에서는 좋아보이나 코드를 읽고자할때 좋아보이지는 않아보임

(String s) -> s.toUpperCase()
String::toUpperCase
ClassName::new 
Supplier : () -> Apple
Supplier<Apple> a = Apple::new ( 기본 생성자 ) : () -> new Apples();
Apple aplle = a.get()
Transaction expensiveTransaction = new Transaction()
expensiveTransaction.getValue()
expensiveTransaction::getValue

익명클래스 vs 람다

  • this의 의미는 다르다! 익명클래스의 this는 익명클래스 자신을 가리키지만 람다에서의 this는 선언된 클래스를 가리킵니다.
profile
나의 시간들을 글로 표현하는 사람🌈

0개의 댓글