映射(Map)

键值对

Map是一个接口 —— 常用的实现类有HashMap、TreeMap

一、 HashMap

最基本、最常用的一种Map

1. 如何创建Map

List

  • 添加 —— add(obj)
  • 获取 —— get(obj)

Map

  • 添加<key, value> —— put(key, value)
  • 用key获取value —— get(key)
//HashMap<key, value>
public class Test01 {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap();

        map.put("小明",15);//姓名、年龄
        map.put("小红",14);
        map.put("小刘",18);
        map.put("小王",20);

        String[] man = "小刘,小王,小强".split(",");

        for (String s : man) {
            System.out.println(s + map.get(s) + "岁了");
        }        
    }
}
/*
小刘18岁了
小王20岁了
小强null岁了
*/
  • 小强之所以为null岁是因为get的键不存在,返回null

    • 因此需要先判断get()的返回值,是否为null,而不是直接使用

      for (String s : man) {
          if(map.get(s)!=null) {
              System.out.println(s + map.get(s) + "岁了");
          }
      }
      /*
      小刘18岁了
      小王20岁了
      */
      

2. 如何遍历Map

两种方式

Map

public class Test02 {
    public static void main(String[] args) {

        //泛型中都是引用对象,因此只能是Character,不能是char
        Map<Character, Double> map = new HashMap();

        map.put('b',6.66);
        map.put('a',3.14);
        map.put('d',13.14);
        map.put('c',9.99);

        //keySet()方法返回,map中的所有的键
        for (Character key : map.keySet()) {
            System.out.print(key+" = ");//获取键
            System.out.println(map.get(key));//获取值
        }
    }
}
/*
a = 3.14
b = 6.66
c = 9.99
d = 13.14
*/
  • 虽然全部输出了,但不是按照put的顺序输出的

    • 因为put(key, value)后,计算其对应的hash值,存放到对应的位置

      image-20201207211419931
    • 因此无法保证hashmap是有序的

Entry

public class Test02 {
    public static void main(String[] args) {
        
        Map<Character, Double> map = new HashMap();

        map.put('b',6.66);
        map.put('a',3.14);
        map.put('d',13.14);
        map.put('c',9.99);

        //entrySet()
        for (Map.Entry<Character,Double> entry : map.entrySet()) {
            String s = String.format("%c = %.2f",entry.getKey(),entry.getValue());
            System.out.println(s);
        }
    }
}
/*
a = 3.14
b = 6.66
c = 9.99
d = 13.14
*/
  • entry返回一个集合

    image-20201207212759619

3. 自定义类作为key

内存机制

public class Test02 {
    public static void main(String[] args) {

        Map<Character, Double> map = new HashMap();

        map.put('b',6.66);
        map.put('a',3.14);
        map.put('d',13.14);
        map.put('c',9.99);

        Character character = new Character('c');
        //两个c不是同一个对象,但依旧可以获得对应的value值
        System.out.println(map.get(character));//9.99

    }
}
  • 由此可以得出键值比较不是用==比较的,而是用equals()比较的

  • 键值比较是equals实现的

自定义的类为Key

之前的key都是系统提供的类,如果使用用户自定义的类作为key该如何判断

  • 重写equals()和hashcode

    • 由于Object的hashCode返回的是对象的hash值,所以即使equals返回TRUE,集合也可能判定两个对象不等,所以必须重写hashCode方法,以保证当equals返回TRUE时,hashCode也返回Ture,这样才能使得集合中存放的对象唯一。

    • 详解为什么要重写equals()和hashcode

      public class Test03 {
          public static void main(String[] args) {
              Map<Player, String> map = new HashMap();
      
              //人,职业
              map.put(new Player("小明",15,"唱",2),"歌唱家");
              map.put(new Player("小红",14,"跳",3),"舞蹈家");
              map.put(new Player("小刘",18,"rap",4),"说唱歌手");
              map.put(new Player("小王",20,"篮球",5),"运动员");
      
              Player man = new Player("小明",15,"唱",2);
              
              //如果不重写equals和hashcode会返回null
              System.out.println(map.get(man));//歌唱家
          }
      }
      
      class Player {
          String name;
          int age;
          String hobby;
          double grade;
      
          public Player(String name, int age, String hobby, int grade) {
              this.name = name;
              this.age = age;
              this.hobby = hobby;
              this.grade = grade;
          }
      
          @Override
          public String toString() {
              String s = String.format("%d岁上%d年级的%s喜欢%s",age, grade,name,hobby);
              return s;
          }
      
          @Override
          public int hashCode() {
              int hash = 0;
              hash += hash * 66 + name.hashCode();
              hash += hash * 66 + age;
              hash += hash * 66 + hobby.hashCode();
              hash += hash * 66 + Double.doubleToLongBits(grade);
              return hash;
          }
      
          @Override
          public boolean equals(Object obj) {
              boolean nameTF;
              boolean ageTF;
              boolean hobbyTF;
              boolean gradeTF;
      
              Player player = (Player)obj;
              if (player.age == this.age) {
                  ageTF = true;
              } else {
                  ageTF = false;
              }
      
              if (player.name.equals(null)){
                  nameTF = false;
              } else if (player.name.equals(this.name)) {
                  nameTF = true;
              } else {
                  nameTF = false;
              }
      
              if (player.hobby.equals(null)){
                  hobbyTF = false;
              } else if (player.hobby.equals(this.hobby)) {
                  hobbyTF = true;
              } else {
                  hobbyTF = false;
              }
      
              if (player.grade == this.grade) {
                  gradeTF = true;
              } else {
                  gradeTF = false;
              }
      
              return ageTF && hobbyTF && nameTF && gradeTF;
          }
      }
      

