• 你可能没用过这种方式的集合!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的遍历方法和以上的差不多!

    来源:https://blog.csdn.net/luman1991/article/details/53034602

    ● 面试再也不怕 Redis 被问的脸发绿了

    ● MySQL百万级数据分页查询优化

    ● 恕我直言,你可能真没用过这些 IDEA 插件!

    ● 用过好几个注册中心,你竟然不知道他们的区别?

    ● Redis 的 KEYS 命令不能乱用啊

  • 相关阅读:
    24张图,九大数据结构安排得明明白白
    mysql中的mvcc解读
    常见电商项目的数据库表设计(MySQL版)
    两万字深度介绍分布式系统原理,一文入魂
    使用消息中间件时,如何保证消息仅仅被消费一次?
    GCC/G++选项 -Wl,-Bstatic和-Wl,-Bdynamic
    sql 练习
    设计模式-单例模式
    设计模式-抽象工厂模式
    设计模式-工厂方法模式
  • 原文地址:https://www.cnblogs.com/coding-farmer/p/13678345.html
Copyright © 2020-2023  润新知