一、容器的打印
你必须使用Arrays.toString()来产生数组的打印表示,但是打印容器无需任何帮助。下面是一个例子,这个例子中也介绍了一些基本类型的容器:
import java.util.*;
public class PrintingContainers {
static Collection fill(Collection<String> collection) {
collection.add("rat");
collection.add("cat");
collection.add("dog");
collection.add("dog");
return collection;
}
static Map fill(Map<String, String> map) {
map.put("rat", "Fuzzy");
map.put("cat", "Rags");
map.put("dog", "Bosco");
map.put("dog", "spot");
return map;
}
public static void main(String[] args) {
System.out.println(fill(new ArrayList<String>()));
System.out.println(fill(new LinkedList<String>()));
System.out.println(fill(new HashSet<String>()));
System.out.println(fill(new TreeSet<String>()));
System.out.println(fill(new LinkedHashSet<String>()));
System.out.println(fill(new HashMap<String, String>()));
System.out.println(fill(new TreeMap<String, String>()));
System.out.println(fill(new LinkedHashMap<String, String>()));
}
}
这里展示了java容器类库中的两种主要类型,它们的区别在于容器中每个“槽”保存的元素个数。Collection在每个槽中只能保存一个元素。此类容器包括:List,它以特定的顺序保存一组元素;Set,元素不能重复;Queue,只允许在容器的一端插入对象,并从另一端移除对象(对于本例来说,这只是另外一种观察序列的方式,因此并没有展示它)。Map在每个槽内保存了两个对象,即键和与之关联的值。
查看输出会发现,默认的打印行为(使用容器提供的toString()方法)即可生成可读性很好的结果。Collection打印出来的内容用方括号括住,每个元素由逗号分隔。Map则用大括号括住,键与值由等号联系(键在等号左边,值在右边)。
第一个fill()方法可以作用于所有类型的Collection,这些类型都实现了用来添加新元素的add()方法。
ArrayList和LinkedList都是List类型,从输出可以看出,它们都按照被插入的顺序保存元素。两者的不同之处不仅在于执行某些类型的操作时的性能,而且LinkedList包含的操作也多于ArrayList。这些将在以后在详细讨论。
HashSet、TreeSet和LinkedHashSet都是Set类型,输出显示在Set中,每个相同的项只有保存一次,但是输出也显示了不同的Set实现存储元素的方式也不同。HashSet使用的是相当复杂的方式来存储元素的,这种方式以后在介绍,此刻你只需要知道这种技术是最快的获取元素方式,因此,存储的顺序看起来并无实际意义(通常你只会关心某事物是否是某个Set的成员,而不会关心它在Set出现的顺序)。如果存储顺序很重要,那么可以使用TreeSet,它按照比较结果的升序保存对象;或者使用LinkedHashSet,它按照被添加的顺序保存对象。
Map(也被称为关联数组)使得你可以用键来查找对象,就像一个简单的数据库。键所关联的对象称为值。使用Map可以将美国州名与其首府联系起来,如果想知道Ohio的首府,可以将Ohio作为键进行查找,几乎就像使用数组下标一样。正由于这种行为,对于每一个键,Map只接受存储一次。
Map.put(key, value)方法将增加一个值(你想要增加的对象),并将它与某个键(你用来查找这个值的对象)关联起来。Map.get(key)方法将产生与这个键相关联的值。上面的示例只添加了键--值对,并没有执行查找。
注意,你不必指定(或考虑)Map的尺寸,因此它自己会自动地调整尺寸。Map还知道如何打印自己,它会显示相关联的键和值。键和值在Map中的保存顺序并不是它们的插入顺序,因为HashMap实现使用的是一种非常快的算法来控制顺序。
本例使用了三种基本风格的Map:HashMap、TreeMap和LinkedHashMap。与HashSet一样,MashMap也提供了最快的查找技术,也没有按照任何明显的顺序来保存其元素。TreeMap按照比较结果的升序保存键,而LinkedHashMap则按照插入顺序保存键,同时还保留了HashMap的查询速度。
二、List
List承若可以将元素维护在特定的序列中。List接口在Collection的基础上添加了大量的方法,使得可以在List的中间插入和移除元素。有两种类型的List:
- 基本的ArrayList,它长于随机访问元素,但是在List的中间插入和移除元素时较慢。
- LinkedList,它通过代价较低的在List中间进行的插入和删除操作,提供了优化的顺序访问。LinkedList在随机访问方面相对比较慢,但是它的特性集较ArrayList更大。
下面是List提供的所有方法:
public interface List<E> extends Collection<E> {
// 查询操作
// 显示List大小
int size();
// 查看List中元素是否为空
boolean isEmpty();
// 判断集合中是否包含某个对象
boolean contains(Object o);
// 把集合元素放入迭代器中
Iterator<E> iterator();
// 把List转换成Object数组
Object[] toArray();
<T> T[] toArray(T[] a);
// 修改操作
// 添加元素
boolean add(E e);
// 移除元素
boolean remove(Object o);
// 批量修改操作
// 判断List元素是否包含 C
boolean containsAll(Collection<?> c);
// 把C中的元素添加到List中
boolean addAll(Collection<? extends E> c);
// 把c中元素插入到List的指定下标的位置
boolean addAll(int index, Collection<? extends E> c);
// 移除List中包含C中的元素
boolean removeAll(Collection<?> c);
// 取List中元素与c中元素的交集
boolean retainAll(Collection<?> c);
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
// 清空List
void clear();
// 比较和散列
// 判断List是否与o相等
boolean equals(Object o);
// 返回散列码
int hashCode();
// 位置访问操作
// 根据索引获取元素
E get(int index);
// 将List指定下标的元素替换为E
E set(int index, E element);
// 把元素插入到List的指定下标
void add(int index, E element);
// 根据下标移除元素
E remove(int index);
// 搜索操作
// 返回o所在List中的位置
int indexOf(Object o);
int lastIndexOf(Object o);
// 列表迭代器
ListIterator<E> listIterator();
// 把List中指定下标与之后的元素,放入迭代器中
ListIterator<E> listIterator(int index);
// 视图
// 返回List中fromIndex(包含)到toIndex(不包含)的List部分集合
List<E> subList(int fromIndex, int toIndex);
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.ORDERED);
}
}
List接口在jdk 1.8中添加了几个默认方法,如下:
(1)replaceAll(UnaryOperator<E> operator),该方法可以把符合某种规则的元素替换成另一个元素。UnaryOperator为函数式接口,所以该方法可以使用Lambda表达式:
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
/**
* Returns a unary operator that always returns its input argument.
*
* @param <T> the type of the input and output of the operator
* @return a unary operator that always returns its input argument
*/
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
例如,把集合中所有偶数替换成1:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(21);
list.add(11);
list.add(2);
list.add(75);
list.replaceAll(x -> {
if (x % 2 == 0)
return 1;
return x;
});
}
(2)sort(Comparator<? super E> c),以指定的顺序将集合中的元素进行排序:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(21);
list.add(11);
list.add(2);
list.add(75);
list.sort((x, y) -> {
return x - y;
});
System.out.println(list);
}
(3)spliterator()返回一个Spliterator对象。Spliterator是一个可分割迭代器(splitable iterator),可以和iterator顺序遍历迭代器一起看。jdk1.8发布后,对于并行处理的能力大大增强,Spliterator就是为了并行遍历元素而设计的一个迭代器,jdk1.8中的集合框架中的数据结构都默认实现了spliterator。
此方法涉及到了线程并发知识,在这里不做详细介绍。