Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1967003
  • 博文数量: 606
  • 博客积分: 9991
  • 博客等级: 中将
  • 技术积分: 5725
  • 用 户 组: 普通用户
  • 注册时间: 2008-07-17 19:07
文章分类

全部博文(606)

文章存档

2011年(10)

2010年(67)

2009年(155)

2008年(386)

分类:

2008-09-16 21:37:13

第十一章 对象的集合

  数组

   Java中,数组是一种效率最高的存储和随机访问对象引用序列的方式。

   Java中,无论使用数组或容器,都有边界检查。如果越界操作就会得到一个RuntimeException异常。

   容器类:ListSetMap,它们不以具体的类型来处理对象。换句话来说,它们将所有对象都按Object类型处理,即Java中所有类的基类。这种方式的好处:你只需构建一个容器,任意的Java对象都可以放入其中。(除开基本类型之外,数组可以保存基本类型,而容器则不能)

   考虑到效率与类型检查,应该尽可能使用数组。

   数组是第一级对象

   对象数组(Object[])保存的是引用,而基本类型数组(int [])保存的是基本类型的值。

   新生成一数组对象时,其中所有的引用被自动初始化为null;所以检查其中引用是否为null,即可知道数组对象的某个数组是否存有对象。同样,基本类型的数组如果是数值型的,就被自动初始化为0;如果是字符型(char)的,就被自动初始化为(char)0;如果是布尔型(boolean),就被自动初始化为false

   基本类型的容器

    容器类只能保存对象的引用。而数组可以创建为直接保存基本类型,还可以保存对象的引用。在容器中可以使用包装类,例如IntegerDouble等,以把基本类型的值放入容器中。

    返回一个数组

    Java采用类似的方法,允许直接返回一个数组。与C++不同,使用Java不需要担心要为数组负责,只要你需要它,它就会一直存在,当你使用完后,来几回收器会清理掉它。

    Arrays

    一部分方法:

    fill()用于以某个值填充整个数组,只能以某个单一的值填充整个数组,如果想要随机生成的若干数字填充数组,fill()就无能为力了;

    sort()用于对数组排序;

    binarySearch()用于已经排序的数组中查找元素,如果找到目标,返回值等于或大于0,否则,发挥负值;

    asList()接受任意的数组为参数,并将器转变List容器。

   复制数组

    System.arraycopy(),用它来复制数组比用for循环复制要快的很多,它针对所有类型做了修改。

    System.arrycopy(fromArray, fromBegin, toArray, toBegin, length)

    数组的比较

     Arrays类提供了重载后的equals()方法,用来比较整个数组。数组相等的条件是原属个数必须相等,并且对应位置的元素也相等。

    数组的比较

import com.bruceeckel.util.*;
import java.util.*;

public class CompType implements Comparable {
  
int i;
  
int j;
  
public CompType(int n1, int n2) {
    i
= n1;
    j
= n2;
  
}
  
public String toString() {
    
return "[i = " + i + ", j = " + j + "]";
  
}
  
public int compareTo(Object rv) {
    
int rvi = ((CompType)rv).i;
    
return (i < rvi ? -1 : (i == rvi ? 0 : 1));
  
}
  
private static Random r = new Random();
  
public static Generator generator() {
    
return new Generator() {
      
public Object next() {
        
return new CompType(r.nextInt(100),r.nextInt(100));
      
}
    
};
  
}
  
public static void main(String[] args) {
    CompType
[] a = new CompType[10];
    Arrays2
.fill(a, generator());
    
System.out.println(
      
"before sorting, a = " + Arrays.asList(a));
    
Arrays.sort(a);
    System.out.println(
      
"after sorting, a = " + Arrays.asList(a));
  
}
}

  sort()需要把参数的类型转变为Comparable,然后进行排序。

  数组排序

import com.bruceeckel.util.*;
import java.util.*;

