Java集合(1)一 集合框架

目录

Java集合(1)一 集合框架
Java集合(2)一 ArrayList 与 LinkList
Java集合(3)一 红黑树、TreeMap与TreeSet(上)
java集合(4)一 红黑树、TreeMap与TreeSet(下)
Java集合(5)一 HashMap与HashSet

引言

集合在任何语言中都是比较重要的基础知识,不同的集合在实现上采用了各种不同的数据结构,导致了各个集合的性能以及使用方式上存在很大差异,深入了解集合框架的整体结构以及各个集合类的实现原理,并灵活使用各个集合对编码有很大帮助。
本系列文章从集合框架的整体设计到源码细节分析了java.util包下各个集合相关接口、抽象类以及各种常用的集合实现类,希望通过这个系列的文章对大家理解各个集合有一定帮助。
如未做特殊说明,本系列所有源码出自以下JDK环境:

java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

接口框架

一个好的框架在设计上都会考虑将接口与实现分离,java.util也不例外。要快速理解java.util,第一步就是从他的接口设计入手,从接口的整体设计可以快速明确这个框架的作用和功能。

图1 集合接口框架

通过上面的接口框架图可以看出:Collection<E>和Map<K,V>是java.util框架中的两个根接口,代表了两种不同的数据结构:集合和映射表。而List<E>、Set<E>则是继承自Collection<E>下最核心的两个接口,List<E>有序可重复并可以通过整数索引来访问,Set<E>不包含重复元素。下面我们来分别来说下这些核心接口的基本功能。

Collection<E>

public interface Collection<E> extends Iterable<E>

Collection<E>接口是集合的根接口,他代表了一组元素。但是Collection<E>并不关心这组元素是否重复,是否有序。他只提供操作对这组元素的基本操作方法,怎么添加,怎么删除,怎么循环。所有的实现类都必须提供这些方法,下面列出了Collection<E>接口的部分方法:

int size();
boolean contains(Object o);
//Returns an iterator over the elements in this collection.
Iterator<E> iterator();
//Returns an array containing all of the elements in this collection.
Object[] toArray();
//Returns an array containing all of the elements in this collection; the runtime type of the returned array is that of the specified array.
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
void clear();

在Collection<E>接口的方法中有几个需要注意的地方

iterator()

Iterator<E> iterator();

iterator方法返回一个实现了Iterator接口的对象,作用是依次访问集合中的元素,Iterator<E>接口包含3个方法:

boolean hasNext();
E next();
void remove();

通过多次调用next()方法可遍历集合中的所有元素,需要注意的是需要在调用next()之前调用hasNext()方法,并在hasNext()返回true的时候才可以调用next(),例如:

private static void collectionIterator() {
    //不用关注Arrays.asList,只需要知道他能返回一个Collection<E>接口就行
    Collection<String> collection = Arrays.asList("Java", "C++", "Python");
    Iterator<String> iterator = collection.iterator();
    while (iterator.hasNext()) {
        String string = (String) iterator.next();
        System.out.println(string);
    }
}
//output:
//Java
//C++
//Python

从JDK5开始使用“for each”这种更加方便的方式来遍历集合,只要实现了Iterable接口,都可以使用“for each”来遍历,效果和使用iterator一样。Iterable接口只包含一个方法:

Iterator<E> iterator();
private static void foreachCollectionIterator() {
    Collection<String> collection = Arrays.asList("Java", "C++", "Python");
    for (String string : collection) {
        System.out.println(string);
    }
}
//output:
//Java
//C++
//Python

toArray( ) 以及 toArray(T[ ] a)

toArray和toArray(T[ ] a)返回的都是当前所有元素的数组。
toArray返回的是一个Object[]数组,类型不能改变。
toArray(T[ ] a)返回的是当前传入的类型T的数组,更方便用户操作,比如需要获取一个String类型的数组:toArray(new String[0])。

List<E>

public interface List&lt;E> extends Collection&lt;E>

List<E>接口最重要的特点在有序(ordered collection)这个关键字上面,实现这个接口的类可以通过整数索引来访问元素。他可以包含重复的元素。
除了包含Collection<E>接口的所有方法外,还包括跟索引有关的部分方法:

