前言:暑期应该开始了,因为小区对面的小学这两天早上都没有像以往那样一到七八点钟就人声喧闹、车水马龙。

前两篇文章介绍了Collection框架的主要接口和常用类,例如List、Set、Queue,和ArrayList、HashSet、LinkedList等等。根据核心框架图,相信我们都已经对Collection这个JavaSE中最常用API之一有一个较为全面的认识。

这个学习过程,还可以推及到其他常用开源框架和公司项目的学习和熟悉上面。借助开发工具或说明文档,先是对项目整体有一个宏观的认识,再根据这个认识逐一熟悉各个核心模块。(如果对关于Collection的两篇文章感兴趣的话,可以在文章的最末尾点击链接阅读。)

1.5 遍历对象的Iterator

Collection,顾名思义,就是收集,在JavaSE当中起到了收集对象的作用。

收集完对象之后,有一个非常普遍的需求,就是要遍历所收集的对象。学生报名之后,老师有浏览都有哪些学生已经注册的需要;客户下了订单之后, 商家有看看哪些商品被购买的需要;病人挂号之后,医院工作人员有了解门诊工作量的需要。

1.5.1 遍历对象遇到的问题

如果要写一个forEach()方法,可以显示List收集的所有对象,也许你会这么写:

  1. private static void forEach(List list) {
  2. int size = list.size();
  3. for(int i = 0; i < size; i++) {
  4. System.out.println(list.get(i));
  5. }
  6. }

这个方法适用于所有实现List接口的对象,如ArrayList、LinkedList等。如果要让你写个forEach()方法显示Set收集的所有对象,你该怎么写呢?在查看过Set的API说明文档后,发现有个toArray()方法,可以将Set收集的对象转为Object[]返回,所以你会这么写:

  1. private static void forEach(Set set) {
  2. for(Object o : set.toArray()) {
  3. System.out.println(o);
  4. }
  5. }

这个方法适用于所有操作Set接口的对象,如HashSet、TreeSet等。如果现在要让你再写一个forEach()方法,可以显示Queue收集的对象,也许你会这么写:

  1. private static void forEach(Queue queue) {
  2. while(queue.peek() != null) {
  3. System.out.println(queue.poll());
  4. }
  5. }

表面上看来好像是正确的,不过Queue的poll()方法会取出对象,当你显示完Queue中所有对象,Queue也空了。这并不是我们想要的结果,怎么办呢?

1.5.2 使用Iterator

事实上,无论是List、Set还是Queue,都会有个Queue,都会有个iterator()方法,这个方法在JDK1.4之前,是定义在Collection接口中,而它们都继承自Collection,所以也都拥有iterator()的行为。

iterator()方法会返回java.util.Iterator接口的操作对象,这个对象包括了Collection收集的所有对象,你可以使用Iterator的hasNext()看看有无下一个对象,若有的话,再使用next()取得下一个对象。因此,无论List、Set、Queue还是任何Collection,都可以使用以下的forEach()方法来显示所收集的对象:

  1. private static void forEach(Collection collection) {
  2. Iterator iterator = collection.iterator();
  3. while(iterator.hasNext()) {
  4. System.out.println(iterator.next());
  5. }
  6. }

在JDK5之后,原先定义在Collection中的iterator()方法,提升至新的java.util.Iterable父接口,因此在JDK5之后,可以使用以下forEach()方法显示收集的所有对象:

  1. private static void forEach(Iterable iterable) {
  2. Iterator iterator = iterable.iterator();
  3. while(iterator.hasNext()) {
  4. System.out.println(iterator.next());
  5. }
  6. }

接下来,我们可以写一个比较完整的demo,看看是否能正确使用forEach()方法:

  1. import java.util.*;
  2.  
  3. /**
  4. * Iterator实验用例
  5. */
  6. public class ForEach {
  7. private static void forEach(Iterable iterable) {
  8. Iterator iterator = iterable.iterator();
  9. while(iterator.hasNext()) {
  10. System.out.println(iterator.next());
  11. }
  12. }
  13.  
  14. public static void main(String[] args) {
  15. List list = Arrays.asList("Tim", "Jack", "Jane");
  16. forEach(list);
  17. forEach(new HashSet(list));
  18. forEach(new ArrayDeque(list));
  19. }
  20. }

