你可能沒用過這種方式的集合!new HashMap<K,V>(){{put(K,V);}};
一、HashMap的初始化
1、HashMap 初始化的文藝寫法
HashMap 是一種常用的資料結構,一般用來做資料字典或者 Hash 查詢的容器。普通青年一般會這麼初始化:
HashMap<String, String> map =
new HashMap<String, String>();
map.put("Name", "June");
map.put("QQ", "2572073701");
看完這段程式碼,很多人都會覺得這麼寫太囉嗦了,對此,文藝青年一般這麼來了:
HashMap<String, String> map = new HashMap<String, String>() { { put("Name", "June"); put("QQ", "2572073701"); } };
嗯,看起來優雅了不少,一步到位,一氣呵成的趕腳。然後問題來了,有童鞋會問:納尼?這裡的雙括號到底什麼意思,什麼用法呢?哈哈,其實很簡單,看看下面的程式碼你就知道啥意思了。
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(); } }
輸出:
Static block called:靜態塊被呼叫
Instance initializer called:例項初始化被呼叫
Constructor called:構造器被呼叫
=======================
Instance initializer called:例項初始化被呼叫
Constructor called:構造器被呼叫
也就是說第一層括弧實際是定義了一個匿名內部類 (Anonymous Inner Class),第二層括弧實際上是一個例項初始化塊 (instance initializer block),這個塊在內部匿名類構造時被執行。這個塊之所以被叫做“例項初始化塊”是因為它們被定義在了一個類的例項範圍內。
上面程式碼如果是寫在 Test 類中,編譯後你會看到會生成 Test$1.class 檔案,反編譯該檔案內容:
D:eclipse_indigoworkspace_homeCDHJobsbinpvuv>jad -p Test$1.class
// 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
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");
}
}
D:eclipse_indigoworkspace_homeCDHJobsbinpvuv>
2、推而廣之
這種寫法,推而廣之,在初始化 ArrayList、Set 的時候都可以這麼玩,比如你還可以這麼玩:
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、文藝寫法的潛在問題
文章開頭提到的文藝寫法的好處很明顯就是一目瞭然。這裡來羅列下此種方法的壞處,如果這個物件要序列化,可能會導致序列化失敗。
- 此種方式是匿名內部類的宣告方式,所以引用中持有著外部類的引用。所以當序列化這個集合時外部類也會被不知不覺的序列化,當外部類沒有實現serialize介面時,就會報錯。
- 上例中,其實是聲明瞭一個繼承自HashMap的子類。然而有些序列化方法,例如要通過Gson序列化為json,或者要序列化為xml時,類庫中提供的方式,是無法序列化Hashset或者HashMap的子類的,從而導致序列化失敗。
解決辦法,重新初始化為一個HashMap物件:
new HashMap(map);
這樣就可以正常初始化了。
4、執行效率問題
當一種新的工具或者寫法出現時,猿們都會來一句:效能怎麼樣?(這和男生談論妹紙第一句一般都是:“長得咋樣?三圍多少?”一個道理:))
關於這個兩種寫法我這邊筆記本上測試文藝寫法、普通寫法分別建立 10,000,000 個 Map 的結果是 1217、1064,相差 13%。
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
}
}
5、由例項初始化塊聯想到的一些變數初始化問題
從程式碼上看,a 為什麼可以不先宣告型別?你覺得 a、b、c 的值分別是多少?能說明理由麼? TIP:如果你對這塊機制不瞭解,建議試著反編譯一下位元組碼檔案。
5.1 測試原始碼
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());
}
}
5.2 位元組碼反編譯:
// 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 - u6210u5458u53D8u91CFuFF1A")).append(this.h).toString());
System.out.println((new StringBuilder("h - u5C40u90E8u53D8u91CF: ")).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;
}
}
5.3 輸出結果:
a: 33
b: 22
c: 11
d: 44
f: 5
e: 6
h - 成員變數:8
h - 區域性變數: 88
g: 77
二、HashMap遍歷方法示例
第一種:
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();
}
效率高,以後一定要使用此種方式!
第二種:
Map map = new HashMap();
Iterator iter = map.keySet().iterator();
while (iter.hasNext()) {
Object key = iter.next();
Object val = map.get(key);
}
效率低,以後儘量少使用!
HashMap的遍歷有兩種常用的方法,那就是使用keyset及entryset來進行遍歷,但兩者的遍歷速度是有差別的,下面請看例項:
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() {
HashMap hashmap = new 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的遍歷方法和以上的差不多!