java编程思想 --11持有对象

基本的集合类:List、Set、Queue和Map。分为Collection(List、Set、Queue)和Map(Map)两个不同的概念。
11.1泛型和类型安全的容器
向上转型也可用在泛型中,即基类可以作为泛型。
11.2基本概念
Collection.addAll():只能接受另外一个Collection对象作为参数
Collections.addAll():接受Collection对象、数组或者一个用逗号分隔的列表作为参数。
Arrays.asList():接受数组或者一个用逗号分隔的列表作为参数(可变参数)。
数组必须用Arrays.toString()来生成可打印表示,但是打印容器无需任何帮助。
11.5List(可以深入到数据结构层次)
List可以将元素维护在特定的序列中。包括ArrayList和LinkedList。
ArrayList:长于随机访问,但是插入和移除元素比较慢
linkedList:通过代价较低的在List中间进行插入和删除操作,提供了优化的访问顺序。随机访问比较慢。
可以用contains()判断某个对象是否在队列中。indexOf()来发现对象所处位置的索引编号。subList()截取片段。retainAll():交集操作,他所产生的行为依赖于equals()方法。set()方法表示替换。
11.6迭代器
真正威力:能够将遍历序列的操作与序列底层的结构分离。iterator的创建时轻量级的。
ListIterator: iterator只能向前移动,而LinkedIterator能双向移动。可以通过ListIterator()方法产生一个指向List开始处的ListIterator,还可以通过ListIterator(n)指向一个索引为n的元素处的ListIterator。可以获得前一个元素和后一个元素的索引,然后通过set()方法来替换访问过的元素。
11.7LinkedList
LinkedList在插入和删除掉的时候要比ArrayList高效 ,但是随机访问要差。
LinkedList添加了可以使其用作栈、队列或者双端队列的方法。
11.8Stack
栈后进先出(LIFO),可以直接将LinkedList作为栈来使用
11.9Set
Set不保存重复的元素,Set最常用的是测试归属性,查找是Set最常用的操作。HashSet是无序的,如果想对结果进行排序,一种方式是用treeSet来替代HashSet。SortedSet set = new treeSet()。
11.10Map
Map数组和其他的Collection一样,可以很容易的扩展到多维,只需要将其值设为Map()。
11.11Queue
队列是先进先出(FIFO)的容器。队列在并发编程中特别重要,因为他可以安全的将一个对象从一个任务传输到另一个任务。LinkedList可以作为Queue的一种实现,通过LinkedList向上转型为Queue,LinkedList实现了Queue接口。
PriorityQueue:PriorityQueue会对入队的元素进行排序,所以在队列顶端的总是最小的元素。Queue<String> pq = new PriorityQueue<String>();


Java容器框架提供了多种不同特性的容器方便我们管理任意数量的对象。

11.1泛型和类型安全的容器

JavaSE5之前的容器允许我们向其中放入不同类型的对象,但是取出的时候需要进行类型强制转换,很容易出现问题。有了泛型之后需要我们只能向集合里添加指定类型及其子类,取出时也不需要类型转换,这个功能是编译器完成的。

11.2基本概念

Java中容器分为2个概念:

1)Collection:一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入顺序保存元素,Set不能有重复元素。Queue按照排队规则确定对象产生的顺序(通常与插入顺序相同)。

2)Map:映射表允许我们使用另一个对象来查找某个对象,它也被称为关联数组。

理想情况下在创建对象时向上转型成接口,方便切换实现,除非需要用到特有方法。

11.3添加一组元素

java.util包里Arrays和Collections类有很多实用方法,可以在一个Collection里添加一组元素。

Arrays.asList()接受一个数组或者可变长参数,转换成一个固定长度(不能add)的list。

Collections.addAll()接受一个Collection对象和一个可变长参数,向Collection里添加一组元素。

Collection.addAll()只能接收一个Collection对象,没有上面灵活。

首选:Collections.addAll()

注意Arrays.asList()对产生的List类型做了最理想的假设:即最近公共父类,这可能会产生问题。

比如:父类Person,一级子类Man,二级子类WhiteMan、BlackMan

List<Person> list = Arrays.asList(new WhiteMan(),new BlackMan());

这时会编译出错,因为编译器找到理想的类型是List<Man>而不是List<Person>,我们需要手动指定asList类型:

List<Person> list = Arrays.<Person>asList(new WhiteMan(),new BlackMan());

使用Collections.addAll()不需要指定类型,因为第一个参数里已经有类型了。

11.4容器的打印

容器类不需要做特殊处理即可直接打印,并且可以生成格式很好的输出。

11.5List

List可以把元素维护在特定的序列里,在Collection基础上添加了很多操作index的方法,如获取,设置,插入,删除。contains()/remove()/indexOf()等行为是根据equals()结果来的。

ArrayList提供高效访问,低效插入删除。LinkedList相反,一般情况使用ArrayList除非进行大量插入删除使用ArrayList影响性能了才改用LinkedList。

subList()可以返回大List里的一个子List,相当于对象引用copy了一份,所以list.containsAll(subList)是true,对subList元素的操作也会影响原来的list。

retainAll()交集操作,保留两个list里的共同元素,基于equals()。

其他方法...

11.6迭代器

任何容器都必须有某种方式可以插入元素并把它取回。对于List可以用get取出,如果从更高层次思考,会发现要使用容器,必须对容器的确切类型编程。如果我们可以使得对List使用的代码也可以对Set使用,就会非常方便。迭代器Iterator就是用于这种目的,使得我们不需要关心序列的底层结构就可以使用next()、hasNext()、remove()来遍历或删除序列中的元素。如果只是遍历不需要修改List,使用foreach更加方便。

remove()用了删除最后一次next()得到的元素,所以必须先使用next()取得元素,然后才能remove()

11.6.1ListIterator

是一个更加强大的Iterator子类型,但只能用于List类型的访问。允许进行双向(前后)访问,可以使用set替换被访问的最后一个元素,可以用listIterator(n)重载方法创建一个一开始就指向索引为n的ListIterator.

11.7LinkedList

LinkedList相比ArrayList除了某些操作性能不同之外,还添加了可以使其用作栈,队列或双端队列的方法。很多方法名称不同但是作用差不多,主要用于在不同上下文环境下使用。如element() getFirst(),remove(),removeFirst(),peek(),pop()等。

11.8Stack

后进先出,就像弹夹一样的储存装置。LinkedList有实现栈功能的所有方法,可以直接作为栈使用,但是有时候用一个真正的栈更可以把问题解决清楚优雅。

Java中提供了java.util.Stack类用于模拟一个Stack,但是设计的并不恰当:

1)Stack从Vector继承而来,多了很多不必要的方法,而且有的方法会破坏Stack的规则:如add(index,obj)

2)Vector是数组实现的,在push()pop()时候效率很低,应该使用链式结构

我们可以自己写一个:

public class Stack<T>{

  private LinkedList<T> list = new LinkedList<>();

  public void push(T t){list.addFirst(t);}

  public T peek(){return list.getFirst();}

  public T pop(){return list.removeFirst();}

  public boolean empty(){return list.isEmpty();}

  public String toString(){return list.toString();}

}

11.9Set

Set不保存重复的元素,最常用的功能就是测试是否包含对象,因此查找就成为了Set中最重要的操作,HashSet实现专门对快速查找进行了优化。

Set与Collection接口完全一致,只是行为不同(多态的特性)。Set是根据对象的值来决定归属性的。

11.10Map

把对象映射到其它对象的能力是解决很多问题的杀手锏。

遍历时可以返回键的set或者键值对的set

11.11Queue

队列是典型的先进先出FIFO容器,取出顺序与放入顺序一致。是一种可靠的把对象从程序的某个区域传输到另一个区域的途径。在并发编程中特别重要。

LinkedList提供了方法可以支持队列行为,并且也实现了Queue接口,所以它可以用作Queue的一种实现。

Queue<Integer> q = new LinkedList<>();

offer():插入队尾或返回false

peek()/element()返回队头,队列为空时peek返回null,element抛出NoSuchElementException

poll()/remove()返回队头并移除,队列为空时pool返回null,remove抛出NoSuchElementException

11.11.1PriorityQueue

