12.JAVA编程思想——集合的类型

12.JAVA编程思想——集合的类型

欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/51100510

标准Java 1.0 和1.1 库配套提供了非常少的一系列集合类。但对于自己的大多数编程要求,它们基本上都能胜任。Java 1.2 提供的是一套重新设计过的大型集合库。

1      Vector

Vector 的用法很简单,大多数时候只需用addElement()插入对象,用elementAt()一次提取一个对象,并用elements()获得对序列的一个“枚举”。但仍有其他一系列方法是非常有用的。

Java 标准集合里包含了toString()方法,所以它们能生成自己的String 表达方式,包括它们容纳的对象。

Vector 中,toString()会在Vector 的各个元素中步进和遍历,并为每个元素调用toString()。假定在想打印出自己类的地址。看起来似乎简单地引用this 即可(特别是C++程序员有这样做的倾向):

1.1      代码如下:

import java.util.*;

publicclass CrashJava {

public String toString() {

return"CrashJava address: " +
this +"\n";

}

publicstaticvoidmain(String[]args){

Vector
v = new Vector();

for (inti = 0;
i < 10;i++)

v.addElement(new CrashJava());

System.out.println(v);

}

}

1.2      输出

Exceptionin thread "main" java.lang.StackOverflowError

at java.lang.StringBuilder.append(UnknownSource)

atjava.lang.StringBuilder.<init>(Unknown Source)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(UnknownSource)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(UnknownSource)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(UnknownSource)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(UnknownSource)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(UnknownSource)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(UnknownSource)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(UnknownSource)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(UnknownSource)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(UnknownSource)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(UnknownSource)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(UnknownSource)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(UnknownSource)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(UnknownSource)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(UnknownSource)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(UnknownSource)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(UnknownSource)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(UnknownSource)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(UnknownSource)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(UnknownSource)

at CrashJava.toString(CrashJava.java:5)

at java.lang.String.valueOf(Unknown Source)

at java.lang.StringBuilder.append(Unknown Source)

若只是简单地创建一个CrashJava 对象,并将其打印出来,就会得到无穷无尽的一系列违例错误。然而,假如将CrashJava对象置入一个Vector,并象这里演示的那样打印Vector,就不会出现什么错误提示,甚至连一个违例都不会出现。此时Java 只是简单地崩溃(但至少它没有崩溃我的操作系统)。

当我们使用下述语句时:

"CrashJava address: "+ this

编译器就在一个字串后面发现了一个“+”以及好象并非字串的其他东西,所以它会试图将this 转换成一个字串。转换时调用的是toString(),后者会产生一个递归调用。若在一个Vector 内出现这种事情,看起来堆栈就会溢出,同时违例控制机制根本没有机会作出响应。

若确实想在这种情况下打印出对象的地址,解决方案就是调用Object 的toString 方法。此时就不必加入this,只需使用super.toString()。当然,采取这种做法也有一个前提:我们必须从Object 直接继承,或者没有一个父类覆盖了toString 方法。

2      B i t S e t

BitSet 实际是由“二进制位”构成的一个Vector。如果希望高效率地保存大量“开-关”信息,就应使用BitSet。它只有从尺寸的角度看才有意义;如果希望的高效率的访问,那么它的速度会比使用一些固有类型的数组慢一些。

此外,BitSet 的最小长度是一个长整数(Long)的长度:64 位。这意味着假如我们准备保存比这更小的数据,如8 位数据,那么BitSet 就显得浪费了。所以最好创建自己的类,用它容纳自己的标志位。

在一个普通的Vector 中,随我们加入越来越多的元素,集合也会自我膨胀。在某种程度上,BitSet也不例外。有时会自行扩展,有时则不然。而且Java 的1.0 版本似乎在这方面做得最糟,它的BitSet 表现十分差强人意(Java1.1 已改正了这个问题)。

2.1      代码如下:

import java.util.*;

