시작하기 앞서
람다라는 녀석을 써본적은 있다. 그러나 공부해 본 적은 없다. 그냥 구글에 굴러다니는 코드를 복사 붙여넣기하고 대충 변수명만 바꿔서 사용했다. 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>
인터페이스를 정의하여 생성할 수 있다.
'자바' 카테고리의 다른 글
[모던 자바 인 액션] 전략 디자인 패턴(strategy) (0) | 2020.01.04 |
---|---|
[모던 자바 인 액션] 리팩터링, 테스팅, 디버깅 (0) | 2020.01.04 |
[모던 자바 인 액션] 자바 병렬처리 (0) | 2020.01.02 |
[모던 자바 인 액션] 스트림 (0) | 2019.12.30 |
[모던 자바 인 액션] 동작 파라미터화 코드 전달하기 (0) | 2019.12.28 |
최근댓글