类比C#、JavaScript和Java的集合数据处理

Java 丢了好多年,最近在拣起来,首先当然是了解这么多年来它的变化,于是发现了 Java 8 的java.util.stream。在学习和试验的过程中,相比较于 C# 和 javascript,有那么些心得,作文以记之。

早些时间写过一篇《ES6 的 for..of 和 Generator,从伪数组 jQuery 对象说起》,和这个主题有点关系。其实我记得还有一篇讲 C# 的,没找到,也许只是想过,没写成,成了虚假记忆。

前言

之所以把 C#、JavaScript 和 Java 三种语言的实现写在一起,主要是为了放在一起有一个类比,可能会有助于理解。

集合数据

C# 的集合数据基类是 Collection<T>,它实现了 ICollection<T>接口,而
ICollection<T> 又从 IEnumerable<T> 接口继承——实际上要讨论的内容都基于
IEnumerable<T> 接口。另外还有一个非泛型的 IEnumerable
接口,不过建议大家尽量使用泛型,所以这个非泛型的接口就当我没说。顺便提一句,数组也是实现了 IEnumerable<T>
接口的。System.Linq 中提供的扩展大大方便了集合处理过程。

JavaScript 最常见的集合数据类型就是数组,自 ES6 发布以后,这个范围扩展到了 iterable
对象。不过这里要讨论的内容都是在Array.prototype 中实现的。除此之外,underscore、lodash
这些第三方库中也实现了很多集合数据处理的方法,但不在本文讨论内容之内。

Java 的集合类型由 Collection<E> 接口定义。本文讨论的内容是 Java 8 的特性,在 java.util.stream 包中实现,由Collection<E>.stream() 引入。

示例语言版本

  • 后面示例中的部分 C# 语句可能需要支持 6.0 语言版本的编译器,如 Visual Studio 2015 或者 Visual Studio "15"
  • JavaScript 代码都使用了 ES6 语法,目前大部分浏览器支持,Node 5 也完全支持。
  • Java 要求 Java 8(或 1.8)版本

遍历

问题提出

给定一个名称列表,数组类型, ["Andy", "Jackson", "Yoo"],要求遍历出到的控制台。

C# 的遍历

对于集合来说,最常用的就是遍历,不过 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。

Func<T, Boolean> 是一个公用委托。Func<...> 系列公用委托都用于委托带有返回值的的方法,所有 Func<..., TResult>都是最后一个参数 TResult 代表返回值类型。

因此,C# 的遍历输出可以这样实现


  1. string[] names = { "Andy", "Jackson", "Yoo" }; 
  2. names.All(name => { 
  3.     Console.WriteLine(name); 
  4.     return true; 
  5. });  

  1. string[] names = { "Andy", "Jackson", "Yoo" }; 
  2. names.Any(name => { 
  3.     Console.WriteLine(name); 
  4.     return false; 
  5. });  

有 Lambda 就是好

JavaScript 的遍历

JavaScript 的 Array 实现了 forEach 实例方法,即 Array.prototype.forEach()。

对于 JavaScript 的数组,可以这样遍历


  1. var names = ["Andy", "Jackson", "Yoo"]; 
  2. names.forEach(name => { 
  3.     console.log(name); 
  4. });  

对于 JavaScript 的伪数组,可以这样


  1. var names = { 
  2.     0: "Andy", 
  3.     1: "Jackson", 
  4.     2: "Yoo", 
  5.     length: 3 
  6. }; 
  7.  
  8. [].forEach.call(names, name => { 
  9.     console.log(name); 
  10. });  

jQuery 的遍历

jQuery 是一个常用的 JavaScript 库,它封装的对象都是基于伪数组的,所以 jQuery 中经常用到遍历。除了网页元素集合外,jQuery 也可以遍历普通数组,有两种方式

可以直接把数组作为第一个参数,处理函数作为第二个参数调用 $.each()。


  1. const names = ["Andy", "Jackson", "Yoo"]; 
  2. $.each(names, (i, name) => { 
  3.     console.log(name); 
  4. }); 

也可以把数组封装成一个 jQuery 对象($(names)),再在这个 jQuery 对象上调用 eash() 方法。


  1. const names = ["Andy", "Jackson", "Yoo"]; 
  2. $(names).each((i, name) => { 
  3.     console.log(name); 
  4. });  

