title: 讀《Java 8 函數(shù)式編程》
date: 2019-01-28
author: maxzhao
tags:
- JAVA
- Lambda
categories: - JAVA
- Lambda
前言
- 以下內(nèi)容帶有“【摘】”字樣的段落,均來自"Java 8 Lambda, Richard Warburton著(O‘Reilly , 2015)"。
- 我自己寫的代碼均使用Junit @Test,實(shí)體均為內(nèi)部類,這里的@Data是lombok插件哦。
- 本文內(nèi)容章節(jié)隨書的章節(jié)而定,并不是所有的章節(jié)都有做筆記,所以部分章節(jié)可能沒有出現(xiàn)。
簡介
每個人對函數(shù)式編程的理解不盡相同。但其核心是:在思考問題時,使用不可變值和函
數(shù),函數(shù)對一個值進(jìn)行處理,映射成另一個值。
不同的語言社區(qū)往往對各自語言中的特性孤芳自賞?,F(xiàn)在談 Java 程序員如何定義函數(shù)式編程還為時尚早,但是,這根本不重要!我們關(guān)心的是如何寫出好代碼,而不是符合函數(shù)式
編程風(fēng)格的代碼。
本書將重點(diǎn)放在函數(shù)式編程的實(shí)用性上,包括可以被大多數(shù)程序員理解和使用的技術(shù),幫助他們寫出易讀、易維護(hù)的代碼?!菊?/p>
Lambda 表達(dá)式
Lambda 表達(dá)式的幾種形式
- unnable noArguments = () -> System.out.println("Hello World");
- ActionListener oneArgument = event -> System.out.println("button clicked");
- Runnable multiStatement = () -> {- System.out.print("Hello");System.out.println(" World");};
- BinaryOperator<Long> add = (x, y) -> x + y;
- BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;
Lambda 表達(dá)式的類型依賴于上下文環(huán)境,是由編譯器
推斷出來的。目標(biāo)類型也不是一個全新的概念。如final String[] array = { "hello", "world" };Java 中初始化數(shù)組時,數(shù)組的類型就是根據(jù)上下文推斷出來的。另一個常見的例子是 null ,只有將 null 賦值給一個變量,才能知道它的類型。
這里的示例為什么要用final呢?我理解為讓我們有一個良好的編程習(xí)慣,常量用final修飾。
看到BinaryOperator就涉及到我的知識盲區(qū)了,下面附帶一個測試代碼。
介紹:表示對同一類型的兩個操作數(shù)的操作,產(chǎn)生與操作數(shù)相同類型的結(jié)果。 對于操作數(shù)和結(jié)果都是相同類型的情況,這是BiFunction的專業(yè)化
@Test
public void add() {
BinaryOperator<Integer> add = (x, y) -> x + y;
System.out.println(add.apply(1, 2));
}
@Test
public void string() {
BinaryOperator<String> add = (x, y) -> x + "===" + y;
System.out.println(add.apply("1", "2"));
System.out.println(add.apply("1", "1"));
}
@Test
public void minBy() {
BinaryOperator<Integer> add = BinaryOperator.minBy(Comparator.naturalOrder());
System.out.println(add.apply(1, 2));
}
@Test
public void maxBy() {
BinaryOperator<Integer> add = BinaryOperator.maxBy(Comparator.naturalOrder());
System.out.println(add.apply(1, 2));
}
@Test
public void maxBy2() {
@Data
class User {
private String name;
}
User user1 = new User();
user1.setName("123");
User user4 = new User();
user4.setName("1234");
BinaryOperator<User> maxBy2 = BinaryOperator.maxBy(Comparator.comparing(User::getName));
System.out.println(maxBy2.apply(user1, user4));
}
2.3 引用值 , 而不是變量
Java 8 可以引用非 final 變量,但是該變量在既成事實(shí)上必須是
final 。雖然無需將變量聲明為 final ,但在 Lambda 表達(dá)式中,也無法用作非終態(tài)變量。如果堅(jiān)持用作非終態(tài)變量,編譯器就會報(bào)錯。
比如:
String str = "final";
// str = str.substring(1);如果不加當(dāng)前操作,下面Lambda不會報(bào)錯,但是如果對當(dāng)前字符串進(jìn)行操作,下面Lambda無法通過編譯。
BinaryOperator<String> finalAdd = (x, y) -> x + str + y;
System.out.println(add.apply("1", "2"));
// 如果必須要進(jìn)行操作,則可以在操作之后替換該變量`String finalStr = str;`,這樣就額可以通過編譯。
2.4 函數(shù)接口
函數(shù)接口是只有一個抽象方法的接口,用作 Lambda 表達(dá)式的類型。
Java中重要的函數(shù)接口
| 接口 | 參數(shù) | 返回類型 | 示例 |
|---|---|---|---|
| Predicate<T> | T | boolean | 這張唱片已經(jīng)發(fā)行了嗎 |
| Consumer<T> | T | void | 輸出一個值 |
| Function<T,R> | T | R | 獲得 Artist 對象的名字 |
| Supplier<T> | None | T | 工廠方法 |
| UnaryOperator<T> | T | T | 邏輯非 (!) |
| BinaryOperator<T> | (T, T) | T | 求兩個數(shù)的乘積 (*) |
好吧,這都是知識盲區(qū),既然見到了不得不學(xué)習(xí)一番。
- Predicate<T> 即對t進(jìn)行斷言,返回true或者false。
@Data
class Employee {
Employee(Integer id, Integer age, String fName) {
this.id = id;
this.age = age;
this.firstName = fName;
}
private Integer id;
private Integer age;
private String firstName;
}
Predicate<Employee> isAgeMoreThan = p -> p.getAge() > 18;
Predicate<Employee> isjack = p -> p.getAge() > 18 && p.getFirstName().equalsIgnoreCase("jack");
Predicate<Employee> isJon = p -> p.getAge() > 21 && p.getFirstName().equalsIgnoreCase("jon");
Employee e2 = new Employee(2, 13, "Martina");
Employee e3 = new Employee(3, 43, "Jack");
Employee e4 = new Employee(4, 26, "Jon");
System.out.println(isAgeMoreThan.test(e3));
System.out.println(isAgeMoreThan.test(e2));
System.out.println(isjack.test(e2));
System.out.println(isJon.test(e4));
- Consumer<T> 表示接受單個輸入?yún)?shù)并且不返回結(jié)果的操作。 與大多數(shù)其他功能界面不同, Consumer預(yù)計(jì)將通過副作用進(jìn)行操作。本人不成熟的見解:當(dāng)前類中的處理進(jìn)行封裝,更有利于調(diào)用,通過副作用進(jìn)行實(shí)現(xiàn)。
// JDK1.8實(shí)現(xiàn)
@Data
class User {
private String name;
User(String name) {
this.name = name;
}
}
User user = new User("max");
Consumer<User> consumer = user1 -> user1.setName(user1.getName() + " Zhao");
// Consumer<User> consumer = user1 ->{ user1.setName(user1.getName() + " Zhao");}可以加上括號做更復(fù)雜的操作。
consumer.accept(user);
System.out.println(user);
// JDK1.8之前的實(shí)現(xiàn),需要調(diào)用下面方法
public String accept(User user){
user1.setName(user1.getName() + " Zhao");
}
- Function<T,R> 表示接受一個參數(shù)并產(chǎn)生結(jié)果的函數(shù)。
Function<Integer, Integer> name = e -> e * 2;
Function<Integer, Integer> square = e -> e * e;
int value = name.andThen(square).apply(3);//36
int value2 = name.compose(square).apply(3);//18
int value3 = name.apply(3);//6
//返回一個執(zhí)行了apply()方法之后只會返回輸入?yún)?shù)的函數(shù)對象
Object identity = Function.identity().apply("MaxZhao");//MaxZhao
- Supplier<T> 是一個提供結(jié)果的函數(shù)接口,每次調(diào)用get()方法的時候才會創(chuàng)建對象。并且每次調(diào)用創(chuàng)建的對象都不一樣;
public class SupplierTest {
@Test
public void test() {
Supplier<String> supplier = () -> "字符串";
String str = supplier.get();
System.out.println("str = " + str);
//創(chuàng)建Supplier容器,此時并不會調(diào)用對象的構(gòu)造方法,即不會創(chuàng)建對象
Supplier<User> user = User::new;
//調(diào)用get()方法,此時會調(diào)用對象的構(gòu)造方法,即獲得到真正對象
user.get();
Supplier<User> user2 = () -> {
User user1 = new User();
//
return user1;
};
Supplier<User> user3 = () -> {
User user1 = new User() {
@Override
public void come(User t) {
System.out.println("User = " + t);
}
};
return user1;
};
}
}
@Data
class User {
private String name;
public void come(User t) {
System.out.println(t.getName() + " is come");
}
}
- UnaryOperator<T> Operator其實(shí)就是Function,函數(shù)有時候也叫作算子。算子在Java8中接口描述更像是函數(shù)的補(bǔ)充,和上面的很多類型映射型函數(shù)類似。它包含UnaryOperator和BinaryOperator。分別對應(yīng)單元算子和二元算子。
Function<Integer, Integer> name = e -> e * 2;
Function<Integer, Integer> square = e -> e * e;
int value = name.andThen(square).apply(3);//36
System.out.println("andThen value=" + value);
int value2 = name.compose(square).apply(3);//18
System.out.println("compose value2=" + value2);
int value3 = name.apply(3);//6
System.out.println("compose value2=" + value3);
//返回一個執(zhí)行了apply()方法之后只會返回輸入?yún)?shù)的函數(shù)對象
Object identity = Function.identity().apply("MaxZhao");//MaxZhao
System.out.println(identity);
- BinaryOperator<T> 上面也介紹到了,部分通用接口部分介紹中也體現(xiàn)了,這里就不講了。
2.5 類型推斷
某些情況下,用戶需要手動指明類型,建議大家根據(jù)自己或項(xiàng)目組的習(xí)慣,采用讓代碼最便于閱讀的方法。有時省略類型信息可以減少干擾,更易弄清狀況;而有時卻需要類型信息幫助理解代碼。經(jīng)驗(yàn)證發(fā)現(xiàn),一開始類型信息是有用的,但隨后可以只在真正需要時才加上類型信息。下面將介紹一些簡單的規(guī)則,來幫助確認(rèn)是否需要手動聲明參數(shù)類型?!菊?br> 下面是一些例子:
Map<String, Integer> oldWordCounts = new HashMap<String, Integer>();
Map<String, Integer> diamondWordCounts = new HashMap<>(); ?
useHashmap(new HashMap<>());
private void useHashmap(Map<String, String> values);
Java 7 中程序員可省略構(gòu)造函數(shù)的泛型類型,Java 8 更進(jìn)一步,程序員可省略 Lambda 表達(dá)式中的所有參數(shù)類型。再強(qiáng)調(diào)一次,這并不是魔法, javac 根據(jù) Lambda 表達(dá)式上下文信息就能推斷出參數(shù)的正確類型。程序依然要經(jīng)過類型檢查來保證運(yùn)行的安全性,但不用再顯式聲明類型罷了。這就是所謂的類型推斷?!菊?/p>
3 流——我最喜歡,不僅使處理易讀,更提升了速度(并行)
流使程序員得以站在更高的抽象層次上對集合進(jìn)行操作?!菊?/p>
3.1 從外部迭代到內(nèi)部迭代
傳統(tǒng)的迭代方式都是為循環(huán)操作,每次迭代集合類時,都需要寫很多樣板代碼。將
for 循環(huán)改造成并行方式運(yùn)行也很麻煩,需要修改每個 for 循環(huán)才能實(shí)現(xiàn)。
外部迭代Iterator:然而,外部迭代也有問題。首先,它很難抽象出本章稍后提及的不同操作;此外,它從本質(zhì)上來講是一種串行化操作。總體來看,使用 for 循環(huán)會將行為和方法混為一談。
另一種方法就是內(nèi)部迭代。 stream()方法的調(diào)用,它和 iterator() 的作用一樣。該方法不是返回一個控制迭代的 Iterator 對象,而是返回內(nèi)部迭代中的相應(yīng)接口: Stream 。
首先對比一下for、iterator、stream對User的操作:
List<User> userList = new ArrayList<>();
for (User user : userList) {
// **
}
Iterator<User> userIterator = userList.iterator();
User user;
while (userIterator.hasNext()) {
user = userIterator.next();
// **
}
userList.stream().filter(user1 -> {
// **
}).count();
Stream 是用函數(shù)式編程方式在集合類上進(jìn)行復(fù)雜操作的工具。【摘】
3.2 實(shí)現(xiàn)機(jī)制
filter、count兩種操作是否意味著需要兩次循環(huán)?事實(shí)上,類庫設(shè)計(jì)精妙,只需對藝術(shù)家列表迭代一次。
count 這樣最終會從 Stream 產(chǎn)生值的方法叫作及早求值方法。
Stream最終都會有終止操作;
3.3 常用的流操作
下面所有的userList為:List<User> userList = Arrays.asList(new User("Jack"), new User("Jon"));
3.3.1 collect(toList())
collect(Collectors.toList())方法由 Stream 里的值生成一個列表,是一個及早求值操作。
List<String> collected = Stream.of("a", "b", "c").collect(Collectors.toList());
System.out.println(Objects.equals(Arrays.asList("a", "b", "c"), collected));//true
3.3.2 map,可以接受Function參數(shù)
如果有一個函數(shù)可以將一種類型的值轉(zhuǎn)換成另外一種類型, map 操作就可以使用該函數(shù),將一個流中的值轉(zhuǎn)換成一個新的流。
userList.stream().map(user -> user.getName().length()).collect(Collectors.toList());//[4, 3]
// 等價(jià)與
userList.stream().map(user -> {return user.getName().length();}).collect(Collectors.toList());//[4, 3]
3.3.3 filter,可以接受Predicate參數(shù)
userList.stream().filter(user -> user.getName().equalsIgnoreCase("Jon")).collect(Collectors.toList());//[StreamTest.User(name=Jon)]
3.3.4 flatMap
flatMap 方 法 可 用 Stream 替 換 值, 然 后 將 多 個 Stream 連 接 成 一 個 Stream
(如圖 3-7 所示)?!菊?/p>
List<Integer> together = Stream.of(asList(1, 2), asList(3, 4))
.flatMap(numbers -> numbers.stream())
.collect(toList());
assertEquals(asList(1, 2, 3, 4), together);
【摘】
3.3.5 max 和 min
userList.stream().min(Comparator.comparing(user -> user.getName().length())).get();//StreamTest.User(name=Jon)
max``min返回的是Optional,也是一個新特性,可以取代三元運(yùn)算符。
3.3.7 reduce
reduce 操作可以實(shí)現(xiàn)從一組值中生成一個值。
Stream.of(1, 2, 3).reduce(0, (acc, element) -> acc + element);//6
3.3.8 整合操作
Stream.of(1, 2, 3).map(item -> item * item).filter(item -> item > 4).collect(Collectors.toList());//[9]
3.6 高階函數(shù)
高階函數(shù)是指接受另外一個函
數(shù)作為參數(shù),或返回一個函數(shù)的函數(shù)。高階函數(shù)不難辨認(rèn):看函數(shù)簽名就夠了。如果函數(shù)的參數(shù)列表里包含函數(shù)接口,或該函數(shù)返回一個函數(shù)接口,那么該函數(shù)就是高階函數(shù)。【摘】
3.10 進(jìn)階練習(xí)
- 只用 reduce 和 Lambda 表達(dá)式寫出實(shí)現(xiàn) Stream 上的 map 操作的代碼,如果不想返回
Stream ,可以返回一個 List 。
// reduce實(shí)現(xiàn)map
private <T, R> Stream<R> map(Stream<T> stream, Function<T, R> fun) {
return stream.reduce(new ArrayList<R>().stream(), // Stream<R>是reduce參數(shù)的的U
(u, t) -> Stream.concat(u, Stream.of(fun.apply(t))),
(a, b) -> Stream.concat(a, b));
}
// 測試方法
// Artist(String name, String nation)
@Test
public void test23() {
System.out.println(
map(Stream.of(
new Artist("a", "aaa"),
new Artist("b", "bbb"),
new Artist("c", "ccc")
), t -> t.getNation()).collect(Collectors.toList())
); // [aaa,bbb,ccc]
}
- 只用 reduce 和 Lambda 表達(dá)式寫出實(shí)現(xiàn) Stream 上的 filter 操作的代碼,如果不想返回
Stream ,可以返回一個 List 。
// reduce實(shí)現(xiàn)filter
private <T> Stream<T> filter(Stream<T> stream, Predicate<T> pre) {
return stream.reduce(new ArrayList<T>().stream(), // Stream<T>是reduce的U
(u, t) -> {
if (pre.test(t))
return Stream.concat(u, Stream.of(t));
return u;
},
(a, b) -> Stream.concat(a, b)
);
}
// 測試方法
@Test
public void test24() {
System.out.println(
filter(Stream.of(
new Artist("a", "aaa"),
new Artist("b", "bb"),
new Artist("c", "c")
), t -> t.getNation().length() >= 2).collect(Collectors.toList())
);
} // [Artist("a", "aaa"), Artist("b", "bb")]
上面來自這里
4 類庫
有點(diǎn)復(fù)雜,建議直接看書,總結(jié)不出來。
4.10 Optional
上面提到過,Optional 是為核心類庫新設(shè)計(jì)的一個數(shù)據(jù)類型,用來替換 null 值。
5 高級集合類和收集器
5.1 方法引用
例如下面兩種語法結(jié)果相同。
user->user.getName()
user::getName
例如創(chuàng)建對象
User::new
更復(fù)雜的還是看書。
5.2 元素順序
另外一個尚未提及的關(guān)于集合類的內(nèi)容是流中的元素以何種順序排列。讀者可能知道,一些集合類型中的元素是按順序排列的,比如 List ;而另一些則是無序的,比如 HashSet 。
增加了流操作后,順序問題變得更加復(fù)雜。
直觀上看,流是有序的,因?yàn)榱髦械脑囟际前错樞蛱幚淼摹_@種順序稱為出現(xiàn)順序。出現(xiàn)順序的定義依賴于數(shù)據(jù)源和對流的操作。
在一個有序集合中創(chuàng)建一個流時,流中的元素就按出現(xiàn)順序排列,因此,List集合代碼總是可以通過?!菊?br>
如果集合本身就是無序的,由此生成的流也是無序的。 HashSet 就是一種無序的集合,因此不能保證程序每次都通過。【摘】
這 會 帶 來 一 些 意 想 不 到 的 結(jié) 果, 比 如 使 用 并 行 流 時, forEach 方 法 不 能 保 證 元 素 是按順序處理的(第 6 章會詳細(xì)討論這些內(nèi)容)。如果需要保證按順序處理,應(yīng)該使用forEachOrdered 方法,它是你的朋友?!菊?/p>
5.3 使用收集器
前面我們使用過 collect(toList()) ,在流中生成列表。顯然, List 是能想到的從流中生成的最自然的數(shù)據(jù)結(jié)構(gòu),但是有時人們還希望從流生成其他值,比如 Map 或 Set ,或者你希望定制一個類將你想要的東西抽象出來。
5.3.1 轉(zhuǎn)換成其他集合
list轉(zhuǎn)Map
System.out.println(Stream.of(new User("jdck")).collect(Collectors.toMap(User::getName, Function.identity())));
System.out.println(Stream.of(new User("jdck")).collect(Collectors.toMap(User::getName, User::getName)));
5.3.3 數(shù)據(jù)分塊
通過一個例子,很好理解
System.out.println(Stream.of(new User("jdck")).collect(partitioningBy(t -> t.getName().equalsIgnoreCase("jdck"))));
System.out.println(Stream.of(new User("jdck")).collect(partitioningBy(t -> t.getName().equalsIgnoreCase("join"))));
//{false=[], true=[User(name=jdck)]}
//{false=[User(name=jdck)], true=[]}
5.3.4 數(shù)據(jù)分組
分組是分塊的子集(可能想等),下面有個例子:
System.out.println(Stream.of(new User("jdck")).collect(groupingBy(t -> t.getName().equalsIgnoreCase("jdck"))));
System.out.println(Stream.of(new User("jdck")).collect(groupingBy(t -> t.getName().equalsIgnoreCase("join"))));
// {true=[User(name=jdck)]}
// {false=[User(name=jdck)]}
讀者可能知道 SQL 中的 group by 操作,我們的方法是和這類似的一個概念,只不過在 Stream 類庫中實(shí)現(xiàn)了而已。
5.3.5 字符串
字符串的joining方法如下:
System.out.println(userList.stream().map(User::getName).collect(Collectors.joining(",", "[", "]")));//[Jack,Jon]
System.out.println(userList.stream().map(User::getName).collect(Collectors.joining(",")));//Jack,Jon
System.out.println(userList.stream().map(User::getName).collect(Collectors.joining()));//JackJon
這里使用 map 操作提取出藝術(shù)家的姓名,然后使用 Collectors.joining 收集流中的值,該方法可以方便地從一個流得到一個字符串,允許用戶提供分隔符(用以分隔元素)、前綴和后綴。
下面這個結(jié)果是報(bào)錯的:
List<Integer> a = Arrays.asList(1, 2, 3);
List<Integer> a2 = Arrays.asList(4, 5, 6);
a.addAll(a2);
調(diào)試之后發(fā)現(xiàn),Arrays.asList()返回類型是Array$ArrayList@
而new ArrayList()<> 返回類型是ArrayList@。
6 數(shù)據(jù)并行化——高潮來了
6.1 并行和并發(fā)
并發(fā)和并行不是一個概念!
并發(fā)是兩個任務(wù)共享時間段,并行則是兩個任務(wù)在同一時間發(fā)生,比如運(yùn)行在多核 CPU上。
并行化是指為縮短任務(wù)執(zhí)行時間,將一個任務(wù)分解成幾部分,然后并行執(zhí)行。這和順序執(zhí)行的任務(wù)量是一樣的,區(qū)別就像用更多的馬來拉車,花費(fèi)的時間自然減少了。實(shí)際上,和順序執(zhí)行相比,并行化執(zhí)行任務(wù)時,CPU 承載的工作量更大。【摘】(簡直廢話)
6.2 為什么并行化如此重要
硬件越來越給力。
6.3 并行化流操作
下面兩個操作都可以實(shí)現(xiàn)并行:
userList.stream().parallel().map(User::getName).collect(Collectors.toList());
userList.parallelStream().map(User::getName).collect(Collectors.toList());
并不是并行速度就快,要看運(yùn)行時的環(huán)境。在一個四核電腦上,如果有 10 張專輯,串行化代碼的速度是并行化代碼速度的 8 倍;如果將專輯數(shù)量增至 100 張,串行化和并行化速度相當(dāng);如果將專輯數(shù)量增值 10 000 張,則并行化代碼的速度是串行化代碼速度的 2.5 倍。
6.4 模擬系統(tǒng)
pass:書上說的很詳細(xì),這里只是把我不了解的拿出來。
// 使用蒙特卡洛模擬法并行化模擬擲骰子事件
public Map<Integer, Double> parallelDiceRolls() {
double fraction = 1.0 / N;
return IntStream.range(0, N)
.parallel()
.mapToObj(twoDiceThrows())
.collect(groupingBy(side -> side,
summingDouble(n -> fraction)));
}
上面的完全看不懂的樣子,查一下API。
IntStream.range(0, 100),是生成[0-100)的區(qū)間為1的stream的流。
6.6 性能
在前面我簡要提及了影響并行流是否比串行流快的一些因素,現(xiàn)在讓我們仔細(xì)看看它們。理解哪些能工作、哪些不能工作,能幫助在如何使用、什么時候使用并行流這一問題上做出明智的決策。影響并行流性能的主要因素有 5 個,依次分析如下。【摘】
? 數(shù)據(jù)大小
輸入數(shù)據(jù)的大小會影響并行化處理對性能的提升。將問題分解之后并行化處理,再將結(jié)果合并會帶來額外的開銷。因此只有數(shù)據(jù)足夠大、每個數(shù)據(jù)處理管道花費(fèi)的時間足夠多時,并行化處理才有意義。6.3 節(jié)討論過。
? 源數(shù)據(jù)結(jié)構(gòu)
每個管道的操作都基于一些初始數(shù)據(jù)源,通常是集合。將不同的數(shù)據(jù)源分割相對容易,這里的開銷影響了在管道中并行處理數(shù)據(jù)時到底能帶來多少性能上的提升。
? 裝箱
處理基本類型比處理裝箱類型要快。
? 核的數(shù)量
極端情況下,只有一個核,因此完全沒必要并行化。顯然,擁有的核越多,獲得潛在性能提升的幅度就越大。在實(shí)踐中,核的數(shù)量不單指你的機(jī)器上有多少核,更是指運(yùn)行時你的機(jī)器能使用多少核。這也就是說同時運(yùn)行的其他進(jìn)程,或者線程關(guān)聯(lián)性(強(qiáng)制線程在某些核或 CPU 上運(yùn)行)會影響性能。
? 單元處理開銷
比如數(shù)據(jù)大小,這是一場并行執(zhí)行花費(fèi)時間和分解合并操作開銷之間的戰(zhàn)爭?;ㄔ诹髦忻總€元素身上的時間越長,并行操作帶來的性能提升越明顯。【摘】
我們可以根據(jù)性能的好壞,將核心類庫提供的通用數(shù)據(jù)結(jié)構(gòu)分成以下 3 組。
? 性能好
ArrayList 、數(shù)組或 IntStream.range ,這些數(shù)據(jù)結(jié)構(gòu)支持隨機(jī)讀取,也就是說它們能輕而易舉地被任意分解。
? 性能一般
HashSet 、 TreeSet ,這些數(shù)據(jù)結(jié)構(gòu)不易公平地被分解,但是大多數(shù)時候分解是可能的。
? 性能差
有些數(shù)據(jù)結(jié)構(gòu)難于分解,比如,可能要花 O(N) 的時間復(fù)雜度來分解問題。其中包括LinkedList ,對半分解太難了。還有 Streams.iterate 和 BufferedReader.lines ,它們長度未知,因此很難預(yù)測該在哪里分解?!菊?/p>
7 測試、調(diào)試和重構(gòu)
7.1 重構(gòu)候選項(xiàng)
使用 Lambda 表達(dá)式重構(gòu)代碼有個時髦的稱呼: Lambda 化(讀作 lambda-fi-cation ,執(zhí)行重構(gòu)的程序員叫作 lamb-di-fiers 或者有責(zé)任心的程序員)。Java 8 中的核心類庫就曾經(jīng)歷過這樣一場重構(gòu)。在選擇內(nèi)部設(shè)計(jì)模型時,想想以何種形式向外展示 API 是大有裨益的。【摘】
7.1.1 進(jìn)進(jìn)出出 、 搖搖晃晃
使用 Lambda 表達(dá)式更好地面向?qū)ο缶幊?OOP),面向?qū)ο缶幊痰暮诵闹皇欠庋b局部狀態(tài).
7.6 Lambda日志和打印消息,解決方案 : peak
使用 peek 方法
Set<String> nationalities
= album.getMusicians()
.filter(artist -> artist.getName().startsWith("The"))
.map(artist -> artist.getNationality())
.peek(nation -> System.out.println("Found nationality: " + nation))
.collect(Collectors.<String>toSet());
8 設(shè)計(jì)和架構(gòu)的原則
8.1 Lambda 表達(dá)式改變了設(shè)計(jì)模式
以曾經(jīng)風(fēng)靡一時的單例模式為例,該模式確保只產(chǎn)生一個對象實(shí)例。在過去十年中,人們批評它讓程序變得更脆弱,且難于測試。敏捷開發(fā)的流行,讓測試顯得更加重要,單例模式的這個問題把它變成了一個反模式:一種應(yīng)該避免使用的模式。
8.1.2 策略模式
這里實(shí)現(xiàn)了壓縮方式的選擇gzip、zip(來源書中)

