聊聊 Java 中 HashMap 初始化的另一种方式

如果你接触过不同的语言,从语法和代码层面来说,Java 是一种不折不扣的“臃肿、啰嗦”的语言,从另一方面来说这种臃肿和啰嗦也体现了它严谨的一面,作为适合构建大型、复杂项目的理由之一。

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();

    }
}

output:

Static block called:静态块被调用
Instance initializer called:实例初始化被调用
Constructor called:构造器被调用
=======================
Instance initializer called:实例初始化被调用
Constructor called:构造器被调用

Note:关于 static 的作用与用法如果不了解,请参考:

http://my.oschina.net/leejun2005/blog/193439#OSC_h3_1  为什么 main 方法是 public static void?

http://my.oschina.net/leejun2005/blog/144349#OSC_h3_2  设计模式之:聊聊 java 中的单例模式(Singleton)

也就是说第一层括弧实际是定义了一个匿名内部类 (Anonymous Inner Class),第二层括弧实际上是一个实例初始化块 (instance initializer block),这个块在内部匿名类构造时被执行。这个块之所以被叫做“实例初始化块”是因为它们被定义在了一个类的实例范围内。 上面代码如果是写在 Test 类中,编译后你会看到会生成 Test$1.class 文件,反编译该文件内容:

D:\eclipse_indigo\workspace_home\CDHJobs\bin\pvuv\>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_indigo\workspace_home\CDHJobs\bin\pvuv\>

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、Java7:增加对 collections 的支持

在 Java 7 中你可以像 Ruby, Perl、Python 一样创建 collections 了。

Note:这些集合是不可变的。

PS:由于原文[5]作者并没有标出 java 7 哪个小版本号引入的这些新特性,对于留言报错的同学,请尝试大于 1.7.0_09 或者 java8 试试?

List<String> list = new ArrayList<String>();
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对象:

new HashMap(map);

这样就可以正常初始化了。

5、执行效率问题

当一种新的工具或者写法出现时,猿们都会来一句:性能怎么样?(这和男生谈论妹纸第一句一般都是:“长得咋样?三围多少?”一个道理:)) 关于这个两种写法我这边笔记本上测试文艺写法、普通写法分别创建 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
    }
}

6、由实例初始化块联想到的一些变量初始化问题

从代码上看,a 为什么可以不先声明类型?你觉得 a、b、c 的值分别是多少?能说明理由么?

TIPS:如果你对这块机制不了解,建议试着反编译一下字节码文件。

6.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());
    }

}

6.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 - \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

7、Refer:

[1]Double Brace Initialization In Java!  http://viralpatel.net/blogs/double-brace-initialization-in-java/

[2]Double Brace Initialization Idiom and its Drawbacks  http://java.dzone.com/articles/double-brace-initialization

[3]Hidden Features of Java  http://stackoverflow.com/questions/15496/hidden-features-of-java

[4]Java 大括号语法糖  http://my.oschina.net/trydofor/blog/79222

[5]Java 7 的新特性:http://code.joejag.com/2009/new-language-features-in-java-7/

http://www.iteye.com/news/11490-java-7?page=5

[6]java map双括号初始化方式的问题  http://blog.csdn.net/liubo2012/article/details/8591956

[7]Efficiency of Java “Double Brace Initialization”?  http://stackoverflow.com/questions/924285/efficiency-of-java-double-brace-initialization

[8]初始化代码块中的变量,为什么可以不需要声明数据类型?  http://segmentfault.com/q/1010000000580267

[9]Java 8速查表  http://www.importnew.com/12156.html

[10] Java 8新特性之旅:使用Stream API处理集合    http://www.importnew.com/16545.html

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏决胜机器学习

PHP数据结构(二十) ——其他插入排序

PHP数据结构(二十)——其他插入排序 (原创内容,转载请注明来源,谢谢) 注:本文是衔接直接插入排序的,因此直接插入排序的相关内容请点击——PHP...

37470
来自专栏企鹅号快讯

Java字符串的10大热点问题盘点

往期精选 下面我为大家总结了10条Java开发者经常会提的关于Java字符串的问题,如果你也是Java初学者,仔细看看吧: 1、如何比较字符串,应该用”==”还...

38880
来自专栏Java后端技术

这个坑,是时候填上了~

​  这两天,在网上逛的时候,发现了如下的一道面试题,感觉还有蛮有意思的,要是不仔细看还真容易掉到坑里面。第一眼看起来比较绕,所以比较难理解。最终我跳出了这个坑...

8510
来自专栏云霄雨霁

Java--多态性之内部类和匿名类

18960
来自专栏于晓飞的专栏

Java 泛型进阶

在 List<String> 中添加 Integer 将不会通过编译,但是List<Sring>与List<Integer>在运行时的确是同一种类型。

24430
来自专栏全沾开发(huā)

co源码解读

co源码解读 背景: 闲来无事,翻了下co的源码来看,源码短小精悍,算上注释,一共240行左右; 决定写一篇博客来记录下学习的心得。 ...

36360
来自专栏用户2442861的专栏

java final 关键字

http://blog.csdn.net/niguang09/article/details/6035813

10520
来自专栏女程序员的日常

值类型和引用类型的区别,struct和class的区别

C#值类型和引用类型 1、简单比较   值类型的变量直接存储数据,而引用类型的变量持有的是数据的引用,数据存储在数据堆中。   值类型(value type):...

27910
来自专栏向治洪

Kotlin的委托属性和区间

委托属性 委托属性 是一种通过委托实现拥有 getter 和可选 setter 的 属性,并允许实现可复用的自定义属性。例如: class Example { ...

25270
来自专栏DannyHoo的专栏

OC中的类和对象

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010105969/article/de...

24760

扫码关注云+社区

领取腾讯云代金券