Java集合类之接口学习

一、前言

      在Java中使用接口能规范实现该接口的类该实现的功能,介绍Java集合类的接口有助于对Java集合整体、对不同场景该使用什么样的集合有个明确的认识,对于学习Java开发的人来说,Java标准库集合的学习是必经之路,所以自今天起,我打算每天从源码和文档两个方向来了解以及在这里记录学习的过程。
      PS:在学习之前我往往会在互联网中四处收集资料,这可能是个坏习惯…当我看到别人博客中对Java集合总结的特别详细时,我自己就会产生惰性不想"重复造轮子"再对其总结一次。但别人理解透彻了我看一遍是达不到同样的效果的,所以我还是打算自己总结一次,也顺便锻炼锻炼表达能力。另外,别人的总结我也会把链接放在这里,如果有更好的博主,希望大家能推荐推荐:
Java学习博客1:https://blog.csdn.net/zhoucheng05_13/article/details/79834945
Java学习博客2:https://blog.csdn.net/u010887744/article/details/50575735#comments

二、Java集合的介绍

      Java的集合框架可以分为两大支系,分别为Collection(收集器\集合)和Map(映射),并不是因为它们是完全不相干的东西,而是因为它们在数据存储和查询这一方面的确有很大的差别,所以集合框架分成了两个接口来实现。
      先开局一张接口继承关系图:
在这里插入图片描述
      没错,这张图就是Java核心技术卷1第十版中P352页的内容,这张图中只涵盖了最常用基本的集合接口,在之后的学习过程中也会围绕以上接口的实现来进行。首先对该图中所有的接口一一介绍:
PS:在此之后Java对并行支持更加完善之后新增了Concurrent*系列接口,这些接口暂不在本次学习的内容中。

2.1、遍历工具接口

Iterable接口学习

public interface Iterable<T>{
    Iterator<T> iterator();   //返回一个泛型迭代器
    default void forEach(Consumer<? super T> action);  //参数为一个函数接口.用于函数式遍历可迭代集
    default Spliterator\<T> spliterator();    //可分割迭代器,用于并行遍历
    }

小结:可以看出该接口的职能是:继承该接口的类必须能返回自己的迭代器,由于之后JDK更新出现了lambda表达式,对并行的支持更加完善等,该接口又多了如上两个默认方法,其中forEach通用性很强,实现该接口的类一般可以直接使用默认方法。但可分割迭代器的分割能力较差,一般得重新实现一遍以提供更好的性能。

Iterator接口学习

public interface Iterator<E> {
//是否有下一个元素
boolean hasNext(); 
//返回下一个元素,如果到达末尾则返回NoSushElementException异常
E next();
//默认方法是抛出异常,需要重新实现,作用是删除上一个next返回的元素
default void remove() 
/*
*默认方法,参数为函数式接口,表示接受单个输入参数且不返回结果的操作
*该方法作用为:遍历集合内的剩余元素,也就是当前迭代器后面的元素
*/
default void forEachRemaining(Consumer<? super E> action)
}

小结:要注意Iterable和Iterator的区别,一个是"可迭代的"另一个是"迭代器",他们之间比较类似的方法就是forEach和forEachRemaining了,参数一致,行为几乎也是一致。但他们是有区别的。在Iterable中是对"可迭代的"元素进行全体遍历直到结束或发生异常,在"迭代器"中该方法是对该迭代器之后的元素进行遍历。
      不能直接连续调用remove方法,remove方法的实现和next方法具有依赖性,如果要删除连续的两个元素得在每次调用remove之前调用next方法来移动迭代器。
      还需要注意的一点是,进行for-each循环时若直接使用对象的remove方法可能抛出异常,在使用迭代器进行元素遍历时若涉及到元素列表的更改应该使用迭代器的remove方法来更改元素列表。

ListIterator接口学习

