thank in java 笔记——第八章 对象的容纳

thank in java 笔记——第八章 对象的容纳

在某些程序开发的时候,只有当程序真正的运行时才会知道某一些的的对象时必须的。在没有运行情况下这是不得而知的。因此为了解决这样的问题,Java提供了容纳对象(或者对象的句柄)的多种方式,其中内建的类型是数组,我们之前已经讨论过它,本章准备加深大家对他的认识。此外,Java的工具库提供了一些“集合类”。利用这些集合类,我们可以容纳乃至操纵自己的对象。

8.1 数组

针对容纳对象来说,有两方面的问题将数组数组和其他集合类型区分开来:效率和类型。对于Java来说,为保存和访问一系列对象(实际是对象的句柄)数组,最有效的方法莫过于数组。数组实际代表一个简单的线性序列,它使得元素的访问速度非常快,但我们去要为这种速度付出代价:创建一个数组对象时,它的大小是固定的,而且不可再那个数组对象的“存在时间”内发生改变。可创建特定大小的数组,然后利用光了存储空间,就在创建一个新数组,将所有的句柄从旧数组移到新数组。这属于“矢量“(vector)类的行为。然而由于为这种大小的灵活性要付出代价,所以我们认为矢量的效率并没有数组高。

其他Java集合类型包括:vector(矢量)、Stack(堆栈)以及Hashtable(散列表)。上诉集合类型并没有什么特定的类型。换言之,它们将其当做Object类型处理。从某些角度来说,这种处理方法是非常合理的:我们仅需要构建一个集合,然后任何对象都可以进入这个集合。这再一次反应了数组优于常规集合:创建一个数组时,可令其容纳一种特定的类型。这意味着可进行编译期类型检查,预防自己设置了错误的类型,或者错误指定了准备提取的类型。当然,在编译器或者运行期,Java会防止我们讲不当的消息发给一个对象。所以我们不必考虑自己的那种做法更危险,只要编译器能及时指出错误,同时运行期间加快速度,目的也就达到了。

8.1.1 数组和第一类对象

无论使用的数组属于什么类型,数组标志符实际都是指向真是对象的一个句柄。那些对象本身是在内存“堆”里创建的,堆对象既可“隐式”创建(即默认产生),亦可“显示”创建(即明确指定,用一个new表达式)。堆对象的一部分(实际是我们能访问的唯一字段或方法)是制度的length成员,它告诉我们那个数组对象最多容纳多少元素。对于数组对象,“[]”语法是我们能唯一采用的唯一另一类访问方法。示例:

