59
Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

Embed Size (px)

DESCRIPTION

2012 Java TWO 你可以在以下鏈結找到中文內容: http://www.codedata.com.tw/java/understanding-lambda-closure-1-from-javascript-function-1/

Citation preview

Page 1: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

Page 2: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

林信良 http://openhome.cc

[email protected]

Page 3: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

議程

•一級函式與 λ 演算

• JDK8 的 Lambda 語法

•介面預設方法(Default method)

•擴充的 Collection 框架

•函數式風格的可能性

Page 4: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

一級函式與 λ 演算

第十一個希臘字母 物理上的波長符號 放射學的衰變常數 線性代數的特徵值 λ 演算 ….

Page 5: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

一級值

•值(Value)可指定給變數

•一級(First-class)值可傳給函式或從函式中傳回

基本型態

物件

函式?

運算式?

Page 6: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

一級函式

•有些語言中函式是一等公民(First-class citizen) – 高階函式(High order function)

function doSome(param) {

// 作些事 }

var doSome = function(param) {

// 作些事 };

function(param) {

// 作些事 };

Page 7: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

一級函式

•有些語言中函式是一等公民(First-class citizen) – 高階函式(High order function)

doOther(function(param) {

// 作些事 });

function doSome(param) {

// 作些事 }

var doSome = function(param) {

// 作些事 };

function(param) {

// 作些事 };

Page 8: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

一級函式

•有些語言中函式是一等公民(First-class citizen) – 高階函式(High order function)

function doAction(opt) {

return function(param) {

// 作些事 };

};

function doSome(param) {

// 作些事 }

var doSome = function(param) {

// 作些事 };

function(param) {

// 作些事 };

Page 9: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

/lambda/

What is

http://en.wikipedia.org/wiki/Lambda

In programming languages such as

Lisp and Python, lambda is an

operator used to denote

anonymous functions or closures

Page 10: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

λ 演算(Lambda calculus)

•每個表達式(Expression)代表具單一參數函數

•參數本身亦可接受具有單一參數的函式

• f(x) = x * 2 可匿名地表示為 …

λ x. x * 2

x -> x * 2

Page 11: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

λ 演算(Lambda calculus)

•如果要套用 x 為 2

• g(y) = y -1 代入 f(x) 匿名地表達為…

(x -> x * 2)(2)

= 2 * 2

= 4

(y -> y - 1)(x -> x * 2)

= x -> x * 2 - 1

Page 12: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

λ 演算(Lambda calculus)

•多參數的函數可使用單參數函數套用而成

•若 x 為 2 而 y 為 3

(x, y) -> x * x + y * y

= x -> (y -> x * x + y * y)

(x -> (y -> x * x + y * y))(2)(3)

= (y -> 2 * 2 + y * y)(3)

= 4 + 3 * 3

= 4 + 9

= 13

Page 13: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

λ 演算(Lambda calculus)

•可用來表現任何可計算函數

•可使用函數實現控制結構(如 if、forEach等)

•一個小型通用語言

butThat = cond->fv->tv->

(cond or fv) and (cond and tv)

butThat(true)(10)(20)

= (true or 10) and (true and 20)

= true and 20

= 20

Page 14: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

一級函式與 λ 演算

•不同語言提供不同程度的 Lambda 表達式支援

(x -> x * 2)(2)

function(x) {

return x * 2;

}(2);

Page 15: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

一級函式與 λ 演算

• JDK8 前存在 Lambda 語法類似品 – 匿名內部類別(Anonymous inner class)

•定義單一抽象方法(Single abstract method)介面模擬一級函式

interface Func<P, R> {

R apply(P p);

}

Page 16: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

一級函式與 λ 演算

•使用匿名內部類別來表示 x -> x * 2

new Func<Integer, Integer>() {

public Integer apply(Integer x) {

return x * 2;

}

};

Page 17: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

一級函式與 λ 演算

•設計 compose(f, g) 達成 g(f(x)) 的函數組合

static <A, B, C> Func<A, C> compose(

final Func<A, B> f, final Func<B, C> g) {

return new Func<A, C>() {

public C apply(A x) {

return g.apply(f.apply(x));

}

};

}

Page 18: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

一級函式與 λ 演算

• f(x) = x + 2 而 g(y) = y * 3

• h(x) = g(f(x))

compose(

new Func<Integer, Integer>() {

public Integer apply(Integer x) {

return x + 2;

}

},

new Func<Integer, Integer>() {

public Integer apply(Integer y) {

return y * 3;

}

}

);

Page 19: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

JDK8 的 Lambda 語法

λ

Page 20: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

JDK8 的 Lambda 語法

•使用 JDK8 的 Lambda 語法來表示 x -> x * 2

•設計 compose(f, g) 達成 g(f(x)) 的函數組合

• f(x) = x + 2 、 g(y) = y * 3、h(x) = g(f(x))

(Integer x) -> x + 2

static <A, B, C> Func<A, C> compose(Func<A, B> f, Func<B, C> g) {

// 忽略.apply 就是 x -> g(f(x)) return x -> g.apply(f.apply(x));

}

compose((Integer x) -> x + 2, (Integer y) -> y * 3)

Page 21: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

JDK8 的 Lambda 表示式

• (type parameter) -> function_body – 單一運算式

– 陳述區塊

(int x, int y) -> x + y

() -> 42

(String s) -> { System.out.println(s); }

Page 22: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

函式介面(Functional interface)

•單一抽象方法的介面

public interface Runnable {

void run();

}

public interface Comparator<T> {

int compare(T o1, T o2);

}

public interface Callable<V> {

V call() throws Exception

}

Page 23: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

目標定型(Target typing)

•目標型態與環境(Context)有關 – 目標型態可用於類型推斷(Type inference)

•獨立於型態的Lambda 表示式

Runnable r = () -> { out.println("later"); };

Comparator<String> c = (s1, s2) -> s1.compareTo(s2);

Collections.sort(lists, (s1, s2) -> -s1.compareTo(s2));

Callable<String> c = () -> "done";

PrivilegedAction<String> a = () -> "done";

Page 24: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

不只是語法蜜糖

•語彙範圍(Lexical scoping) – Lambda 語法本體中的名稱與包裹環境相同

public class Hello {

Runnable r1 = () -> { out.println(this); }

Runnable r2 = () -> { out.println(toString()); }

public String toString() { return "Hello, world!"; }

public static void main(String... args) {

new Hello().r1.run();

new Hello().r2.run();

}

}

Page 25: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

不只是語法蜜糖

•變數捕捉(Variable capture) – 與 final 等效的區域變數不需要 final

•編譯錯誤...

•建議...

Callable<String> helloCallable(String name) {

String hello = "Hello";

return () -> (hello + ", " + name);

}

int sum = 0;

list.forEach(e -> { sum += e.size(); });

int sum = list.map(e -> e.size())

.reduce(0, (a, b) -> a + b);

Page 26: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

方法參考(Method reference)

•類別靜態方法 public class Person {

...

public static int compareByAge(Person a, Person b) { ... }

public static int compareByName(Person a, Person b) { ... }

}

Person[] people = ...

// Arrays.sort(people, (a, b) -> a.getAge() – b.getAge());

Arrays.sort(people, Person::compareByAge);

interface Block<T> { void run(T arg); }

Block<Integer> b1 = System::exit; // void exit(int status)

Block<String[]> b2 = Arrays::sort; // void sort(Object[] a)

Block<String> b3 = MyProgram::main; // void main(String... args)

Runnable r = MyProgram::main; // void main(String... args)

Page 27: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

方法參考(Method reference)

•特定物件實例方法

•實例方法中可帶物件狀態 – 相當於讓 Lambda 語法帶有狀態(Closure 的替代?)

public class ComparisonProvider {

public int compareByName(Person p1, Person p2) { ... }

public int compareByAge(Person p1, Person p2) { ... }

}

...

ComparisonProvider comparisonProvider = ...;

Arrays.sort(people, comparisonProvider::compareByName);

Page 28: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

方法參考(Method reference)

•任意類別的實例方法

Collections.sort(names, String::compareToIgnoreCase);

List<String> names = Arrays.asList("Justin", ...);

Collections.sort(names,

(s1, s2) -> s1.compareToIgnoreCase(s2));

Page 29: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

方法參考(Method reference)

•讓 Lambda 語法的目標類型可以變換

Callable<String> c = () -> "done";

PrivilegedAction<String> a = c::call;

Page 30: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

建構式參考(Constructor reference)

•像是特殊的靜態方法參考:

SocketFactory factory = SocketImpl::new;

Page 31: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

介面預設方法(Default method)

Page 32: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

介面預設方法(Default method)

• JDK8 前實作介面以實現多個規格的繼承來源 – 捨棄繼承多個實作來源的可能性,規避了多重繼承下實

作衝突等問題

public class Ball implements Comparable<Ball> {

public boolean notEquals(Ball that) {

return this.radius - that.radius;

}

// 底下都是根據notEquals()實作

public boolean lessThan(Ball that) {

return this.notEquals(that) < 0;

}

public boolean lessOrEquals(Ball that) {

return this.lessThan(that) || this.notEquals(that) == 0;

}

public boolean greaterThan(Ball that) {

return !this.lessOrEquals(that);

} ...

Page 33: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

介面預設方法(Default method)

• JDK8 介面可以是根據抽象的共用實作

public interface Comparable<T> {

boolean notEquals(T that);

boolean lessThan(T that) default {

return this.notEquals(that) < 0;

}

boolean lessOrEquals(T that) default {

return this.lessThan(that) || this.notEquals(that) == 0;

}

boolean greaterThan(T that) default {

return !this.lessOrEquals(that);

} ...

Page 34: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

介面預設方法(Default method)

• JDK8 可以實作具有預設方法的介面

•實作可能發生衝突..

public class Ball implements Comparable<Ball> {

public boolean notEquals(Ball that) {

return this.radius - that.radius;

} ...

interface Robot implements Artist, Gun {

void draw() default { Artist.super.draw(); }

}

Page 35: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

介面預設方法(Default method)

•擴充既有程式庫以搭配 Lambda 語法 – 僅在介面中新增方法定義? – 使用靜態方法?像是 Collections.filter(…)?

– 獨立的新 API?像是 Collection2 框架?

interface Iterator<E> {

boolean hasNext();

E next();

void remove();

void skip(int i) default {

for (; i > 0 && hasNext(); i--) next();

}

}

Page 36: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

擴充的 Collection 框架

•高階函式 – 可讀性 – 簡潔性 – 導入新的抽象層

•以迭代為例…

for(Ball b : balls) {

b.setColor(RED);

}

balls.forEach(b -> { b.setColor(RED); });

Page 37: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

擴充的 Collection 框架

•匿名類別作不到嗎? – 想分離關切點,但又帶入新的關切點

•傳入 Lambda 表示式,而非傳入不想要的雜訊

balls.forEach(new Block<Ball> {

public void apply(Ball ball) {

ball.setColor(RED);

}

});

Page 38: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

擴充的 Collection 框架

•為什麼是 Collection 框架?

•許多問題都是資料處理問題… – 關聯式資料通常就是待處理資料群集…

• filter、map、fold 等是處理資料時的高階抽象 – 過濾一組資料 – 將一組資料對應至另一組資料 – 逐一取得資料計算單一結果 – …

Page 39: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

擴充的 Collection 框架

•管線化(Piped)操作

int sum = names.filter(s -> s.length() < 3) .map(s -> s.length())

.reduce(0, (sum, len) -> sum + len);

int sum = names.filter(s -> s.length() < 3)

.map(s -> s.length())

.sum();

Page 40: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

擴充的 Collection 框架

•管線化(Piped)操作

blocks.filter(b -> b.getColor() == BLUE)

.forEach(b -> { b.setColor(RED); });

List<Block> blue = blocks.filter(b -> b.getColor() == BLUE)

.into(new ArrayList<>());

Set<Box> blueBlock = blocks.filter(b -> b.getColor() == BLUE)

.map(b -> b.getContainingBox())

.into(new HashSet<>());

Page 41: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

擴充的 Collection 框架

•延遲(Laziness)

•捷徑(short-circuiting)

int sum = blocks.filter(b -> b.getColor() == BLUE)

.map(b -> b.getWeight())

.sum();

Block firstBlue = blocks.filter(b -> b.getColor() == BLUE)

.getFirst();

Page 42: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

擴充的 Collection 框架

•共用的函式介面(java.util.functions) – Predicate

– boolean test(T t)

– Block

– void apply(T t);

– Mapper

– U map(T t)

– ...

Page 43: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

擴充的 Collection 框架

•已實作 filter、map 與 fold 的細節 – 循序? – 遞迴? – 共用資料結構? – 平行化?

•可以用更高階的抽象來處理資料

int sum = blocks.parallel()

.filter(b -> b.getColor() == BLUE)

.map(b -> b.getWeight())

.sum();

Page 44: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

全部放在一起 for (Method m : enclosingInfo.getEnclosingClass().getDeclaredMethods()) {

if (m.getName().equals(enclosingInfo.getName()) ) {

Class<?>[] candidateParamClasses = m.getParameterTypes();

if (candidateParamClasses.length == parameterClasses.length) {

boolean matches = true;

for(int i = 0; i < candidateParamClasses.length; i++) {

if (!candidateParamClasses[i].equals(parameterClasses[i])) {

matches = false;

break;

}

}

if (matches) { // finally, check return type

if (m.getReturnType().equals(returnType) )

return m;

}

}

}

}

throw new InternalError("Enclosing method not found");

Page 45: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

全部放在一起

Method matching =

Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())

.filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())

.filter(m -> Arrays.equals(m.getParameterTypes(), parameterClasses))

.filter(m -> Objects.equals(m.getReturnType(), returnType))

.getFirst();

if (matching == null)

throw new InternalError("Enclosing method not found");

return matching;

Page 46: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

全部放在一起 List<Album> favs = new ArrayList<>();

for (Album a : albums) {

boolean hasFavorite = false;

for (Track t : a.tracks) {

if (t.rating >= 4) {

hasFavorite = true;

break;

}

}

if (hasFavorite)

favs.add(a);

}

Collections.sort(favs, new Comparator<Album>() {

public int compare(Album a1, Album a2) {

return a1.name.compareTo(a2.name);

}});

Page 47: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

全部放在一起

List<Album> sortedFavs =

albums.filter(a -> a.tracks.anyMatch(t -> (t.rating >= 4)))

.sortedBy(a -> a.name)

.into(new ArrayList<>());

Page 48: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

函數式風格的可能性

Page 49: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

函數式風格的可能性

• Lambda 語法在原語言中提供小型通用語言

•在某些問題領域… – 提供以 λ 演算解題的可能性

• Java 在函數式程式設計(Functional Parogramming)的可能性?

Page 50: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

函數式風格的可能性

•費式數的定義

•指令式程式設計(Imperative programming)?

int fib(int n) {

int a = 1; int b = 1;

for(int i = 2; i < n; i++) {

int tmp = b; b = a + b; a = tmp;

}

return b;

}

{ F0 = 0, F1 = 1, Fn = Fn-1 + Fn-2 }

Page 51: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

函數式風格的可能性

•函數式程式設計?

•使用 Haskell

fib 0 = 0

fib 1 = 1

fib n = fib (n-1) + fib (n-2)

int fib(int n) {

if(n == 0 || n == 1) return n;

else return fib(n - 1) + fib(n - 2);

}

Page 52: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

函數式風格的可能性

•函數式語言的特徵 – 一級函式 – Immutable – Lazy evaluation – Pattern match – List comprehension – Curried function

{2 * x | x ∈ {1, 2..100}}

[2 * x | x <- [1, 2..100]]

fib 0 = 0

fib 1 = 1

fib n = fib (n-1) + fib (n-2)

sum [] = 0

sum (x:xs) = x + sum xs

f x y = x + y

g y = f 1 y

Page 53: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

函數式風格的可能性

• JDK8 有多少函數式風格的特性? – 直接支援 Lambda 語法 – 使用 final 變數強制 Immutable

– 以 API 封裝複雜細節達到… – 延遲求值 – 模式匹配 – List comprehension – Partially applied 與 Curried function

Page 54: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

函數式風格的可能性

•以 API 封裝複雜細節,留下函數式外觀給使用者 – Functional Java(functionaljava.org) – 目的就是為了讓 Java 實現函數式風格 – 主要針對 JDK5 以上 – 使用匿名內部類別繼承抽象類別

final Array<String> a = array("Hello", "There", "what");

final boolean b = a.exists(new F<String, Boolean>() {

public Boolean f(final String s) {

return fromString(s).forall(isLowerCase);

}

});

boolean b = a.exists(s -> fromString(s).forall(isLowerCase));

Page 55: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

函數式風格的可能性

•以 API 封裝複雜細節,留下函數式外觀給使用者 – Guava(code.google.com/p/guava-libraries) – 主要目的並非為了讓 Java 實現函數式風格 – 主要針對 JDK5 以上 – 使用匿名內部類別實作介面

Page 56: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

函數式風格的可能性

Multiset<Integer> lengths = HashMultiset.create(

FluentIterable.from(strings)

.filter(new Predicate<String>() {

public boolean apply(String string) {

return CharMatcher.JAVA_UPPER_CASE.matchesAllOf(string);

}

})

.transform(new Function<String, Integer>() {

public Integer apply(String string) {

return string.length();

}

}));

•Guava 目前不鼓勵函數式風格

Multiset<Integer> lengths = HashMultiset.create(

FluentIterable.from(strings)

.filter(s -> CharMatcher.JAVA_UPPER_CASE.matchesAllOf(s))

.transform(s -> s.length())

));

Page 57: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

總結

• λ 語法為 Java 提供了小型通用語言,在某些問題領域,提供 λ 演算解題的可能性

•除 JDK 程式庫將搭配 λ 語法演化,開放原始碼如 Functional Java、Guava 等也將呈現不同風格

•新的抽象層將平行化、函數式等實作予以封裝,簡潔的表述能力,使開發者進一步思索 map、filter、fold 等問題的基本形式

Page 58: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

參考資料

• State of the Lambda v4 – http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-4.html

• State of the Lambda: Libraries Edition – http://cr.openjdk.java.net/~briangoetz/lambda/collections-

overview.html

• Project Lambda – http://openjdk.java.net/projects/lambda/

• Lambda calculus – http://en.wikipedia.org/wiki/Lambda_calculus

• Functional Programming for Java Developers – http://shop.oreilly.com/product/0636920021667.do

• APIO讲稿——函数式编程 – http://www.byvoid.com/blog/apio-fp/

Page 59: Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

感謝 Orz 林信良 http://openhome.cc [email protected]