参考的文章:
https://blog.csdn.net/wangwei_620/article/details/82049502
https://blog.csdn.net/Airsaid/article/details/51066637
https://blog.csdn.net/shf4715/article/details/47052385
https://blog.csdn.net/zhangqunshuai/article/details/80660974
https://blog.csdn.net/zhangqunshuai/article/details/80660974
一.集合框架总系架构图分析
颜色含义:
黄色:接口
绿色:抽象接口
蓝色:实现类
两个集合接口:Collection接口和Map接口,Collection集合是单列集合,Map集合是双列集合。
二、Collection集合接口
1.集合与数组区别
1)长度:
数组长度固定,想增加长度,需要创建新数组。
集合长度可变,理论上可以无限添加,自动扩容。
2)元素类型
数组:必须同一类型,String[] arr = ["aaa","bbb"];
集合:可不同类型,ArrayList<Object> arr = new ArrayList<Object>();
2.集合框架出现
jdk1.1只有一种集合,Vector<XX>
jdk1.5出现集合框架
3.官方解释
集合层次结构中的根界面。 集合表示一组被称为其元素的对象。 一些集合允许重复元素,而其他集合不允许。 有些被命令和其他无序。 JDK不提供此接口的任何直接实现:它提供了更具体的子接口的实现,如Set和List 。 该界面通常用于传递集合,并在需要最大的通用性的情况下对其进行操作。
包或多重集 (可能包含重复元素的无序集合)应直接实现此接口。
4.Collection接口实现以及子实现类、功能以及特点
集合顶层接口:Collection<E>
下面三个接口:
List,Set,Queue
插个楼,学习一下Queue(这个一直一知半解)
Queue(队列):LinkedList实现了Queue接口,因此LinkedList进行插入和删除操作效率较高
常用方法:
boolean offer(E e):将元素追加到队列末尾,若添加成功则返回true.
E poll():从队首删除并返回该元素。
E peek():返回队首元素,但是不删除。
双向队列(Deque),是Queue的一个子接口,双向队列是指该队列两端的元素既能入队(offer)也能出队(poll),如果将Deque限制为只能从一端入队和出队,则可实现栈的数据结构。对于栈而言,有入栈(push)和出栈(pop),遵循先进后出原则
常用方法如下:
void push(E e):将给定元素”压入”栈中。存入的元素会在栈首。即:栈的第一个元素
E pop():将栈首元素删除并返回。
List:有序,有下标,可重复
Set:无序,LinkHashSet除外,无下标,不可重复
List:主要实现类:ArrayList,LinkedList,Vector
ArrayList底层实现:数组
LinkedList底层实现:链表
Vector(ArrayList最大区别:Vector线程安全,但性能比ArrayList低):数组
ps:为什么Vector是线程安全的?因为每个方法都添加了Synchronized的关键字保证同步,但是这些方法的同步让其效率大大降低,比ArrayList要慢。
Set的实现类是:Hash Set LinkedHashSet TreeSet
Hash Set底层是通过哈希表实现的
LinkedHashSet是通过链表+哈希表实现的,它也是一种链式哈希级
TreeSet底层是通过树结构实现的
5.Collection的一些通用方法
增:public boolean add(E e)
删:public void remove(E e)
改:无
查:无
其他方法:
public void clear();//清空集合中的元素
public int size();获取集合的长度(元素的个数);
public boolean contains(E e);//判断当前集合中是否包含指定对象
public boolean isEmpty();//判断集合是否为元素
public Object[] toArray();//将集合元素转换为数组
containsAll(Collection c)://是否包含集合c中的所有元素
iterator()://返回Iterator对象,用于遍历集合中的元素
remove(Object o)://移除元素
removeAll(Collection c)://相当于减集合
6.迭代器实现原理
Collection接口扩展了Iterator接口,所有Collection接口的实现类,都可以通过迭代器去遍历集合中的元素。增强for循环也可以,因为增强for循环的底层也是通过迭代器的原理实现的。
代码实现:
Iterator<集合泛型> it =newIterator();
while(it.hasNext()){
集合泛型 next = it.next();
System.out.println(next);
通过hasNext()指针判断集合中是否有写一个元素,如果有通过next()访问集合元素,然后输出。注意如果访问的是ArrayList集合则是按照一定的顺序输出的,访问set集合则是通过按照访问的随机顺。
主要这个非常重要:在使用迭代器的时候,一定不能改变集合的长度(增加或者删除),每次迭代器都会判断底层的记忆长度是否与实际长度相等,如果不相等,则会出现一个并发修改异常,ConcarrentModifiCationException,可以在集合中修改元素,但不能改变长度。
如何比较两个元素是否相等?
1.通过hash码比较,如果hash码值相等
2.通过equals方法比较,如果元素内容相等,则是同一个元素,反之不是同一个元素。
小结:
List,Set,Map都是接口,前两个继承自collection接口,map为独立接口
Set下有HashSet,LinkedHashSet,TreeSet
List下有ArrayList,Vector,LinkedList
Map下有Hashtable,LinkedHashMap,HashMap,TreeMap
collection接口下还有个Queue接口,有PriorityQueue类
注意:Queue接口与List、Set同一级别,都是继承了collection接口。
看图你会发现,LinkedList既可以实现Queue接口,也可以实现List接口.只不过呢, LinkedList实现了Queue接口。Queue接口窄化了对LinkedList的方法的访问权限(即在方法中的参数类型如果是Queue时,就完全只能访问Queue接口所定义的方法 了,而不能直接访问 LinkedList的非Queue的方法),以使得只有恰当的方法才可以使用。
SortedSet是个接口,它里面的(只有TreeSet这一个实现可用)中的元素一定是有序的。
总结:
collection接口(首字母小写)
一.List有序,可重复
ArrayList
优点:底层数据结构是数组,查询快,增删慢
缺点:线程不安全,效率高
Vector
优点:底层数据结构是数组,查询快,增删慢
缺点:线程安全,效率低
LinkedList
优点:底层数据结构是链表,查询慢,增删快
缺点:线程不安全,效率高
二、Set 无序 唯一
HashSet底层数据结构是哈希表(无序,唯一)
如何保证元素唯一性:
1.依赖两个方法:hashCode()和equals()
LinkedHashSet
底层数据结构是链表和哈希表。(FIFO插入有序,唯一)
1.由链表保证元素有序
2.由哈希表保证元素唯一
TreeSet
底层数据结构是红黑树。(唯一,有序)
1.如何保证元素排序?
自然排序
比较器排序
2.如何保证元素唯一?
根据比较的返回值是否是0来判断。
collection集合到底使用谁呢?
唯一吗?
是,Set
排序吗?
是:TreeSet或LinkedList
否:HashSet
如果你知道是Set,但是不知道是哪个Set,就用HashSet
否,List
要安全吗?
是,Vector
否,ArrayList或者LinkedList
查询多:ArrayList
增删多:LinkedList
如果你知道是List,但是不知道是哪个List,就用ArrayList
如果你知道是collection集合,但是不知道使用谁,就用ArrayList。
如果你知道使用集合,就用ArrayList
关于Map接口
有三个较重要的实现类:HashMap,TreeMap,HashTable
TreeMap是有序的,HashMap和HashTable是无序的
HashTable方法时同步的,HashMap是不同步的(这是两者最主要的区别)
这就意味着:
HashTable是线程安全的,HashMap不是线程安全的。
HashTable效率较低,HashMap效率较高。
如果对同步性或与遗留代码的兼容性没有任何要求,建议使用HashMap。 查看Hashtable的源代码就可以发现,除构造函数外,Hashtable的所有 public 方法声明中都有 synchronized关键字,而HashMap的源码中则没有。
HashTable不允许null值,HashMap允许null值(key和value都允许)
父类不同,HashTable父类是Dictionary,HashMap的父类是AbstractMap
重点问题重点分析
(一)TreeSet,LinkedHashSet和HashSet的区别
1.TreeSet,LinkedHashSet和HashSet在Java中都是实现Set的数据结构
TreeSet主要功能用于排序
LinkedHashSet主要功能用于保证FIFO即有序的集合(先进先出)
HashSet只是通用的存储数据的集合
2.相同点
Duplicates elements: 因为三者都实现Set interface,所以三者都不包含duplicate elements
Thread safety: 三者都不是线程安全的,如果要使用线程安全可以collections.synchronizedSet()
3.不同点
Performance and Speed: HashSet插入数据最快,其次LinkHashSet,最慢的是TreeSet因为内部实现排序
Ordering: HashSet不保证有序,LinkHashSet保证FIFO即按插入顺序排序,TreeSet安装内部实现排序,也可以自定义排序规则
null:HashSet和LinkHashSet允许存在null数据,但是TreeSet中插入null数据时会报NullPointerException
(二)、TreeSet的两种排序方式比较
1.排序的引入(以基本数据类型的排序为例)
由于TreeSet可以实现对元素按照某种规则进行排序
public class MyClass {
public static void main(String[] args) {
// 创建集合对象
// 自然顺序进行排序
TreeSet<Integer> ts = new TreeSet<Integer>();
// 创建元素并添加
// 20,18,23,22,17,24,19,18,24
ts.add(20);
ts.add(18);
ts.add(23);
ts.add(22);
ts.add(17);
ts.add(24);
ts.add(19);
ts.add(18);
ts.add(24);
// 遍历
for (Integer i : ts) {
System.out.println(i);
}
}
}
2.如果是引用数据类型呢,比如自定义对象,又该如何排序呢?
测试类
public class MyClass {
public static void main(String[] args) {
TreeSet<Student> ts=new TreeSet<Student>();
//创建元素对象
Student s1=new Student("zhangsan",20);
Student s2=new Student("lis",22);
Student s3=new Student("wangwu",24);
Student s4=new Student("chenliu",26);
Student s5=new Student("zhangsan",22);
Student s6=new Student("qianqi",24);
//将元素对象添加到集合对象中
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
//遍历
for(Student s:ts){
System.out.println(s.getName()+"-----------"+s.getAge());
}
}
}
Student.java:
public class Student {
private String name;
private int age;
public Student() {
super();
// TODO Auto-generated constructor stub
}
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
结果报错:
原因分析:
由于不知道该安照那一中排序方式排序,所以会报错。
解决方法:
1.自然排序
2.比较器排序
---------------------
(1)自然排序
操作:1)Student类中实现Comparable接口 2)重写Comparable接口中的CompareTo方法
compareTo(T o) 比较此对象与指定对象的顺序。
public class Student implements Comparable<Student>{
private String name;
private int age;
public Student() {
super();
// TODO Auto-generated constructor stub
}
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Student s) {
//return -1; //-1表示放在红黑树的左边,即逆序输出
//return 1; //1表示放在红黑树的右边,即顺序输出
//return o; //表示元素相同,仅存放第一个元素
//主要条件 姓名的长度,如果姓名长度小的就放在左子树,否则放在右子树
int num=this.name.length()-s.name.length();
//姓名的长度相同,不代表内容相同,如果按字典顺序此 String 对象位于参数字符串之前,则比较结果为一个负整数。
//如果按字典顺序此 String 对象位于参数字符串之后,则比较结果为一个正整数。
//如果这两个字符串相等,则结果为 0
int num1=num==0?this.name.compareTo(s.name):num;
//姓名的长度和内容相同,不代表年龄相同,所以还要判断年龄
int num2=num1==0?this.age-s.age:num1;
return num2;
}
}
(2)比较器排序
步骤:
1.单独创建一个比较类,以MyComapator为例,并且让其继承Comparator接口。
2.重写Comparator接口中的
Compare方法 (compare(T o1,T o2) 比较用来排序的两个参数。)
3.在主类中使用下面的 构造方法
TreeSet(Comparator<? superE> comparator) 构造一个新的空 TreeSet,它根据指定比较器进行排序。
public class MyClass {
public static void main(String[] args) {
//创建集合对象
//TreeSet(Comparator<? super E> comparator) 构造一个新的空 TreeSet,它根据指定比较器进行排序。
TreeSet<Student> ts=new TreeSet<Student>(new MyComparator());
//创建元素对象
Student s1=new Student("zhangsan",20);
Student s2=new Student("lis",22);
Student s3=new Student("wangwu",24);
Student s4=new Student("chenliu",26);
Student s5=new Student("zhangsan",22);
Student s6=new Student("qianqi",24);
//将元素对象添加到集合对象中
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
//遍历
for(Student s:ts){
System.out.println(s.getName()+"-----------"+s.getAge());
}
}
}
Student.java:
public class Student {
private String name;
private int age;
public Student() {
super();
// TODO Auto-generated constructor stub
}
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
MyComparator类:
public class MyComparator implements Comparator<Student> {
@Override
public int compare(Student s1,Student s2) {
// 姓名长度
int num = s1.getName().length() - s2.getName().length();
// 姓名内容
int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
// 年龄
int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;
return num3;
}
}
四、Map接口集合
Map 表示的是一个键(key)值(Value)对的映射,其中键是唯一的,每个键可以映射最多一个值。该接口取代了 Dictionary 抽象类。
需要注意的是,如果我们把可变对象作为 Map 的键,则必须要非常小心了,因为有可能会导致对象更改后查找不到对应的 value 了。这是因为,像 Map 的实现类,如 HashMap,是以 key 的哈希值来存储和查找键值对的(在后面的文章中会进行深深入分析),而一个可变对象在创建后其哈希值是可能被改变的。为了解决这种问题,我们最好是用 String、Integer 等不可变对象来作为 key,或者我们重写类的 hasCode() 、equals() 方法,用类的成员变量来进行计算。例如:
public class Person {
private String id;
public Person(String id) {
this.id = id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (id != null ? !id.equals(person.id) : person.id != null) return false;
return true;
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
}
---------------------
另外,我们虽然可以将 Map 作为 Map 的值,但是应避免将 Map 作为 Map 的键,因为 Map 比较复杂,比较难定义 hashCode() 与 equals() 方法。
Map 所有通用的实现类应该提供两个“标准”的构造函数:
一个无参数无返回值的空构造函数。
一个具有 Map 类型单个参数的构造函数,它创建一个具有参数相同键值的新 Map。
虽然没有强制要求这样做,但是我们要是自定义 Map 实现类时最好都按照这样来。
---------------------
结构:
源码分析:
查询操作
/**
* 返回此 map 中键值映射的数量。如果 map 包含多于 Integer.MAX_VALUE 的元素,返回 Integer.MAX_VALUE。
*/
int size();
/**
* 如果此 map 中不包含键值映射,返回 true,否则返回 false。
*/
boolean isEmpty();
/**
* 如果此 map 的映射中包含指定的映射键,则返回 true。
*/
boolean containsKey(Object key);
/**
* 如果此 map 中将一个或多个键映射到指定的 value,则返回 true。
*/
boolean containsValue(Object value);
/**
* 返回指定键映射的值,如果此映射中不包含该键的映射,则返回 null。
*/
V get(Object key);
修改操作
/**
* 将指定的值于此 map 中指定的键相关联。
* 如果 map 中已经存在该键,那么将会替换值。
*/
V put(K key, V value);
/**
* 如果此 map 中存在指定 key 的映射,那么删除这个映射,并返回对于的值。
* 如果不存在指定 key 的映射,那么返回 null。(如果此 map 允许 null 值
* ,那么返回 null 并不一定表示该 map 不存在指定 key 的映射)
*/
V remove(Object key);
批量操作
/**
* 将指定 map 中的所有映射复制到此 map。
* 如果在操作过程中修改了指定的 map,则此操作的行为是未定义的。
*/
void putAll(Map<? extends K, ? extends V> m);
/**
* 从此 map 中删除所有的映射。
*/
void clear();
查看
/**
* 返回此 map 中包含所有键的 Set 集合。
* 该集合由 map 支持,因此对 map 的更改将反映在集合中,反之亦然。
* 如果在集合的迭代过程中修改了 map(除了用迭代器自己的删除操作之外),迭代的结果是未定义的。
* 该集合支持删除元素,可以通过 Iteration.remove,Set.remove,removeAll,retainAll
* 和 clear 操作从 map 中删除相应的映射。它不支持 add 或 addAll 操作。
*/
Set<K> keySet();
/**
* 返回此 map 中包含所有值的 Collection 集合。
* 该集合由 map 支持,因此对 map 的更改将反映在集合中,反之亦然。
* 如果在集合的迭代过程中修改了 map(除了用迭代器自己的删除操作之外),迭代的结果是未定义的。
* 该集合支持删除元素,可以通过 Iteration.remove,Collection.remove,removeAll,retainAll
* 和 clear 操作从 map 中删除相应的映射。它不支持 add 或 addAll 操作。
*/
Collection<V> values();
/**
* 返回此 map 中包含所有映射(Map.Entry)的 Set 集合。
* 该集合由 map 支持,因此对 map 的更改将反映在集合中,反之亦然。
* 如果在集合的迭代过程中修改了 map(除了用迭代器自己的删除操作,或
* 通过迭代器返回的映射中的 setValue 操作之外),迭代的结果是未定义的。
* 该集合支持删除元素,可以通过 Iteration.remove,Set.remove,removeAll,retainAll
* 和 clear 操作从 map 中删除相应的映射。它不支持 add 或 addAll 操作。
*/
Set<Map.Entry<K, V>> entrySet();
Entry 是 Map 接口当中的一个内部接口,表示的是一个键值对的映射。其源码如下:
/**
* map 条目(键值对)。通过 Map.entrySet() 方法可以获取在 Set 集合中其所有的映射条目。
* 因为是 Set 集合,所以该映射条目也是唯一的。要想获取映射条目的引用唯一的方法就是通过集合的迭代器。
* 这些 Map.Entry 对象仅在迭代期间有效。如果在迭代器返回条目之后修改了底层映射,
* 除了通过映射条目上的 setValue 操作,则映射条目的行为是不确定的。
*/
interface Entry<K,V> {
/**
* 返回与此条目想对应的键。
*/
K getKey();
/**
* 返回与此条目相对应的值。
* 如果这个映射已经从底层映射中删除(通过迭代器的 remove 操作),则此调用结果未定义。
*/
V getValue();
/**
* 用指定的值替换此条目相对应的值。(写入 map)
* 如果这个映射已经从底层映射中删除(通过迭代器的 remove 操作),则此调用结果未定义。
*/
V setValue(V value);
/**
* 将指定的对象与此条目进行比较以获得相等性。
* 如果给定的对象也是映射条目,并且两个条目表示相同的映射,则返回 true。
*/
boolean equals(Object o);
/**
* 返回此映射条目的哈希码。
*/
int hashCode();
// 下面的这几个都是从 Java8 开始提供的静态方法。关于什么是静态方法,可以看看下面提到的文章。
/**
* 返回一个以 key 的自然顺序进行比较的比较器。
* 返回的比较器是可序列化的,并在与为 null 的 key 比较时返回 NullPointerExcetion。
* 该方法是从 Java8 开始提供的。
*/
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());
}
/**
* 返回一个以 value 的自然顺序进行比较的比较器。
* 返回的比较器是可序列化的,并在与为 null 的 value 比较时返回 NullPointerExcetion。
* 该方法是从 Java8 开始提供的。
*/
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());
}
/**
* 返回一个使用指定的比较器比较 Map.Entry key 的比较器。
* 如果指定的比较器是可序列化的,那么返回的比较器也可序列化。
* 该方法是从 Java8 开始提供的。
*/
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());
}
/**
* 返回一个使用指定的比较器比较 Map.Entry value 的比较器。
* 如果指定的比较器是可序列化的,那么返回的比较器也可序列化。
* 该方法是从 Java8 开始提供的。
*/
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());
}
}
比较和散列
/**
* 将指定的对象与此 map 进行比较以获得相等性。
* 如果给定的对象也是一个 map,并且两个 map 代表相同的映射,则返回 true。
*/
boolean equals(Object o);
/**
* 返回此 map 的哈希码。map 的哈希码定义是 map 的 entrySet() 集合中每个条目的哈希码的总和。
*/
int hashCode();
Java 8 之接口中的默认方法与静态方法:
https://blog.csdn.net/airsaid/article/details/51017534
由于 Map 是接口,不可实例化,于是我们用 HashMap 为实例,演示下以下默认方法的使用。
首先,存储几个数据,后面的例子以这些数据为例来演示。
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "a");
map.put(2, "b");
map.put(3, "c");
getOrDefault
/**
* 如果指定的 key 存在,则返回指定 key 对应的 value。
* 如果不存在则返回指定的默认 value 值。
*/
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
例:
System.out.println(map.getOrDefault(1, "d"));// a
System.out.println(map.getOrDefault(4, "d"));// d
forEach
/**
* 遍历 Map 中所有的 Entry,对 key、value 进行处理。
*
* 这个方法的默认实现相当于:
* for (Map.Entry<K, V> entry : map.entrySet())
* action.accept(entry.getKey(), entry.getValue());
* }
*
* 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
*/
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
例:
map.forEach((key, value) -> System.out.print(key + value));// 1a2b3c
replaceAll
/**
* 将 map 中每个 Entry 的 value 替换为给定的 value。
*
* 这个方法的默认实现相当于:
* for (Map.Entry<K, V> entry : map.entrySet())
* entry.setValue(function.apply(entry.getKey(), entry.getValue()));
* }
*
* 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
*/
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
// ise thrown from function is not a cme.
v = function.apply(k, v);
try {
entry.setValue(v);
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
}
}
例:
map.replaceAll((key, value) -> "d" );
map.forEach((key, value) -> System.out.println(key + value));// 1d 2d 3d
putIfAbsent
/**
* 如果指定的 key 尚未与指定的 value 相关联(或映射到 null),
* 则将其指定的 value 相关联并返回 null,否则返回当前 value。
*
* 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
*/
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
例:
System.out.println(map.putIfAbsent(4, "d"));// null
map.forEach((key, value) -> System.out.println(key + value));// 1a 2b 3c 4d
remove
/**
* 当指定的 key 映射到指定的 value 时,删除该 Entry。
*
* 该方法的默认实现相当于:
* if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
* map.remove(key);
* return true;
* } else
* return false;
* }
*
* 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
*/
default boolean remove(Object key, Object value) {
Object curValue = get(key);// 获取指定 key 的映射 value
if (!Objects.equals(curValue, value) ||// 比对指定的 value 与映射 value 是否是同一个,不是同一个直接返回 false
(curValue == null && !containsKey(key))) {// 当是同一个时(可能都为 null),再判断映射值是否为 null 并且 key 不存在,返回 false
return false;
}
remove(key);// 删除指定 Entry (抽象方法,由具体实现类实现删除逻辑)
return true;
}
例:
System.out.println(map.remove(1, "a"));// true
System.out.println(map.remove(4, null));// false
map.forEach((key, value) -> System.out.println(key + value));// 2b 3c
replace
/**
* 当指定的 key 和 value 是映射关系时,用 newValue 替换指定 value。
*
* 该方法的默认实现相当于:
* if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
* map.put(key, newValue);
* return true;
* } else
* return false;
* }
*
* 如果 oldValue 为 null,则默认实现不会为不支持 null 值的映射抛出 NullPointerException,除非 newValue 也为 null。
*
* 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
*/
default boolean replace(K key, V oldValue, V newValue) {
Object curValue = get(key);
if (!Objects.equals(curValue, oldValue) ||
(curValue == null && !containsKey(key))) {
return false;
}
put(key, newValue);
return true;
}
例:
System.out.println(map.replace(1, "a", "d"));// true
System.out.println(map.replace(2, "c", "e"));// false
map.forEach((key, value) -> System.out.println(key + value));// 1d 2b 3c
/**
* 当指定 key 的映射 value 不为 null 或 key 存在时,给 key 替换指定的 value,并返回被替换的 value。否则返回 null。
*
* 该默认实现相当于:
* if (map.containsKey(key)) {
* return map.put(key, value);
* } else
* return null;
* }
*
* 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
*/
default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
例:
System.out.println(map.replace(1, "d"));// a
System.out.println(map.replace(4, "d"));// null
map.forEach((key, value) -> System.out.println(key + value));// 1d 2b 3c
coomputeIfAbsent
/**
* 如果指定的 key 尚未与 value 相关联(或映射到 null),
* 则尝试使用给定的映射函数计算其 value 并返回,当返回不是 null,则将其输入到此映射。
*
* 默认实现相当于:
* if (map.get(key) == null) {
* V newValue = mappingFunction.apply(key);
* if (newValue != null)
* map.put(key, newValue);
* }
*
* 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
* 特别的,只有当该值不存在时,子接口 ConcurrentMap 的所有实现类必须记录该函数是否在原子上应用。
*/
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
例:
System.out.println(map.computeIfAbsent(1, (key) -> "d"));// a
System.out.println(map.computeIfAbsent(4, (key) -> "d"));// d
map.forEach((key, value) -> System.out.println(key + value));// 1a 2b 3c 4d
computeIfPresent
/**
* 如果指定的 key 存在并且不为 null(不存在则返回 null),则根据旧的 key 和 value 计算新的 value,如果新 value 不为 null,
* 则设置 key 的新 value,并返回新 value。否则,删除指定 key 的 Entry,并返回 null。
*
* 默认实现相当于如下操作:
* if (map.get(key) != null) {
* V oldValue = map.get(key);
* V newValue = remappingFunction.apply(key, oldValue);
* if (newValue != null)
* map.put(key, newValue);
* else
* map.remove(key);
* }
*
* 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
* 特别的,只有当该值不存在时,子接口 ConcurrentMap 的所有实现类必须记录该函数是否在原子上应用。
*/
default V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue;
if ((oldValue = get(key)) != null) {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null) {
put(key, newValue);
return newValue;
} else {
remove(key);
return null;
}
} else {
return null;
}
}
例:
System.out.println(map.computeIfPresent(4, (key, value) -> key + value));// null (不存在指定的 key,直接返回 null)
System.out.println(map.computeIfPresent(1, (key, value) -> key + value));// 1a (存在指定的 key,将该 key 的值替换为 key + value:1a)
System.out.println(map.computeIfPresent(2, (key, value) -> null));// null (key 存在,但是 value 为 null,于是该 Entry 被删除并返回 null)
map.forEach((key, value) -> System.out.println(key + value));// 11a 3c
compute
/**
* 根据指定的 key 与映射的 value 计算新的 value,
* 新 value 不为 null 时,则设置为 key 的新 value,并返回新 value。
* 否则当旧 value 不为 null 或者 key 存在时,删除 key 对应的 Entry,并返回 null。
*
* 默认实现相当于:
* V oldValue = map.get(key);
* V newValue = remappingFunction.apply(key, oldValue);
* if (oldValue != null ) {
* if (newValue != null)
* map.put(key, newValue);
* else
* map.remove(key);
* } else {
* if (newValue != null)
* map.put(key, newValue);
* else
* return null;
* }
* }
*
* 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
* 特别的,只有当该值不存在时,子接口 ConcurrentMap 的所有实现类必须记录该函数是否在原子上应用。
*/
default V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue = get(key);
V newValue = remappingFunction.apply(key, oldValue);
if (newValue == null) {
// delete mapping
if (oldValue != null || containsKey(key)) {
// something to remove
remove(key);
return null;
} else {
// nothing to do. Leave things as they were.
return null;
}
} else {
// add or replace old mapping
put(key, newValue);
return newValue;
}
}
例:
System.out.println(map.compute(4, (key, value) -> key + value));// 4null (指定 key 虽然不存在,但是新 value 不为 null,于是 key 与 新 value 组成一对映射)
System.out.println(map.compute(1, (key, value) -> key + value));// 1a (指定 key 存在,并且新 value 也不为 null,于是设置 key 的 value 为新 value)
System.out.println(map.compute(2, (key, value) -> null));// null (指定 key 存在,但是新 value 为 null,于是删除 key 对应的 Entry)
map.forEach((key, value) -> System.out.println(key + value));// 11a 3c 44null
marge
/**
* 如果指定的 key 尚未与 value 想关联或与 null 相关联,则将其与给定的非空 value 相关联。
* 否则,将关联的值替换为给定的重映射函数的结果,如果结果为 null,则将其移除。
*
* 默认实现相当于:
* V oldValue = map.get(key);
* V newValue = (oldValue == null) ? value :
* remappingFunction.apply(oldValue, value);
* if (newValue == null)
* map.remove(key);
* else
* map.put(key, newValue);
* }
*
* 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
* 特别的,只有当该值不存在时,子接口 ConcurrentMap 的所有实现类必须记录该函数是否在原子上应用。
*/
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
例:
// 指定 key 不存在并且指定 value 不为 null 时,key 与该 value 关联映射,并返回该 value。
System.out.println(map.merge(4, "d", (key, value) -> key + value));// d
// 指定 key 存在但是与指定 value 并不存在映射关系,于是将 key 的 value 设置为新 value,并返回新 value。
System.out.println(map.merge(3, "d", (key, value) -> "new"));// new
// 计算后的新 value 结果为 null,于是移除这个 Entry,并返回 null
System.out.println(map.merge(2, "b", (key, value) -> null));// null
map.forEach((key, value) -> System.out.println (key + value));// 1a 3new 4d
至此,Map 接口中的所有抽象方法、子接口、静态方法以及默认方法全部都理了一遍。
总结
Map 接口作为映射集合的顶层父接口,里面定义了一些必要的操作方法。其中大部分是抽象的,交由具体实现类根据自身的数据结构作具体的实现。另外还包括了 Java 8 中新引入的默认方法、静态方法。
---------------------
作者:Airsaid
来源:CSDN
原文:https://blog.csdn.net/Airsaid/article/details/51066637
版权声明:本文为博主原创文章,转载请附上博文链接!