两种方法的处理函数都一样,但是要注意,这和原生 forEach() 的处理函数有点不同。jQuery 的 each()
处理函数,第一个参数是序号,第二个参数是数组元素;而原生 forEach() 的处理函数正好相反,第一个参数是数组元素,第二个参数才是序号。

另外,$.each() 对伪数组同样适用,不需要通过 call() 来调用。

Java 的遍历


  1. String[] names = { "Andy", "Jackson", "Yoo" }; 
  2. List<String> list = Arrays.asList(names); 
  3. list.forEach(name -> { 
  4.     System.out.println(name); 
  5. });  

过滤(筛选)数据

问题提出

给出一组整数,需要将其中能被 3 整除选出来


  1. [46, 74, 20, 37, 98, 93, 98, 48, 33, 15] 

期望结果


  1. [93, 48, 33, 15] 

C# 中过滤使用 Where() 扩展


  1. int[] data = { 46, 74, 20, 37, 98, 93, 98, 48, 33, 15 }; 
  2. int[] result = data.Where(n => n % 3 == 0).ToArray();  

注意:Where() 的结果即不是数组也不是 List,需要通过 ToArray() 生成数组,或者通过 ToList()
生成列表。Linq 要在ToArray() 或者 ToList() 或者其它某些操作的时候才会真正遍历,依次执行 Where()
参数提供的那个筛选函数。

JavaScript 中有 Array.prototype.filter


  1. const data = [46, 74, 20, 37, 98, 93, 98, 48, 33, 15]; 
  2. const result = data.filter(n => { 
  3.     return n % 3 === 0; 
  4. });  

Java 中使用到 java.util.stream.*

Java 中可以通过 java.util.stream.IntStream.of() 来从数组生成 stream 对象


  1. final int[] data = { 46, 74, 20, 37, 98, 93, 98, 48, 33, 15 }; 
  2. int[] result = IntStream.of(data) 
  3.         .filter(n -> n % 3 == 0) 
  4.         .toArray();  

需要注意的是,Arrays.asList(data).stream() 看起来也可以生成 stream 对象,但是通过调试会发现,这是一个
Stream<int[]>而不是 Stream<Integer>。原因是 asList(T ...a)
其参数可变参数,而且要求参数类型是类,所以 asList(data) 是把 data 作为一个 int[] 类型参数而不是 int
类型的参数数据。如果要从 int[] 生成 List<Integer>,还得通过 IntStream 来处理


  1. List<Integer> list = IntStream.of(data) 
  2.         .boxed() 
  3.         .collect(Collectors.toList());  

映射处理

映射处理是指将某种类型的集合,将其元素依次映射成另一种类型,产生一个新类型的集合。新集合中的每个元素都与原集中的同样位置的元素有对应关系。

问题提出

这里提出一个精典的问题:成绩转等级,不过为了简化代码(switch 或多重 if 语句代码比较长),改为判断成绩是否及格,60 分为及格线。

偷个懒,就用上个问题的输入 [46, 74, 20, 37, 98, 93, 98, 48, 33, 15],

期望结果:


  1. ["REJECT","PASS","REJECT","REJECT","PASS","PASS","PASS","REJECT","REJECT","REJECT"] 

C# 通过 Select() 来进行映射处理。


  1. int[] scores = { 46, 74, 20, 37, 98, 93, 98, 48, 33, 15 }; 
  2. string[] levels = scores 
  3.     .Select(score => score >= 60 ? "PASS" : "REJECT") 
  4.     .ToArray();  

JavaScript 通过 Array.prototype.map 来进行映射处理。


  1. const scores = [46, 74, 20, 37, 98, 93, 98, 48, 33, 15]; 
  2. const levels = scores.map(score => { 
  3.     return score >= 60 ? "PASS" : "REJECT"; 
  4. });  

Java 的 Stream 提供了 mapToObj() 等方法处理映射


  1. final int[] scores = { 46, 74, 20, 37, 98, 93, 98, 48, 33, 15 }; 
  2. String[] levels = IntStream.of(scores) 
  3.         .mapToObj(score -> score >= 60 ? "PASS" : "REJECT") 
  4.         .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 排列的,需要建立一个查找到,使之能通过姓名很容易找到对应的学号。


  1. ["Andy", "Jackson", "Yoo", "Rose", "Lena", "James", "Stephen"] 

