|
第六章 语言类库Java.lang(上)
从这一章开始,我们将陆续介绍Java基本类库。在Java开发工具集JDK1.0.2版本中,包含8个包(package),分别包含了语言、输入输出、窗口工具、Applet、常用工具等文献的201个类,到了1.1.1版更增加到21个包,478个类。这些类是Java的开发者们向我们提供的软件资源。根据面向对象程序设计的代码重用原则,我们可以在自己的软件中恰当地使用这些类,从而避免从最底层的、我们不太熟悉的操作做起。 在这一章中,我们将对语言类库作较详细的介绍。本章的第一节将讲述引用类库和方法的细节,这是为不熟悉面向对象编程的读者准备的。事实上它不过集中地重复了我们前面零散提到的概念。较有经验的读者可以略过这一节。
6.1 如何利用类库
当你在机器上安排JDK时,可以看到一个压缩文件class.zip。这个文件缺省置于JDK安装目录的lib子目录下。我们无需对它解压缩,而且解开后浪费空间——但它包含着对我们编程工作的强有力支持,也即Java类库。 让我们用java.lang来做例子,解释对类库中类的运用。 首先必须对程序中出现的任何类注明来源,除非它是在该程序中被定义的。“注明来源”的目的是让编译器和解释器知道此类在哪里,以便引用。注明的方法有两种,其一是在程序文件的开头,用import语句: import
<类名>; 如:import
java.lang.Class; 若要引用的类较多,且在同一个package之内,则可以使用‘*’来做通配符。如: import
java.lang.*; 这种注明方法要注意两点。第一,类名之前的package名是必须的,否则类将找不到。例如未例中的java.lang.Class中,java.lang就是package名。对于没有注明package名的类,java将默认它属于一个缺省的package,这样路径就不对了。第二,‘*’通配符与我们常用的通配符含义不完全相同。不允许用 import
java.lang.T*; 之类的语句。*只能用来代替完整的类名。 注明来源的方法之二是在用到该类的地方直接把类名连同package名加上去,如 public
class MyThread extends
java.lang.Thread{ ... } 这里的MyThread类是Thread类的子类,而Thread类是java.lang包中的类。 仅需用以上两种方法之一,你就完成了利用某个类的基础工作。应当提醒的是,即使是程序中出现的异常,也要注明来源。例如: import
java.io.IOException; 注明了输入输出异常的来源,这样程序中才能处理这种异常。 当编译时出现“class
xx not found in
...”之类的错误时,请检查这个类是否被注明了来源;如果是,再检查是否拼写类名时拼错了,尤其要注意字母的大小写。这两条简单的检查可以解决大部分这类错误。 其次,正确地引用类的方法。 这个工作更为简单。当你要使用一个方法时,考虑一下它的参数、返回值以及是不是static方法(类方法)。如果它是类方法,那么你可以用<类名>.<方法名>(<参数表>)来引用它,如 Thread.sleep(200);//sleep是类方法 否则,你需先得到一个该类的对象,再用<对象名>.<方法名>(<参数表>)来引用它,如 StringBuffer
sbuf3=new
StringBuffer(10); subf3.setLength(9); 如果要接收返回值,就用相应类型的变量去接收它。如 StringBuffer
sbuf2=new StringBuffer("This is the initial
value"); sbuf2=sbuf2.append(" ").insert(0,"
"); 其中第一行创建了一个对象,第二行引用了它的append()方法。这样得到的返回值仍是一个StringBuffer类的对象,我们又对它运用insert()方法,最后得到的结果用sbuf2来接收。 这里需要解释一下如何得到一个对象。通常的作法是利用构造函数,用new来实现,但有些类是没有构造函数的,需用特殊的途径得到对象,如我们将在本章中介绍的Runtime类;有些类则根本就不能得到其对象,如Math类。因此处理时要具体问题具体分析。 类库中还有相当一部分成员是异常。对它们的处理在“异常”一章中提到过,要么在方法的开头“抛出”(throws),要么用try-catch语句捕捉。这些读者已经熟悉,就不再举例了。 在运用类和方法时,拼写错是最常见的错误之一,尤其是字母的大小写问题。其实类和方法的命名都是有规律的,一般可以望文生义地推理出其功能。类名的第一个字母总是大写,方法的第一个字母总是小写。名字由多个单词组成时,单词间无分隔符,但除第一个单词外每个单词的开头字母总是大写。记住这一点,或许会给你省不少麻烦呢。 java.lang包中的类较多。常用的类继承关系如图6.1(省略了接口和Exception和Error的众多子类)。 ┌──────┐ │Object│ └──┬───┘ ┌──────┐ ├────────────┤Number│ │ └─┬────┘ │ ├─Number ├─Boolean ├─Byte ├─Class ├─Float ├─Character ├─Integer ├─ClassLoader ├─Long ├─String └─Short ├─Runtime ├─StringBuffer ├─System ├─Void ├─Compiler ├─Math ├─SecurityManger ├─Process │ ┌─────────┐ ├──────────┤Throwable│ │ └───┬─────┘ ├─Thread ├Error └─ThreadGroup └Exception 图6.1 java.lang包中常用类继承关系图 从图6.1可以看出,java.lang包中的常用类可以大致分为几种。有把各个基本类型进行封装而得到的“包装类”,如Byte,Char,Integer,Double,Boolean等;有与线程有关的类,如Thread,ThreadGroup;有与Java环境有关的类,如ClassLoader,SecurityManager,Compiler;有错误类和异常类,除了图上已有的Error和Exception之外,还有这两个类的许多派生类;另外还有运行时系统类Runtime,进程类Process,系统类System等等。 这些类的继承关系较为简单。从图中可见,大多数常用类是Object类的子类,Number类是各种数类的父类,而Error和Exception都是Throwable的子类。Error又派生出许多类,表示各种错误,由Exception则派生出多个异常类(图中未标出)。 本章后面的小节将分别介绍java.lang类库中的类。
6.2 关于字符串
在这一节中我们把注意力集中于两个类:String和StringBuffer。 对字符串我们并不陌生。但Java对字符串的处理有些异乎寻常,使用了两个类。String类封装了有关字符串的操作。这里的字符串是常量,创建后就不可改变。StringBuffer类则是动态可变的字符串缓冲,它提供了一系列方法,把不同类型(如整型、布尔型等)数据插入缓冲或追加在缓冲的末尾。让我们通过一个例子来看二者的区别和使用(例6.1)。 例6.1
StringDemo.java。 1: import java.lang.*;//原文如此,实际上不应该有 2:
import java.io.*; 3: 4: public class StringDemo{ 5:
public static void main(String args[]) throws IOException{
//String对象的哈希值 6: String s1=new String("This is the
initial value."); 7: System.out.println("s1's value:" + s1 +
" HashCode:" + s1.hashCode()); 8: String s2="This is the
initial value."; 9: System.out.println("s2's value:" + s2 + "
HashCode:" + s2.hashCode()); 10: s2="
"+s2+""; 11: System.out.println("s2's value:"+s2+"
HashCode:"+s2.hashCode()); //StringBuffer对象的哈希值 12: StringBuffer
sbuf1=new StringBuffer(s1); 13: System.out.println("sbuf1's
value:"+sbuf1+"
HashCode:" 14: +sbuf1.hashCode()); 15: StringBuffer
sbuf2=new StringBuffer(s1); 16: System.out.println("sbuf2's
value:"+sbuf2+"
HashCode:" 17: +sbuf2.hashCode()); 18: sbuf2=sbuf2.append("
").insert(0,""); 19: System.out.println("sbuf2's
value:"+sbuf2+"
HashCode:" 20: +sbuf2.hashCode()); //String
类的方法演示 //改变大小写的方法 21: System.out.println("UpperCase:"+s1.toUpperCase()+"\nLowerCase:" 22: +s1.toLowerCase()); //替换字符 23: String
s3=s1.replace('i','I'); 24: System.out.println("Replace 'i' to
'I',we got
s3:"+s3); //判等 25: if(s3.equals(s1))System.out.println("s3
equals s1"); 26: else System.out.println("s3 doesn't equals
s1"); //忽略大小写判等 27: if(s3.equalsIgnoreCase(s1)) 28: System.out.println("s3
equalsIgnoreCase s1"); 29: else System.out.println("s3 doesn't
equalsIgnoreCase
s1"); //去除首尾空格 30: System.out.println("s2:(after
trim)"+s2.trim()); //求特定字符在串中的位置取给定位置的字符 31: System.out.println(s1.charAt(s1.indexOf('h'))); //求子串 32: System.out.println(s1.substring(s1.indexOf("is",0), 33: s1.lastIndexOf("is"))); //转化成字符数组 34: char
ch[]=s1.toCharArray(); //从字符数组的一段得到字符串 35: System.out.println(String.valueOf(ch,3,5)); 36: double
d=123.456; //从双精度数得字符串 37: System.out.println(String.valueOf(d)); 38: boolean
b1=true; 39: int i=100; 40: long
l=200000; //StringBuffer的方法演示 41: StringBuffer
sbuf3=new
StringBuffer(10); //插入和追加 42: System.out.println("sbuf3:"+sbuf3.append(b1).insert(2,i).insert(5,l)); 43: try{ //设置缓冲区长度 44: sbuf3.setLength(9); 45: System.out.println("sbuf3's
value(after
setLength):"+sbuf3); 46: }catch(StringIndexOutOfBoundsException
ex){ 47: System.out.println("String index out of
bounds."); 48: } 49: } 50:} 这个例子结果运行如下: C:\bookDemo\ch6>javac
StringDemo.java C:\bookDemo\ch6>java
StringDemo E:\Inetpub\wwwroot\ecapital\java\tutorial\java01>java
StringDemo s1's value:This is the initial value.
HashCode:769637276 s2's value:This is the initial value.
HashCode:769637276 s2's value: This is the initial value.
HashCode:1476514748 sbuf1's value:This is the initial value.
HashCode:2438296 sbuf2's value:This is the initial value.
HashCode:2092911 sbuf2's value:This is the initial value.
HashCode:2092911 UpperCase:THIS IS THE INITIAL
VALUE. LowerCase:this is the initial value. Replace 'i' to
'I',we got s3:ThIs Is the InItIal value. s3 doesn't equals
s1 s3 equalsIgnoreCase s1 s2:(after trim)This is the
initial value. h is s
is 123.456 sbuf3:tr100200000ue sbuf3's value(after
setLength):tr1002000
这个例子可能有些琐碎,但其中包含了关于这两个类的许多要点。先看第一部分(第1至20行)。 便子的第一部分着重显示String与StringBuffer在静态、动态方面的不同。hashcode()方法重写(String类)/继承(StringBuffer类)了Object类中的同名方法,作用是求出对应字符串/字符串缓冲类对象的哈希值。哈希希值与对象一一对应,可以唯一地标识对象。对照执行结果我们发现,两个相同的字符串的哈希值是相同的,而两个StringBuffer对象,尽管用同样的字符串为参数创建,哈希值却不同。原来,Java把String对象当作常量来处理,两个串常量既然值相同,就认作是一个。而StringBuffer则不同,它就像变量一样,尽管两个对象目前值相同却必须分开存储,以备发生改变。 读者看到第10行 s2=""+s2+""; 一句或许会有疑问:既然s2是String对象,它又不可改变,这里值怎么会变了呢?原因在于,提供静态字符串后,若不允许对它做任何操作,这个类本身的意义就不大了。如何解决这个问题呢?编译器做了这样的幕持球工作:它将String对象转化为StringBuffer对象,实施完操作后再变回String对象。比如,上面提到的语句可解释为: s2=new
StringBuffer("").append(s2).append("").toString(); 所以,虽然名字相同,此s2却不是原先的s2了,它的哈希值已发性了改变。而我们对StringBuffer对象的悠却没有改变其哈希值。这充分表明了二者“静态”和“动态”的区别。 例子和第一段末尾(行18)用了append()和insert()方法。这两个方法是StringBuffer的主要功能所在。这两个方法有许多重载形式,比如,append()方法可在字符缓冲的末尾追加对象(Object类的对象)、字符串(String类对象)、字符数组(char[])字符数组的一部分(用两个int
值指明始末位置)、布尔值、字符、整数、浮点数、双精度浮点数等。insert()则把许多种对象插入字符缓冲之中。例子第三段第42行中给出了一些例子,演示如何将布尔值、整数、长整数放入缓冲区。 例子第二段中演示了String类的部分方法,我们无法也不必要列出该类的所有方法——读者可以轻易地从网上得到Java类库的文档来查阅——只将例子中用到的作一解释,以期起到指导读者使用的作用。 toUpperCase()和toLowerCase()是进行大/小写转换的方法。replace()方法将字符串中的某字符(第一参数)替换成另一字符(第二个参数)。它将对串中所有该字符的出现都进行替换。 equals()方法是比较两个字符串是否相等的,也即比较每个字符是否对应相等。 equalsIgnoreCase()也是判等方法,但它忽略字符大小写民的不同。trim()方法去年字符串开头和结尾的空白字符。 indexOf()方法求出某个字符在字符串中第一次出现的位置,它的参数是int型的,返值也是整型。它还有一个重载形式是以两上int型变量为参数,这时,第一个int指待查字符,第二个int指起始查找位置。如语句 indexOf('h',3); 指从第3个字符起查找'h'字符第一次出现的位置。如果等查找字符在串中并不存在,将返回-1。此外,indexOf()还可以用来查找某一子串在字符串中的位置,与前面两种重载形式相对应,也是两种形式,不同在于前面的待查找字符换成待查找的String对象即可。 indexOf()方法用来寻找给定字符(串)在字符串中第一次出现的位置,对应地,lastIndexOf()方法则寻找给定字符(串)中字符串中最后一次出现的位置。它也有四种重载形式。 charAt()方法用来取字符串中给定位置的字符,因此,参数为整型,返值为字符型。注意,用于指明位置的int值必须大于等于零,小于字符串长。否则,将出现StringIndexOutOfBoundsException,即字符串下标越界异常(由于它属于运行时异常,程序可以不处理它)。 例子的第三段(41至50行)演示了StringBuffer的方法。除了前面提过的append()和insert()方法之外,还用了一个setLength()方法,该方法强制限定字符缓冲的大小。若设小了,会发生字符的丢失(我们的例子中有意制造了这种情况),设大了,多出来的字符会置为零值。该方法也可能出现StingIndexOutOfBoundsException。 最后附加一点说明。上面的例子对于JDK1.0.2与JDK1.1.1版本中为20527408和20527520。 以上例子覆盖了这两个类的常用方法。我们没有演示的,读者也可举一反三。你将发现使用这两个类将为串操作带来莫大的方便。
6.3 类型包装
进行过程序设计的人们对“类型”并不陌生。在传统的程序设计中,类型的说明和使用都很方便。这似乎已是非常完善的概念了。然而,进入面向对象编程之后,类型却显露出一些弱点。比如,许多方法的操作是针对对象的,而无法操作某个类型的变量;类型不是类,不可以被实例化,也不可被继承。例如,在java.util包中提供了一个Vector类(以后将会讲到),它就像一个多态数组,可以存放任意类的对象。这在某些应用场合中无疑是很有用的,但我们若想在其中放一个整数却不可能。 类型不是类,这使得它在面向对象的世界里颇有些无立足之地的感觉。但我们对它的应用又太熟悉了,绝不能忍痛割爱。Java的设计者们想出了“对类型进行包装的办法。对整型、字符型、数字型、长整型、浮点型、双精度型、布尔型等基本类型分别构筑了相应的类(可参看下文例子中前七个import语句),这些类中,封装了一些有关的常量,以及相关的类转换、判断、求值等方法。也就是说,为类型创建了带有面向对象特点的“包装类”(Wrapped
class)。 包装类的名字与相应的类型较为接近。类的名字首字母境外是大写的。这些类有一个共性,都有将对象转化为基本类型的方法。如Boodean类有booleanValue(),Character类有charValue()等。数类Number则有四个方法,分别求出对象所对应的int型值、long型值、float型值、double型值。Integer、Long、Float和Double类作为Number的子类,自然也拥有这四个方法。这些数值转换方法都很好记,名字都是返值类型加上“Value”。 下面让我们一起来看例子6.2,介绍一下用到的变量和方法。 例6.2WrapDemo.java 1:import
java.lang.Boolean; 2:import java.lang.Character; 3:import
java.lang.Number; 4:import java.lang.Double; 5:import
java.lang.Float; 6:import java.lang.Integer; 7:import
java.lang.Long; 8:import
java.lang.System; 9: 10:public class
WrapDemo{ 11: public static void main(String[]
args){ 12: System.out.println("Demo of
Boolean..."); //布尔型 13: Boolean b1=new
Boolean(true); //求对应的基本类型值 14: if(b1.booleanValue())
System.out.println("The value is
true!"); //字符型 15: System.out.println("Demo of
Character..."); //变为大写 16: Character ch=new
Character(Character.toUpperCase('a')); //判等 17: if(b1.equals(ch))
System.out.println("b1 equals ch"); 18: else
System.out.println("b1 not equals ch"); //进制转换 19: int
i=Character.digit('e',16); 20: System.out.println("Character.digit('e',16)="+i); 21: System.out.println(Character.forDigit(i,16)); 22: System.out.println("Demo
of Integer..."); //整型 23: Integer itg=new
Integer(15); 24: System.out.println("flaot
value="+itg.floatValue()+" lang value="+itg.longValue()+" double
valur="+itg.doubleValue()); 25: System.out.println(Integer.parseInt(Integer.toString(22,16),10)); 26: System.out.println("Demo
of
Long..."); 27: try{ 28: System.out.println("0xabcd="+Long.parseLong("abcd",16)); 29: }catch(NumberFormatException
ex){ 30: System.out.println("Illegal
Long"); 31: } //浮点型 32: System.out.println("Demo of
Float..."); //常量 33: System.out.println("Now list the
Float's static const:"); 34: System.out.println(" MAX_VALUE="
+ Float.MAX_VALUE ); 35: System.out.println("
MIN_VALUE="+Float.MIN_VALUE); 36: System.out.println("
NaN="+Float.NaN); 37: System.out.println("
POSITIVE_INFINITY="+Float.POSITIVE_INFINITY); 38: System.out.println("
NEGATIVE_INFINITY="+Float.NEGATIVE_INFINITY); 39: Float
fltnum=new
Float(12345678987.98765432); //值转换 40: System.out.println("12345678987.98765432's
float value="+fltnum.floatValue()+";Int
value="+fltnum.intValue()); 41: System.out.println("long
value="+fltnum.longValue()+";double
value="+fltnum.doubleValue()); //双精度型 42: System.out.println("Demo
of Double..."); 43: Double db=new
Double(123.4567); 44: System.out.println("123.4567's float
value="+db.floatValue()+" double
value="+db.doubleValue()); 45: } 46:} 执行例6.2,得到
如下的运行结果: D:\archive\MyDeomo\ch6>java WrapDemo Demo of
Boolean... The value is true! Demo of Character... b1
not equals ch Character.digit('e',16)=14 e Demo of
Integer... flaot value=15.0 lang value=15 double
valur=15.0 16 Demo of Long... 0xabcd=43981 Demo
of Float... Now list the Float's static
const: MAX_VALUE=3.4028235E38 MIN_VALUE=1.4E-45 NaN=NaN POSITIVE_INFINITY=Infinity NEGATIVE_INFINITY=-Infinity 12345678987.98765432's
float value=1.23456788E10;Int value=2147483647 long
value=12345678848;double value=1.2345678848E10 Demo of
Double... 123.4567's float value=123.4567 double
value=123.4567
D:\archive\MyDeomo\ch6>
在这个例子中,我们首先创建了一人布尔类的对象b1(13行)构造函数的参数是一个布尔值true。我们还可以用String对象来作参数,但这个字符串应转化成布尔值。比如,我们可以用“true”来做这个参数。然后我们用booleanValue()求b1对应的布尔14行。 Character类的方法较多,先看看创建对象(16行)。其构造函数唯有一个,是用一个char型值作为参数。例子中用Character.toUpperCase('a')来做参数,实际是用'A'作参数。不难看出,toUppercase()是一个类方法(static方法),作用是把字母变为大写。 以下一个语句中用了布尔类的成员方法equals(17行)。为什么能把Boolean对象与Character对象相比较呢?原来equals()的参数是Object类的对象,所有完全可以这样用,当然这样比一定是不会相等的,这样用就是为了提醒读者注意参数类型。 Character类的类方法digit(char
ch,int radix)返回在radix进制下ch对应的值。如'e'在16进制中代表14。而 forDigit(int
digit,int
radix) 则相反,将给定进制下的一个数值转化为字符,如forDigit(14,16)返回值为'e'。 Integer类除求值的方法intValue()之外,还有parseInt()方法也可以返回int值。该方法有两种重载形式,例子中的这一种有两个参数,参数一为字符串,参数二是一个int值,指明进制。其原型为: public
static int parseInt(String s,int radix) throws
NumberFormatException 功能是从字符串中分析出指定进制的整数。若串不合法,抛出数格式异常NumberFormatException。另一种重载形式不指明进制,只有一个字符串参数,功能是分析出十进制的整数,原型为 public
static int parseInt(String s) throws
NumberFormatException。 与parseInt()相对的,Integer类还有从int值转化为字符串的方法,也有两种形式,一种指明进制,一种不指明进制。原型为: public
static String toString(int i,int radix) public static String
toString(int
i) 例子25行将parseInt()和toString()一起运用,将十进制的22变为16进制的16。 Long类中有与parseInt()相似的方法parseLong(),也有toString()方法,用法显而易见,参照例子就可以明白(28行)。 Float与Double类中均有判断是否为无穷大的方法isInfinite(),也有判断是否为NaN的方法IsNaN()。其中NaN是“Not
a
Number”的缩写,意即不是任意一个数。此外,数类Number的各子类中均有一些常量。例子中我们列举了Float类的一些常量作为示意。另外,我们在创建Float对象时特意选了一个较大的值,以例请读者看一下inValue()与longValue()的区别。显然,软化为整形整值后由于数过大而出现了错误。 1.1版的API中增添了Void类,以封装void类型。该类不可被实例化,只含有一个成员域TYPE: public
static final Class
TYPE 即表示Void类的Class对象。 乍一接触包装类,可以会觉得它多少有些多此一举。但正如本节开头所提的,这种处理对Java这样一种纯面向对象的语言是必要的。读者可以在将来的编程实践中逐步加深体会。
[目录][上页][下页]
|