队列规则:给定一组队列元素,确定下一个弹出元素的规则。先进先出是典型的一种队列规则。

优先级队列声明下一个弹出元素是优先级最高的元素。PriorityQueue在JavaSE5开始出现,默认元素顺序是按照自然顺序弹出,可以设置一个Comparator来控制这种优先级。

11.12Collection和Iterator

实现Collection接口就必须提供创建Iterator的方法,保证了通用能力。

11.13ForEach与迭代器

任何实现了Iterable接口的类,都可以在foreach中使用。数组和Collection类都可以使用。但Map不行。

11.13.1适配器方法惯用法

使用不同的方法产生不同的Iterator,用于不同的迭代方式。

11.14总结

Java提供了大量持有对象的方式:

1.数组,保存单一类型对象,可以是多维的,可以保存基本类型,但是容量不可变。

2.Collection保存单一元素,Map保存键值对。有了泛型不需要进行类型转换,不可持有基本类型但是会自动包装来处理,容量可变。

3.List也建立了数字和对象的关联,所以数组和List都是排序好的容器。List能自动扩容

4.大量随机访问用ArrayList,经常中间插入删除用LinkedList。

5.队列以及堆栈行为用LinkedList完成。

6.Map是一种将对象与对象相关联的设计。HashMap用于快速访问,TreeMap保持键始终处于排序状态,没有HashMap快。LinkedHashMap保持元素插入顺序,但是也通过散列提供了快速访问的能力。

7.Set不接受重复元素。HashSet提供最快查询速度,TreeSet保持元素处于排序状态,LinkedHashSet以插入顺序保存元素,查询也快。

8.不应该使用过时的Vector(同步效率慢,扩容时候翻倍ArrayList扩一半),HashTable(同步效率慢),Stack

集合框架图:


作者说的,如果一个程序包含固定数量的且其生命周期都是已知的对象,那么这是一个非常简单的程序。确实,如果数组大小已知,那么就很简单了。

除了数组,Java提供了容器类来holding object。

1)泛型和类型安全的容器
ArrayList,可以自动扩充大小的数组,add插入对象,get访问对象,size查看对象数目。
class Apple{}

public class Box {
    public static void main(String[] args) {
        ArrayList<Apple> a = new ArrayList<Apple>();
        a.add(new Apple());
    }
}

泛型(就是跟在ArrayList后面的那个尖括号指明Apple类型的标识)的添加可以在编译期间防止将错误类型的对象放进容器中。
同样,容器也可以用foreach语法。

2)概念
容器类作用是保存对象。分为两个:
一、Collection,List顺序保存元素,Set不能有重复元素,Queue按照排队来。
二、Map,键值对,通过键找值或者被称为字典。

对了,写了这么多篇,忘记说一件重要的事情了,懂得查API文档,最好看英文版。Collection是什么,类还是接口,一查就知道了。
public interface List<E>
extends Collection<E>


public class ArrayList<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable
这样的关系就出来了。

3)添加一组元素
public class AddGroup {
    public static void main(String[] args) {
        Collection<Integer> c = new ArrayList<Integer>(Arrays.asList(1,2,3,4));
        Integer[] group = {5,6,7,8 };
        c.addAll(Arrays.asList(group));
        System.out.println(c);
        Collections.addAll(c, 9,0);
        System.out.println(c);
    }
}

//[1, 2, 3, 4, 5, 6, 7, 8]
//[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]

Collections.addAll,Adds all of the specified elements to the specified collection.
将其他元素添加到Collection c中,而Collection.addAll是添加一组元素。

4)容器的打印
其实上面的代码已经看出,具体的容器已经实现了自己的toString方法。

5)List
List有两种:ArrayList,随机访问元素快,中间插入和删除操作慢。
                     LinkedList,随机访问慢,但是中间插入和删除快,类似链表。

List常用方法:
class Member{
    int age;
    Member(int i){
        age = i;
    }
    public String toString(){
        return "member"+age;
    }
}

public class ListMethod {
    public static void main(String[] args) {
        List<Member> members = new ArrayList<Member>();
        Member member1 = new Member(1);
       
        //添加元素
        members.add(member1);
       
        //判断容器是否为空
        System.out.println(members.isEmpty());
       
        //判断容器是否包含该元素
        System.out.println(members.contains(member1));
       
        //显示索引
        System.out.println(members.indexOf(member1));
       
        //移除元素
        members.remove(member1);
        System.out.println(members);
       
        Member member2 = new Member(2);
        Member member3 = new Member(3);
        Member member4 = new Member(4);
        members.add(member2);
        members.add(member3);
        members.add(member4);
       
        //类似subString,从索引0开始截取到1,包含0和1
        System.out.println(members.subList(0, 2));
       
        //移除 不同于remove
        //removeAll(Collection<?> c)
        //remove(int index)   remove(Object o)
        members.removeAll(members);
        System.out.println(members);
    }
   
}

6)迭代器
在没用迭代器之前,遍历是这样写的:
        for(int i = 0; i < newList.size(); i++){
            System.out.println(newList.get(i));
        }

而迭代器被称为轻量级对象,创建的代价小。
        Iterator<Member> iterator = members.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

next移动下一个元素,但是拿到的当前元素。hasNext检查是否还有元素,Iteratorf容器返回一个Iterator。

7)ListIterator
这个之前没听过,Iterator只能向前移动,ListIterator可以双向移动。
        ListIterator<Member> iterator = members.listIterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
       
        while(iterator.hasPrevious()){
            System.out.println(iterator.previous());
        }

向前输出,向后输出。

8)LinkedList
插入移除高效。
方法很容易理解,属于自解释型的方法名。
public class TestLinkedList {
    public static void main(String[] args) {
        LinkedList<Member> members = new LinkedList<Member>();
        Member member1 = new Member(1);
        Member member2 = new Member(2);
        Member member3 = new Member(3);
        members.add(member1);
        members.add(member2);
        members.add(member3);
       
        //返回列表头
        System.out.println(members.peek());
       
        //移除并返回列表头
        System.out.println(members.removeFirst());
        System.out.println(members);
       
        //返回并移除表头
        System.out.println(members.poll());
        System.out.println(members);
       
        //removelast 移除最后一个
        members.add(member1);
        members.add(member2);
        System.out.println(members.removeLast());
        System.out.println(members);
       
       
        //addLast和add一样 都是往列表尾插入元素 addFirst自然就是表头
        members.add(member2);
        members.addFirst(member2);
        members.addLast(member2);
        System.out.println(members);
    }
}

9)Stack
栈,后进先出,
其实用LinkedList就可以实现栈的功能了,push,进的时候,只需要addFirst,pop,出的时候,只需要removeFirst,这样就达到了先进后出

10)Set
Set,元素不重复。
Set与Collection有完全一样的接口,但是不同List,虽然Set就是Collection,但是行为不同,这就是多态和继承的应用了。
public class TestSet {
    public static void main(String[] args) {
        Set<Integer> set = new HashSet<Integer>();
        Random r = new Random(400);
       
        for(int i = 0;i<20;i++){
            set.add(r.nextInt(300));
        }
        System.out.println(set);
    }
}

HashSet,没有重复元素,顺序也无规律,其实是使用了散列,以后会提到。

如果:
  Set<Integer> set = new TreeSet<Integer>();
会发现是有序的,TreeSet将元素存储在了红黑树里面。
书中有一处错误:LinkedHashSet写成LinkedHashList了,它也使用散列,但是看起来使用了链表维护元素插入顺序。


11.1

在第一个例子中使用了@SuppressWarnings注解。它的作用是抑制编译器产生的告警信息。

(1)用于抑制一个类型的警告:

@SuppressWarnings("unchecked")

(2)用于抑制多个类型的警告:

@SuppressWarnings(value = {"unchecked", "rawtypes"})

(3)用于抑制多有类型的警告:

@SuppressWarnings("all")

该注解目标为类、字段、函数、函数形参、构造函数和函数的局部变量。建议注解声明在最接近告警发生的位置。

javac -Xlint 用于在编译程序的过程中进行更细节的额外检查。

javac带有-X表示非标准选项(不带-X自然是标准选项了),表示当前版本支持,未来不一定支持的选项。通过javac -X可查看当前版本支持的非标准选项。

