Java集合(二):List列表

Java List 接口与实现
本文介绍了 Java 中 List 接口及其两种实现方式:LinkedList 和 ArrayList 的特点与使用方法。详细探讨了 LinkedList 的双向链表结构和 ArrayList 的动态数组特性。

在上一节中,介绍了Java集合的整体情况。从这节开始,将介绍具体的类。这里不单单介绍类的用法,还会试图从源码的角度分析类的实现。这一节将介绍List接口及实现类,即列表中的链表LinkedList和数组列表ArrayList。

1 List接口及抽象类

List接口扩展自Collection接口,这个接口设计了一些适合列表操作的方法。List是一个有序集合,元素可以添加到容器中某个特定的位置。

使用javac编译List.java源码后,可以使用javap反编译源码获得接口的具体信息,如下是调用后的结果:

  1. Compiled from "List.java"  
  2. public interface java.util.List<E> extends java.util.Collection<E> {  
  3.   public abstract int size();  
  4.   public abstract boolean isEmpty();  
  5.   public abstract boolean contains(java.lang.Object);  
  6.   public abstract java.util.Iterator<E> iterator();  
  7.   public abstract java.lang.Object[] toArray();  
  8.   public abstract <T> T[] toArray(T[]);  
  9.   public abstract boolean add(E);  
  10.   public abstract boolean remove(java.lang.Object);  
  11.   public abstract boolean containsAll(java.util.Collection<?>);  
  12.   public abstract boolean addAll(java.util.Collection<? extends E>);  
  13.   public abstract boolean addAll(int, java.util.Collection<? extends E>);  
  14.   public abstract boolean removeAll(java.util.Collection<?>);  
  15.   public abstract boolean retainAll(java.util.Collection<?>);  
  16.   public void replaceAll(java.util.function.UnaryOperator<E>);  
  17.   public void sort(java.util.Comparator<? super E>);  
  18.   public abstract void clear();  
  19.   public abstract boolean equals(java.lang.Object);  
  20.   public abstract int hashCode();  
  21.   public abstract E get(int);  
  22.   public abstract E set(int, E);  
  23.   public abstract void add(int, E);  
  24.   public abstract E remove(int);  
  25.   public abstract int indexOf(java.lang.Object);  
  26.   public abstract int lastIndexOf(java.lang.Object);  
  27.   public abstract java.util.ListIterator<E> listIterator();  
  28.   public abstract java.util.ListIterator<E> listIterator(int);  
  29.   public abstract java.util.List<E> subList(intint);  
  30.   public java.util.Spliterator<E> spliterator();  
  31. }  
Compiled from "List.java"
public interface java.util.List<E> extends java.util.Collection<E> {
  public abstract int size();
  public abstract boolean isEmpty();
  public abstract boolean contains(java.lang.Object);
  public abstract java.util.Iterator<E> iterator();
  public abstract java.lang.Object[] toArray();
  public abstract <T> T[] toArray(T[]);
  public abstract boolean add(E);
  public abstract boolean remove(java.lang.Object);
  public abstract boolean containsAll(java.util.Collection<?>);
  public abstract boolean addAll(java.util.Collection<? extends E>);
  public abstract boolean addAll(int, java.util.Collection<? extends E>);
  public abstract boolean removeAll(java.util.Collection<?>);
  public abstract boolean retainAll(java.util.Collection<?>);
  public void replaceAll(java.util.function.UnaryOperator<E>);
  public void sort(java.util.Comparator<? super E>);
  public abstract void clear();
  public abstract boolean equals(java.lang.Object);
  public abstract int hashCode();
  public abstract E get(int);
  public abstract E set(int, E);
  public abstract void add(int, E);
  public abstract E remove(int);
  public abstract int indexOf(java.lang.Object);
  public abstract int lastIndexOf(java.lang.Object);
  public abstract java.util.ListIterator<E> listIterator();
  public abstract java.util.ListIterator<E> listIterator(int);
  public abstract java.util.List<E> subList(int, int);
  public java.util.Spliterator<E> spliterator();
}
List接口提供了这些方法,大部分是Abstract的,但也有一部分不是,这部分方法是JDK 1.8 新增的default方法,比如sort方法。