二、 EnumMap

如果key是Enum 可以使用 EnumMap

EnumMap初始化的时候需要使用有参构造

image-20201208163757060
public class Test04 {
    public static void main(String[] args) {
        //等号另一侧new会自动补充泛型
        Map<DayOfWeek,String> map = new EnumMap<DayOfWeek, String>(DayOfWeek.class);
        map.put(DayOfWeek.MONDAY,"周一");

        System.out.println(map.get(DayOfWeek.MONDAY));//周一

    }
}

三、 TreeMap

hashMap是按照计算出hash值顺序存储的,而不是按照put(key,value)进入的先后排序的,因此为无序的

1. TreeMap是有序map——自定义排序

而不是所谓的先进先出

public class Test05 {
    public static void main(String[] args) {
        //无序Map
        System.out.println("=============hashMap=============");
        Map<String, String> map = new HashMap();

        map.put("一号","小明");
        map.put("二号","小红");
        map.put("三号","小刘");
        map.put("四号","小王");

        for (String key : map.keySet()) {
            System.out.print(key);//键
            System.out.println(map.get(key));//值
        }
        
        //有序Map
        System.out.println("=============treeMap=============");
        Map<String,String> seqMap = new TreeMap<>();

        seqMap.put("一号","小明");
        seqMap.put("二号","小红");
        seqMap.put("三号","小刘");
        seqMap.put("四号","小王");

        for (String key : seqMap.keySet()) {
            System.out.print(key);
            System.out.println(seqMap.get(key));
        }

    }
}
/*
=============hashMap=============
二号小红
四号小王
三号小刘
一号小明
=============treeMap=============
一号小明
三号小刘
二号小红
四号小王
*/
  • 之所以能排序的原因——treeMap的put方法实现了用到了Comparator接口、comparable接口

    TreeMap源码

    2020-12-08_182414

2. 自定义的类

系统提供的类都重写了comparable接口的方法,因此这些类是可以在TreeMap中进行比较排序的。

  • String

    image-20201208204529202
  • Integer

    image-20201208204637294