publicclass Bits {

publicstaticvoidmain(String[]args){

Random rand =new Random();

// Take the LSB of nextInt():

bytebt = (byte)rand.nextInt();

BitSet bb =new BitSet();

for (inti = 7;
i >= 0;i--)

if (((1 <<i) &
bt) != 0)

bb.set(i);

else

bb.clear(i);

System.out.println("byte value: " +bt);

printBitSet(bb);

shortst = (short)rand.nextInt();

BitSet bs =new BitSet();

for (inti = 15;
i >= 0;i--)

if (((1 <<i) &
st) != 0)

bs.set(i);

else

bs.clear(i);

System.out.println("short value: " +st);

printBitSet(bs);

intit =
rand.nextInt();

BitSet bi =new BitSet();

for (inti = 31;
i >= 0;i--)

if (((1 <<i) &
it) != 0)

bi.set(i);

else

bi.clear(i);

System.out.println("int value: " +it);

printBitSet(bi);

// Test bitsets >= 64 bits:

BitSet b127 =new BitSet();

b127.set(127);

System.out.println("set bit 127: " +b127);

BitSet b255 =new BitSet(65);

b255.set(255);

System.out.println("set bit 255: " +b255);

BitSet b1023 =new BitSet(512);

// Without the following, an exception is thrown

// in the Java 1.0 implementation of BitSet:

// b1023.set(1023);

b1023.set(1024);

System.out.println("set bit 1023: " +b1023);

}

staticvoidprintBitSet(BitSet
b){

System.out.println("bits: " +b);

String bbits =new String();

for (intj = 0;
j < b.size();
j++)

bbits += (b.get(j) ?"1" :
"0");

System.out.println("bit pattern: " +bbits);

}

} /// :~

2.2      输出如下:

bytevalue: 96

bits:{5, 6}

bitpattern: 0000011000000000000000000000000000000000000000000000000000000000

shortvalue: 28482

bits:{1, 6, 8, 9, 10, 11, 13, 14}

bitpattern: 0100001011110110000000000000000000000000000000000000000000000000

intvalue: -1121710154

bits:{1, 2, 4, 5, 7, 8, 9, 10, 11, 18, 21, 24, 26, 27, 28, 29, 31}

bitpattern: 0110110111110000001001001011110100000000000000000000000000000000

setbit 127: {127}

setbit 255: {255}

set bit 1023: {1024}

随机数字生成器用于创建一个随机的byte、short 和int。每一个都会转换成BitSet 内相应的位模型。此时一切都很正常,因为BitSet 是64 位的,所以它们都不会造成最终尺寸的增大。但在Java 1.0 中,一旦BitSet 大于64 位,就会出现一些令人迷惑不解的行为。假如我们设置一个只比BitSet 当前分配存储空间大出1 的一个位,它能够正常地扩展。但一旦试图在更高的位置设置位,同时不先接触边界,就会得到一个恼人的违例。这正是由于BitSet
在Java 1.0 里不能正确扩展造成的。本例创建了一个512 位的BitSet。构建器分配的存储空间是位数的两倍。所以假如设置位1024 或更高的位,同时没有先设置位1023,就会在Java1.0 里得到一个违例。但幸运的是,这个问题已在Java 1.1 得到了改正。如果是为Java 1.0 写代码,请尽量避免使用BitSet。

3      S t a c k

Stack 有时也可以称为“后入先出”(LIFO)集合。换言之,我们在堆栈里最后“压入”的东西将是以后第一个“弹出”的。和其他所有Java 集合一样,我们压入和弹出的都是“对象”,所以必须对自己弹出的东西进行“造型”。一种很少见的做法是拒绝使用Vector 作为一个Stack 的基本构成元素,而是从Vector 里“继承”一个Stack。这样一来,它就拥有了一个Vector 的所有特征及行为,另外加上一些额外的Stack 行为。很难判断出设计者到底是明确想这样做,还是属于一种固有的设计。

3.1      代码如下:

import java.util.*;

publicclass Stacks {

static String[]months = {
"January", "February",
"March","April","May","June","July","August","September",

"October","November","December" };

publicstaticvoidmain(String[]args){

Stack
stk = new Stack();

for (inti = 0;
i < months.length;i++)

stk.push(months[i] +" ");

System.out.println("stk = " +stk);

// Treating a stack as a Vector:

stk.addElement("The last line");

System.out.println("element 5 = " +stk.elementAt(5));

System.out.println("popping elements:");

while (!stk.empty())

System.out.println(stk.pop());

}

}

3.2      输出如下:

stk= [January , February , March , April , May , June , July , August , September, October , November , December ]

element5 = June

poppingelements:

Thelast line

December

November

October

September

August

July

June

May

April

March

February

January