List接口提供了随机访问方法,比如get(int)方法,但是List并不管这些方法都某个特定的实现是否高效。为了避免执行成本较高的随机访问操作,Java SE 1.4 引入了一个标记接口RandomAccess。这个接口没有任何方法,但可以用来检测一个特定的集合是否支持高效的随机访问:

  1. if(c instanceof RandomAccess)  
  2. {  
  3.     use random access algorighm  
  4. }  
  5. else  
  6. {  
  7.     use sequential access algorithm  
  8. }  
if(c instanceof RandomAccess)
{
    use random access algorighm
}
else
{
    use sequential access algorithm
}
ArrayList就实现了这个接口。

List接口中的例行方法在抽象类AbstractList中实现了,这样就不需要在具体的类中实现,比如isEmpty方法和contains方法等。这些例行方法比较简单,含义也明显。对于随机访问元素的类(比如ArrayList),优先继承这个抽象类。

在AbstractList抽象类中,有一个重要的域,叫modCount:

  1. protected transient int modCount = 0;  
protected transient int modCount = 0;

这个域可以用来跟踪列表结构性修改的次数,什么是结构性修改呢?就是改变列表长度的修改,比如增加、删除等。对于只修改某个节点的值不算结构性修改。

这个域在后面的迭代器中非常有用。迭代器可以使用这个域来检测并发修改问题,这个问题会在LinkedList类中介绍。

抽象类AbstractSequentialList实现了List接口中的一些方法,对于顺序访问元素的类(比如LinkedList),优先继承这个抽象类。

2 链表:LinkedList

链表是一个大家非常熟悉的数据结构。链表解决了数组列表插入和删除元素效率太低的问题,链表的插入和删除就非常高效。

链表将每个对象存放在独立的节点中。Java中的LinkedList链表,每个节点除了有后序节点的引用外,还有一个前序节点的引用,也就是说,LinkedList是一个双向链表。

LinkedList类有三个域,分别是大小、头结点和尾节点:

  1. transient int size;  
  2. transient Node<E> first;  
  3. transient Node<E> last;  
transient int size;
transient Node<E> first;
transient Node<E> last;

还有两个构造器,一个无参构造器和一个含参构造器:

  1. public java.util.LinkedList();  
  2. public java.util.LinkedList(java.util.Collection<? extends E>);  
public java.util.LinkedList();
public java.util.LinkedList(java.util.Collection<? extends E>);

其中无参构造器构造一个空的链表,含参构造器根据传进来的一个集合构造一个链表。

2.1 Node<E>内部类

LinkedList类中,定义了一个Node<E>内部类来表示一个节点。这个类的定义如下:

  1. private static class Node<E> {  
  2.     E item;  
  3.     Node<E> next;  
  4.     Node<E> prev;  
  5.   
  6.     Node(Node<E> prev, E element, Node<E> next) {  
  7.         this.item = element;  
  8.         this.next = next;  
  9.         this.prev = prev;  
  10.     }  
  11. }  
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}
这是一个静态内部类,也没有对外部的引用,这个类有三个域:值,前序节点的引用,后序节点的引用,也有一个构造方法,定义很简单。

如果要创建一个Node节点,可以这样:

  1. Node<E> node=new Node<>(pre,item,next);  
Node<E> node=new Node<>(pre,item,next);

其中,pre和next分别是前序节点和后序节点的引用。

2.2 链表操作的基本方法

既然是链表,就少不了链表节点的添加与删除。在LinkedList类中,提供了六个基本的链表操作的方法,这些方法都对链表的结构进行修改,因此会改变AbstractList类中的modCount域,这六个方法如下:

  1. private void linkFirst(E);//在链表头部添加给定值的节点作为头结点  
  2. void linkLast(E);//在链表尾部添加一个给定值的节点作为尾节点  
  3. void linkBefore(E, java.util.LinkedList$Node<E>);//在给定的节点前插入一个节点  
  4. private E unlinkFirst(java.util.LinkedList$Node<E>);//删除头结点,并返回头结点的值  
  5. private E unlinkLast(java.util.LinkedList$Node<E>);//删除尾节点,并返回尾节点的值  
  6. E unlink(java.util.LinkedList$Node<E>);//删除给定的节点  
private void linkFirst(E);//在链表头部添加给定值的节点作为头结点
void linkLast(E);//在链表尾部添加一个给定值的节点作为尾节点
void linkBefore(E, java.util.LinkedList$Node<E>);//在给定的节点前插入一个节点
private E unlinkFirst(java.util.LinkedList$Node<E>);//删除头结点,并返回头结点的值
private E unlinkLast(java.util.LinkedList$Node<E>);//删除尾节点,并返回尾节点的值
E unlink(java.util.LinkedList$Node<E>);//删除给定的节点
这些方法都是私有的(或包内私有的),因此可以称为工具方法,LinkedList类中的所有结构性修改操作都是基于这六个方法实现的。

