自顶向下理解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
接口及其子接口List
、Set
,Map
接口)内部维护一个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。这样我们还能够看看到底删除值对不对。
自顶向下理解Java集合框架(三)Map接口的更多相关文章
- Java集合框架之Map接口浅析
Java集合框架之Map接口浅析 一.Map接口综述: 1.1java.util.Map<k, v>简介 位于java.util包下的Map接口,是Java集合框架的重要成员,它是和Col ...
- Java集合框架中Map接口的使用
在我们常用的Java集合框架接口中,除了前面说过的Collection接口以及他的根接口List接口和Set接口的使用,Map接口也是一个经常使用的接口,和Collection接口不同,Map接口并不 ...
- 【深入理解Java集合框架】红黑树讲解(上)
来源:史上最清晰的红黑树讲解(上) - CarpenterLee 作者:CarpenterLee(转载已获得作者许可,如需转载请与原作者联系) 文中所有图片点击之后均可查看大图! 史上最清晰的红黑树讲 ...
- 第19章 集合框架(3)-Map接口
第19章 集合框架(3)-Map接口 1.Map接口概述 Map是一种映射关系,那么什么是映射关系呢? 映射的数学解释 设A,B是两个非空集合,如果存在一个法则,使得对A中的每一个元素a,按法则f,在 ...
- Java集合框架之四大接口、常用实现类
Java集合框架 <Java集合框架的四大接口> Collection:存储无序的.不唯一的数据:其下有List和Set两大接口. List:存储有序的.不唯一的数据: Set:存储无序的 ...
- Java集合框架之Set接口浅析
Java集合框架之Set接口浅析 一.java.util.Set接口综述: 这里只对Set接口做一简单综述,其具体实现类的分析,朋友们可关注我后续的博文 1.1Set接口简介 java.util.se ...
- Java集合框架之List接口浅析
Java集合框架之List接口浅析 一.List综述: 毫无疑问List接口位于java.util包下,继承自 Collection接口 存储元素的特点: 有序可重复(有序:即存进去是什么顺序,取出来 ...
- Java集合框架之Collection接口
Java是一门面向对象的语言,那么我们写程序的时候最经常操作的便是对象了,为此,Java提供了一些专门用来处理对象的类库,这些类库的集合我们称之为集合框架.Java集合工具包位于Java.util包下 ...
- Java集合框架之map
Java集合框架之map. Map的主要实现类有HashMap,LinkedHashMap,TreeMap,等等.具体可参阅API文档. 其中HashMap是无序排序. LinkedHashMap是自 ...
随机推荐
- Nginx01---简单使用
基于腾讯云--ubuntu系统 1.安装nginx sudo apt-get install nginx 2.启动,停止nginx nginx -c /usr/local/nginx/conf/ngi ...
- 2019-5-1 maven学习笔记
一.maven的好处 同样的项目使用maven工程来实现,由于不需要导入很多jar包,源码很小 原理:根据坐标到项目的仓库里查找需要的依赖 二.安装步骤 1.到http://maven.apache. ...
- CentOS7l联网
原文:https://blog.csdn.net/nothing2017/article/details/61420767 步骤: 1.以root管理员身份登录系统,输入 --->(ls / ...
- docker容器时间不对及java程序时间不对解决
使用docker容器部署的应用,会出现时间与主机不一致的情况 1. 容器时间与主机差8个小时:主机的与容器的/etc/localtime不一致 解决方法:挂载主机的/etc/localtime,如果没 ...
- Qt 学习之路 2(14):对话框数据传递
Home / Qt 学习之路 2 / Qt 学习之路 2(14):对话框数据传递 Qt 学习之路 2(14):对话框数据传递 豆子 2012年9月15日 Qt 学习之路 2 53条评论 对话框 ...
- Android模拟器手动设置经纬度坐标
第一种方式可以在eclipse的DDMS中的Emulator control中设置,如下图 另一种是在cmd中输入telnet localhost 5554(注:5554是模拟器在本机的端口,有可能不 ...
- Redis学习笔记(5)—— Redis的持久化方案&Redis的集群搭建
一.Redis的持久化方案 Redis的高性能是由于其将所有数据都存储在了内存中,为了使Redis在重启之后仍能保证数据不丢失,需要将数据从内存中同步到硬盘中,这一过程就是持久化. Redis支持两种 ...
- 富文本编辑器...quill 的使用放...
移动端 quill 时候用的 是 div 而不是 textarea.... 引入 dom <link href="//cdn.quilljs.com/1.3.6/quill.snow. ...
- angularJs(2)表单中下拉框单选多选
多选 <input type="checkbox" ng-model='game' ng-true-value="1" ng-false-value=&q ...
- windows 7下安装MySQL5.6
一. 软件下载 从MySql官网上下载响应的版本,我的是5.6.17. 二.安装过程 以管理员权限运行安装程序,收集信息. 选择安装MySql产品,如果之前有安装过,那么就选择更新了. 同意Licen ...