본문 바로가기
BackEnd/Java

[Java] 더 자바, Java 8 이야기 1부 - 함수형 인터페이스 & 람다

by 뽀뽀이v 2020. 9. 20.

이 글은 백기선 님의 더 자바, Java 8 강의를 듣고 복습 차원에서 적은 글입니다.

 

Java 8 에서는 무엇이 바뀌었을까? 현재 자바 개발자 중 약 83%가 사용 중이라고 한다. 

이 글을 통해서 확실히 공부해놓으면 좋을 것 같다.

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

  • 추상 메소드를 딱 하나만 가지고 있는 인터페이스이다.
  • SAM(Single Abstract Method) 인터페이스라고도 불린다.
  • @FuncationInterface 어노테이션을 가지고 있는 인터페이스이다.
@FunctionalInterface
public interface Foo {
    void doIt();
}

람다 표현식 (Lambda Expressions)

  • 함수형 인터페이스의 인스턴스를 만드는 방법으로 사용된다.
  • 코드를 간략하게 만들 수 있다.
  • 메서드 매개변수, 리턴 타입, 변수로 만들어 사용할 수도 있다.

기본 함수형 인터페이스

  • Function <T, R>
  • Supplier <T>
  • Consumer <T>
  • Function <T, R>
  • Predicate <T>

이외에 많은 함수형 인터페이스를 여기서 확인하실 수 있습니다.

그래서 함수형 인터페이스 그리고 람다는 뭐라는 거야?

잠시 자바스크립트 코드를 보자!

자주 사용하는 더하기 함수를 만들고 싶다고 가정하자!

var add = function (a, b) { 
	return a + b
};

add(1, 1) // 2
add(10, 3) // 13

너무나 사용하기가 편하다. 자바도 이렇게 사용하면 편하고 재사용성도 좋을 것 같은데...

그래서 자바도 함수형 인터페이스라는 것을 만들었다. 그리고 람다라는 녀석도 만들었다.

@FunctionalInterface
public interface Add {
    int add(int a, int b);
}
public static void main(String[] args) {
    // 익명 클래스
    Add Func1 = new Add() {
        @Override
        public int add(int a, int b) {
            return a + b;
        }
    };

    System.out.println(Func1.add(1, 1)); // 2
    System.out.println(Func1.add(10, 3)); // 13

    // 람다
    Add Func2 = (a, b) -> {
        return a + b;
    };

    System.out.println(Func2.add(1, 1)); // 2
    System.out.println(Func2.add(10, 3)); // 13
}

기존의 익명 클래스 방식에서 람다 방식으로 편하게 사용이 가능해졌다.

그리고 Add라는 커스텀 함수형 인터페이스는 구현이 간단해서 기본 함수형 인터페이스(intBinaryOperator)가 존재한다.

즉 람다는 함수형 인터페이스와 셋뚜 셋뚜 이다. 또한 람다만의 특징이 있는데 변수의 참조 범위가 다르다.

public static void main(String[] args) {
    Main main = new Main();
    main.run();
}

private void run() {
    final int baseNumber = 10;

    // 로컬 클래스
    class LocalClass {
        void printBaseNumber() {
            int baseNumber = 11; // 재선언 가능하다.
            System.out.println(baseNumber);
        }
    };

    // 익명 클래스
    Consumer<Integer> integerConsumer = new Consumer<Integer>() {
        @Override
        public void accept(Integer integer) {
            int baseNumber = 11; // 재선언 가능하다.
            System.out.println(baseNumber);
        }
    };

    // 람다
    IntConsumer printInt = (i) -> {
        // int baseNumber = 11; // 재선언 불가능하다. 람다를 감싸고 있는 Scope과 같다.
        System.out.println(i + baseNumber);
    };

    printInt.accept(10);
}

마지막으로 람다는 메소드 레퍼런스를 사용하여 더욱더 간결하게 표현할 수 있다.

(아직 완벽하게 이해는 못하겠다.)

스테틱 메스드 참조 타입:스테틱 메소드
특정 객체의 인스턴스 메소드 참조 객체 레퍼런스:: 인스턴스 메소드
생성자 참조 타입::new
임의 객체의 인스턴스 메소드 참조 타입::인스턴스 메소드
public class Greeting {
    private String name;

    public Greeting() {
    }

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

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

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

    public String getName() {
        return name;
    }

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

    public static void main(String[] args) {
        // 스태틱 메소드 참조법
        Greeting greeting = new Greeting();
        UnaryOperator<String> hello = greeting::hello;
        System.out.println(hello.apply("giuly"));

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

        // 특정 객체의 인스턴스 메소드 참조
        Function<String, Greeting> funcGreeting = Greeting::new;
        Greeting str = funcGreeting.apply("giuly");
        System.out.println(str.getName());

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

}

언젠가 익숙해지겠지 ...

댓글