这六个方法都是链表的基本操作,代码比较简单,不过给出实现可以看看源码实现者的写法,对于自己编程还是有帮助的:

  1. /** 
  2.  * Links e as first element. 
  3.  */  
  4. private void linkFirst(E e) {  
  5.     final Node<E> f = first;  
  6.     final Node<E> newNode = new Node<>(null, e, f);  
  7.     first = newNode;  
  8.     if (f == null)  
  9.         last = newNode;  
  10.     else  
  11.         f.prev = newNode;  
  12.     size++;  
  13.     modCount++;  
  14. }  
  15.   
  16. /** 
  17.  * Links e as last element. 
  18.  */  
  19. void linkLast(E e) {  
  20.     final Node<E> l = last;  
  21.     final Node<E> newNode = new Node<>(l, e, null);  
  22.     last = newNode;  
  23.     if (l == null)  
  24.         first = newNode;  
  25.     else  
  26.         l.next = newNode;  
  27.     size++;  
  28.     modCount++;  
  29. }  
  30.   
  31. /** 
  32.  * Inserts element e before non-null Node succ. 
  33.  */  
  34. void linkBefore(E e, Node<E> succ) {  
  35.     // assert succ != null;  
  36.     final Node<E> pred = succ.prev;  
  37.     final Node<E> newNode = new Node<>(pred, e, succ);  
  38.     succ.prev = newNode;  
  39.     if (pred == null)  
  40.         first = newNode;  
  41.     else  
  42.         pred.next = newNode;  
  43.     size++;  
  44.     modCount++;  
  45. }  
  46.   
  47. /** 
  48.  * Unlinks non-null first node f. 
  49.  */  
  50. private E unlinkFirst(Node<E> f) {  
  51.     // assert f == first && f != null;  
  52.     final E element = f.item;  
  53.     final Node<E> next = f.next;  
  54.     f.item = null;  
  55.     f.next = null// help GC  
  56.     first = next;  
  57.     if (next == null)  
  58.         last = null;  
  59.     else  
  60.         next.prev = null;  
  61.     size--;  
  62.     modCount++;  
  63.     return element;  
  64. }  
  65.   
  66. /** 
  67.  * Unlinks non-null last node l. 
  68.  */  
  69. private E unlinkLast(Node<E> l) {  
  70.     // assert l == last && l != null;  
  71.     final E element = l.item;  
  72.     final Node<E> prev = l.prev;  
  73.     l.item = null;  
  74.     l.prev = null// help GC  
  75.     last = prev;  
  76.     if (prev == null)  
  77.         first = null;  
  78.     else  
  79.         prev.next = null;  
  80.     size--;  
  81.     modCount++;  
  82.     return element;  
  83. }  
  84.   
  85. /** 
  86.  * Unlinks non-null node x. 
  87.  */  
  88. E unlink(Node<E> x) {  
  89.     // assert x != null;  
  90.     final E element = x.item;  
  91.     final Node<E> next = x.next;  
  92.     final Node<E> prev = x.prev;  
  93.   
  94.     if (prev == null) {  
  95.         first = next;  
  96.     } else {  
  97.         prev.next = next;  
  98.         x.prev = null;  
  99.     }  
  100.   
  101.     if (next == null) {  
  102.         last = prev;  
  103.     } else {  
  104.         next.prev = prev;  
  105.         x.next = null;  
  106.     }  
  107.   
  108.     x.item = null;  
  109.     size--;  
  110.     modCount++;  
  111.     return element;  
  112. }  
    /**
     * Links e as first element.
     */
    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

    /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

    /**
     * Inserts element e before non-null Node succ.
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

    /**
     * Unlinks non-null first node f.
     */
    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

    /**
     * Unlinks non-null last node l.
     */
    private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        final Node<E> prev = l.prev;
        l.item = null;
        l.prev = null; // help GC
        last = prev;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

    /**
     * Unlinks non-null node x.
     */
    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }
2.3 列表迭代器:ListIterator接口

