前情回顾:上一篇除了回顾了异常处理的后半部分之外,还进行了对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 @Override
17 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 @Override
17 public String toString() {
18 return String.format("Account2(%s, %s, %d)", name, number, balance);
19 }
20
21 @Override
22 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<E> {
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 Map<String, String> messages = 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 (Entry<K,V> e = 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 Map<String, String> messages = 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 Map<String, String> map = 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<String> 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 Map<String, String> map = 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<Map.Entry<String, String>> iterable) {
15 for(Map.Entry<String, String> entry: iterable) {
16 System.out.printf("(键 %s, 值 %s)%n",
17 entry.getKey(), entry.getValue());
18 }
19 }
20 }
——————————————————————————第二十天——————————————————————————
紧赶慢赶,终于赶完了。
1.今早妈妈给我打了个电话。我跟她说了我最近的情况。希望她能够明白,我最近过得很好,没有浪费大学仅剩不多的时间。
2.关于误会,恐怕只有理解和沟通才能解开。就怕赌气,就怕错过能够解开的时机。
3.昨晚还算有点怨气,到现在下午就已经慢慢没什么感觉了。我给自己最好的生日礼物就是,按照我希望的那样成长起来。