一、容器的打印

    你必须使用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。

    此方法涉及到了线程并发知识,在这里不做详细介绍。