java集合——集合接口+迭代器接口

【0】README

0.1) 本文描述转自 core java volume 1, 源代码 diy 的, 旨在理解 java集合框架——集合接口+迭代器接口 的相关知识;
0.2) for full source code , please visit https://github.com/pacosonTang/core-java-volume/tree/master/chapter13


【1】将集合的接口与实现分离

1.1)java集合类库将 接口与实现分离。 我们看一下, 队列是如何分离的;
1.2)队列接口指出可以再队列的尾部添加元素, 在队列的头部删除元素, 并可以查找队列中元素的个数;

  • 1.2.1)一个队列接口的最小形式类似下面这样:
interface Queue<E>
{
    void add(E element);
    E remove();
    int size;
}
  • 1.2.2)队列通常有两种实现方式: 一种是使用循环数组, 另一种是使用链表;
    这里写图片描述
  • 1.2.3)每一个实现都可以通过一个实现了 Queue接口的类表示:
class CircularArrayQueue<E> implements Queue<E> // not an actual library class
{
    CircularArrayQueue(int capacity) {...}
    pubilc void add(E element) {...}
    public E remove() {...}
    public int size(){...}
    private E[] elements;
    private int head;
    private int tail;
}
class LinkedListQueue<E> implements Queue<E> // not an actual library class
{
    LinkedListQueue() {}
     pubilc void add(E element) {...}
    public E remove() {...}
    public int size(){...}
    private Link head;
    private Link tail;    
}

Annotation)

  • A1)如果需要使用循环数组队列: 可以使用 ArrayDeque 类;
  • A2)如果需要使用 一个链表队列: 就直接使用 LinkedList 类, 这个类实现了 Queue接口;

1.3)使用接口类型存放集合的引用:

Queue<Customer> expressLane = new CircularArrayQueue<>(100);
expressLane.add(new Customer("Harry"));
  • 1.3.1)只需要对程序的一个地方做出修改,即调用构造器的地方, 如果觉得LinkedListQueue是个更好的选择,将代码修改为:
Queue<Customer> expressLane = new LinkedListQueue<>(100);
expressLane.add(new Customer("Harry")); // nothing changed
  • 1.3.2)因为循环数组要比链表效率高,因此多数人优先选择循环数组;循环数组是一个有界集合, 即容量有限。如果程序中要收集的对象数量没有上限, 就最好使用链表来实现;
  • 1.3.3)在研究API文档时,发现另外一组名字为 Abstract开头的类: 如, AbstractQueue, 这些类是为类库实现者而设计的, 如果想要实现自己的队列类,会发现扩展AbstractQueue类要比实现Queue接口中的所有方法要轻松得多;

这里写图片描述


【2】java类库中的集合接口和迭代器接口

2.1)在java类库中, 集合类的基本接口是 Collection 接口,有两个基本方法:

public interface Collection<E>
{
    boolean add(E element); 
    Iterator<E> iterator();
}

对上述代码的分析(Analysis):

  • A1)add方法: 用于向集合添加元素,如果添加元素确实改变了集合就返回ture, 否则返回false;如,如果试图向集合中添加一个对象, 而这个对象在集合中已经存在, 这个添加请求就没有实效了,因为集合中不允许有重复的对象;
  • A2)iterator方法:该方法用于返回一个实现了 Iterator接口的对象 。 可以使用这个迭代器对象依次访问集合中的元素:

2.2)迭代器, Iterator接口包含3个方法:

public interface Iterator<E>
{
    E next();
    boolean hasNext();
    void remove();
}
  • 2.2.1)通过反复调用 next方法, 可以逐个访问集合中的每个元素。但是, 如果达到了集合的末尾,next方法将抛出一个 NoSuchElementException;因此,需要在 next之前调用 hasNext方法;
  • 2.2.2)看个荔枝:
Collection<String> c = ...; //集合
Iterator<String> iter = c.iterator(); //集合迭代器
while(iter.hasNext()) //判断集合中是否还有下一元素
{
    String element = iter.next();
    do sth with element
}

Attention)从Java SE 5.0 后, 可以使用for each循环表示上述操作:

for(String elements : c)
{
    do sth with element
}

这里写图片描述
对上述代码的分析(Analysis):

  • A1)编译器将简单地将 “for each”循环翻译为 带有迭代器的循环;
  • A2)for each循环可以与任何实现了 Iterator接口的对象一起工作,这个接口只包含一个方法:
public interface Iterable<E>
{
    Iterator<E> iterator();
}

