集合框架Map
Map接口
Map接口集合框架的两大根接口之一,另外一个根接口是Collection接口。
Collection接口底层依靠遍历器,至于具体的实现类又根据自身数据结构特征加强迭代器功能自由扩展
而Map(映射) 是一种用于存储 键值对(Key-Value Pair) 的数据结构。它允许通过唯一的键(Key)快速查找、插入或删除对应的值(Value)
内部核心子接口:Map.Entry<K,V>
Map.Entry<K,V>
是 Map 接口的 内部子接口,表示一个键值对条目。它是 Map 中键值对的最小组成单元
,通常通过 entrySet()
方法遍历 Map 时使用
interface Entry<K,V> {
//返回当前条目的键
K getKey();
//返回当前条目的值
V getValue();
//修改当前条目的值(部分实现可能不支持,如不可变 Map)
V setValue(V value);
//比较两个条目是否相等(键和值均相等)
boolean equals(Object o);
//返回条目的哈希值(基于键和值的哈希组合)
int hashCode();
//静态方法,用于生成比较器(Comparator)
//comparingByKey():根据键排序
public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>>
comparingByKey() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getKey().compareTo(c2.getKey());
}
//comparingByValue():根据值排序
public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>>
comparingByValue() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getValue().compareTo(c2.getValue());
}
//comparingByKey(Comparator):自定义 Key键的 比较规则
public static <K, V> Comparator<Map.Entry<K, V>>
comparingByKey(Comparator<? super K> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
}
//comparingByValue(Comparator):自定义 Value值的 比较规则
public static <K, V> Comparator<Map.Entry<K, V>>
comparingByValue(Comparator<? super V> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
}
}
在java中内部子接口的编码方式是不常见的,Map.Entry<K,V>是 Map 接口唯一的直接子接口,它定义了最小键对值单元以及Map的数据结构特性,但在具体实现类中(如 HashMap、TreeMap)会有更复杂的内部结构
HashMap.Node<K,V>
:HashMap
中实现Map.Entry
的内部类,用于哈希表的链表节点。TreeMap.Entry<K,V>
:TreeMap
中实现Map.Entry
的内部类,用于红黑树节点。
同时Entry 并没有提供直接设置键值的方法,没有setKey方法,表明它是用来表示一个不可变的键值对
核心定义方法
1.Map接口未 提供功能实现方法的部分
public interface Map<K,V> {
//添加元素
V put(K key, V value);//添加一个元素
void putAll(Map<? extends K, ? extends V> m);//批量添加元素
//获取
int size();//获取集合大小
V get(Object key);//获取值
Set<K> keySet();//获取所有键
Collection<V> values();//获取所有值
//删除
V remove(Object key);//删除键值对
void clear();//情况map集合
//判断
boolean isEmpty();//判空
boolean containsKey(Object key);//检查键是否存在
boolean containsValue(Object value);//检查值是否存在
//返回的Set集合,其中的元素类型是 Map.Entry<K, V>
Set<Map.Entry<K, V>> entrySet();
//这两个方法主要还得看子类具体实现
boolean equals(Object o);
int hashCode();
简单使用示例代码
Map<String, Integer> map = new HashMap<>();
// 添加键值对
map.put("apple", 10);
map.put("banana", 20);
Map<String, Integer> map2 = new HashMap<>();
map2.put("c", 10);
map2.put("d", 20);
map.putAll(map2);
int apples = map.get("apple"); // // 获取值 10
map2.remove("aaaa");//移除键值对
Set<String> keySet = map2.keySet();//获取所有Key
Collection<Integer> values = map2.values();//获取所有vlaue
keySet.forEach(System.out::println);
values.forEach(System.out::println);
map2.remove("a");
map2.clear();//清空集合Map2
System.out.println(map2.isEmpty());
System.out.println(map.containsKey("a"));//false
System.out.println(map.containsValue(10));//ture
// 遍历所有 Entry
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
int value = entry.getValue();
entry.setValue(value * 2); // 修改值(仅对支持可变集合setValue的 Map 有效)
}
// 使用 comparingByValue 排序
List<Map.Entry<String, Integer>> sortedEntries = new ArrayList<>(map.entrySet());
sortedEntries.sort(Map.Entry.comparingByValue());
在map.entrySet()中获取最小键值单元entrySet(),但直接修改条目 entry.setValue会影响原始 Map
2.Map接口 提供功能实现方法的部分
//用于获取指定key对应的value,如果key不存在,则返回默认值defaultValue
default V getOrDefault(Object key, V defaultValue) {}
//用于对 Map 中的每个元素执行给定的操作,直到异常或运行结束,常用于遍历
default void forEach(BiConsumer<? super K, ? super V> action) {}
//将键映射为指定的旧值时,才用于替换指定键的旧值。如果替换了旧值,则此方法返回 true,否则返回 false
default boolean replace(K key, V oldValue, V newValue) {}
//对 Map 中的所有元素进行操作,并用操作结果替换原值
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {}
//映射判断添加,如果key已经存在 返回对应的V值,如果key不存在 则添加进去并返回null
default V putIfAbsent(K key, V value) {}
//指定键值对移除方法,返回布尔类型代表移除成功与否
default boolean remove(Object key, Object value) {}
//对特定键 的键值对作操作
default V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {}
//对对特定键 对应的值对作操作,但如果参数键不存在,则报空指针异常
default V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {}
//BiFunction会在键存在时用来合并新的值和旧的值
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {}
这些方法直接简单代码更容易学习了解
Map<String, Integer> map = new HashMap<>();
map.put("a", 10);
map.put("b", 20);
//区别于get方法,如果key对应值没有 输出参数值
System.out.println(map.get("c"));// null
System.out.println(map.getOrDefault("d",100));//100
//移除元素 返回对应键对应的值,返回值代表移除成功
System.out.println(map.remove("c"));// 返回 null
//指定的键存在,并且该键对应的值等于指定的值,两个条件都满足则从映射中移除该键值对
System.out.println(map.remove("c",10));//返回false
//仅在先前将键映射为指定的旧值时,才用于替换指定键的旧值,替换成功返回ture,否则false
System.out.println(map.replace("a",10,100));
// 对使用元素执行value*2的动作,如果任何条目产生异常,则该方法会停止执行
BiFunction<String, Integer, Integer> function = (key, value) -> value * 2;
map.replaceAll(function);
// 如果键不存在,则将其添加到映射中,并返回null
System.out.println(map.putIfAbsent("apple", 1)); // 输出:null
// 如果键存在,则不进行任何操作,并返回现有值
System.out.println(map.putIfAbsent("apple", 2)); // 输出:1
System.out.println(map);//{a=200, apple=1, b=40}
// 对已存在的键进行操作
map.computeIfPresent("a", (key, value) -> value + 1);
System.out.println(map); // 输出: {a=201, apple=1, b=40}
// 对不存在的键不进行操作
map.computeIfPresent("c", (key, value) -> value + 1);
System.out.println(map); // 输出: {a=201, apple=1, b=40}
// 使用compute方法将所有值乘以2,但如果键不存在报空指针异常,使用前需先判断键存在
map.compute("a", (key, value) -> value * 2);
System.out.println(map);//{a=402, apple=1, b=40}
map.compute("c", (key, value) -> value * 2);//NullPointerException
//forEach对 Map 中的每个元素执行给定的操作,这里操作是打印
map.forEach((k,v)->{
System.out.println(k);// null
System.out.println(v);// null
});
merge单独拿出来示例:允许插入新的键值对或者更新已有的键的值,这个方法接受三个参数:键、值和一个BiFunction,这个参数BiFunction会在键存在时用来合并新的值和旧的值(就是新值+旧值 存入)
Map<String, Integer> map = new HashMap<>();
map.put("a", 10);
map.put("b", 20);
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
List<String> immutableList = Collections.unmodifiableList(list);//转换成不可变集合
//模拟合并新值和旧值 存入map过程,如果元素c不存在
immutableList.forEach(k-> {
Integer oldValue= map.get(k);
map.put(k, oldValue+ 1);
});
System.out.println(map);//{a=11, b=21}
list.add("c");//比起map的键所有值多个元素c
// 使用merge()方法重构上述代码
immutableList.forEach(k -> map.merge(k, 1, (oldValue,newValue) ->
oldValue + newValue));//newValue代表第二个参数1,oldValue代表k对应的value值
System.out.println(map);//{a=11, b=21, c=1}
这个 方法作用就是 对所有键对应的值 和 新的值合并处理,比起如果原不存在的键值对,则合并后的值就是参数value值并存入map中
AbstractMap抽象类
抽象类一般用于模板方法中,提供抽象方法 和 通用实现功能,该抽象类的还提供了两个内部类,这种设计模式表明内部类提供基础的构造和方法结构,我们先从内部类,再到核心方法步骤查看解读该类
public abstract class AbstractMap<K,V> implements Map<K,V> {
//构造方法:虽然抽象类不能被实例化,但是它的子类可以通过调用父类的构造方法来完成对父类成员变量的初始化
protected AbstractMap() {}
//抽象方法
public abstract Set<Entry<K,V>> entrySet();
//通用方法实现
...........................................
//两个内部类
public static class SimpleEntry<K,V>;
public static class SimpleImmutableEntry<K,V>
}
内部类SimpleEntry<K,V>
SimpleEntry<K,V>:可修改的键值对条目
public static class SimpleEntry<K,V>
implements Entry<K,V>, java.io.Serializable
{
private static final long serialVersionUID = -8499721149061103585L;
private final K key;
private V value;
public SimpleEntry(K key, V value) {
this.key = key;
this.value = value;
}
public SimpleEntry(Entry<? extends K, ? extends V> entry) {
this.key = entry.getKey();
this.value = entry.getValue();
}
1.首先这是static修饰的内部类即静态内部类,外层类是抽象类不可以实例化但内部类是静态,可以不依靠外层类的实例化直接自主实例化,同时作为内部类为抽象类方法提供基础能力
AbstractMap.SimpleEntry<String,String> simpleEntry = new AbstractMap.SimpleEntry<>("1","2");
2.实现了Map接口的子接口Entry<K,V>,表明拥有Map集合的最小逻辑单元处理能力
3.实现了序列化接口,表明了序列化和反序列化的能力,是实现克隆拷贝的能力方式之一
4.提供了两个构造参数,上面的代码展示参数为<K,V>,构造器,下面展示参数为<>(Entry< K, V> entry)的
Map.Entry<String,String> entry = new Map.Entry<String, String>() {
.......................................................
};
AbstractMap.SimpleEntry<String,String> simpleEntry2 =
new AbstractMap.SimpleEntry<>(entry);
核心方法,既然实现了implements Entry<K,V>子接口,拥有该接口的能力
public V getValue() {return value;}
public K getKey() {return key;}
//更新value值方法
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
// 比较方法重写实现
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
return eq(key, e.getKey()) && eq(value, e.getValue());
}
//hashCode方法重写实现
public int hashCode() {
return (key == null ? 0 : key.hashCode()) ^
(value == null ? 0 : value.hashCode());
}
这个内部类并没有特别扩展什么方法,主要的目标在实现子接口implements Entry<K,V>并对其方法进行了重写实现,用于简化条目(Entry)的创建
内部类SimpleImmutableEntry<K,V>
SimpleImmutableEntry<K,V>:不可修改的键值对条目(调用 setValue() 会抛出异常
public static class SimpleImmutableEntry<K,V>
implements Entry<K,V>, java.io.Serializable
{
private static final long serialVersionUID = 7138329143949025153L;
private final K key;
private final V value;
public SimpleImmutableEntry(K key, V value) {
this.key = key;
this.value = value;
}
public SimpleImmutableEntry(Entry<? extends K, ? extends V> entry) {
this.key = entry.getKey();
this.value = entry.getValue();
}
// 如果进行更改value值 抛出UnsupportedOperationException异常
public V setValue(V value) {throw new UnsupportedOperationException();}
// 其余方法内容和SimpleEntry类相似 不展示了
SimpleImmutableEntry和SimpleEntry类结构相似,并且都实现了子接口implements Entry<K,V>,这表明了这两个内部类都拥有Map集合中最小逻辑单元的处理能力,用于简化条目(Entry)的创建,区别在于更改集合内容setValue方法,SimpleImmutableEntry回抛出不可操作异常,该异常是对不可变集合的支持
- SimpleEntry<K,V>:可修改的键值对条目。
- SimpleImmutableEntry<K,V>:不可修改的键值对条目(调用 setValue() 会抛出异常)
抽象方法:entrySet()
public abstract Set<Entry<K,V>> entrySet();
方法作用说明:
1.子类必须实现此方法,返回一个 Set<Map.Entry<K,V>>,表示所有键值对的集合
2.AbstractMap 的其他方法(如 get()、containsKey())基于 entrySet() 的遍历实现
核心目标解析:
1.该方法源自从Map接口中Set<Map.Entry<K, V>> entrySet()方法,抽象类AbstractMap并没有对其进行扩展实现,而且作为抽象方法由子类必须实现,目的在于:由子类根据自身数据结构特征扩展
2.为什么返回类型是Set视图集合
,需要解释下在框架中视图(View)集合
的概念,比如这个方法,改变底层Map数据结构,对应的返回的Set集合内容随之变化,表明该Set视图集合并不存储实际数据,只是提供了一种访问和操作底层数据的方式,这种就是视图集合
,而为什么要用Set作为返回类型,是因为Set集合元素的唯一性,能去重保证每个键值对单元都是唯一
Entry<String, Integer> entry = new Entry<String, Integer>() {...};
entry.setValue(10);
System.out.println(entry == null);//false
Set<Entry<String,Integer>> set = new HashSet<>();
set.add(entry);//添加2次
set.add(entry);
System.out.println(set.size());//1
保证不会有重复的元素,Set和Map这两个集合类型底层实现相互引用,这是集合框架里面比较绕的地方
通用方法实现
1.get(key):遍历 entrySet() 查找匹配的键
public V get(Object key) {
//entrySet()上面抽象方法Set集合,get方法调用Set接口中返回迭代器 Iterator<E> iterator()遍历;
Iterator<Entry<K,V>> i = entrySet().iterator();// Iterator<E> iterator();
if (key==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getKey()==null)
return e.getValue();
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
return e.getValue();
}
}
return null;//默认返回
}
2.containsKey(key):遍历 entrySet() 检查是否存在键
和上面个体方法实现类似,也是遍历判断查找
public boolean containsKey(Object key) {
Iterator<Map.Entry<K,V>> i = entrySet().iterator();
if (key==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getKey()==null)
return true;
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
return true;
}
}
return false;
}
3.containsValue(value):遍历 entrySet() 检查是否存在值
public boolean containsValue(Object value) {
Iterator<Entry<K,V>> i = entrySet().iterator();
if (value==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getValue()==null)
return true;
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (value.equals(e.getValue()))
return true;
}
}
return false;
}
4.size():返回 entrySet().size()即Set集合大小
public int size() {
return entrySet().size();// 即返回的Set.size();
}
5.isEmpty():判断 size() == 0
public boolean isEmpty() {
return size() == 0;//即判断Set集合元素是否为0,是则返回false
}
6.put(key, value):默认抛出 UnsupportedOperationException(需子类重写)
public V put(K key, V value) {
//单独对该抽象类来讲,也是默认支持不可变集合的
throw new UnsupportedOperationException();
}
7.remove(key):基于 entrySet() 的迭代器删除匹配条目(迭代遍历匹配删除)
public V remove(Object key) {
Iterator<Entry<K,V>> i = entrySet().iterator();
Entry<K,V> correctEntry = null;
if (key==null) {
while (correctEntry==null && i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getKey()==null)
correctEntry = e;
}
} else {
while (correctEntry==null && i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
correctEntry = e;
}
}
V oldValue = null;
if (correctEntry !=null) {
oldValue = correctEntry.getValue();
i.remove();// 核心删除方法在这,本质是迭代删除iterator.remove()
}
return oldValue;
}
8.clear():调用 entrySet().clear() 清空Map集合
public void clear() {
entrySet().clear();//调用set.clear()方法,清空集合元素
}