框架和容器:源码

参考的文章:

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接口


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接口


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集合的整体UML图


源码分析:

查询操作

/** 

 * 返回此 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

版权声明:本文为博主原创文章,转载请附上博文链接!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,548评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,497评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,990评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,618评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,618评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,246评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,819评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,725评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,268评论 1 320
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,356评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,488评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,181评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,862评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,331评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,445评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,897评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,500评论 2 359

推荐阅读更多精彩内容