//该接口继承于Iterator并且有如下 新增 方法:
public interface ListIterator<E> extends Iterator<E> {
//是否有上一个元素
boolean hasPrevious(); 
//返回上一个元素,如果到达初始则返回NoSushElementException异常
E previous(); 
//返回后续调用 next 时返回元素的索引,如果列表迭代器位于列表的末尾,则返回列表大小
int nextIndex();  
//返回后续调用 previous 时返回元素的索引,如果列表迭代器位于列表的开头,则返回-1
int previousIndex();	
//作用更新:用于删除上次调用next或previous方法时所返回的元素
void remove(); 
/*
*将上次调用next方法或previous方法所返回的元素进行替换
*如果next或previous后对表结构修改后调用该方法则返回异常
*/
void set(E e);
 //在迭代器位置之前插入元素
void add(E e);
}

小结:对比Iterator多了向前遍历(previous)的功能,以及获取前后元素索引的功能,remove方法也随之变成了删除上次迭代器返回的元素。还增加了设置元素和新增元素的方法。

RandomAccess接口学习

public interface RandomAccess {
}

小结:该接口属于标记接口,能高效进行随机访问的Collection实现应该实现该标机接口。
小结:一般底层使用数组实现的集合类都需要实现该接口以便让遍历或查询的实现能更加高效

2.2 、集合接口

Collection接口学习

//该接口继承于 Iterable<E>并且有如下新增方法:
public interface Collection<E> extends Iterable<E> {
------查询类--------------------------
int size();			//返回集合中元素的个数
boolean isEmpty();	//判断集合是否为空
boolean contains(Object o);	//判断是否包含该元素
boolean equals(Object o);	//判断集合是否相等
int hashCode();	//返回该集合的hash值
boolean containsAll(Collection<?> c);  //如果传参集合中包含指定集合的​​所有元素,则返回true。
\--------------------------------------

------修改类--------------------------
boolean add(E e);	//添加元素,成功更改集合内容则返回true
boolean remove(Object o);	//删除元素,成功则返回true
void clear();	//从此集合中删除所有元素
boolean addAll(Collection<? extends E> c);  //将传参集合所有元素追加至该集合
boolean removeAll(Collection<?> c);  	//将此集合中包含传参集合中的元素全部删除
boolean retainAll(Collection<?> c);		//仅保留此集合中包含在参数集合中的元素
default boolean removeIf(Predicate<? super E> filter)  //根据参数描述的条件删除元素
\--------------------------------------

------转换类--------------------------
Object[] toArray();	//返回为Object数组
<T> T[] toArray(T[] a);	//传入类型数组参数,将集合返回到类型数组
default Stream<E> stream()	//返回集合的序列流
default Stream<E> parallelStream()	//返回集合的并行流
default Spliterator<E> spliterator()  // 默认方法,继承于Iterable,在该接口中对其默认实现进行了修改
\--------------------------------------
}

小结:该接口是后续所有具体集合的父接口,所以它所拥有的特征即是之后绝大部分集合所都必须拥有的特征,它所需实现的方法可以分类如上,前两类都是比较好理解和实践的分类,需要特别说明的就是最后的转换类方法了。所谓转换类方法也就是将集合转换为另一种表达形式的方法。
首先讨论两种不同的 toArray();
从方法签名中就能很清楚的看出俩toArray()仅返回的类型不同罢了,要返回正确类型的数组就得传入正确类型的参数才行,比如:ArrayList<Sring>,如果该方法直接调用toArray()方法,很明显会返回Object类型数组。该方法在设计上是因为Java是用类型擦除来实现泛型的,基本类型Java能用隐式强制类型转换来达成泛型目的,而数组则无法直接进行强制类型转换了,同时集合接口并不知道集合类中存储的是何种类型,类型是否一致,如果使用原型集合类来存储元素就能存储一切类型元素了。而该集合也只能由Object数组来存储。所以设计了该方法。
后面的toArray方法很明显将类型处理成了一致类型,参数的作用是:如果传入的数组参数长度足够则将集合内容复制到该数组中,多余的长度赋值为空,最后返回该数组的引用。如果数组参数长度不够则只参考传入参数的类型来构造长度与集合长度一致的数组复制并返回其引用。
上面的实现步骤是参考自ArrayList的实现,不过接口的行为应该是一致的,所以该实现所造成的行为可以推广到其余实现中。

后面的三种方法都是来自于后面Java对并发程序的支持,首先
stream方法,该方法在后续路线至ArrayList类中都没有重新实现,所以它们所用的版本都是Collection中的默认实现:

   default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

