11.3 添加一组元素
在java.util包中的Arrays和Collection类中都有很多实用方讼,可以在一个Collection中添加
一组元素。Arrays.asList()方法接受一个数组或是一个用逗号分割的元素列表(使用可变参数) ,
并将其转换为一个List对象。ColIections.addAlI()方法接受一个Collection对象,以及一个数组或
是一个用逗号分割的列表,将元素添加到Collection中。下面的示例展示了这两个方怯,以及更
加传统addAlI()方法,所有ColIection类型都包含该方法:
package com.cy.container;

import java.util.*;

public class AddingGroups {
  public static void main(String[] args) {
    Collection<Integer> collection = new ArrayList<Integer>(Arrays.asList(1,2,3,4,5));
    Integer[] moreInts = { 6, 7, 8, 9, 10 };
    collection.addAll(Arrays.asList(moreInts));
    
    Collections.addAll(collection, 11,12,13,14,15);
    Collections.addAll(collection, moreInts);
    
    System.out.println(collection);
    
    // Produces a list "backed by" an array:
    List<Integer> list = Arrays.asList(16, 17, 18, 19, 20);
    list.set(1, 99); // OK -- modify an element
    list.add(21);     // Runtime error because the underlying array cannot be resized.
      
  }
}

打印:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 6, 7, 8, 9, 10]
Exception in thread "main" java.lang.UnsupportedOperationException
    at java.util.AbstractList.add(AbstractList.java:148)
    at java.util.AbstractList.add(AbstractList.java:108)
    at com.cy.container.AddingGroups.main(AddingGroups.java:19)
Collection的构造器可以接受另一个Collection ,用它来将自身初始化,因此你可以使用
Arrays.List()来为这个构造器产生输入。但是, Collection.addAlIO方捧运行起来要快得多,而且
构建一个不包含元素的Collection ,然后调用Collections.addAII()这种方式很方便,因此它是首
选方式。
Collection.addAII()成员方法只能接受另一个Collection对象作为参数,因此它不如
Arrays.asList()或Collections.addAII()灵活,这两个方法使用的都是可变参数列表。
你也可以直接使用Arrays.asList()的输出,将其当作List ,但是在这种情况下,其底层表示
的是数组,因此不能调整尺寸。如果你试图用add()或delete()方法在这种列表中添加或删除元素,
就有可能会引发去改变数组尺寸的尝试,因此你将在运行时获得"Unsupported Operation (不支
持的操作)错误。
 
 

 

11.4一些基本类型的容器
package com.cy.container;

//: holding/PrintingContainers.java
// Containers print themselves automatically.
import java.util.*;
import static com.java.util.Print.*;

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) {
    print(fill(new ArrayList<String>()));
    print(fill(new LinkedList<String>()));
    print(fill(new HashSet<String>()));
    print(fill(new TreeSet<String>()));
    print(fill(new LinkedHashSet<String>()));
    print(fill(new HashMap<String,String>()));
    print(fill(new TreeMap<String,String>()));
    print(fill(new LinkedHashMap<String,String>()));
  }
} /* Output:
[rat, cat, dog, dog]
[rat, cat, dog, dog]
[dog, cat, rat]
[cat, dog, rat]
[rat, cat, dog]
{dog=Spot, cat=Rags, rat=Fuzzy}
{cat=Rags, dog=Spot, rat=Fuzzy}
{rat=Fuzzy, cat=Rags, dog=Spot}
*///:~
  ArrayList和LinkedList都是List类型,从输出可以看出,它们都按照被插入的顺序保存元素。
两者的不同之处不仅在于执行某些类型的操作时的性能,而且LinkedList包含的操作也多于
ArrayList。这些将在本章后续部分更详细地讨论。
  HashSet 、TreeSet和LinkedHashSet郁是Set类型,输出显示在Set中,每个相同的项只有保
存一次,但是输出也显示了不同的Set实现存储元素的方式也不同。
  Hashset使用的是相当复杂的方式来存储元素的,这种方式将在第17章中介绍,此刻你只需要知道这种技术是最快的获取