链表是一个有序集合,每个对象的位置十分重要。LinkedList.add方法只是将节点加到尾部,然而对于链表的操作还有很大一部分需要将节点添加到链表中间。由于迭代器是秒数集合中的位置的,所以这种依赖位置的添加方法将由迭代器负责。只有对自然有序的集合使用迭代器添加元素才有意义。比如,对于无序的集合set,在Iterator接口中就没有add方法。相反的,在集合类库中提供了ListIterator接口,其中就有add方法:

  1. interface ListIterator<E> extends Iterator<E>  
  2. {  
  3.     void add(E element);  
  4.     ...  
  5. }  
interface ListIterator<E> extends Iterator<E>
{
    void add(E element);
    ...
}
与Collection接口中的add方法不同,这个方法不返回boolean类型的值,因为它假定添加操作总是改变链表。

另外,除了hasNext和next方法,ListIterator接口还提供了下面的两个方法:

  1. E previous();  
  2. boolean hasPrevious();  
E previous();
boolean hasPrevious();
这两个方法用来反向遍历链表,previous也像next一样,返回越过的对象。

LinkedList类的listIterator方法返回一个迭代器对象:

  1. ListIterator<String> iter=list.listIterator();  
ListIterator<String> iter=list.listIterator();
在介绍接口时我们知道,不能实例化一个接口对象,但可以声明一个接口对象然后引用一个实现了该接口的类的实例。那么listIterator方法返回的就必然是一个类的实例,而这个类也必然实现了这个接口,问题是,这个类是什么?

这个类其实是LinkedList的一个内部类,即ListItr:

  1. Compiled from "LinkedList.java"  
  2. class java.util.LinkedList$ListItr implements java.util.ListIterator<E> {  
  3.   private java.util.LinkedList$Node<E> lastReturned;  
  4.   private java.util.LinkedList$Node<E> next;  
  5.   private int nextIndex;  
  6.   private int expectedModCount;  
  7.   final java.util.LinkedList this$0;  
  8.   java.util.LinkedList$ListItr(java.util.LinkedList, int);  
  9.   public boolean hasNext();  
  10.   public E next();  
  11.   public boolean hasPrevious();  
  12.   public E previous();  
  13.   public int nextIndex();  
  14.   public int previousIndex();  
  15.   public void remove();  
  16.   public void set(E);  
  17.   public void add(E);  
  18.   public void forEachRemaining(java.util.function.Consumer<? super E>);  
  19.   final void checkForComodification();  
  20. }  
Compiled from "LinkedList.java"
class java.util.LinkedList$ListItr implements java.util.ListIterator<E> {
  private java.util.LinkedList$Node<E> lastReturned;
  private java.util.LinkedList$Node<E> next;
  private int nextIndex;
  private int expectedModCount;
  final java.util.LinkedList this$0;
  java.util.LinkedList$ListItr(java.util.LinkedList, int);
  public boolean hasNext();
  public E next();
  public boolean hasPrevious();
  public E previous();
  public int nextIndex();
  public int previousIndex();
  public void remove();
  public void set(E);
  public void add(E);
  public void forEachRemaining(java.util.function.Consumer<? super E>);
  final void checkForComodification();
}
上面也是使用javap反编译的结果。可以看到,这个内部类实现了ListIterator接口,并实现了这个接口的方法。

这正是理解迭代器的关键。我们知道,迭代器可以看做是一个位置,这个位置在两个节点的中间,也就是说,对于一个大小为n的链表,迭代器的位置有n+1个:

| a | b | ...| z |

在这个例子中,链表表示26个字母,迭代器的位置就有27个。

这里也是把迭代器形象化为光标,next方法就是光标移到下一个位置,饭后返回刚刚越过的元素,同理previous也是一样,只不过是左移一个位置,然后返回刚刚越过的元素。下面是这两个方法的代码:

  1. public E next() {  
  2.     checkForComodification();  
  3.     if (!hasNext())  
  4.         throw new NoSuchElementException();  
  5.   
  6.     lastReturned = next;  
  7.     next = next.next;  
  8.     nextIndex++;  
  9.     return lastReturned.item;  
  10. }  
  11.   
  12. public E previous() {  
  13.     checkForComodification();  
  14.     if (!hasPrevious())  
  15.         throw new NoSuchElementException();  
  16.   
  17.     lastReturned = next = (next == null) ? last : next.prev;  
  18.     nextIndex--;  
  19.     return lastReturned.item;  
  20. }  
public E next() {
    checkForComodification();
    if (!hasNext())
        throw new NoSuchElementException();

    lastReturned = next;
    next = next.next;
    nextIndex++;
    return lastReturned.item;
}

