• 自顶向下理解Java集合框架(三)Map接口


    Map基本概念

    数据结构中Map是一种重要的形式。Map接口定义的是查询表,或称查找表,其用于储存所谓的键/值对(key-value pair),其中key是映射表的索引。

    JDK结构中还存在实现Map类似功能的遗留集合:

    Hashtable(线程安全的散列映射表)

    Properties(属性映射表),常用于配置文件(如db.properties)。

    Map接口源代码

    package java.util;
    
    /* 映射表(查询表)泛型接口 */
    public interface Map<K,V> { /* 基本与Collection相同,返回映射表中key-value映射对数量(内部属性size) */ int size(); /* 基本与Collection相同,返回映射表是否包含映射对 */ boolean isEmpty(); /* 返回映射表是否包含指定的键 */ boolean containsKey(Object key); /* 返回映射表是否包含指定的值,或者说指定的值是否有大于等于1个映射的键 */ boolean containsValue(Object value); /* 如果映射表中存在指定key,返回此key的值;否则,返回null */ V get(Object key); /* 将键值对放入映射表中。
    * 如果键存在,则用现在的值替换原有值,返回原有值对象;
    * 如果键不存在则存入键值对,返回null */
    */ V put(K key, V value); /* 移除指定键对应的值。如果存在键,则移除,并返回移除值对象;反之,则返回null */ V remove(Object key); /* 复制另一张映射表元素到本映射表中 */
    void putAll(Map<? extends K, ? extends V> m); /* 基本同Collection,清空映射表 */ void clear(); /* 获得键的Set集合 */ Set<K> keySet(); /* 获得值的Collection集合 */ Collection<V> values(); /* 获得键值对的Set集合 */ Set<Map.Entry<K, V>> entrySet(); /* 内部接口 Entry<K,V> */ interface Entry<K,V> { /* 获取键值对的键 */ K getKey(); /* 获取键值对的值 */ V getValue(); /* 设置键值对的值 */ V setValue(V value); /* 比较entry(键值对) */ boolean equals(Object o); /* 生成entry对象的hash值 */ int hashCode(); } /* 比较映射表 */ boolean equals(Object o); /* 生成映射表对象的hash码*/ int hashCode(); }

    深入理解码源

    对象比较好基友:equals(Object obj)hashcode()

    Map<K, V>接口及其内部接口Entry<K, V>都有这俩方法。如此设计,目的就是规范其实现子类,要求子类必须重写Object类的这俩方法,从而完成映射表这种数据结构的既定思想。

    boolean equals(Object o);   // 对象比较
    int hashCode();             // 哈希码

    集合框架通用方法:size()isEmpty()

    集合框架(包括Collection接口及其子接口ListSetMap接口)内部维护一个size属性,其描述用户提供可操纵元素数量,通过size()方法向用户提供可见性,通过isEmpty()方法向用户说明是否集合中仍存在其可操纵的元素。

    int size();                // 元素保有量
    boolean isEmpty();         // 元素数量是否为0 

    键、值存在性判断:containsKey(Object key)containsValue(Object value)

    boolean containsKey(Object key);       // 映射表是否包含指定键的元素
    boolean containsValue(Object value);   // 映射表是否包含指定值得元素

    映射表增删查改:

    增、改  V put(K key, V value)  V putAll(Map<? extends K, ? value V> m)

    删        V remove(Object key)   void clear()

    查        V get(Object key)

    V put(K key, V value)    // 放入或替换指定key的键值对      
    V putAll(Map<? extends K, ? value V> m)    // 将另一映射表所有元素放入本映射表
    V remove(Object key)     // 移除指定key的键值对 
    V get(Object key)        // 获取指定key的键值对
    void clear()             // 清除所有映射表元素 
    package com.forget406.study;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.TreeMap;
    
    public class MapStudy {
        @SuppressWarnings({ "unchecked", "rawtypes" })
        public static void main(String[] args) {
            /* JDK 1.7允许后面的尖括号内不写泛型变量 */
            Map<String, Coordinate> rect = 
                new HashMap<>();
            rect.put("point A", new Coordinate(0, 0));
            rect.put("point B", new Coordinate(1, 0));
            rect.put("point C", new Coordinate(1, 1));
            rect.put("point D", new Coordinate(0, 1));
            
            Map<String, Coordinate> line =
                new TreeMap<String, Coordinate>();
            line.put("point A", new Coordinate(0, 0));
            line.put("point B", new Coordinate(3, 3));
            
            /***** 实验测试部分  *****/
            System.out.println(rect);  // output rectangle
            System.out.println(line);  // output line
            System.out.println(rect.put("point D", new Coordinate(2, 2)));  // (0, 1)
            System.out.println(rect.get("point C"));  // (1,1) 
            System.out.println(rect.get("point E"));  // null
            rect.putAll(line);
            System.out.println(rect); 
            System.out.println(line.remove("point C"));  // null
            System.out.println(line.remove("point A"));  // (0, 0)    
        }
    
    }
    
    /**
     * 坐标类
     * 
     * @author forget406
     * @since  09/08/2016
     * @param <T> 泛型参数
     */
    class Coordinate<T> {
        private T x;
        private T y;
        
        public Coordinate(T x, T y) {
            this.x = x;
            this.y = y;
        }
    
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((x == null) ? 0 : x.hashCode());
            result = prime * result + ((y == null) ? 0 : y.hashCode());
            return result;
        }
    
        @SuppressWarnings("unchecked")
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Coordinate<T> other = (Coordinate<T>) obj;
            if (x == null) {
                if (other.x != null)
                    return false;
            } else if (!x.equals(other.x))
                return false;
            if (y == null) {
                if (other.y != null)
                    return false;
            } else if (!y.equals(other.y))
                return false;
            return true;
        }
        
        @Override
        public String toString() {
            return "(" + x + "," + y + ")";
        }
    }
    {point C=(1,1), point B=(1,0), point A=(0,0), point D=(0,1)}
    {point A=(0,0), point B=(3,3)}
    (0,1)
    (1,1)
    null
    {point C=(1,1), point B=(3,3), point A=(0,0), point D=(2,2)}
    null
    (0,0)
    程序运行结果 

    映射表元素遍历三种方式(或称 映射表集合视图(*)):

    • key遍历  Set<K> keySet()  返回映射表中所有键的视图。可以从这个Set中删除元素,同时也从映射表中删除了它们。   
    • value遍历  Collection<V> values() 返回映射表中所有值的视图。可以从这个Collection中删除元素,同时也从映射表中删除了它们。
    • Entry(key-value对)遍历  Set<Map.Entry<K, V>> entrySet() 返回Map.Entry对象Set集的视图,即映射表中的键/值对。可以从这个集合中删除元素,同时也从映射表中删除了它们。

    注意:虽然可以从这三种遍历方式遍历获得的视图集合中删除元素,但是均不能够在其中添加元素。

    Set<K> keySet();                   // 元素键视图集
    Collection<V> values();            // 元素值视图集
    Set<Map.Entry<K, V>> entrySet();   // 元素键/值对视图集

    Map接口的内部接口,即Entry<K, V>接口,起封装键值对的作用。通过键值对遍历Map时,可以通过Entry接口提供的方法获取相应的键、值,以及设置键值对的值。

    K getKey()   获得键值对的键

    V getValue()  获得键值对的值

    V setValue(V value)   设置键值对的值,同时映射表的内容也同时更新(*)

    K setKey(K key)   但没有提供设置键值对键的方法,why? 键值对的键是键值对的索引,改变键不具有实际意义,而且改变之后会引起一系列诸如hash值改变等问题。

    /* Entry<K, V>接口,属于内部接口 */
    interface
    Entry<K,V> { K getKey(); // 获得键值对的键 V getValue(); // 获得键值对的值 V setValue(V value); // 设置键值对的值 /* 前面已经论述过 */ boolean equals(Object o); int hashCode(); }
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Set;
    
    public class MapStudy {
        @SuppressWarnings({ "unchecked", "rawtypes" })
        public static void main(String[] args) {
            Map<String, Coordinate> rect = 
                new HashMap<String, Coordinate>();
            rect.put("point A", new Coordinate(0, 0));
            rect.put("point B", new Coordinate(1, 0));
            rect.put("point C", new Coordinate(1, 1));
            rect.put("point D", new Coordinate(0, 1));
            
            /***** 实验测试代码   *****/
            Set<Entry<String, Coordinate>> pos = rect.entrySet();
    /* 最好判断一下是否为null */
    if (pos != null) { for(Entry<String, Coordinate> p : pos) { System.out.println(p.getKey()+ "=" +p.getValue()); // 获得键、值 p.setValue(new Coordinate("?", "?")); // 设置对应Entry的值 } System.out.println(rect); // 更新Entry后,原来映射表内容也随之更新 } } } /** * 坐标类 * * @author forget406 * @since 09/08/2016 * @param <T> 泛型参数 */ class Coordinate<T> { private T x; private T y; public Coordinate(T x, T y) { this.x = x; this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((x == null) ? 0 : x.hashCode()); result = prime * result + ((y == null) ? 0 : y.hashCode()); return result; } @SuppressWarnings("unchecked") @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Coordinate<T> other = (Coordinate<T>) obj; if (x == null) { if (other.x != null) return false; } else if (!x.equals(other.x)) return false; if (y == null) { if (other.y != null) return false; } else if (!y.equals(other.y)) return false; return true; } @Override public String toString() { return "(" + x + "," + y + ")"; } }
    point C=(1,1)
    point B=(1,0)
    point A=(0,0)
    point D=(0,1)
    {point C=(?,?), point B=(?,?), point A=(?,?), point D=(?,?)}
    程序测试结果

    如果读者需要进一步了解Java的内部接口机制,可以阅读另一篇文章:《Java高级特性(二)内部接口》

    Map遍历方式

    Map遍历作为文章体系中极为重要的一块内容,从上面模块中抽出来单独总结。

    Iterator<E>接口实现

    这种实现方式相对来说比较鸡肋,反正遍历Map我很少用到的。

    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Set;
    
    public class MapStudy {
        @SuppressWarnings({ "unchecked", "rawtypes" })
        public static void main(String[] args) {
            Map<String, Coordinate> rect = 
                new HashMap<String, Coordinate>();
            rect.put("point A", new Coordinate(0, 0));
            rect.put("point B", new Coordinate(1, 0));
            rect.put("point C", new Coordinate(1, 1));
            rect.put("point D", new Coordinate(0, 1));
            
            /***** Iterator<E>方式遍历代码   *****/
            // 遍历key,获取key
            Set<String> keys = rect.keySet();
            Iterator<String> it1 = keys.iterator();
            while(it1.hasNext()) {  //
                String key = it1.next();  //
                System.out.println(key);
                // it1.remove();  删
            }
            
            // 遍历value,获取value
            Collection<Coordinate> values = rect.values();
            Iterator<Coordinate> it2 =  values.iterator();
            while(it2.hasNext()) {
                Coordinate value = it2.next();
                System.out.println(value);
            }
                    
            // 遍历key-value对,获取key、value,设置value
            Set<Entry<String, Coordinate>> entries = rect.entrySet();
            Iterator<Entry<String, Coordinate>> it3 = entries.iterator();
            while(it3.hasNext()) {
                Entry<String, Coordinate> entry = it3.next();
                String key = entry.getKey();
                System.out.println(key);
                Coordinate value = entry.getValue();
                System.out.println(value);
                entry.setValue(new Coordinate("?", "?"));
            }
            System.out.println(rect);
        }
    }
    
    /**
     * 坐标类
     * 
     * @author forget406
     * @since  09/08/2016
     * @param <T> 泛型参数
     */
    class Coordinate<T> {
        private T x;
        private T y;
        
        public Coordinate(T x, T y) {
            this.x = x;
            this.y = y;
        }
    
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((x == null) ? 0 : x.hashCode());
            result = prime * result + ((y == null) ? 0 : y.hashCode());
            return result;
        }
    
        @SuppressWarnings("unchecked")
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Coordinate<T> other = (Coordinate<T>) obj;
            if (x == null) {
                if (other.x != null)
                    return false;
            } else if (!x.equals(other.x))
                return false;
            if (y == null) {
                if (other.y != null)
                    return false;
            } else if (!y.equals(other.y))
                return false;
            return true;
        }
        
        @Override
        public String toString() {
            return "(" + x + "," + y + ")";
        }
    }
    point C
    point B
    point A
    point D
    (1,1)
    (1,0)
    (0,0)
    (0,1)
    point C
    (1,1)
    point B
    (1,0)
    point A
    (0,0)
    point D
    (0,1)
    {point C=(?,?), point B=(?,?), point A=(?,?), point D=(?,?)}
    程序运行结果

    for-each实现

    在JDK1.5版本引入for-each(由JVM维护,内部实现也是通过迭代器实现)后,上面那种有点原始社会的感觉。所以一般我都是使用for-each遍历Map

    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Set;
    
    public class MapStudy {
        @SuppressWarnings({ "unchecked", "rawtypes" })
        public static void main(String[] args) {
            Map<String, Coordinate> rect = 
                new HashMap<String, Coordinate>();
            rect.put("point A", new Coordinate(0, 0));
            rect.put("point B", new Coordinate(1, 0));
            rect.put("point C", new Coordinate(1, 1));
            rect.put("point D", new Coordinate(0, 1));
            
            /***** for-each方式遍历代码   *****/
            // 遍历key,获取key
            Set<String> keys = rect.keySet();
            for(String key : keys) {
                System.out.println(key);
            }
            
            // 遍历value,获取value
            Collection<Coordinate> values = rect.values();
            for(Coordinate value : values) {
                System.out.println(value);
            }
                    
            // 遍历key-value对,获取key、value,设置value
            Set<Entry<String, Coordinate>> entries = rect.entrySet();
            for(Entry<String, Coordinate> entry : entries) {
                String key = entry.getKey();
                System.out.println(key);
                Coordinate value = entry.getValue();
                System.out.println(value);
                entry.setValue(new Coordinate("?", "?"));
            }
            System.out.println(rect);
        }
    }
    
    /**
     * 坐标类
     * 
     * @author forget406
     * @since  09/08/2016
     * @param <T> 泛型参数
     */
    class Coordinate<T> {
        private T x;
        private T y;
        
        public Coordinate(T x, T y) {
            this.x = x;
            this.y = y;
        }
    
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((x == null) ? 0 : x.hashCode());
            result = prime * result + ((y == null) ? 0 : y.hashCode());
            return result;
        }
    
        @SuppressWarnings("unchecked")
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Coordinate<T> other = (Coordinate<T>) obj;
            if (x == null) {
                if (other.x != null)
                    return false;
            } else if (!x.equals(other.x))
                return false;
            if (y == null) {
                if (other.y != null)
                    return false;
            } else if (!y.equals(other.y))
                return false;
            return true;
        }
        
        @Override
        public String toString() {
            return "(" + x + "," + y + ")";
        }
    }

    Map体系结构

    Map接口的实现类大致分为两大类:

    通用映射表类  

    HashMap(散列映射表)、TreeMap(二叉树映射表)

    专用映射表类  

    IdentityHashMap(标识散列映射表)、WeakHashMap(弱散列映射表)、LinkedHashMap(链接散列映射表)、EnumMap(枚举映射表)等。

    当然,上面只是比较粗略地分类,后续文章将进一步深入分析Map相关接口、抽象类、实现类的内部机制,以及其于Set集的对应关系。

    Map相关问题思考

    思考1:标识符命名单复数

    这个问题不光光是Map有,整个Java语言在设计时都是采用这种思路。这种写法归根结底还是由于英语国家语言语法习惯,值得我们在平时命名的时候注意。

    boolean containsKey(Object key);

    如果换成我习惯的命名法则,让我来设计底层代码,可能就写成

    boolean containKey(Object key);

    boolean isContainKey(Object key);

    真的是一个大写的囧 o(╯□╰)o  和大牛的差距就体现出来了。

    对于设计方法标识符时大致应该遵循:

    1、如果方法用于判断,即返回值是boolean

    boolean isAb(Ab是名词)  // 判断对象是Ab吗?
    boolean AbsCd(Abs是动词单数,Cd是名词) // 判断对象Abs了Cd?比如上面的ContainsKey

    2、一般的方法,规则是动词开头(具体判断是否加单数),后可以加名词或者连词(Of、As等)

    思考2:Map视图

    视图(views)其实是集合框架(collection frame)所共有的语言特性。

    关于视图的内容,会单独整理总结成文,这里就是作为一块需要重点理解的内容提出来。

    Map涉及到视图的内容:

    Set<K> keySet();                   // 元素键视图集
    Collection<V> values();            // 元素值视图集
    Set<Map.Entry<K, V>> entrySet();   // 元素键/值对视图集
    /** Entry<K, V>接口,属于内部接口 */
    interface Entry<K,V> { K getKey(); // 获得键值对的键 V getValue(); // 获得键值对的值 V setValue(V value); // 设置键值对的值 }  

    思考3:Map遍历需要用Collection(集合)和Set(集)的原因分析

    回头看前面Map接口源代码,你会发现它并没有写成

    public interface Map<K, V> extends Iterable<E>

    而是简单地写成

    public interface Map<K, V>

    也就是说,Map接口实际上是一个顶层接口

    相比较Collection<T>接口继承更顶层的Iterable<T>(拥有iterator()方法,以及for-each使用必须继承的接口),Map接口显得较为特殊。

    为什么Map遍历必须借助Collection或Set呢?

    假设现在Map接口源代码写成

    public interface Map<K, V> extends Iterable<E>

    那么问题来了,该怎么遍历Map呢?现在有key、value两个泛型参数,当然我们可以将这两个泛型参数看成一对Entry,不过如果我需要单独遍历key或者value呢?无法单独通过Map实现。这就会显得功能太单调。

    与其这样,不如一不做二不休,将Map<K, V>接口的遍历查询结果做成三种Set集或Collection集合视图。

    Map<K, V>的K不能够重复,选择合适的数据结构Set储存。

    Map<K, V>的V可以重复,选择合适的数据结构Collection储存。当然我个人认为List储存也是可以的,只不过设计者选择更加稳妥、保守的方式而已。

    思考4:增删查改方法都有泛型返回值V(映射表值)

    这一点其实也没琢磨太清楚,可能是设计者认为这么定义方法更加人性化。比如:

    V remove(Object o) 删除映射表中指定键元素,如果删除则返回删除元素的值对象,反正则返回null。这样我们还能够看看到底删除值对不对。

  • 相关阅读:
    spring 源码解析一(bean定义)
    IOC 容器中添加组件的方式
    spring 后置处理器
    .NetCore3.1 配置Kestrel端口的几种方式及优先级
    CESIUM空间中AB两点A绕B点的地面法向量旋转任意角度后新的A点坐标(A’)
    Cesium坐标转换:根据两个坐标点(坐标点a、坐标点b)的经纬度,计算a点和b点的角度
    任意一个点A(x,y)围绕任意一个点B(a,b)旋转任意角度后的坐标值
    已知地球上的2点坐标,A和B,求A,B线上 任意点位置。
    centos6下mysql5.7.13制作rpm包
    sql 删除狐立帐户
  • 原文地址:https://www.cnblogs.com/forget406/p/5850194.html
Copyright © 2020-2023  润新知