public class StringSorting {
  
public static void main(String[] args) {
    
String[] sa = new String[30];
    Arrays2
.fill(sa, new Arrays2.RandStringGenerator(5));
    
System.out.println(
      
"Before sorting: " + Arrays.asList(sa));
    
Arrays.sort(sa);
    
System.out.println(
      
"After sorting: " + Arrays.asList(sa));
  
}
}

   String排序算法默认依据词典编顺序排序,所以大写字符开头的此都放在前面输出,然后才是小写字母开头的词。如果想忽略大小写字符将单词都放在一起排序,可以定义自己的Comparator类,然后覆盖器默认的String Comparable的行为。

package com.bruceeckel.util;
import java.util.*;

public class AlphabeticComparator implements Comparator {
  
public int compare(Object o1, Object o2) {
    
String s1 = (String)o1;
    
String s2 = (String)o2;
    
return s1.toLowerCase().compareTo(s2.toLowerCase());
  
}
}

   Java标准类库中的排序算法针对你正排序的特殊类型进行了优化,针对基本类型设计的快速排序quicksort),以及针对对象设计的稳定归并排序

容器

  Java2容器类类库的用途是保存对象并将器划分为两个不同的概念:

  1Colleaction。一组独立的元素,通常这些元素都服从某种规则。List必须保持元素特定的顺序,而Set不能有重复元素。(Java的容器类库没有实现bag,因为List提供了足够的功能)

  2Map一组成对的键值对对象。

   容器的打印

import com.bruceeckel.simpletest.*;
import java.util.*;

public class PrintingContainers {
  
private static Test monitor = new Test();
  
static Collection fill(Collection c) {
    c
.add("dog");
    c
.add("dog");
    c
.add("cat");
    
return c;
  
}
  
static Map fill(Map m) {
    m
.put("dog", "Bosco");
    m
.put("dog", "Spot");
    m
.put("cat", "Rags");
    
return m;
  
}
  
public static void main(String[] args) {
    
System.out.println(fill(new ArrayList()));
    
System.out.println(fill(new HashSet()));
    
System.out.println(fill(new HashMap()));
    
monitor.expect(new String[] {
      
"[dog, dog, cat]",
      
"[dog, cat]",
      
"{dog=Spot, cat=Rags}"
    
});
  
}
}

    Java的容器类有两种基本类型:ColleactionMap。区别在于容器中没个位置保存元素个数。Collection只能保存一个元素;Map保存的是键值对,“键”必须是唯一的,而“值”可以有重复,就像一个小型数据库。

  List按对象进入的顺序保存对象,不做排序或编辑操作(能按照对象加入的顺序输出对象)。Set对每个对象只接受一次,并使用自己内部的排序方法(通常,只需关心某个元素是否属于Set,而不需关心它的顺序-否则应该使用List)。Map同意对每个元素只保存一份,但这是给予的,Map也有内置的顺序,因而不关心添加的顺序。如果添加元素的顺序对你很重要,应该使用LinkedHashSet或者LinkedHashMap

    容器的缺点:未知类型

    使用Java容器有个缺点,在将对象加入容器的时候丢失了类型信息。容器只保存对Object的引用,Object是所有类的基类,因此容器可以保存任何类型的对象(当然不包括基本类型,因为他们便是真正的对象,没有继承任何东西)。

    1)因为在将对象的引用加入容器时就丢失了类型的信息,所以对于添入容器的对象没有类型限制,即使可以保持容器的类型,例如容器,别人还是可以轻易将放入容器。

    2)因为丢失了类型信息,所以容器只知道他保存的是指向对象的引用。在使用容器中的元素前必须要做类型转换,转换为正确类型

    好在Java并不会让人们误用容器中的对象。如果将丢入存放的容器,然后尝试将其中的每件东西都作为,当将指向的引用取出容器,并试着类型转换为时,会收到RuntimeException异常。

package c11;
import java.util.*;

public class CatsAndDogs {
  public static void main(String[] args) {
    List cats = new ArrayList();
    for(int i = 0; i < 7; i++)
      cats.add(new Cat(i));
   
// Not a problem to add a dog to cats:
    cats.add(new Dog(7));
    for(int i = 0; i < cats.size(); i++)
     
((Cat)cats.get(i)).id();
      // Dog is detected only at run time
  }
} ///:~

   在程序运行的时候,当将Dog对象类型转换为Cat时,会收到异常。

   有时候它也能工作

   某些情况下,即使没有将对象转回原本的类型,仍能正常工作。有一种特殊情况:编译器对String类有特别的支持,时期运作自如。如果编译器需要的是String对象,而它并没有得到,编译器就自动调用toString()方法。

 public class Mouse {
  private int mouseNumber;
  public Mouse(int i) { mouseNumber = i; }
  // Override Object.toString():
  public String
toString() {
    return "This is Mouse #" + mouseNumber;
  }
  public int getNumber() { return mouseNumber; }
} ///:~

