일반적으로 코드 가독성이 좋다는 것은 '어떤 코드를 다른 사람이 보았을 때 쉽게 이해할 수 있음'을 의미한다.
여러가지 방법들이 있지만 세 가지 리팩터링 예제를 알아보자
익명 클래스를 람다 표현식으로 리팩터링하기
람다 표현식을 메서드 참조로 리팩터링하기
명령형 데이터 처리를 스트림으로 리팩터링하기
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
};
Runnable r2 = () -> System.out.println("hello");
익명 클래스 자신
을 가리키지만 람다에서 this는 람다를 감싸는 클래스
를 가리킨다.shadow variable
int a = 10;
Runnable r1 = new Runnable() {
@Override
public void run() {
int a = 2;
System.out.println(a);
}
};
Runnable r2 = () -> {
int a = 2; // error
System.out.println(a);
};
interface Task {
void execute();
}
public static void doSomething(Runnable r) { r.run(); }
public static void doSomething(Task t) { t.execute(); }
조건부 연기 실행
과 실행 어라운드
패턴을 살펴보자
디자인 패턴에 람다 표현식이 더해지면 색다른 기능을 발휘할 수 있다.
기존 패턴
@FunctionalInterface
public interface ValidationStrategy {
boolean execute(String s);
}
public class IsAllLowerCase implements ValidationStrategy {
@Override
public boolean execute(String s) {
return s.matches("[a-z]+");
}
}
public class isNumeric implements ValidationStrategy {
@Override
public boolean execute(String s) {
return s.matches("\d+");
}
}
public class Validator {
private final ValidationStrategy validationStrategy;
public Validator(ValidationStrategy validationStrategy) {
this.validationStrategy = validationStrategy;
}
public boolean validate(String s) {
return validationStrategy.execute(s);
}
}
Validator numericValidator = new Validator(new isNumeric());
numericValidator.validate("aaa");
Validator lowerCaseValidator = new Validator(new IsAllLowerCase());
lowerCaseValidator.validate("bbbb");
람다 사용
Validator lowerCaseValidator2 = new Validator((String s) -> s.matches("[a-z]+"));
lowerCaseValidator2.validate("bbbb");
Validator numericValidator2 = new Validator((String s) -> s.matches("\d+"));
numericValidator2.validate("1234");
알고리즘의 일부
를 고칠 수 있는 유연함을 제공해야 할 때 템플릿 메서드 디자인 패턴을 사용한다.기존
abstract class OnlineBanking {
public void processCustomer(int id) {
Customer c = Database.getCustomerWithId(id);
makeCustomerHappy(c);
}
abstract void makeCustomerHappay(Customer c);
}
람다 사용
public void processCustomer(int id, Cusumer<Customer> makeCustomerHappy) {
Customer c = Database.getCustomerWithId(id);
makeCustomerHappy.accept(c);
}
new OnlineBankingLambda().processCustomer(1337, (Customer c) -> print("hello" + c.getName()));
이전에 정의한 makeCustomerHappy의 메서드 시그니처와 일치하도록 Consumer 형식을 갖는 두 번째 인수를 메서드에 추가
기존
interface Observer {
void notify(String tweet);
}
public class NyTimes implements NotiObserver {
@Override
public void notify(String tweet) {
if (tweet != null && tweet.contains("monety")) {
System.out.println("Breaking news in NY ! " + tweet);
}
}
}
public class Guardian implements NotiObserver {
@Override
public void notify(String tweet) {
if (tweet != null && tweet.contains("queen")) {
System.out.println("Yet more new from London .. " + tweet);
}
}
}
public class LeMonde implements NotiObserver {
@Override
public void notify(String tweet) {
if (tweet != null && tweet.contains("wine")) {
System.out.println("Today cheese, wine and news! " + tweet);
}
}
}
public interface NotiSubject {
void registerObserver(NotiObserver o);
void notifyObservers(String tweet);
}
public class Feed implements NotiSubject {
private final List<NotiObserver> observers = new ArrayList<>();
@Override
public void registerObserver(NotiObserver o) {
observers.add(o);
}
@Override
public void notifyObservers(String tweet) {
observers.forEach(o -> o.notify(tweet));
}
}
Feed f = new Feed();
f.registerObserver(new NyTimes());
f.registerObserver(new LeMonde());
f.registerObserver(new Guardian());
f.notifyObservers("The Queen ...")
람다
Feed feed = new Feed();
feed.registerObserver((String tweet) -> {
if(tweet != null && tweet.contains("money")) {
System.out.println("Breaking news in NY ! " + tweet);
}
});
feed.registerObserver((String tweet) -> {
if(tweet != null && tweet.contains("queen")) {
System.out.println("Yet more new from London .." + tweet);
}
});
옵저버
가 상태
를 가지며, 여러 메서드를 정의하는 등 복잡
하다면 람다 표현식보다 기존의 클래스 구현방식
을 고수하는 것이 바람직할 수 있다.작업 처리 추상 클래스
로 의무 체인 패턴을 구성한다.작업 처리 객체 예제 코드
public abstract class ProcessingObject<T> {
protected ProcessingObject<T> successor;
public void setSuccessor(ProcessingObject<T> successor) {
this.successor = successor;
}
public T handle(T input) {
T r = handleWork(input);
if (successor != null) {
return successor.handle(r);
}
return r;
}
abstract protected T handleWork(T input);
}
기존
public class HandleTextProcessing extends ProcessingObject<String> {
@Override
protected String hadleWork(String input) {
return "From Raoul, Mario and Alan : " + input;
}
}
public class SpellCheckProcessing extends ProcessingObject<String> {
@Override
protected String hadleWork(String input) {
return input.replaceAll("labda", "lambda");
}
}
ProcessingObject<String> p1 = new HandleTextProcessing();
ProcessingObject<String> p2 = new SpellCheckProcessing();
p1.setSuccessor(p2);
p1.handle("Aren't ladbas really sexy?");
람다
UnaryOperator<String> headerProcessing = (String text) -> "From Raoul, Mario and Alan : " + text;
UnaryOperator<String> spellCheckProcessing = (String text) -> text.replaceAll("labda", "lambda");
Function<String, String> pipeline = headerProcessing.andThen(spellCheckProcessing);
pipeline.apply("Aren't ladbas really sexy?");
기존
public class ProductFactory {
public static Product createProduct(String name) {
switch (name) {
case "loan" : return new Loan();
case "stock" : return new Stock();
case "bond" : return new Bond();
default: throw new RuntimeException("...");
}
}
}
Product p = ProductFactory.createProduct("loan");
람다
final static Map<String, Supplier<Product>> map = new HashMap<>();
static {
map.put("loan", Loan::new);
map.put("stock", Stock::new);
map.put("bond", Bond::new);
}
public static Product createProduct(String name) {
Supplier<Product> p = map.get(name);
if (p != null) {
return p.get();
}
throw new IllegalArgumentException("No such product: " + name);
...
}
람다 표현식을 사용하는 메서드의 동작을 테스트
함으로서 람다 표현식을 검증 할 수 있다.고차원 함수란 함수를 인수로 받거나 다른 함수를 반환하는 메서드 이다.
람다 표현식과 스트림은 기존의 디버깅 기법을 무력화한다. 디버깅 방법을 살펴보자!!