본문으로 바로가기

함수형 인터페이스와 람다

category JAVA/JAVA8 2021. 1. 12. 22:20
반응형

함수형 인터페이스와 람다 표현식 소개

@FunctionalInterface
public interface RunSomthing {

    //추상 메서드가 하나만 있으면 함수형 인터페이스
    //abstract 생략
    //어노테이션을 붙여주는게 좋음
    //추상 메서드의 수가 중요함
    void doIt();
    //int doIt(int number);

    //static 메서드를 정의가 가능 (인터페이스임에도 불구하고)
    static void printName() {
        System.out.println("Tistory");
    }

    //default 메서드도 정의 가능
    default void printAge() {
        System.out.println("27");
    }
}
public class Foo {

    public static void main(String[] args) {
        //JAVA 8 이전
        //익명 내부 클래스 anonymous inner class
        RunSomthing runSomthing = new RunSomthing() {
            @Override
            public void doIt() {
                System.out.println("Hello");
            }
        };

        runSomthing.doIt();

        //람다 표현식
        RunSomthing runSomthingOne = () -> System.out.println("Hello");

        runSomthingOne.doIt();

        //처리해야할 내용이 한줄이 아닌경우
        RunSomthing runSomthingTwo = () -> {
            System.out.println("Hello");
            System.out.println("World");
        };

        runSomthingTwo.doIt();

        /*RunSomthing runSomthingThree = (number) -> {
            return number + 10;
        };*/

    }
}

 

함수형 인터페이스 (Functional Interface)

  • 추상 메소드를 딱 하나만 가지고 있는 인터페이스
  • SAM (Single Abstract Method) 인터페이스
  • @FunctionalInterface 어노테이션을 가지고 있는 인터페이스

람다 표현식 (Lamda Expressions)

  • 함수형 인터페이스의 인스턴스를 만드는 방법으로 쓰일 수 있다.
  • 코드를 줄일 수 있다.
  • 메소드 매개변수, 리턴타입, 변수로 만들어 사용할 수도 있다.

자바에서 함수형 프로그래밍

  • 함수를 First class object로 사용할 수 있다.
  • 순수함수 (Pure function)
    • 사이드 이팩트가 없다. (함수 밖에 있는 값을 변경하지 않음)
    • 상태가 없다. (함수 밖에 있는 값을 사용하지 않음)
  • 고차 함수 (Higher-Order Function)
    • 함수가 함수를 매개변수로 받을 수 있고 함수를 리턴할 수도 있다.
    • 불변성

 

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

 

Java가 기본으로 제공하는 함수형 인터페이스

 

자바에서 미리 정의해둔 자주 사용할만한 함수 인터페이스 (URL 참조)

docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

 

java.util.function (Java Platform SE 8 )

Interface Summary  Interface Description BiConsumer Represents an operation that accepts two input arguments and returns no result. BiFunction Represents a function that accepts two arguments and produces a result. BinaryOperator Represents an operation u

docs.oracle.com

import java.util.function.Function;

public class Plus10 implements Function<Integer, Integer> {
    @Override
    public Integer apply(Integer integer) {
        return integer + 10;
    }
}
public class Foo {
    public static void main(String[] args) {
        //이전 방식
        //Plus10 plus10 = new Plus10();
        //plus10.apply(1);

        //람다 표현식
        Function<Integer, Integer> plus10 = (i) -> i + 10;
        Function<Integer, Integer> multiply2 = (i) -> i * 2;

        System.out.println(plus10.apply(1)); // 11
        System.out.println(multiply2.apply(1)); // 2

        //함수 조합 (compose, andThen)
        Function<Integer, Integer> multiPly2AndPlus10 = plus10.compose(multiply2);
        System.out.println(multiPly2AndPlus10.apply(2)); //14
        System.out.println(plus10.andThen(multiply2).apply(2)); //24

        Consumer<Integer> printT = (i) -> System.out.println(i);
        printT.accept(10); //10

        Supplier<Integer> get10 = () -> 10;
        System.out.println(get10.get()); //10

        Predicate<String> startsWithKeesun = (s) -> s.startsWith("Tistory");
        System.out.println(startsWithKeesun.test("TistoyT") ? "t" : "f");
        Predicate<Integer> isEven = (i) -> i%2 == 0;

        UnaryOperator<Integer> unaryOp = (i) -> i + 10;
        unaryOp.apply(1);
    }
}

 

람다 표현식

 

public class Foo {

    public static void main(String[] args) {
        //(인자리스트) -> {바디}
        RunSomthing runSomthingThree = (number) -> {
            return number + 10;
        };
    }
}

 