11.2

Java 容器划分成两个不同的概念:

(1)Collection。一个独立元素的序列,这些元素都服从一些规则。例如,List必须按顺序插入,Set不能有重复的元素,Queue是队列;

(2)Map。一组键值对对象,允许使用键来查找值。

11.3

本小节主要介绍了四种得到列表的方法:

(1)Collection的构造函数可以接受另一个Collection对象,用它来初始化自身;

(2)Arrays.asList()方法的参数可以是数组或变长参数,返回的是List对象,但是在这种情况下,其底层表示的是数组,因此不能调整尺寸,当调用它的add()方法和remove()(此处书中写的是delete()方法,应该是笔误)方法时会抛出Unsupported Operation异常;

(3)Collections.addAll()方法的参数是一个Collection对象、数组或变长参数,将元素添加到Collection对象中;

(4)Collection.addAll()(注意这里是Collection不是Collections)只能接口另一个Collection对象作为参数,这种方法不如(2)和(3)灵活,但是这种方法速度很快,当复合条件时它为首选。

本小节的最后,作者举例说明了Arrays.asList()可能会遇到的问题和解决的方法。当将Light和Heavy类型的对象作为参数传入Arrays.asList()方法中时,编译器会认为方法返回的类型是List,将其赋值给一个List的引用就会报错,解决方法是给Arrays.asList()方法添加显示类型Arrays.asList()。

经过编码测试这个问题在1.8中得到了解决,但是1.7之前的版本依旧存在这个问题。

11.4

如果想要打印数组的内容,必须将其作为参数传递给Arrrays.toString()方法,但是容器不需要类似的帮助,它的toString()直接可以打印出容器的内容。

Collection容器主要包括:

(1)List,它以特定的顺序保存一组元素。ArrayList和LinkedList是List的两个主要类型。ArrayList长于随机访问元素,但是在中间插入和移除元素较慢。LinkedList优化了顺序访问,并且在List中间进行插入和删除操作代价较低,但是随机访问较慢,并且它的特性比ArrayList更大。

(2)Set,它的元素不能重复。HashSet、TreeSet和LinkedHashSet是主要的Set类型。通过将它们的内容打印出来可以看出,它们实现存储元素的方式不同。HashSet使用较复杂的存储方法以提供最快的获取元素方式;TreeSet按照比较结果的升序保存对象;LinkedHashSet按照添加的顺序保存对象。

(3)Queue,它只允许在一端插入对象,在另一端移除对象。

HashMap、TreeMap和LinkedHashMap是Map的三种主要类型,主要区别于 HashSet、TreeSet和LinkedHashSet相同。

11.5

例子中总结了一些List的方法:

可以使用contains()方法来确定某个对象是否在列表中,如果想移除一个对象,则可以将这个对象的引用传递给remove()方法,无此对象会导致删除失败,方法返回false。如果有一个对象的引用,可以使用indexOf()方法来发现对象在List中的索引号,无此对象返回-1。

注意:确定一个元素是否属于List、发现某个元素的索引以及从List中移除一个元素,会用到equals()方法。所以List的这些行为会根据持有对象的equals()行为不同有所变化。也可以使用索引来移除List中的一个元素,这样就不用担心equals()方法。

可以在List中间插入,ArrayList中间插入是很昂贵的,而LinkedList中间插入是很廉价的。

containsAll()判断不在乎顺序。

shuffle()方法的作用是随机打乱原来的元素顺序(洗牌)。

retainAll()仅在List中保留指定collection中所包含的元素。

set()方法替换掉指定索引位置的元素。

addAll()不指定位置将添加到list最后,如果指定位置可以插入到List中间。

toArray()方法将Collection转换为数组。无参数版本返回的是Object数组,有参数的版本可以向其传递目标类型的数组,那么它将产生指定类型的数组(而不是Object数组)。如果参数数组太小,存放不下目标类型的数据,那么它将创建合适尺寸的数组。

11.6

迭代器通常被称为轻量级对象,创建它的代价非常小。

Iterator的作用(它只能单向移动):

(1)使用容器的iterator()方法要求容器返回一个Iterator。Iterator将准备好返回序列的第一个元素;

(2)使用next()获得序列中的下一个元素;

(3)使用hasNext()检测序列中是否还有元素;

(4)使用remove()将迭代器新近返回的元素删除。这里需要注意,迭代器真的可以删除容器里的元素,但是注意调用remove()之前必须调用next()方法。remove()方法是一个可选的方法,即不是所有的Iterator实现都必须实现该方法。(为什么它是可选的?我看API remove()方法如果迭代器不支持 remove 操作会抛出 UnsupportedOperationException  。)

迭代器其实是一种设计模式,使用它可以不必知道容器的确切类型。这样就能够将遍历序列的操作与序列底层的结构分离。

ListIterator是Iterator的子类型,它更强大,但是它只能用于各种List类的访问。它可以双向移动。

nextIndex()对 next 的后续调用所返回元素的索引。(如果列表迭代器在列表的结尾,则返回列表的大小);

previousIndex()返回对 previous 的后续调用所返回元素的索引。(如果列表迭代器在列表的开始,则返回 -1);

可以使用set()方法替换访问过的最后一个元素;

调用listIterator()方法产生一个指向List开始处的ListIterator,也可以调用listIterator(int n)方法创建一个开始就指向 索引为n的元素处的ListIterator。

11.7

LinkedList在实现了基本的List接口的功能之上还添加了可以使其用作栈、队列或双端队列的方法。

有些方法并没有差别、有些只有细微的差别,它们有不同的名字主要是因为这些名字在特定的环境中更加 合适。例如:

getFirst()、element()和peek()都返回列表的第一个元素且不删除它们,如果列表为空前两个方法会抛出NoSuchElmentException,peek()方法则会返回null;

removeFirst()、remove()和poll()方法都是移除并返回列表的头,如果列表为空前两个会抛出NoSuchElementException,poll()方法会返回null;

addFirst()与add()和addLast()相同,都将某个元素 插入到列表的尾部;

removeLast()移除并返回列表的最后一个元素;

offer()将指定元素添加的列表的末尾。

练习13上一段有句话翻译有误,原话为“如果你浏览一下Queue接口就会发现,它在LinkedList的基础上添加了element()、offer()、peek()、poll()和remove()方法,以使其可以成为一个Queue的实现。”翻了一下英文版,认为应该翻译为:“如果你浏览一下Queue接口就会发现,element()、offer()、peek()、poll()和remove()方法被添加到LinkedList类中使其成为一个Queue接口的实现。

11.8

作者使用代理用LinkedList实现了一个栈,这里使用代理而不是继承LinkedList是因为LinkedList还有许多与栈无关的方法,使用代理可以屏蔽掉。一个栈需要的方法主要有push(T t)入栈、poll()出栈和peek()返回栈顶元素但不删除,另外还需要有一个empty()方法判断栈是否为空。

Java在早期版本中就已经有了栈java.util.Stack,但是由于设计糟糕,现在已经不推荐使用了,还有Vector、Dictionary等。

11.9

TreeSet的构造函数允许传入一个比较器(Comparator) 作为参数用于给元素排序

在最后一个例子中作者为字符串不区分大小写排序传入了String.CASE_INSENSITIVE_ORDER比较器。

11.10

对象可以返回它的键的Set——keySet(),返回值的Collection——values(),返回键值对的Set——entrySet()。

11.11

如果想使用队列,可以创建LinkedList对象并向上转型为Queue,这样Queue接口窄化了LinkedList的访问权限。

offer()方法在允许的情况下将元素插入到队尾,peek()和element()在不删除元素的情况下返回队头,如果队列为空前者返回Null,而后者抛出NoSuchElementException异常。poll()和remove()方法返回队头并删除元素,队列为空时前者返回Null,后者抛出异常。

Java 1.5添加了PriorityQueue类,在这个队列中,不是根据插入的时间来弹出元素,而是根据元素的优先级,优先级高的先弹出。当用offer()向PriorityQueue插入对象后,它会在队列中根据自然顺序进行排序,可以向PriorityQueue传入自定义的Comparator来改变默认的顺序。

