在写程序时并不知道将需要多少个对象,Java类库提供了一套完整的容器类来解决这个问题。最基本的类型是List、Set、Queue、Map。容器类都可以自动地调整自己的大小,可以将任意数量的对象放置到容器中。
泛型和类型安全的容器
在Java5之前的主要问题是编译器允许你向容器中插入不正确的类型。
import java.util.ArrayList;
class Apple{
private static long counter;
private final long id = counter++;
public long id() { return id;}
}
class Orange{}
public class TestContainer {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
ArrayList apples = new ArrayList();
for(int i = 0; i < 3; i++)
apples.add(new Apple());
apples.add(new Orange()); //添加时不报错
for(int i=0; i < apples.size(); i++)
((Apple)apples.get(i)).id(); //运行时报错
}
}
使用泛型后编译时即报错
public class TestContainer {
public static void main(String[] args) {
ArrayList<Apple> apples = new ArrayList<>();
for(int i = 0; i < 3; i++)
apples.add(new Apple());
apples.add(new Orange()); //编译时报错
for(int i=0; i < apples.size(); i++)
System.out.println(apples.get(i).id());
for(Apple a: apples)
System.out.println(a.id());
}
}
使用泛型后Apple的子类也可以添加到容器中。
基本概念
在java8源码中List声明为
public interface List<E> extends Collection<E>
而Collection接口声明为
public interface Collection<E> extends Iterable<E>
你可以像下面创建一个List:
List<Apple> apples = new ArrayList<Apple>();
注意,ArrayList已经被向上转型为List,这样使用接口的话如果你决定要修改你的实现,只需要在创建时修改它:
List<Apple> apples = new LinkedList<Apple>();
如果需要用LinkedList独有而List接口中没有的方法,就不能将它向上转型为更通用的接口。
public class TestContainer {
public static void main(String[] args) {
Collection<Integer> c = new ArrayList<>();
for(int i = 0; i < 10; i++)
c.add(i); //Autoboxing
for(Integer i : c)
System.out.print(i + ", ");
}
}/* Output:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
*///
添加一组元素
在java.util包中的Arrays和Collections类中都有很多实用的方法。
Arrays.asList()方法接受一个数组或是一个用逗号分隔的元素列表(使用可变参数)
Collections.addAll()方法接受一个Collection对象,以及一个数组或是一个逗号分隔的列表,将元素添加到Collection中。
public class TestContainer {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(16,17,18,19,20);
list.set(1,99); //OK -- modify an element
//list.add(21); //UnsupportedOperationException
}
}
Arrays.asList()方法返回的ArrayList是Arrays类的一个内部类,没有实现add()方法,只重写了set()方法。如果要支持可变,需要
List<Integer> list = new ArrayList<>(Arrays.asList(16,17,18,19,20));
List
ArrayList适用于随机访问元素,但是插入和移除元素较慢
LinkedList通过较低的代价插入和移除元素,但在随机访问方面比较慢
当判断一个元素是否属于一个List,以及移除一个元素都会用到equals()方法。
//以下来自ArrayList.class源码
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
迭代器
使用iterator()要求容器返回一个Iterator,next()获得序列中的下一个元素,hasnext()检查序列中是否还有元素,remove()将迭代器新近返回的元素删除。
Set
Set不保存重复的元素,查找成为了Set中最重要的操作,因此你通常都会选择一个HashSet的实现,它专门对快速查找进行了优化。
Set具有与Collection完全一样的接口,因此没有任何额外的功能,只是行为不同。
public interface Set<E> extends Collection<E>
HashSet输出的顺序没有任何规律所寻,使用了散列。HashSet的add()实现:
public class HashSet<E> extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
...
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
}
调用了HashMap.class的put函数
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}