分类:
2008-09-16 21:37:13
数组
在Java中,数组是一种效率最高的存储和随机访问对象引用序列的方式。
在Java中,无论使用数组或容器,都有边界检查。如果越界操作就会得到一个RuntimeException异常。
容器类:List、Set和Map,它们不以具体的类型来处理对象。换句话来说,它们将所有对象都按Object类型处理,即Java中所有类的基类。这种方式的好处:你只需构建一个容器,任意的Java对象都可以放入其中。(除开基本类型之外,数组可以保存基本类型,而容器则不能)
考虑到效率与类型检查,应该尽可能使用数组。
数组是第一级对象
对象数组(Object[])保存的是引用,而基本类型数组(int [])保存的是基本类型的值。
新生成一个数组对象时,其中所有的引用被自动初始化为null;所以检查其中引用是否为null,即可知道数组对象的某个数组是否存有对象。同样,基本类型的数组如果是数值型的,就被自动初始化为0;如果是字符型(char)的,就被自动初始化为(char)0;如果是布尔型(boolean),就被自动初始化为false。
基本类型的容器
容器类只能保存对象的引用。而数组可以创建为直接保存基本类型,还可以保存对象的引用。在容器中可以使用“包装”类,例如Integer、Double等,以把基本类型的值放入容器中。
返回一个数组
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.*; |
sort()需要把参数的类型转变为Comparable,然后进行排序。
数组排序
import com.bruceeckel.util.*; |
String排序算法默认依据词典编顺序排序,所以大写字符开头的此都放在前面输出,然后才是小写字母开头的词。如果想忽略大小写字符将单词都放在一起排序,可以定义自己的Comparator类,然后覆盖器默认的String Comparable的行为。
package com.bruceeckel.util; |
Java标准类库中的排序算法针对你正排序的特殊类型进行了优化,针对基本类型设计的“快速排序”(quicksort),以及针对对象设计的“稳定归并排序”。
容器
Java2容器类类库的用途是“保存对象”,并将器划分为两个不同的概念:
1)Colleaction。一组独立的元素,通常这些元素都服从某种规则。List必须保持元素特定的顺序,而Set不能有重复元素。(Java的容器类库没有实现bag,因为List提供了足够的功能)
2)Map。一组成对的“键值对”对象。
容器的打印
import com.bruceeckel.simpletest.*; |
Java的容器类有两种基本类型:Colleaction和Map。区别在于容器中没个位置保存元素个数。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,对其他类型无效。
迭代器
迭代器是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不必知道或关系该序列底层的结构。此外,迭代器通常被称为“轻量级”对象:创建它的代价小。因此,经常可以见到对迭代器有些奇怪的限制;例如某些迭代器只能单向移动。
Java的Iterator就是迭代器具有这些限制的例子,它只能用来
1)使用方法iterator()要求返回一个Iterator。第一次调用Iterator的next()方法时,它发挥序列的第一个元素。
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();
}
} ///:~
容器的分类法
点线箭头代表特定的类实现一个接口(若是抽象类,则表示部分实现了接口)。
与持有对象有关的接口是Collection、List、Set和Map。最理想的情况是,代码只与这些接口打交道,仅在创建容器的时候说明的明确类型。由此可以这样创建一个List:
List x = new LinkedList();
接口的优美之处(和目的)在于,如果你决定改变当前的实现,只需要在创建的位置做些修改即可,就像这样:
List x = new ArrayList();
图中:以Abstract开头的,他们只是部分实现了某个特定接口的简单工具而已。举个例子,如果要生成自己的Set,一般不会直接继承Set接口,然后实现所有的方法;而是应该继承AbstracetSet,然后做少量必要的工作来生成自己的新类。
Collection的功能方法
过时的容器:Vector、Stack和Hashtable
List的功能方法
List实际上有两种类型:一种是基本的ArrayList,器优点在于随即访问元素;另一种是更强大的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"
});
}
} ///:~