months 数组的每一行都通过push()继承进入堆栈,稍后用pop()从堆栈的顶部将其取出。要声明的一点是,Vector 操作亦可针对Stack 对象进行。这可能是由继承的特质决定的——Stack“属于”一种Vector。因此,能对Vector 进行的操作亦可针对Stack 进行,例如elementAt()方法。

4      H a s h t a b l e

Vector 允许我们用一个数字从一系列对象中作出选择,所以它实际是将数字同对象关联起来了。但假如我们想根据其他标准选择一系列对象呢?堆栈就是这样的一个例子:它的选择标准是“最后压入堆栈的东西”。

这种“从一系列对象中选择”的概念亦可叫作一个“映射”、“字典”或者“关联数组”。从概念上讲,它看起来象一个Vector,但却不是通过数字来查找对象,而是用另一个对象来查找它们!这通常都属于一个程序中的重要进程。

在Java 中,这个概念具体反映到抽象类Dictionary 身上。该类的接口是非常直观的size()告诉我们其中包含了多少元素;isEmpty()判断是否包含了元素(是则为true);put(Object key, Object value)添加一个值(我们希望的东西),并将其同一个键关联起来(想用于搜索它的东西);get(Object key)获得与某个键对应的值;而remove(ObjectKey)用于从列表中删除“键-值”对。还可以使用枚举技术:keys()产生对键的一个枚举(Enumeration);而elements()产生对所有值的一个枚举。这便是一个Dict
ionary(字典)的全部。

Dictionary 的实现过程并不麻烦。下面列出一种简单的方法,它使用了两个Vector,一个用于容纳键,另一个用来容纳值:

4.1      代码1如下:

import java.util.*;

publicclass AssocArray
extendsDictionary{

privateVector
keys = newVector();

privateVector
values = newVector();

publicintsize() {

returnkeys.size();

}

publicbooleanisEmpty() {

returnkeys.isEmpty();

}

public Object put(Objectkey, Object
value){

keys.addElement(key);

values.addElement(value);

returnkey;

}

public Object get(Objectkey) {

intindex =
keys.indexOf(key);

// indexOf() Returns -1 if key not found:

if (index == -1)

returnnull;

returnvalues.elementAt(index);

}

public Object remove(Objectkey) {

intindex =
keys.indexOf(key);

if (index == -1)

returnnull;

keys.removeElementAt(index);

Object returnval =values.elementAt(index);

values.removeElementAt(index);

returnreturnval;

}

publicEnumeration keys() {

returnkeys.elements();

}

publicEnumeration elements() {

returnvalues.elements();

}

// Test it:

publicstaticvoidmain(String[]args){

AssocArray aa =new AssocArray();

for (charc =
‘a‘;c <=‘z‘;
c++)

aa.put(String.valueOf(c), String.valueOf(c).toUpperCase());

char[]ca = {
‘a‘,‘e‘,‘i‘,‘o‘,‘u‘};

for (inti = 0;
i < ca.length;
i++)

System.out.println("Uppercase: "+aa.get(String.valueOf(ca[i])));

}

} /// :~

4.2      输出1如下:

Uppercase:A

Uppercase:E

Uppercase:I

Uppercase:O

Uppercase:U

在对AssocArray 的定义中,它“扩展”了字典。AssocArray 属于Dictionary 的一种类型,所以可对其发出与Dictionary 一样的请求。如果想生成自己的Dictionary,而且就在这里进行,那么要做的全部事情只是填充位于Dictionary内的所有方法(而且必须覆盖所有方法,因为它们——除构建器外——都是抽象的)。

Vector key 和value 通过一个标准索引编号链接起来。也就是说,如果用“roof”的一个键以及“blue”的一个值调用put()——假定我们准备将一个房子的各部分与它们的油漆颜色关联起来,而且AssocArray 里已有100 个元素,那么“roof”就会有101 个键元素,而“blue”有101 个值元素。而且要注意一下get(),假如我们作为键传递“roof”,它就会产生与keys.index.Of()的索引编号,然后用那个索引编号生成相关的值矢量内的值。

main()中进行的测试是非常简单的;将小写字符转换成大写字符,这显然可用更有效的方式进行。向我们揭示出了AssocArray 的强大功能。