该方法进入后又调用了StreamSupport类中的方法,该方法使用集合的splierator()方法产生的可分割迭代器 和 是否并行(false为序列流,true为并行流) 作为参数返回一个流。继续深入该部分则涉及到了Java对于流以及并行的设计,所以该方法的介绍就此打住了。

parallelStream方法就如同stream方法的实现,仅将false改为了true,具体源码如下:

   default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), true);
    }

spliterator方法返回一个可分割迭代器,是产生并行流所需用到的参数。这里也不过多讨论可分割迭代器的实现和用法,只简要介绍一下概念:

    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 0);
    }

从源码跟踪开始分析,该方法使用最终类Spliterators中的spliterator传入了this和0,跟踪进入该代码即可得知:

 public static <T> Spliterator<T> spliterator(Collection<? extends T> c,
                                                 int characteristics) {
        return new IteratorSpliterator<>(Objects.requireNonNull(c),
                                         characteristics);
    }

该方法会返回一个IteratorSpliterator对象,该对象以Spliterator接口作为句柄,返回给方法调用者,要弄清楚该迭代器的作用也就得从该接口和具体的实现类上手了,这里暂时不打算细讲。
IteratorSpliterator是该接口的内部类,构造函数的任务是初始化集合的引用以及集合的特征(用int表示以位区分)。可分割迭代器相对于普通迭代器在概念上主要仅多了个可分割的功能,也就是能使用trySplit方法对可分割迭代器根据特征进行分割,使原迭代器和产生的新的可分割迭代器各自处理分割后的一块内容。

List接口学习

//该接口继承于Collection<E>并且有如下新增方法:
public interface List<E> extends Collection<E> {
------查询类--------------------------
E get(int index);	//返回该列表索引处的元素
int indexOf(Object o);	//返回该列表中出现该对象时的第一个索引
int lastIndexOf(Object o); //返回该列表中出现该对象时的最后一个索引
\--------------------------------------

------修改类--------------------------
void add(int index, E element);	//在列表索引index处增加元素element
E remove(int index);	//删除列表index处的元素
E set(int index, E element);		//设置列表index处的元素为element
default void sort(Comparator<? super E> c)  //根据传入的比较器参数对列表元素进行排序
default void replaceAll(UnaryOperator<E> operator)   //对列表的每一个元素根据旧值生成新值
\--------------------------------------

------转换类--------------------------
ListIterator<E> listIterator();  	//返回该列表的列表迭代器
List<E> subList(int fromIndex, int toIndex);		//返回该列表[fromIndex, toIndex)的子列表
\--------------------------------------

小结:该接口继承于Collection,所以父接口的部分我都省略掉了,在新增的具体化的方法中需要重点注意只有subList方法,该方法返回的列表并不是新的列表,而是对原列表的某部分的引用,所以我们将之称之为"视图",在子列表视图中操作元素将会把更改反映到原列表,需要特别注意的是如果操作原列表改变了其列表结构(比如增删元素),子列表视图将会作废。
子列表作废的检测是一种叫做 快速失败迭代器 的操作完成的,关键变量为modCount,代表使用迭代器修改该列表的次数,需要快速失败机制的列表实现每次操作列表时都会检查该变量,如果该变量的值意外更改则会抛出异常。
举例: A为具体列表,B为A的子列表视图,如果A调用了修改列表结构的方法则会更新A的modCount但不会更新B的modCount,这时B调用操作列表的方法就会因为抛出异常而失败。
而如果B调用修改列表结构的方法,由于B修改列表结构实际上是在检查范围等值的基础上调用的A的方法和做一些善后处理,所以A和B的modCount都会改变,这样一来A和B两个列表都不会出现问题。

最后可能需要说一次的replaceAll方法,该方法和Collection接口中的removeIf方法类似,传入的都是函数式接口参数,由方法签名很容易知道其用途,实现也仅仅是以前匿名内部类的改进,由于以前想传递方法参数就得传递一个只包含一个方法的对象,这样写代码十分繁琐,所以才出现了函数式接口。该replaceAll方法的作用通过观察其源码很容易发现是通过函数式参数根据列表旧值改变所有元素的方法。

Set接口学习

//该接口继承于Collection<E>并且有如下新增方法:
public interface Set<E> extends Collection<E>{
// 该接口方法与Collection方法签名完全一样,但描述不同,方法的具体行为有更加严谨的定义
}

小结:该接口所拥有的方法签名是完全等同于Collection的,但方法的行为有更加严谨的定义,比如:

  • add 方法不允许添加重复元素
  • 要适当定义 equals 方法
  • 只要两个集包含同样的元素就认为是相等的而不要求其顺序
  • hashCode方法的定义要保证包含相同元素的两个集会得到相同的散列码。
  • Set迭代器元素的返回不要求保证其顺序性

再讨论,为什么方法一样还要定义一个相同的接口呢?明显的是Set是Collection的子概念,拥有更完整和严谨的定义,就如同编译原理中的文法等级一样,层次依次递增,定义越加严谨的。从Collection的子接口中可以看出,要将链表,队列,集的共性抽象出一个父接口,根据木桶原理也只能从最简洁的集开始下手。将Set与Collection分离以后就能方便的编写适合于所有Collection类型的或者Set类型的方法了,否则需要编写只适合Set而不适合Collection的方法将很困难。

Queue接口学习

//该接口继承于Collection<E>并且有如下新增方法:
public interface Queue<E> extends Collection<E>{
------查询类--------------------------
E element();	//检索队列头部,如果队列为空则抛出异常
E peek();		//检索队列头部,如果队列为空则返回null
\--------------------------------------

------修改类--------------------------
boolean offer(E e);	//添加元素到队列,和add的区别是add因为容量不够会抛出异常,该方法只返回false
E remove();	//检查并删除返回队列头部,如果队列为空则抛出异常
E poll();			//检查并删除返回队列头部,如果队列为空则返回null
\--------------------------------------
}

小结:作为Collection的三大子接口该接口实现的队列是比较容易理解的,该接口对比Collection新增了不同的查询/新增和删除元素的方法,根据其需求在队列为空时有返回null和抛出异常两种操作方式。
可能会有这样的疑问,为什么同样功能的方法仅仅因为一丁点儿特殊情况就需要两种不同的方法签名呢?我个人认为这样是为了使队列能适用于各种不同的情况(可能有点废话),要弄清楚这一点可以先从 “抛出异常” 和 返回null 的区别开始分析,因为该类方法仅在队列为空时的返回动作有所不同。
抛出异常,代表的是正常运行时不该出现的情形,通常异常都会有其处理程序来使系统在局部出错的情况下仍然能恢复健康。
而返回null值,在正常情况下通常都作为程序跳转的一个条件,在不为null时使用A方法处理,在为null时使用B方法处理。
然而,实际上,两种情况都是可以相互替代的,都是起到一个在队列为空的情况下做出特别处理的一个作用。
在JDK标准文档中描述的是:队列的方法通常会有两种形式,在操作失败时抛出异常和返回特殊值。通常后一种形式的插入专用于操作容量有限的队列实现;

还需要注意的是因为 null 是接口方法规定的特殊返回值,所以队列最好不要存入null作为数据项。

Deque接口学习

//该接口继承于Queue<E>并且有如下新增方法:
public interface Deque<E> extends Queue<E> {
------查询类--------------------------
E getFirst();
E getLast();
E peekFirst();
E peekLast();
/*如果队列非空,则返回队列头部或尾部元素,如果队列为空,则前俩方法抛出异常,后俩方法返回null*/
\--------------------------------------

------修改类--------------------------
void addFirst(E e);
void addLast(E e);
boolean offerFirst(E e);
boolean offerLast(E e);
/*将给定对象添加到双端队列的头部或尾部,如果队列满了,前两个方法抛出异常,后两个方法返回false*/
E removeFirst();
E removeLast();
E pollFirst();
E pollLast();
/*如果队列不空,删除并返回队列头部或尾部元素。如果队列为空,前俩方法抛出异常,后俩方法返回null*/

boolean removeFirstOccurrence(Object o);
//从双端队列中删除第一次出现的指定元素,如果双端队列不包含该元素,则不会更改
//如果此双端队列包含指定元素,则返回true

void push(E e); //将元素入栈,此方法等同于addFirst
E pop();	//元素出栈,删除并返回此双端队列的第一个元素。
\--------------------------------------

------转换类--------------------------
Iterator<E> descendingIterator();  //返回一个反向迭代器
\--------------------------------------
}

小结:该接口继承于Queue,并且有反向迭代器方法,所以对比原队列接口可以从正反两个方向遍历该队列,也就是双端队列,因为栈是双端队列的一个特例,所以该接口中包含入栈出栈的方法,可以当做栈用。
该双端队列除了要实现原有于Queue的方法之外,还增加了操作两端的方法,当然,也是同时对队列为空时提供了两种方法。
这里可能会有人有疑问,原来的查看/增加/删除方法不正好对应双端队列一端的情况吗?为什么还要新增这么多方法呢?关于这点,可能是设计该接口人员的方法签名强迫症,接口继承继承了原先不严谨,不清楚的名字,所以需要为它更名。比如offerLast方法,就对应于原Queue接口中的offer方法,它们是完全等效的,仅名称不同。所以offerLast方法的实现一般都是这样的:

    public boolean offer(E e) {
        return offerLast(e);
    }

这种方法我们称之为桥方法,仅使这个方法签名和原有的方法起到一个桥接的作用。名称风格一致的方法签名更易于使用,代码可读性也越高。所以这样做是有价值的。

注意:老版本Java中使用栈所用的Stack类是继承于Vector的,而Vertor是遗留的线程安全类,效率不高,所以现代程序要使用栈时一般使用Deque。需要线程安全也可以使用相关的包将现存的非线程安全集合转换为线程安全的。

SortedSet接口学习

//该接口继承于Set<E>并且有如下新增方法:
public interface SortedSet<E> extends Set<E>{
------查询类--------------------------
Comparator<? super E> comparator();  //返回一个比价器,如果使用自然排序则返回null
E first();	//返回此集合中第一个元素。
E last();  //返回此集合中最后一个元素。
\--------------------------------------

------修改类--------------------------
// 使用Set/Collection接口中的方法修改
\--------------------------------------

------转换类--------------------------
//和subList类似,返回集合的[fromElement,toElement)的视图
SortedSet<E> subSet(E fromElement, E toElement); 
//返回集合小于toElement元素的视图
SortedSet<E> headSet(E toElement);
//返回集合大于fromElement元素的视图
SortedSet<E> tailSet(E fromElement);
\--------------------------------------
}

小结:该接口定义了可排序集合的方法定义,关于集合子视图的描述在之前已经讲过了,在这里概念完全适用,查询类中的方法就比较器那个需要注意。
在Java中关于排序的接口据我所知有两种,分别是:Comparator和Comparable,通过查看代码可知,后者需要实现的方法只有一个:

public int compareTo(T o);

很明显,该方法用于 继承该接口的类使用this和传入的参数o进行比较,通常该比较方案被称之为内部比较器。因为该比较方案需要借助具体对象。
而Comparator接口就不同了,里面定义了很多比较方法,当然,大多都是有默认实现的,必须实现的就两个:

int compare(T o1, T o2);
boolean equals(Object obj);

通过查看方法签名中的入口参数,可知该接口实现的比较方案不需要this参数,也就是说可以将该比较器在需要比较的类外部重新定义一个XX比较器的类,专用于比较XX对象。所以实现该比较器不需要修改需要比较的类的内部结构。该比较器一般被称之为外部比较器。
在SortSet接口中,很明显该接口需要实现可排序功能,但它并没有继承Comparable接口和Comparator接口,而是定义了一个返回Comparator对象的方法签名。这样做同样能做到使实现该接口的类能进行排序。具体可以参考TreeSet。没有提供比较器时该方法会返回null,比较元素时会采用自然排序,也就是使用具体元素的Comparable方法进行排序。
如果具体元素没有实现Comparable接口,而又没有提供Comparator比较器,则在添加元素时会就会抛出异常。

NavigableSet接口学习

//该接口继承于SortedSet<E>并且有如下新增方法:
public interface NavigableSet<E> extends SortedSet<E> {
------查询类--------------------------
E lower(E e);  	 //返回小于参数元素的最大元素,如果没有这样的元素,则返回null
E floor(E e);    //返回小于或等于参数元素的最大元素,如果没有这样的元素,则返回null。
E ceiling(E e);  //返回大于或等于参数元素的最小元素,如果没有这样的元素,则返回null。
E higher(E e);   //返回大于参数元素的最小元素,如果没有这样的元素,则返回null。
\--------------------------------------
------修改类--------------------------
E pollFirst();     //检索并删除第一个元素,如果集合为空,则返回null。
E pollLast();		//检索并删除最后一个元素,如果集合为空,则返回null。
\--------------------------------------
------转换类--------------------------
NavigableSet<E> descendingSet(); //返回该集合的逆序视图。
Iterator<E> descendingIterator();    //返回逆序迭代器
//返回集合从fromElement到toElement的集合视图,boolean为是否包括边界
NavigableSet<E> subSet(E fromElement, boolean fromInclusive,
                       E toElement,   boolean toInclusive);
//同SortSet的headSet,多了个是否包括边界参数
NavigableSet<E> headSet(E toElement, boolean inclusive);
//同SortSet的headSet,多了个是否包括边界参数
NavigableSet<E> tailSet(E fromElement, boolean inclusive);
\--------------------------------------
}

小结:NavigableSet也就是可导航的集合,除了包含排序功能外,还新增了很多匹配搜索目标的方法,比如方法lower,floor,ceiling和higher,在转换生成子集合视图方面新增了是否包括边界的选项,逆序集合视图等。在迭代器方面可以返回正反两个方向的迭代器。

2.3、映射接口

Map接口学习

public interface Map<K,V> {
------查询类--------------------------
int hashCode();	//返回映射的哈希码
int size();		//返回映射的大小
boolean containsKey(Object key);		//包含指定键则返回true
boolean containsValue(Object value);	//包含指定值则返回true
V get(Object key);	//返回该键映射的值,不过没有该键则返回null
boolean equals(Object o);	//是否相等,比较方法为m1.entrySet().equals(m2.entrySet())
boolean isEmpty();	//如果不包含键值映射则返回true

//返回传入键映射到的值,如果键不存在则返回传入的默认值
default V getOrDefault(Object key, V defaultValue) 
\--------------------------------------

------修改类--------------------------
V put(K key, V value);	//将键和值相关联入映射,如果键存在则更新其值
void putAll(Map<? extends K, ? extends V> m);	//将指定的映射复制到该映射中
V remove(Object key); //将该和该键关联的映射删除,并返回该值。如果没有该键则返回null
void clear();		//从此映射中删除所有映射

//对此映射中的每个条目执行给定操作,直到处理完所有条目或操作引发异常
default void forEach(BiConsumer<? super K, ? super V> action)

//将映射中每个条目替换为在该条目上调用给定函数的结果,直到所有条目都已处理或函数抛出异常。
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)

//如果指定的键尚未与值关联(或映射为null),则将其与给定值关联并返回null,否则返回当前值。
default V putIfAbsent(K key, V value)

//当传入的键能映射到传入值时才删除该条映射并返回true,否则返回false
 default boolean remove(Object key, Object value)
 
