특징
이름이 없다는 것 ⇒ 함수명이 없다는 것
어떤 특정 클래스에 종속적이지 않다.
람다표현식을 메서드 인수로 전달, 변수로 저장할 수 있다. 즉, 추상메소드를 인스턴스화
함수형 인터페이스가 가지고 있는 메소드를 간편하게 즉흥적으로 구현해서 사용하는 것이 목적
익명 클래스 등의 판같이 박힌 코드를 구현할 필요가 없다는 점
여기서 뜬금없이 람다가 나오다가 함수형 인터페이스가 나온이유 :
@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개의 메소드를 구현을 해야하고 그 메소드를 구현한 것이 람다
람다로 인스턴스로 취급하고 일급객체처럼 넘길 수 있다.
저는 이렇게 외웁니다.
함수형 인터페이스를 보고 함수 디스크립터를 먼저 고려하는것이 가장 좋다
@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) // 인스턴스화
);
제네릭 파라미터에는 참조형만 쓸 수 있다 ( 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