public E previous() {
    checkForComodification();
    if (!hasPrevious())
        throw new NoSuchElementException();

    lastReturned = next = (next == null) ? last : next.prev;
    nextIndex--;
    return lastReturned.item;
}
这两个方法首先调用checkForComodifcation方法检查并发修改问题。前面说过,AbstractList的modCount记录了链表的修改次数,而每一个迭代器都通过下面的字段维护一个独立的计数器:

  1. private int expectedModCount = modCount;  
private int expectedModCount = modCount;
这个域初始化为类的modCount修改次数。而checkForComodification检查迭代器自己维护的计数器是否和类的modCount相等,如果不等,就会抛出一个ConcurrentModificationException。

并发修改检查通过后,会调用hasNext或hasPrevious方法检查是否有待访问的元素。ListItr类有一个nextIndex域:

  1. private int nextIndex;  
private int nextIndex;
这个域维护迭代器的当前位置,当然,对于LinkedList来说,由于迭代器指向两个元素中间,所以可以同时产生两个索引:nextIndex方法返回下一次调用next方法时返回元素的整数索引;previousIndex返回下一次调用previous方法时返回元素的索引,这个索引比nextIndex小1。

hasNext和hasPrevious方法就是检查nextIndex和previousIndex是否在正确范围来确实是否有待访问元素的。

ListItr类还有两个域:

  1. private Node<E> lastReturned;  
  2. private Node<E> next;  
private Node<E> lastReturned;
private Node<E> next;
lastReturned用来保存上次返回的节点,next就是迭代器位置的下一个元素,也可以看做光标的下一个元素(下一个元素总是光标的右面那个元素)。调用next方法后,光标右移一位,越过next域保存的节点,然后更新这两个域的值,即刚才的next变为lastReturned,next就是再下一个元素,然后nextIndex增1。

previous相对于next操作来说相当于光标左移一位,在更新lastReturned和next时,需要考虑next是否为null。如果next为null,说明在没执行previous时,迭代器在最后一个位置,所以执行previous后,next应该是链表的尾节点last;如果next不是null,那么next更新为next的前序节点。而lastReturned为光标刚越过的元素,即现在的next节点,这时,lastReturned和next节点指向同一个元素。

ListItr类有三个可以修改链表的方法:add、remove和set。其中add和remove会改变迭代器的位置,因为这两个方法修改了链表的结构;而set方法不会修改迭代器的位置,因为它不修改链表的结构。

这三个方法的代码如下:

  1. public void remove() {  
  2.     checkForComodification();  
  3.     if (lastReturned == null)  
  4.         throw new IllegalStateException();  
  5.   
  6.     Node<E> lastNext = lastReturned.next;  
  7.     unlink(lastReturned);  
  8.     if (next == lastReturned)  
  9.         next = lastNext;  
  10.     else  
  11.         nextIndex--;  
  12.     lastReturned = null;  
  13.     expectedModCount++;  
  14. }  
  15.   
  16. public void set(E e) {  
  17.     if (lastReturned == null)  
  18.         throw new IllegalStateException();  
  19.     checkForComodification();  
  20.     lastReturned.item = e;  
  21. }  
  22.   
  23. public void add(E e) {  
  24.     checkForComodification();  
  25.     lastReturned = null;  
  26.     if (next == null)  
  27.         linkLast(e);  
  28.     else  
  29.         linkBefore(e, next);  
  30.     nextIndex++;  
  31.     expectedModCount++;  
  32. }  
public void remove() {
    checkForComodification();
    if (lastReturned == null)
        throw new IllegalStateException();

    Node<E> lastNext = lastReturned.next;
    unlink(lastReturned);
    if (next == lastReturned)
        next = lastNext;
    else
        nextIndex--;
    lastReturned = null;
    expectedModCount++;
}

public void set(E e) {
    if (lastReturned == null)
        throw new IllegalStateException();
    checkForComodification();
    lastReturned.item = e;
}

public void add(E e) {
    checkForComodification();
    lastReturned = null;
    if (next == null)
        linkLast(e);
    else
        linkBefore(e, next);
    nextIndex++;
    expectedModCount++;
}
值得注意的是remove方法。在每次调用remove方法后,都会将lastReturned置为null。也就是说,如果连续调用remove方法,第二次调用就会抛出一个IllegalStateException异常。因此,remove操作必须跟在next或previous操作之后。