而由于自定义的类不是一个可以比较的对象,在TreeMap中会抛出异常

  • 把Map里的四个人按年龄从小到大排序

    public class Test06 {
        public static void main(String[] args) {
            Map<Person,String> map = new TreeMap<>();
    
            map.put(new Person("小明",15),"歌唱家");
            map.put(new Person("小红",14),"舞蹈家");
            map.put(new Person("小刘",18),"说唱歌手");
            map.put(new Person("小王",20),"运动员");
    
            for (Map.Entry<Person,String> entry : map.entrySet()) {
                System.out.println(entry);
            }
            
            /* 在java10中引入了var,可以由编译器判断类型
            for (var entry : map.entrySet()) {
                System.out.println(entry);
            } */        
            
        }
    }
    
    class Person {
        String name;
        int age;
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        
        @Override
        public String toString() {
            String s = String.format("%d岁的%s",this.age,this.name);
            return s;
        }
    }
    
    • 抛出异常

      image-20201208183838151

    • 解决方案

      1. 把Person这个自定义类去实现comparable接口

        //如果不约束Conparable接口的泛型,CompareTo方法的参数默认为Objeact类型
        //需要先强制转换成Person类型,即Person person = (Person)o;
        class Person implements Comparable<Person> {
            String name;
            int age;
        
            public Person(String name, int age) {
                this.name = name;
                this.age = age;
            }
        
            @Override
            public String toString() {
                String s = String.format("%d岁的%s",this.age,this.name);
                return s;
            }
            //compare,比较此对象与指定对象的顺序,如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
        
            @Override
            public int compareTo(Person o) {
                //返回的值>0则说明,这个对象就大于当前的对象
                return this.age-o.age;
            }
        }
        /*
        14岁的小红=舞蹈家
        15岁的小明=歌唱家
        18岁的小刘=说唱歌手
        20岁的小王=运动员
        */
        
      2. 写一个比较器的实现类作为TreeMap的参数

        public class Test06 {
            public static void main(String[] args) {
                //创建比较器
                PersonName personName = new PersonName();
        
                //在TreeMap的有参构造,放入比较器
                Map<Person,String> map = new TreeMap<>(personName);
        
                map.put(new Person("小明",15),"歌唱家");
                map.put(new Person("小红",14),"舞蹈家");
                map.put(new Person("小刘",18),"说唱歌手");
                map.put(new Person("小王",20),"运动员");
        
                for (Map.Entry<Person,String> entry : map.entrySet()) {
                    System.out.println(entry);
                }
            }
        }
        
        class Person {
            String name;
            int age;
        
            public Person(String name, int age) {
                this.name = name;
                this.age = age;
            }
        
            @Override
            public String toString() {
                String s = String.format("%d岁的%s",this.age,this.name);
                return s;
            }
        }
        
        //比较器
        class PersonName implements Comparator<Person> {
            @Override
            public int compare(Person o1, Person o2) {
                //o2.age-o1.age就是逆序
                return o1.age-o2.age;
            }
        }
        /*
        14岁的小红=舞蹈家
        15岁的小明=歌唱家
        18岁的小刘=说唱歌手
        20岁的小王=运动员
        */
        
        • compare方法比较o1和o2的大小,然后返回一个int类型的值

          • 返回值"<0"代表“o1比o2小”,"=0"代表“o1等于o2”,">0"代表“o1比o2大”
        • 为了方便也可以不写外部类,把比较器写成匿名内部类

          public class Test06 {
              public static void main(String[] args) {
          
                  //在TreeMap的有参构造,放入比较器
                  Map<Person,String> map = new TreeMap<>(new Comparator<Person>() {
                      @Override
                      public int compare(Person o1, Person o2) {
                          return o1.age-o2.age;
                      }
                  });
          
                  map.put(new Person("小明",15),"歌唱家");
                  map.put(new Person("小红",14),"舞蹈家");
                  map.put(new Person("小刘",18),"说唱歌手");
                  map.put(new Person("小王",20),"运动员");
          
                  for (Map.Entry<Person,String> entry : map.entrySet()) {
                      System.out.println(entry);
                  }
              }
          }
          
          class Person {
              String name;
              int age;
          
              public Person(String name, int age) {
                  this.name = name;
                  this.age = age;
              }
          
              @Override
              public String toString() {
                  String s = String.format("%d岁的%s",this.age,this.name);
                  return s;
              }
          }
          /*
          14岁的小红=舞蹈家
          15岁的小明=歌唱家
          18岁的小刘=说唱歌手
          20岁的小王=运动员
          */
          

3. Comparable接口和Comparetor的比较

  • Comparable相当于内部比较器
    • 比较简单,但需要修改源代码
    • 只要实现Comparable 接口的对象直接就成为一个可以比较的对象
  • Comparator相当于外部比较器
    • 自定义程度高,不需要修改源代码,而是另外实现一个比较器
      • 在这个比较器中,可以自己实现复杂的可以通用的逻辑,还可以把比较器和对象一起传递

四、LinkedHashMap

TreeMap虽然是有序Map,但是只能进行排序,仍无法做到FIFO——按put的顺序输出

  • LinkedHashMap也是有序Map,但是LinkedHashMap内元素顺序只和插入顺序有关
public class Test07 {
    public static void main(String[] args) {
        Map<Person,String> map = new LinkedHashMap<>();

        map.put(new Person("小明",15),"歌唱家");
        map.put(new Person("小红",14),"舞蹈家");
        map.put(new Person("小刘",18),"说唱歌手");
        map.put(new Person("小王",20),"运动员");

        for (Map.Entry<Person,String> entry : map.entrySet()) {
            System.out.println(entry);
        }
    }
}

class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        String s = String.format("%d岁的%s",this.age,this.name);
        return s;
    }
}
/*
15岁的小明=歌唱家
14岁的小红=舞蹈家
18岁的小刘=说唱歌手
20岁的小王=运动员
*/
posted @ 2020-12-08 20:05  球球z  阅读(178)  评论(0)    收藏  举报