定義壓縮數(shù)據(jù)的策略接口
public interface CompressionStrategy {
public OutputStream compress(OutputStream data) throws IOException;
}
使用 gzip 算法壓縮數(shù)據(jù)
public class GzipCompressionStrategy implements CompressionStrategy {
@Override
public OutputStream compress(OutputStream data) throws IOException {
return new GZIPOutputStream(data);
}
}
使用 zip 算法壓縮數(shù)據(jù)
public class ZipCompressionStrategy implements CompressionStrategy {
@Override
public OutputStream compress(OutputStream data) throws IOException {
return new ZipOutputStream(data);
}
}
在構(gòu)造類時提供壓縮策略
public class Compressor {
private final CompressionStrategy strategy;
public Compressor(CompressionStrategy strategy) {
this.strategy = strategy;
}
public void compress(Path inFile, File outFile) throws IOException {
try (OutputStream outStream = new FileOutputStream(outFile)) {
Files.copy(inFile, strategy.compress(outStream));
}
}
}
如果使用這種傳統(tǒng)的策略模式實(shí)現(xiàn)方式,可以編寫客戶代碼創(chuàng)建一個新的 Compressor ,并
且使用任何我們想要的策略(如例 8-13 所示)。
例 8-13 使用具體的策略類初始化Compressor
Compressor gzipCompressor = new Compressor(new GzipCompressionStrategy());
gzipCompressor.compress(inFile, outFile);
Compressor zipCompressor = new Compressor(new ZipCompressionStrategy());
zipCompressor.compress(inFile, outFile);
和前面討論的命令者模式一樣,使用 Lambda表達(dá)式或者方法引用可以去掉樣板代碼。在
這里,我們可以去掉具體的策略實(shí)現(xiàn),使用一個方法實(shí)現(xiàn)算法,這里的算法由構(gòu)造函數(shù)
中對應(yīng)的 OutputStream 實(shí)現(xiàn)。使用這種方式,可以完全舍棄 GzipCompressionStrategy 和
ZipCompressionStrategy 類。例 8-14 展示了使用方法引用后的代碼。
例 8-14 使用方法引用初始化 Compressor
Compressor gzipCompressor = new Compressor(GZIPOutputStream::new);
gzipCompressor.compress(inFile, outFile);
Compressor zipCompressor = new Compressor(ZipOutputStream::new);
zipCompressor.compress(inFile, outFile);
本文地址
讀《Java 8 函數(shù)式編程》