package package08;public class ArraySize {public static void main(String[] args) { Weeble[] a; Weeble[] b = new Weeble[5]; Weeble[] c = new Weeble[4]; for(int i = 0;i < c.length;i++){ c[i] = new Weeble(); } Weeble[] d = { new Weeble(),new Weeble(),new Weeble() }; System.out.println("b.length=" + b.length); for(int i = 0;i < b.length;i++) System.out.println("b["+ i +"]="+b[i]); System.out.println("c.length="+c.length); System.out.println("d.length="+d.length); a = d; System.out.println("a.length="+a.length); a = new Weeble[]{ new Weeble(), new Weeble() }; System.out.println("a.length=" +a.length); int[] e; int[] f = new int[5]; int[] g = new int[4]; for(int i = 0;i

上述例子展示了对数组进行初始化的不同方式,以及如何将数组句柄分配给不同的数组对象。它也揭示出对象数组和基本数据类型数组在使用方法上的几乎是完全一致的。唯一的差别在于对象数组容纳的是句柄而基本数据类型容纳的是具体数值。

基本数据类型集合

集合类只能容纳对象句柄。但对于一个数组,却既可令其直接容纳指向对象的句柄。领用象Integer、Double之类的“封装器”类,可将基本数据类的值置入一个集合里。

8.1.2 数组的返回

由于垃圾收集器的存在,Java中写一个方法用作返回一系列东西,比C/C++要来的简单。虽然Java返回的任然是数组的指针但是,由于垃圾收集器的存在,无需考虑在java这个数组是否可用——只要需要,它就会自动存在,而且垃圾收集器会在我们完成后自动将其清除。示例:

package package08;public class IceCream {static String[] flav = { "Chocolate","strawberry","Vanilla Fudge Swirl","Mint Chip", "Mocha Almond Fudge","Rum Raisin","Praline Cream","Mud Pie"};static String[] flavorSet(int n){ n =Math.abs(n)%(flav.length + 1); String[] results = new String[n]; int [] picks = new int[n]; for (int i = 0; i < picks.length; i++) { picks[i] = -1; } for (int i = 0; i < picks.length; i++) { retry: while(true){ int t = (int)(Math.random()*flav.length); for(int j = 0;j

8.2 集合

总结之前提到过的知识:为容纳一组对象,最适宜的选择应当是数组。而假如容纳的是一系列基本数据,更是必须采用数组。在编写程序的过程中,通常不能确切地知道最终需要多少个对象。有些时候甚至想用更复杂的方式来保存对象。为解决这个问题,Java提供了四中类型的“集合类”:Vector(矢量)、BitSet(位集)、Stack(堆栈)以及Hashtable(散列表)。与拥有集合功能的其他语言相比,尽管这儿的数量显得相当少,但仍能用它们解决数量惊人的实际问题。这些集合类具有形形色色的特征。例如,stack实现了一个LIFO(先入先出)序列,而Hashtable是一种“关联数组”,允许我们将任何对象关联起来。除此之外,所有Java集合类都能自动改变自身的大小。所以,在编程的时可以使用数量众多的对象,同时不必担心会将集合弄得有多大。

8.2.1 缺点:类型未知

使用Java集合的“缺点”是在将对象置入一个集合时丢失了类型信息。之所以会发生这种情况,是由于当初编写集合时,那个集合的程序员根本不知道用户到底把什么类型置入集合。若指示某个集合只允许特定的类型,会妨碍它成为一个“常规用途”的工具,为用户带来麻烦。为解决这个问题,集合实际容纳得是类型为object的一些对象的句柄。这种类型当然代表Java中的所有对象,因为他是所有类的根。当然,也要注意这并不包括基本数据类型,因为他们并不是从“任何东西”继承来的。这是一个很好的方案,只是并不适用下诉场合:

(1)将一个对象句柄置入集合时,由于类型会被抛弃,所以任何类型的对象都可进入我们的集合——即便特别指示它能容纳特定类型的对象。举个例子来说,虽然指示它只能容纳猫,但事实上任何人都可以把一条狗扔进来。

(2)由于类型信息不复存在,所以集合能肯定的唯一事情就是自己容纳的是一个对象的句柄。正式使用它之前,必须对其进行造型,使其具有正确的类型。

总而言之,就是进入集合相当于将进入的任何带有类型的对象进行清洗获得“白身”,使用的时候需要从集合出去,需要重新染色,变成“色身”。

值得欣慰的是,Java不允许人们滥用置入集合的对象。假如将一条狗扔进一个猫的集合,那么仍会将集合内的所有东西看作猫,所以在使用那条狗时会得到一个“违例”错误。在同样的意义上,假如试图将一条狗的句柄“造型”到一只猫。那么运行期间仍会得到一个“违例”错误。示例:

package package08;import java.util.*;class Cat {private int catNumber;Cat(int i){ catNumber = i;}void print(){ System.out.println("Cat #" + catNumber);}}package package08;class Dog {private int dogNumber;Dog(int i){ dogNumber = i;}void print(){ System.out.println("Dog #" +dogNumber);}}package package08;import java.util.Vector;public class CatAndDog {public static void main(String[] args) { Vector cats = new Vector(); for (int i = 0; i < 7; i++) { cats.addElement(new Cat(i)); } for (int i = 0; i < cats.size(); i++) { ((Cat)cats.elementAt(i)).print(); }}}输出:Cat #0Cat #1Cat #2Cat #3Cat #4Cat #5Cat #6

可以看出,Vector的使用是非常简单的:先创建一个,再用addElement()置入对象,以后用elementAt()取出那些对象。dog不能以在cat的集合中输出dog。

1.错误有时候并不会显露出来

在某些情况下,程序似乎正确的工作,不造型回我们与那里的类型。第一种情况是相当特殊的:String类从编译器获得了额外的帮助,使其能够正常的工作。只要编译器期待的是一个String对象,但它没有得到一个,就会自动调用在Object里定义、并且能够由任何Java类覆盖的toString()方法。这个方法能生成满足要求的String对象,然后在我们需要的时候使用。

因此,为了让自己类的对象能够显示出来,要做的全部事情就是覆盖toString()方法,如下示例:

package package08;import java.util.*;class Mouse {private int mouseNumber;Mouse(int i){ mouseNumber = i;}public String toString(){ return "This is Mouse #" + mouseNumber;}void print(String msg){ if (msg != null) { System.out.println(msg); } System.out.println("Mouse number" + mouseNumber);}}package package08;class MouseTrap {static void caughtYa(Object m){ Mouse mouse = (Mouse)m; mouse.print("caught one!");}}package package08;import java.util.Vector;public class WorksAnyway {public static void main(String[] args) { Vector mice = new Vector(); for (int i = 0; i < 3; i++) { mice.addElement(new Mouse(i)); } for (int i = 0; i < mice.size(); i++) { System.out.println("Free mouse:"+ mice.elementAt(i)); MouseTrap.caughtYa(mice.elementAt(i)); }}}输出:Free mouse:This is Mouse #0caught one!Mouse number0Free mouse:This is Mouse #1caught one!Mouse number1Free mouse:This is Mouse #2caught one!Mouse number2

2.生成自动判别类型的Vector

用Vector创建一个新类,使其只接收我们指定的类型,也只生成我们希望的类型。如下所示:

package package08;class Gopher {private int gopherNumber;Gopher(int i){ gopherNumber = i;}void print(String msg){ if (msg != null) { System.out.println(msg); } System.out.println( "Gopher number" + gopherNumber);}}package package08;class GopherTrap {static void caughtYa(Gopher g){ g.print("Caught one!");}}package package08;import java.util.Vector;class GopherVector {private Vector v = new Vector();public void addElement(Gopher m){ v.addElement(m);}public Gopher elementAt(int index){ return(Gopher)v.elementAt(index);}public int size(){ return v.size();}public static void main(String[] args){ GopherVector gophers = new GopherVector(); for (int i = 0; i < 3; i++) { gophers.addElement(new Gopher(i)); } for (int i = 0; i < gophers.size(); i++) { GopherTrap.caughtYa(gophers.elementAt(i)); }}}输出:Caught one!Gopher number0Caught one!Gopher number1Caught one!Gopher number2

这与之前的一个例子类似,只是新的GopherVector类有一个类型为Vector的private成员,而且方法也和Vector类似。然而,他不会接受和产生普通的Object,只对Gopher对象感兴趣。

3.参数化类型

这类问题并不是孤立的——我们许多时候都要在其他类型的基础上创建新类型。此时,在编译器件拥有特定的类型信息是非常有帮助的。这便是“参数化类型”的概念。在C++中,他是由语言通过“模板”获得了直接的支持。至少,java保留了关键字generic,期望有一天能够支持参数化类型。但我们现在无法确定这一天何时来临。

8.3 枚举器(反复器)

在任何集合类中,必须通过某种方法在某中置入对象,再用另一种方法从取得对象。毕竟,容纳各种各样的对象正式集合的首要任务。在Vecrot中,addElement()便是我们插入对象采用的方法,而elementAt()是提取对象的唯一方法。vector非常灵活,我们可以在任何时候选择任何东西,并可使用不同的索引选择多个元素。

8.4 集合的类型

8.4.1 Vector

1.崩溃Java

Java标准集合里包含了toString()方法,所以他们能生成自己的String表达方式,包括他们容纳的对象。例如在Vector中,toString()会在Vector的各个元素中进步和遍历,并未每个元素调用toString()。假定我们现在想打印出自己类的地址。看起来似乎简单地引用this即可:

package package08;import java.util.*;public class CrashJava {public String toString(){ return "CrashJava address:"+ this +"\n";}public static void main(String[] args) { Vector v = new Vector(); for (int i = 0; i < 10; i++) { v.addElement(new CrashJava()); } System.out.println(v);}}

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

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

若确实想在这种情况下打印出对象的地址,解决方案就是调用这种做法也有一个前提:我们必须从Object直接继承,或者没有一个弗雷覆盖ltoString方法。

8.4.2 BitSet

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

此外,BitSet的最小长度是一个长整数(long)的长度:64为。这意味着假如我们准备保存比这个更小的数据,如8位数据,那么BitSet就浪费了。所以最好创建自己的类,用它容纳自己的标志位。在一个普通的Vector中,随我们假如越来越多的元素,集合也会自我膨胀。在某种程度上,BitSet也不例外。也就是说,它有事会自行扩展,有时则不然。示例BitSet的使用:

package package08;import java.util.*;import sun.print.resources.serviceui;import jdk.nashorn.internal.runtime.regexp.joni.BitSet;public class Bits {public static void main(String[] args) { Random rand =new Random(); byte bt = (byte)rand.nextInt(); BitSet bb = new BitSet(); for (int i = 7; i >= 0; i--) { if(((i<= 0; i--) { if(((1<= 0; i--) { if(((i << i) & it) != 0) bi.set(i); else { bi.clear(i); } } System.out.println("int value:" +it); printBitSet(bi); BitSet b127 = new BitSet(); b127.set(127); System.out.println("set bit 127:" + b127); BitSet b255 = new BitSet(); b255.set(255); System.out.println("set bit 255:" +b255); BitSet b1023 = new BitSet(512); b1023.set(1024); System.out.println("set bit 1023:" +1023);} static void printBitSet(BitSet b){ System.err.println("bits:" +b); String bbits = new String(); for (int j = 0; j < b.size(); j++) { bbits += (bbits.get(j)?"1":"0"); } System.err.println("bit pattern:" +bbits); }}

随机数字生成器用于创建一个随机的byte、short和int。每一个都会转成BitSet内响应的位模型。此时一切都很正常,因为bitSet'是64位的。

thank in java 笔记——第八章 对象的容纳


分享到:


相關文章: