持有对象
其实这章就是告诉我们如何使用容器 。除了数组外,Java还提供了一套相当完整的容器类来解决这个问题。其中最基本的类型是List 、Set 、Queue 和Map 。这些对象类型也称为集合类 ,但由于Java的类库中使用了Collection 这个名字来指代该类库的一个特殊子集,所以也被称为容器 。 下面是关于容器的关系图:
泛型和类型安全的容器
这一小节主要讲了在使用List和Map这些容器的时候,加上泛型能预防一些类型转换的错误。并且get的时候也不必加上强转的代码。总之好处多多,因为我平常就是这么做的,我也就不记录了。
基本概念
Java容器类类库的用途是保存对象 ,并将其化为两个不同的概念:
Collection 。一个独立元素的序列,这些元素都服从一条或多条规则 。List 必须按照插入的顺序保存元素,而Set 不能有重复元素。Queue 按照排队规则 来确定对象产生的顺序。Map 。一组成对的键值对 对象,允许你使用键来查找值。映射表 允许我们使用另一个对象来查找某个对象,它也被称为关联数组 ,或被称为字典 ,是一个强大的编程工具。
添加一组元素
在java.util 包中的Arrays 和Collections 类中有很多实用方法。下面会举例说明Arrays.asList()、Collections.addAll() 和Collection.addAll() 的用法。
import java.util.*;
public class Test {
public static void main (String args[]) {
Integer[] i = new Integer[] {1 , 2 , 3 , 4 , 5 };
Collection c = new ArrayList<>();
Collection c1 = new ArrayList<>();
c.addAll(c1);
c.addAll(Arrays.asList(i));
c.addAll(Arrays.asList(1 , 2 , 3 , 4 , 5 ));
Collections.addAll(c, i);
Collections.addAll(c, 1 , 2 , 3 , 4 , 5 );
List<Integer> ints = Arrays.asList(i);
ints.set (0 , 50 );
System.out .println(ints);
ArrayList c2 = new ArrayList<StringBuffer>();
c2.add(new StringBuffer("hello" ));
List<StringBuffer> c3 = new ArrayList<>(c2);
System.out .println(c3);
c3.get (0 ).append(" world" );
System.out .println(c2);
}
}
下面是关于asList() 产生的类型的一些要注意的地方:
import java.util.*;
class O { }
class A extends O { }
class B extends O { }
class A1 extends A { }
class A2 extends A { }
class B1 extends B { }
class B2 extends B { }
public class Test {
public static void main(String args[]) {
List <O> ol0 = Arrays.asList(new A(), new B());
List <O> ol1 = Arrays.asList(new A1(), new B1());
List <A> ol2 = Arrays.asList(new A1(), new A2());
List <O> ol4 = new ArrayList<O>();
Collections.addAll(ol4, new A1(), new A2());
List <O> ol5 = Arrays.<O>asList(new A1(), new A2());
}
}
List
List 接口在Collection 的基础上添加了大量的方法,使得可以在List的中间插入和移除元素。有三种常用类型的List:
ArrayList ,底层是用数组存放元素,所以随机访问元素比较快,但是由于插入和移除元素 时需要考虑数组大小和扩容的问题,所以在插入和移除元素 时速度较慢。初始大小为10 ,扩容方式为扩展到1.5 倍,还不够就直接扩容到最大下标值。LinkedList ,底层是用一个内部类(Node) 的对象包含元素实体 和前后元素引用 的形式实现的。所以相对于ArrayList ,它的插入和移除元素 速度较快,但是随机访问元素速度较慢(只能通过第一个元素循环获得下一个元素的位置 )。Vector ,底层也是用数组存放元素,和ArrayList 大同小异( Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe)),接下来不会进行过多分析。大家可以看看这篇文章 。初始大小为10 ,默认扩容为自身2 倍,也有传入每次扩容大小的构造器。不过现在已经过时了,不再推荐使用这个类。下面我总结了一下List中的方法,记录是为了过一遍API,真的要使用可以去查看API(E指泛型 ):
boolean add(E e);void add(int index, E e); 前者调用了后者在末尾插入元素,后者是在指定位置插入元素,使该元素的下标为index。如果该元素后面还有元素,则要依次下标+1(其实是通过System.arraycopy() 生成新数组实现)。这里如果是ArrayList 也可以看出效率不高。boolean addAll(Collection< ? extends E> c);boolean addAll(int index, Collection< ? extends E> c); 和上面类似,c的顺序和迭代器顺序相同。比如插入的是List那就相当于保持原样。void clear() ,移除所有元素。boolean contains(Object o);boolean containsAll(Collection< ?> c) ,ArrayList 和LinkedList 都是通过indexOf() 方法判断是否包含对象,而indexOf() 方法则是通过对象的equals() 方法判断两个对象是否相等。这也意味着一般情况下两个属性完全相同的对象也不会返回false(除非你重写equals()),但是两个实体相同的String对象则会返回true。boolean equals(Object o) 这个方法是继承自AbstractList ,如果o不是List的实现类型返回false,否则对每个元素按下标进行equals() 比较,如果是两个null也被认为是符合的。E get(int index) 这个方法在ArrayList 和LinkedList 中的实现有所不同。在LinkedList 中会先检查下标是否越界,然后判断下标的位置。如果在前半部分,则通过firstNode 往后查询,否则通过lastNode 往前查询。而在ArrayList 就没有那么麻烦了,除了同样需要检查是否越界以外,ArrayList 只需通过下标返回数组中相应的元素就行了。int hashCode() 这个方法是继承自AbstractList 的,简单来说就是对每个元素的hashCode() 进行公式累加(有兴趣可以看看源码),因为其为int 类型,所以可能出现越界返回负值。int indexOf(Object o);int lastIndexOf(Object o); 这个方法比较的方式是调用对象的equals() 方法。所以如果是普通的对象则是用== 去比较。但是类似String类型,就可能重写这个方法。另外还可以传入null ,将会返回从左往右第一个为null 的下标。后者则是从右到左boolean isEmpty() 如果List为空返回true。Iterator< E> iterator() 返回一个迭代器,下一小节细讲。ListIterator< E> listIterator(); ListIterator< E> listIterator(int index); 返回一个列表迭代器,下一小节细讲。E remove(int index);boolean remove(Object o); 这个不多说了,前者返回类型是E,后者如果没有该元素返回false。这两个方法在ArrayList 和LinkedList 中的实现有所不同。前者需要重新生成一个数组,后者只需要修改一下引用就可以了。boolean removeAll(Collection< ?> c) ;boolean retainAll(Collection< ?> c) 前者是移除c中的元素,后者是保留c中的元素。有意思的是在源码中,ArrayList 对这两个方法其实使用了同一个私有方法实现,只是传入的boolean 值有所不同。算法大致是这样的。拿remove 举例,先对原来的Collection进行遍历,如果c不包含该元素,则将该元素设置为原Collection的第0个下标,依次类推。如果中间出现异常的话(我也不知道会出现什么异常),则认为还未遍历的元素都符合并通过System.arraycopy加入原Collection。最后再将后面都置null (见下面代码1 )。而LinkedList 则是则是直接继承AbstractCollection 中的方法,直接利用Iterator的remove() 实现。E set(int index, E element) ; 用指定元素替换列表中指定位置的元素,具体实现的话ArrayList 就是直接换,LinkedList 需要先定位,然后将内部类的item置换成element就行了。int size() 返回列表中的元素数。List< E> subList(int fromIndex, int toIndex) 返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图。Object[] toArray(); < T> T[] toArray(T[] a) 前者没有什么好说的,后者的话用法见代码2 。
private boolean batchRemove (Collection<?> c, boolean complement) {
final Object[] elementData = this .elementData;
int r = 0 , w = 0 ;
boolean modified = false ;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
for (int i = w; i < size; i++)
elementData[i] = null ;
modCount += size - w;
size = w;
modified = true ;
}
}
return modified;
}
public <T> T[] toArray (T[] paramArrayOfT)
{
if (paramArrayOfT.length < this .size) {
return (Object[])Arrays.copyOf(this .elementData, this .size, paramArrayOfT.getClass());
}
System.arraycopy(this .elementData, 0 , paramArrayOfT, 0 , this .size);
if (paramArrayOfT.length > this .size) {
paramArrayOfT[this .size] = null ;
}
return paramArrayOfT;
}
-----------------------------以下是测试代码:
import java.util.*;
public class Test {
public static void main (String args[]) {
String s1 = new String("123" );
ArrayList<String> l = new ArrayList<String>();
l.add(s1);
l.add(s1);
l.add(s1);
String[] i = new String[0 ];
String[] copyi = l.toArray(i);
String[] copyi1 = l.toArray(new String[0 ]);
Object[] copyi3 = l.toArray();
System.out .println(Arrays.toString(copyi));
System.out .println(Arrays.toString(copyi1));
System.out .println(Arrays.toString(copyi3));
System.out .println(Arrays.toString(i));
}
}
--------------------运行结果
[123 , 123 , 123 ]
[123 , 123 , 123 ]
[123 , 123 , 123 ]
[]
List 接口中的方法已经简单说明了。当然ArrayList 和LinkedList 中还有很多额外的方法,此处就不展开了,平常开发中使用List 的方法差不多就够了。不过还是需要知道他们实现的区别。有兴趣可以去看源码。
迭代器
迭代器 是一种设计模式。迭代器 是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不必知道或关心该序列底层的结构。此外,迭代器通常被称为轻量级对象 :创建它的代价小。所以限制也比较多(因为还是借用其底层结构实现了相应的接口方法 ),例如Java的Iterator 只能单向移动。Collection 和Map 接口都是继承自Iterable 接口。这个接口有一个 Iterator< T> iterator(); 方法。所以我们可以对Collection 和Map 的实例通过iterator() 方法获得一个迭代器对象。Iterator 接口一共有三个方法:
boolean hasNext(); E next(); void remove(); 不同类型的实现方式有所不同,这里主要讲一下ArrayList的实现方式,来看一下源代码:
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator <E > {
int cursor;
int lastRet = -1 ;
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked" )
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this .elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1 ;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0 )
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this .remove(lastRet);
cursor = lastRet;
lastRet = -1 ;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
在ArrayList 中是以一个私有内部类的形式,返回一个实现了Iterator 接口的对象。构造方式非常简单,几乎不需要什么代价。在这个内部类中一共有三个属性和三个方法。前面两个看注释也就懂了,expectedModCount 这个属性主要是用来判断在迭代过程中,原集合是否大小进行了修改。这个我没有仔细去解读过,modCount 是AbstractList 的一个属性:protected transient int modCount = 0;
用法大致就是在add和remove的时候自增。三个方法的作用我觉得一目了然,就不再多说了。 ListIterator 是Iterator 的一个子类型。前面我们已经提到List 接口中有两个方法是返回这个类型的。相对于Iterator ,由于它是针对List 对象,所以它的方法也更加丰富。由于其允许双向移动,所以ListIterator< E> listIterator(int index) 允许我们传入一个下标创建一个ListIterator 对象。我们来看看ArrayList 中的源代码:
public ListIterator<E> listIterator() {
return new ListItr(0 );
}
public ListIterator<E> listIterator(int index ) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: " +index );
return new ListItr(index );
}
private class ListItr extends Itr implements ListIterator <E > {
ListItr(int index ) {
super ();
cursor = index ;
}
public boolean hasPrevious() {
return cursor != 0 ;
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1 ;
}
@SuppressWarnings("unchecked" )
public E previous() {
checkForComodification();
int i = cursor - 1 ;
if (i < 0 )
throw new NoSuchElementException();
Object[] elementData = ArrayList.this .elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[lastRet = i];
}
public void set(E e) {
if (lastRet < 0 )
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this .set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public void add(E e) {
checkForComodification();
try {
int i = cursor;
ArrayList.this .add(i, e);
cursor = i + 1 ;
lastRet = -1 ;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
这代码多看几遍就明白了,这里也不想过多解释。新增方法有add(E e),set(E e),previous(),previousIndex(),nextIndex() (感觉是为了对称才出的),hasPrevious()
LinkedList
LinkedList 除了实现List 接口外还实现了Deque 接口,这个接口继承了Queue 接口,所以LinkedList 还实现了可以使其用作栈、队列或双端队列 的方法。这些方法中有些彼此之间只是名称有些差异,或者只存在些许差异,以使得这些名字在特定用法的上下文环境中更加适用。例如getFirst() 和element() 完全一样,都是返回列表中首元素,如果列表为空,则抛出NoSuchElementException 。而peek() 方法也是返回首元素,只不过在列表为空的情况下会返回null 。removeFirst() 和remove() 也是一样的,都是移除首个元素并返回,不存在抛出NoSuchElementException 。poll() 稍有差异,在列表为空的时候返回null 。addLast() 和add() 都是在尾部添加元素,而addFirst() 是在首部添加元素。removeLast() 是移除并返回最后一个元素。
Stack
栈 通常指后进先出的容器。LinkedList 具有能够直接实现栈的所有功能的方法,因此LinkedList 可以直接作为栈来使用。下面是组合LinkedList 创建的一个Stack类。
class MyStack<T> {
private LinkedList<T> storage = new LinkedList<T>();
public void push (T e) {storage.addFirst(e);}
public T peek () {return storage.getFirst();}
public T pop () {return storage.removeFirst();}
public boolean empty () {return storage.isEmpty();}
public String toString () {return storage.toString();}
}
Stack没有被设计成接口,可能是因为Java1.0 中的设计欠佳的java.util.Stack 类占用了这个名字。尽管已经有了java.util.Stack ,但是LinkedList 可以产生更好的Stack (但是LinkedList 不是线程安全的)。java.util.Stack 设计欠佳是因为底层使用的是数组而不是链表,而Stack 需要频繁进栈出栈,所以使用链表更加合理。
import java.util.*;
public class Test {
public static void main(String[] args) {
List<Integer> data1 = new ArrayList<>();
for (int i=0 ; i<10 ; i++) {
data1.add(i);
}
List<Integer> data2 = new ArrayList<>();
for (int i=0 ; i>-10 ; i--) {
data2.add(i);
}
StackInterface<Integer> s1 = new MyStack<Integer>();
PushThread<Integer> pt1 = new PushThread(s1, data1);
PushThread<Integer> pt2 = new PushThread(s1, data2);
pt1.start();
pt2.start();
}
}
interface StackInterface <T > {
public T push(T e);
public T peek();
public T pop();
public boolean empty();
public String toString();
}
class SubStack <T > extends Stack <T > implements StackInterface <T > { }
class MyStack <T > implements StackInterface <T > {
private LinkedList<T> storage = new LinkedList<T>();
public T push(T e) {storage.addFirst(e);return e;}
public T peek() {return storage.getFirst();}
public T pop() {return storage.removeFirst();}
public boolean empty() {return storage.isEmpty();}
public String toString() {return storage.toString();}
}
class PushThread <T > extends Thread {
StackInterface<T> si;
List<T> pushList;
public PushThread(StackInterface<T> si, List<T> pushList) {
this .si = si;
this .pushList = pushList;
}
@Override
public void run() {
for (int i = 0 ; i < pushList.size(); i++) {
T t = si.push(pushList.get(i));
System.out.println("目前: " + si);
}
}
}
class PopThread <T > extends Thread {
StackInterface<T> si;
int popSize;
public PopThread(StackInterface<T> si, int popSize) {
this .si = si;
this .popSize = popSize;
}
@Override
public void run() {
for (int i = 0 ; i < popSize; i++) {
T t = si.pop();
System.out.println("目前: " + si + " pop:" + t);
}
}
}
Set
这里要开始讲另外一个与List 接口平起平坐的Set 接口。不保存重复的元素是Set的最大特点 。Set中最被常使用的是测试归属 性,你可以很容易地询问某个对象是否在某个Set 中。所以查找 就成为了Set中最重要的操作,因此通常会选择HashSet 作为实现,它专门对快速查找做了优化。 其实看了源码你会发现,Set 接口的定义居然和Collection 一模一样,虽然List 接口也是继承Collection 的,但是List至少对Collection 有一些扩展,这个Set 同学就很过分了,完全就是照抄,那为什么需要定义一个Set 接口呢?其实这里只不过是体现了继承和多态的思想。多定义一个Set 接口,以此表名该接口具有的特殊行为。下面先简单介绍一下Set 接口。一共有三个类实现了该接口:
HashSet ,使用了散列 (后面章节介绍),保存顺序与插入顺序无关,查询速度优秀。它借助了HashMap 实现了数据的存储。TreeSet ,将元素存储于红-黑树结构 中,保存顺序与插入顺序无关,但是它会按照一定规则 自动对元素进行排序(构造方法能够传入比较器,关于比较器后期会进行说明)。它借助了TreeMap 实现了数据的存储。LinkedHashSet ,也使用了散列 ,保存的顺序与插入顺序相同,同时查询速度也不赖。它借助了LinkedHashMap 实现了数据的存储。 这位Set 同学不仅照抄Collection ,连下面的实现都是借助了Map,真的是上梁不正下梁歪。等说到Map 了再仔细说说Hash 和Tree 是如何实现的吧。下面是一个简单的应用Set 的例子。
public class Test {
public static void main (String[] args) {
Set<Integer> s1 = new HashSet<Integer>();
Set<Integer> s2 = new TreeSet<Integer>();
Set<Integer> s3 = new LinkedHashSet<Integer>();
test(s1);
test(s2);
test(s3);
}
public static void test (Set s) {
s.add(5 );
s.add(4 );
s.add(1 );
s.add(1 );
s.add(2 );
s.add(3 );
for (int i=6 ; i<30 ; i++) {
s.add(i);
}
System.out .println(s);
}
}
-----------------执行结果:
HashSet:后面元素位置不会保持顺序
TreeSet:按照从小到大排序
LinkedHashSet:与插入顺序相同
Map
其他就不废话,接下来主要说说HashMap 、LinkedHashMap 、TreeMap 是如何实现的。关于Map此处不再展开讨论方法的使用方式,只是简单说一下原理。
HashMap
HashMap 中有一个default属性Entry< K, V>[] table 以此存放元素,初始默认大小为16,阈值为16*0.75=12。当元素大于阈值时会进行扩容,每次扩大2倍。扩容的同时也会对元素进行重排列。这个Entry 是一个内部类,4个属性是K key,V value,int hash,Entry next. 这个hash 值就是通过散列函数计算得来的,而next 就是一种链表的应用。这个table 属性的长度就是HashMap 的最大长度,接下来说明为什么HashMap 的优势是查找速度。当你根据一个key 要插入一个value 时,它会根据key 的hash值(int型) 映射到这个table 对应的下标。然后生成对应的Entry 放入table 中,如果多个key 对应了同个下标,那么table 只会保存最后一个Entry ,而之前的Entry 则以next 属性的形式去维持。这样当我们根据一个key 值要去获得value 时,就不必遍历整个Map 了,我们可以根据hash 值得知相应的table 下标,再用next 去遍历这个链表。
LinkedHashMap
这个类继承于HashMap ,它有一个新的属性Entry header ;这里的Entry 同样也继承了HashMap 中的Entry ,扩展了Entry before 和Entry after ;header 的功能也就是维持一个插入顺序的链表。当每次插入新的元素时,LinkedHashMap 就会使该元素成为header 元素,然后使之前的header 元素成为新header 的after 值。所以就是在HashMap 的基础上再多维持一个链表而已。而生成迭代器时,迭代器的方法会借助这个属性。
TreeMap
TreeMap 的实现借助了红黑树,具体它是如何维持整棵树的顺序,以及add 和remove 后树的调整这里不再展开。简单来说,TreeMap 中维护了一个root 属性,该属性是该树的根节点。当我们要查找一个key时,会根据key的大小不断遍历节点的左子树或右子树(小左右大)。它的查找最多次数即是树的层数,可见其查询速度也是优秀的(TreeSet的查找速度比LinkedList快,比HashSet慢 )。它的最大的特点是有序。我们可以通过后序遍历获得一个按照key 排序的EntrySet 。
Queue
队列 是一个先进先出 的容器。LinkedList 实现了Queue 接口,提供了支持队列行为的方法。这部分就不重新复述了。接下来讲一下Queue 的一个实现类。
PriorityQueue
优先级队列声明下一个弹出元素是最需要的元素(具有最高的优先级 )。当你在PriorityQueue 上调用offer() 方法来插入一个对象时,这个对象会在队列中被排序,由此确保当你调用peek(),poll()或remove() 方法时,获得的元素将是队列中优先级最高的元素。 当我读完定义时,我就想到是否可以借助TreeSet 的有序性实现PriorityQueue 。
import java.util.Comparator;
import java.util.Queue;
import java.util.TreeSet;
class TestObj {
public int pri;
private String key;
public TestObj (int pri, String key) {
this .pri = pri;
this .key = key;
}
public String toString () {
return key;
}
}
public class MyPriQueue<E> extends TreeSet<E> implements Queue<E> {
public static void main (String args[]) {
Queue<TestObj> queue = new MyPriQueue<TestObj>(
new Comparator<TestObj>() {
@Override
public int compare (TestObj o1, TestObj o2) {
return o1.pri - o2.pri;
}
}
);
queue.offer(new TestObj(5 , "5" ));
queue.offer(new TestObj(4 , "4" ));
queue.offer(new TestObj(3 , "3" ));
System.out .println(queue.poll());
queue.offer(new TestObj(6 , "6" ));
System.out .println(queue.poll());
}
public MyPriQueue () {
}
public MyPriQueue (Comparator<? super E> c) {
super(c);
}
public boolean offer (E e) {
return add(e);
}
public E poll () {
return pollFirst();
}
public E element () {
return first();
}
public E peek () {
return first();
}
public E remove () {
return remove();
}
}
JDK 已经实现了PriorityQueue ,他存放元素使用的是Object[] ,并且还有一个 Comparator< ? super E> comparator 。因为像Integer 、String 这些都实现了Comparator 接口,比较方式就是其默认的比较方式,PriorityQueue 的行为除了在add 的时候通过比较器 进行比较获得插入的位置以外,其余的和ArrayList 类似。不过它获取插入的位置的算法特别有意思,并不是简单的维持整个数组有序,它只保持了局部有序,这种方式优化了出队入队的速度。
private void siftUpUsingComparator(int paramInt, E paramE)
{
while (paramInt > 0 )
{
int i = paramInt - 1 >>> 1 ;
Object localObject = this .queue [i];
if (this .comparator.compare(paramE, localObject) >= 0 ) {
break ;
}
this .queue [paramInt] = localObject;
paramInt = i;
}
this .queue [paramInt] = paramE;
}
通过这个方法可以看出,当要插入一个元素时,它会将其与当前数组的(-1 /2)位置进行比较,如此并维护了局部方向上的有序性。而当我们将元素出队列时,会调用下列方法:
private void siftDownUsingComparator(int paramInt, E paramE)
{
int i = this .size >>> 1 ;
while (paramInt < i)
{
int j = (paramInt << 1 ) + 1 ;
Object localObject = this .queue [j];
int k = j + 1 ;
if ((k < this .size) && (this .comparator.compare(localObject, this .queue [k]) > 0 )) {
localObject = this .queue [(j = k)];
}
if (this .comparator.compare(paramE, localObject) <= 0 ) {
break ;
}
this .queue [paramInt] = localObject;
paramInt = j;
}
this .queue [paramInt] = paramE;
}
将元素从0到(*2+1和 *2+2)中的更小的值调换,首先0位置已经是空的了。 根据之前的插入规则,我们可以得出 0 *2+1和 0 *2+2 将是剩余元素中的最小的2个值。设置好0的位置以后,我们还需要将后面的位置也设置好。这个算法有兴趣可以研究一下。
Foreach和迭代器
我们知道数组和实现Collection 的接口类都是直接能够使用foreach 语法的,最重要的原因是Collection 实现了Iterable 接口。以下是我做的一个测试类:
import java.util.*;
public class Test {
public static void main (String args[]) {
Test2<Integer> t = new Test2<Integer>(10 );
t.add(1 );
t.add(2 );
t.add(4 );
t.add(8 );
for (Integer i:t) {
System.out.println(i);
}
}
}
class Test2<T> implements Iterable<T> {
private final Object[] t;
private final int length;
private int size;
public Test2 (int size) {
length = size;
t = new Object[size];
}
public void add (T obj) {
t[size++] = obj;
}
public Iterator<T> iterator () {
return new Iterator<T>() {
private int index = 0 ;
public boolean hasNext () {
return index < length && t[index] != null ;
}
public T next () {
return (T) t[index++];
}
public void remove () {
}
};
}
}
小结
其实只有四种容器:Map 、List 、Set 和Queue ,他们各有两到三个实现版本。看到此处希望你可以再次回味一下这四个接口的用法。