E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
int indexOf(Object o);
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
List<E> subList(int fromIndex, int toIndex);

其中需要引起注意的地方是ListIterator这个类:
List<E>接口在Iterator迭代器的基础上提供了另一个迭代器ListIterator,先来看看ListIterator<E>接口的定义:

public interface ListIterator<E> extends Iterator<E> {
    boolean hasNext();
    E next();
    boolean hasPrevious();
    E previous();
    int nextIndex();
    int previousIndex();
    void remove();
    void set(E e);
    void add(E e);
}

ListIterator<E>接口继承自Iterator<E>接口,所以他们的差异在ListIterator<E>接口新增的功能上:

  • ListIterator<E>可以向后迭代previous()
  • ListIterator<E>可以获取前后索引nextIndex()
  • ListIterator<E>可以添加新值add(E e)
  • ListIterator<E>可以设置新值set(E e)

Set<E>

public interface Set<E> extends Collection<E>

Set<E>接口在方法签名上与Collection<E>接口是一样的,只不过在方法的说明上有更严格的定义,最重要的特点是他拒绝添加重复元素,不能通过整数索引来访问。Set<E>的equals方法定义如果两个集相等是他们包含相同的元素但顺序不必相同。
至于为什么要定义一个方法签名完全重复的接口,我的理解是为了让框架结构更加清晰,将集合从可以添加重复元素和不可以添加重复元素,可以通过整数索引访问和不可以通过整数索引这几点上区别开来,这样当程序员需要实现自己的集合时能够更准确的继承相应接口。

Map<K,V>

public interface Map<K,V>

API说明上关于Map<K,V>的说明非常精炼:从键映射到值的一个对象,键不能重复,每个键至多映射到一个值。
从键不能重复这个特点很容易想到通过Set<E>来实现键,他的接口方法Set<K> keySet()也证明了这点,下面选取了Map<K,V>接口中的一些典型方法:

int size();
boolean containsKey(Object key);
V get(Object key);
V put(K key, V value);
V remove(Object key);
void clear();
//Returns a Set view of the keys contained in this map.
Set<K> keySet();
Returns a Collection view of the values contained in this map.
Collection<V> values();
//Returns a Set view of the mappings contained in this map.
Set<Map.Entry<K, V>> entrySet();
boolean equals(Object o);
int hashCode();

java Set<K> keySet()
返回映射中包含的键集视图,是一个Set<E>,说明了映射中键是不可重复的。
java Collection<V> values()
返回映射中包含的值得集合视图,Collection<E>,说明了映射中值是可以重复的。
java Set<Map.Entry<K,V>> entrySet()
返回映射中包含的映射集合视图,这个视图是一个Set<E>,这是由他的键集不能重复的特点决定的。
entrySet()返回的是一个Map.Entry<K,V>类型的集,Map.Entry<K,V>接口定义了获取键值、设置值的方法,定义如下:

interface Entry<K,V> {
    K getKey();
    V getValue();
    V setValue(V value);
    boolean equals(Object o);
    int hashCode();
}

类框架

从一个框架的接口知道了这个框架的结构和功能,能够用来做什么。但具体怎么使用,怎么扩展,那就需要了解框架中实现这些接口的部分。
java.util提供了一系列抽象类,来实现上面的接口,这些抽象类中提供了大量基本的方法。如果需要实现自己的集合类,扩展这些抽象类比直接继承接口方便的多。

图2 抽象类以及实现类框架

除了图中的抽象类和具体实现类,还有部分历史版本遗留下来的类,包括Vetor,Stack,Hashtable,Properties等,在这里就不做说明,重点关注图中的类即可。

抽象类

需要关注几个关键的抽象类包括AbstractCollection<E>;、AbstractMap<K,V>、AbstractList<E>和AbstractSet<E>,从命名可以看出他们分别实现了Collection<E>、Map<K,V>、List<E>和Set<E>接口。
各个集合的关键区别就在每个集合所使用的数据结构和算法上,所以在抽象类层面都没有涉及具体的数据结构和算法,只对操作这些数据结构的方法做了基本实现。

AbstractCollection<E>