练习29让我们创建一个继承自Object的类,然后把这个类的实例添加到PriorityQueue队列中,这样会抛出异常,因为这个类没有实现Comparable接口,PriorityQueue没有办法判断元素的优先级。

11.12

首先介绍了AbstractCollection抽象类,它实现了Collection接口,只有iterator()和size()方法是抽象方法,继承时需要实现。另外需要注意的是AbstractCollection的add()方法虽然被实现了,但是函数里仅有一句代码——抛出UnSupportedOperationException异常。所以继承AbstractCollection时还要覆盖add()方法。

11.13

Java 1.5添加了Iterable接口,它的iterator()方法返回一个迭代器,注意到Collection接口也继承了Iterable接口。实现Iterable接口的类都可以应用在foreach语句中,同时数组也可以应用于foreach语句,但是注意数组并没有实现Iterable接口。

System.getEnv()方法返回操作系统的环境变量。

例子中使用了设计模式中的适配器模式,它的主要功能是将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。

例子中着重提到了Arrays.asList()方法,如果修改方法返回的List,那么作为参数传入的数组也将会被修改,此种情况容易被忽略。

11.14

新程序中不应该再使用已经过时的Vector、Hashtable和Stack。

下图是容器相关的接口和类之间的关系:



总结起来共有四种容器:List、Set、Queue和Map。每一种各有两三个实现。常用的在图中用粗线框表示,虚线框表示接口,实线框表示类。空心虚线箭头接口或者类实现了上层接口,实心虚线箭头表示某个类可以生成箭头所指向的类。

)基本概念
/*
* P218
*/
import java.util.*;
class Apple{
private static long counter;
private final long id =counter++;
public long id(){return id;}
}
class GrannySmith extends Apple{}
class Gala extends Apple{}
class Fuji extends Apple{}
class Braeburn extends Apple{}
public class GenericsAndUpcasting {
public static void main(String[] args){
ArrayList<Apple> apples = new ArrayList<Apple>();
apples.add(new GrannySmith());
apples.add(new Gala());
apples.add(new Fuji());
apples.add(new Braeburn());
for(Apple c : apples)
System.out.println(c);
}
}
在ArrayList中,尖括号括起来的是类型参数()可以有多个,它指定了这个容器实例可以保存的类型。通过使用泛型,就可心在编译斯防止将错误类型的对象放置到容器中。
如果不需要使用每个元素的索引,可以使用foreach语法来选择List中的每个元素。
1)Collection。一个独立元素的序列,这些元素都服从一条或者多条规则。List必须按照插入的顺序保存元素,而Set不能有重复元素。Queue按照排除规则来确定对象产生的顺序(通常与它们被插入的顺序相同)。
2)Map。一组成对的"键值对"对象,允许你使用键来查找值。ArrayList允许你使用数字来查找值,因此在某种意义上讲,它将数字与对象关联在一起。映射表允许我们使用另一个对象来查找某个对象,它也被称为“关联数组”,因为它将某些对象与另外一些对象关联在了一起,这也被称为“字典”。
/*
* P220-221
*/
import java.util.*;
class Snow{}
class Powder extends Snow{}
class Light extends Powder{}
class Heavy extends Powder{}
class Crusty extends Snow{}
class Slush extends Snow{}
public class AsListInference {
public static void main(String[] args){
List<Snow> snow1=Arrays.asList(new Crusty(),new Slush(),new Powder());
//Won't compile:
//List<Snow> snow2 =Arrays.asList( new Light(),new Heavy());
//Compiler says:
// fount :java.util.List<Powder>
// required:java.util.List<Snow>
// Collections.addAll() doesn't get confused:
List<Snow> snow3=new ArrayList<Snow>();
Collections.addAll(snow3, new Light(),new Heavy());
//Give a hint using an
//explicit type argument specification;
List<Snow> snow4=Arrays.<Snow>asList(new Light(),new Heavy());
}
}
Collection.addAll()方法运行起来要快得多,而且构建一个不包含元素的Collection,然后调用Collections.addAll()这种方式很方便,因此它是首选方式。
你也可以直接使用Array.asList()的输出,将其当作List,但是在这种情况下,其底层表示的是数组,因此不能调整尺寸。如果你试图用add()或delete()方法在这种列表中添加或删除元素,就有可能会引发去改变数组尺寸的尝试,这会导致Unsupported Operation错误。
List类型:ArrayList和LinkedList都是List类型。它们都按照被插入的顺序保存元素。
Set类型:HashSet,TreeSet和LinkedHashSet都是Set类型。每个相同的项只有保存一次。HashSet按一种复杂但最快获取元素的方式存储,TreeSet按比较结果升序保存对象,LinkedHashSet按照添加的顺序保存对象。
Map类型::按键值对来存储对象,其键和值都是对象并相关联。
(2)选代器
选代器模式:选代器是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不必知道或关心该序列底层的结构。此外,选代器通常被称为轻量级对象:创建它的代价小。
import java.util.*;
import typeinfo.*;
/*
* P226
*/
public class SimpleIteration {
public static void main(String [] args){
List<Pet> pets=Pets.arrayList(12);
Iterator<Pet> it=pets.iterator();
while(it.hasNext()){
Pet p=it.next();
System.out.print(p.id()+" :"+p+"");
}
System.out.println();
//A simpler approach,when possible;
for(Pet p:pets)
System.out.print(p.id() +" :"+p+" ");
System.out.println();
//an Iterator can also remove elements;
it=pets.iterator();
for(int i=0;i<6;i++){
it.next();
it.remove();
}
System.out.println(pets);
}
}
要注意的是,在此例中使用了foreach语句,该语句可以用于数组或其他任何Iterable,但是这并不意味着数组肯定也是一个Iterable,任何自动包装也不会发生。

11)Map
作者说将对象映射到其他对象的能力是解决编程问题的杀手锏。
确实,例如查看随机数的分布,如果真是随机数的话,那么10000次产生20以内的随机数,每个数字出现的次数应该是相近的。
public class TestMap {
    public static void main(String[] args) {
        Map<Integer,Integer> map = new HashMap<Integer,Integer>();
        Random r = new Random(47);
       
        //map里面为空
        System.out.println(map.get(1));
        for (int i = 0; i < 10000; i++) {
            int j = r.nextInt(10);
            Integer temp = map.get(j);
            map.put( j ,temp == null ? 1 : temp+1);
        }
        System.out.println(map);
    }
}

//result:null
//{0=994, 1=1033, 2=1010, 3=1014, 4=958, 5=1000, 6=1052, 7=980, 8=946, 9=1013}
数字确实是随机分布的,Map也很好用。
Map也用到多维。
Map<Person,List<TV>>;

12)Queue
先进先出,买基金的时候,由于后期追加购买,但是前期的基金已经满1个月,这样不用赎回费,然后问了客服之后发现,先买进的先赎回,然后自己大拍大腿,这不就是所谓的队列设计吗?

LinkedList实现了Queue接口。
对了,经常用到Random,竟然忘了说为什么作者传参用了47,其实Random传参是传入一个计算的种子,默认是系统时间,47在他看来一直是“魔幻数字”。
public class TestQueue {
    public static void main(String[] args) {
        Queue<Integer> q  = new LinkedList<Integer>();
        Random r = new Random(47);
        for (int i = 0; i < 10; i++) {
        //将一个元素插入队尾
            q.offer(r.nextInt(12));
        }
        //返回队头
        System.out.println(q.peek());
        System.out.println(q);
    }
}

一、PriorityQueue
直接看例子:
public class TestPriorityQueue {
    public static void main(String[] args) {
        String s = "What 's your favorite number ,1 or 2?";
          List<String> l = Arrays.asList(s.split(""));
          PriorityQueue<String> pq = new PriorityQueue<String>(l);

          while(pq.peek()!=null){
              System.out.print(pq.remove()+" ");
          }
    }
}

result:
[,  ,  ,  ,  , b,  , 1,  , i, e, n, h, ',  , a, o, 2, ?, y, t, t, o, u, u, m, r, e,
 r, f, ,, s, a, v, r, o, W, r]             
              
          ' , 1 2 ? W a a b e e f h i m n o o o r r r r s t t u u v y


