참고
코드 가독성 개선
NOTE
자바 8에서는 코드 가독성에 도움을 주는 다음과 같은 기능을 제공한다!
•
람다를 활용한 단축코드
•
메서드 참조, 스트림 API를 활용한 코드의 의도를 쉽게 표현
익명 클래스 → 람다 표현식 리팩토링
NOTE
// 익명 클래스를 사용한 코드
Runnable r1 = new Runnable() {
public void run() {
System.out.println("Hello");
}
};
// 람다 표현식을 사용한 코드
Runnable r2 = () -> System.out.println("Hello");
Java
복사
•
하지만 모든 익명 클래스를 람다 표현식으로 변환할 수 있는 것은 아니다.
◦
익명 클래스에서 사용한 this와 super는 람다 표현식에서는 다른 의미를 가진다.
▪
익명클래스 this → 자기자신
▪
람다 표현식 this → 감싸는 클래스
•
익명 클래스는 감싸고 있는 클래스의 변수를 가릴 수 있다. 하지만 람다는 불가능하다.
int a = 10;
// 람다 표현식(외부에 a가 선언되서 불가)
Runnable r1 = () -> {
int a = 2; // 컴파일 에러!!!!
System.out.println("Hello");
};
// 익명 클래스 ( 함수를 선언하고 내부에 작성하기에 a사용 가능)
Runnable r2 = new Runnable() {
public void run() {
int a = 2;
System.out.println("Hello");
}
};
Java
복사
•
익명 클래스를 람다 표현식으로 바꾸면 오버로딩에 따른 모호함이 초래될 수 있다.
interface Task {
public void execute();
}
// 같은 타입을 다른 람다 표현식으로 구현
public static void doSomething(Runnable r){ r.run(); }
public static void doSomething(Task a){ a.execute(); }
// Task를 구현하는 익명 클래스를 전달할 수 있다.
doSomething(new Test() {
public void execute() {
System.out.println("Danger danger!!");
}
});
// 람다 표현식으로는 어떤 인터페이스를 사용하는지 알 수 없다.
doSomeThing(() -> System.out.println("Danger danger!!"));
// 명시적 형변환을 이용해서 모호함을 제거할 수 있다.
doSomeThing((Task)() -> System.out.println("Danger danger!!"));
Java
복사
람다 표현식 → 메서드 참조 리팩토링
NOTE
// 람다로 인해 코드가 길어짐
Map<CaloricLevel, List<Dish>> dishedByCaloricLevel =
menu.stream()
.collect(
groupingBy(dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
}));
// 외부 함수를 사용해서 코드단
Map<CaloricLevel, List<Dish>> dishedByCaloricLevel =
menu.stream()
.collect(
groupingBy(dish::getCaloricLevel));
Java
복사
람다가 아닌, 외부에서 함수를 작성해서 사용하게 하는 것
// 비교 구현에 신경써야 한다.
inventory.sort(
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
// 코드가 문제 자체를 설명한다.
inventory.sort(comparing(Apple::getWeight));
Java
복사
자주 사용되는 리듀싱 연산은 내장 헬퍼 메서드(comparing, maxBy)를 사용하는것이 좋다
명령형 데이터 → 스트림 리팩토링
NOTE
스트림은 쇼트서킷, 게으름이라는 최적화뿐 아니라, 멀티코어 아키텍쳐를 활용할 수 있는 지름길을 제공한다!
•
하지만 명령형 코드의 break, continue, return 등의 제어 흐름문을 모두 분석해서 같은 기능으로 바꾸는건 쉬운일이 아니다.
•
람다 표현식을 사용하기 위해선 함수형 인터페이스가 필요하다. 따라서 함수형 인터페이스를 코드에 추가해야 한다.
// 조건이 참일 경우에만 로그 메시지를 작성하도록 함
if (logger.isLoggable(Log.FINER)) {
logger.finer("Problem: " + generateDiagnostic());
}
Java
복사
조건부 연기 실행
- logger의 상태가 isLoggable이라는 메서드에 의해 클라이언트에게 노출된다.
- 로깅마다 logger의 상태를 매번 확인해야 하나?
public void log(Level level, Supplier<String> msgSupplier){
if(logger.isLoggable(level)){
log(level, msgSupplier.get()); // 람다 실행
}
}
Logger.log(Level.FINER, () -> "Problem: " + generateDiagnostic());
Java
복사
로깅 이전에 미리 적절한 수준인지 내부적으로 확인하게한다.
람다로 객체지향 디자인 패턴 리팩토링 하기
전략 패턴
NOTE
한 유형의 알고리즘을 보유한 상태에서 런타임에 적절한걸로 선택하는 기법!
// 알고리즘을 나타내는 인터페이스
public interface ValidationStrategy {
boolean execute(String s);
}
// 다양한 알고리즘을 나타내는 1개 이상의 인터페이스 구현 클래스
public class IsAllLowerCase implements ValidationStrategy {
public boolean execute(String s) {
return s.matches("[a-z]+");
}
}
public class IsNumeric implements ValidationStrategy {
public boolean execute(String s) {
return s.matches("\\d+");
}
}
// 전략 객체를 사용하는 클라이언트
public class Validator {
private final ValidationStrategy strategy;
public Validator(ValidationStrategy strategy) {
this.strategy = strategy;
}
public boolean validate(String s) {
// 어떤 전략을 구현했냐에 따라 동작이 달라짐
return this.strategy.execute(s);
}
}
// 실제 사용코드
Validator numericValidator = new Validator(new IsNumeric());
boolean b1 = numericValidator.validate("aaaa"); // false
Validator lowerCaseValidator = new Validator(new IsAllLowerCase());
boolean b2 = lowerCaseValidator.validate("bbbb"); // true
Java
복사
Validator numericValidator = new Validator((String s) -> s.matches("[a-z]+"));
boolean b1 = numericValidator.validate("aaaa"); // true
Validator lowerCaseValidator = new Validator((String s) -> s.matches("\\d+"));
boolean b2 = lowerCaseValidator.validate("bbbb"); // false
Java
복사
전략 구현체를 만들지 않고 람다 표현식으로 생성!
템플릿 메서드 패턴
NOTE
알고리즘의 개요를 제시한 다음에 알고리즘의 일부를 고칠 수 있는 기능을 제공한다!
abstract class OnlineBanking {
public void processCustomer(int id) {
Customer c = Database.getCustomerWithId(id);
makeCustomerHappy(c);
}
abstract void makeCustomerHappy(Customer c);
}
Java
복사
온라인 뱅킹 애플리케이션 동작코드
⇒ makeCustomerHappy를 구현방식에 따라 다르게 동작시킬 수 있음
public class OnlineBankingLamda {
public void processCustomer(int id, Consumer<Customer> makeCustomerHappy) {
Customer c = Database.getCustomerWithId(id);
makeCustomerHappy.accept(c);
}
}
new OnlineBankingLamda().processCustomer(1337, (Customer c) ->
System.out/println("Hello "+ c.getName());
Java
복사
추상 메서드가 아닌, 함수객체를 인자로 받아서 람다로 구현한다!
옵저버 패턴
NOTE
이벤트 발생했을 때 한 객체(주제 subject)가 다른 객체 리스트(옵저버)에 자동으로 알림을 보내주는 패턴!
불나면 알람보내
interface Observer {
void notify(String tweet);
}
// 트윗에 포함된 다양한 키워드에 다른 동작을 수행할 수 있는 여러 옵저버를 정의할 수 있다.
class NYTimes implements Observer {
public void notify(String tweet) {
if(tweet != null && tweet.contains("money")) {
System.out.println("Breaking news in NY! " + tweet);
}
}
}
class Guardian implements Observer {
public void notify(String tweet) {
if(tweet != null && tweet.contains("queen")) {
System.out.println("Yet another new in London... " + tweet);
}
}
}
class LeMonde implements Observer {
public void notify(String tweet) {
if(tweet != null && tweet.contains("wine")) {
System.out.println("Today cheese, wine and news! " + tweet);
}
}
}
// Subject 인터페이스의 정의다.
interface Subject {
void registerObserver(Observer o);
void notifyObservers(String tweet);
}
class Feed implements Subject {
private final List<Observer> observers = new ArrayList<>();
// 새로운 옵저를 등록한다.
public void registerObserver(Observer o) {
this.observers.add(o);
}
// 트윗을 등록한 옵저들에게 알린다.
public void notifyObservers(String tweet) {
observers.forEach(o -> o.notify(tweet));
}
}
Feed f = new Feed();
// 구독할 옵저버를 등록한다.
f.registerObserver(new NYTimes());
f.registerObserver(new Guardian());
f.registerObserver(new LeMonde());
// 옵저버들에게 메시지를 전송한다.
f.notifyObservers("The queen said her favorite book is Java 8 in Action!");
Java
복사
Feed f = new Feed();
f.registerObserver((String tweet) {
if(tweet != null && tweet.contains("money")) {
System.out.println("Breaking news in NY! " + tweet);
}
});
f.registerObserver((String tweet) {
if(tweet != null && tweet.contains("queen")) {
System.out.println("Yet another new in London... " + tweet);
}
});
Java
복사
팩토리 패턴
NOTE
인스턴스화 로직을 클라이언트에 노출하지 않고 객체를 만들 때 팩토리 패턴을 사용한다!
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("No such product " + name);
}
}
}
Java
복사
new 로직이 클라이언트에게 노출되지 않는다!
Supplier<Product> loanSupplier = Loan::new;
Loan loan = loanSupplier.get();
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);
}
Java
복사
현재 경우는 생성자에 파라미터가 없어서 쉽게 되지만 파라미터가 있다면 인터페이스로는 불가능