public abstract class AbstractCollection<E> implements Collection<E>

AbstractCollection<E>基本实现了Collection<E>下的所有方法,除了以下几个方法:

public abstract Iterator<E> iterator();

public abstract int size();

public boolean add(E e) {
    throw new UnsupportedOperationException();
}

如果需要实现的是一个不可修改的集合,只需要实现iterator()和size()方法即可,如果需要实现一个可修改的集合,必须重写add(E e)方法。
在AbstractCollection<E>已经实现的方法中可以发现,AbstractCollection<E>所实现的所有方法都是通过Iterator<E>来操作的。

public boolean contains(Object o) {
    //获取迭代器
    Iterator<E> it = iterator();
    if (o==null) {
        while (it.hasNext())
            if (it.next()==null)
                return true;
    } else {
        while (it.hasNext())
            if (o.equals(it.next()))
                return true;
    }
    return false;
}

AbstractList<E>

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>

AbstractList<E>抽象类在AbstractCollection<E>抽象类的基础上添加了专属于List<E>接口的部分方法,但大部分方法都是空方法,没有具体实现。

abstract public E get(int index);

public E set(int index, E element) {
    throw new UnsupportedOperationException();
}

public void add(int index, E element) {
    throw new UnsupportedOperationException();
}

public E remove(int index) {
    throw new UnsupportedOperationException();
}

public Iterator<E> iterator() {
    return new Itr();
}

public ListIterator<E> listIterator() {
    return listIterator(0);
}

public ListIterator<E> listIterator(final int index) {
    rangeCheckForAdd(index);

    return new ListItr(index);
}

没有实现的原因在于AbstractList<E>是一个抽象类,他并没有确定具体的数据结构,当在数据结构没有确定的情况下通过索引去操作一个元素,是直接使用整数索引的方式还是通过迭代器循环遍历的方式来查找具体的位置更方便是不确定的,所以在具体实现上都由他的子类来决定。
值得注意的是AbstractList<E>实现了Iterator以及ListIterator两种类型的迭代器:

private class Itr implements Iterator<E> {
//......
    public E next() {
        checkForComodification();
        try {
            int i = cursor;
            //向后遍历集合,通过get(i)获取当前索引的元素,每次调用之后cursor = i + 1,get(i)为抽象方法
            E next = get(i);
            lastRet = i;
            cursor = i + 1;
            return next;
        } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
        }
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            //通过AbstractList<E>类的remove方法来删除元素,AbstractList<E>中remove(int index)是一个空方法,需要子类来实现
            AbstractList.this.remove(lastRet);
            if (lastRet < cursor)
                cursor--;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException e) {
            throw new ConcurrentModificationException();
        }
    }
}
//......

private class ListItr extends Itr implements ListIterator<E> {
//......
    public E previous() {
        checkForComodification();
        try {
            int i = cursor - 1;
            //向前遍历集合,通过get(i)获取当前索引的元素,每次调用之前cursor - 1,get(i)为抽象方法
            E previous = get(i);
            lastRet = cursor = i;
            return previous;
        } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
        }
    }
//......
}

AbstractSet<E>

public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E>

AbstractSet<E>抽象类在实现上非常简单,只在AbstractCollection<E>抽象类的基础上实现了equal 和 hashCode 方法,但具体的实现还是需要通过contain()方法来判断,由于Set<E>接口类型不考虑元素的顺序,所以只要两个AbstractSet<E>包含相同元素就判断为相等,不需要元素顺序相同,而AbstractList<E>则需要顺序也相同。

//AbstractSet<E> 中的 equals
public boolean equals(Object o) {
    if (o == this)
        return true;

    if (!(o instanceof Set))
        return false;
    Collection<?> c = (Collection<?>) o;
    if (c.size() != size())
        return false;
    try {
        //containsAll在AbstractCollection<E>中已经实现,只要包含所有元素就可以
        return containsAll(c);
    } catch (ClassCastException unused)   {
        return false;
    } catch (NullPointerException unused) {
        return false;
    }
}

//AbstractCollection<E> 中的containsAll
public boolean containsAll(Collection<?> c) {
    for (Object e : c)
        if (!contains(e))
            return false;
    return true;
}