现在已经介绍了ListIterator接口的基本方法,可以从前后两个方向遍历链表中的元素,并可以添加、删除元素。

记住一点:链表的任意位置添加与删除节点的操作是ListIterator迭代器提供的,类本身的add方法只能在结尾添加。

2.4 随机访问

在Java类库中,还提供了许多理论上存在一定争议的方法。链表不支持快速随机访问。如果要查看链表中的第n个元素,就必须从头开始,越过n-1个元素,没有捷径可走。鉴于这个原因,在程序需要采用整数索引访问元素时,一般不选用链表。

尽管如此,LinkedList类还提供了一个用来访问某个特定元素的get方法:

  1. LinkedList<String> list=...;  
  2. String s=list.get(n);  
LinkedList<String> list=...;
String s=list.get(n);
当然,这个方法的效率不太高。绝不应该使用这种让人误解的随机访问方法来遍历链表。下面的代码效率极低:

  1. for(int i=0;i<list.size();i++)  
  2. {  
  3.     dosomething with list.get(i);  
  4. }  
for(int i=0;i<list.size();i++)
{
    dosomething with list.get(i);
}

每次查找一个元素都要从头开始重新搜索。LinkedList对象根本不做任何缓存位置信息的处理。

其实,在LinkedList类中,get方法会判断当前的位置距离头和尾哪一端更近,然后判断从左向右遍历还是从右向左遍历。

2.5 例子

下面的代码演示了LinkedList类的基本操作。它简单的创建两个链表,将它们合并在一起,然后从第二个链表中每间隔一个元素删除一个元素,最后测试removeAll方法:

  1. import java.util.*;  
  2. public class LinkedListTest {  
  3.     public static void main(String[] args) {  
  4.         List<String> a=new LinkedList<>();  
  5.         a.add("A");  
  6.         a.add("C");  
  7.         a.add("E");  
  8.   
  9.         List<String> b=new LinkedList<>();  
  10.         b.add("B");  
  11.         b.add("D");  
  12.         b.add("F");  
  13.         b.add("G");  
  14.   
  15.         ListIterator<String> aIter=a.listIterator();  
  16.         Iterator<String> bIter=b.iterator();  
  17.   
  18.         while(bIter.hasNext()){  
  19.             if(aIter.hasNext())aIter.next();  
  20.             aIter.add(bIter.next());  
  21.         }  
  22.         System.out.println(a);  
  23.   
  24.         bIter=b.iterator();  
  25.         while(bIter.hasNext()){  
  26.             bIter.next();  
  27.             if(bIter.hasNext()){  
  28.                 bIter.next();  
  29.                 bIter.remove();  
  30.             }  
  31.         }  
  32.         System.out.println(b);  
  33.   
  34.         a.removeAll(b);  
  35.         System.out.println(a);  
  36.     }  
  37. }  
import java.util.*;
public class LinkedListTest {
    public static void main(String[] args) {
        List<String> a=new LinkedList<>();
        a.add("A");
        a.add("C");
        a.add("E");

        List<String> b=new LinkedList<>();
        b.add("B");
        b.add("D");
        b.add("F");
        b.add("G");

        ListIterator<String> aIter=a.listIterator();
        Iterator<String> bIter=b.iterator();

        while(bIter.hasNext()){
            if(aIter.hasNext())aIter.next();
            aIter.add(bIter.next());
        }
        System.out.println(a);

        bIter=b.iterator();
        while(bIter.hasNext()){
            bIter.next();
            if(bIter.hasNext()){
                bIter.next();
                bIter.remove();
            }
        }
        System.out.println(b);

        a.removeAll(b);
        System.out.println(a);
    }
}
结果如下:


3 数组列表:ArrayList

前面介绍了List接口和实现了这个接口的LinkedList类。List接口用于描述一个有序集合,并且集合中每个元素的位置十分重要。有两种访问元素的协议:一种是用迭代器,另一种使用get和set方法随机访问每个元素。后者不适用于链表,但对数组很有用。集合类库提供了一个大家非常熟悉的ArrayList类,这个类也实现了List接口。ArrayList类封装了一个动态再分配的对象数组。

Java集合类库中还有一个动态数组:Vector类。不过这个类的所有方法是同步的,可以由两个线程安全的访问一个Vector对象。但是,如果一个线程访问Vector,代码要在同步上消耗大量的时间。而ArrayList方法不是同步的,因此,如果不需要同步时使用ArrayList。

ArrayList详解中详细介绍了类的实现及方法的使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值