public class MouseTrap {
  static void caughtYa(Object m) {
   
Mouse mouse = (Mouse)m; // Cast from Object
    System.out.println("Mouse: " + mouse.getNumber());
  }
} ///:~

    System.out.println("Free mouse: " + mice.get(i))

    编译器期待“+”号之后是一个String对象。get()返回一个Object,编译器为了得到所需的String会隐式地调用toString()。可惜这种神奇的工作方式仅限于String,对其他类型无效。

    迭代器 

    迭代器是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不必知道或关系该序列底层的结构。此外,迭代器通常被称为轻量级对象:创建它的代价小。因此,经常可以见到对迭代器有些奇怪的限制;例如某些迭代器只能单向移动。

    JavaIterator就是迭代器具有这些限制的例子,它只能用来

    1)使用方法iterator()要求返回一个Iterator。第一次调用Iteratornext()方法时,它发挥序列的第一个元素。

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

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

    4)使用remove()见迭代器新近返回的元素删除。

    Iterarot是迭代器最简单的实现,但是已经很有用了(而且还有为List而设计的更强大的ListIterator)。

import com.bruceeckel.simpletest.*;
import java.util.*;

public class CatsAndDogs2 {
  private static Test monitor = new Test();
  public static void main(String[] args) {
    List cats = new ArrayList();
    for(int i = 0; i < 7; i++)
      cats.add(new Cat(i));
   
Iterator e = cats.iterator();
   
while(e.hasNext())
      ((Cat)e.next()).id();
  }
} ///:~

 容器的分类法

点线箭头代表特定的类实现一个接口(若是抽象类,则表示部分实现了接口)。

与持有对象有关的接口CollectionListSetMap。最理想的情况是,代码只与这些接口打交道,仅在创建容器的时候说明的明确类型。由此可以这样创建一个List

List x = new LinkedList();

接口的优美之处(和目的)在于,如果你决定改变当前的实现,只需要在创建的位置做些修改即可,就像这样:

List x = new ArrayList();

图中:以Abstract开头的,他们只是部分实现了某个特定接口的简单工具而已。举个例子,如果要生成自己的Set,一般不会直接继承Set接口,然后实现所有的方法;而是应该继承AbstracetSet,然后做少量必要的工作来生成自己的新类。

 Collection的功能方法 

   过时的容器:VectorStackHashtable

    List的功能方法

    List实际上有两种类型:一种是基本的ArrayList,器优点在于随即访问元素;另一种是更强大的LinkedList,它并不是针对快速随即访问而设计的,而是具有一头更通用的方法。

LinkedList制作

   LinkedList具有能够直接实现的所有功能的方法,由此可以直接将LinkedList作为使用。不过,有时一个真正的更能把事情讲清楚:       

 import com.bruceeckel.simpletest.*;
import java.util.*;
import com.bruceeckel.util.*;

public class StackL {
  private static Test monitor = new Test();
  private LinkedList list = new LinkedList();
  public void
push(Object v) { list.addFirst(v); }
  public Object top() { return list.getFirst(); }
  public Object pop() { return list.removeFirst(); }
  public static void main(String[] args) {
    StackL stack = new StackL();
    for(int i = 0; i < 10; i++)
      stack.push(Collections2.countries.next());
    System.out.println(stack.top());
    System.out.println(stack.top());
    System.out.println(stack.pop());
    System.out.println(stack.pop());
    System.out.println(stack.pop());
    monitor.expect(new String[] {
      "CHAD",
      "CHAD",
      "CHAD",
      "CENTRAL AFRICAN REPUBLIC",
      "CAPE VERDE"
    });
  }
} ///:~

 

阅读(790) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~