Java8 Stream 的最佳實踐
java8stream提供了對于集合類的流失處理,其具有以下特點:
Lazy Evaluation(長度可以無限)
只能使用一次
內部迭代
Lazy Evaluation類似函式式中的LazyList,只有在需要時才去求值,減少了記憶體消耗,Java中可以用Iterator模擬,只有在進行終端操作時,stream才會執行,但是這個延遲計算不能保證流中的某個值單獨延遲,需要時單獨分配資源,
內部迭代的意思是我們告訴程式要實作的功能,迭代由程式自己控制,如filter時,我們只提供Predicate,而不是自己寫for回圈,至于程式自己是如何實作過濾資料的,我們并不關心,可能隨著stream類別庫的迭代,實作效率會逐步提升,PS:新版本的Java類別庫String終于可以直接拼串了 ??,而且性能很好,
使用Stream的原則:
可讀性、bugfree、短
Do
- 開發程序中最常用的Stream實作就是filter map reduce/collect操作,這種處理可以替代很大部分的(至少50%)的for回圈,
// 讀入資料,筆試用
Scanner sc = new Scanner(System.in);
String ss = sc.nextLine();
int[] nums = Arrays.stream(ss.split("\\s")).mapToInt(Integer::valueOf).toArray();
// flatMap只使用一次
// 這個例子來自于On Java 8
// 字串打散,注意不要打散成char
// 常見的模式(方法回傳流)
public static Stream<String> stream(String filePath) throws Exception {
return Files.lines(Paths.get(filePath))
.skip(1) // First (comment) line
.flatMap(line ->
Pattern.compile("\\W+").splitAsStream(line));
}
// 一對多關系,保存到資料庫,命令式編程需要兩層for回圈
List<Role> roles = users.stream().flatMap(user -> user.getRoles().stream()).collect(toList());
saveBatch(roles);
// 生成隨機陣列
int[] nums = new Random().ints(0, 100).limit(10).toArray();
// optional流的處理
stream1.filter(Optional::isPresent).map(Optional::get).collect(toList());
stream2.map(opt -> opt.orElse(defaultValue)).collect(toList());
// forEach
stream3.forEach(System.out::println);
// 生成Map,這種最終操作我后面會發文章詳細分析Collectors工具類
Map<Long, User> idToUserMap = users.stream().collect(toMap(User::getId, it -> it));
// 獲取角色到用戶的映射(一對多)
HashMultiMap<Long, User> roleIdToUsersMap = userVos.stream().collect(toMultiMap(UserVo::getRoleId, UserVo::getUser, HashMultimap::create));
// 分組
Map<Integer, List<User>> usersByLevelMap = users.collect(groupingBy(User::getLevel));
// 字串拼接
subscriptions.stream().map(Object::toString).collect(joining(",", "(", ")"));
// 引數校驗
boolean validated = request.getResources().stream().allMatch(authorities::contains);
if (!validated) throw ...
IntStream::sum, max, min, average, count...
// 教學意義
質數流,完全數流...
- 開發中的大部分時候我們不需要特別在意性能,僅在需要時優化,
比如你需要一個字串匹配演算法,需要自己實作一些特殊功能,先寫出暴力匹配,之后再優化,
比如我想獲取一個前十名的排行榜,我不必在自己撰寫優先佇列,或者使用其他類別庫,當需要優化時,再考慮優化,
// 至少我們可以確定:使用stream的內部迭代中時間復雜度最多是nlogn,
Comparator<User> compScore = Comparator.comparingDouble(User::getScore).reversed();
List<User> top10 = users.stream().sorted(compScore).limit(10).collect(toList());
Don’t
畢竟Java不是純函式式的語言,還要前向兼容,所以只能部分利用函式式的思想,以下這些做法在開發中應該避免,
- 忽略空指標,對于可能存在空指標的類直接使用Stream,特別是類中具有復雜的參考關系時,比如我通過Feign獲取的物件,即使使用Optional,也會令可讀性大大下降,
- 包含復雜的例外處理,例外處理時不要使用stream,stream天生和例外相沖突,函式式編程中例外也是一類副作用,兩種很難相容,Java也沒有Either類,盡量少用,非要用可以使用Optional或者 try-catch 包裝,
- 使用stream類中的flatMap實作多重回圈,這樣可讀性很差,就想try-catch嵌套一樣難受,英文里叫boilerplate,不知道中文用哪個詞,大意就是跟八股文一樣繁雜,
- 使用forEach,盡量不要使用forEach,forEach只限定在幾種特定的編程模式,這個方法很難除錯,
- 在代碼面試或筆試中使用,因為人家面試官考的就是你對命令式編程的理解、演算法與資料結構、函式內部狀態的追蹤,而且代碼筆試中特別看重性能,stream會生成很多不可變的中間變數,占用空間,在在線編程環境上性能不穩定,
- 使用parallel方法,這個方法對于性能的提升有限,只有在進行大量計算時有用,幾乎用不到,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/471751.html
標籤:Java
