一、HashMap的初始化
1、HashMap 初始化的文藝寫法
HashMap 是一種常用的數據結構,一般用來做數據字典或者 Hash 查找的容器。普通青年一般會這么初始化:
1
2
3
4
|
HashMap<String, String> map = new HashMap<String, String>(); map.put( "Name" , "June" ); |
看完這段代碼,很多人都會覺得這么寫太啰嗦了,對此,文藝青年一般這么來了:
1
2
3
4
5
6
7
|
HashMap<String, String> map = new HashMap<String, String>() { { put( "Name" , "June" ); put( "QQ" , "2572073701" ); } }; |
嗯,看起來優雅了不少,一步到位,一氣呵成的趕腳。然后問題來了,有童鞋會問:納尼?這里的雙括號到底什么意思,什么用法呢?哈哈,其實很簡單,看看下面的代碼你就知道啥意思了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class Test { /*private static HashMap< String, String> map = new HashMap< String, String>() { { put("Name", "June"); put("QQ", "2572073701"); } };*/ public Test() { System.out.println( "Constructor called:構造器被調用" ); } static { System.out.println( "Static block called:靜態塊被調用" ); } { System.out.println( "Instance initializer called:實例初始化塊被調用" ); } public static void main(String[] args) { new Test(); System.out.println( "=======================" ); new Test(); } } |
輸出:
1
2
3
4
5
6
|
Static block called:靜態塊被調用 Instance initializer called:實例初始化被調用 Constructor called:構造器被調用 ======================= Instance initializer called:實例初始化被調用 Constructor called:構造器被調用 |
也就是說第一層括弧實際是定義了一個匿名內部類 (Anonymous Inner Class),第二層括弧實際上是一個實例初始化塊 (instance initializer block),這個塊在內部匿名類構造時被執行。這個塊之所以被叫做“實例初始化塊”是因為它們被定義在了一個類的實例范圍內。
上面代碼如果是寫在 Test 類中,編譯后你會看到會生成 Test$1.class 文件,反編譯該文件內容:
1
|
D:\eclipse_indigo\workspace_home\CDHJobs\bin\pvuv\>jad -p Test$1.class |
1
2
3
4
|
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) // Source File Name: Test.java |
1
2
3
4
5
6
7
8
9
10
11
12
|
package pvuv.zhaopin; import java.util.HashMap; // Referenced classes of package pvuv.zhaopin: // Test class Test$ 1 extends HashMap // 創建了一個 HashMap 的子類 { Test$ 1 () { // 第二個 {} 中的代碼放到了構造方法中去了 put( "Name" , "June" ); put( "QQ" , "2572073701" ); } } |
1
|
D:\eclipse_indigo\workspace_home\CDHJobs\bin\pvuv\> |
2、推而廣之
這種寫法,推而廣之,在初始化 ArrayList、Set 的時候都可以這么玩,比如你還可以這么玩:
1
2
3
4
5
6
7
8
|
List<String> names = new ArrayList<String>() { { for ( int i = 0 ; i < 10 ; i++) { add( "A" + i); } } }; System.out.println(names.toString()); // [A0, A1, A2, A3, A4, A5, A6, A7, A8, A9] |
3、Java7:增加對 collections 的支持
在 Java 7 中你可以像 Ruby, Perl、Python 一樣創建 collections 了。
Note:這些集合是不可變的。
PS:由于原文[5]作者并沒有標出 java 7 哪個小版本號引入的這些新特性,對于留言報錯的同學,請嘗試大于 1.7.0_09 或者 java8 試試?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
List list = new ArrayList(); list.add( "item" ); String item = list.get( 0 ); Set< String> set = new HashSet< String>(); set.add( "item" ); Map< String, Integer> map = new HashMap< String, Integer>(); map.put( "key" , 1 ); int value = map.get( "key" ); // 現在你還可以: List< String> list = [ "item" ]; String item = list[ 0 ]; Set< String> set = { "item" }; Map< String, Integer> map = { "key" : 1 }; int value = map[ "key" ]; |
4、文藝寫法的潛在問題
文章開頭提到的文藝寫法的好處很明顯就是一目了然。這里來羅列下此種方法的壞處,如果這個對象要串行化,可能會導致串行化失敗。
1.此種方式是匿名內部類的聲明方式,所以引用中持有著外部類的引用。所以當串行化這個集合時外部類也會被不知不覺的串行化,當外部類沒有實現serialize接口時,就會報錯。
2.上例中,其實是聲明了一個繼承自HashMap的子類。然而有些串行化方法,例如要通過Gson串行化為json,或者要串行化為xml時,類庫中提供的方式,是無法串行化Hashset或者HashMap的子類的,從而導致串行化失敗。解決辦法:重新初始化為一個HashMap對象:
1
|
new HashMap(map); |
這樣就可以正常初始化了。
5、執行效率問題
當一種新的工具或者寫法出現時,猿們都會來一句:性能怎么樣?(這和男生談論妹紙第一句一般都是:“長得咋樣?三圍多少?”一個道理:))
關于這個兩種寫法我這邊筆記本上測試文藝寫法、普通寫法分別創建 10,000,000 個 Map 的結果是 1217、1064,相差 13%。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class Test { public static void main(String[] args) { long st = System.currentTimeMillis(); /* for (int i = 0; i < 10000000; i++) { HashMap< String, String> map = new HashMap< String, String>() { { put("Name", "June"); put("QQ", "2572073701"); } }; } System.out.println(System.currentTimeMillis() - st); // 1217 */ for ( int i = 0 ; i < 10000000 ; i++) { HashMap< String, String> map = new HashMap< String, String>(); map.put( "Name" , "June" ); map.put( "QQ" , "2572073701" ); } System.out.println(System.currentTimeMillis() - st); // 1064 } } |
6、由實例初始化塊聯想到的一些變量初始化問題
從代碼上看,a 為什么可以不先聲明類型?你覺得 a、b、c 的值分別是多少?能說明理由么?
TIPS:如果你對這塊機制不了解,建議試著反編譯一下字節碼文件。
6.1 測試源碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
public class Test { int e = 6 ; Test() { int c = 1 ; this .f = 5 ; int e = 66 ; } int f = 55 ; int c = 11 ; int b = 1 ; { a = 3 ; b = 22 ; } int a = 33 ; static { d = 4 ; } static int d = 44 ; int g = 7 ; int h = 8 ; public int test(){ g = 77 ; int h = 88 ; System.out.println( "h - 成員變量:" + this .h); System.out.println( "h - 局部變量: " + h); return g; } public static void main(String[] args) { System.out.println( "a: " + new Test().a); System.out.println( "b: " + new Test().b); System.out.println( "c: " + new Test().c); System.out.println( "d: " + new Test().d); System.out.println( "f: " + new Test().f); System.out.println( "e: " + new Test().e); System.out.println( "g: " + new Test().test()); } } |
6.2 字節碼反編譯:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) // Source File Name: Test.java import java.io.PrintStream; public class Test { Test() { this .e = 6 ; f = 55 ; this .c = 11 ; b = 1 ; a = 3 ; b = 22 ; a = 33 ; g = 7 ; h = 8 ; int c = 1 ; f = 5 ; int e = 66 ; } public int test() { g = 77 ; int h = 88 ; System.out.println(( new StringBuilder( "h - \u6210\u5458\u53D8\u91CF\uFF1A" )).append( this .h).toString()); System.out.println(( new StringBuilder( "h - \u5C40\u90E8\u53D8\u91CF: " )).append(h).toString()); return g; } public static void main(String args[]) { System.out.println(( new StringBuilder( "a: " )).append(( new Test()).a).toString()); System.out.println(( new StringBuilder( "b: " )).append(( new Test()).b).toString()); System.out.println(( new StringBuilder( "c: " )).append(( new Test()).c).toString()); new Test(); System.out.println(( new StringBuilder( "d: " )).append(d).toString()); System.out.println(( new StringBuilder( "f: " )).append(( new Test()).f).toString()); System.out.println(( new StringBuilder( "e: " )).append(( new Test()).e).toString()); System.out.println(( new StringBuilder( "g: " )).append(( new Test()).test()).toString()); } int e; int f; int c; int b; int a; static int d = 4 ; int g; int h; static { d = 44 ; } } 6.3 output: a: 33 b: 22 c: 11 d: 44 f: 5 e: 6 h - 成員變量: 8 h - 局部變量: 88 g: 77 |
二、HashMap遍歷方法示例
第一種:
1
2
3
4
5
6
|
Map map = new HashMap(); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); Object key = entry.getKey(); Object val = entry.getValue(); } |
效率高,以后一定要使用此種方式!
第二種:
1
2
3
4
5
6
|
Map map = new HashMap(); Iterator iter = map.keySet().iterator(); while (iter.hasNext()) { Object key = iter.next(); Object val = map.get(key); } |
效率低,以后盡量少使用!
HashMap的遍歷有兩種常用的方法,那就是使用keyset及entryset來進行遍歷,但兩者的遍歷速度是有差別的,下面請看實例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public class HashMapTest { public static void main(String[] args) ...{ HashMap hashmap = new HashMap(); for ( int i = 0 ; i < 1000 ; i ) ...{ hashmap.put( "" i, "thanks" ); } long bs = Calendar.getInstance().getTimeInMillis(); Iterator iterator = hashmap.keySet().iterator(); while (iterator.hasNext()) ...{ System.out.print(hashmap.get(iterator.next())); } System.out.println(); System.out.println(Calendar.getInstance().getTimeInMillis() - bs); listHashMap(); } public static void listHashMap() ...{ java.util.HashMap hashmap = new java.util.HashMap(); for ( int i = 0 ; i < 1000 ; i ) ...{ hashmap.put( "" i, "thanks" ); } long bs = Calendar.getInstance().getTimeInMillis(); java.util.Iterator it = hashmap.entrySet().iterator(); while (it.hasNext()) ...{ java.util.Map.Entry entry = (java.util.Map.Entry) it.next(); // entry.getKey() 返回與此項對應的鍵 // entry.getValue() 返回與此項對應的值 System.out.print(entry.getValue()); } System.out.println(); System.out.println(Calendar.getInstance().getTimeInMillis() - bs); } } |
對于keySet其實是遍歷了2次,一次是轉為iterator,一次就從hashmap中取出key所對于的value。而entryset只是遍歷了第一次,他把key和value都放到了entry中,所以就快了。
注:Hashtable的遍歷方法和以上的差不多!