美文网首页
J2SE复习内容 - 容器理解

J2SE复习内容 - 容器理解

作者: 做梦枯岛醒 | 来源:发表于2019-04-15 19:52 被阅读0次

    容器在日常开发中经常会用到,但是对于一些容器的原理或者边角知识却不太容易被记起,所以作此总结,也算是复习也算是预习。
    在本文中会穿插一些小问题,也算是比较好玩的知识,答案是我自己总结的,在面试中可能被问到,可以着重看一下,若有问题还望指出。其他的部分,比如说API,简单看下即可,使用可以自行查阅API文档,更深入的知识将在文末链接的其他文章中指出。
    还有一点要注意的是:某些知识点会随着深入而修正(比如说开场的图,就没有包含Queue),这套路貌似跟我们学习数学一样。

    1. 简单分类

    Java所提供的容器相关API位于java.util中,而一个比较简洁的结构组织如下,在掌握各种各样的容器之前,读懂这幅图至关重要。

    简单分类

    可以看到所有的容器会以Collection和Map为父接口来实现的,而Collection的特点就是装的时候是单个元素,Map的特点就是装的时候是键值对。

    而我们再看Collection的第二层,分别是Set和List,前者是要求数据没有顺序且不可以重复,而后者的数据是有顺序且可以重复的

    而最下面的相关实现类,远远不止这些,暂时先做简单了解。

    2. Collection

    public interface Collection<E> extends Iterable<E> {
        int size();    //集合大小
        boolean isEmpty();    //是否为空
        boolean contains(Object o);    //包含某个对象
        Iterator<E> iterator();    // 返回迭代器
        Object[] toArray();     //转换成object数组
        <T> T[] toArray(T[] a);    //转换成泛型数组
        boolean add(E e);    //添加一个元素
        boolean remove(Object o);   //删除一个元素
        boolean containsAll(Collection<?> c);     //包含另一个集合里的全部元素
        boolean addAll(Collection<? extends E> c);//添加另一个集合里的全部元素
        boolean removeAll(Collection<?> c);  //移除另一个集合里的全部元素
        boolean retainAll(Collection<?> c);  //求两个集合的交集,并复制给this
        void clear();   //清空
        boolean equals(Object o);    //object equals方法
        int hashCode();    //object hashCode方法
    }
    

    上面是Collection中定义的相关方法(default未列出),根据字面意思我们就能知道它包含什么功能。

    Collection像个协议,实现它的相关容器类就一定要实现它的方法。

    要知道Collection是继承自Iterable的,为了实现迭代器的功能。
    而我们查看源代码可以看到


    Iterable

    iterator方法正是这个接口所提供的,这个方法会返回一个Iterator(遍历器,用于遍历访问),而Iterator也是一个接口,所定义的的方法如下:

    //default方法均未列出。
    
    public interface Iterator<E> {
         boolean hasNext();   //有下一位
         E next();    //取出下一位数据
         void remove();  //后期改为default方法,用于删除next元素,迭代期间会加锁,可以用于安全删除。
    }
    

    可以看出内部定义很简单,就是若有则取的一种方式。

    Q1:为什么要实现这样一个接口?为什么不在Collection里统一定义get方法?
    由于我们的容器底层有很多数据结构支撑,比如说ArrayList的数组,LinkedList的链表,学过数据结构的童鞋都知道,两者的遍历方式,取值方式都不一样,这里还是仅仅举了两个例子,还有更多的操作都不一样,所以为了实现各种特殊的方法,就使用了Iterator了。

    Q2:补充:foreach
    foreach是一种增强的for循环,内部还是依靠于Iterator实现,但是不能实现remove操作,不方便删除集合中的内容,与传统for相比不能方便读取下标。注意一个要点,Iterator内部remove是锁定方法,意味着当使用Iterator遍历容器的时候,容器本身的remove方法是不可用的,所以同理foreach也用不了。

    3. Set (集合)

    Set的特点上面说过,(添加)不一定有顺序,但一定不重复。
    而Set接口内部提供的方法跟Collection是一样的(除个别方法外),所以参考一下上面说过的Collection接口,相关内容在后面介绍。

    4. List

    List实现的容器是(添加)有序的,且可以重复的,List是有访问的下标的,所以平时用的比较多的还是List的相关实现,如ArrayList,LinkedList,但是List相比Collection的话,增加了一些List相关操作方法,如下:

    /*排序*/
    default void sort(Comparator<? super E> c) 
    
    /*取得指定索引的元素*/
    E get(int index);
    
    /* 替换迭代器游标位置的数据,具体规则暂不深究*/
    void set(E e);
    
    /*替换index位置的元素为element,返回旧数据*/
    E set(int index, E element);
    
    /* 按照位置要求插入数据*/
    void add(int index, E element);
    
    /* 返回元素o的第一次出现的位置*/
    int indexOf(Object o);
    
    /* 元素o最后一次出现的位置 */
    int lastIndexOf(Object o);
    
    /* 返回一个列表iterator*/
    ListIterator<E> listIterator();
    
    /* 返回一个从指定位置开始的列表iterator */
    ListIterator<E> listIterator(int index);
    
    /* 截取一个子列表 */
    List<E> subList(int fromIndex, int toIndex); 
    

    这里有一个listIterator我们需要认识一下,大部分方法的原理还是跟我们上面说过的Iterator一样。

    public interface ListIterator<E> extends Iterator<E>{
          //next组
          boolean hasNext();
          E next();
          //previous组
          boolean hasPrevious();
          E previous();
          //返回对 next 的后续调用所返回元素的索引。(如果列表迭代器在列表的结尾,则返回列表的大小)。 
          int nextIndex();
          //返回对 previous 的后续调用所返回元素的索引。(如果列表迭代器在列表的开始,则返回 -1)
          int previousIndex();
          //移除元素方法
          void remove();
          //替换和添加操作
          void set(E e);
          void add(E e);
    }
    

    5. Collections

    这个类实现了一些常用的容器工具算法,(注意区分Collection接口和Collections)

    常用的如下:

    void shuffle(List<?> list)  //随机排序
    <T> void fill(List<? super T> list, T obj)   //数据填充 
    <T> void copy(List<? super T> dest, List<? extends T> src)  //列表复制
    int binarySearch(List<? extends Comparable<? super T>> list, T key)   //二分查找
    <T> void sort(List<T> list, Comparator<? super T> c)   //排序
    

    现在来深究一下sort方法。
    看一下源代码

     public static <T> void sort(List<T> list, Comparator<? super T> c) {
            list.sort(c);
        }
    

    仅仅是调用了list的sort方法,继续查看list.sort方法。

      default void sort(Comparator<? super E> c) {
            Object[] a = this.toArray();
            Arrays.sort(a, (Comparator) c);
            ListIterator<E> i = this.listIterator();
            for (Object e : a) {
                i.next();
                i.set((E) e);
            }
        }
    

    可以看到list.sort方法是有默认实现的,但是内部也就是调用Arrays.sort方法来排序的。

    我们看到的sort方法中,除了待排序数组之外,还有一个Comparator接口,相信它的意思应该很显而易见。

    举个例子,1 < 10, 2.0 < 10.0 这种问题很显然。
    但是比如说给你studentA 和 studentB ,你如何确定A和B的大小,这就要求我们使用Comparator来定义他们的大小。

    在说Comparator之前,我们要先明白一个东西。
    我下面贴两个源代码:

     public static <T extends Comparable<? super T>> void sort(List<T> list) {
            list.sort(null);
    }
    
     public static <T> void sort(List<T> list, Comparator<? super T> c) {
            list.sort(c);
        }
    
    

    这是Collections提供的两个排序方法,可以看到第一个是要求数据类实现Comparable接口,第二个是要求比较的时候传入Comparator对象,那这二者有什么区别呢?

    答: 没啥区别,调皮了。

    看一下Comparator的这一个核心方法,用于判断o1和o2的大小。

     int compare(T o1, T o2);
    

    官方文档说: return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.

    也就是 若 o1 < o2 返回负数,o1 = o2 返回0 ,o1 > o2返回正数。

    再看一下Comparable


    虽然参数不一样,但是注释一模一样,也就是功能一样。

    Q3:那为什么还要有这两者呢?
    考虑一下Comparable的用法是要实现,Comparator的用法是包装的方式,所以你能想到,如果你要用前者,在版本迭代中加入,就要修改类的源代码,如果用后者,只需要新建一个包装类即可,同时后者也可以实现一些复杂的操作,具体可以自行体验一下。

    接下来我们来看一下Integer里的这个方法(必然是有实现的)

    可以看到,确实是有自己实现。

    所以按照简单的写法,我们将自己的类实现Comparable接口即可。

    6. Map

    键值对存储方式,实现类如HashMap(哈希表实现),TreeMap(红黑树实现),键值不允许重复。

    下面给出Map中一些常用的方法定义(跟Collection类似的方法就不说了)

    public interface Map<K,V> {
        /* 是否包含某键*/ 
        boolean containsKey(Object key);
        /*是否包含某值*/
        boolean containsValue(Object value);
        /*通过key取值*/ 
        V get(Object key);
        /* 添加键值对,如果已有相应键,替换,返回替换前的值*/
        V put(K key, V value);
        /* 通过key删除某键值*/
        V remove(Object key);
        /* 批量添加*/
        void putAll(Map<? extends K, ? extends V> m);
        /* 返回键集合*/ 
        Set<K> keySet();
        /*返回值集合*/ 
        Collection<V> values();
        /*返回键值对集合*/
        Set<Map.Entry<K, V>> entrySet();
    }
    

    7. 总结

    本文作为对于容器的相关回顾和总结,提到了一些简单的API和琐碎的知识点。

    首先是一张图,这个很重要,但是这张图不是详细的图,只是一个大致的结构。

    然后穿插说了迭代器比较器两种辅助工具。

    其次还有提到过三个Question,也比较有意思。
    对于其他知识,应该是学习阶段就要了解的,但是不了解也无所谓,可以查阅API文档,不难理解。

    8. 相关文章

    Set系列:
    HashSet 浅谈

    相关文章

      网友评论

          本文标题:J2SE复习内容 - 容器理解

          本文链接:https://www.haomeiwen.com/subject/uabuwqtx.html