元素方式,因此,存储的顺序看起来并无实际意义(通常你只会关心某事物是否是某个Set的成
员,而不会关心它在Set出现的顺序〉。如果存储顺序很重要,那么可以使用TreeSet ,它按照比
较结果的升序保存对象;或者使用LinkedHashSet ,它按照被添加的顺序保存对象。
  你不必指定(或考虑) Map的尺寸,因为它自己会自动地调整尺寸。Map还知道如
何打印自己,它会显示相关联的键和值。键和值在Map中的保存顺序并不是它们的插入顺序,因为HashMap实现使用的是一种非常快的算法来控制顺序。
  本例使用了三种基本风格的Map: HashMap 、TreeMap和LinkedHashMap。 与HashSet一
样, HashMap也提供了最快的查找技术,也没有按照任何明显的顺序来保存其元素。TreeMap
按照比较结果的升序保存键,而LinkedHashMap则按照插入顺序保存键,同时还保留了
HashMap的查询速度。
 
 
 
11.5 List
有两种类型的List:
• 基本的ArrayList ,它长于随机访问元素,但是在List的中间插入和移除元素时较慢。
• LinkedList ,它通过代价较低的在List中间进行的插入和删除操作,提供了优化的顺序访问。LinkedList在随机访问方面相对比较比较慢,但是它的特性集较ArrayList更大。
在List中提插入元素是可行的,也是这带来了一个问题:
对于LinkedList ,在列表中插入和删除都是廉价操作,但是对于ArrayList ,这可是代价高昂的操作。这是否意味着你应该永远都不要在ArrayList的中间插入元素,并最好是切换到LinkedList? 不,这仅仅意味着,你应该意识到这个问题,如果你开始在某个ArrayList的中间执行很多插入操作,并且你的程序开始变慢,那么你应该看看你的List实现有可能就是罪耻祸首(发现此类瓶颈的最佳方式是使用仿真器)优化是一个很棘手的问题,最好的策略就是置之不顾,直到你发现需要担心它了(尽管理解这些
问题总是一种好的思路)。
 
 
 
11.6迭代器
我们有时会说:选代器统一了对容器的访问方式。
11.6.1 Listlterator
ListIterator是一个更加强大的Iterator的子类型,它只能用于各种List类的访问。尽管
Iterator只能向前移动,但是ListIterator可以双向移动。它还可以产生相对于选代器在列袋中指
向的当前位置的前一个和后一个元素的索引,并且可以使用set()方法替换它访问过的最后一个
元素。你可以通过调用listIterator()方法产生一个指向List开始处的ListIterator,并且还可以通
过调用ListIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator.
package com.cy.container;

//: holding/ListIteration.java
import typeinfo.pets.*;
import java.util.*;

public class ListIteration {
  public static void main(String[] args) {
    List<Pet> pets = Pets.arrayList(8);
    ListIterator<Pet> it = pets.listIterator();
    while(it.hasNext())
      System.out.print(it.next() + ", " + it.nextIndex() +
        ", " + it.previousIndex() + "; ");
    System.out.println();
    
    // Backwards:
    while(it.hasPrevious())
      System.out.print(it.previous().id() + " ");
    System.out.println();
    System.out.println(pets);
    
    it = pets.listIterator(3);
    while(it.hasNext()) {
      it.next();
      it.set(Pets.randomPet());
    }
    System.out.println(pets);
  }
} 
/* Output:
Rat, 1, 0; Manx, 2, 1; Cymric, 3, 2; Mutt, 4, 3; Pug, 5, 4; Cymric, 6, 5; Pug, 7, 6; Manx, 8, 7;
7 6 5 4 3 2 1 0
[Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Manx]
[Rat, Manx, Cymric, Cymric, Rat, EgyptianMau, Hamster, EgyptianMau]
*///:~
Pet.randomPet()方法用来替换在列表中从位置3开始向前的所有Pet对象。
 
 
 
11.7 LinkedList
LinkedList也像ArrayList一样实现了基本的List接口,但是它执行某些操作(在List的中间
插入和移除)时比ArrayList更高效,但在随机访问操作方面却要逊色一些。
LinkedList还添加了可以使其用作栈、队列、或双端队列的方法。
这些方法中有些彼此之间只是名称有些差异,或者只存在些许差异,以使得这些名字在特
定用法的上下文环境中更加适用(特别是在Queue中)。例如, getFirst()和element()完全一样,
它们都返回列表的头(第一个元素) ,而并不移除它,如果List为空,则抛出NoSuchElementException
peek()方法与这两个方式只是稍有差异,它在列表为空时返回null.
removeFirst()与remove()也是完全一样的,它们移除并返回列表的头,而在列表为空时抛出
NoSuchElementException。poll()稍有差异,它在列表为空时返回null。
addFirst()与add()和addLast()相同,它们都将某个元素插入到列表的尾(端)部。
removeLast()移除并返回列表的最后一个元素。
 
package com.cy.container;

//: holding/LinkedListFeatures.java
import typeinfo.pets.*;
import java.util.*;
import static com.java.util.Print.*;

public class LinkedListFeatures {
  public static void main(String[] args) {
    LinkedList<Pet> pets = new LinkedList<Pet>(Pets.arrayList(5));
    print(pets);
    
    // Identical:
    print("pets.getFirst(): " + pets.getFirst());
    print("pets.element(): " + pets.element());
    
    // Only differs in empty-list behavior:
    print("pets.peek(): " + pets.peek());
    
    // Identical; remove and return the first element:
    print("pets.remove(): " + pets.remove());
    print("pets.removeFirst(): " + pets.removeFirst());
    
    // Only differs in empty-list behavior:
    print("pets.poll(): " + pets.poll());
    print(pets);
    
    pets.addFirst(new Rat());
    print("After addFirst(): " + pets);
    
    pets.offer(Pets.randomPet());
    print("After offer(): " + pets);
    
    pets.add(Pets.randomPet());
    print("After add(): " + pets);
    
    pets.addLast(new Hamster());
    print("After addLast(): " + pets);
    
    print("pets.removeLast(): " + pets.removeLast());
  }
} 
/* Output:
[Rat, Manx, Cymric, Mutt, Pug]
pets.getFirst(): Rat
pets.element(): Rat
pets.peek(): Rat
pets.remove(): Rat
pets.removeFirst(): Manx
pets.poll(): Cymric
[Mutt, Pug]
After addFirst(): [Rat, Mutt, Pug]
After offer(): [Rat, Mutt, Pug, Cymric]
After add(): [Rat, Mutt, Pug, Cymric, Pug]
After addLast(): [Rat, Mutt, Pug, Cymric, Pug, Hamster]
pets.removeLast(): Hamster
*///:~
Pets.arrayList()的结果交给了LinkedList的构造器,以便使用它来组装LinkedList。如果
你浏览一下Queue接口就会发现,它在LinkedList的基础上添加了element() 、offer() 、peek() 、
poll()和remove()方法,以使其可以成为一个Queue的实现。Queue的完整示例将在本章稍后
给出。
 
 
 
 
11.8 Stack
"栈"通常是指"后进先出" 的容器。有时栈也被称为叠加栈,因为最后"压入"
栈的元素,第一个"弹出"栈。经常用来类比栈的事物是装有弹簧的储放器中的自助餐托盘,
最后装入的托盘总是最先拿出使用的。
LinkedList具有能够直接实现栈的所有功能的方法,因此可以直接将LinkedList作为栈使用。
不过,有时一个真正的"栈"更能把事情讲清楚:
package com.java.util;

// Making a stack from a LinkedList.
import java.util.LinkedList;

public class Stack<T> {
  private LinkedList<T> storage = new LinkedList<T>();
  
  public void push(T v) { 
      storage.addFirst(v); 
  }
  
  public T peek() {
      return storage.getFirst(); 
  }
  
  public T pop() {
      return storage.removeFirst(); 
  }
  
  public boolean empty() { 
      return storage.isEmpty(); 
  }
  
  public String toString() { 
      return storage.toString(); 
  }
}
这里通过使用泛型,引入了在栈的类定义中最简单的可行示例。类名之后的<T>告诉编译
器这将是一个参数化类型,而其中的类型参数,即在类被使用时将会被实际类型替换的参数,
就是T。 大体上,这个类是在声明"我们在定义一个可以持有T类型对象的Stack 。" Stack是用
LinkedList实现的,而LinkedList也被告知它将持有τ类型对象。注意, push()接受的是T类型的
对象,而peek()和pop()将返回T类型的对象。peek()方法将提供栈顶元素,但是并不将其从栈顶
移除,而pop()将移除并返回栈顶元素。
 
如果你只需要栈的行为,这里使用继承就不合适了,因为这样会产生具有LinkedList的其他
所有方法的类(就象你将在第17章中所看到的, Java1.0的设计者在创建java.utiI.Stack时,就犯
了这个错误)。
 
新的Stack类:
package com.cy.container;

//: holding/StackTest.java
import com.java.util.*;

public class StackTest {
  public static void main(String[] args) {
    Stack<String> stack = new Stack<String>();
    for(String s : "My dog has fleas".split(" "))
      stack.push(s);
    while(!stack.empty())
      System.out.print(stack.pop() + " ");
  }
} 

/* Output:
fleas has dog My
*///:~
如果你想在自己的代码中使用这个Stack类,当你在创建其实例时,就需要完整指定包名,
或者更改这个类的名称;否则,就有可能与java.util包中的Stack发生冲突。例如,如果我们在
上面的例子中导人java.util.*,那么就必须使用包名以防止冲突:
package com.cy.container;

//: holding/StackCollision.java
public class StackCollision {
  public static void main(String[] args) {
    com.java.util.Stack<String> stack = new com.java.util.Stack<String>();
    for(String s : "My dog has fleas".split(" "))
      stack.push(s);
    while(!stack.empty())
      System.out.print(stack.pop() + " ");
    System.out.println();
    
    java.util.Stack<String> stack2 = new java.util.Stack<String>();
    for(String s : "My dog has fleas".split(" "))
      stack2.push(s);
    while(!stack2.empty())
      System.out.print(stack2.pop() + " ");
  }
} 
/* Output:
fleas has dog My
fleas has dog My
*///:~
View Code
这两个Stack具有相同的接口,但是在java.util中没有任何公共的Stack接口,这可能是因为
在Javal.0中的设计欠佳的最初的java.util.Stack类占用了这个名字。尽管已经有了java.util.Stack,
但是LinkedList可以产生更好的Stack ,因此com.java.util.Stack所采用的方式更是可取的。
 
 
 
11.9 Set
package com.cy.container;

import java.util.*;

public class SetOfInteger {
  public static void main(String[] args) {
    Random rand = new Random(47);
    Set<Integer> intset = new HashSet<Integer>();
    for(int i = 0; i < 10000; i++)
      intset.add(rand.nextInt(30));
      System.out.println(intset);
  }
}

//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 16, 19, 18, 21, 20, 23, 22, 25, 24, 27, 26, 29, 28]
View Code
在0到29之间的10000个随机数被添加到了Set中,因此你可以想象,每一个数都重复了许多
次。但是你可以看到,每一个数只有一个实例出现在结果中。
你还可以往意到,输出的顺序没有任何规律可蟹,这是因为出于速度原因的考虑, HashSet
使用了散列…散列将校第17章中介绍。HashSet所维护的顺序与TreeSet或LinkedHashSet都不
同,因为它们的实现具有不同的元素存错方式。TreeSet将无素存储在红…黑树数据结构中,而
HashSet使用的是散列函数。LinkedHashList因为查询速度的原因也使用了散列,但是看起来它
使用了链表来维护无素的插入顺序。
如果你相对结果排序,一种方式是使用TreeSet来代替HashSet:
package com.cy.container;

import java.util.*;

public class SortedSetOfInteger {
  public static void main(String[] args) {
    Random rand = new Random(47);
    SortedSet<Integer> intset = new TreeSet<Integer>();
    for(int i = 0; i < 10000; i++)
      intset.add(rand.nextInt(30));
      System.out.println(intset);
  }
} /* Output:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
*///:~
View Code
你将会执行的最常见的操作之…,就是使用contains()测试Set的归属性,但是还有很多操作
会让你想起在上小学时所教授的文氏图(译者注:用圆表示集与集之间关系的图) :
 

相关文章: