시작하기 앞서

람다라는 녀석을 써본적은 있다. 그러나 공부해 본 적은 없다. 그냥 구글에 굴러다니는 코드를 복사 붙여넣기하고 대충 변수명만 바꿔서 사용했다. JS 진영의 Arrow Function도 그렇게 사용은 많이 했다. 그러나 공부해 본 적은 없다. 이번 기회에 모던 자바 인 액션을 읽고 제대로 파헤쳐보자

바로 코드를 보자

우테코 프리코스 3주차에 작성한 코드이다. 이 코드를 함께 살펴보자.

private boolean isContainAce(User user) {
        boolean isContain = false;
        for (Card card : user.getCards()) {
            isContain = card.getSymbolByString().equals(ACE_TYPE);
        }
        return isContain;
}

User 객체에 대해서 설명을 하자면 User는 Card 객체의 List를 포함하고 있다. Card 객체는 트럼프 카드로서 ACE, DIAMOND 같은 그림과 1~10까지의 숫자, 킹,잭,퀸의 값을 가진다. 그리고 위의 메소드는 메소드명 그대로 user의 Card중에서 ACE가 있는지 확인하는 메소드이다. 위의 코드를 자바 8의 stream과 람다를 활용하면 아래와 같이 바꿀 수 있다.

private boolean isContainAce(User user) {
        return user.getCards().stream().anyMatch(
        (card) -> card.getSymbolByString().equals(ACE_TYPE)
        );
}

위의 코드는 stream의 anyMatch라는 메소드에 함수형 인터페이스 Predicate를 매개변수로 받기 때문에 람다를 사용해서 불필요한 isContain변수를 제거하고 코드도 한 줄로 줄일 수 있었다. 이전에 포스팅했던 동작 파라미터화랑도 좀 비슷한거 같다.

람다란?

람다 표현식은 메소드에 전달할 수 있는 익명 함수이다. 메소드 명을 짓는게 아주 골치아팠는데 잘됐다. 이름은 없지만 파라미터 리스트, 바디, return 값, 예외 리스트를 가질 수 있다.
람다는 익명이며 함수이다. 그리고 다른 메서드에 인수로 전달하거나 변수에 저장할 수 있다.

람다와 함수형 인터페이스

이전에 포스팅했던 동작 파라미터화에서 말한 Predicate같은 함수형 인터페이스에 람다 표현식을 전달할 수 있다. (함수형 인터페이스는 추상 메서드가 오직 하나인 인터페이스이다.)
이번에는 Predicate가 아닌 Runnable이라는 함수형 인터페이스를 알아볼 것이다. Runnalble은 이렇게 구현되어 있다.

public interface Runnable {
    void run();
}

이제 이 인터페이스에 람다 표현식을 전달해보자.

 process(() -> System.out.println("Hello, World!")); 

실행결과는 당연히 Hello,World!이다. 람다는 변수에 저장할 수 있는 특성을 이용해서 이렇게도 사용할 수 있다.

Runnable r1 = () -> System.out.println("World, Hello!");
process(r1);

실행결과는 당연히 World, Hello!이다. 람다 표현식을 통해서 간결하게 반복을 피해 다른 동작을 수행할 수 있다.

메소드 참조

메소드 참조를 이용하면 기존에 작성했던 메소드 정의를 재활용해서 람다 표현식처럼 전달할 수 있다. 람다와 비교해 가독성이 더 좋으며 메소드명을 통해 의도를 드러낼 수 있다. 내가 우테코 3주차 미션을 수행하면서 작성했던 코드를 리팩토링하며 메서드 참조를 알아보자.

 int score = user.getCards().stream().mapToInt(x -> x.getSymbolByScore()).sum(); 

위의 코드는 user 객체가 가지고 있는 card들의 int형 점수를 모두 더하여 return하는 코드이다. 람다와 stream을 사용하여 잘 작성한 코드인 것 같다.

 int score = user.getCards().stream().mapToInt(Card::getSymbolByScore).sum(); 

위의 코드는 람다를 매소드 참조로 바꾼 코드이다. 클래스명::메소드명의 문법을 띄며 함수를 호출하는 것이 아니기 때문에 함수명()이 아니다. 괄호가 제거된다.

생성자 참조

메소드 참조를 넘어 생성자도 참조할 수 있다. 이 부분은 아직 책을 읽어도 잘 이해하지 못했다. 그런데 우테코에서 객체들을 생성할 때 반복되는 부분은 분명히 존재하는데 어떻게 해결해야 하는지 감이 안잡히던 부분이 있었다. 그 부분을 해결할 수 있을 것 같다. 시간을 두고 천천히 살펴볼 예정이다. 2019.12.29 작성 오늘 바로 살펴보자. 내가 작성한 코드를 리팩토링하며 생성자 참조를 사용해보자. 내 코드를 보자.

private void createPlayer(List nameList) {
    int i = 0;
    for (String name : nameList) {
        announce.announceMoneyInput(name);
        players[i] = new Player(
                name, moneyinput.inputBettingMoney());
        System.out.println();
        cardManager.giveCard(players\[i\], MAKE\_TWO\_CARD);
        i++;
    }
} 

상당히 부끄러운 코드지만 매소드의 동작을 설명을 해보자면 List을 매개변수로 받고 forEach문을 통해 player 객체 배열에 bettingMoney를 입력받아서 생성자로 넘겨주며 Player 객체를 하나씩 생성한다. 물론 생성자는 Player(int i, double d)로 선언이 되어있다. int i = 0이라는 변수를 따로 운용해야하는 것도 상당히 마음에 들지 않는다. 지금보니 메소드가 너무 많은 일을 하기도하고 여러가지로 정말 못봐줄 코드이다. 어쨌든 내가 생성자 참조를 사용하여 리팩토링해보고자 한다.

    private void createPlayer(List<String> nameList) {
        List<Player> player = map(nameList, Player::new);
    }

    private List<Player> map(List<String> nameList, Function<String, Player> f) {
        List<Player> result = new ArrayList<>();

        // version lambda
        nameList.forEach(x -> result.add(f.apply(x)));

        // version forEach
        for (String name : nameList) {
            result.add(f.apply(name));
        }

        return result;
    }

위와 같이 함수형인터페이스 Function<T, R>을 사용하여 map을 정의하고 createPlayer 매소드에서 Player::new로 생성자를 참조함으로써 코드의 복잡성을 줄이고 간결하게 코드를 짤 수 있다. 1차로 리팩토링한 코드는 bettingMoney에 대한 생성은 하고 있지 않다. BiFunction<T, U, R>을 이용하여 bettingMoney도 함께 생성할 수 있다. 또는 또 다른 Function<T, R> 인터페이스를 정의하여 생성할 수 있다.

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 라이프코리아트위터 공유하기
  • shared
  • 카카오스토리 공유하기