Upload
jiandson
View
44
Download
0
Embed Size (px)
Citation preview
나에 첫번째 자바8 람다식 - @blueiur(twitter)
오! 벌써 새벽 3시네, 컴퓨터 끄고 자야겠어!
한 시간 후 ...
정대원@blueiur(twitter)● like ..
○ programming language○ functionl programming○ elixir○ scala○ ruby
람다
● 람다 계산법● 익명 함수● 함수 리터럴● 클로저
익명 함수Wikipedia
● 특정 식별자 없이 정의되거나 호출될 수 있는 함수
람다가 왜 필요할가?● 행위 매개변수(코드 블럭) 전달
// 1. 컬렉션 정렬 (익명 클래스 사용)
Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return a.compareTo(b); }});
컬렉션 정렬 - 익명 클래스 사용
// 1. 컬렉션 정렬 (익명 클래스 사용)Collections.sort(names, ...);
Collections.sort + a.compareTo(b)
new Comparator<String>() { @Override public int compare(String a, String b) { return a.compareTo(b); }}
// 1. 컬렉션 정렬 (익명 클래스 사용)Collections.sort(names, ...);
Collections.sort + [a.compareTo(b), b.compareTo(a)]
new Comparator<String>() { @Override public int compare(String a, String b) { return a.compareTo(b); }}
new Comparator<String>() { @Override public int compare(String a, String b) { return b.compareTo(a); }}
새 쓰레드 생성 - 익명 클래스 사용
// 쓰레드 생성 (익명 클래스 사용)new Thread(new Runnable() { @Override public void run() { System.out.println("I consume memory, therefore i am!"); } }).start();
// 1. 쓰레드 생성 (익명 클래스 사용)new Thread(...);
new Thread(...) + System.out.println("...");
new Thread(new Runnable() { @Override public void run() { System.out.println("I consume memory, therefore i am!"); } }).start();
// 1. 쓰레드 생성 (익명 클래스 사용)new Thread(...);
new Thread(...) + [System.out.println("..."), DB.write("...")]
new Thread(new Runnable() { @Override public void run() { System.out.println("I consume memory, therefore i am!"); } }).start();
new Thread(new Runnable() { @Override public void run() { DB.write("I consume memory, therefore i am!"); } }).start();
행위 매개변수 사용에 장점● 고정된 코드 + 행위 매개변수(익명 클래스) 조합을 사용한 다양한 확장
a. 더 일반화된 메서드b. 더 유연한 인터페이스 c. 코드 중복 제거
요구사항 - 리스트 정렬
● 정수형 리스트 정렬● 문자열 리스트 정렬● comparable을 상속받지 않은 객체 정렬
a. 서로 다른 필드를 기준으로 정렬(ex: price, name)
public static <T extends Comparable<T>> List<T> sort(List<T> list) { ArrayList<T> ls = new ArrayList<>(list); for(int i=0; i<ls.size(); i++) { int minIndex = i; for(int j=i+1; j<ls.size(); j++) { if (ls.get(j).compareTo(ls.get(minIndex)) < 0) { minIndex = j; } } T tmp = ls.get(i); ls.set(i, ls.get(minIndex)); ls.set(minIndex, tmp); } return ls;}
sort(Arrays.asList("c", "b", "d", "a"))sort(Arrays.asList(1, 3, 2, 4))
Generic을 사용한 정렬 구현
comparable을 상속
요구사항 - 리스트 정렬
● 정수형 리스트 정렬● 문자열 리스트 정렬● comparable을 상속받지 않은 객체 정렬
a. 서로 다른 필드를 기준으로 정렬(ex: price, name)
정렬 기준을 변경하고 싶다
sort(Arrays.asList(k7, k5, k3, i30), 가격 순으로 정렬)sort(Arrays.asList(k7, k5, k3, i30), 이름 순으로 정렬)
public class Car { public String name; public int price;
public Car(String name, int price) { this.name = name; this.price = price; }}
public static <T extends Comparable<T>> List<T> sort(List<T> list) { ArrayList<T> ls = new ArrayList<>(list); for(int i=0; i<ls.size(); i++) { int minIndex = i; for(int j=i+1; j<ls.size(); j++) { if (ls.get(j).compareTo(ls.get(minIndex)) < 0) { minIndex = j; } } T tmp = ls.get(i); ls.set(i, ls.get(minIndex)); ls.set(minIndex, tmp); } return ls;}
코드 분석
값 2개를 비교해서 [-1, 0, 1]중 하나를 반환
public static <T extends Comparable<T>> List<T> sort(List<T> list) { ArrayList<T> ls = new ArrayList<>(list); for(int i=0; i<ls.size(); i++) { int minIndex = i; for(int j=i+1; j<ls.size(); j++) { if (ls.get(j).compareTo(ls.get(minIndex)) < 0) { minIndex = j; } } T tmp = ls.get(i); ls.set(i, ls.get(minIndex)); ls.set(minIndex, tmp); } return ls;}
인터페이스로 분리
interface Comparator<T> { int compare(T a, T b);}
값 2개를 비교해서 [-1, 0, 1]중 하나를 반환
public static <T> List<T> sort(List<T> list, Comparator<T> comp) { ArrayList<T> ls = new ArrayList<>(list); for(int i=0; i<ls.size(); i++) { int minIndex = i; for(int j=i+1; j<ls.size(); j++) { if (comp.compare(ls.get(j),ls.get(minIndex) < 0) { minIndex = j; } } T tmp = ls.get(i); ls.set(i, ls.get(minIndex)); ls.set(minIndex, tmp); } return ls;}
Comparator 인터페이스를 사용
interface Comparator<T> { int compare(T a, T b);}
값 2개를 비교해서 [-1, 0, 1]중 하나를 반환
// 2. 이름으로 정렬sort(cars, new Comparator<Car>() { @Override public int compare(Car a, Car b) { return a.name.compareTo(b.name); }});
익명 클래스를 사용한 행동 전달
// 1. 가격으로 정렬sort(cars, new Comparator<Car>() { @Override public int compare(Car a, Car b) { return a.price.compareTo(b.price); }});
다른 부분가격/이름 으로 비교
요구사항 - 리스트 정렬● 정수형 리스트 정렬● 문자열 리스트 정렬● comparable을 상속받지 않은 객체 정렬
a. 서로 다른 필드를 기준으로 정렬(ex: price, name)
행위 매개변수 사용에 장점● 고정된 코드 + 행위 매개변수(코드 블럭) 조합을 사용한 다양한 확장
a. 더 일반화된 메서드b. 더 유연한 인터페이스 c. 코드 중복 제거
람다가 왜 필요할가?● 행위 매개변수(코드 블럭) 전달● 자바8 이전에는 익명 클래스를 사용
익명 함수Wikipedia
● 특정 식별자 없이 정의되거나 호출될 수 있는 함수● 자바에 함수가 있나? -> 람다!
(인자 목록) -> { 구문 }● x -> x + 1● (x) -> x + 1● (int x) -> x + 1● (int x, int y) -> x + y● (x, y) -> { System.out.println(x + y) }● () -> { System.out.println("runnable!"); }
람다 문법
@Functional Interface● 추상 메서드가 1개 뿐인 인터페이스● 인터페이스를 함수처럼 사용하자
@FunctionalInterfaceinterface Action { void run(String param); void stop(String param);}
@FunctionalInterfaceinterface Runnable() { void run();}
메서드 1개 OK!
메서드 2개 NO!
익명 클래스를 람다로 변환해 주는 IntelliJ
// 1. 람다 사용 (자바8)Collections.sort(names, (a, b) -> a.compareTo(b));
// 1. 익명 클래스 사용 (자바8 이전)
Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return a.compareTo(b); }});
행위 매개변수 전달: 클래스 -> 람다
1:1 대응
interface Comparator<T> { int compare(T a, T b);}
함수형 인터페이스를 사용한 정렬
// 1. 익명 클래스 사용 (자바8 이전)
Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return a.compareTo(b); }});
// 1. 익명 클래스 사용 (자바8 이전)
Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return a.compareTo(b); }});
interface Comparator<T> { int compare(T a, T b);}
불필요한 객체 생성 제거, 메서드도 1개뿐이니 별도 이름 불필요
객체 이름 제거메서드 이름 제거
// 1. 익명 클래스 사용 (자바8 이전)
Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return a.compareTo(b); }});
interface Comparator<T> { int compare(T a, T b);}
반환 타입과 파라미터 타입도 이미 정해져 있으니 제거
객체 이름 제거메서드 이름 제거반환 값 및 파라미터 타입 추론
// 1. 람다 사용 (자바8)Collections.sort(names, (a, b) -> { return a.compareTo(b); });
비슷하다
// 1. 익명 클래스 사용 (자바8 이전)
Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return a.compareTo(b); }});
람다 문법: (인자 목록) -> { 구문 }● x -> {return x * 2}● x -> x * 2● (int x) -> x + 1● (int x, int y) -> x + y● (x, y) -> { System.out.println(x + y) }● () -> { System.out.println("runnable!"); }
실행문이 1개인 경우 {} 와 return 키워드 생략 가능
// 1. 람다 사용 (자바8)Collections.sort(names, (a, b) -> a.compareTo(b));
익명 클래스 -> 람다
// 1. 익명 클래스 사용 (자바8 이전)
Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return a.compareTo(b); }});
@FunctionalInterfaceinterface Adder { int add(int a, int b);}
????? func = (int a, int b) -> { return a + b };????? shortFunc = (a, b) -> a + b;
람다 타입
@FunctionalInterfaceinterface Adder { int add(int a, int b);}
Adder func = (int a, int b) -> { return a + b };Adder shortFunc = (a, b) -> a + b;
람다 타입
Q: 람다는 단순히 익명 클래스에 문법 치환인가?
A: No! 실제로는 익명 클래스에 비효율을 제거하기 위해서 사용
invoke dynamic
interface Comparator<T> { int compare(T a, T b);}
함수형 인터페이스 - 추상 메서드를 1개만 들고 있다
@FunctionalInterfaceinterface Runnable<T> { void run();}
???
@FunctionalInterface 어노테이션은 붙이는 이유?● 컴파일러가 추상 메서드가 2개인 경우 컴파일 오류 발생● Javadoc에 @FunctionalInterface 글 추가
Target typing● 람다 -> 익명 클래스 변환시 가장 유사한 타입을 찾아가는것
public static <X, Y> void processElements( Iterable<X> source, Predicate<X> tester, Function <X, Y> mapper, Consumer<Y> block) { for (X p : source) { if (tester.test(p)) { Y data = mapper.apply(p); block.accept(data); } }}
Target typing
processElements( roster, p -> p.getGender() == Person.Sex.MALE // Predicate p -> p.getEmailAddress(), // Function email -> System.out.println(email) // Consumer);
람다 두개 모두 같은 모습이지만알아서 잘 찾아간다
Target typing and Method arguments
predicate가 왜 필요할가?
Function<String, Boolean> isDaewon = s -> "daewon".equals(s);Predicate<String> isDaewon = s -> "daewon".equals(s);
Target typing and Method arguments
interface Runnable { void run();}
interface Callable<V> { V call();}
void invoke(Runnable r) { r.run();}
<T> T invoke(Callable<T> c) { return c.call();}
invoke(() -> {});invoke(() -> "done");
Target typing and Method arguments
interface Runnable { void run();}
interface Callable<V> { V call();}
void invoke(Runnable r) { r.run();}
<T> T invoke(Callable<T> c) { return c.call();}
invoke(() -> {});invoke(() -> "done");
반환값을 참고해서오버로드된 메서드도 잘 찾아간다
변수 포획
public static void thread(String msg) { int tmp = 10; new Thread( () -> System.out.println(msg) ).start(); }
람다 내부에 선언되지 않은 변수를 참조할 수 있다● 자유변수: 나를 감싸고 있는 유효 범위 변수
a. String msg, int tmp
자신은 감싸고 있는 유효 범위 변수에 접근 가능
public static void thread(String msg) { new Thread( () -> { msg = "must be final"; System.out.println(msg) ).start(); }}
컴파일 에러: 포획된 변수를 수정
포획된 변수는 언제나 final이여야 한다
Function<String, Predicate<String>> startsWithFactory = s1 -> { return (s2) -> s2.indexOf(s1) > -1;};
Predicate<String> isIncludeGoogle = startsWithFactory.apply("google");Predicate<String> isIncludeApple = startsWithFactory.apply("apple");
System.out.println(isIncludeApple.test("microsoft.com apple.com")); // trueSystem.out.println(isIncludeGoogle.test("microsoft.com apple.com")); // false
변수 참조를 활용해서 동적으로 새로운 람다를 생성하는 람다
자신은 감싸고 있는 유효 범위 변수에 접근 가능
미리 정의된 함수형 인터페이스● 람다를 사용하려면 항상 인터페이스를 필요할가? ● 람다 사용시 새로운 인터페이스를 매번 만들어야 하나?
미리 정의된 함수형 인터페이스● 람다를 사용하려면 항상 인터페이스를 필요할가? - yes● 람다 사용시 새로운 인터페이스를 매번 만들어야 하나? - no(반만 yes)
Car price price > 3000 sum
cars.stream().map(c -> c.gerPrice()).filter(p -> p > 3000).reduce((a, b) -> a + b));
람다를 위해 3개 인터페이스 필요
interface Function interface Predicate interface BinaryOperator
미리 정의된 함수형 인터페이스
IntPredicateIntSupplierIntToDoubleFunctionIntToLongFunctionIntUnaryOperatorLongBinaryOperatorLongConsumerLongFunction<R>LongPredicateLongSupplierLongToDoubleFunctionLongToIntFunctionLongUnaryOperatorObjDoubleConsumer<T>ObjIntConsumer<T>ObjLongConsumer<T>Predicate<T>Supplier<T>
BiConsumer<T,U>BiFunction<T,U,R>BinaryOperator<T>BiPredicate<T,U>BooleanSupplierConsumer<T>DoubleBinaryOperatorDoubleConsumerDoubleFunction<R>DoublePredicateDoubleSupplierDoubleToIntFunctionDoubleToLongFunctionDoubleUnaryOperatorFunction<T,R>IntBinaryOperatorIntConsumerIntFunction<R>
지연 연산
// 디버그 모드에서만 실행public void debug(String message) { if (log.isDebugEnabled()) { log.log(message); }}
debug(some.expensive("operation"));
디버그 모드에서만 동작하는 함수
디버그 모드에서는 동작
// 디버그 모드에서만 실행public void debug(String message) { if (log.isDebugEnabled()) { log.log(message); }}
debug(some.expensive("operation"));
평가 시점
함수 인자는 호출 시점에 평가가 된다
// 디버그 모드에서만 실행public void debug(Consumer<String> consumer) { if (log.isDebugEnabled()) { log.log(consumer.accept()); }}
debug(() -> some.expensive("operation"));
람다로 평가 시점 조절
람다를 사용해서 평가를 뒤로 미룬다
// 람다가 없는 경우debug(some.expensive("operation"));
// 람다 사용debug(() -> some.expensive("operation"));
호출 하는 쪽이 조금 불편해 졌다
문제 다시 보기● 빌려쓰기 패턴
public void withFile(String fileName) { BufferedReader bufferedReader = null; try { bufferedReader = new BufferedReader(new FileReader(filename)); String line = null; while ((line = bufferedReader.readLine()) != null) { System.out.println(line); } } catch (IOException ex) { ex.printStackTrace(); } finally { if (bufferedReader != null) bufferedReader.close(); }}
파일을 한 라인씩 읽어서 STDOUT으로 출력
public void withFile(String fileName) { BufferedReader bufferedReader = null; try { bufferedReader = new BufferedReader(new FileReader(filename)); String line = null; while ((line = bufferedReader.readLine()) != null) { DB.write(line); } } catch (IOException ex) { ex.printStackTrace(); } finally { if (bufferedReader != null) bufferedReader.close(); }}
파일을 한 라인씩 읽어서 DB에 저장
public void withFile(String fileName) { BufferedReader bufferedReader = null; try { bufferedReader = new BufferedReader(new FileReader(filename)); String line = null; while ((line = bufferedReader.readLine()) != null) { // 이 부분 외 모두 중복! } } catch (IOException ex) { ex.printStackTrace(); } finally { if (bufferedReader != null) bufferedReader.close(); }}
중복 발생!
DB.write(line);
System.out.println(line);
현재 라인으로 무엇인가를 처리
public void withFile(String fileName) { BufferedReader bufferedReader = null; try { bufferedReader = new BufferedReader(new FileReader(filename)); String line = null; while ((line = bufferedReader.readLine()) != null) { // 이 부분 외 모두 중복! } } catch (IOException ex) { ex.printStackTrace(); } finally { if (bufferedReader != null) bufferedReader.close(); }}
중복 발생!
interface Consumer<T> { void accept(T a); }
DB.write(line);System.out.println(line);
어떤 값을 받아서 소비한다.
interface Consumer<T> { void accept(T a); }
각 라인을 인자로 넘겨 받아서 소비한다 public void accept(String line) { System.out.println(line); }
class DBWorker class PrintWorker class DBAndPrintWorker
interface Consumer
클래스로 구현
interface Consumer<T> { void accept(T a); }
class DBWorker implements Consumer<String> { public void accept(String line) { db.store(line); } }
class PrintWorker implements Consumer<String> { public void accept(String line) { System.out.println(line); } };
class DBAndPrintWorker implements Consumer<String> { public void accept(String line) { db.store(line); System.out.println(line); } };
withFile("input.txt", new DBWorker()); withFile("input.txt", new PrintWorker()); withFile("input.txt", new DBAndPrintWorker());
다양한 종류에 클래스 구현
interface Consumer<T> { void accept(T a);}
withFile("input.txt", new Consumer<String> { public void accept(String line) { db.store(line); } });
withFile("input.txt", new Consumer<String> { public void accept(String line) { System.out.println(line); } });
withFile("input.txt", new Consumer<String> { public void accept(String line) { db.store(line); System.out.println(line); } });
익명 클래스 사용
interface Consumer<T> { void accept(T a);}
메서드 딱 1개!
함수형 인터페이스
withFile("input.txt", new Consumer<String> { public void accept(String line) { db.store(line); } });
withFile("input.txt", new Consumer<String> { public void accept(String line) { System.out.println(line); } });
withFile("input.txt", new Consumer<String> { public void accept(String line) { db.store(line); System.out.println(line); } });
interface Consumer<T> { void accept(T a);}
익명 클래스 사용
withFile("input.txt", new Consumer<String> { public void accept(String line) { db.store(line); } });
withFile("input.txt", new Consumer<String> { public void accept(String line) { System.out.println(line); } });
withFile("input.txt", new Consumer<String> { public void accept(String line) { db.store(line); System.out.println(line); } });
interface Consumer<T> { void accept(T a);}
중복되는 객체 이름 제거
withFile("input.txt", new Consumer<String> { public void accept(String line) { db.store(line); } });
withFile("input.txt", new Consumer<String> { public void accept(String line) { System.out.println(line); } });
withFile("input.txt", new Consumer<String> { public void accept(String line) { db.store(line); System.out.println(line); } });
interface Consumer<T> { void accept(T a);}
중복되는 메서드 이름 제거
withFile("input.txt", new Consumer<String> { public void accept(String line) { db.store(line); } });
withFile("input.txt", new Consumer<String> { public void accept(String line) { System.out.println(line); } });
withFile("input.txt", new Consumer<String> { public void accept(String line) { db.store(line); System.out.println(line); } });
interface Consumer<T> { void accept(T a);}
실제로 다른 부분은 함수 본체
interface Consumer<T> { void accept(T a);}
람다 사용
withFile("input.txt", line -> db.store);withFile("input.txt", line -> System.out.println);withFile("input.txt", line -> {
db.store(line);System.out.println(line);
});
interface Consumer<T> { void accept(T a); }
class DBWorker implements Consumer<String> { public void accept(String line) { db.store(line); } }
class PrintWorker implements Consumer<String> { public void accept(String line) { System.out.println(line); } };
class DBAndPrintWorker implements Consumer<String> { public void accept(String line) { db.store(line); System.out.println(line); } };
// 클래스 사용 withFile("input.txt", new DBWorker()); withFile("input.txt", new PrintWorker()); withFile("input.txt", new DBAndPrintWorker());
불필요 코드가 많이 사라짐
// 람다 사용withFile("input.txt", line -> db.store);withFile("input.txt", line -> System.out.println);withFile("input.txt", line -> {
db.store(line);System.out.println(line);
});
감사합니다. - @blueiur(twitter)