Java已经失传多年,最近又捡起来了。首先当然是了解它这些年的变化,所以发现了Java8的java.util.stream。在学习和实验的过程中,相对于C#和javascript,我有了一些体会,我会写它下来。之前写过一篇文章《ES6 的 for..of 和 Generator,从伪数组 jQuery 对象说起》,跟这个话题有点关系。其实我记得还有一篇关于C#的文章,但是没找到,可能只是想了想,没有写,就成了虚假的记忆。前言之所以把C#、JavaScript和Java的实现写在一起,主要是想打个比方,或许有助于理解。集合数据C#中集合数据的基类是Collection,它实现了ICollection接口,ICollection继承自IEnumerable接口——其实要讨论的内容是基于IEnumerable接口。还有一个非泛型的IEnumerable接口,但是建议大家尽量使用泛型,这个非泛型接口我就不提了。顺便说一下,数组还实现了IEnumerable接口。System.Linq中提供的扩展大大简化了收集过程。JavaScript中最常见的集合数据类型是数组,自ES6发布以来,这个范围已经扩展到可迭代对象。不过这里要讨论的内容是在Array.prototype中实现的。此外,underscore、lodash等第三方库也实现了很多集合数据的处理方法,本文不做讨论。Java的集合类型由Collection接口定义。本文讨论的是Java8的一个特性,在java.util.stream包中实现,由Collection.stream()引入。以下示例中的部分C#语句可能需要支持6.0语言版本的编译器,如VisualStudio2015或VisualStudio“15”JavaScript代码使用ES6语法,目前大部分浏览器都支持,Node完全支持5.Java要求Java8(或1.8)版本遍历问题问给定一个列表名称,数组类型,["Andy","Jackson","Yoo"],要求遍历出到控制台。C#的遍历最常用的是集合,但是for,foreach,while之类的大家都很熟悉,就不多说了。这是forEach()方法。遗憾的是,C#的Linq扩展中并没有提供ForEach()方法,但是可以使用All(IEnumerable,Func)和Any(IEnumerable,Func)反而。这两种方法的区别在于第二个参数Func的返回值。这两个方法会遍历集合,依次调用集合中每个元素的第二个参数,Func所引用的委托方法,并检查其返回值,All()检查为false停止遍历,并且Any()检查为true以中止遍历。all()表示如果所有元素都满足条件就返回true,只要有一个元素不满足条件就返回false,则遍历终止,返回false;Any()表示只要找到任何元素满足条件,就会返回true。Func是公共委托。Func<...>系列的公共委托都是用来委托有返回值的方法。所有的Func<...,TResult>都有最后一个参数TResult表示返回值类型。所以C#的遍历输出可以这样实现string[]names={"Andy","Jackson","Yoo"};names.All(name=>{Console.WriteLine(name);returntrue;});string[]names={"Andy","Jackson","Yoo"};names.Any(name=>{Console.WriteLine(name);returnfalse;});Lambda是一个很好的JavaScript遍历JavaScriptArray实现了forEachInstance方法,即Array.prototype.forEach()。对于JavaScript数组,您可以像这样遍历varnames=["Andy","Jackson","Yoo"];names.forEach(name=>{console.log(name);});对于JavaScript伪数组,您可以这样做varnames={0:"Andy",1:"Jackson",2:"Yoo",length:3};[].forEach.call(names,name=>{console.log(name);});jQuery遍历jQuery是一个常用的JavaScript库。它封装的对象是基于伪数组的,所以在jQuery中经常使用遍历。除了网页元素的集合,jQuery还可以遍历普通的数组。有两种方式,直接使用数组作为第一个参数,使用处理函数作为第二个参数调用$.each()。constnames=["Andy","Jackson","Yoo"];$.each(names,(i,name)=>{console.log(name);});您还可以将数组封装到一个jQuery对象($(names))中,并在这个jQuery对象上调用eash()方法。constnames=["Andy","Jackson","Yoo"];$(names).each((i,name)=>{console.log(name);});这两个方法的处理功能是相同的,但是请注意,这与原生的forEach()处理程序有点不同。jQuery的each()处理函数,第一个参数是序号,第二个参数是数组元素;而原生的forEach()处理函数正好相反,第一个参数是数组元素,第二个参数是序号。另外$.each()也适用于伪数组,不需要通过call()调用。Java的遍历String[]names={"Andy","Jackson","Yoo"};Listlist=Arrays.asList(names);list.forEach(name->{System.out.println(name);});Filtering(过滤)数据题目提出一组整数,需要被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()生成列表。当ToArray()或ToList()或其他一些操作时,Linq实际上会遍历,依次执行Where()参数提供的过滤函数。JavaScript有Array.prototype.filterconstdata=[46,74,20,37,98,93,98,48,33,15];constresult=data.filter(n=>{return%3===0;});Java使用java.util.stream.*Java可以通过java.util.stream.IntStream.of()从数组中生成流对象finalint[]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.原因是asList(T...a)有可变参数,要求参数类型是类,所以asList(data)使用的data是int[]类型的参数,而不是int类型的参数data。如果要从int[]生成List,则必须通过IntStream对其进行处理。Listlist=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进行映射处理。constscores=[46,74,20,37,98,93,98,48,33,15];constlevels=scores.map(score=>{returnscore>=60?"PASS":"REJECT";});Java的Stream提供了mapToObj()等方法来处理映射finalint[]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);与“filter”例子不同的是,在“filter”例子中,由于过滤结果是一个IntStream,可以直接调用InStream::toArray()得到int[]。但是在这个例子中,mapToObj()得到的是一个Stream,是类型擦除后的Stream,所以Stream::toArray()默认得到的是一个Object[],而不是String[]。如果要获取String[],需要为toArray()指定String[]的构造函数,即String[]::new。生成查找表(如哈希表)查找表在数据结构中的含义比较广泛,通过哈希算法实现的称为哈希表。Directory一般在C#中使用,不知道是不是通过哈希实现的。而Java中的HashMap和Hashtable是从名字上实现的。据说JavaScript的对象文字也实现了散列。问个问题现在有个名字列表,按照学号从1到7排列,需要创建一个搜索,方便通过名字找到对应的学号。["Andy","Jackson","Yoo","Rose","Lena","James","Stephen"]预期结果Andy=>1Jackson=>2Yoo=>3Rose=>4Lena=>5James=>6Stephen=>7C#使用ToDictionary()string[]names={"Andy","Jackson","Yoo","Rose","Lena","James","Stephen"};inti=1;Dictionarymap=names.ToDictionary(n=>n,n=>i++);C#Linq扩展提供的几种方法都没有将序号传递给处理函数,所以在上面的例子中,使用了统计临时变量的方法。但是有一种方法看起来更好,使用Enumerable.Range()生成一个序列号序列,然后根据这个处理string[]names={"Andy","Jackson","Yoo","Rose"序列,“Lena”,“James”,“Stephen”};IEnumerableindexes=Enumerable.Range(0,names.Length);Dictionarymap=indexes.ToDictionary(i=>names[i],i=>i+1);JavaScript的两种处理方式JavaScript没有提供[]到{}的转换函数,不过做这个转换也不是太麻烦,直接用forEach遍历即可varmap=(function(){varm={};names.forEach((name,i)=>{m[name]=i+1;});returnm;})();为了不让临时变量污染外层作用域,在编写中使用了上面的例子IEFE。但是,如果使用Array.prototype.reduce,可以让代码更简洁varmap=names.reduce((m,name,i)=>{m[name]=i+1;returnm;},{});JavaCollectorsJava的处理函数也没有传入序号,所以Java中的实例类似于C#。但是第一种方法不可用,因为JavaLambda的实现相当于对接口实现了一个匿名类,只能访问局部的final变量。我需要进行i++运算,显然不是final的,只能使用第二种方法MethodfinalString[]names={"Andy","Jackson","Yoo","Rose","Lena","James","Stephen"};Mapmap=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]。预期结果:562C#实现C#可以直接使用Sum()方法sumsint[]scores={46,74,20,37,98,93,98,48,33,15};intsum=scores.Sum();聚合实现(使用Aggregate())int[]scores={46,74,20,37,98,93,98,48,33,15};intsum=scores.Aggregate(0,(total,score)=>{returntotal+score;});聚合实现更加灵活。例如,可以通过更改为乘法来计算阶乘。当然,在其他更复杂的情况下使用它是没有问题的。前面生成查找表的JavaScript部分是使用聚合实现的。JavaScript通过聚合实现constscores=[46,74,20,37,98,93,98,48,33,15];constsum=scores.reduce((total,score)=>{returntotal+score;},0);请注意,C#的初始值和JavaScript的初始值之间存在差异。只需注意参数的顺序即可。Java使用Stream::reduce进行聚合处理IntStream提供sum()方法finalint[]scores={46,74,20,37,98,93,98,48,33,15};finalintsum=IntStream.of(scores)。和();也可以用reduce处理finalint[]scores={46,74,20,37,98,93,98,48,33,15};finalintsum=IntStream.of(scores).reduce(0,(total,score)->total+score);综合应用题提出班级有7名学生,他们是["Andy","Jackson","Yoo","Rose","Lena","James","Stephen"]这7个人的成绩按照学号顺序依次为[66,74,43,93,98,88,83],Student数组结构Student{number:intname:stringscore:int}是需要获取班级7名学生的学生数组,数组按照分数从高到低排序。C#实现sealedclassStudent{publicintNumber{get;}publicstringName{get;}publicintScore{get;}publicStudent(intnumber,stringname,intscore){Number=number;Name=name;Score=score;}publicoverridestringToString()=>$"[{Number}]{Name}:{Score}";}Student[]students=Enumerable.Range(0,names.Length).Select(i=>newStudent(i+1,names[i],scores[i])).OrderByDescending(s=>s.Score).ToArray();请注意,有OrderBy和OrderByDescending两个方法,一般情况下,只需要给出一个映射函数,从原始数据中找到要用于比较的数据,然后使用它的>,<等操作符进行比较,如果比较复杂,就需要提供第二个参数,IComparer的一个实现JavaScript实现constructor(number,name,score){this._number=number;this._name=name;this._score=score;}getnumber(){returnthis._number;}getname(){returnthis._name;}getscore(){returnthis._score;}toString(){return`[${this.number}]${this.name}:${this.score}`;}}constnames=["Andy","Jackson","Yoo","Rose","Lena","James","Stephen"];constscores=[66,74,43,93,98,88,83];varstudents=names.map((name,i)=>newStudent(i+1,name,scores[i])).sort((a,b)=>{returnb.score-a.score;});JavaScript排序就是直接给一个比较函数,根据返回值是小于0,等于0还是大于0来判断是小于,等于还是大于0。Java实际finalclassStudent{privateintnumber;privateStringname;privateintscore;publicStudent(intnumber,Stringname,intscore){this.number=number;this.name=name;this.score=score;}publicintgetNumber(){returnnumber;}publicStringgetName(){returnname;}publicintgetScore(){returnscore;}@OverridepublicStringtoString(){returnString.format("[%d]%s:%d",getNumber(),getName(),getScore());}}finalString[]names={"Andy","Jackson","Yoo","Rose","Lena","James","Stephen"};finalint[]scores={66,74,43,93,98,88,83};Student[]students=IntStream.range(0,names.length).mapToObj(i->newStudent(i+1,names[i],scores[i])).sorted((a,b)->b.getScore()-a.getScore()).toArray(Student[]::new);