一、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的遍历方法和以上的差不多!