先级最高最先弹出,在优先队列里面 最小的值拥有最高的优先级,如果为String,从上面看,空格优先级最高。
直接输出并不会排序,这和我之前预想的不一样。
要顺序输出,需要用peek方法,返回队头,空返回null,然后remove的时候优先级高的会现出来。
public static void main(String[] args) {
        String s = "werwerwer";
        List<String> l = Arrays.asList(s.split(""));
        PriorityQueue<String> pq = new PriorityQueue<String>(l);
        while (pq.peek() != null) {
            System.out.print(pq.remove() + " ");
        }
        pq = new PriorityQueue<String>(l);
        System.out.println(pq.peek());
        pq.remove();
        System.out.println(pq.peek());
        pq.remove();
        System.out.println(pq.peek());
    }

result:
 e e e r r r w w w
e
e

结果让我奇怪的是String里面有空格。这个看了字符串再回来解决。

13)Foreach与迭代器
foreach遍历:
public class Box {
    public static void main(String[] args) {
        Collection<String> c = new LinkedList<String>();
        String s = "you are so great";
        Collections.addAll(c,s.split(" "));
        for(String string : c){
            System.out.println(string);
        }
    }
}
原来foreach是Java SE5引入的特性,因为同时也引入了Iterable接口,接口产生Iterator的Iterator方法,Iterable被Foreach用来在序列中移动。所以实现Iterable的接口的类都可以用于Foreach语句。

All Known Subinterfaces:
BeanContext, BeanContextServices, BlockingDeque<E>, BlockingQueue<E>,
Collection<E>, Deque<E>, DirectoryStream<T>, List<E>, NavigableSet<E>,
 Queue<E>, Set<E>, SortedSet<E>

其实Iterable的子接口有这么多,接下来实现Collection接口的自然也就实现了Iterable接口,所以也适用。

总结:
先看容器分类图:

点线为接口,实线为具体类,空心箭头指实现接口,实心箭头指某个类可以生成箭头所指向的类的对象。

1、数组可以存放对象,存放基本类型的数据,可以多维,就是容量不能改变。

2、Collection保存单一的元素,Map保存键值对。

3、List和数组类似,但是List可以自动扩充容量。大量随机访问使用ArrayList,经常进行插入删除操作,用LinkedList。
public class Box {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
       Random r = new Random(47);
       LinkedList<Integer> l = new LinkedList<Integer>();
       for(int i = 0; i< 1000000; i++){
           l.add(r.nextInt(1000));
       }
       long end = System.currentTimeMillis();
       System.out.println(end - start);
      
      ArrayList<Integer> a = new ArrayList<Integer>();
      for(int i = 0; i< 1000000; i++){
          a.add(r.nextInt(1000));
      }
      long end2 = System.currentTimeMillis();
      System.out.println(end2 -end);
    }
}

4、Map,HashMap用来快速访问,TreeMap保持键的排序,速度没HashMap快,LinkedHashMap保持元素排序,也通过散列也能快速访问,于两者中间。

5、Set元素不重复,TreeSet,HashSet,LinkedHashSet与Map的类似。


最后你会发现,这些容器可以存放对象,还可以无限大小,还可以帮你计数,这么神奇的类是怎么设计出来的,这就是学好数据结构的重要性了。

11.9 Set
查找成为了Set中最重要的操作。因此通常都会选择一个HashSet的实现,它专门对快速查找进行了优化。
HashSet使用散列数据结构。TreeSet使用了红黑树数据结构:是有序的。
  Set<String> set1 = new HashSet<String>();
 
  Collections.addAll(set1, "A B C D E F G H I J K L".split(" "));
  set1.add("M");
  System.out.println("H:" + set1.contains("H"));
  System.out.println("M:" + set1.contains("M"));
 
  Set<String> set2 = new HashSet<String>();
  Collections.addAll(set2, "H I J K L".split(" "));
  System.out.println("set2 in set1 : " + set1.containsAll(set2));
 
  System.out.println("set1 : " + set1);
  set1.remove("H");
  System.out.println("set1 : " + set1);
 
  set1.removeAll(set2);
  System.out.println("set1 : " + set1);
11.10 Map
11.11 Queue P269
队列常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。
队列在并发编程中特别重要。因为他们可以安全地将对象从一个任务传输给另一个任务。
LinkedList提供了方法以支持队列的行为。并且它实现了Queue接口。因为可以作为Queue的一种实现。
11.11.1 PriorityQueue
通过提供自己的Comparator来修改顺序。
11.12 Collection和Iterator
C++只用迭代器来表示容器之间的共性。
与底层容器的特定实现解耦。只和对象类型有关。
新程序中不应该使用过时的Vector,HashTable,Stack。

容器类的基本类型是List,Set,Queue,Map。这些对象类型也成为集合类。

1、泛型和类型安全的容器

