ݺߣ

ݺߣShare a Scribd company logo
나에 첫번째 자바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개 뿐인 인터페이스
● 인터페이스를 함수처럼 사용하자
@FunctionalInterface
interface Action {
void run(String param);
void stop(String param);
}
@FunctionalInterface
interface 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);
}
});
@FunctionalInterface
interface Adder {
int add(int a, int b);
}
????? func = (int a, int b) -> { return a + b };
????? shortFunc = (a, b) -> a + b;
람다 타입
@FunctionalInterface
interface 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개만 들고 있다
@FunctionalInterface
interface 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")); // true
System.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
미리 정의된 함수형 인터페이스
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>
지연 연산
// 디버그 모드에서만 실행
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)

More Related Content

2014.07.26 KSUG와 지앤선이 함께하는 테크니컬 세미나 - 나의 첫번째 자바8 람다식 (정대원)

  • 1. 나에 첫번째 자바8 람다식 - @blueiur(twitter)
  • 2. 오! 벌써 새벽 3시네, 컴퓨 터 끄고 자야 겠어! 한 시간 후 ... 정대원@blueiur(twitter) ● like .. ○ programming language ○ functionl programming ○ elixir ○ scala ○ ruby
  • 3. 람다 ● 람다 계산법 ● 익명 함수 ● 함수 리터럴 ● 클로저
  • 4. 익명 함수 Wikipedia ● 특정 식별자 없이 정의되거나 호출될 수 있는 함수
  • 5. 람다가 왜 필요할가? ● 행위 매개변수(코드 블럭) 전달
  • 6. // 1. 컬렉션 정렬 (익명 클래스 사용) Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return a.compareTo(b); } }); 컬렉션 정렬 - 익명 클래스 사용
  • 7. // 1. 컬렉션 정렬 (익명 클래스 사용) Collections.sort(names, ...); Collections.sort + a.compareTo(b) new Comparator<String>() { @Override public int compare(String a, String b) { return a.compareTo(b); } }
  • 8. // 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); } }
  • 9. 새 쓰레드 생성 - 익명 클래스 사용 // 쓰레드 생성 (익명 클래스 사용) new Thread(new Runnable() { @Override public void run() { System.out.println("I consume memory, therefore i am!"); } }).start();
  • 10. // 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();
  • 11. // 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();
  • 12. 행위 매개변수 사용에 장점 ● 고정된 코드 + 행위 매개변수(익명 클래스) 조합을 사용한 다양한 확장 a. 더 일반화된 메서드 b. 더 유연한 인터페이스 c. 코드 중복 제거
  • 13. 요구사항 - 리스트 정렬 ● 정수형 리스트 정렬 ● 문자열 리스트 정렬 ● comparable을 상속받지 않은 객체 정렬 a. 서로 다른 필드를 기준으로 정렬(ex: price, name)
  • 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. 요구사항 - 리스트 정렬 ● 정수형 리스트 정렬 ● 문자열 리스트 정렬 ● comparable을 상속받지 않은 객체 정렬 a. 서로 다른 필드를 기준으로 정렬(ex: price, name)
  • 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. 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. 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. 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]중 하나를 반환
  • 20. // 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); } }); 다른 부분 가격/이름 으로 비교
  • 21. 요구사항 - 리스트 정렬 ● 정수형 리스트 정렬 ● 문자열 리스트 정렬 ● comparable을 상속받지 않은 객체 정렬 a. 서로 다른 필드를 기준으로 정렬(ex: price, name)
  • 22. 행위 매개변수 사용에 장점 ● 고정된 코드 + 행위 매개변수(코드 블럭) 조합을 사용한 다양한 확장 a. 더 일반화된 메서드 b. 더 유연한 인터페이스 c. 코드 중복 제거
  • 23. 람다가 왜 필요할가? ● 행위 매개변수(코드 블럭) 전달 ● 자바8 이전에는 익명 클래스를 사용
  • 24. 익명 함수 Wikipedia ● 특정 식별자 없이 정의되거나 호출될 수 있는 함수 ● 자바에 함수가 있나? -> 람다!
  • 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. @Functional Interface ● 추상 메서드가 1개 뿐인 인터페이스 ● 인터페이스를 함수처럼 사용하자
  • 27. @FunctionalInterface interface Action { void run(String param); void stop(String param); } @FunctionalInterface interface Runnable() { void run(); } 메서드 1개 OK! 메서드 2개 NO!
  • 28. 익명 클래스를 람다로 변환해 주는 IntelliJ
  • 29. // 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 대응
  • 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. // 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개뿐이니 별도 이름 불필요 객체 이름 제거 메서드 이름 제거
  • 32. // 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); } 반환 타입과 파라미터 타입도 이미 정해져 있으니 제거 객체 이름 제거 메서드 이름 제거 반환 값 및 파라미터 타입 추론
  • 33. // 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); } });
  • 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. // 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); } });
  • 36. @FunctionalInterface interface Adder { int add(int a, int b); } ????? func = (int a, int b) -> { return a + b }; ????? shortFunc = (a, b) -> a + b; 람다 타입
  • 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. Q: 람다는 단순히 익명 클래스에 문법 치환인가? A: No! 실제로는 익명 클래스에 비효율을 제거하기 위해서 사용 invoke dynamic
  • 39. interface Comparator<T> { int compare(T a, T b); } 함수형 인터페이스 - 추상 메서드를 1개만 들고 있다 @FunctionalInterface interface Runnable<T> { void run(); } ???
  • 40. @FunctionalInterface 어노테이션은 붙이는 이유? ● 컴파일러가 추상 메서드가 2개인 경우 컴파일 오류 발생 ● Javadoc에 @FunctionalInterface 글 추가
  • 41. Target typing ● 람다 -> 익명 클래스 변환시 가장 유사한 타입을 찾아가는것
  • 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. Target typing and Method arguments predicate가 왜 필요할가? Function<String, Boolean> isDaewon = s -> "daewon".equals(s); Predicate<String> isDaewon = s -> "daewon".equals(s);
  • 44. 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");
  • 45. 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"); 반환값을 참고해서 오버로드된 메서드도 잘 찾아간다
  • 47. public static void thread(String msg) { int tmp = 10; new Thread( () -> System.out.println(msg) ).start(); } 람다 내부에 선언되지 않은 변수를 참조할 수 있다 ● 자유변수: 나를 감싸고 있는 유효 범위 변수 a. String msg, int tmp 자신은 감싸고 있는 유효 범위 변수에 접근 가능
  • 48. public static void thread(String msg) { new Thread( () -> { msg = "must be final"; System.out.println(msg) ).start(); } } 컴파일 에러: 포획된 변수를 수정 포획된 변수는 언제나 final이여야 한다
  • 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. 미리 정의된 함수형 인터페이스 ● 람다를 사용하려면 항상 인터페이스를 필요할가? ● 람다 사용시 새로운 인터페이스를 매번 만들어야 하나?
  • 51. 미리 정의된 함수형 인터페이스 ● 람다를 사용하려면 항상 인터페이스를 필요할가? - yes ● 람다 사용시 새로운 인터페이스를 매번 만들어야 하나? - no(반만 yes)
  • 52. 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
  • 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>
  • 55. // 디버그 모드에서만 실행 public void debug(String message) { if (log.isDebugEnabled()) { log.log(message); } } debug(some.expensive("operation")); 디버그 모드에서만 동작하는 함수 디버그 모드에서는 동작
  • 56. // 디버그 모드에서만 실행 public void debug(String message) { if (log.isDebugEnabled()) { log.log(message); } } debug(some.expensive("operation")); 평가 시점 함수 인자는 호출 시점에 평가가 된다
  • 57. // 디버그 모드에서만 실행 public void debug(Consumer<String> consumer) { if (log.isDebugEnabled()) { log.log(consumer.accept()); } } debug(() -> some.expensive("operation")); 람다로 평가 시점 조절 람다를 사용해서 평가를 뒤로 미룬다
  • 58. // 람다가 없는 경우 debug(some.expensive("operation")); // 람다 사용 debug(() -> some.expensive("operation")); 호출 하는 쪽이 조금 불편해 졌다
  • 59. 문제 다시 보기 ● 빌려쓰기 패턴
  • 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. 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. 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); 현재 라인으로 무엇인가를 처리
  • 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); } DB.write(line); System.out.println(line);
  • 64. 어떤 값을 받아서 소비한다. interface Consumer<T> { void accept(T a); } 각 라인을 인자로 넘겨 받아서 소비한다 public void accept(String line) { System.out.println(line); }
  • 65. class DBWorker class PrintWorker class DBAndPrintWorker interface Consumer 클래스로 구현
  • 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. 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. interface Consumer<T> { void accept(T a); } 메서드 딱 1개! 함수형 인터페이스
  • 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. 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. 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. 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. 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. 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); });