集合系列

梦想与她 提交于 2020-03-18 01:26:48

某厂面试归来,发现自己落伍了!>>>

1、关于集合的两道面试题

先来看几道题目:

1、创建一个不可变的的集合:

public static void main(String[] args) {
    Set<String> set = new HashSet<String>();  

    set.add("Java");  
    set.add("JEE");  
    set.add("Spring");  
    set.add("Hibernate");  
    set = Collections.unmodifiableSet(set);  
    set.add("Ajax"); // not allowed.  
}  

    可以看到,创建不可变集合主要是调用了Collections的unmodifiableSet()方法,而Collections类通过装饰模式实现了对一般集合的封装。

    2、去除List集合中的重复元素,且保持原有的顺序

    public static void main(String args[]) {  
            List<String> list=new ArrayList();  
            list.add("A");  
            list.add("B");  
            list.add("C");  
            list.add("A");  
            // List中允许元素重复  
            for(int i=0;i<list.size();i++)   
                   System.out.print(" "+list.get(i));// A B C A  
            // 去除重复的元素且保持原有元素的顺序  
            List list2=new testD().function(list);  
            for(int i=0;i<list2.size();i++)   
                   System.out.print(" "+list2.get(i));// A B C   
              
    }  
    public <e> List<e> function (List<e> list) {  
             return new ArrayList<e>(new LinkedHashSet<e>(list));  
    }

    上面的代码代码通过把原有列表传入一个LinkedHashSet来去除重复的元素。在这个情况里,LinkedHashSet可以保持元素原来的顺序。如果这个顺序是不需要的话,那么上面的LinkedHashSet可以用HashSet来替换。

    2、集合框架类图及集合对比

    其实如上的两道题目并不难,只是没有对集合了解的更深。下面就来大概的看一下集合吧。集合的继承类如下所示。

    如上的集合可以归纳为三大类,即List、Set和Map。同时还有一些辅助的工具类,像Collections、Arrays等方便了集合的操作,各个集合的比较情况如下图。

    其实对于集合的操作,无外乎就是

    1. 增加:向一个集合中添加一个元素、将一个集合中的所有元素插入到当前集合中
    2. 删除:删除指定的一个元素、删除集合中所有的元素
    3. 修改:修改指定的一个元素
    4. 查找:查当前集合的大小、查找两个集合中的共同元素等
    5. 其它:如将一个集合中的元素转换为数组表示形式、判断一个集合是否为空等

    3、Iterable、Iterator、Collection接口

    从Java集合框架图可以看出,所有的类都直接或间接继承了Iterable接口,源代码如下:

    package java.lang;
    import java.util.Iterator;
      
    /** 
     * Implementing this interface allows an object to be the target of 
     * the "foreach" statement. 
     */  
    public interface Iterable<T> {  
        Iterator<T> iterator(); // 返回一个在一组 T 类型的元素上进行迭代的迭代器  
    }

    Iterable接口在java.lang包下,其中定义了一个获取Iterator的实例方法。

    Iterator类的源代码如下:

    public interface Iterator<E> {  
        boolean hasNext();  // 是否含有下一个元素  
        E next();           // 获取集合的下一个元素  
        void remove();      // 从集合中移除当前元素  
    }

    Iterator模式是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来,从而避免向客户端暴露出集合的内部结构。例如,如果没有使用Iterator,遍历一个数组的方法是使用索引: 

    for(int i=0; i<集合的大小;i++){  
        // 省略操作代码  
    } 

    当访问一个链表(LinkedList)时又必须使用while循环:

    while((e=e.next())!=null) {   
        // 省略操作代码  
    }

    以上两种方法遍历办法都必须事先知道集合的内部存储结构,访问代码和集合本身是紧耦合,无法将访问逻辑从集合类中分离出来,每一种集合对应一种遍历方法,客户端代码无法复用。 而且如果以后需要把ArrayList更换为LinkedList,则原来的客户端代码必须要进行重写。 为解决以上问题,Iterator模式总是用同一种逻辑来遍历集合:

    for(Iterator it = c.iterater();it.hasNext(); ) {   
        // ...   
    }

    设计的巧妙之处在于客户端自身不维护遍历集合的"指针",所有的内部状态(如当前元素位置,是否有下一个元素)都由Iterator来维护,而这个Iterator由集合类通过工厂方法生成,因此,它知道如何遍历整个集合。值得提醒的是,这个工厂方法就是我们前面提到的iterator()方法,因为所有的类都继承了Iterable接口,所以他的实现类必须要实现iterator()方法。

    以后如果要访问集合时,就可以通过控制Iterator,向它发送"向前","向后","取当前元素"的命令,来间接遍历整个集合。 

    举例如下:

    public class testSet {  
        public static void main(String args[]) {  
            // HashSet<String> ct = new HashSet<>();  
            // Set<String> ct=new HashSet<>();  
            // List<String> ct=new ArrayList<>();  
            List<String> ct=new LinkedList<>();  
              
            ct.add("abc");  
            ct.add("def");  
              
            for(Iterator<String> myitr = ct.iterator();myitr.hasNext();){  
                System.out.println(myitr.next());  
            }  
      
            Iterator<String> iter = ct.iterator();  
            while (iter.hasNext()) {  
                System.out.println(iter.next());  
            }  
      
            for (String str : ct) {  
                System.out.println(str);  
            }  
        }  
    }

    使用了3种方法进行集合的遍历,输出的结果都是一致的,如下:

    abc
    def
    abc
    def
    abc
    def

    每一种集合类返回的Iterator具体类型可能不同,Array可能返回ArrayIterator,Set可能返回 SetIterator,Tree可能返回TreeIterator,但是它们都实现了Iterator接口,因此,客户端不关心到底是哪种 Iterator,它只需要获得这个Iterator接口即可,这就是面向对象所带来的好处。

    查看各个集合的Iterator具体实现地址如下:

    传送门1、http://blog.csdn.net/mazhimazh/article/details/17759579

    传送门2、http://blog.csdn.net/mazhimazh/article/details/17835467

    继续看Collection接口,源代码如下:

    public interface Collection<E> extends Iterable<E> {  
        // 查找操作  
        int size();  
        Iterator<E> iterator();  
          
        // 判断  
        boolean isEmpty();  
        boolean contains(Object o);  
        boolean containsAll(Collection<?> c);  
          
        // 增加操作  
        boolean add(E e);  
        boolean addAll(Collection<? extends E> c);  
          
        // 删除操作  
        boolean remove(Object o);  
        boolean removeAll(Collection<?> c);// Removes all of this collection's elements that are also contained in the specified collection (optional operation).  
        boolean retainAll(Collection<?> c);// Retains only the elements in this collection that are contained in the specified collection (optional operation).  
        void clear();  
          
        // 集合转换  
        Object[] toArray();  
        <T> T[] toArray(T[] a);  
          
        // 提供equals()和hashCode()  
        boolean equals(Object o);  
        int hashCode();  
    }

    Collection接口针对集合定义了一些基本的操作,所以任何一个具体的集合实现类都有含有这些方法。但是由于抽象层次较高,所以一般一个具体 的集合实现类,如ArrayList、HashMap等都不会直接继承这个接口,而是继承这个接口的一些子类来实现。所以说每个集合的具体实现类都直接或 间接继承了这个接口。

    有一类重要的方法还需要说明一下:

    boolean add(E e);  
    boolean addAll(Collection<? extends E> c);  
      
    boolean remove(Object o);  
    boolean removeAll(Collection<?> c);  

    拿add(E e)方法来举例,这个方法将在集合中添加一个新的元素。方法会返回一个boolean,但是返回值不是表示添加成功与否。 仔细阅读doc可以看到,Collection规定:如果一个集合拒绝添加这个元素,无论任何原因,都必须抛出异常。这个返回值表示的意义是add()方 法执行后,集合的内容是否改变了(就是元素有无数量,位置等变化),这是由具体类实现的。即:如果方法出错,总会抛出异常;返回值仅仅表示该方法执行后这 个Collection的内容有无变化。

    观察发现传入的参数有许多都是Collection<?>类型的,这就为各个集合的具体实现类实现相互的操作提供了便利。举个例子:

    LinkedList<String> list=new LinkedList<>();  
      
    list.add("xy");  
    list.add("mn");  
      
    Collection<String> ct=new HashSet<String>();  
    ct.add("abc");  
    ct.add("def");  
    ct.addAll(list);  
      
    Iterator<String> iter=ct.iterator();  
    while(iter.hasNext()){  
        System.out.println(iter.next());  
    }  

    将LinkedList集合中的元素添加到HashSet中,只需要调用addAll()方法即可。最后结果输出如下:
    mn
    abc
    def
    xy

    4、总结

    1 Collection是一个接口,是高度抽象出来的集合,它包含了集合的基本操作和属性。

      Collection包含了List和Set两大分支。
      (01) List是一个有序的队列,每一个元素都有它的索引。第一个元素的索引值是0。
              List的实现类有LinkedList, ArrayList, Vector, Stack。

      (02) Set是一个不允许有重复元素的集合。
              Set的实现类有HastSet和TreeSet。HashSet依赖于HashMap,它实际上是通过HashMap实现的;TreeSet依赖于TreeMap,它实际上是通过TreeMap实现的。

    2 Map是一个映射接口,即key-value键值对。Map中的每一个元素包含“一个key”和“key对应的value”。

       AbstractMap是个抽象类,它实现了Map接口中的大部分API。而HashMap,TreeMap,WeakHashMap都是继承于AbstractMap。
       Hashtable虽然继承于Dictionary,但它实现了Map接口。

    接下来,再看Iterator。它是遍历集合的工具,即我们通常通过Iterator迭代器来遍历集合。我们说Collection依赖于Iterator,是因为Collection的实现类都要实现iterator()函数,返回一个Iterator对象。
    ListIterator是专门为遍历List而存在的。

    再看Enumeration,它是JDK 1.0引入的抽象类。作用和Iterator一样,也是遍历集合;但是Enumeration的功能要比Iterator少。在上面的框图 中,Enumeration只能在Hashtable, Vector, Stack中使用。

    最后,看Arrays和Collections。它们是操作数组、集合的两个工具类。

    易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
    该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!