 //当传入键值对存在时讲旧值替换为新值并返回true,否则返回false
 default boolean replace(K key, V oldValue, V newValue)
 
 //传入的键有映射关系时使传入的值替换原有的值并返回原有的值,否则返回null
 default V replace(K key, V value)
 
//如果参数键没有映射关系则尝试使用给定函数为其计算值,并保存到映射中再返回,否则返回null
 default V computeIfAbsent(K key,
           Function<? super K, ? extends V> mappingFunction) 
           
//如果参数键有映射关系且值非空则尝试使用给定函数为其计算值,并保存到映射中再返回,否则删除该键并返回null
default V computeIfPresent(K key,
           BiFunction<? super K, ? super V, ? extends V> remappingFunction)
           
//尝试使用给定函数为参数键以及其值(如果没有该键则值为null)计算新值
//如果新值为null则删除旧键的映射并返回null
//否则将新值与参数键存入映射,并返回新值
default V compute(K key,
           BiFunction<? super K, ? super V, ? extends V> remappingFunction)

//如果参数键没有映射关系或值为null,则将其与给定的非空值关联。
//否则,将旧值替换为给定重映射函数的结果
default V merge(K key, V value,
           BiFunction<? super V, ? super V, ? extends V> remappingFunction)
\--------------------------------------

------转换类--------------------------
Set<K> keySet();	//返回键的集合视图
Collection<V> values();	//返回值的收集器视图
Set<Map.Entry<K, V>> entrySet(); //返回包含的映射的集合视图
\--------------------------------------
}