运行之后,我们可以得到以下结果:

1.5.3 Iterator小结

遍历显示所收集的所有对象,这是使用Celletion框架时频繁会遇到的需求。在了解List、Set、Queue如何显示收集对象之后,我们意外地发现可以通过使用定义在上层接口的iterator()方法,达到正确、安全地满足这一需求的目的。

无论是学习Colletion等JavaSE的API框架,还是熟悉开源或公司项目,在熟悉各个模块的同时,也要有抽象的理解能力来宏观地理解项目。在满足一些普遍常用需求时,可以找到更通用、复用程度更高的解决方案。如果能做到这一点,就不会犯新手常犯的“只见树木不见森林”的错误。

1.6 收集对象后的排序

在收集对象之后,对对象进行排序是常用的动作。

我的上家是做ERP系统的,常常有这样的需求:几家供应商提供的报价单价格不一样,系统用户希望可以在按照报价从低到高查看商品的报价;生产物料紧缺时,他们希望根据物料需求日期从近到远查看;同一个生产件,不同的生产配方和工艺流程产生的生产成本是不一样的,他们希望能按照成本从低到高查看生产明细。以上的这些需求,都要求系统对所收集的不同对象进行排序

1.6.1 Collection自带的排序算法

java.util.Collections提供有sort()方法,用来满足对对象进行排序的需求。由于必须有索引才能进行排序,因此Collections的sort()方法接受List实现对象。例如以下这段demo:

  1. import java.util.*;
  2.  
  3. /**
  4. * Collections的sort()方法实验用例
  5. */
  6. public class Sort {
  7. public static void main(String[] args) {
  8. List numbers = Arrays.asList(10, 3, 4, 21, 9);
  9. Collections.sort(numbers);
  10. System.out.println(numbers);
  11. }
  12. }

执行结果我们可以看到已经排好序的一串数字:

可是,如果我们需要排序的对象稍微复杂一点点,会出现什么样的情况呢?

  1. import java.util.*;
  2.  
  3. /**
  4. * Collections的sort()方法实验用例2
  5. */
  6. class Account {
  7. private String name;
  8. private int balance;
  9.  
  10. Account (String name, int balance) {
  11. this.name = name;
  12. this.balance = balance;
  13. }
  14.  
  15. @Override
  16. public String toString() {
  17. return String.format("Account(%s, %d)", name, balance);
  18. }
  19. }
  20.  
  21. public class Sort {
  22. public static void main(String[] args) {
  23. List accounts = Arrays.asList(
  24. new Account("Tim", 100),
  25. new Account("Tom", 1300),
  26. new Account("Jack", 5)
  27. );
  28. Collections.sort(accounts);
  29. System.out.println(accounts);
  30. }
  31. }

运行结果出现了抛出ClassCastException报错,到底是怎么回事呢?

1.6.2 实现Comparable

要说原因,是因为你根本没告诉Collections的sort()方法,到底要根据Account的name还是balance进行排序。用一句时下流行的话说:“我有什么办法,我也很绝望啊。”

Collections的sort()方法要求被排序的对象必须实现java.lang.Comparable接口,这个接口有个compareTo()方法必须返回大于0、等于0或小于0的数。这有什么用呢?我们直接来看下面这个针对账户余额排序的demo就了解了:

  1. import java.util.*;
  2.  
  3. /**
  4. * Collections的sort()方法实验用例3
  5. */
  6. class Account implements Comparable{
  7. private String name;
  8. private int balance;
  9.  
  10. Account (String name, int balance) {
  11. this.name = name;
  12. this.balance = balance;
  13. }
  14.  
  15. @Override
  16. public String toString() {
  17. return String.format("Account(%s, %d)", name, balance);
  18. }
  19.  
  20. @Override
  21. public int compareTo(Object o) {
  22. Account other = (Account) o;
  23. return this.balance - other.balance;
  24. }
  25. }
  26.  
  27. public class Sort {
  28. public static void main(String[] args) {
  29. List accounts = Arrays.asList(
  30. new Account("Tim", 100),
  31. new Account("Tom", 1300),
  32. new Account("Jack", 5)
  33. );
  34. Collections.sort(accounts);
  35. System.out.println(accounts);
  36. }
  37. }