使用ArrayList相当简单:创建一个实例,用add()插入对象,get()访问对象, size()获取ArrayList中元素的个数。
注解 @SuppressWarning 可以接受参数。
如果一个类没有显示的声明继承哪个类,那么它自动的继承自Object()。
向上转型同样可以作用于泛型。
import java.util.*; 


     class GrannySmith extends Apple {} 
     class Gala extends Apple {} 
     class Fuji extends Apple {} 
     class Braeburn extends Apple {} 


     public class GenericsAndUpcasting { 
        public static void main(String[] args) { 
          ArrayList<Apple> apples = new ArrayList<Apple>(); 
          apples.add(new GrannySmith());       
          apples.add(new Gala()); 
          apples.add(new Fuji()); 
          apples.add(new Braeburn()); 
          for(Apple c : apples) 
             System.out.println(c); 
        } 
     } /* Output: (Sample) 
     GrannySmith@7d772e 
     Gala@11b86e7 
     Fuji@35ce36 
     Braeburn@757aef 


2、基本概念

Java容器类类库的用途是“保存对象”,并将其划分为两个不同的概念:
1)Collection。一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素,而Set不能有重复元素。Queue按照队列规则来确定对象产生的顺序。
2)Map。一组成对的"键值对"对象,允许使用键值来查找值。映射表允许我们使用另一个对象查找某个对象,它也被称为“关联数组”。
所有的Collection都可以用foreach语法遍历。


3、添加一组元素

下面介绍Collection类添加元素的实用方法:
Arrays.asList()接受一个数组或者一个逗号分隔的元素列表(使用可变参数),将其转换为一个List对象。
 List<Integer> list = Arrays.asList(16, 17, 18, 19, 20); 
 list.set(1, 99); 

Collection.addAll()方法运行起来很快,而且构建一个不包含元素的Collection,然后调用Collection.addAll()这种方式很方便,因此它是首选方式。
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));

而Collection.addAll()只能接受另一个Collection对象作为参数,不如Arrays.asList()和Collections.addAll()灵活,这两个方法采样的都是可变参数列表。
Collections.addAll(collection, 11, 12, 13, 14, 15); 

可以直接使用Arrays.asList()的输出,将其当做List,但是这种情况下,其底层表示的是数组,不能调整其尺寸,所以不能add()或delete()。

     class Snow {} 
     class Powder extends Snow {} 
     class Light extends Powder {} 
     class Heavy extends Powder {} 
     class Crusty extends Snow {} 
     class Slush extends Snow {} 


     public class AsListInference { 
       public static void main(String[] args) { 
          List<Snow> snow1 = Arrays.asList( 
            new Crusty(), new Slush(), new Powder()); 


          // Won’t compile: 
          // List<Snow> snow2 = Arrays.asList( 
          //    new Light(), new Heavy()); 
          // Compiler says: 
          // found      : java.util.List<Powder> 
          // required: java.util.List<Snow> 


          // Collections.addAll() doesn’t get confused: 
          List<Snow> snow3 = new ArrayList<Snow>(); 
          Collections.addAll(snow3, new Light(), new Heavy());
对于这种多重向上转型,必须显示类型参数说明。


4、容器的打印

可直接打印容器,它自带了打印函数。
     import java.util.*; 
     import static net.mindview.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} 

这两种主要类型的区别在于容器的每个“槽”保存的元素个数。
Collection在每个槽中只能保存一个元素。此类容器包括:List,它以特定的顺序保存一组元素;Set,元素不能重复;Queue,一端插入对象,另一端移除对象。 
Map在每个槽内保存两个对象,即键和与之相关联的值。

Collection打印出来的内容用[ ]括住,Map打印出来的内容用{ }括住。

HashMap提供了最快的查找技术,TreeMap按照比较结果升序保存键,LinkedHashMap则按照插入顺序保存键,同时还保留了HashMap的查询速度。


5、List

两种类型的List:
基本的ArrayList,它长于随机访问元素,但是在List的中间插入和移除元素时较慢。
LinkedList,它提供了代价较低的在List中间进行的插入和删除操作,提供了优化的顺序访问。LinkedList在随机访问方面相对比较慢。

List重要价值在于,它提供了一种可修改的序列。
indexOf()来发现对象在List中所处位置的索引编号。
从List中移除一个元素,都会涌到equals()方法,List的行为根据equals()的行为而有所变化。
优化是一个棘手的问题,最好的策略就是弃之不顾,知道你发现需要担心它。
subList()方法允许很容易的从较大的列表中创建一个片段,顺序并不影响containsAll()的判断结果。
retainAll()保留两List的交集。
removeAll()也是基于equals()方法的。
addAll()方法使得我们可以在初始List中插入新的列表,而不是仅仅只能用Collection的addAll()方法追加到表尾。


6、迭代器

对于List,add()是插入元素的方法之一,而get()是取出元素的方法之一。
迭代器(Iterator,一种设计模式)是一个对象,它的工作是遍历并选择序列中的对象,迭代器通常被称为轻量级对象,创建它们的代价小。
Java的Iterator只能单向移动,用来:
使用方法Iterator()要求容器返回一个Iterator。Iterator准备好返回序列的第一个元素。
使用next()获得序列中的下一个元素。
使用hasNext()检查序列中是否还有元素。
使用remove()将迭代器新近返回的元素删除。
在remove()之前必须先调用next()。
Iterator 能够将遍历序列的操作与序列底层的结构分离。迭代器统一了对容器的访问方式。如下:
import typeinfo.pets.*; 
     import java.util.*; 


     public class CrossContainerIteration { 
public static void display(Iterator<Pet> it) { 
          while(it.hasNext()) { 
             Pet p = it.next(); 
             System.out.print(p.id() + ":" + p + " "); 
          } 
          System.out.println(); 
       } 
       public static void main(String[] args) { 
          ArrayList<Pet> pets = Pets.arrayList(8); 
          LinkedList<Pet> petsLL = new LinkedList<Pet>(pets); 
          HashSet<Pet> petsHS = new HashSet<Pet>(pets); 
          TreeSet<Pet> petsTS = new TreeSet<Pet>(pets); 
          display(pets.iterator()); 
          display(petsLL.iterator()); 
          display(petsHS.iterator()); 
          display(petsTS.iterator()); 
       } 
     } /* Output: 
     0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 
     0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 
     4:Pug 6:Pug 3:Mutt 1:Manx 5:Cymric 7:Manx 2:Cymric 0:Rat 
     5:Cymric 2:Cymric 7:Manx 1:Manx 3:Mutt 6:Pug 4:Pug 0:Rat 

ListIterator:
ListIterator可以双向移动,并且可以使用set()方法替换它访问过的最后一个元素。


7、LinkedList

LinkedList在执行某些操作时比ArrayList更高效,但是在随机访问操作方面却要逊色一些。
LinkedList还添加了可以作为栈,队列,双端队列的方法。可以使用它们的方法。


8、Stack

“栈”通常是指“后进先出”(LIFO)的容器。
可以直接将LinkedList当做栈使用。如:
package net.mindview.util; 
     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(); } 
     }
 用Stack实现:
import net.mindview.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 
注意显示的导入import net.mindview.util.Stack; 而不是jiava.util.Stack。
如果想在自己的代码里使用Stack类,在创建实例时,需要完整的指定包名,否则可能会和java.util包中的Stack发生冲突。


9、Set

Set最常使用的是测试归属性,可以轻易的查询某个对象是否在某个Set中。查找成为Set中最重要的操作,因此通常会选择一个HashSet的实现,它专门对快速查找进行了优化。
Set具有与Collection完全一样的接口,实际上Set就是Collection,只是行为不同。Set是基于对象的值来确定归属性的,使用contains()。
      如下:
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); 
       } 
     } /* Output: 
     [15, 8, 23, 16, 7, 22, 9, 21, 6, 1, 29, 14, 24, 4, 19, 26, 11, 18, 3, 
     12, 27, 17, 2, 13, 28, 20, 25, 10, 5, 0]
从0到29之间10000个随机数被添加到Set中,每一个数只有一个实例出现在结果中。

HashSet使用了散列,HashSet所维护的顺序与TreeSet和LinkedHashSet都不一样,因为他们的实现具有不同的元素存储方式。TreeSet将元素存储在红-黑树数据结构中,而HashSet使用的是散列函数。LinkedHashList因为查询速度的原因也使用了散列,但是看起来是使用了链表来维护元素的插入顺序。

import java.util.*; 
     import net.mindview.util.*; 


     public class UniqueWords { 
       public static void main(String[] args) { 
          Set<String> words = new TreeSet<String>( 
            new TextFile("SetOperations.java", "\\W+")); 
          System.out.println(words); 
       } 
     } /* Output: 
     [A, B, C, Collections, D, E, F, G, H, HashSet, I, J, K, L, M, N, Output, 
     Print, Set, SetOperations, String, X, Y, Z, add, addAll, added, args, 
     class, contains, containsAll, false, from, holding, import, in, java, 
     main, mindview, net, new, print, public, remove, removeAll, removed, 
     set1, set2, split, static, to, true, util, void] 

如上所示代码,打开一个文件,并将其读入一个Set中。TextFile继承自List<String>,构造器打开文件,并根据正则表达式“\\W+”将其断开为单词,这个正则表达式表示“一个或多个字母”,TreeSet将其按字典顺序排列,大小写分开。
如果想按字母顺序排列,可以向TreeSwt构造器传入String.CASE_INSENTIVE_ORDER比较器。
public class UniqueWordsAlphabetic { 
       public static void main(String[] args) { 
          Set<String> words = 
            new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); 
          words.addAll( 
            new TextFile("SetOperations.java", "\\W+")); 
          System.out.println(words); 
       } 
     } /* Output: 
     [A, add, addAll, added, args, B, C, class, Collections, contains, 
     containsAll, D, E, F, false, from, G, H, HashSet, holding, I, import, 
     in, J, java, K, L, M, main, mindview, N, net, new, Output, Print, 
     public, remove, removeAll, removed, Set, set1, set2, SetOperations, 
     split, static, String, to, true, util, void, X, Y, Z] 


10、Map

由map可以储存随机数及其个数,如下:
import java.util.*; 


     public class Statistics { 
       public static void main(String[] args) { 
          Random rand = new Random(47); 
          Map<Integer,Integer> m = 
            new HashMap<Integer,Integer>(); 
          for(int i = 0; i < 10000; i++) { 
            // Produce a number between 0 and 20: 
            int r = rand.nextInt(20); 
            Integer freq = m.get(r); 
            m.put(r, freq == null ? 1 : freq + 1); 
          } 
          System.out.println(m); 
       } 
     } /* Output: 
     {15=497, 4=481, 19=464, 8=468, 11=531, 16=533, 18=478, 3=508, 7=471, 
     12=521, 17=509, 2=489, 13=506, 9=549, 6=519, 1=502, 14=477, 10=513, 
     5=503, 0=481} 

Map的get方法返回该键对应的值。如果没有则返回null。
Map的put方法放入键值对。
Map的contansKey()返回是否含有这个键,containsValue()返回是否含有这个值。
Map可以返回它的键的Set,它的值的Collection,或者它的键值对的Set。value()返回所有值组成的Collection,KeySet()方法产生所有健组成的Set,可用于遍历。


11、Queue

       队列是一个典型的先进先出(FIFO)的容器。队列常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程中特别重要。因为它们可以安全的将对象从一个任务传输给另一个任务。
      offer()是与Queue相关的方法之一,他在允许的情况下,将一个元素插入到队尾,或者返回false。
      peek()和element()都将在布衣橱的情况下返回队头,但是peek()方法在队列为空时返回null,而element()会抛出NoSuchElementException异常。
      poll()和remove()方法将一出并返回队头,但是poll()在队列为空时返回null,而remove()会抛出NoSuchElementException异常。
      Queue可以由LinkedList来实现。而Queue接口窄化了对LinkedList的方法的访问权限。

PriorityQueue:
        先进先出描述了最典型的队列规则。队列规则是指在给定一组队列中的元素的情况下,确定下一个弹出队列的元素的规则。先进先出声明的是下一个元素应该是等待时间最长的元素。
        优先级队列声明下一个弹出元素是最需要的元素(拥有最高优先级)。 PriorityQueue可以确保当你调用peek()、poll()和remove()方法时,获取的元素将是队列中优先级最高的元素。
        重复是允许的,最小的值拥有最高的优先级(如果是String,空格也可以算作值,并且比字母优先级高),可以使用Collection.reverseOrder()来改变顺序。如下:
            stringPQ = new PriorityQueue<String>(strings.size(), Collections.reverseOrder()); 
可以用HashSet来消除重复的Charactor.比如:
         String fact = "EDUCATION SHOULD ESCHEW OBFUSCATION"; 
         Set<Character> charSet = new HashSet<Character>(); 
         for(char c : fact.toCharArray()) 
            charSet.add(c); // Autoboxing 
         PriorityQueue<Character> characterPQ = new PriorityQueue<Character>(charSet); 
         QueueDemo.printQ(characterPQ); 

Output: A B C D E F H I L N O S T U W 


12、Collection和Iterator

Collection是描述所有序列容器的共性的接口。在Java中,Collection和迭代器绑定到了一起,所有实现Collection就意味着需要实现Iterator()方法。
普通情况下,两种方法都奏效:
        public static void display(Iterator<Pet> it) { 
           while(it.hasNext()) { 
              Pet p = it.next(); 
              System.out.print(p.id() + ":" + p + " "); 
           } 
           System.out.println(); 
        } 
        public static void display(Collection<Pet> pets) { 
           for(Pet p : pets) 
              System.out.print(p.id() + ":" + p + " "); 
           System.out.println(); 
        } 

           List<Pet> petList = Pets.arrayList(8); 
           Set<Pet> petSet = new HashSet<Pet>(petList); 
           display(petList); 
           display(petSet); 
           display(petList.iterator()); 
           display(petSet.iterator());

       然而如果一个不是Collection类型的外部类,就不能实现Collection接口了,此时使用Iterator就是个不错的选择。继承AbstractCollection可以很容易的实现,它强制实现iterator()和size()方法。
       import typeinfo.pets.*; 
     import java.util.*; 


     public class CollectionSequence 
     extends AbstractCollection<Pet> { 
       private Pet[] pets = Pets.createArray(8); 
       public int size() { return pets.length; } 
       public Iterator<Pet> iterator() { 
          return new Iterator<Pet>() { 
            private int index = 0; 
            public boolean hasNext() { 
               return index < pets.length; 
            } 
            public Pet next() { return pets[index++]; } 
            public void remove() { // Not implemented 
               throw new UnsupportedOperationException(); 
            } 
             }; 
       } 
       public static void main(String[] args) { 
          CollectionSequence c = new CollectionSequence(); 
          InterfaceVsIterator.display(c); 
          InterfaceVsIterator.display(c.iterator()); 
       } 
     } /* Output: 
     0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 
     0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 

上例可见,如果实现Collection(),就必须实现Iterator(),更糟糕的是,如果这个外部类已经有需要继承的类而不能继承自AbstractCollection,要实现Collection就必须实现该接口中的所有方法。
       class PetSequence { 
       protected Pet[] pets = Pets.createArray(8); 
     } 


     public class NonCollectionSequence extends PetSequence { 
       public Iterator<Pet> iterator() { 
          return new Iterator<Pet>() { 
            private int index = 0; 
            public boolean hasNext() { 
               return index < pets.length; 
            } 
            public Pet next() { return pets[index++]; } 
            public void remove() { // Not implemented 
               throw new UnsupportedOperationException(); 
            } 
          }; 
       } 
       public static void main(String[] args) { 
          NonCollectionSequence nc = new NonCollectionSequence(); 
          InterfaceVsIterator.display(nc.iterator()); 
       } 
     } /* Output: 
     0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 
生成Iterator是将队列与消费队列的方法连接在一起耦合度最小的方式,并且与实现Collection相比,它在序列类上所施加的约束也少得多。


13、Foreach与迭代器

       Iterable接口包含一个能够产生Iterator的iterator()方法,并且Iterator接口被foreach用来在序列中移动。
       大量的类都是Iterable类型,主要包括所有的Collection类(但是不包含各种Map)。
       foreach语句可以用于数组或其它任何Iterable,但并不意味着数组肯定是一个Iterable,而任何自动包装也不会自动发生,不存在任何从数组到Iterable的自动转换,必须手动执行这种转换。
       import java.util.*; 


     public class ArrayIsNotIterable { 
        static <T> void test(Iterable<T> ib) { 
           for(T t : ib) 
             System.out.print(t + " "); 
        } 
        public static void main(String[] args) { 
           test(Arrays.asList(1, 2, 3)); 
           String[] strings = { "A", "B", "C" }; 
           // An array works in foreach, but it’s not Iterable: 
           //! test(strings); 
           // You must explicitly convert it to an Iterable: 
           test(Arrays.asList(strings)); 
        } 
     } /* Output: 
     1 2 3 A B C 
如上,String数组不可以直接作为Iterator类传递,而是被手动转换成了Collection类,也即Iterable类型。

适配器方法惯用法:
        当有一个接口并需要另一个接口,编写适配器就可以解决问题。至于为什么有一个接口还要写另一个接口么,比如像有乱序倒序的Iterator方法,如果直接继承就会覆盖原有的顺序方法,所以要写别的能产生Iterable对象的方法接口(适配器)以满足foreach语句实现功能。
        通过使用方法中的内部类,下例实现了在foreach方法中将Iterable对象倒序和乱序遍历的方法:
     import java.util.*; 


     public class MultiIterableClass extends IterableClass { 
       public Iterable<String> reversed() { 
         return new Iterable<String>() { 
            public Iterator<String> iterator() { 
              return new Iterator<String>() { 
                 int current = words.length - 1; 
                 public boolean hasNext() { return current > -1; } 
                 public String next() { return words[current--]; } 
                 public void remove() { // Not implemented 
                   throw new UnsupportedOperationException(); 
                 } 
              }; 
            } 
         }; 
       } 
       public Iterable<String> randomized() { 
         return new Iterable<String>() { 
            public Iterator<String> iterator() { 
              List<String> shuffled = 
                 new ArrayList<String>(Arrays.asList(words)); 
              Collections.shuffle(shuffled, new Random(47)); 
              return shuffled.iterator(); 
            } 
         }; 
       } 
       public static void main(String[] args) { 
         MultiIterableClass mic = new MultiIterableClass(); 
         for(String s : mic.reversed()) 
            System.out.print(s + " "); 
         System.out.println(); 
         for(String s : mic.randomized()) 
            System.out.print(s + " "); 
         System.out.println(); 
         for(String s : mic) 
            System.out.print(s + " "); 
       } 
     } /* Output: 
     banana-shaped. be to Earth the know we how is that And 
     is banana-shaped. Earth that how the be And we know to 
    And that is how we know the Earth to be banana-shaped. 

         注意乱序方法并没有创建自己的Iterator,而是直接返回被打乱的List(作为Collection类的一种)中的Iterator。
         需要注意的是,Arrrays.asList()产生的List对象会使用底层数组作为其物理实现,也就是会修改原来的数组。如果不想这种情况发生,就应该在另一个容器中创建一个副本。即使用 List<Integer> list = new ArrayList<Integer>(Arrays.asList(数组)) 的方法,这样修改的只是list引用而不是原数组。
        import java.util.*; 


     public class ModifyingArraysAsList { 
       public static void main(String[] args) { 
       Random rand = new Random(47); 
          Integer[] ia = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 
          List<Integer> list1 = 
             new ArrayList<Integer>(Arrays.asList(ia)); 
          System.out.println("Before shuffling: " + list1); 
          Collections.shuffle(list1, rand); 
          System.out.println("After shuffling: " + list1); 
          System.out.println("array: " + Arrays.toString(ia)); 


          List<Integer> list2 = Arrays.asList(ia); 
          System.out.println("Before shuffling: " + list2); 
          Collections.shuffle(list2, rand); 
          System.out.println("After shuffling: " + list2); 
          System.out.println("array: " + Arrays.toString(ia)); 
        } 
     } /* Output: 
     Before shuffling: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 
     After shuffling: [4, 6, 3, 1, 8, 7, 2, 5, 10, 9] 
     array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 
     Before shuffling: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 
     After shuffling: [9, 1, 6, 3, 7, 2, 5, 10, 4, 8] 
     array: [9, 1, 6, 3, 7, 2, 5, 10, 4, 8] 


14、总结

Java提供了大量持有对象的方式:
1、数组。一旦生成,容量就不能改变。
2、Collection保存单一的元素,Map保存相关联的键值对。它们可以自动的调整其尺寸。容器不能持有基本类型,但是自动包装机制会自动地执行基本类型到容器中所持有的包装器类型之间的双向转换。
数组和List都是排好序的容器,List可以自动扩容。
如果需要大量随机访问,使用ArrayList,如果要经常在其中插入删除,则使用LinkedList。
各种Queue以及栈的行为,由LinkedList提供支持。
Map将对象与对象相关联,HashMap用来快速访问,而TreeMap保持“键”始终处于排序状态,所以没有HashMap快。LinkedHashMap保持原始插入的顺序,但也通过散列提供了快速访问能力。
Set不接受重复元素。HashSet提供最快的查询速度,TreeSet保持元素处于排序状态,LinkedHashSet以插入顺序保存元素。
新程序不该使用过时的Vector、Hashtable和Stack。 

除了TreeSet之外的所有Set都拥有与Collection完全一样的接口。
List和Collection存在着明显的不同,尽管List所要求的方法都在Collection中。
另一方面,在Queue接口中的方法都是独立的,在创建具有Queue功能的实现时,不需要使用Collection方法。
最后,Map和Collection之间唯一的重叠就是Map可以使用entrySet()和values()方法来产生Collection。

1、基本概念
Java容器类类库的作用是“保存对象”,分为
1)Collection.一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素,而Set不能有重复元素。Queue按照排队规则来确定对象产生的顺序(通常与他们被插入的顺序相同)。
2)Map.一组成对的“键值对”对象。
2、List
List 将元素维护在特定的序列中,有两种类型:
(1)基本的ArrayList,它长于随机访问元素,但是在List中间插入和移除元素时较慢。
(2)LinkedList,在List中间进行插入和删除操作代价较低,但是在随机访问方面相对较慢,但是它的特性集较ArrayList更大。
在List中,一些函数的用法:
(1)contains()方法来确定某个对象是否在对象中。
(2)remove()方法用来移除某个对象。
(3)如果你有一个对象的引用,可以使用indexOf()来确定该对象在List中所处的位置的索引编号。
(4)subList()方法用来从较大的列表中创建出一个片段。
(5)retainAll()方法用来求交集。
(6)removeAll()方法用来移除在参数List中的所有元素。
(7)addAll()方法用来在初始List的中间插入新的列表。
迭代器
迭代器是一个对象,它的工作是遍历并选择序列中的对象。从而做到了只是使用容器而不关心容器的类型。
在我看来,Java中的Iterator类似于数据结构中的单向链表,只能单向移动。只能用来:
(1)使用方法iterator()要求容器返回一个Iterator。Iterator将准备好返回序列的第一个元素。
(2)使用next()获得序列中的下一个元素。
(3)使用hasNext()检查序列中是否还有元素。
(4)使用remove()将迭代器新近返回的元素删除。

ListIterator
ListIterator是一个更强大的Iterator的子类型。只能用于各种List类的访问,但是可以双向移动。
(1)nextIndex()用来指向当前位置的后一个元素的指引。
(2)previousIndex()用来指向当前位置的前一个元素的指引。
(3)listIterator()方法产生一个指向List开始处的ListIterator。
(4)listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator。
LinkedList
LinkedList中添加了可以用作栈、队列或双端队列的方法。
(1)getFirst()和element()返回列表的头(第一个元素),如果为空,提示NoSuchElementException。peek()方法类似,不过在列表为空时返回null。
(2)addFirst()将某个元素插入到列表的头部。而offer()、add()和addLast()作用类似,将某个元素插入到列表的尾部。
(3)remove()、removeFirst()和poll()都是将某个元素从列表的头部移除。
Stack
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。
3、Set
TreeSet将元素存储在红-黑树数据结构中,而HashSet使用的是散列函数,LinkedHashList因为查询速度的原因也使用了散列,但是看起来它使用了链表来维护元素的插入顺序。
如果想按照字母序排序,可以向TreeSet的构造器传入String.CASE_INSENSITIVE_ORDER比较器(比较器就是建立排序顺序的对象)
Set<String> words = new TreeSet<String>       (String.CASE_INSENSITIVE_ORDER);
1
4、Map
containsKey()和constainsValue()方法分别测试是否包含某个键或某个值。
keySet()方法产生一个由在某个Map中的所有键组成的Set。
5、Queue
LinkedList提供了方法以支持队列的行为,并且实现了Queue接口。
public class QueueDemo{
    public static void printQ(Queue queue) {
    while(queue.peek() != null)
     System.out.print(queue.remove() + " ");
    System.out.println();
    }
    public static void main (String[] args){
        Queue<Integer>  queue = new LinkedList<Integer>();
        Random rand = new Random(47);
        for(int i = 0; i < 10; i++)
            queue.offer(rand.nextInt(i + 10));
        printQ(queue);
        Queue<Character> qc = new LinkedList<Charactor>();
        for(char c: "Brontosaurus".toCharArray())
             qc.offer(c);
        printQ(qc);
    }
}/*Output
8 1 1 1 5 14 3 1 0 1
B r o n t o s a u r u s
*///