小结:映射接口也是属于Java集合框架的,但不是继承于Collection接口的,因为他们对于值的存储有很大的不同,所以该接口是后续所有具体映射关系的原始父接口。
在该接口中有一些方法描述还是有些奇怪,比如确定不了是否能一键多值,值是否能为null等问题,所以方法描述有些模糊。不过在具体实现中应该是清晰无比的,所以也不是太担心。
在该接口中有很多要求传入函数参数的默认方法,代码十分清晰。

SortedMap接口学习

//该接口继承于Map<E>并且有如下新增方法:
public interface SortedMap<K,V> extends Map<K,V> {
------查询类--------------------------
//返回基于可排序映射的键的比较器,若为null则采用自然排序(Comparable)
Comparator<? super K> comparator(); 
K firstKey();	  //返回映射中的第一个键。
K lastKey();	 //返回映射中的最后一个键。
\--------------------------------------

------修改类--------------------------
// 继承于父接口
\--------------------------------------

------转换类--------------------------
SortedMap<K,V> subMap(K fromKey, K toKey);	//返回子映射视图
SortedMap<K,V> headMap(K toKey);    //返回映射的子视图,其键小于toKey
SortedMap<K,V> tailMap(K fromKey);    //返回映射的子视图,其键大于或等于fromKey
\--------------------------------------
}

小结:该接口作为一个"可排序"的映射,自然和上述SortedSet一样有一个比较器,然后增加了一些有了排序特性后的方法,比如关于键值大小的映射子视图,返回最大的键或最小的键之类的。
实际上在标准实现中Set是使用Map实现的,但仅仅使用了键,没有使用值,所以它们在某些方面的特性是共通的。

NavigableMap接口学习

//该接口继承于SortedMap<E>并且有如下新增方法:
public interface NavigableMap<E> extends SortedMap<E>{
------查询类--------------------------
//返回严格小于参数键的最大键的映射,如果没有这样的键,则返回null。
Map.Entry<K,V> lowerEntry(K key);
//返回小于给定键的最大键,如果没有这样键,则返回null。
K lowerKey(K key);
//返回小于或等于参数键的最大键的映射,如果没有此键,则返回null。
Map.Entry<K,V> floorEntry(K key);
//返回小于或等于给定键的最大键,如果没有这样的键,则返回null。
K floorKey(K key);
//返回大于或等于参数键的最小键的映射,如果没有这样的键,则返回null。
Map.Entry<K,V> ceilingEntry(K key);
//返回大于或等于参数键的最小键,如果没有这样的键,则返回null。
K ceilingKey(K key);
//返回大于参数键的最小键的映射,如果没有这样的映射,则返回null。
Map.Entry<K,V> higherEntry(K key);
//返回大于参数键的最小键,如果没有这样的键,则返回null。
K higherKey(K key);
//返回与此映射中的最小键的映射,如果映射为空,则返回null。
Map.Entry<K,V> firstEntry();
//返回与此映射中的最大键的映射,如果映射为空,则返回null。
Map.Entry<K,V> lastEntry();
\--------------------------------------

------修改类--------------------------
//删除并返回与此映射中的最小键的映射,如果映射为空,则返回null。
Map.Entry<K,V> pollFirstEntry();   
//删除并返回与此映射中的最大键的映射,如果映射为空,则返回null。
Map.Entry<K,V> pollLastEntry();
\--------------------------------------

------转换类--------------------------
//返回此映射的逆序视图。
NavigableMap<K,V> descendingMap();
//返回此映射中包含的键的NavigableSet视图,支持删除不支持添加
NavigableSet<K> navigableKeySet();
//返回此映射中包含的逆序键的NavigableSet视图,支持删除不支持添加
NavigableSet<K> descendingKeySet();
//返回此映射部分的视图,其键的范围从fromKey到toKey。布尔值代表是否包含边界
NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive,
                            K toKey,   boolean toInclusive);
