JAVA程序员必读:基础篇(4)类和继承
类别: JAVA教程
这节教程将开始讨论对象的生命周期。包括怎样创建对象、怎样使用它以及在不使用它的时候将它从系统中清除。下面一个一个介绍: 4.1 对象的生命周期 在这一小节中你可以学到怎样创建和使用任何类型的对象,还讨论了当对象不再需要的时候系统怎样清除对象的。 典型的JAVA程序创建对象,对象之间的交互是通过发送消息来实现的。通过这些对象的交互,JAVA程序可以执行一个GUI、运行一个动画或者通过网络发送和接收信息。一旦对象已经完成了任务,它就被作为无用信息被回收,它的资源可以由其它对象回收利用。 以下是一个小的例子程CreateObjectDemo,它创建三个对象:一个是Point对象和两个Rectange对象,你需要这三个源程序才可以编译这个程序: public class CreateObjectDemo { public static void main(String[] args) { //创建一个Point对象和两个Rectangle对象 Point origin_one = new Point(23, 94); Rectangle rect_one = new Rectangle(origin_one, 100, 200); Rectangle rect_two = new Rectangle(50, 100); // 显示rect_one的宽、高以及面积 System.out.println(\"Width of rect_one: \" + rect_one.width); System.out.println(\"Height of rect_one: \" + rect_one.height); System.out.println(\"Area of rect_one: \" + rect_one.area()); // 设置rect_two的位置 rect_two.origin = origin_one; // 显示rect_two的位置 System.out.println(\"X Position of rect_two: \" + rect_two.origin.x); System.out.println(\"Y Position of rect_two: \" + rect_two.origin.y); // 移动rect_two并且显示它的新位置 rect_two.move(40, 72); System.out.println(\"X Position of rect_two: \" + rect_two.origin.x); System.out.println(\"Y Position of rect_two: \" + rect_two.origin.y); } } 一旦创建了对象,程序就可以操作对象并将它们有关的一些信息显示出来,以下是这个程序的输出结果: Width of rect_one: 100 Height of rect_one: 200 Area of rect_one: 20000 X Position of rect_two: 23 Y Position of rect_two: 94 X Position of rect_two: 40 Y Position of rect_two: 72 这一节使用这个例子来在程序中描述对象的生命周期。从这你可以学到怎样编写代码来创建、使用对象以及系统怎样将它从内存中清除的。 4.1 对象的生命周期 下面主要分成几部分来讨论: 1. 创建对象
4.1.1 创建对象 众所周知,可以从类来创建对象。下面的几条语句都是用来创建对象的,它们都是来自上面程序CreateObjectDemo程序: Point origin_one = new Point(23, 94); Rectangle rect_one = new Rectangle(origin_one, 100, 200); Rectangle rect_two = new Rectangle(50, 100); 上面第一条语句从Point类创建了一个对象,而第二条和第三条语句是从Rectangle类众创建了对象。但是每条语句都有三部分组成: 1. 声明:Point origin_one、Rectangle rect_one以及Rectangle rect_two都是变量的声明,它们的格式是类型后加变量名。当你创建一个对象的时候,你不必声明一个变量来引用它。然而,变量生命经常出现在创建对象代码的相同行上。
下页也对这几个部分逐个介绍: 4.1.1.1 声明一个变量来引用对象 从前面的教程,你应该知道了如何声明一个变量了,你可以这样来编写: type name 其中type是数据类型,而name是变量名。 除了原始类型(比如int和boolean),JAVA平台还直接提供了类和接口也是数据类型。这样为了声明一个变量来引用对象,你可以使用类或者接口的名字作为变量的类型。下面的例程使用了Point和Rectangle类作为类型来声明变量: Point origin_one = new Point(23, 94); Rectangle rect_one = new Rectangle(origin_one, 100, 200); Rectangle rect_two = new Rectangle(50, 100); 声明没有创建新对象。Point origin_one代码没有一个新的Point对象,它只是声明一个变量orgin_one,它将用来引用Point对象。这个引用暂时是空的直到被赋值了。一个空的引用就是一个NULL引用。 4.1.1.2 实例化对象 为了创建一个对象你必须用new来实例化它。New运算符是通过为新对象分配内存来实例化一个类的。这个new运算符需要一个后缀参数,即构造函数的一个调用。构造函数的名字提供了要初始化类的名字。构造函数初始化了新的对象。 New运算符号返回一个引用给它创建的对象的。通常,这个引用被赋值为适当类型的变量。 4.1.1.3 初始化对象 以下是Point类的代码: public class Point { public int x = 0; public int y = 0; //一个构造函数 public Point(int x, int y) { this.x = x; this.y = y; } } 这个类包含了一个构造函数。你可以识别这个构造函数因为它跟类的名字是相同名字的,它没有任何的返回类型。这个在Point类中的构造函数有两个整型参数,它是由代码(int x, int y)来定义的。下面的整数23和94就是这个参数的数值: Point origin_one = new Point(23, 94); 4.1.1.3 初始化对象 下面是Rectangle类的代码,它包含了四个构造函数: public class Rectangle { public int width = 0; public int height = 0; public Point origin; //四个构造函数 public Rectangle() { origin = new Point(0, 0); } public Rectangle(Point p) { origin = p; } public Rectangle(int w, int h) { this(new Point(0, 0), w, h); } public Rectangle(Point p, int w, int h) { origin = p; width = w; height = h; } //用于移动rectangle的方法 public void move(int x, int y) { origin.x = x; origin.y = y; } //用于计算矩形面积的方法 public int area() { return width * height; } } 每一个构造函数可以让你为矩形的各个方法提供初始数值,你可以设置矩形的原点、宽度和高度。如果一个类中有多个构造函数,它们的名字都是相同的只是它们有不同类型的参数或者不同数目的参数。JAVA平台可以根据参数的不同数目和类型类来区分构造函数。当JAVA平台遇到的代码的时候,它就调用在Rectangle类中的构造函数,这个函数需要一个Point参数以及两个整型参数: Rectangle rect_one = new Rectangle(origin_one, 100, 200); 这个调用初始化了矩形的原点(orgin_one)。代码也设置了矩形的宽度(100)和高度(200)。 4.1.1.3 初始化对象 多个引用可以引用相同的对象。下面的代码行调用了需要两个整型参数的构造函数,它为宽度和高度提供了初始化数值。如果你仔细看看这个代码,你会发现它创建一个Point对象,它的x和y数值被初始化为0。 下面的Rectangle构造函数没有任何参数: Rectangle rect = new Rectangle(); 如果一个类没有显性声明任何构造函数,JAVA平台自动提供一个没有参数的构造函数,这是一个缺省的构造函数,它没有完成任何事情。这样,所有的类就至少有一个构造函数。 4.1.2 使用对象 一旦你创建了一个对象,你可能想使用它来做一些事情。你可能从它得到一些信息,或者想改变它的状态或者让它来完成一些动作。对象允许你做以下两件事情: 1. 操作或者检查它的变量。
4.1.2.1 引用对象的变量 下面是引用对象变量的基本形式,它是使用了有条件的名字即长名字: objectReference.variableName 当实例变量处在作用域内的时候,你可以为实例变量使用一个简单的名字,也就是说,在对象类的代码中。处在对象类外面的代码必须使用有条件的名字。比如,在CreateObjectDemo类中的代码处在类Rectangle类代码的外面。所以为了引用Rectangle对象rect_one的origin、width和height变量,CreateObjectDemo必须相应使用rect_one.origin、rect_one.width和rect_one.height。这个程序使用了rect_one的width和height: System.out.println(\"Width of rect_one: \" + rect_one.width); System.out.println(\"Height of rect_one: \" + rect_one.height); 如果直接使用在CreateObjectDemo类中的变量width和height,那就将产生一个编译错误。在后面,程序还将使用类似的代码来显示关于rect_two的信息。相同类型的对象将有相同实例变量的副本。这样,每一个Rectangle对象就都有变量origin、width和height了。当你通过对象引用来访问实例变量的时候,你就引用了特定对象的变量。在CreateObjectDemo程序有两个对象rect_one和rect_two,它们有不同的origin、width和height变量: 对象的长文件名的第一部分是对象引用,它必须是一个对象的引用。这里你可以使用引用变量的名字,或者你可以使用任何的表达式来返回一个对象引用。重新调用这个new运算符可以返回一个对象的引用。这样你可以使用从new返回的数值来访问一个新的对象变量: int height = new Rectangle().height; 这个语句创建了一个新的Rectangle对象并且得到它的height(高度)。从本质上讲,这条语句计算了Rectangle缺省的高度。这里注意,在这条语句被执行后,程序不再有创建了的Rectangle的引用,因为程序不再在变量中存储这个引用。对象就被取消引用,而它的资源可以JAVA平台重新使用。 4.1.2.2 关于变量访问 利用其它对象和类对对象变量直接的操作是不允许的,因为有可能为变量设置的数值没有任何的意义。比如,我们使用前面教程中的Rectangle类,你可以创建一个矩形,它的width和height都是负的,但是它是没有意义的。 较好的做法是:不采用直接对变量进行操作,类提供一些方法,其它的对象可以通过这些方法来检查或者改变变量。这些方法要确保变量的数值是有意义的。这样,Rectangle类将提供setWidth、setHeight、getWidth以及getHeight方法来设置或者获得宽度和高度。这些用于设置变量的方法将在调用者试图将width和height设置为负数的时候汇报一个错误。使用方法而不使用直接变量访问的好处还有:类可以改变变量的类型和名字来存储width和height而没有影响它的客户程序。 但是,在实际情况下,有时允许对对象变量直接访问。比如,通过定义Point类和Rectangle类为public,它们都允许对它们的成员变量自由访问。 JAVA编程语言提供了一个反问控制机制,凭它,类可以决定什么其它的类可以直接访问它的变量。如果其它对象对类直接操作可能导致无意义的话,类可以保护变量。改变这些变量应该利用方法调用来控制。如果类授权访问给它的变量,你可以检查和改变这些变量而但不能造成不利的效果。 4.1.2.2 调用对象的方法 同样你可以使用有限制的名字(长名字)来调用对象的方法。有限制的名字的格式是:在对象引用的后面加上点(.)再跟着方法的名字,即对象引用.方法名字。同样你还可以利用圆括号(和)来为方法提供参数。如果方法不需要任何参数就留空它: objectReference.methodName(argumentList); or objectReference.methodName(); Rectangle类有两个方法:area和move,即计算矩形的面积和改变矩形的原点。这里是CreateObjectDemo代码,它调用这两个方法: System.out.println(\"Area of rect_one: \" + rect_one.area()); ... rect_two.move(40, 72); 上面的第一条语句调用rect_one的area方法并显示结果。第二条语句是移动rect_two,因为move方法为对象的原点坐标x和y赋了新值。其中objectReference 必须是一个对象的引用。你可以使用一个变量名字,而且你也可以使用任何表达式来返回对象的引用。而new运算符返回一个对象的引用,因此你可以使用从new返回的数值来调用一个新的对象方法: new Rectangle(100, 50).area() 表达式new Rectangle(100,50)返回一个对象引用,它是引用一个Rectangle对象。上面已经提到,你可以使用点符号(.)来调用新的Rectangle的面积方法以计算新矩形的面积。另外方法area也返回一个数值。对于这些返回数值的方法,你可以使用在表达式中使用方法调用。你可以指定返回的数值给变量,参见如下的例子: int areaOfRectangle = new Rectangle(100, 50).area(); 这里要提醒一下,在特定对象中调用一个方法跟发送一个信息给对象是相同的。 4.1.2.3 关于方法访问 在Point和Rectangle类中的方法都被声明为public,因此它们可以被任何其它的类所访问。有时,类需要限制访问它的方法。比如,一个类可能可能有一个方法,只有它的子类才能调用它。类可以在它用于控制访问它的变量的时候,使用相同的机制来对它的方法进行控制访问。 4.1.3 清除没有使用的对象 有些面向对象语言需要保持对所有对象的跟踪,所以需要在对象不再使用的使用来将它从内存中清除。管理内存是一个很沉闷的事情而且容易出错。JAVA平台允许你创建任意个对象(当然会受到系统的限制),所以你也不必要老是要将它清除。JAVA是在当对象不再使用的使用被清除的。这个过程就是所谓的“垃圾收集”。 当对象不再有引用的时候,对象就会被清除,即作为垃圾收集的对象。保留在变量中的引用通常在变量超出作用域的时候被清除。或者,你可以通过设置变量为NULL来清除对象引用。这里注意,程序中同一个对象可以有多个引用,对象的所有引用必须在对象被作为垃圾收集对象清除之前清除。 下面讲讲垃圾收集器: JAVA有一个立即收集器,它周期性地讲不再被引用的对象从内存中清除。这个垃圾收集器是自动执行的,虽然有时候你可能想通过调用系统类的gc方法来显性运行垃圾收集程序。比如,你可能想在创建大量垃圾代码之后或者在需要大量内存代码之前运行垃圾收集器。垃圾收集器从内存中清除不再被引用的对象的机制已经被要到了VB.net和C#中去了。 最后介绍对象finalize方法: 在一个对象得到垃圾收集之前,垃圾收集器在对象自己调用对象的finalize方法之后给对象一个机会来从内存中清除掉。这个过程就是所说的最后处理。 绝大部分的程序员不得不关系这个finalize方法的执行。在少数情况下,程序员不得不执行finalize方法来释放资源。 Finalize方法是一个对象类的成员函数,它处在JAVA平台类分级结构的顶部,而且是所有类的子类。这个类重载了finalize方法来完成对象的最后处理工作。 4.2字符和字符串 字符数据(不管是单一字符或者一系列的字符)可以利用java.lang三个类来储存和操作,这三个类分别为:Character、String和StringBuffer。 JAVA平台包含了三个类,在处理字符数据的时候你可以使用这三个类: 1. Character:这个类的实例可以容纳单一的字符数值。这个同样定义了简洁的方法来操作或者检查单一字符数据。
下面开始详细介绍字符: 字符类型的对象包含了单一字符数值。你可以在对象需要的时候使用一个字符对象取代原始字符类型。比如,当传递一个字符给改变数值的方法或者当放置一个字符到一个数据结构中,比如一个矢量等等。下面的例程序CharacterDemo创建了一些字符对象并显示一些信息。 这个程序的数据结果为: a is less than b. a is equal to a2. The character a is lowercase. 4.2字符和字符串 CharacterDemo程序调用以下由Character类提供的构造函数和方法: Character(char) Character类只是构造函数,它创建了一个Character对象包含了由参数提供的数值。一旦Character对象被创建,它包含的数值就不能改变。 compareTo(Character) 一个实例方法比较了由两个Character对象容纳的数值:方法被调用的对象(即例子中的a)以及方法的参数(即例子中的b)。这个方法返回一个整数表明在当前对象中的数值是否大于、等于或者小于参数给定的数值。如果字母的数值较大那么这个字母就较大。 equals(Object) 一个实例方法,它比较当前对象容纳的数值和由其它容纳的数值。这个方法在两个对象容纳的数值相等的时候返回true。 toString() 一个实例方法,它将对象转换为字符串。结果的字符串在长度上就是一个Character对象并且它包含了有这个Character对象容纳的数值。 charValue() 一个实例方法,它返回由Character对象容纳的数值作为原始字符数值。 isUpperCase(char) 类方法决定是否原始字符数值为大写。这个是许多Character类方法中的一个,它可以检查或者操作字符数据。 下面我们再谈谈于string(字符串)有关的几个问题: 1. 为什么有两个String类:String 和StringBuffer
下页接着一个一个加以细讲。 1)为什么有两个String类:String 和StringBuffer JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。比如,如果你编写一个方法,它需要字符串数据并且方法不能修改字符串,而要将一个String对象传递给方法。而这个StringBuffer类提供的字符串进行修改。当你直到字符数据要改变的时候你就可以使用StringBuffer。典型地,你可以使用StringBuffers来动态构造字符数据。比如,从一个文件读文本数据。因为Strings是常量,所以它用起来比StringBuffers更有效,并且可以共享。因此在允许的情况下还是使用Strings。 下面是一个例程StringsDemo,它反转了字符串中的字符。这个程序同时使用了String和StringBuffer: public class StringsDemo { public static void main(String[] args) { String palindrome = \"Dot saw I was Tod\"; int len = palindrome.length(); StringBuffer dest = new StringBuffer(len); for (int i = (len - 1); i >= 0; i--) { dest.append(palindrome.charAt(i)); } System.out.println(dest.toString()); } } 这个程序的输出为: doT saw I was toD 2)创建Strings 和StringBuffers 字符串经常是由双引号括起的一系列字符组成。比如,当有以下的字符串,JAVA平台就创建一个String对象,它的数值为Goodluck。 \"Goodluck\" 这个StringsDemo程序使用这个记述来创建字符串,它是由palindrome变量引用的: String palindrome = \"Dot saw I was Tod\"; 同样你可以利用其它方法来创建String对象:使用new关键字和构造函数。String类提供了几个构造函数,它允许你提供字符串的初始值,它们是使用不同的资源的,比如字符的数组、字节的数组或者一个StringBuffer。 char[] helloArray = { \'h\', \'e\', \'l\', \'l\', \'o\' }; helloString = new String(helloArray); System.out.println(helloString); 上面的这段程序输出为:hello。 你必须使用new来创建一个StringBuffer。StringsDemo程序创建了SringBuffer,它是由dest引用的,这里使用了设置缓冲区容量的构造函数。 String palindrome = \"Dot saw I was Tod\"; int len = palindrome.length(); StringBuffer dest = new StringBuffer(len); 这个代码创建了一个字符串的缓冲区,这个缓冲区的容量等于被palindrome引用的字符串的长度。这个确保了为dest只分配一个内存,因为它只有包含被复制的字符串那么大的空间。通过初始化字符串的缓冲区的容量,你最小化必须分配的内存空间。这就使得你的代码更加有效,因为内存分配是相对高级的操作。 (3)存取器函数 首先讲解一下获得String或者StringBuffer的长度: 获得有关对象信息的方法称为存取器函数。你可以用在字符串和字符串缓冲区的一个存取器方法是length方法,它可以包含在字符串或者字符串缓冲区的字符的个数。下面给出一个例程,当这个程序执行后,len就将等于17: String palindrome = \"Dot saw I was Tod\"; int len = palindrome.length(); 除了length,StringBuffer类还有一个方法叫做capacity,它是返回为字符串缓冲区分配的空间大小而不是空间使用了多少。比如,字符串缓冲区的容量在StringsDemo例程中被dest引用时,它是不能改变的,虽然它的长度在每次的循环中增加1。如图15,给出了在dest里面已经有9个字符之后的容量和长度。 (图15) 一个字符串缓冲区的长度是它包含的字符的个数。一个字符串缓冲区的容量是被分配的字符空间的数量。而String类没有capacity方法,因为字符串不能改变。 下面讲讲索引String或者String Buffer得到字符: 你可以通过使用charAt存取器来在一个特定的位置索引一个字符串或者字符串缓冲区以得到字符串。第一个字符的索引是0,最后一个字符的索引是length()-1。比如,下面的代码在字符串中得到索引为9的字符: String anotherPalindrome = \"Niagara. O roar again!\"; char aChar = anotherPalindrome.charAt(9); 因为索引是从0开始的,所以索引为9的字符为\'O\',如图16所示: (图16) 使用charAt方法来在特定的索引下得到字符。如图16所示给处了计算字符串最后一个字符的索引的方法,你必须将length方法返回的数值减去1才得到最后一个字符的索引。 (3)存取器函数 如果你想从一个字符串或者字符串缓冲区得到多个字符,你可以使用substring方法。这个substring方法有两个版本,如下表所示:
下面的代码是得到字符串\"Niagara. O roar again!\"从索引为11到索引为15之间的子串,结果得到单词\"roar\": String anotherPalindrome = \"Niagara. O roar again!\"; String roar = anotherPalindrome.substring(11, 15); 如图17所示: (图17) 使用substring方法来得到字符串或者字符串缓冲区的一部分。这里一定要记住索引是从0开始的。 (4)修改StringBuffers reverseIt方法使用StringBuffer的append方法来增加一个字符到dest字符串末尾: class ReverseString { public static String reverseIt(String source) { int i, len = source.length(); StringBuffer dest = new StringBuffer(len); for (i = (len - 1); i >= 0; i--) { dest.append(source.charAt(i)); } return dest.toString(); } } 如果这个被增加的字符导致StringBuffer的大小超过当前的容量,这个StringBuffer就会为它分配更多的内存。因为内存分配示相对高级的操作,所以你可以同时初始化StringBuffer的容量为较合理的大小来使你的代码更有效率。比如reverseIt方法以初始的容量来构造StringBuffer等于源字符串的长度,以只为dest分配一次内存。 4)修改StringBuffers 用在reverseIt的append方法只是一个StringBuffer方法的其中一种,它在StringBuffer末尾增补了一个数据。有几种append方法来增补各个类型的数据(比如float、int、boolean以及对象)到StringBuffer末尾。但是在增补操作之前,数据都要先转换为字符串。 下面讲讲插入字符: 有时候,你可能想插入数据到StringBuffer中间。你可以使用StringBuffer的insert方法来实现。以下的这个例子就是介绍如何将一个字符串插入到StringBuffer中去的: StringBuffer sb = new StringBuffer(\"Drink Java!\"); sb.insert(6, \"Hot \"); System.out.println(sb.toString()); 这些代码将输出: Drink Hot Java! 利用StringBuffer的许多insert方法,你可以在插入数据之前指定索引。在上面的例子中,\"Hot \"要插到\"Java\"的\'J\'之前。因为索引是从0开始的,所以\'J\'的索引为6。为了插入数据到StringBuffer之前,使用索引0.为了在StringBuffer的末尾插入数据,你可以将所以等于StringBuffer的长度或者使用append方法来增补。 下面讨论设置字符: 另外一个有用的StringBuffer修改量是setCharAt,它在StringBuffer的指定位置用参数列表中的指定字符来替换字符。SetCharAt在你想重新使用StringBuffer的时候是很有用的。 (5)将对象转换为Strings 首先介绍toString方法: 经常需要将一个对象转换为String,因为你需要将它传递给方法,而这个方法只接收String数值。前面我们使用reverseIt方法使用了StringBuffer的toString方法来将StringBuffer转换为String对象。 class ReverseString { public static String reverseIt(String source) { int i, len = source.length(); StringBuffer dest = new StringBuffer(len); for (i = (len - 1); i >= 0; i--) { dest.append(source.charAt(i)); } return dest.toString(); } } 所有的类从对象类继承了toString,并且在java.lang包的许多类重载了这个方法来提供一个很有意义的方法 下面说说valueOf方法: 简单起见,String类提供了一个类方法valueOf。你可以使用valueOf来转换不同类型的变量为String。比如,为了打印pi的数值,你可以这样来编写代码: System.out.println(String.valueOf(Math.PI)); 6)将String转换为数字 String类本身没有提供任何的方法来转换String为浮点型、整型或者其它的数值类型。但是,\"type wrapper\"类的四种类型(integer、Double、Float和Long)提供了一个类方法valueOf来将String转换为那个类型的对象。举例如下: String piStr = \"3.14159\"; Float pi = Float.valueOf(piStr); (7)Strings和JAVA编译器 JAVA编译器使用了String和StringBuffer类来处理字符串和字符串的连接。 下面先讲讲Strings: 在JAVA种,你可以在双引号之间指定字符串,如: \"Hello World!\" 你可以在任何使用String对象的地方使用字符串。比如,System.out.println接收一个String参数,所以你可以在这里使用字符串了。 System.out.println(\"Might I add that you look lovely today.\"); 同样,你可以使用从字符串直接使用String方法,比如: int len = \"Goodbye Cruel World\".length(); 因为编译器自动为字符串创建了一个新的String对象,你可以使用字符串来初始化一个String对象: String s = \"Hola Mundo\"; 上面的这条语句等价于下面的语句: String s = new String(\"Hola Mundo\"); 最后讲讲字符串的连接以及+运算符: 在JAVA种,你可以使用+来连接字符串,比如: String cat = \"cat\"; System.out.println(\"con\" + cat + \"enation\"); 这条语句看起来有点问题,因为我们直到Strings是不能改变的。但是,在这条语句的背后,编译器却使用了StringBuffers来执行字符串的连接。上面的语句实际上编译为: String cat = \"cat\"; System.out.println(new StringBuffer().append(\"con\"). append(cat).append(\"enation\").toString()); 同样你可以使用+运算符号来增不一个String: System.out.println(\"Java\'s Number \" + 1); 在JAVA中,在执行字符串连接操作之前编译器讲非String的数值(如本例的整数1)转化为一个String对象。 4.3 数字 为了处理数值类型,你可以使用Number类。这个Number类是所有在JAVA平台中所有数字类的父类。它的子类包括Float、Interger等等。 本节教程将讨论java.lang和它的子类中的Number类。特别地,这节教程要讨论为什么你需要这些类,并指出通用的方法和类变量,以及向你介绍如何将实例转换为字符串。 此外,这节教程还讨论你需要的其它类来处理数字。比如,如果你需要以某个特殊格式来显示数字的时候,你可以使用在java.test中的NumberFormat和DecimalFormat类来格式化它。同时,java.lang中的Math包含了类方法来执行数学函数。这个类还有用于三角函数、指数函数等等的方法。如图18所示: (图18) 数字类包含: 1. Number :这个抽象类Number是Byte、Double、Float、Integer、Long和Short的父类。Number的子类必须提供将数值转换为byte、double、float、int、long以及short的方法。
4.3 数字 8. BigDecimal:不可变的、任意精度的有符号的十进制数字。BigDecimal包含了任意精度的整型数和非负的32位的整型数,BigDecimal 提供了用于基本算术运算、比较、格式转换等等的操作。这个BigDecimal类给了用户对舍入行为的完全控制,并且强迫用户为舍弃精度的操作显性指定一个舍入行为。基于这个目的有八种舍入模式。对BigDecimal有两种类型的操作:比例/舍入操作和十进制点移动操作。比例/舍入操作返回一个BigDecimal,它的数值大约或者精确地等于运算对象,但是它的数值范围要指定,也就是说,它们可以对它的数值最小的影响来增加或者减少数字的精度。十进制点移动操作(movePointLeft和movePointRight)返回一个由运算对象创建的BigDecima,它是通过在指定方向和指定距离来移动十进制点的。为了达到简短和清晰的目的,伪代码用在了BigDecimal方法的描述中。比如伪代码表达式(i j)就代表了“BigDecimal的数值是由BigDecimal i加上BigDecimal j的数值”。而伪代码表达式(i=\"=j)代表“当且仅当BigDecimal\" i的数值与BigDecimal j相同的时候才为true”。其它的伪代码表达式也有类似的解释。 BigInteger:不可变任意精度的整型数。BigInteger为所有的Java原始整型操作以及所有在java.lang.Math中的相关方法提供相似的操作。另外BigInteger伪模数运算、GCD计算、位操作等等提供了运算。算术运算的语义模仿了定义在JAVA语言规范中的Java的整型算术运算。比如,如果除数为0就会导致ArithmeticException(算术异常)等等。所有在规范中的溢出都将被忽略,因为BigIntegers将尽量适应运算结果的需要。而移位操作扩展了Java的移位操作,它允许负方向的移位。以负的位距来往右移就相当于往左移位。而无符号的右移运算符(>>>)这里就不再使用。 而按位逻辑操作跟Java的按位操作是相似的。比较运算执行有符号的整型比较,它跟Java的是相似的。模数算术运算提供计算余数、执行乘幂等方法。这些方法总是返回一个非零的结果(介于0到-1之间)。为了简洁和清晰的目的,伪代码也用在BigInteger方法中。这个伪代码表达式(i j)代表了“等于BigInteger i加上BigInteger j的BigInteger数值”。而伪代码表达式(i=\"=j)代表了“当且仅当BigInteger\" i等于BigInteger j的时候才返回true”。 4.3 数字 其它的数据类型: 1. Boolean :这个Boolean类在对象中包装了原始类型Boolean。Boolean类的对象包含单一类型(double)的对象。另外,这个类提供了几个用于将Boolean转换为String或者将String转换为Boolean的方法。
非基本算术类型Math: 这个Math类包含了用于执行数值操作的方法,比如初步的指数、对数、平反根以及三角函数。不象StrictMath类中的有些数值函数,这个Math类的所有函数不是定义为返回bit-for-bit相同的结果。这允许更好的执行,因为严格的再现情已经不需要了。 缺省地,许多Math函数简单代表了在StrictMath中的等价函数。代码发生器支持使用特殊平台的本地库或者微处理器指令以提供性能更高的Math函数实行。这样高性能的执行必须符合Math的规范。 4.4 数组 在任何的编程语言中,数组都是一个重要的数据结构。数组是一个固定长度的结构,它存储多个相同类型的数值。你可以在数组中集合相同类型的数值。数组直接被JAVA编程语言所支持,所以没有一个数组类。数组是一个Object类的一个不明显的扩展,所以你可以指定一个数组给一个类型定义为Object的变量。 JAVA平台集合了它的类到功能包中。不是编写你自己的类,你可以使用有平台提供的类。这节教程中要讨论的绝大多数的类都是java.lang包的成员函数。所有java.lang中的类对你的程序都是有效的。 数组的长度在数组创建的时候就已经确定。一旦创建以后,数组就有了固定长度的结构,如图19所示: (图19) 数组元素就是数组中的一个数值,可以通过数组中的位置来访问它。 如果你想在一个结构中存储不同类型的数据,或者如果你需要一个长度可以动态改变的结构,可以使用Collection,比如Vector而不用数组。 本教程中将讨论以下的几个方法: 1. 创建和使用数组合
4.4.1 创建和使用数组合 以下是个简单的程序ArrayDemo,它创建了一个数组,并放置了一些数值在上面,然后进行显示: public class ArrayDemo { public static void main(String[] args) { int[] anArray; // 声明一个整型数组 anArray = new int[10]; // 创建一个整型数组 // 为每一个数组元素赋值并打印出来 for (int i = 0; i < anArray.length; i++) { anArray[1] = i; System.out.print(anArray[1] + \" \"); } System.out.println(); } } 这节教程覆盖了以下这些内容: 1. 声明一个变量来引用一个数组
下一页将加以详细说明。 4.4.1 创建和使用数组合 (1)声明一个变量来引用一个数组 以下的代码是声明一个数组变量: int[] anArray; // 声明整型的数组 象声明其它类型的变量,一个数组声明有两个组件:数组的类型和数组的名字。数组的类型是这样来写的type[],其中type可以是float、boolean、Object、String等,而[]代表了这是一个数组。这里记住在数组中的所有元素都是相同类型的。上面的例程中使用了int[],所以数组anArray就将容纳整型数据了。以下是声明其它类型的数组: float[] anArrayOfFloats; boolean[] anArrayOfBooleans; Object[] anArrayOfObjects; String[] anArrayOfStrings; 就象声明其它类型的变量,声明数组变量也没有为数组元素分配任何内存。所以必须在引用数组之前被数组赋值。 4.4.1 创建和使用数组合 (2)创建一个数组 你可以使用JAVA的new运算符来创建一个数组。下面的语句是为10个整型元素分配一个数组内存: anArray = new int[10]; // create an array of integers 总得说来,当创建数组的时候,你可以使用new操作符,后面跟着数组元素的数据类型然后就是用方括号[和]括起来的元素的数目,格式如下所示: new elementType[arraySize] 如果在上面的例程中new语句没有的话,编译器将打印除一个如下的错误是的编译发生错误: ArrayDemo.java:4: Variable anArray may not have been initialized(变量anArray 可能还没有初始化。) (3)访问数组元素 既然已经给数组分配了内存,我们来为数组元素赋值吧: for (int i = 0; i < anArray.length; i++) { anArray[1] = i; System.out.print(anArray[1] + \" \"); } 这部分的代码给出了怎样引用一个数组元素,或者示为它赋予一个数值,或者访问数值。在方括号之间的数值指示了要访问的元素的索引。在JAVA中,数组的索引是从0开始的并且以-1结束。 4.4.1 创建和使用数组合 (4)获得数组的大小 为了获得数组的大小,你可以使用下面的代码: arrayname.length 这里还提醒一下:JAVA的新手可能会在length后面加一个圆括号(和)。这样是否错误的,因为length不是一个方法。Length是由JAVA平台为所有数组提供的一个属性。 在上面的例程中的for循环在anArray的每一个元素进行循环,并给每个元素赋值。这个for循环使用了anArray.length来决定什么时候终止循环。 (5)数组初始化程序 JAVA编程语言为创建和初始化数组提供了简捷的语法。一下是这个语法的例子: boolean[] answers = { true, false, true, true, false }; 数组的length(长度)是由大括号{和}之间的数值的数目决定的。 4.4.2 对象的数组 数组可以保留引用类型。你可以用创建原始类型数组的方法来创建这样一个数组。以下是一个小的程序ArrayOfStringsDemo,它创建了一个数组包含了三个String对象并且将这三个字符串以小写字母的形式打印出来: public class ArrayOfStringsDemo { public static void main(String[] args) { String[] anArray = { \"String One\", \"String Two\", \"String Three\" }; for (int i = 0; i < anArray.length; i++) { System.out.println(anArray[1].toLowerCase()); } } } 这个程序中用单一的语句创建和操作了数组。但是,你可以创建一个数组而不用在里面放置任何元素。这对于JAVA新手将是一个容易出错的地方。假如有以下的代码: String[] anArray = new String[5]; 一旦这条语句被执行了,数组anArray就存在了并且有充足的空间来容纳5个String对象。但是,数组并不包含任何的字符串,它是空的。程序必须显性地创建字符串并将他们放置到数组中。这本来示很显然的事情,但是许多JAVA的新手会以为上面的语句已经创建了数组并创建了5和个空的字符串在里面了。这样他们会象如下进行编写代码,结果只会导致一个NullPointerException的异常错误: String[] anArray = new String[5]; for (int i = 0; i < anArray.length; i++) { // 错误:下面的行将导致一个运行错误 System.out.println(anArray[1].toLowerCase()); } 4.4.3 数组的数组 数组可以容纳数组。以下的例程ArrayOfArraysDemo创建一个数组并使用一个初始化程序来包含四个子数组: public class ArrayOfArraysDemo { public static void main(String[] args) { String[][] cartoons =\" { { \"Flintstones\", \"Fred\", \"Wilma\", \"Pebbles\", \"Dino\" }, { \"Rubbles\", \"Barney\", \"Betty\", \"Bam Bam\" }, { \"Jetsons\", \"George\", \"Jane\", \"Elroy\", \"Judy\", \"Rosie\", \"Astro\" }, { \"Scooby Doo Gang\", \"Scooby Doo\", \"Shaggy\", \"Velma\", \"Fred\", \"Daphne\" } }; for (int i = 0; i < cartoons.length; i++) { System.out.print(cartoons[1][0] + \": \"); for (int j = 1; j < cartoons[1].length; j++) { System.out.print(cartoons[1][j] + \" \"); } System.out.println(); } } } 这里注意,所有的子数组都有不同的长度。子数组的名字是否cartoons[0]和cartoons[1]等等。 4.4.3 数组的数组 跟对象的数组一样,你必须显性地在数组中创建子数组。因此如果你不使用初始化程序的话,你必须编写一些的代码: public class ArrayOfArraysDemo2 { public static void main(String[] args) { int[][] aMatrix = new int[4][]; for (int i = 0; i < aMatrix.length; i++) { aMatrix[1] = new int[5]; //create sub-array for (int j = 0; j < aMatrix[1].length; j++) { aMatrix[1][j] = i + j; } } //打印 for (int i = 0; i < aMatrix.length; i++) { for (int j = 0; j < aMatrix[1].length; j++) { System.out.print(aMatrix[1][j] + \" \"); } System.out.println(); } } } 你必须在创建数组的时候为主数组指定长度。你可以让子数组的长度未指定直到创建了它们。 4.4.4 复制数组 使用系统的arraycopy方法来有效地从一个数组复制数据到另外一个数组中去。这个arraycopy方法需要5个参数: public static void arraycopy(Object source, int srcIndex, Object dest, int destIndex, int length) 其中两个Object参数指定了从哪个数组复制以及要复制到哪个数组。三个整型参数指示了每个源数组和目标数组的开始位置,以及要复制的元素的数目。如图20所示说明了复制是如何发生的: (图20) 4.4.4 复制数组 下面的程序ArrayCopyDemo使用了arraycopy方法来从copyForm数组复制元素到copyTo数组: public class ArrayCopyDemo { public static void main(String[] args) { char[] copyFrom = { \'d\', \'e\', \'c\', \'a\', \'f\', \'f\', \'e\', \'i\', \'n\', \'a\', \'t\', \'e\', \'d\' }; char[] copyTo = new char[7]; System.arraycopy(copyFrom, 2, copyTo, 0, 7); System.out.println(new String(copyTo)); } } 程序中调用arraycopy方法,开始复制所以为2的源数组元素。因为数组的索引是从0开始的,所以复制是从第3个元素开始的,即从\'c\'开始。Arraycopy方法将复制的元素复制到目标数组中,目标数组的索引是从0开始的,即复制到目标数组的第一个元素开始。这个程序一共复制了7个元素\'c\'、 \'a、 \'f\'、 \'f\'、 \'e\'、 \'i\'和\'n\'。实际上arraycopy方法是从\"decaffeinated\"复制\"caffein\",如图21所示: (图21) 这里注意,目标数组必须在调用arraycopy之间分配内存,而且这个内存空间必须足够大以容纳被复制的数据。 |
- 上一篇: JAVA程序员必读:基础篇(3)语言基础
- 下一篇: JAVA写的NotePad
-= 资 源 教 程 =-
文 章 搜 索