Java 丢了好多年,最近在拣起来,首先当然是了解这么多年来它的变化,于是发现了 Java 8 的java.util.stream。在学习和试验的过程中,相比较于 C# 和 javascript,有那么些心得,作文以记之。
创新互联是专业的横山网站建设公司,横山接单;提供网站建设、网站设计,网页设计,网站设计,建网站,PHP网站建设等专业做网站服务;采用PHP框架,可快速的进行横山网站开发网页制作和功能扩展;专业做搜索引擎喜爱的网站,专业的做网站团队,希望更多企业前来合作!
早些时间写过一篇《ES6 的 for..of 和 Generator,从伪数组 jQuery 对象说起》,和这个主题有点关系。其实我记得还有一篇讲 C# 的,没找到,也许只是想过,没写成,成了虚假记忆。
前言
之所以把 C#、JavaScript 和 Java 三种语言的实现写在一起,主要是为了放在一起有一个类比,可能会有助于理解。
集合数据
C# 的集合数据基类是 Collection
JavaScript 最常见的集合数据类型就是数组,自 ES6 发布以后,这个范围扩展到了 iterable 对象。不过这里要讨论的内容都是在Array.prototype 中实现的。除此之外,underscore、lodash 这些第三方库中也实现了很多集合数据处理的方法,但不在本文讨论内容之内。
Java 的集合类型由 Collection
示例语言版本
遍历
问题提出
给定一个名称列表,数组类型, ["Andy", "Jackson", "Yoo"],要求遍历出到的控制台。
C# 的遍历
对于集合来说,最常用的就是遍历,不过 for,foreach, while 之类大家都耳熟能详了,不再多说。这里说的是 forEach() 方法。
很遗憾,C# 的 Linq 扩展 里没有提供 ForEach() 方法,不过 All(IEnumerable
All() 的意思是,所有元素都符合条件则返回 true,所有只要有一个不符合条件,返回了 false,则中止遍历,返回false;Any() 的意思是只要发现有元素符合条件则返回 true。
Func
因此,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 的遍历
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 的遍历
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() 来调用。
Java 的遍历
- String[] names = { "Andy", "Jackson", "Yoo" };
- List
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]
C# 中过滤使用 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() 参数提供的那个筛选函数。
JavaScript 中有 Array.prototype.filter
- 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.*
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
- List
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"]
C# 通过 Select() 来进行映射处理。
- int[] scores = { 46, 74, 20, 37, 98, 93, 98, 48, 33, 15 };
- string[] levels = scores
- .Select(score => score >= 60 ? "PASS" : "REJECT")
- .ToArray();
JavaScript 通过 Array.prototype.map 来进行映射处理。
- const scores = [46, 74, 20, 37, 98, 93, 98, 48, 33, 15];
- const levels = scores.map(score => {
- return score >= 60 ? "PASS" : "REJECT";
- });
Java 的 Stream 提供了 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
生成查找表(如哈希表)
查找表在数据结构里的意义还是比较宽的,其中通过哈希算法实现的称为哈希表。C# 中通常是用 Directory
提出问题
现在有一个姓名列表,是按学号从 1~7 排列的,需要建立一个查找到,使之能通过姓名很容易找到对应的学号。
- ["Andy", "Jackson", "Yoo", "Rose", "Lena", "James", "Stephen"]
期望结果
- Andy => 1
- Jackson => 2
- Yoo => 3
- Rose => 4
- Lena => 5
- James => 6
- Stephen => 7
C# 使用 ToDictionary()
- string[] names = { "Andy", "Jackson", "Yoo", "Rose", "Lena", "James", "Stephen" };
- int i = 1;
- Dictionary
map = names.ToDictionary(n => n, n => i++);
C# Linq 扩展提供的若干方法都没有将序号传递给处理函数,所以上例中采用了临时变量计数的方式来进行。不过有一个看起来好看一点的办法,用 Enumerable.Range() 先生成一个序号的序列,再基于这个序列来处理
- string[] names = { "Andy", "Jackson", "Yoo", "Rose", "Lena", "James", "Stephen" };
- IEnumerable
indexes = Enumerable.Range(0, names.Length); - Dictionary
map = indexes.ToDictionary(i => names[i], i => i + 1);
JavaScript 的两种处理办法
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 的 Collectors
Java 的处理函数也没有传入序号,所以在 Java 中的实例和 C# 类似。不过,***种方法不可用,因为 Java Lambda 的实现相当于是匿名类对接口的实现,只能访问局部的 final 变量,i 要执行 i++ 操作,显然不是 final 的,所以只能用第二种办法
- final String[] names = { "Andy", "Jackson", "Yoo", "Rose", "Lena", "James", "Stephen" };
- Map
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# 的实现
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 部分就是采用聚合来实现的。
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 的初始值在后,这是有区别的。参数顺序嘛,注意一下就行了。
Java 中使用 Stream::reduce 进行聚合处理
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 数组,且该数组按分数从高到低排序
C# 实现
- 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
JavaScript 实现
- 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来判断是小于、等于还是大于。
Java 实现
- 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);
新闻名称:类比C#、JavaScript和Java的集合数据处理
链接地址:http://www.csdahua.cn/qtweb/news25/74225.html
网站建设、网络推广公司-快上网,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 快上网