//返回此映射的部分视图,其键小于(或等于,如果inclusive为true)toKey
NavigableMap<K,V> headMap(K toKey, boolean inclusive);
//返回此映射的部分视图,其键大于(或等于,如果inclusive为true)fromKey
NavigableMap<K,V> tailMap(K fromKey, boolean inclusive);
\--------------------------------------
}

小结:该接口中定义了大量查询以及视图化的方法,和NavigableSet类似。在Map系列接口中除了上述方法以外,还得了解一个内部类,也就是Map.Entry,该类也就是映射关系元素的类,在之后所有的Map相关实现中都有该类的使用。

 interface Entry<K,V> {
K getKey();
V getValue();	
V setValue(V value);
boolean equals(Object o);
int hashCode();
public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey()
public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue()
public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp)
public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp)
}

可以看出,这是个十分普通继承于Object的类,对于映射元素的存储拥有获取键值的方法,拥有设置值的方法。后面四个方法看起来比较复杂,是用于比较的方法。

前两个方法是用于将 键/值 的自然比较器(Comparable) 重包装 为Map.Entry比较器的方法
后两个方法是分别将 传入的键/值比较器 重包装 为 Map.Entry 比较器的方法

Java去除了C++中指针以及类型的麻烦,但又带来了新的麻烦,不过这个麻烦比较好解决,看起来繁琐的方法签名和繁琐的指针一样令人生厌。这里试着解释一下后面的几个方法签名:
首先

public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey()

该方法是个静态泛型方法,拥有两个类型参数K和V,其中K必须扩展了Comparable<? super K>的接口,也就是说K或者K的父类实现Comparable都能接受,V是任意类型都能接受。返回值为Comparator<Map.Entry<K,V>>,方法名为 comparingByKey。
紧接的方法和该方法类似,然后直接看第三个方法:

public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp)

该方法也是静态泛型方法,拥有两个没有限制的类型参数K和V,返回Comparator<Map.Entry<K,V>>,方法名为comparingByKey,参数为Comparator<? super K> cmp,也就是说传入一个实现Comparator接口的K或K的父类对象作为参数,返回Comparator<Map.Entry<K,V>>类型。

…我还是觉得我的表述能力有些不清,不过泛型方法的难点就是理解多态,特别是? super和? extends之类的写法。要理解当然实践最好。因为该章主要是学习集合框架的接口,所以这里只稍微提一提,就不做赘言了。

三、总结以及注意点

接口学习总结:
      接口源码的总结对于一个对集合认识比较少,没怎么使用过的人来说还是有点困难的,因为没有用过对应的方法,也就不知道方法的行为应该是什么样的。不过还好有标准JDK文档,在文档中较清晰的描述出了各种方法所应有的行为。不过对于不理解的地方还是得实践验证才行。

注意点1:
      存在子接口对父接口方法的严格化,比如Collection和Set他们的方法签名是完全一致的,但部分方法的行为还是有所区别的。关于这一点一般按要求严格的进行遵守。
      理论上说,方法签名一致由Set直接继承Collection就不需要写方法签名了,但实际上并不是这样。在Set接口中还是把和Collection接口中一模一样的方法签名重写写了一遍。这样的原因是JavdDoc,因为虽然方法名称一样,但意义有所改变,所以在方法签名上面的注释中会重新描述该方法的作用。

注意点2:
      最后最重要的就是不要过度设计,和被接口的职能所迷惑,接口是为了描述类职能的,区分类类别的方案,是C++中多重继承的简化版,能容易的上手和使用。设计一个接口时尽量考虑全面。
如果在具体类对接口的实现过程中,某个方法不符合该类的实现之时可以直接抛出异常,而不要随意更改之前设计的接口。

更加详细完整的接口关系可以参考JDK文档:
http://tool.oschina.net/apidocs/apidoc?api=jdk-zh
https://docs.oracle.com/javase/10/docs/api/overview-summary.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值