offer()方法在允许的情况下,将一个元素插入到队尾,或者返回false。
peek()和element()都将在不移除的情况下返回对头,但是peek()方法在队列为空时返回null,而element()会抛出NoSuchElementException异常。
poll()和remove()方法将移除并返回队头,但是poll()在队列为空时返回null,而remove()会抛出NoSuchElementException异常。

11.1 泛型和类型安全的容器
ArrayList容器的使用,ArrayList就是动态数组;可以动态增加的容器;
11.2 基本概念
java容器类库的用途是“保存对象”,有两种概念:
(1)Collection:List、Set、Queue
(2)Map:Map、ArrayList
11.3 添加一组元素
Arrays.asList(),Collections.addAll()
11.4 容器的打印
List以特定的顺序保存元素;Set元素不能重复;Queue从一端插入另一端移除;Map保存了两个对象,键值和与之相关的值,Map.put(key,value),Map.get(key),HashMap(),TreeMap(),LinkedHashMap()
HashMap提供了最快的查找技术,也没有按照任何明显的顺序来保存元素;
TreeMap按照比较结果升序保存键值;
LinkenHashMap按照插入顺序保存键值,同时还保留了HashMap的查询速度
11.5 List
有两种类型的List:
(1)ArrayList():随机访问较快,插入和移除较慢;
(2)LinkedList():随机访问相对较慢,插入和移除较快;
11.6 迭代器
迭代器也是一种设计模式,关键字:Ierator(),迭代器与List综合使用,将List转换成Iterator(),Iterator有hasNext和next方法;
迭代器同意了对容器的访问方式
ListIterator只能用于List类的访问;
11.7 LinkedList()
11.8 栈
遵循“先进后出”,LinkedList可以作为栈使用;Stack类;
11.9 Set
Set不保存重复元素(如何判断元素相同较复杂,最好清楚);HashSet();Set实际就是Collection;
11.10 Map
11.11 Queue
11.12 Collection和Iterator
11.13 Foreach与迭代器
Foreach可以用于任何迭代器对象;
11.14 总结

















猜你喜欢

转载自zhyp29.iteye.com/blog/2306859