标准Java 库只包含Dictionary 的一个变种,名为Hashtable(散列表)。Java 的散列表具有与AssocArray 相同的接口(因为两者都是从Dictionary 继承来的)。但有一个方面却反映出了差别:执行效率。若仔细想想必须为一个get()做的事情,就会发现在一个Vector 里搜索键的速度要慢得多。但此时用散列表却可以加快不少速度。不必用冗长的线性搜索技术来查找一个键,而是用一个特殊的值,名为“散列码”。散列码可以获取对象中的信息,然后将其转换成那个对象“相对唯一”的整数(int)。所有对象都有一个散列码,而hashCode()是根类Object
的一个方法。Hashtable 获取对象的hashCode(),然后用它快速查找键。这样可使性能得到大幅度提升。

现在只需要知道散列表是一种快速的“字典”(Dictionary)即可,而字典是一种非常有用的工具。

4.3      代码2

应用散列表的一个例子,可考虑用一个程序来检验Java 的Math.random() 方法的随机性到底如何。在理想情况下,它应该产生一系列完美的随机分布数字。但为了验证这一点,我们需要生成数量众多的随机数字,然后计算落在不同范围内的数字多少。散列表可以极大简化这一工作,因为它能将对象同对象关联起来

importjava.util.*;

classCounter {

inti = 1;

publicString toString() {

returnInteger.toString(i);

}

}

classStatistics {

publicstaticvoidmain(String[]args)
{

Hashtable
ht =newHashtable();

for (inti
= 0; i < 10000;i++) {

// Produce anumber between 0 and 20:

Integerr =new
Integer((int) (Math.random()* 20));

if (ht.containsKey(r))

((Counter)ht.get(r)).i++;

else

ht.put(r,newCounter());

}

System.out.println(ht);

}

}
/// :~

4.4      输出如下:

{19=489,18=510, 17=478, 16=509, 15=515, 14=477, 13=479, 12=523, 11=504, 10=501, 9=522,8=489, 7=508, 6=481, 5=505, 4=502, 3=542, 2=481, 1=528, 0=457}

在main()中,每次产生一个随机数字,它都会封装到一个Integer 对象里,使句柄能够随同散列表一起使用(不可对一个集合使用基本数据类型,只能使用对象句柄)。containKey()方法检查这个键是否已经在集合里(也就是说,那个数字以前发现过吗?)若已在集合里,则get()方法获得那个键关联的值,此时是一个Counter(计数器)对象。计数器内的值i 随后会增加1,表明这个特定的随机数字又出现了一次。

假如键以前尚未发现过,那么方法put()仍然会在散列表内置入一个新的“键-值”对。在创建之初,Counter 会自己的变量i 自动初始化为1,它标志着该随机数字的第一次出现。

为显示散列表,只需把它简单地打印出来即可。Hashtable toString()方法能遍历所有键-值对,并为每一对都调用toString()。Integer toString() 是事先定义好的,可看到计数器使用的toString。

或许会对Counter 类是否必要感到疑惑,它看起来似乎根本没有封装类Integer 的功能。为什么不用int 或Integer 呢?事实上,由于所有集合能容纳的仅有对象句柄,所以根本不可以使用整数。学过集合后,封装类的概念对大家来说就可能更容易理解了,因为不可以将任何基本数据类型置入集合里。然而,我们对Java 封装器能做的唯一事情就是将其初始化成一个特定的值,然后读取那个值。也就是说,一旦封装器对象已经创建,就没有办法改变一个值。这使得Integer
封装器对解决我们的问题毫无意义,所以不得不创建一个新类,用它来满足自己的要求。

4.5      代码3创建关键类

用一个标准库的类(Integer)作为Hashtable 的一个键使用。作为一个键,它能很好地工作,因为它已经具备正确运行的所有条件。但在使用散列表的时候,一旦我们创建自己的类作为键使

用,就会遇到一个很常见的问题。例如,假设一套天气预报系统将Groundhog(土拔鼠)对象匹配成Prediction(预报)。这看起来非常直观:我们创建两个类,然后将Groundhog作为键使用,而将Prediction 作为值使用。如下所示:

importjava.util.*;

classGroundhog {

intghNumber;

Groundhog(intn)
{

ghNumber =n;

}

}

classPrediction {

booleanshadow = Math.random() > 0.5;

publicString toString() {

if (shadow)

return"Six more weeks of Winter!";

else

return"Early Spring!";

}

}

publicclass SpringDetector {

publicstaticvoidmain(String[]args)
{

Hashtable
ht =newHashtable();

for (inti
= 0; i < 10;i++)

ht.put(new
Groundhog(i),
new
Prediction())
;

System.out.println("ht
= " + ht +"\n");

System.out.println("Looking
up prediction for groundhog #3:");

Groundhoggh =new
Groundhog(3);

if (ht.containsKey(gh))

System.out.println((Prediction)ht.get(gh));

}

}

4.6      输出3

ht= {[email protected]=Early Spring!, [email protected]=Early Spring!,[email protected]=Early Spring!, [email protected]=Early Spring!,[email protected]=Six more weeks of Winter!, [email protected]=Six more weeksof Winter!,
[email protected]=Six more weeks of Winter!,[email protected]=Early Spring!, [email protected]=Early Spring!,[email protected]=Six more weeks of Winter!}

Lookingup prediction for groundhog #3:

每个Groundhog 都具有一个标识号码,所以赤了在散列表中查找一个Prediction,只需指示它“告诉我与Groundhog 号码3 相关的Prediction”。Prediction类包含了一个布尔值,用Math.random()进行初始化,以及一个toString()为我们解释结果。在main()中,用Groundhog 以及与它们相关的Prediction 填充一个散列表。散列表被打印出来,以便我们看到它们确实已被填充。随后,用标识号码为3
的一个Groundhog 查找与Groundhog#3 对应的预报。

看起来似乎非常简单,但实际是不可行的。问题在于Groundhog 是从通用的Object 根类继承的(若当初未指定基础类,则所有类最终都是从Object 继承的)。事实上是用Object 的hashCode()方法生成每个对象的散列码,而且默认情况下只使用它的对象的地址。所以,Groundhog(3)的第一个实例并不会产生与Groundhog(3)第二个实例相等的散列码。

用代码4进行检索。除了正确地覆盖hashCode()。再做另一件事情:覆盖也属于Object 一部分的equals()。当散列表试图判断我们的键是否等于表内的某个键时,就会用到这个方法。同样地,默认的Object.equals()只是简单地比较对象地址,所以一个Groundhog(3)并不等于另一个Groundhog(3)。

因此,为了在散列表中将自己的类作为键使用,必须同时覆盖hashCode()和equals(),如下

4.7      代码4

import java.util.*;

class Groundhog2 {

intghNumber;

Groundhog2(intn) {

ghNumber =n;

}

publicinthashCode() {

returnghNumber;

}

publicbooleanequals(Object
o) {

return (oinstanceof Groundhog2) && (ghNumber
== ((Groundhog2)o).ghNumber);

}

}

publicclass SpringDetector2 {

publicstaticvoidmain(String[]args){

Hashtable
ht = new Hashtable();

for (inti = 0;
i < 10;i++)

ht.put(new Groundhog2(i),new
Prediction())
;

System.out.println("ht = " +ht +
"\n");

System.out.println("Looking up prediction for groundhog #3:");

Groundhog2 gh =new Groundhog2(3);

if (ht.containsKey(gh))

System.out.println((Prediction)ht.get(gh));

}

}

4.8      输出4

ht= {[email protected]=Six more weeks of Winter!, [email protected]=Six more weeks ofWinter!, [email protected]=Six more weeks of Winter!, [email protected]=Six more weeks ofWinter!, [email protected]=Early Spring!, [email protected]=Six more weeks of Winter!,[email protected]=Six
more weeks of Winter!, [email protected]=Six more weeks of Winter!,[email protected]=Six more weeks of Winter!, [email protected]=Early Spring!}

Lookingup prediction for groundhog #3:

Sixmore weeks of Winter!

这段代码使用了来自前一个例子的Prediction,所以SpringDetector.java 必须首先编译,否则就会在试图编译SpringDetector2.java 时得到一个编译期错误。

Groundhog2.hashCode()将土拔鼠号码作为一个标识符返回(在这个例子中,程序员需要保证没有两个土拔鼠用同样的ID 号码并存)。为了返回一个独一无二的标识符,并不需要hashCode(),equals()方法必须能够严格判断两个对象是否相等。

equals()方法要进行两种检查:检查对象是否为null;若不为null ,则继续检查是否为Groundhog2 的一个实例(要用到instanceof 关键字)。即使为了继续执行equals(),它也应该是一个Groundhog2。这种比较建立在实际ghNumber 的基础上。这一次一旦我们运行程序,就

会看到它终于产生了正确的输出(许多Java 库的类都覆盖了hashcode() 和equals()方法,以便与自己提供的内容适应)。

在以前笔记中,我们使用了一个名为Properties(属性)的Hashtable 类型。在那个例子中,下述程序行:

Properties p =System.getProperties();

p.list(System.out);

调用了一个名为getProperties()的static 方法,用于获得一个特殊的Properties 对象,对系统的某些特征进行描述。list()属于Properties 的一个方法,可将内容发给我们选择的任何流式输出。也有一个save()方法,可用它将属性列表写入一个文件,以便日后用load()方法读取。尽管Properties 类是从Hashtable 继承的,但它也包含了一个散列表,用于容纳“默认”属性的列表。所以假如没有在主列表里找到一个属性,就会自动搜索默认属性。

Properties 类在Java 库的用户文档中,往往可以找到更多、更详细的说明。

5      再论枚举器

演示Enumeration(枚举)的真正威力:将穿越一个序列的操作与那个序列的基础结构分隔开。PrintData 类用一个Enumeration 在一个序列中移动,并为每个对象都调用toString()方法。此时创建了两个不同类型的集合:一个Vector 和一个Hashtable。并且在它们里面分别填充Mouse 和Hamster 对象(先编译HamsterMaze.java 和WorksAnyway.java,否则下面的程序不能编译)。由于Enumeration
隐藏了基层集合的结构,所以PrintData 不知道或者不关心Enumeration 来自于什么类型的集合:

5.1      代码

WorksAnyway.java:

importjava.util.*;

classMouse {

privateintmouseNumber;

Mouse(inti)
{

mouseNumber =i;

}

// Magic method:

publicString toString() {

return"This is Mouse #"
+mouseNumber;

}

voidprint(Stringmsg) {

if (msg
!=null)

System.out.println(msg);

System.out.println("Mouse
number " + mouseNumber);

}

}

classMouseTrap {

staticvoid caughtYa(Objectm)
{

Mousemouse =(Mouse)m;//
Cast fromObject

mouse.print("Caught
one!");

}

}

publicclass WorksAnyway {

publicstaticvoidmain(String[]args)
{

Vector
mice =newVector();

for (inti
= 0; i < 3;i++)

mice.addElement(new
Mouse(i))
;

for (inti
= 0; i <mice.size();i++)
{

// No castnecessary, automatic call

// toObject.toString():

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

MouseTrap.caughtYa(mice.elementAt(i));

}

}

}

Enumerators2.java

import java.util.*;

class PrintData {

staticvoidprint(Enumeration
e) {

while (e.hasMoreElements())

System.out.println(e.nextElement().toString());

}

}

class Enumerators2 {

publicstaticvoidmain(String[]args){

Vector
v = new Vector();

for (inti = 0;
i < 5;i++)

v.addElement(new Mouse(i));

Hashtable
h = new Hashtable();

for (inti = 0;
i < 5;i++)

h.put(new Integer(i),new
Hamster(i))
;

System.out.println("Vector");

PrintData.print(v.elements());

System.out.println("Hashtable");

PrintData.print(h.elements());

}

} /// :~

5.2      输出

Vector

Thisis Mouse #0

Thisis Mouse #1

Thisis Mouse #2

Thisis Mouse #3

Thisis Mouse #4

Hashtable

Thisis Hamster #4

Thisis Hamster #3

Thisis Hamster #2

Thisis Hamster #1

Thisis Hamster #0

注意PrintData.print()利用了这些集合中的对象属于Object 类这一事实,所以它调用了toString()。但在解决自己的实际问题时,经常都要保证自己的Enumeration 穿越某种特定类型的集合。例如,可能要求集合中的所有元素都是一个Shape(几何形状),并含有draw()方法。若出现这种情况,必须从Enumeration.nextElement()返回的Object 进行下溯造型,以便产生一个Shape。

时间: 2024-10-14 00:53:55

12.JAVA编程思想——集合的类型的相关文章

Java编程思想(十五) —— 类型信息之反射

讲完.class,Class之后,继续. 1)泛化的Class引用 Class也可以加入泛型,加入之后会进行类型检查. 贴一下书上原话,Class<?>优于Class,虽然他们是等价的,Class<?>的好处是碰巧或疏忽使用了一个非具体的类引用.我搞不懂这个所谓非具体是什么? 后面弄懂了,其实<?>作为通配符,就是未知的,直接写结论的话不能写个具体类型吧,作者的意思其实就是说加了泛型的Class就是选择了非具体的版本. 加入泛型的原因是提供编译期间的类型检查,操作失误的

Java编程思想(十四) —— 类型信息RTTI(1)

译者翻译的时候有些奇怪,Runtime type information (RTTI) allows you to discover and use type information while a program is running. 运行时类型信息(原来的翻译没有括号这里面的内容,Runtime type information,简称RTTI,个人觉得这样注释比较好)可以让你在程序运行的时候发现和使用类型信息.后面直接出现RTTI让人疑惑. 1)为什么需要RTTI 之前的多态的例子中: p

Java编程思想(十八) —— 再谈反射

在Java编程思想(十五) -- 类型信息之反射和Java编程思想(十六) -- 联系JVM再谈Class,书上只用了3页就讲完了,还有讲了那么多Class的东西,接下来要从反射中怎么用,自己结合API和其他资料再写多一些. 示例:Test.java public class Test { public Test() {     }      public Test(int i) {         System.out.println(i);     } private void pri()

java编程排序之自定义类型的集合,按业务需求排序

自定义引用类型放入集合中,按实际业务需求进行排序的两种思路 第一种思路: (1)自定义实体类实现java.lang.Comparable接口,重写public int compareTo(Object obj)方法.自定义业务比较规则 (2)利用java.util.Collections类的静态方法sort(List<自定义类型> list)进行排序(默认升序)或者.自己编写排序工具类.冒泡+compareTo(obj)方法 第二种思路 (1)自己编写业务比较规则类.实体类不用实现任何借口.业

Java 编程思想 Chapter_14 类型信息

本章内容绕不开一个名词:RTTI(Run-time Type Identification) 运行时期的类型识别 知乎上有人推断作者是从C++中引入这个概念的,反正也无所谓,理解并能串联本章知识才是最重要的 本章的内容其实都是为类型信息服务的,主要内容有 一.Class对象 问题: 1.Class对象的创建过程是怎么样的 2.Class对象有哪几种创建方式,之间有什么差异 3.使用泛型 在了解类型信息之前,需要了解class对象 创建class对象,需要先查找这个这个类的.class文件, 查找

java编程思想-枚举类型思维导图

后续加深理解,感觉java编程思想内容博大精深,java每个特性都有很全面深入的讲解,实际工作中一般很难会遇到,期望以后会用到 把书中的代码copy到eclipse中,理解会更快一些

java编程思想总结(二)

java编程思想总结(二) java编程思想总结是一个持续更新的系列,是本人对自己多年工作中使用到java的一个经验性总结,也是温故而知新吧,因为很多基础的东西过了这么多年,平时工作中用不到也会遗忘掉,所以看看书,上上网,查查资料,也算是记录下自己的笔记吧,过一段时间之后再来看看也是蛮不错的,也希望能帮助到正在学习的人们,本系列将要总结一下几点: 面向对象的编程思想 java的基本语法 一些有趣的框架解析 实战项目的整体思路 代码的优化以及性能调优的几种方案 整体项目的规划和视角 其它遗漏的东西

JAVA编程规则【转自java编程思想】

本附录包含了大量有用的建议,帮助大家进行低级程序设计,并提供了代码编写的一般性指导: (1) 类名首字母应该大写.字段.方法以及对象(句柄)的首字母应小写.对于所有标识符,其中包含的所有单词都应紧靠在一起,而且大写中间单词的首字母.例如:ThisIsAClassNamethisIsMethodOrFieldName若在定义中出现了常数初始化字符,则大写static final基本类型标识符中的所有字母.这样便可标志出它们属于编译期的常数.Java包(Package)属于一种特殊情况:它们全都是小

【Java编程思想】一、对象导论

作为一个电子专业的人,在学习了将近3年的嵌入式技术后,决定投奔移动互联网,在互联网大潮中急流勇进! 为了学习OOP(Object-oriented Programming),为了转向移动互联网,我决定开始学习android开发,那么就从Java开始吧! Java的学习资料很多,在研究几天之后,决定从<Java编程思想>这本书开始. 而在这本书之前,我已经看完了一个培训导师的Java4Android的Java教学视频,看的很快,因为我学过C和C++. 但我的Java水平依旧很差,主要在于面向对象