//AbstractList<E> 中的equals
public boolean equals(Object o) {
    if (o == this)
        return true;
    if (!(o instanceof List))
        return false;

    ListIterator<E> e1 = listIterator();
    ListIterator<?> e2 = ((List<?>) o).listIterator();
    //需要两个集合中的元素以及元素顺序都相同才返回true
    while (e1.hasNext() && e2.hasNext()) {
        E o1 = e1.next();
        Object o2 = e2.next();
        if (!(o1==null ? o2==null : o1.equals(o2)))
            return false;
    }
    return !(e1.hasNext() || e2.hasNext());
}

AbstractMap<K,V>

public abstract class AbstractMap<K,V> implements Map<K,V>

AbstractMap<K,V>抽象类中实现了除entrySet()方法外的基本所有方法,其中返回键集的Set<K> keySet()和返回值集的Collection<V> values()在实现上非常有趣,从返回值上看是创建了一个新的集合,但实际实现上是返回来一个实现Set<K>或Collection<V>的类对象,类对象的所有操作都是在原映射表的基础上进行的,这种有趣的操作叫视图,java.util框架中存在大量应用。
这里使用视图的好处在于抽象类中你不需要确定返回的Set<K>或Collection<V>的具体实现类是什么,这样就可以在抽象类中没有决定使用哪种数据结构的时候最大化抽象类的功能,增加扩展的方便性。
keySet()的源码:

public Set<K> keySet() {
    Set<K> ks = keySet;
    if (ks == null) {
        ks = new AbstractSet<K>() {
            public Iterator<K> iterator() {
                return new Iterator<K>() {
                    //获取原映射表的迭代器来实现自己的迭代器
                    private Iterator<Entry<K,V>> i = entrySet().iterator();

                    public boolean hasNext() {
                        return i.hasNext();
                    }

                    public K next() {
                        return i.next().getKey();
                    }

                    public void remove() {
                        i.remove();
                    }
                };
            }

            public int size() {
                //直接操作原映射表的size()方法
                return AbstractMap.this.size();
            }

            public boolean isEmpty() {
                return AbstractMap.this.isEmpty();
            }

            public void clear() {
                AbstractMap.this.clear();
            }

            public boolean contains(Object k) {
                return AbstractMap.this.containsKey(k);
            }
        };
        keySet = ks;
    }
    return ks;
}

总结

java.util这个框架的结构还是非常清晰的,从接口的分类,每个接口的抽象类实现,都很好的保证了框架的伸缩性,为后续的实现和自定义扩展提供了极大地方便。
除了上述的接口以及抽象类以外,java.util框架还提供了一些其他结构,在使用频率上不是太高,比如Queue<E> ,Deque<E> 等。
在后面的章节中我们会详细讲解几个关键集合的实现,从数据结构、算法以及性能等各方面分析孰优孰劣。

时间: 2024-08-28 22:17:02

Java集合(1)一 集合框架的相关文章

Java 集合系列 01 总体框架

