前情回顾:上一篇除了回顾了异常处理的后半部分之外,还进行了对Collection的初步了解,包括它的继承和实现架构以及各子类的特性以及用法。
不知道大家有没有这样的经历,就是自己在寻求别人的帮助时,有时候会因为对方的没有痛快答应而感到恼火。自己私底下再想想,告诉自己别人真没有必须要帮助自己的义务,真不能对他们要求太多。想着想着,转而会觉得自己好没用,怎么就知道靠别人。就这样,被自尊和自卑不清不楚地折磨着。
若是能够走出来倒是好。如果确定某些事情必须只能靠自己去完成,某些困难只能靠自己去克服,那就绝不要怨天尤人,责怪没人帮助自己。像日本动漫里面的热血主人公一样,在心里大喊一句:少年,勇敢起来, 冲吧!然后该做什么,就做什么,做完做好就对了。
————————————————————————闲聊结束—————————————————————————————
第八章:Collection
第五节:访问对象的Iterator
其实,详细看过API说明文档Collection部分的童鞋都应该有印象。在Collection接口中,有定义一个方法,就是这一节的主题——iterator()方法会返回java.util.Iterator接口的实现对象,这个对象包括了Collection收集的所有对象。我们可以使用Iterator的hasNext()看看有无下一个对象,若有的话,再使用next()取得下一个对象。因此,无论List、Set、Queue还是任何Collection,都可以使用以下的forEach()来显示所收集的对象:
1 ...2 private static void forEach(Collection collection){3 Iterator iterator = collection.iterator();4 while(iterator.hasNext()){5 System.out.println(iterator.next());6 }7 }8 ...
任何实现了Iterable的对象,都可以使用整个forEach()方法,而不一定是Collection。
第六节:对收集的对象进行排序
相信学过数据结构与常用算法的童鞋都对几个排序算法比较熟悉,例如冒泡、归并、快排等。在java中,排序是常用的方法,不用我们亲自去实现。java.util.Collection就有提供sort()方法以供排序。要想排序,必须得有索引才行,因此Collection的sort()方法把实现List的对象作为参数。看到以下的示例代码:
1 package cc.openhome; 2 3 import java.util.*; 4 5 public class Sort { 6 public static void main(String[] args) { 7 List numbers = Arrays.asList(10, 2, 3, 1, 9, 15, 4); 8 Collections.sort(numbers); 9 System.out.println(numbers);10 }11 }
可是,如果是下面的例子,却会出现异常
1 package cc.openhome; 2 3 import java.util.*; 4 5 class Account { 6 private String name; 7 private String number; 8 private int balance; 9 10 Account(String name, String number, int balance) {11 this.name = name;12 this.number = number;13 this.balance = balance;14 }15 16 @Override17 public String toString() {18 return String.format("Account(%s, %s, %d)", name, number, balance);19 }20 21 }22 23 public class Sort2 {24 public static void main(String[] args) {25 List accounts = Arrays.asList(26 new Account("Justin", "X1234", 1000),27 new Account("Monica", "X5678", 500),28 new Account("Irene", "X2468", 200)29 );30 Collections.sort(accounts);31 System.out.println(accounts);32 }33 }
异常如下截图:
抛出这个异常,是因为我们的程序没有告诉sort()方法到底要根据Account的name、number或balance进行排序。Collections的sort()方法要求被排序的对象,必须实现java.lang.Comparable接口,这个接口有个compareTo()方法必须返回大于0、等于0或小于0的数。可是,这又有什么用呢?先看看下面的代码:
1 package cc.openhome; 2 3 import java.util.*; 4 5 class Account2 implements Comparable { 6 private String name; 7 private String number; 8 private int balance; 9 10 Account2(String name, String number, int balance) {11 this.name = name;12 this.number = number;13 this.balance = balance;14 }15 16 @Override17 public String toString() {18 return String.format("Account2(%s, %s, %d)", name, number, balance);19 }20 21 @Override22 public int compareTo(Object o) {23 Account2 other = (Account2) o;24 return this.balance - other.balance;25 }26 }27 28 public class Sort3 {29 public static void main(String[] args) {30 List accounts = Arrays.asList(31 new Account2("Justin", "X1234", 1000),32 new Account2("Monica", "X5678", 500),33 new Account2("Irene", "X2468", 200)34 );35 Collections.sort(accounts);36 System.out.println(accounts);37 }38 }
Collections的sort()方法在取得a对象与b对象进行比较时,会先将a对象扮演(Cast)为Comparable(也因此若对象没实现Comparable,将会抛出ClassCastException),然后调用a.compareTo(b),如果a对象顺序上小于b对象,必须返回小于0的值,若顺序上相等则返回0,若大于则返回大于0的值。为什么前面的Sort类中,可以直接对Integer进行排序呢?若查看API文档,我们就可以发现,Integer实现了Comparable接口。
第七节:使用泛型
Collection收集对象的时候,考虑到会收集各种对象,所以内部实现采用了Object参考收集的对象,所以执行时期被收集的对象会失去形态信息,也因此取回对象之后,必须自行记得对象的真正类型,并在语法上告诉编译程序让对象重新扮演为自己的类型。
Collection虽然可以收集各种对象,但实际上通常只会收集同一种类型的对象,例如都是收集Integer对象。在JDK5之后,新增了泛型(Genertics)语法,让我们在设计API时可以指定类或方法支持泛型,而是用API的客户端在语法上会更为简洁,并得到编译时期检查。加入泛型语法的ArrayList示范:
1 package cc.openhome; 2 3 import java.util.Arrays; 4 5 public class ArrayList{ 6 private Object[] list; 7 private int next; 8 9 public ArrayList(int capacity) {10 list = new Object[capacity];11 }12 13 public ArrayList() {14 this(16);15 }16 17 public void add(E e) {18 if(next == list.length) {19 list = Arrays.copyOf(list, list.length * 2);20 }21 list[next++] = e;22 }23 24 public E get(int index) {25 return (E) list[index];26 }27 28 public int size() {29 return next;30 }31 }
注意到类名称盘的角括号<E>,这表示这个类支持泛型。实际加入ArrayList的对象会是客户端声明的E类型。当然了,E(Element)只是一个类型代号,我们可以用A~Z都行。由于使用<E>定义类型,在需要编译程序检查类型的地方,都可以使用E,例如add()方法必须检查传入的对象类型是E,get()方法必须转换为E类型。
Collection就进行到这里了。更多的内容,尤其是关于泛型的语法细节,我们会在后面的博客中介绍。
第九章:键-值对应的Map
根据某个键(Key)来取得对应的值(Value),我们可以用实现java.util.Map接口的类对象来建立键值对应数据。建立之后,如果要取得值,只要用对应的键就可以迅速取得。
第一节:Map设计架构
先了解一下Map设计架构,对正确使用API帮助相当大,至少不会稀里糊涂。
常用的Map实现类有java.util.HashMap与java.util.TreeMap,都继承了抽象类java.util.AbstractMap。至于Dictionary和HashTable是JDK1.0遗留下来的API,不被建议使用,但是图上没有的java.util.Properties是HashTable的子类,却是挺常用的。
第二节:HashMap
Map支持上一章最后提到的泛型语法,我们先直接来看一个范例,可以根据用户名称取得对应的信息:
1 package cc.openhome; 2 3 import java.util.*; 4 5 public class Messages { 6 public static void main(String[] args) { 7 Mapmessages = new HashMap<>(); //以泛型语法指定键值类型 8 messages.put("Justin", "Hello!Justin的信息!"); //建立键值对应 9 messages.put("Monica", "给Monica的悄悄话!");10 messages.put("Irene", "Irene的可爱猫喵喵叫!");11 12 Scanner scanner = new Scanner(System.in);13 System.out.print("取得谁的信息:");14 String message = messages.get(scanner.nextLine()); //根据键取回值15 System.out.println(message);16 System.out.println(messages);17 }18 }
建立Map实现对象的时候,可以使用泛型语法来指定键-值的类型。在这里键使用String,值也使用String类型。要建立键值对应,可以使用put()方法,第一个自变量是键,第二个变量是值。键,是不会重复的。如果要指定键取回对应的值,则使用get()方法。那么,大家知不知道,如果我们指定的键不存在,会有什么样的结果呢?这次先不看API文档,来一段源代码好了。
1 public V get(Object key) { 2 if (key == null) 3 return getForNullKey(); 4 int hash = hash(key.hashCode()); 5 for (Entrye = table[indexFor(hash, table.length)]; 6 e != null; 7 e = e.next) { 8 Object k; 9 if (e.hash == hash && ((k = e.key) == key || key.equals(k)))10 return e.value;11 }12 return null;13 }
根据这个get方法,我们很明显能看到,如果我们的key等于null,那么就会返回getForNullKey()的结果,如果key不为null也不在HashMap里,那就会返回null。(哈哈,这算是我第一次看JDK的源代码吧)。那getForNullKey()的结果是什么呢?其实Key的可以设为null,如果我们有一个键的值为null,get()方法就会返回键对应的值,如果不存在,则还是会返回null。
第三节:TreeMap
在HashMap中建立键值对应之后,键是无序的。如果想排序,我们可以用TreeMap。不过条件是作为键盘的对象必须实现Compareable接口,或者是在创建TreeMap时指定实现Comparable接口的对象。例如下面这个范例:
1 package cc.openhome; 2 3 import java.util.*; 4 5 public class Messages2 { 6 public static void main(String[] args) { 7 Mapmessages = new TreeMap<>(); 8 messages.put("Justin", "Hello!Justin的信息!"); 9 messages.put("Monica", "给Monica的悄悄话!");10 messages.put("Irene", "Irene的可爱猫喵喵叫!");11 System.out.println(messages);12 }13 }
由于String实现了Comparable接口,因此我们可以看到结果是排序的。(截图就不贴出来了,大家去试试吧。)
第四节:访问键值
有的时候,我们可能需要取得Map中所有的值,或者是想取得Map中所有的值。Map虽然跟Collection没有继承上的关系,然而却是彼此配合的API。
如果想要取得Map中所有的键,可以调用Map的keySet()返回Set对象。由于键是不重复的,所以用Set返回是当然的;如果想要取得Map中所有的值,则可以使用values()返回Collection对象。看下面这段代码:
1 package cc.openhome; 2 3 import java.util.*; 4 5 public class MapKeyValue { 6 public static void main(String[] args) { 7 Mapmap = new HashMap<>(); 8 map.put("one", "一"); 9 map.put("two", "二");10 map.put("three", "三");11 12 System.out.println("显示键");13 foreach(map.keySet());14 15 System.out.println("显示值");16 foreach(map.values());17 }18 19 public static void foreach(Iterable iterable) {20 for(String element : iterable) {21 System.out.println(element);22 }23 }24 }
或者说,想要同时取得Map的键和值,我们可以使用entrySet()方法,这会返回一个Set对象,每个元素都是Map.Entry实例,可以调用getKey()取得键,调用getValue()取得值。看下面这段代码:
1 package cc.openhome; 2 3 import java.util.*; 4 5 public class MapKeyValue2 { 6 public static void main(String[] args) { 7 Mapmap = new TreeMap<>(); 8 map.put("one", "一"); 9 map.put("two", "二");10 map.put("three", "三");11 foreach(map.entrySet());12 }13 14 public static void foreach(Iterable > iterable) {15 for(Map.Entry entry: iterable) {16 System.out.printf("(键 %s, 值 %s)%n", 17 entry.getKey(), entry.getValue());18 }19 }20 }
——————————————————————————第二十天——————————————————————————
紧赶慢赶,终于赶完了。
1.今早妈妈给我打了个电话。我跟她说了我最近的情况。希望她能够明白,我最近过得很好,没有浪费大学仅剩不多的时间。
2.关于误会,恐怕只有理解和沟通才能解开。就怕赌气,就怕错过能够解开的时机。
3.昨晚还算有点怨气,到现在下午就已经慢慢没什么感觉了。我给自己最好的生日礼物就是,按照我希望的那样成长起来。