Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

나에 첫번째 자바8 람다식 지앤선

2,258 views

Published on

Published in: Software

나에 첫번째 자바8 람다식 지앤선

  1. 1. 나에 첫번째 자바8 람다식 - @blueiur(twitter)
  2. 2. 오! 벌써 새벽 3시네, 컴퓨 터 끄고 자야 겠어! 한 시간 후 ... 정대원@blueiur(twitter) ● like .. ○ programming language ○ functionl programming ○ elixir ○ scala ○ ruby
  3. 3. 람다 ● 람다 계산법 ● 익명 함수 ● 함수 리터럴 ● 클로저
  4. 4. 익명 함수 Wikipedia ● 특정 식별자 없이 정의되거나 호출될 수 있는 함수
  5. 5. 람다가 왜 필요할가? ● 행위 매개변수(코드 블럭) 전달
  6. 6. // 1. 컬렉션 정렬 (익명 클래스 사용) Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return a.compareTo(b); } }); 컬렉션 정렬 - 익명 클래스 사용
  7. 7. // 1. 컬렉션 정렬 (익명 클래스 사용) Collections.sort(names, ...); new Comparator<String>() { @Override public int compare(String a, String b) { return a.compareTo(b); } } Collections.sort + a.compareTo(b)
  8. 8. // 1. 컬렉션 정렬 (익명 클래스 사용) Collections.sort(names, ...); 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); } } Collections.sort + [a.compareTo(b), b.compareTo(a)]
  9. 9. // 쓰레드 생성 (익명 클래스 사용) new Thread(new Runnable() { @Override public void run() { System.out.println("I consume memory, therefore i am!"); } 새 쓰레드 생성 - 익명 클래스 사용 }).start();
  10. 10. // 1. 쓰레드 생성 (익명 클래스 사용) new Thread(...); new Thread(new Runnable() { @Override public void run() { System.out.println("I consume memory, therefore i am!"); } }).start(); new Thread(...) + System.out.println("...");
  11. 11. // 1. 쓰레드 생성 (익명 클래스 사용) new Thread(...); 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(); new Thread(...) + [System.out.println("..."), DB.write("...")]
  12. 12. 행위 매개변수 사용에 장점 ● 고정된 코드 + 행위 매개변수(익명 클래스) 조합을 사용한 다양한 확장 a. 더 일반화된 메서드 b. 더 유연한 인터페이스 c. 코드 중복 제거
  13. 13. 요구사항 - 리스트 정렬 ● 정수형 리스트 정렬 ● 문자열 리스트 정렬 ● comparable을 상속받지 않은 객체 정렬 a. 서로 다른 필드를 기준으로 정렬(ex: price, name)
  14. 14. 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을 상속
  15. 15. 요구사항 - 리스트 정렬 ● 정수형 리스트 정렬 ● 문자열 리스트 정렬 ● comparable을 상속받지 않은 객체 정렬 a. 서로 다른 필드를 기준으로 정렬(ex: price, name)
  16. 16. 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; } }
  17. 17. 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]중 하나를 반환
  18. 18. 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]중 하나를 반환
  19. 19. 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; } interface Comparator<T> { int compare(T a, T b); } 값 2개를 비교해서 [-1, 0, 1]중 하나를 반환 Comparator 인터페이스를 사용
  20. 20. // 1. 가격으로 정렬 sort(cars, new Comparator<Car>() { @Override public int compare(Car a, Car b) { return a.price.compareTo(b.price); // 2. 이름으로 정렬 sort(cars, new Comparator<Car>() { @Override public int compare(Car a, Car b) { return a.name.compareTo(b.name); } }); 익명 클래스를 사용한 행동 전달 } }); 다른 부분 가격/이름 으로 비교
  21. 21. 요구사항 - 리스트 정렬 ● 정수형 리스트 정렬 ● 문자열 리스트 정렬 ● comparable을 상속받지 않은 객체 정렬 a. 서로 다른 필드를 기준으로 정렬(ex: price, name)
  22. 22. 행위 매개변수 사용에 장점 ● 고정된 코드 + 행위 매개변수(코드 블럭) 조합을 사용한 다양한 확장 a. 더 일반화된 메서드 b. 더 유연한 인터페이스 c. 코드 중복 제거
  23. 23. 람다가 왜 필요할가? ● 행위 매개변수(코드 블럭) 전달 ● 자바8 이전에는 익명 클래스를 사용
  24. 24. 익명 함수 Wikipedia ● 특정 식별자 없이 정의되거나 호출될 수 있는 함수 ● 자바에 함수가 있나? -> 람다!
  25. 25. (인자 목록) -> { 구문 } ● 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!"); } 람다 문법
  26. 26. @Functional Interface ● 추상 메서드가 1개 뿐인 인터페이스 ● 인터페이스를 함수처럼 사용하자
  27. 27. @FunctionalInterface interface Action { void run(String param); void stop(String param); } @FunctionalInterface interface Runnable() { void run(); 메서드 2개 NO! } 메서드 1개 OK!
  28. 28. 익명 클래스를 람다로 변환해 주는 IntelliJ
  29. 29. // 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, (a, b) -> a.compareTo(b)); 행위 매개변수 전달: 클래스 -> 람다 1:1 대응
  30. 30. 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); 함수형 인터페이스를 사용한 정렬 } });
  31. 31. 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개뿐이니 별도 이름 불필요
  32. 32. 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); } }); 반환 타입과 파라미터 타입도 이미 정해져 있으니 제거
  33. 33. // 1. 익명 클래스 사용 (자바8 이전) Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { // 1. 람다 사용 (자바8) Collections.sort(names, (a, b) -> { return a.compareTo(b); }); 비슷하다 return a.compareTo(b); } });
  34. 34. 람다 문법: (인자 목록) -> { 구문 } ● 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 키워드 생략 가능
  35. 35. // 1. 익명 클래스 사용 (자바8 이전) Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { // 1. 람다 사용 (자바8) Collections.sort(names, (a, b) -> a.compareTo(b)); 익명 클래스 -> 람다 return a.compareTo(b); } });
  36. 36. @FunctionalInterface interface Adder { int add(int a, int b); } ????? func = (int a, int b) -> { return a + b }; ????? shortFunc = (a, b) -> a + b; 람다 타입
  37. 37. @FunctionalInterface interface Adder { int add(int a, int b); } Adder func = (int a, int b) -> { return a + b }; Adder shortFunc = (a, b) -> a + b; 람다 타입
  38. 38. Q: 람다는 단순히 익명 클래스에 문법 치환인가? A: No! 실제로는 익명 클래스에 비효율을 제거하기 위해서 사용 invoke dynamic
  39. 39. interface Comparator<T> { int compare(T a, T b); } @FunctionalInterface interface Runnable<T> { 함수형 인터페이스 - 추상 메서드를 1개만 들고 있다 void run(); } ???
  40. 40. @FunctionalInterface 어노테이션은 붙이는 이유? ● 컴파일러가 추상 메서드가 2개인 경우 컴파일 오류 발생 ● Javadoc에 @FunctionalInterface 글 추가
  41. 41. Target typing ● 람다 -> 익명 클래스 변환시 가장 유사한 타입을 찾아가는것
  42. 42. 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 ); 람다 두개 모두 같은 모습이지만 알아서 잘 찾아간다
  43. 43. Function<String, Boolean> isDaewon = s -> "daewon".equals(s); Predicate<String> isDaewon = s -> "daewon".equals(s); predicate가 왜 필요할가? Target typing and Method arguments
  44. 44. interface Runnable { Target typing and Method arguments 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");
  45. 45. interface Runnable { 반환값을 참고해서 오버로드된 메서드도 잘 찾아간다 Target typing and Method arguments 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");
  46. 46. 변수 포획
  47. 47. public static void thread(String msg) { int tmp = 10; new Thread( () -> System.out.println(msg) ).start(); } 람다 내부에 선언되지 않은 변수를 참조할 수 있다 ● 자유변수: 나를 감싸고 있는 유효 범위 변수 a. String msg, int tmp 자신은 감싸고 있는 유효 범위 변수에 접근 가능
  48. 48. public static void thread(String msg) { new Thread( () -> { msg = "must be final"; System.out.println(msg) ).start(); } } 컴파일 에러: 포획된 변수를 수정 포획된 변수는 언제나 final이여야 한다
  49. 49. 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")); // true System.out.println(isIncludeGoogle.test("microsoft.com apple.com")); // false 변수 참조를 활용해서 동적으로 새로운 람다를 생성하는 람다 자신은 감싸고 있는 유효 범위 변수에 접근 가능
  50. 50. 미리 정의된 함수형 인터페이스 ● 람다를 사용하려면 항상 인터페이스를 필요할가? ● 람다 사용시 새로운 인터페이스를 매번 만들어야 하나?
  51. 51. 미리 정의된 함수형 인터페이스 ● 람다를 사용하려면 항상 인터페이스를 필요할가? - yes ● 람다 사용시 새로운 인터페이스를 매번 만들어야 하나? - no(반만 yes)
  52. 52. Car price price > 3000 sum cars.stream().map(c -> c.gerPrice()).filter(p -> p > 3000).reduce((a, b) -> a + b)); interface Function interface Predicate interface BinaryOperator 람다를 위해 3개 인터페이스 필요
  53. 53. IntPredicate IntSupplier IntToDoubleFunction IntToLongFunction IntUnaryOperator LongBinaryOperator LongConsumer LongFunction<R> LongPredicate LongSupplier LongToDoubleFunction LongToIntFunction LongUnaryOperator ObjDoubleConsumer<T> ObjIntConsumer<T> ObjLongConsumer<T> Predicate<T> Supplier<T> 미리 정의된 함수형 인터페이스 BiConsumer<T,U> BiFunction<T,U,R> BinaryOperator<T> BiPredicate<T,U> BooleanSupplier Consumer<T> DoubleBinaryOperator DoubleConsumer DoubleFunction<R> DoublePredicate DoubleSupplier DoubleToIntFunction DoubleToLongFunction DoubleUnaryOperator Function<T,R> IntBinaryOperator IntConsumer IntFunction<R>
  54. 54. 지연 연산
  55. 55. // 디버그 모드에서만 실행 public void debug(String message) { if (log.isDebugEnabled()) { log.log(message); } } debug(some.expensive("operation")); 디버그 모드에서는 동작 디버그 모드에서만 동작하는 함수
  56. 56. // 디버그 모드에서만 실행 public void debug(String message) { if (log.isDebugEnabled()) { log.log(message); } } debug(some.expensive("operation")); 평가 시점 함수 인자는 호출 시점에 평가가 된다
  57. 57. // 디버그 모드에서만 실행 public void debug(Consumer<String> consumer) { if (log.isDebugEnabled()) { log.log(consumer.accept()); } } debug(() -> some.expensive("operation")); 람다로 평가 시점 조절 람다를 사용해서 평가를 뒤로 미룬다
  58. 58. // 람다가 없는 경우 debug(some.expensive("operation")); // 람다 사용 debug(() -> some.expensive("operation")); 호출 하는 쪽이 조금 불편해 졌다
  59. 59. 문제 다시 보기 ● 빌려쓰기 패턴
  60. 60. 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으로 출력
  61. 61. 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에 저장
  62. 62. 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(); } } 중복 발생! 현재 라인으로 무엇인가를 처리 System.out.println(line); DB.write(line);
  63. 63. 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); } System.out.println(line); DB.write(line);
  64. 64. 어떤 값을 받아서 소비한다. interface Consumer<T> { void accept(T a); } 각 라인을 인자로 넘겨 받아서 소비한다 public void accept(String line) { System.out.println(line); }
  65. 65. interface Consumer 클래스로 구현 class DBWorker class PrintWorker class DBAndPrintWorker
  66. 66. 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()); 다양한 종류에 클래스 구현
  67. 67. 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); } }); 익명 클래스 사용
  68. 68. interface Consumer<T> { void accept(T a); } 메서드 딱 1개! 함수형 인터페이스
  69. 69. 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); } 익명 클래스 사용
  70. 70. 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); } 중복되는 객체 이름 제거
  71. 71. 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); } 중복되는 메서드 이름 제거
  72. 72. 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); } 실제로 다른 부분은 함수 본체
  73. 73. 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); });
  74. 74. 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); });
  75. 75. 감사합니다. - @blueiur(twitter)

×