期望结果


  1. Andy    => 1 
  2. Jackson => 2 
  3. Yoo     => 3 
  4. Rose    => 4 
  5. Lena    => 5 
  6. James   => 6 
  7. Stephen => 7    

C# 使用 ToDictionary()


  1. string[] names = { "Andy", "Jackson", "Yoo", "Rose", "Lena", "James", "Stephen" }; 
  2. int i = 1; 
  3. Dictionary<string, int> map = names.ToDictionary(n => n, n => i++);  

C# Linq 扩展提供的若干方法都没有将序号传递给处理函数,所以上例中采用了临时变量计数的方式来进行。不过有一个看起来好看一点的办法,用 Enumerable.Range() 先生成一个序号的序列,再基于这个序列来处理


  1. string[] names = { "Andy", "Jackson", "Yoo", "Rose", "Lena", "James", "Stephen" }; 
  2. IEnumerable<int> indexes = Enumerable.Range(0, names.Length); 
  3. Dictionary<string, int> map = indexes.ToDictionary(i => names[i], i => i + 1);  

JavaScript 的两种处理办法

JavaScript 没有提供从 [] 到 {} 的转换函数,不过要做这个转换也不是好麻烦,用 forEach 遍历即可


  1. var map = (function() { 
  2.     var m = {}; 
  3.     names.forEach((name, i) => { 
  4.         m[name] = i + 1; 
  5.     }); 
  6.     return m; 
  7. })();  

为了不让临时变量污染外面的作用域,上面的示例中采用了 IEFE 的写法。不过,如果用 Array.prototype.reduce 则可以让代码更简洁一些


  1. var map = names.reduce((m, name, i) => { 
  2.     m[name]  = i + 1; 
  3.     return m; 
  4. }, {});  

Java 的 Collectors

Java 的处理函数也没有传入序号,所以在 Java 中的实例和 C# 类似。不过,第一种方法不可用,因为 Java Lambda
的实现相当于是匿名类对接口的实现,只能访问局部的 final 变量,i 要执行 i++ 操作,显然不是 final 的,所以只能用第二种办法


  1. final String[] names = { "Andy", "Jackson", "Yoo", "Rose", "Lena", "James", "Stephen" }; 
  2. Map<String, Integer> map = IntStream.range(0, names.length) 
  3.     .boxed() 
  4.     .collect(Collectors.toMap(i -> names[i], i -> i + 1));  

我只能说 .boxed() 是个大坑啊,一定要记得调。

汇总和聚合处理

汇总处理就是合计啊,平均数啊之类的,使用方式都差不多,所以以合计(Sum)为例。

汇总处理其实是聚合处理的一个特例,所以就同一个问题,再用普通的聚合处理方式再实现一次。

问题提出

已知全班成绩,求班总分,再次用到了那个数组


  1. [46, 74, 20, 37, 98, 93, 98, 48, 33, 15] 

期望结果:562

C# 的实现

C# 可以直接使用 Sum() 方法求和


  1. int[] scores = { 46, 74, 20, 37, 98, 93, 98, 48, 33, 15 }; 
  2. int sum = scores.Sum();  

聚合实现方式(用 Aggregate())


  1. int[] scores = { 46, 74, 20, 37, 98, 93, 98, 48, 33, 15 }; 
  2. int sum = scores.Aggregate(0, (total, score) => { 
  3.     return total + score; 
  4. });  

聚合实现方式要灵活得多,比如,改成乘法就可以算阶乘。当然用于其它更复杂的情况也不在话下。前面生成查找表的 JavaScript 部分就是采用聚合来实现的。

JavaScript 都是通过聚合来实现的


  1. const scores = [46, 74, 20, 37, 98, 93, 98, 48, 33, 15]; 
  2. const sum = scores.reduce((total, score) => { 
  3.     return total + score; 
  4. }, 0);  

注意 C# 的初始值在前,JavaScript 的初始值在后,这是有区别的。参数顺序嘛,注意一下就行了。

Java 中使用 Stream::reduce 进行聚合处理

IntStream 提供了 sum() 方法


  1. final int[] scores = { 46, 74, 20, 37, 98, 93, 98, 48, 33, 15 }; 
  2. final int sum = IntStream.of(scores).sum();  