Attention)Collection接口扩展了 Iterable 接口, 因此, 对于标准类库中的任何集合都可以使用 for each;

  • 2.2.3)元素被访问的顺序取决于集合类型

    • 2.2.3.1)如果对 ArrayList进行迭代, 迭代器将从索引0开始;
    • 2.2.3.2)如果访问 HashSet中的元素, 每个元素将会按照某种随机的次序出现;虽然可以确定在迭代过程中能够遍历到集合中的所有元素, 但却无法预知元素被访问的顺序;
      这里写图片描述
      Annotation)编程老手会注意到: Iterator接口的next 和 hasNext方法 与 Enumeration 接口的nextElement 和 hasMoreElements 方法的作用一样。 java集合类库的设计者可以选择使用Enumeration接口。但是, 它们不喜欢这个接口累赘的方法名, 于是引入了较短的方法名的新接口;
  • 2.2.4)java迭代器:应该将java迭代器认为是位于 两个元素之间, 当调用next 方法时, 迭代器就越过下一个元素, 并返回刚刚越过的那个元素的引用;

Annotation)有用的类推:可以将Iterator.next 和 InputStream.read 看做是等效的。从数据流中读取一个字节, 就会自动地消耗掉这个字节。下一次调用read将会消耗并返回输入的下一个字节。用同样的方式, 反复地调用next 就可以读取集合中所有元素了;

2.3)删除元素

  • 2.3.1)Iterator的remove方法 将会删除上次调用next方法时返回的 元素。在大多数情况下,在决定删除某个元素前应该先看一下这个元素是很具有实际意义的。然而, 如果想要删除指定位置上的元素, 仍然需要越过这个元素。
  • 2.3.2)看个荔枝(如何删除集合中第一个元素):
Iterator<String> it = c.iterator();
it.next(); // skip over the first element
it.remove(); // now remove it
  • 你要记住, 要删除某个元素, 你得要先越过这个元素;
  • 2.3.3)对next方法 和 remove方法的调用具有相互依赖性。 如果调用 remove 之前没有调用next 将是不合法的。 如果这样做, 会抛出一个 IllegalStateException 异常;
  • 2.3.4)看个荔枝: 如果想删除两个相邻元素, 不能直接这样调用:
it.remove();
it.remove(); //ERROR
正确的方法是:
it.remove();
it.next();
it.remove();

这里写图片描述
这里写图片描述

2.4)泛型使用方法
* 2.4.1)由于Collection 和 Iterator 都是泛型接口,可以编写操作任何集合类型的实用方法。看个荔枝:下面是一个检测任意寄贺卡是否包含指定元素的泛型方法:

public static<E> boolean contains(Collection<E> e, Object obj)
{
    for(E element : c)
        if(element.equals(obj))
            return true;
    return false;
}
  • 2.4.2)事实上, Collection接口声明了很多有用的方法, 所有的实现类都必须提供这些方法, 下面列举了部分:
int size()
boolean isEmpty()
boolean contains(Object obj)
boolean containsAll(Collection<?> c): 这个集合包含other 集合中的所有元素,则返回trueboolean equals(Object other)
boolean addAll(Collection<? extends E> from)
boolean remove(Object obj)
boolean removeAll(Collection<?> c)
void clear() :移除集合中的所有元素;
boolean retainAll(Collection<?> c) : 从集合中移除所有与 other 集合中的元素不同的元素。如果这个调用改变了集合,返回trueObject[] toArray(); : 返回这个集合的对象数组;
<T> T[] toArray(T[] arrtyToFill[]): 返回这个集合的对象数组。如果arrayToFill 数组足够大, 就将集合中的元素填入这个数组中。 剩余空间填补 null ; 否则, 分配一个新数组, 其成员类型与 arrayToFill 的成员类类型相同, 其长度等于集合的大小, 并添入集合元素;
  • 2.4.3)AbstractCollection:当然, 如果实现 Collection接口的每个类都要提供如此多的例行方法是一件很烦人的事情。为了能够让实现者更容易地实现这个接口,java类库提供了一个 类 AbstractCollection , 它将基础方法 size 和 iterator 抽象化了;
    (我的理解为: 抽象类AbstractCollection实现部分的例行方法, 其他可变的方法交给用户去实现)
public abstract class AbstractCollection<E> implements Collection<E>
{
    ...
    public abstract Iterator<E> iterator();
    public boolean contains(Object obj)
    {
        for(E element: this) // calls iterator()
            if(element.equals(obj))
                return true;
        return false;
    }
    ......
}

对以上代码的分析(Analysis):

  • A1)一个具体集合类可以扩展 AbstractCollection类了;
  • A2)现在要由具体的集合类提供 iterator 方法, 而 contains 方法已由 AbstractCollection超类提供了;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值