Collections的sort()方法在取得a对象与b对象进行比较时,会先将对象Cast为Comparable(也因为这样,如果对象没实现这个接口,就会抛出ClassCastException),然后调用a.compareTo(b),如果a对象顺序上小于b对象,必须返回小于0的值;如果顺序上相等则返回0;如果顺序上a大于b,则要返回大于0的值。因此,上面的demo输出结果会是按照余额从小到大排列:

1.6.3 实现Comparator

如果你有用过Collections对所收集的String对象排序,你应该会知道JavaSE是按照A、B、C的字母表来排序的。可是,如果今天突然有个需求,要让排序结果反过来呢?

首先,String已经实现了Comparable接口,我们很难进行修改。另外,由于String声明为final,我们也没有办法通过继承的方式重新定义compareTo()方法。不过幸好,JavaSE给这种情况留下了备用的解决方案。

Collections的sort()方法有另一个重载版本,可以接受java.util.Comparator接口的操作对象。如果使用这个版本,排序方式将根据Comparator的compare()方法的定义来决定。例如下面这个demo:

  1. import java.util.*;
  2.  
  3. /**
  4. * Collections的sort()方法实验用例4
  5. */
  6. class StringComparator implements Comparator {
  7. @Override
  8. public int compare(Object o1, Object o2) {
  9. String str1 = (String) o1;
  10. String str2 = (String) o2;
  11. return -str1.compareTo(str2);
  12. }
  13. }
  14.  
  15. public class Sort {
  16. public static void main(String[] args) {
  17. List words = Arrays.asList("B", "C", "A", "X", "Z", "Y");
  18. Collections.sort(words, new StringComparator());
  19. System.out.println(words);
  20. }
  21. }

结果如下,符合我们之前的期望:

1.6.4 Sort()小结

在Java的规范中,与顺序有关的行为,通常要不就是对象本身是Comparable,即实现了Comparable接口,要不就是另行制定Comparator对象告知如何排序。

这就是深入学习API的好处,可以介绍遇到麻烦的次数。另外,无论你的工作语言是Java还是PHP、C#,熟悉语言规范是必不可少的内功,是衡量一个程序员实力的硬指标。

相关文章推荐:

JavaSE中Collection集合框架学习笔记(1)——具有索引的List

JavaSE中Collection集合框架学习笔记(2)——拒绝重复内容的Set和支持队列操作的Queue

如果你喜欢我的文章,可以扫描关注我的个人公众号“李文业的思考笔记”。

不定期地会推送我的原创思考文章。