java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例 Java集合是java提供的工具包,包含了常用的数据结构:集合.链表.队列.栈.数组.映射等.Java集合工具包位置是java.util.*Java集合主要可以划分为4个部分:List列表.Set集合.Map映射.工具类(Itera

java多线程系类:JUC集合:01之框架

概要 之前,在"Java 集合系列目录(Category)"中,讲解了Java集合包中的各个类.接下来,将展开对JUC包中的集合进行学习.在学习之前,先温习一下"Java集合包".本章内容包括:Java集合包JUC中的集合类 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3498454.html Java集合包 在"Java 集合系列01之 总体框架"中,介绍java集合的架构.主体内容包括Colle

Java集合系列之总体框架

集合--童年的美好时光 集合,忽然让小编想起那段美好的学生时光,集合第一次遇见她的时候,小编当年还是一个懵懂的丫头,也不曾想过会在计算机的世界再次相遇,再回首,集合在数学中是一个基本概念,集合就是"一堆东西",集合里面的"东西"叫做元素,由一个或多个元素所构成的叫做集合,又邂逅,计算机的世界中,集合是一组可变数量的数据项也可能是0个的组合,这些数据项可能共享某些特征,需要以某种操作方式一起进行操作,一般来说,这些数据项的类型都是相同的,或者基类相同(若使用的语言支持

1.熟练的使用Java语言进行面向对象程序设计,有良好的编程习惯,熟悉常用的Java API,包括集合框架、多线程(并发编程)、I/O(NIO)、Socket、JDBC、XML、反射等。[泛型]\

. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用.而线程是在进程中执行的一个任务.Java运行环境是一个包含了不同的类和程序的单一进程.线程可以被称为轻量级进程.线程需要较少的资源来创建和驻留在进程中,并且可以共享进程中的资源. 2. 多线程编程的好处是什么? 在多线程程序中,多个线程被并发的执行以提高程序的效率,CPU不会因为某个线程需要等待资源而进入空闲状态. 3. 用户线程和守护线程有什么区别? 当我们在Java

Java笔记(15):集合框架(01)

1.对象数组的概述和使用 1 package cn.itcast_01; 2 3 public class Student { 4 // 成员变量 5 private String name; 6 private int age; 7 8 // 构造方法 9 public Student() { 10 super(); 11 } 12 13 public Student(String name, int age) { 14 super(); 15 this.name = name; 16 thi

Java中泛型在集合框架中的应用

泛型是Java中的一个重要概念,上一篇文章我们说过,当元素存入集合时,集合会将元素转换为Object类型存储,当取出时也是按照Object取出的,所以用get方法取出时,我们会进行强制类型转换,并且通过代码也可以看出来,我们放入其他类型时,如字符串,编译器不会报错,但是运行程序时会抛出类型错误异常,这样给开发带来很多不方便,用泛型就解决了这个麻烦 泛型规定了某个集合只能存放特定类型的属性,当添加类型与规定不一致时,编译器会直接报错,可以清楚的看到错误 当我们从List中取出元素时直接取出即可,不

JAVA学习第三十六课(常用对象API)- 集合框架(四)— Set集合:HashSet集合演示

随着Java学习的深入,感觉大一时搞了一年的ACM,简直是明智之举,Java里很多数据结构.算法类的东西,理解起来就轻松多了 Set集合下有两大子类开发常用 HashSet集合 .TreeSet集合 Set集合的元素是不重复且无序 一.HashSet集合 API文档解释:此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持.它不保证 set 的迭代顺序:特别是它不保证该顺序恒久不变.此类允许使用null 元素. 此类为基本操作提供了稳定性能,注意,此实现不是同步的. 由上可

【Java基础】04_集合框架

一.java中集合了类的关系图[简化版] 注: 虚线矩形表示接口 实线矩形表示具体实现类 实线矩形加粗表示具体实现类使用频率高,作为重点掌握 集合是存储多个元素的容器,但是,由于数据结构不同,java就提供了多种集合类. 而这多种集合类有共性的功能,所以,通过不断的向上抽取,最终形成了集合体系结构. 二.集合与数组的异同(面试题) 1.相同点 他们都是容器,都可以储存数据. 2.区别 长度区别 数组固定:数组对象创建时,长度就固定 集合可变:集合可以随着存储元素个数的变化,长度发生变化 存储元素

Java笔记(18):集合框架(04)

1.Map集合概述特点及测试 1 package cn.itcast_01; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 6 /* 7 * 作为学生来说,是根据学号来区分不同的学生的,那么假设我现在已经知道了学生的学号,我要根据学号去获取学生姓名,请问怎么做呢? 8 * 如果采用前面讲解过的集合,我们只能把学号和学生姓名作为一个对象的成员,然后存储整个对象,将来遍历的时候,判断,获取对应的名称. 9 * 但是呢,如果我都能把学生

Java笔记(16):集合框架(02)

1.ArrayList存储字符串并遍历 1 package cn.itcast_01; 2 3 import java.util.ArrayList; 4 import java.util.Iterator; 5 6 /* 7 * List的子类特点: 8 * ArrayList: 9 * 底层数据结构是数组,查询快,增删慢 10 * 线程不安全,效率高 11 * Vector: 12 * 底层数据结构是数组,查询快,增删慢 13 * 线程安全,效率低 14 * LinkedList: 15 *