第十一章 持有对象
2013年6月24日 星期一 20时57分09秒
第十一章 持有对象
如果一个程序只包含固定数量的且其生命周期都是已知的对象,那么只是一个非常简单的程序。
通常,程序总是根据运行时才知道的某些具体条件去创建对象。在此之前,不会知道所需对象的数量,甚至不知道确切的类型。为解决这个问题,需要在任意时刻和任意位置创建任意数量的对象。所以就不能依靠创建命名的引用来持有一个对象。
Java通过容器(collection)来解决该问题。(Set,List,Map,Queue)
11.1 泛型和类型安全的容器
package chapter11;
import java.util.*;
/*@name DotNew.java
* @describe 11.1 泛型和类型安全的容器
* @since 2013-06-24 21:19
* @author 张彪
*/
class Apple{
private static long counter;
private final long id=counter++;
public long id(){return counter;}
}
class Orange{}
public class ApplesAndOrangesWithoutGenerics {
@SuppressWarnings("unchecked")
public static void main(String[] args){
ArrayList<Apple> apples=new ArrayList<Apple>();
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();
}
for(Apple a:apples){
System.out.print(a.id()+" ");
}
}
}
通过使用泛型,你不仅知道编译器将会检查你放置到容器中的对象类型,而且在使用容器中的对象时,可以使用更加清晰的语法。
当你指定了某个类型为泛型参数时,你并不仅限于将该确切类型的对象放置到容器中。向上转型也可以像作用于其他类型一样作用于泛型。
package chapter11;
import java.util.ArrayList;
/*@name DotNew.java
* @describe 11.1 泛型和类型安全的容器
* @since 2013-06-24 21:37
* @author 张彪
*/
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 a:apples){
System.out.print(a.id()+" ");
}
}
}
因此你可以将Apple的子类添加到被指定为保存Apple对象的容器中。
11.2 基本概念
Java容器类类库的用途是“保存对象”,并将其划分为两个不同的概念:
1) Collection。一个独立的元素序列,这些元素都服从一条或多条规则。List必须按插入的顺序保存元素,而Set不能有重复的元素。Queue按照排队规则来确定对象产生的顺 序(通常与它被插入的顺序相同)。
2) Map。一组成对的“键值”对象。允许你使用键来查找值。ArrayList允许你使用数字来查找值,因此在某种意义上,它将数字和对象关联起来。
Collection接口示例:
package chapter11.holding;
import java.util.*;
public class SimpleCollection {
public static void main(String[] args){
Collection<Integer> c=new ArrayList<Integer>();
for(int i=0;i<4;i++){
c.add(i);
}
for(Integer i:c){
System.out.print(i+" ");
}
}
}
add()方法在Set中只有元素不存在的情况下才会添加,而List不关心是否存在重复。
所有的Collection都可以使用foreach方法进行遍历,后续将会学习“迭代器”的概念。
11.3 添加一组元素
package chapter11.holding;
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};
Collections.addAll(collection, 11,12,13,14,15);
Collections.addAll(collection, moreInts);
List<Integer> list=Arrays.asList(16,17,18,19,20);
list.set(1,99);
//list.add(21);
for(Integer t:collection){
System.out.print(t+" ");
}
}
}
--------------------------------------------
package chapter11.holding;
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());
//List<Snow> snow2=Arrays.asList(new Light(),new Heavy()); // can't covert from List<Powder> to List<Snow>
List<Snow> snow3=new ArrayList<Snow>();
Collections.addAll(snow3,new Light(),new Heavy());
//显式类型参数说明
List<Snow> snow4 =Arrays.<Snow> asList(new Light(),new Heavy());
for(Snow s:snow3){
System.out.println(s+" ");
}
}
}
当创建snow2时,Arrays.asList()中只有Power类型,因此它会创建List<Power>而不是List<Snow>。
正如snow4的操作中所看到的,可以在Arrays.asList()中间插入一条线索,以告诉编译器对于由Arrays.asList()产生的List类型,实际的目标类型应该是什么。这称为显式类型参 数说明。
11.4 容器的打印
你必须使用Arrays.toString()来产生数组的可打印表示。但是打印容器无需任何帮助。 如下例所示:
package chapter11.holding;
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()));
System.out.println(fill(new LinkedList<String>()));
//Set会去重
System.out.println(fill(new HashSet<String>()));
System.out.println(fill(new TreeSet<String>()));
System.out.println(fill(new LinkedHashSet<String>()));
//Map也会去重
System.out.println(fill(new HashMap<String,String>()));
System.out.println(fill(new TreeMap<String,String>()));
System.out.println(fill(new LinkedHashMap<String,String>()));
}
}
/*[rat, cat, dog, dog]
[rat, cat, dog, dog]
[cat, dog, rat]
[cat, dog, rat]
[rat, cat, dog]
{cat=Rags, dog=Spot, rat=Fuzzy}
{cat=Rags, dog=Spot, rat=Fuzzy}
{rat=Fuzzy, cat=Rags, dog=Spot}*/
由输出结果可知:默认的打印行为(使用容器提供的toString()方法)可读性很好的结果。[] and {}
HashSet,TreeSet,LinkedHashSet都是Set类型,每个相同的项只保存一次,但从输出结果来看,不同的Set实现存储元素的方式也不同。此时,需知HashSet是最快 获取元素的方式,如果存储顺序很重要,可以使用TreeSet,它按照比较结果的升序保存对象,或者LinkedHashSet,它是按照被添加的顺序保存对象。
Map(也为称为关联数组),你可以通过键来查找值,对于每一个键,Map只接受存储一次。同时你会发现Map中的保存顺序并不是他们的插入顺序。此时,需知HashMap实现使用 的是一种非常快速的控制顺序。
HashMap,TreeMap,LinkedHashMap都是Map类型。HashMap提供了最快的查找技术。TreeMap按比较结果的升序保存值,LinkedHashMap则按插入顺序保存 值,同时还保留了HashMap的查下速度。
11.5 List
List承诺可以将元素维护在特定的序列中,有两种类型的List:
1) ArrayList ,长于随机方法,插入和删除比较慢
2) LinkedList ,访问比较慢
由于typeinfo.pets使用了14章的类库,所以此案例稍后再补充。
11.5.1
11.5.2
11.6 迭代器
任何容器,都必须有某种方式可以插入元素并将它们再次取回。毕竟,持有事物是容器最基本的工作。
迭代器(也是一种设计模式)也是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不必知道或关心该序列底层的结构。此外,迭代器通常被称作轻量级对象;创建它的代价 小。因此经常可以看到对迭代器有些奇怪的限制。例如:Java的Iterator只能单向移动,这个Iterator只能用来:
1) 使用方法iterator()要求容器返回一个Iterator。Iterator将准备好返回序列的第一个元素。
2) 使用next()获得序列的下一个元素
3) 使用hasNext()检查序列中是否还有元素
4) 使用remove()将迭代器新近返回的元素删除
示例:
package chapter11.holding;
import java.util.Iterator;
import java.util.List;
import chapter14.typeinfo.pets.Pet;
import chapter14.typeinfo.pets.Pets;
/*@name SimpleIteration.java
* @describe 11.6 迭代器
* @since 2013-07-02 21:47
* @author 张彪
*/
public class SimpleIteration {
public static void main(String[] args) {
List<Pet> pets=Pets.arrayList(12);
Iterator<Pet> it=pets.iterator();
while(it.hasNext()){
Pet t=it.next();
System.out.println(t.getClass());
}
System.out.println("---------");
for(Pet p:pets){
System.out.println(p.id()+" --- "+p);
}
it=pets.iterator();
for(int i=0;i<6;i++){
it.next();
it.remove();
}
}
}
有了Iterator就不必为容器汇总的元素数量操心了。
如果只是向前遍历List,并不打算修改List对象本身,那么你可以看到foreach语法会显得更加简洁。
Iterator还可以移除由next()产生的最后一个元素,这意味着在调用remove()之前必须先调用next()方法。
在使用Iterator时可以不必知道容器的类型。
11.6.1 ListIterator
ListIterator是一个更加强大的Iterator的子类型。它只能用于各种List类的访问。尽管Iterator只能向前移动,但是ListIterator可以双向移动。
package chapter11.holding;
import java.util.List;
import java.util.ListIterator;
import chapter14.typeinfo.pets.Pet;
import chapter14.typeinfo.pets.Pets;
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.println(it.next()+" , "+it.nextIndex()+" , "+ it.previousIndex()+";");
}
System.out.println("-----------");
while(it.hasPrevious()){
System.out.println(it.previousIndex()+","+it.previous().id());
}
System.out.println("============");
it=pets.listIterator(3);
while(it.hasNext()){
it.next();
it.set(Pets.randomPet());
}
System.out.println(pets);
}
}
11.7 LinkedList
LinkedList也像ArrayList一样实现了基本的List接口。
LinkedList还添加了可以使其使用作栈,队列或双端队列的方法。
11.8 Stack
“栈”通常是后进先出(LIFO)的容器。有时栈也被称为叠加栈。
LinkedList具有能够直接实现栈的所以功能的方法,因此可以直接将LinkedList作为栈使用。
package net.mindview.util;
import java.util.*;
public class Stack<T> {
private LinkedList<T> storage=new LinkedList<T>();
public void push(T e){storage.addFirst(e);}
public T peek(){return storage.getFirst();}
public T pop(){return storage.pop();}
public boolean empty(){return storage.isEmpty();}
public String toString(){return storage.toString();}
public static void main(String[] args) {
Stack<String> s=new Stack<String>();
for (String s1: "My dog has fleas".split(" ")) {
s.push(s1);
}
System.out.println(s.toString());
System.out.println("s.peek()="+s.peek());
System.out.println("s.pop()"+s.pop());
while(!s.empty()){
System.out.println(s.pop()+" ");
}
}
}
11.9 Set
Set不保存重复的元素。Set中最常用的就测试归属性。HashSet专门对快速查找进行了优化。
Set具有与Collection完全一样的接口,实际上Set就会Collection,只是行为不同。Set是基于对象的值来确定归属性的,而更加复杂的问题我们将在17章进行介绍。
HashSet所维护的顺序与TreeSet或LinkedHashList都不同,因为他们的实现具有不同的元素存储方式。TreeSet将元素存储在红--黑树数据结构中,而HashSet使用的是散列函数。
如果想对结构进行排序,可以使用TreeSet来代替HashSet。
11.10 Map
将对象映射到其他对象的能力是一种解决编程问题的杀手锏。
、 Map与数组和其他的Collection一样,可以很容易的扩展到多维,而我们只需将其值设置为Map。因此我们能够很容易地将容器组合起来从而快速生成强大的数据结构。
例如: Map<Person,List<? extends Pet>> petPeople= new HashMap<Person,List<? extends Pet>>();
11.11 Queue
队列是一个典型的先进先出(FIFO)容器。
队列被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程中特别重要,就像你在21章中所看到的,因为它可以安全地将对象从一个对象传输到另一 个对象。
LinkedList提供了方法以支持队列的行为,而且它实行了Queue接口,因此LinkedList可以用作Queue的一种实现。
package chapter11.holding;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
/*@name QueueDemo.java
* @describe 11.11 Queue
* @since 2013-07-07 0:35
* @author 张彪
*/
public class QueueDemo {
public static void print(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 random=new Random(47);
for (int i = 0; i < 10; i++) {
queue.add(random.nextInt(i+10));
}
print(queue);
Queue<Character> queue1=new LinkedList<Character>();
for (char c:"HELLO".toCharArray()) {
queue1.offer(c);
}
print(queue1);
}
}
offer():它在运行的情况下,将一个元素插入队尾,或者是返回false。
peek()和element() :不移除的情况下返回对头。
poll()和remove() :移除并返回对头。
11.11.1 PriorityQueue(优先级队列)
优先级队列声明下一个弹出元素时最需要的元素。 (Java SE5 新特性)
当你在PriorityQueue上调用offer()方法插入一个对象时,这个对象会在队列中被排序。默认的排序将使用对象在队列中的自然排序,但是你可以通过提供自己的Comparator来修 改这个顺序。
package chapter11.holding;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Random;
/*@name PriorityQueueDemo.java
* @describe 11.11.1 PriorityQueue
* @since 2013-07-07 0:56
* @author 张彪
*/
public class PriorityQueueDemo {
public static void main(String[] args) {
PriorityQueue<Integer> priorityQueue=new PriorityQueue<Integer>();
Random random=new Random(47);
for (int i = 0; i < 10; i++) {
priorityQueue.offer(random.nextInt(i+10));
}
QueueDemo.print(priorityQueue);
List<Integer> ints=Arrays.asList(25,22,20,18,14,9,3,1,1,2,3,9,14,18,21,23,25);
priorityQueue=new PriorityQueue<Integer>(ints);
QueueDemo.print(priorityQueue);
priorityQueue=new PriorityQueue<Integer>(ints.size(),Collections.reverseOrder());
priorityQueue.addAll(ints);
QueueDemo.print(priorityQueue);
String fact="EDUCATION SHOULD ESCHEW OBFUSCATION";
List<String> strings=Arrays.asList(fact.split(""));
PriorityQueue<String> priorityQueue1=new PriorityQueue<String>(strings);
QueueDemo.print(priorityQueue1);
}
}
11.12 Collection和Iterator
java.util.AbstractCollection类提供了Collection的默认实现。
11.13 Foreach与迭代器
foreach语法可以 应用于任何Collection对象。
Java SE5引入了新的被称为Iterable的接口,该接口包含了一个能够产生Iterator的iterator()方法,并且Iterable接口被foreach用来在序列中移动。因此如果你创建了任何实现 Iterable的类,都可以将他用于foreach语句中。
package chapter11.holding;
import java.util.Iterator;
/*@name PriorityQueueDemo.java
* @describe 11.13 Foreach与迭代器
* @since 2013-07-09 0:12
* @author 张彪
*/
public class IterableClass implements Iterable<String>{
protected String[] words=("And tha is how we know the earth to bananan-shaped.").split(" ");
public Iterator<String> iterator() {
return new Iterator<String>(){
private int index=0;
public boolean hasNext(){
return index<words.length;
}
public String next(){
return words[index++];
}
public void remove(){
throw new UnsupportedOperationException();
}
};
}
public static void main(String[] args) {
for(String s:new IterableClass()){
System.out.print(s+" ");
}
}
}
iterator()返回的是实现了Iterable<String>的匿名内部类的实例,该匿名内部类可以遍历数据中的所以单词。
package chapter11.holding;
import java.util.Map;
/*@name PriorityQueueDemo.java
* @describe 11.13 遍历环境变量
* @since 2013-07-07 0:21
* @author 张彪
*/
public class EnvironmentVariables {
public static void main(String[] args) {
for(Map.Entry entry:System.getenv().entrySet()){
System.out.println(entry.getKey() +" "+entry.getValue());
}
}
}
===================================================================================================================
11.13.1 适配器方法惯用法
package chapter11.holding;
import java.util.*;
public class ModifyingArrayAsList {
public static void main(String[] args) {
Random random=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( list1);
Collections.shuffle(list1,random);
System.out.println("shuffled: "+list1);
System.out.println(Arrays.toString(ia));
}
}
/*[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
shuffled: [4, 6, 3, 1, 8, 7, 2, 5, 10, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
*/
11.14 总结
2013-07-09 0:43 记 @tangxiacun.tianhequ.guanzhou