JavaSE中Collection集合框架学习笔记(3)——遍历对象的Iterator和收集对象后的排序的更多相关文章

  1. JavaSE中Collection集合框架学习笔记(2)——拒绝重复内容的Set和支持队列操作的Queue

    前言:俗话说“金三银四铜五”,不知道我要在这段时间找工作会不会很艰难.不管了,工作三年之后就当给自己放个暑假. 面试当中Collection(集合)是基础重点.我在网上看了几篇讲Collection的 ...

  2. JavaSE中Collection集合框架学习笔记(1)——具有索引的List

    前言:因为最近要重新找工作,Collection(集合)是面试中出现频率非常高的基础考察点,所以好好恶补了一番. 复习过程中深感之前的学习不系统,而且不能再像刚毕业那样死背面试题,例如:String是 ...

  3. Java集合框架学习笔记

    集合类的由来:对象用于封装特有数据,对象多了需要存储,如果对象的长度不确定,就使用集合存储. 集合特点1.用于存储对象的容器.2.集合的长度可变.3.集合中不可以存储基本类型 集合容器因为内部的数据结 ...

  4. 集合框架学习笔记<二>

    1.什么是ArrayList ArrayList就是传说中的动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了如下一些好处: 动态的增加和减少元素 实现了ICollection和ILis ...

  5. 集合框架学习笔记<三>

    一些重要的区别 set与list的区别: set是无索引的,list是有索引的: ArrayList与LinkList的区别: 前者是基于数组实现的,后者是基于链表实现的: 两者的使用方法一样,但是在 ...

  6. hibernate框架学习笔记4:主键生成策略、对象状态

    创建一个实体类: package domain; public class Customer { private Long cust_id; private String cust_name; pri ...

  7. JavaSE中Map框架学习笔记

    前言:最近几天都在生病,退烧之后身体虚弱.头疼.在床上躺了几天,什么事情都干不了.接下来这段时间,要好好加快进度才好. 前面用了三篇文章的篇幅学习了Collection框架的相关内容,而Map框架相对 ...

  8. JavaSE中线程与并行API框架学习笔记1——线程是什么?

    前言:虽然工作了三年,但是几乎没有使用到多线程之类的内容.这其实是工作与学习的矛盾.我们在公司上班,很多时候都只是在处理业务代码,很少接触底层技术. 可是你不可能一辈子都写业务代码,而且跳槽之后新单位 ...

  9. JavaSE中线程与并行API框架学习笔记——线程为什么会不安全?

    前言:休整一个多月之后,终于开始投简历了.这段时间休息了一阵子,又病了几天,真正用来复习准备的时间其实并不多.说实话,心里不是非常有底气. 这可能是学生时代遗留的思维惯性--总想着做好万全准备才去做事 ...

随机推荐

  1. R – GPU Programming for All with ‘gpuR’

    INTRODUCTION GPUs (Graphic Processing Units) have become much more popular in recent years for compu ...

  2. intersect for multiple vectors in R

    Say you have a <- c(1,3,5,7,9) b <- c(3,6,8,9,10) c <- c(2,3,4,5,7,9) A straightforward way ...

  3. Xmpp学习之Android-smack入门指导

    Xmpp学习之Android-smack入门指导 版权声明:本文为博主原创文章,未经博主允许不得转载. 转载请表明出处:http://www.cnblogs.com/cavalier-/p/69404 ...

  4. Java常用类之要点总结

    Java常用类之要点总结

  5. python爬虫从入门到放弃(九)之 实例爬取上海高级人民法院网开庭公告数据

    通过前面的文章已经学习了基本的爬虫知识,通过这个例子进行一下练习,毕竟前面文章的知识点只是一个 一个单独的散知识点,需要通过实际的例子进行融合 分析网站 其实爬虫最重要的是前面的分析网站,只有对要爬取 ...

  6. 实现UDP高效接收/响应

    环境Linux g++6.3.0 问题一:一个ip地址如何接收高并发请求 问题二:如何高并发响应消息 发送请求端只能通过ip地址+端口号向服务器发送请求码,所以服务器只能用一个UDP去绑定此ip以及端 ...

  7. RedHat6.2 x86手动配置LNMP环境

    因为公司要求用RedHat配,顺便让我练习一下Linux里面的操作什么的. 折腾来折腾去终于搞好了,其实也没那么难嘛.但是也要记录一下. 首先,是在服务器里面用VMware搭建的RedHat6.2 x ...

  8. pod trunk push --verbose 失败的原因总结

    用 pod trunk push --verbose  添加一个 pod 的时候,经常出现如下的错误 [!] The podspec does not validate. /Library/Ruby/ ...

  9. JDK并发包

    JDK5之后引进了并发包java.util.concurrent,让并发的开发更加可控,更加简单.所以有必要好好学习下,下面从同步控制.并发容器.线程池三部分来详细了解它. 1. 各种同步控制工具的使 ...

  10. VR全景智慧城市常诚:信息技术点亮“智慧城市”

    亚太城市峰会暨市长论坛日前在澳大利亚昆士兰州首府布里斯班举行,"智慧城市"成为焦点议题.来自135个国家和地区的市长.副市长及代表们共同讨论如何利用高新科技解决城市发展中的问题,让 ...