同样也可以用 reduce 处理


  1. final int[] scores = { 46, 74, 20, 37, 98, 93, 98, 48, 33, 15 }; 
  2. final int sum = IntStream.of(scores) 
  3.         .reduce(0, (total, score) -> total + score);  

综合应用

问题提出

已知全班 7 个人,按学号 从 1~7 分别是


  1. ["Andy", "Jackson", "Yoo", "Rose", "Lena", "James", "Stephen"] 

这 7 个人的成绩按学号序,分别是


  1. [66, 74, 43, 93, 98, 88, 83] 

有 Student 数组结构


  1. Student { 
  2.     number: int 
  3.     name: string 
  4.     score: int 
  5. }  

要求得到全班 7 人的 student 数组,且该数组按分数从高到低排序

C# 实现


  1. sealed class Student { 
  2.     public int Number { get; } 
  3.     public string Name { get; } 
  4.     public int Score { get; } 
  5.      
  6.     public Student(int number, string name, int score) { 
  7.         Number = number; 
  8.         Name = name; 
  9.         Score = score; 
  10.     } 
  11.      
  12.     public override string ToString() => $"[{Number}] {Name} : {Score}"; 
  13. Student[] students = Enumerable.Range(0, names.Length) 
  14.     .Select(i => new Student(i + 1, names[i], scores[i])) 
  15.     .OrderByDescending(s => s.Score) 
  16.     .ToArray();  

注意 C# 中排序有 OrderBy 和 OrderByDescending
两个方法,一般情况下只需要给一个映射函数,从原数据里找到要用于比较的数据即可使用其 >、<
等运算符进行比较。如果比例起来比较复杂的,需要提供第二个参数,一个 IComparer<T> 的实现

JavaScript 实现


  1.  constructor(number, name, score) { 
  2.         this._number = number; 
  3.         this._name = name; 
  4.         this._score = score; 
  5.     } 
  6.  
  7.     get number() { 
  8.         return this._number; 
  9.     } 
  10.  
  11.     get name() { 
  12.         return this._name; 
  13.     } 
  14.  
  15.     get score() { 
  16.         return this._score; 
  17.     } 
  18.  
  19.     toString() { 
  20.         return `[${this.number}] ${this.name} : ${this.score}`; 
  21.     } 
  22. const names = ["Andy", "Jackson", "Yoo", "Rose", "Lena", "James", "Stephen"]; 
  23. const scores = [66, 74, 43, 93, 98, 88, 83]; 
  24.  
  25. var students = names 
  26.     .map((name, i) => new Student(i + 1, name, scores[i])) 
  27.     .sort((a, b) => { 
  28.         return b.score - a.score; 
  29.     }); 

JavaScript 的排序则是直接给个比较函数,根据返回的数值小于0、等于0或大于0来判断是小于、等于还是大于。

Java 实现


  1. final class Student { 
  2.     private int number; 
  3.     private String name; 
  4.     private int score; 
  5.  
  6.     public Student(int number, String name, int score) { 
  7.         this.number = number; 
  8.         this.name = name; 
  9.         this.score = score; 
  10.     } 
  11.  
  12.     public int getNumber() { 
  13.         return number; 
  14.     } 
  15.  
  16.     public String getName() { 
  17.         return name; 
  18.     } 
  19.  
  20.     public int getScore() { 
  21.         return score; 
  22.     } 
  23.  
  24.     @Override 
  25.     public String toString() { 
  26.         return String.format("[%d] %s : %d", getNumber(), getName(), getScore()); 
  27.     } 
  28. final String[] names = { "Andy", "Jackson", "Yoo", "Rose", "Lena", "James", "Stephen" }; 
  29. final int[] scores = { 66, 74, 43, 93, 98, 88, 83 }; 
  30. Student[] students = IntStream.range(0, names.length) 
  31.         .mapToObj(i -> new Student(i + 1, names[i], scores[i])) 
  32.         .sorted((a, b) -> b.getScore() - a.getScore()) 
  33.         .toArray(Student[]::new);  

作者:边城

来源:51CTO

时间: 2024-10-26 21:36:48

类比C#、JavaScript和Java的集合数据处理的相关文章

Javascript实现的Map集合工具类完整实例_javascript技巧

本文实例讲述了Javascript实现的Map集合工具类.分享给大家供大家参考.具体如下: var Map = function(){ // 构造entry实体 var Entry = function(key, value){ this.key = key; this.value = value; } this.entries = new Array(); // 构造put方法在数组中放入一个Entry this.put = function(key, value){ // 数组中已存在就不放

Java遍历集合方法分析(实现原理、算法性能、适用场合)_javascript技巧

概述 Java语言中,提供了一套数据集合框架,其中定义了一些诸如List.Set等抽象数据类型,每个抽象数据类型的各个具体实现,底层又采用了不同的实现方式,比如ArrayList和LinkedList. 除此之外,Java对于数据集合的遍历,也提供了几种不同的方式.开发人员必须要清楚的明白每一种遍历方式的特点.适用场合.以及在不同底层实现上的表现.下面就详细分析一下这一块内容. 数据元素是怎样在内存中存放的? 数据元素在内存中,主要有2种存储方式: 1.顺序存储,Random Access(Di

JavaScript实现Java中Map容器的方法_javascript技巧

本文实例讲述了JavaScript实现Java中Map容器的方法.分享给大家供大家参考,具体如下: 声明一下,JavaScript和Java的区别就像雷锋和雷峰塔的区别. 在Java中,Map是一种集合,用来存储Key-Value键值对的容器.根据键得到值,因此不允许键重复(重复了的覆盖),但允许值重复.JavaScript中的对象特性,就是不允许有相同的属性存在,和Java的Map非常的相似,所以可以利用这个特性在JavaScript中来实现Map容器,实现基本的增删查的操作. functio

JavaScript实现Java的Map、List功能

JavaScript实现Java的Map.List功能,如下代码: function HashMap(){      this.size=0;      this.map=new Object();  }    HashMap.prototype.put=function(key,value){      if(!this.map[key]){          this.size++;      }      this.map[key]=value;  };  HashMap.prototyp

javascript所有on事件集合

javascript所有on事件集合,整理起来方便查找和记忆. onactivate 当对象设置为活动元素时触发. onafterprint 对象所关联的文档打印或打印预览后立即在对象上触发. onbeforeactivate 对象要被设置为当前元素前立即触发. onbeforecut 当选中区从文档中删除之前在源对象触发. onbeforedeactivate 在 activeElement 从当前对象变为父文档其它对象之前立即触发. onbeforeeditfocus 在包含于可编辑元素内的

初学认识:JavaScript和Java是两个完全不同的语言

javascript|初学 JavaScript 是一种描述性语言,它可以被嵌入 HTML 的文件之中.通过 JavaScript 可以做到响应用户的需求事件(如表单的输入),这样当一位使用者输入一项信息时,它不需要通过网络传送到服务器端进行处理再传回来的过程,而可以直接在客户端进行事件的处理.你也可以想像成有一个可执行程序在你的客户端上执行一样(但这种执行程序是有限的,它对客户端电脑的控制力很差)! JavaScript 和 Java 很相似,但它们却是完全不同的语言! Java 是由 Sun

Java 语言集合架构(Set规则集)

集合中存在的是没有重复元素的对象的序列. Java提供了相应的类和接口存储这样的元素--Set规 则集. 下面以一个例子学习Set集合架构,其中包含注释: package com.brucezhang.test; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Set; public class SetTest { /** * @param

JavaScript 和 Java 的区别浅析

虽然JavaScript与Java有紧密的联系,但却是两个公司开发的不同的产品.Java是SUN公司推出的 新一代面向对象的程序设计语言,特别适合于Internet应用程序开发:而JavaScript是Netscape公司的产品,其目的是为了扩展 Netscape Navigator功能,而开发的一种可以嵌入Web页面中的基于对象和事件驱动的解释性语言, 它的前身是Live Script:而Java的前身是Oak语言.   下面对两种语言间的异同作如下比较:1.基于对象和面向对象Java是一种真

Javascript和Java获取各种form表单信息的简单实例

 本篇文章主要是对Javascript和Java获取各种form表单信息的简单实例进行了介绍,需要的朋友可以过来参考下,希望对大家有所帮助 大家都知道我们在提交form的时候用了多种input表单.可是不是每一种input表单都是很简单的用Document.getElementById的方式就可以获取到的.有一些组合的form类似于checkbox或者radio或者select我们如何用javascript获取和在服务器中获取提交过来的参数呢?多说无用.上代码:   Jsp-html代码: