람다와 인터페이스 스펙 변화

람다와 인터페이스 스펙 변화

람다의 도입 이유.

람다가 도입된 이유에는 여러가지 이유가 있겠지만 가장 주된 이유는 빅데이터를 관리하기 위함이라고 볼 수 있습니다. 기존의 CPU는 내부의 하나의 코어만을 가지고있었지만, 이제는 하나의 CPU 안에 다수의 코어를 삽입하는 멀티 코어 프로세서들이 등장하면서 일반 프로그래머에게도 병렬화 프로그래밍에 대한 필요성이 생기기 시작했습니다. 이러한 추세에 대응하기위해 자바 8에서는 병렬화를 위해 컬렉션을 강화했고, 이러한 컬렉션을 효율적이게 사용하기 위해 스트림을 강화했습니다. 또 스트림을 강화하기위해 함수형 프로그래밍이, 람다가 그리고 함수형 인터페이스까지 등장하게 되었습니다.

람다란?

람다는 한마디로 코드 블록입니다. 기존의 코드 블록은 메서드 내에 존재해야 했습니다. 그래서 코드 블록만을 가지고 싶어도 익명 객체를 만드는 방식을 사용했습니다. 람다가 도입된 이후로 이런 수고를 할 필요가 없어졌습니다. 람다를 메서드의 인자나 반환값으로 사용할 수 있게 되었습니다. 이말은 즉슨 코드 블록을 변수처럼 사용할 수 있다는 것입니다.

기존 방식의 코드 블록 사용

public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { System.out.println("hi"); } }; r.run(); }

람다 적용

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

Runnable 인터페이스가 가진 추상 메서드가 run() 메서드 단 하나기 때문에 단순하게 public void run 을 ()로 변경해서 사용할 수 있습니다. 만약 로직이 한 줄이라면 { } 괄호를 생략할 수 있습니다.

함수형 인터페이스

추상 메서드를 하나만 갖는 인터페이스를 함수형 인터페이스입니다. 함수형 인터페이스만을 람다식으로 변경할 수 있습니다.

public class Main { public static void main(String[] args) { MyFunctionalInterface mfi = (int a) -> { return a*a;}; int n = mfi.runSomething(5); System.out.println(n); // 25 } } @FunctionalInterface interface MyFunctionalInterface { public abstract int runSomething(int cnt); }

@FunctionalInterface 어노테이션이 붙은 인터페이스는 컴파일러가 해당 인터페이스가 함수형 인터페이스의 조건에 맞는지 검사하게됩니다.

람다식을 좀 더 간소화시킬 수 있습니다.

(int a) -> { return a*a;} // 기존 식 (a) -> { return a*a;} // 인자가 한개임으로 인자 타입 생략 가능. a -> {return a * a;} // 인자가 하나며, 타입을 생략한 경우 괄호 생략 가능 a -> a * a // 코드가 한 줄인경우 괄호 생략 가능. 이때 return, ;도 생략해야 함

메소드 호출 인자로 람다 사용.

public class Main { public static void main(String[] args) { MyFunctionalInterface mfi = a -> a * a; doIt(mfi); // doIt(a -> a + a); 와 같은방식으로도 사용 가능. } public static void doIt(MyFunctionalInterface mfi) { int b = mfi.runSomething(5); System.out.println(b); } }

메소드 반환값으로 람다 사용.

public class Main { public static void main(String[] args) { MyFunctionalInterface mfi = todo(); int result = mfi.runSomething(3); System.out.println(result); } public static MyFunctionalInterface todo() { return a -> a * a; } }

자바 8 API에서 제공하는 함수형 인터페이스.

java.util.function 패키지와 여러 패키지에서 많이 사용되는 함수형 인터페이스들을 제공하고있습니다.

함수형 인터페이스 추상 메서드 용도 Runnable void run() 실행할 수 있는 인터페이스. Supplier T get() 제공할 수 있는 인터페이스 Consumer void accept(T t) 소비할 수 있는 인터페이스 Function R apply(T t) 입력 받아서 출력할 수 있는 인터페이스 Predicate Boolean test(T t) 입력받아 찹/거짓 판별할 수 있는 인터페이스 UnaryOperator T apply(T t) 단항 연산할 수 있는 인터페이스. BiConsumer void accept(T t, U u) 이항 소비자 인터페이스. BiFunction R apply(T t, U u) 이항 함수 인터페이스. BiPredicate Boolean test(T t, U u) 이항 단정 인터페이스. BinaryOperator T apply(T t, T t) 이항 연산자 인터페이스.

Bi는 Binary의 약자입니다. 표에 설명한 함수형 인터페이스를 비롯해 java.util.function 패키지에서는 총 43개의 함수형 인터페이스를 제공합니다. 만약 43개 중에서 필요로하는 함수형 인터페이스가 없다면 사용자 정의 함수형 인터페이스를 정의해서 사용하면 됩니다.

다음은 사용 예시입니다.

Runnable runnable = () -> System.out.println("hello"); Supplier sup = () -> "hi"; Consumer con = n -> System.out.println(n); Function fun = num -> "input: " + num; Predicate pre = num -> num > 10; UnaryOperator uOp = num -> num * num; BiConsumer bCon = (str, num) -> System.out.println(str + num); BiFunction bFun = (num1, num2) -> "add result: " + (num1+num2); BiPredicate bPre = (num1, num2) -> num1 > num2; BinaryOperator bOp = (num1, num2) -> num1 - num2;

컬렉션 스트림에서 람다 사용

람다는 다양한 용도가 있지만 그 중에서도 컬렉션 스트림을 위한 기능에 초점이 맞춰져 있습니다. 컬렉션 스트림과 람다를 통해 더 작은 코드로 더 안정적인 코드를 만들 수 있습니다.

사용 예시

Integer[] ages = { 20,22,15,16,27,21,28,23}; // 20세 미만 판별 Arrays.stream(ages) .filter(age -> age < 20) .forEach(age -> System.out.println("can`t enter: " + age)); System.out.println("sum: " + Arrays.stream(ages).mapToInt(age -> age).sum()); System.out.println("average: " + Arrays.stream(ages).mapToInt(age -> age).average()); System.out.println("min: " + Arrays.stream(ages).mapToInt(age -> age).min()); System.out.println("max: " + Arrays.stream(ages).mapToInt(age -> age).max()); System.out.println("모두가 21살 이상인지?: " + Arrays.stream(ages).allMatch(age -> age > 20)); System.out.println("모두가 30살 이하인지?: " + Arrays.stream(ages).allMatch(age -> age < 31)); // 정렬해서 출력 Arrays.stream(ages).sorted().forEach(System.out::println);

메서드 레퍼런스, 생성자 레퍼런스

Arrays.stream(ages).sorted().forEach(age -> System.out.println(age));

다음 코드를 다음과 같이 축약할 수 있습니다.

Arrays.stream(ages).sorted().forEach(System.out::println);

메서드 레퍼런스는 다음과 같은 세 가지 유형이 있습니다.

인스턴스::인스턴스메서드

클래스::정적메서드

클래스::인스턴스메서드

Integer[] nums = { 20,22,15,16,27,21,28,23}; // 람다 Arrays.stream(nums) .map(num -> Math.sqrt(num)) .forEach(num -> System.out.println(num)); // 메소드 참조 Arrays.stream(nums) .map(Math::sqrt) // 클래스::정적메서드 .forEach(System.out::println); // 인스턴스::인스턴스메서드 BiFunction biFunction1 = (a, b) -> a.compareTo(b); BiFunction biFunction2 = Integer::compareTo; // 클래스::인스턴스

마지막으로 메서드 레퍼런스와 유사한 생성자 레퍼런스가 있습니다. 클래스::new

Main main = new Main(); // Error! // Main main = Main::new; // 생성자 레퍼런스로 생성한 것은 Main 클래스 객체가 아니라 함수형 인터페이스 구현 객체이기 때문. Supplier sup = Main::new; // 생성자 레퍼런스를 람다식으로 변경하면 다음과 같습니다 // Supplier sup = () -> new Main();

from http://jinukix.tistory.com/49 by ccl(A) rewrite - 2021-10-17 08:02:21