인자리스트

  • 인자가 없을 경우 : ()
  • 인자가 한개일 경우 : (one) 또는 one
  • 인자가 여러개일 경우 : (one, two)
  • 인자의 타입은 생략 가능, 컴파일러가 추론 하지만 명시할 수도 있다. (Integer one, Integer two)

바디

  • 화살표 오른쪽에 함수 본문을 정의
  • 여러 줄인 경우에 { }를 사용해서 묶는다.
  • 한 줄일 경우에 생략 가능, return도 생략 가능

변수 캡쳐 (Variable Capture)

  • 로컬 변수 캡쳐
    • final이거나 effective final인 경우에만 참조가 가능
    • 그렇지 않을 경우 concurrency 문제가 생길 수 있어서 컴파일 방지.
  • effective final
    • 이것도 역시 자바 8부터 지원하는 기능으로 사실상 final 변수.
    • final 키워드 사용하지 않는 변수를 익명 클래스 구현체 또는 람다에서 참조할 수 있다.
  • 익명 클래스 구현체와 달리 쉐도윙하지 않는다.
    • 익명 클래스는 새로 스코프를 만들지만 람다는 람다를 감싸고 있는 스코프와 같다.
public class FooThree {

    public static void main(String[] args) {
        BinaryOperator<Integer> get10 = (a, b) -> a + b;

        UnaryOperator<Integer> plus10 = (i) -> i + 10;
        UnaryOperator<Integer> multiply2 = (i) -> i * 2;

        FooThree foo = new FooThree();
        foo.run();

    }

    private void run() {
        //로컬과 익명은 쉐도윙 가능 (별도의 스코프이기 때문에)
        //람다는 쉐도윙이 되지 않음. (람다는 run 메서드와 스코프가 같음)

        //effective final 변수
        int baseNumber = 10;
        //baseNumber++; 값을 바꾸는 변수가 생길시 참조 불가능

        //로컬 클래스
        class LocalClass {
            void printBaseNumber() {
                int baseNumber = 11;
                System.out.println(baseNumber); // 11 (11로 정의한 baseNumber가 10으로 정의한 baseNumber를 가림)
            }
        }

        //익명 클래스
        Consumer<Integer> integerConsumer = new Consumer<Integer>() {
            @Override
            public void accept(Integer baseNumber) {
                System.out.println(baseNumber); //파라미터 baseNumber를 참조함.
            }
        };

        //람다
        IntConsumer printInt = (i) -> {
            //int baseNumber = 11; //같은 스코프이기 떄문에 컴파일 에러
            System.out.println(i + baseNumber);
        };
    }

}

 

메소드 레퍼런스

 

람다가 하는 일이 기존 메소드 또는 생성자를 호출하는 거라면, 메소드 레퍼런스를 사용해서 매우 간결하게 표현할 수 있다.

스태틱 메소드 참조 타입::스태틱 메소드
특정 객체의 인스턴스 메소드 참조 객체 레퍼런스::인스턴스 메소드
임의 객체의 인스턴스 메소드 참조 타입::인스턴스 메소드
생성자 참조 타입::new

메소드 또는 생성자의 매게변수로 람다의 입력값을 받는다.

리턴값 또는 생성한 객체는 람다의 리턴값이다.

 

public class Greeting {
    private String name;

    public Greeting() {
    }

    public Greeting(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String hello(String name) {
        return "hello " + name;
    }

    public static String hi(String name) {
        return "hi " + name;
    }
}
import java.util.Arrays;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;

public class App {

    public static void main(String[] args) {

        //스태틱 메소드 참조
        UnaryOperator<String> hi = Greeting::hi;
        System.out.println(hi.apply("Tistory"));

        //특정 객체의 인스턴스 메소드 참조
        Greeting greeting = new Greeting();
        UnaryOperator<String> hello = greeting::hello;
        System.out.println(hello.apply("Tistory"));

        //생성자 참조
        Supplier<Greeting> newGreeting = Greeting::new;
        Greeting greetingNew = newGreeting.get();
        System.out.println(greetingNew.hello("Tistory"));

        //문자열 받는 생성자 참조 (Function 사용)
        Function<String, Greeting> tistoryGreeting = Greeting::new;
        Greeting greetingNewTwo = tistoryGreeting.apply("tistory");
        System.out.println(greetingNewTwo.getName());

        //임의 객체의 인스턴스 메소드 참조
        String[] names = {"tistory", "whiteship", "toby"};
        Arrays.sort(names, String::compareToIgnoreCase);
        System.out.println(Arrays.toString(names));

    }

}

 

 

 

반응형

'JAVA > JAVA8' 카테고리의 다른 글

Stream  (0) 2021.01.17
인터페이스의 변화  (0) 2021.01.17