Java 丟了好多年,最近在揀起來,首先固然是瞭解這麼多年來它的變化,因而發現了 Java 8 的
java.util.stream
。在學習和試驗的過程當中,相比較於 C# 和 javascript,有那麼些心得,做文以記之。javascript早些時間寫過一篇《ES6 的 for..of 和 Generator,從僞數組 jQuery 對象提及》,和這個主題有點關係。其實我記得還有一篇講 C# 的,沒找到,也許只是想過,沒寫成,成了虛假記憶。html
之因此把 C#、JavaScript 和 Java 三種語言的實現寫在一塊兒,主要是爲了放在一塊兒有一個類比,可能會有助於理解。java
C# 的集合數據基類是 Collection<T>,它實現了 ICollection<T>接口,而 ICollection<T>
又從 IEnumerable<T> 接口繼承——實際上要討論的內容都基於 IEnumerable<T>
接口。另外還有一個非泛型的 IEnumerable
接口,不過建議你們儘可能使用泛型,因此這個非泛型的接口就當我沒說。順便提一句,數組也是實現了 IEnumerable<T>
接口的。System.Linq
中提供的擴展大大方便了集合處理過程。算法
JavaScript 最多見的集合數據類型就是數組,自 ES6 發佈之後,這個範圍擴展到了 iterable 對象。不過這裏要討論的內容都是在 Array.prototype 中實現的。除此以外,underscore、lodash 這些第三方庫中也實現了不少集合數據處理的方法,但不在本文討論內容以內。segmentfault
Java 的集合類型由 Collection<E> 接口定義。本文討論的內容是 Java 8 的特性,在 java.util.stream
包中實現,由 Collection<E>.stream()
引入。api
- 後面示例中的部分 C# 語句可能須要支持 6.0 語言版本的編譯器,如 Visual Studio 2015 或者 Visual Studio "15"
- JavaScript 代碼都使用了 ES6 語法,目前大部分瀏覽器支持,Node 5 也徹底支持。
- Java 要求 Java 8(或 1.8)版本
給定一個名稱列表,數組類型, ["Andy", "Jackson", "Yoo"]
,要求遍歷出到的控制檯。數組
對於集合來講,最經常使用的就是遍歷,不過 for
,foreach
, while
之類你們都耳熟能詳了,再也不多說。這裏說的是 forEach()
方法。瀏覽器
很遺憾,C# 的 Linq 擴展 裏沒有提供 ForEach()
方法,不過 All(IEnumerable<T>, Func<T, Boolean>)
和 Any(IEnumerable<T>, Func<T, Boolean>)
均可以代替。這兩個方法的區別就在於第二個參數 Func<T, Boolean>
的返回值。這兩個方法都會遍歷集合,對集合中的每一個元素依次調用第二個參數,Func<T, Boolean>
所指的委託方法,並檢查其返回值,All()
檢查到 false
停止遍歷,而 Any()
檢查到 true
停止遍歷。數據結構
All()
的意思是,全部元素都符合條件則返回true
,全部只要有一個不符合條件,返回了false
,則停止遍歷,返回false
;Any()
的意思是隻要發現有元素符合條件則返回true
。oracle
Func<T, Boolean>
是一個公用委託。Func<...>
系列公用委託都用於委託帶有返回值的的方法,全部Func<..., TResult>
都是最後一個參數TResult
表明返回值類型。
所以,C# 的遍歷輸出能夠這樣實現
string[] names = { "Andy", "Jackson", "Yoo" }; names.All(name => { Console.WriteLine(name); return true; });
string[] names = { "Andy", "Jackson", "Yoo" }; names.Any(name => { Console.WriteLine(name); return false; });
有 Lambda 就是好
JavaScript 的 Array 實現了 forEach
實例方法,即 Array.prototype.forEach()。
對於 JavaScript 的數組,能夠這樣遍歷
var names = ["Andy", "Jackson", "Yoo"]; names.forEach(name => { console.log(name); });
對於 JavaScript 的僞數組,能夠這樣
var names = { 0: "Andy", 1: "Jackson", 2: "Yoo", length: 3 }; [].forEach.call(names, name => { console.log(name); });
jQuery 是一個經常使用的 JavaScript 庫,它封裝的對象都是基於僞數組的,因此 jQuery 中常常用到遍歷。除了網頁元素集合外,jQuery 也能夠遍歷普通數組,有兩種方式
能夠直接把數組做爲第一個參數,處理函數做爲第二個參數調用 $.each()
。
const names = ["Andy", "Jackson", "Yoo"]; $.each(names, (i, name) => { console.log(name); });
也能夠把數組封裝成一個 jQuery 對象($(names)
),再在這個 jQuery 對象上調用 eash()
方法。
const names = ["Andy", "Jackson", "Yoo"]; $(names).each((i, name) => { console.log(name); });
兩種方法的處理函數都同樣,可是要注意,這和原生 forEach()
的處理函數有點不一樣。jQuery 的 each()
處理函數,第一個參數是序號,第二個參數是數組元素;而原生 forEach()
的處理函數正好相反,第一個參數是數組元素,第二個參數纔是序號。
另外,$.each()
對僞數組一樣適用,不須要經過 call()
來調用。
String[] names = { "Andy", "Jackson", "Yoo" }; List<String> list = Arrays.asList(names); list.forEach(name -> { System.out.println(name); });
給出一組整數,須要將其中能被 3 整除選出來
[46, 74, 20, 37, 98, 93, 98, 48, 33, 15]
指望結果
[93, 48, 33, 15]
Where()
擴展int[] data = { 46, 74, 20, 37, 98, 93, 98, 48, 33, 15 }; int[] result = data.Where(n => n % 3 == 0).ToArray();
注意:Where()
的結果即不是數組也不是 List,須要經過 ToArray()
生成數組,或者經過 ToList()
生成列表。Linq 要在 ToArray()
或者 ToList()
或者其它某些操做的時候纔會真正遍歷,依次執行 Where()
參數提供的那個篩選函數。
const data = [46, 74, 20, 37, 98, 93, 98, 48, 33, 15]; const result = data.filter(n => { return n % 3 === 0; });
Java 中能夠經過 java.util.stream.IntStream.of()
來從數組生成 stream 對象
final int[] data = { 46, 74, 20, 37, 98, 93, 98, 48, 33, 15 }; int[] result = IntStream.of(data) .filter(n -> n % 3 == 0) .toArray();
須要注意的是,Arrays.asList(data).stream()
看起來也能夠生成 stream 對象,可是經過調試會發現,這是一個 Stream<int[]>
而不是 Stream<Integer>
。緣由是 asList(T ...a)
其參數可變參數,並且要求參數類型是類,因此 asList(data)
是把 data
做爲一個 int[]
類型參數而不是 int
類型的參數數據。若是要從 int[]
生成 List<Integer>
,還得經過 IntStream
來處理
List<Integer> list = IntStream.of(data) .boxed() .collect(Collectors.toList());
映射處理是指將某種類型的集合,將其元素依次映射成另外一種類型,產生一個新類型的集合。新集合中的每一個元素都與原集中的一樣位置的元素有對應關係。
這裏提出一個精典的問題:成績轉等級,不過爲了簡化代碼(switch 或多重 if 語句代碼比較長),改成判斷成績是否及格,60 分爲及格線。
偷個懶,就用上個問題的輸入 [46, 74, 20, 37, 98, 93, 98, 48, 33, 15]
,
指望結果:
["REJECT","PASS","REJECT","REJECT","PASS","PASS","PASS","REJECT","REJECT","REJECT"]
Select()
來進行映射處理。int[] scores = { 46, 74, 20, 37, 98, 93, 98, 48, 33, 15 }; string[] levels = scores .Select(score => score >= 60 ? "PASS" : "REJECT") .ToArray();
const scores = [46, 74, 20, 37, 98, 93, 98, 48, 33, 15]; const levels = scores.map(score => { return score >= 60 ? "PASS" : "REJECT"; });
mapToObj()
等方法處理映射final int[] scores = { 46, 74, 20, 37, 98, 93, 98, 48, 33, 15 }; String[] levels = IntStream.of(scores) .mapToObj(score -> score >= 60 ? "PASS" : "REJECT") .toArray(String[]::new);
與「篩選」示例不一樣,在「篩選」示例中,因爲篩選結果是 IntStream
,能夠直接調用 InStream::toArray()
來獲得 int[]
。
但在這個示例中,mapToObj()
獲得的是一個 Stream<String>
,類型擦除後就是 Stream
,因此 Stream::toArray()
默認獲得的是一個 Object[]
而不是 String[]
。若是想獲得 String[]
,須要爲 toArray()
指定 String[]
的構造函數,即 String[]::new
。
查找表在數據結構裏的意義仍是比較寬的,其中經過哈希算法實現的稱爲哈希表。C# 中一般是用 Directory<T>
,不過它是否是經過哈希實現我就不清楚了。不過 Java 中的 HashMap
和 Hashtable
,從名稱就看得出來是實現。JavaScript 的字面對象據稱也是哈希實現。
如今有一個姓名列表,是按學號從 1~7 排列的,須要創建一個查找到,使之能經過姓名很容易找到對應的學號。
["Andy", "Jackson", "Yoo", "Rose", "Lena", "James", "Stephen"]
指望結果
Andy => 1 Jackson => 2 Yoo => 3 Rose => 4 Lena => 5 James => 6 Stephen => 7
string[] names = { "Andy", "Jackson", "Yoo", "Rose", "Lena", "James", "Stephen" }; int i = 1; Dictionary<string, int> map = names.ToDictionary(n => n, n => i++);
C# Linq 擴展提供的若干方法都沒有將序號傳遞給處理函數,因此上例中採用了臨時變量計數的方式來進行。不過有一個看起來好看一點的辦法,用 Enumerable.Range() 先生成一個序號的序列,再基於這個序列來處理
string[] names = { "Andy", "Jackson", "Yoo", "Rose", "Lena", "James", "Stephen" }; IEnumerable<int> indexes = Enumerable.Range(0, names.Length); Dictionary<string, int> map = indexes.ToDictionary(i => names[i], i => i + 1);
JavaScript 沒有提供從 []
到 {}
的轉換函數,不過要作這個轉換也不是好麻煩,用 forEach
遍歷便可
var map = (function() { var m = {}; names.forEach((name, i) => { m[name] = i + 1; }); return m; })();
爲了避免讓臨時變量污染外面的做用域,上面的示例中採用了 IEFE 的寫法。不過,若是用 Array.prototype.reduce 則可讓代碼更簡潔一些
var map = names.reduce((m, name, i) => { m[name] = i + 1; return m; }, {});
Java 的處理函數也沒有傳入序號,因此在 Java 中的實例和 C# 相似。不過,第一種方法不可用,由於 Java Lambda 的實現至關因而匿名類對接口的實現,只能訪問局部的 final
變量,i
要執行 i++
操做,顯然不是 final
的,因此只能用第二種辦法
final String[] names = { "Andy", "Jackson", "Yoo", "Rose", "Lena", "James", "Stephen" }; Map<String, Integer> map = IntStream.range(0, names.length) .boxed() .collect(Collectors.toMap(i -> names[i], i -> i + 1));
我只能說
.boxed()
是個大坑啊,必定要記得調。
彙總處理就是合計啊,平均數啊之類的,使用方式都差很少,因此以合計(Sum)爲例。
彙總處理實際上是聚合處理的一個特例,因此就同一個問題,再用普通的聚合處理方式再實現一次。
已知全班成績,求班總分,再次用到了那個數組
[46, 74, 20, 37, 98, 93, 98, 48, 33, 15]
指望結果:562
C# 能夠直接使用 Sum()
方法求和
int[] scores = { 46, 74, 20, 37, 98, 93, 98, 48, 33, 15 }; int sum = scores.Sum();
聚合實現方式(用 Aggregate()
)
int[] scores = { 46, 74, 20, 37, 98, 93, 98, 48, 33, 15 }; int sum = scores.Aggregate(0, (total, score) => { return total + score; });
聚合實現方式要靈活得多,好比,改爲乘法就能夠算階乘。固然用於其它更復雜的狀況也不在話下。前面生成查找表的 JavaScript 部分就是採用聚合來實現的。
const scores = [46, 74, 20, 37, 98, 93, 98, 48, 33, 15]; const sum = scores.reduce((total, score) => { return total + score; }, 0);
注意 C# 的初始值在前,JavaScript 的初始值在後,這是有區別的。參數順序嘛,注意一下就好了。
IntStream
提供了 sum()
方法
final int[] scores = { 46, 74, 20, 37, 98, 93, 98, 48, 33, 15 }; final int sum = IntStream.of(scores).sum();
一樣也能夠用 reduce
處理
final int[] scores = { 46, 74, 20, 37, 98, 93, 98, 48, 33, 15 }; final int sum = IntStream.of(scores) .reduce(0, (total, score) -> total + score);
已知全班 7 我的,按學號 從 1~7 分別是
["Andy", "Jackson", "Yoo", "Rose", "Lena", "James", "Stephen"]
這 7 我的的成績按學號序,分別是
[66, 74, 43, 93, 98, 88, 83]
有 Student
數組結構
Student { number: int name: string score: int }
要求獲得全班 7 人的 student 數組,且該數組按分數從高到低排序
sealed class Student { public int Number { get; } public string Name { get; } public int Score { get; } public Student(int number, string name, int score) { Number = number; Name = name; Score = score; } public override string ToString() => $"[{Number}] {Name} : {Score}"; }
Student[] students = Enumerable.Range(0, names.Length) .Select(i => new Student(i + 1, names[i], scores[i])) .OrderByDescending(s => s.Score) .ToArray();
注意 C# 中排序有 OrderBy
和 OrderByDescending
兩個方法,通常狀況下只須要給一個映射函數,從原數據裏找到要用於比較的數據便可使用其 >
、<
等運算符進行比較。若是比例起來比較複雜的,須要提供第二個參數,一個 IComparer<T>
的實現
class Student { constructor(number, name, score) { this._number = number; this._name = name; this._score = score; } get number() { return this._number; } get name() { return this._name; } get score() { return this._score; } toString() { return `[${this.number}] ${this.name} : ${this.score}`; } }
const names = ["Andy", "Jackson", "Yoo", "Rose", "Lena", "James", "Stephen"]; const scores = [66, 74, 43, 93, 98, 88, 83]; var students = names .map((name, i) => new Student(i + 1, name, scores[i])) .sort((a, b) => { return b.score - a.score; });
JavaScript 的排序則是直接給個比較函數,根據返回的數值小於0、等於0或大於0來判斷是小於、等於仍是大於。
final class Student { private int number; private String name; private int score; public Student(int number, String name, int score) { this.number = number; this.name = name; this.score = score; } public int getNumber() { return number; } public String getName() { return name; } public int getScore() { return score; } @Override public String toString() { return String.format("[%d] %s : %d", getNumber(), getName(), getScore()); } }
final String[] names = { "Andy", "Jackson", "Yoo", "Rose", "Lena", "James", "Stephen" }; final int[] scores = { 66, 74, 43, 93, 98, 88, 83 }; Student[] students = IntStream.range(0, names.length) .mapToObj(i -> new Student(i + 1, names[i], scores[i])) .sorted((a, b) -> b.getScore() - a.getScore()) .toArray(Student[]::new);