已阅读5页,还剩55页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第一章 Java概述本章是Java编程语言的概述,可以让你迅速开始编写Java程序。在本章中,我们概要地讨论Java,而不涉及技术细节,后续章节将详细讨论Java的特性。1.1初步认识Java程序是由类(class)构建而成的。从类定义开始,你可以创建任意多的对象(Object),这些对象通常称为那个类的实例(instance)。你可以将类想象成一个工厂,它具有零件的蓝图和指令,对象则是工厂制造的零件。类包含成员(member),最主要的成员有域(field)和方法(method)。域是属于类本身好哦类对象的数据变量,它们构成对象或类的状态(state)。方法是在域上进行运算从而操纵状态的语句(statement)集合。语句定义类的行为:把值赋给域或其他变量,计算算术表达式,调用方法和控制执行流程。长期以来,介绍任何语言时用的第一个例子都是打印“Hello,world”。class HelloWorldpublic static void main(String args)System.out.println(“Hello,world”);用你熟知的文本编译器把上述源程序存放为文件,然后运行编译器把源程序编译为Java字节码(bytecode),即Java虚拟机的“机器语言”。编辑和编译源程序的细节因系统而异,详细信息可参考系统手册。在我们最常用的系统,即由Sun Microsystem免费提供的Java2软件开发工具包(Java2 SDK)上,你需要将上述源程序存在为HelloWorld.java的文件。使用下面的命令进行编译:javac HelloWorld.java要运行该程序时,输入:java HelloWorld这将执行HelloWorld的main方法。当用户运行该程序时,屏幕上就会显示:Hello,world现在已经有了一个能做点事的Java小程序,但它是什么意思呢?上述程序声明了一个叫做HelloWrld的类,它只有一个成员:main方法。类成员出现在类名后的花括号内。main方法是一个特殊的方法:如果想前面那样声明类的main方法,那么,当我们把类作为应用程序运行时,它就会被执行。在运行时,main方法能够创建对象,为表达式求值,调用其他方法,以及完成为应用程序行为所需要的其他任何事情。main方法被声明为public ,这样Java虚拟机中的任何对象都能调用它;同时它也被声明为static,这意味着该方法属于类本身,而不是与特定实例相关联。方法名前面是方法的返回类型。main方法声明为void的,因为它不返回任何值,也就没有返回类型。方法名后面是方法的参数(parameter)列表零个或多个的类型与名字对,彼此间用逗号隔开,整个包含在()之间。main方法仅有的一个参数是String对象的数组,有名字args引用。对象数组以类型名后面加的形式表示。从命令行调用该程序时,args包含所带的自变量(argument)。数组、字符串将在本章后面介绍。args对main方法的意义将在第二章第62页介绍。方法的名称和参数列表一起组成方法的签名(signature)。签名和修饰符(比如public 和static 等)以及抛出异常列表(本征后面将谈到)一起组成方法头(header)。方法的声明(declaration)包括方法头及其后面的方法体在花括号之间出现的语句块。在本例中,main方法体只包含一条调用println方法的语句使用分号结束该语句。方法通过提供对象引用(在本实例的System.out中out域属于System类)和方法名(println)来调用,它们之间用句点隔开。HelloWorld通过out对象的println方法,在标准输出流上打印以换行符终止的字符串。输入的字符串是文字串(string literal)“Hello World”,它作为自变量传递给println。文字串是在双引号“和”之间的字符串序列。1.2变量下一个例子打印Fibonacci Saguence 序列,这个无穷序列的前几项是:1 ,1,2,3,5,8,13,21,34Fibonacci Saguence 的前两项是1和1,后续各项是前两项之和。Fibonacci Saguence序列打印程序非常简单,从中可以知道如果声明变量(variable)、编写简单的循环,以及执行基本的算术运算。这个程序像HelloWorld那样声明了一个Fibonacci类,它具有main方法。在main方法中,前两行声明了两个局部变量hi和lo,hi是序列中的当前项,lo则是前一项。局部变量在代码块内部声明,比如在方法体中,但是,它与域不同,域是作为类的成员声明的。声明变量时,必须定义它的类型(type)。hi和lo的类型是int ,32位带符号整数,取值范围是。Java的基本数据类型包括整型、浮点型、布尔型、和字符型值。基本数据类型中存储的数据比较直观,它与程序员定义的对象类型不同。类型的每一个便都必须显式定义。Java的基本类型有:boolean char byte short int long float double 在Fibonacci程序中,我们声明hi和lo 变量,同时初始化为1.初始值是在变量声明时通过=运算符被初始化表达式所设定。=运算符(也叫赋值运算符)将它左边变量的值设为右边表达式的值。在初始化之前,局部变量尚未确定。用户必须在声明变量时就对变量进行初始化,但如果在给变量赋值之前使用它,Java编译器及会拒绝编译,直到问题得到改正。由于hi和lo是同一种类型,我们就可以用更简洁的方式来声明它们。我们可以一次声明给定类型的多个变量,用逗号将它们(包括其初始化表达式)隔开。例如,你可以用一行等价的代码替换main中的前两行:int lo = 1,hi = 1;注意,把一行断开不会影响语句的意思断行、空格、制表符和其他空白符只是为了程序员的方便。程序中的while语句是Java 中的一种虚化方式。while语句中的表达式被求值,如果为true(真),就执行循环体,然后,继续测试表达式。当表达式变为false(假)时,while循环结束。如果它永远不为false,程序及会不断运行,除非循环中有break语句或异常发生。while的循环体由单个语句组成。它可能是简单语句(例如,调用一个方法),也可以是其他控制语句,或者语句块在花括弧之间的零条或多条单独的语句。while循环测试的表达式是职位true或false的布尔值表达式。用比较运算符比较两个值得相对大小,或者用=运算符或!=运算符测试两个值是否相等,这些都可以形成布尔型表达式。上述布尔型表达式hi50,测试序列中当前最大值是否小于50。如果最大值小于50,则打印它的值并计算下一项。如果最大值等于或大于50,控制权就就给while循环体后的第一条语句。在这个程序中,就是main方法的结尾,程序随机结束。为了计算序列中的下一个值,我们进行了一些简单的算术运算,并用=运算符把右边的算术表达式的值赋给左边的变量。正如读者所预期的那样,+运算符计算其操作数的和,而-运算符计算其差。Java语言中定义了许多用于基本整型和浮点型数据的算术运算符,包括加号(+)减号(-)乘号(*)和除号(/),以及后面将谈到的其他运算符。在上例的Fibonacci程序中,println方法接受一个整型变量,而在HelloWorld中,接受一个字符串型变量,其实,println是许多重载(overload)方法之一。经过重载后,方法可以接收不同类型的变量。运行时系统根据传递给它的自变量类型和数量,从而决定实际调用哪个方法。1.3程序中的注释夹杂在程序中的英语文本就是注释。如Fibonacci程序所示,Java有三种形式的注释。注释允许你在代码相关位置编写描述性的文本,便于将来阅读这些代码的程序员理解。这些程序员中可能就有你,只不过是在几个月或几年之后。通过给自己的代码添加注释,可以节省以后的时间。而且,在编写注释时常常会发现代码中的错误,因为要解释代码,这就会迫使你去仔细考虑代码。在/*和*/之间的文本会被编译器忽略。这种类型的注释可以用于某行的一部分,一整行,以及最常见的是(如例中所示),用它来定义多行的注释。对于单行和不到一行的注释。可以用/来告诉编译器,忽略该行中它后面的任何内容。第三种注释出现在最上面,位于/*和*/之间。以两个星号起头的注释叫做文档注释。文档注释紧跟其后的声明部分。在上例中,这些注释主要说明main方法。你可以通过一种工具,把注释提取出来,生成类的参考文档。习惯上,文档注释中或/*和*/之间的每行注释前,都会有一个星号,它是作为给读者展示注释的可视性标志。1.4命名常量常量是指如12、17.9和“String Like This”这样的一些值。常量,正如其子面含义,是指定的、不必运算的,而且在程序生命周期内保持不变的值。Fibonnaci程序打印小于50的所有费波纳数。常量50被用于while循环的表达式,以及描述mian方法的文档注释中。如果你想修改这个例子,使其打印小于100的所有费波纳数,那么,你将不得不浏览整个源代码,查找所有的常量50并将其修改为100。这对于该例并不复杂,但在大多数情况下,这将是一件费时并且容易出错的事情。此外,当人们读到表达式hi50时,并不知道50确切代表什么。这样的“魔术数字”降低了程序的可读性和可维护性。命名常量(named constant)是通过名字引用的常量值。例如,在费波纳的例子中,我们就用名字MAX来引用常量50。定义命名常量时要相应地声明其类型值以及初始值。这样并未并以一个常量,而只是定义了一个可以通过赋值语句改变其值的域。要使我们声明的值为常量,可以将其声明为final。fianl域或者变量一旦初始化以后,就不能在更改了即它是不可变的。此外,我们也不希望命名常量与类的实例相关。所以把它声明为static的。你可以通过类名加句点再加上成员的名字来访问类的static(静态)成员。经过如上声明以后,程序中的牌就可以通过Suit.HEARTS、Suit.SPADES等访问,这样就将所有的牌名组合到一个类中。注意修饰符final和static的顺序无所谓虽然在程序中我们使用了相同的顺序。在前面的所有例子中,我们已经访问过static 域了,你可能意识到out 是system类的static域。1.5 Unicode字符假设我们定义了一个处理圆的类,并且希望用一个命名常量类代表符号。在绝大多数程序语言中,我们将把常量命名为pi,因为大多数语言的标识符(名字的技术术语)仅限于ASCII字符集中的字母和数字。但在Java中,我们可以这样定义:Java将你带进国际化的软件世界:以Unicode这种国际字符集标准来编写Java程序。Unicode是16位字符集,它的字符足够覆盖世界上使用的主要语言,因此可以用作上例中的常量名。是Unicode希腊语区的一个合法字符,所以也是Java的有效字符。绝大部分Java程序用ASCII(一种7位标准字符集)或者ISO-Latin-1(一种8位标准字符集,通常称为Latin-1)编写,但是在处理之前它们都要转换成Unicode。这样,Java的字符集就总是Unicode。1.6 控制流控制流失决定程序中那条语句将执行以及什么顺序执行的术语。费波纳程序中的while循环就是一种控制流语句,它对于多个语句块,定义了这些分组语句的执行顺序。其他的控制语句包括for,if-else,switch do while 我们现在改变费波纳程序为序列元素进行编号,并用星号标记偶数元素。为了给序列元素进行编号,我们用for循环代替了while循环。for循环是while循环的简化形式,它增加了循环变量初始化递增部分。ImprovedFibonacci中的for循环与下面的while 循环的等价for循环引用一种新的变量声明机制:在初始化部分钟声明循环变量。这种方法很方便,声明的变量只在循环执行时存在,并且只对for循环起作用其他控制语句都不允许在语句内部声明变量。变量i值在for语句的循环体内可用。以这种方式定义的循环变量在循环结束时就不存在了,也就是说,变量名对后面的语句而言是可重用的。如果以前没有接触过C程序语言,你可能不熟悉上面程序段中的+运算符。+运算符使与它相邻的变量(本例中指i)递增一个单位值。如果+在操作数前面,则是一个前缀运算符。同样地,-运算符使与它紧邻的变量递减一个单位值,它也可以用作前缀运算符。+和运算符均来源于C语言。在上例的上下文中,以下语言:i+ ;等同于 :i = i+1;在原值上加一个值之后,在将结果赋给原值的表达式很常见,所以Java提供了一种简写方式。该表达式将+=运算符右边的操作数1加到左边的变量i上。许多java二元运算符(需要两个操作数的算术运算符)可以同样用=号相连。在for循环中,我们利用if/else语句判断hi值是否为偶数。if语句判断圆括号中的布尔表达式的值:如果表达式为true,执行if体中的语句(或语句块);如果表达式为false,执行else子句后的语句。else部分是可选的:如果else 不存在,表达式为false时,就什么也不做。在确定执行的代码(如果有的话)之后,就实际执行它,最后将控制权交给if语句体后的代码。上例利用%(求余)运算符验算hi是否为偶数。运算符右边的制对运算符左边的值取模得到的余数。在本例中,如果左边的数是偶数,余数则为零,接下来的语句把一个包含偶数标识的字符串赋值给mark。如果是奇数,则执行else语句,mark被赋值为空字符串。在本例中,println的调用要复杂一些,因为prinrln的子变量自身在println调用之前,方法先要计算出来。首先,我们用到表达式“1”+lo ,该表达式在字符串常量“1:”后面加上代表lo(初始值位1)的字符串从而形成字符串1:1。只要有一个操作数是字符串,而另一个是其他类型,这时,+号运算符就变成了连接运算符。在方法的参数列表中使用连接运算符,这是一种常见的使代码避免过于详细冗长的方式:在for循环体内的println调用构造了一个字符串,该字符串由代表当前循环变量i的字符串,分隔字符串(:)代表hi当前值的字符串和marker字符串组成。1.7 类和对象Java和所有的面向对象程序设计语言一样,通过类和对象的概念提供了一种程序设计工具。Java中的每个对象都有类(class),类定义了对象的数据和行为。每个类都有三种类型的成员:域(field) 是与类和它的对象相关联的数据变量。域保存通过类运算的结果。方法(method) 包含类的可执行代码。方法由语句构建。方法的调用方式及方法包含的语句最终决定程序的执行。类和接口可以是其他累或接口的成员(很快就要说到接口了)。这个Point类有两个域,分别表示一个点的x,y坐标,现在它还不具有方法。这样的类生命从概念上说是一个计划,指明了由类产生的对象会是什么样子,并定义这些对象行为的指令集。类成员可以有多种级别的可见性(visibility)和可访问性(accessibility)。Point类的x,y的public 声明意味着所有可以访问Point对象的代码都可以读些这些值。其他级别的可访问性将限制类本身代码,或者其它相关类代码对成员的访问。1.7.1创建对象对象通过含有new关键字的表达式创建。从类定义创建对象也叫做实例化(instantiation)所以对象经常叫做实例(instance)。在Java中,新创建的对象分配在叫做堆(heap)的系统内存区中。Java中的所有对象都通过对象引用(object reference)进行访问那些看起来讲有对象的变量,实际上只是将由该对象的引用。这种变量类型称做引用类型(Object reference),它与基本类型不同,基本类型的变量中存在的是对应类型的值。当对象引用不再引用任何对象时,就为空(null)。大多数时候,不必精确区分实际对象和对象的引用。你可以将“把对象引用传递给方法”说成“把对象传递给方法”。只有需要区别对待时,我们才要注意它们的差异。大多数情况下,可以混用“对象”和“对象引用”。在Point类中,假设你要开发一个图形程序,需要跟踪许多个点。你可以用一个具体Point对象表示一个点。每个Point 对象都是唯一的,并有自己的x和y域。改变lowerLeft的x的值,并不会影响upperRight对象的x 值。对象的域被称为实例变量(instance variable),因为在类的每个对象(实例)中都有唯一的拷贝。1.7.2静态域或类域按对象访问域往往是你所需要的。你通常需要某个对象的域与其他同类实例化而成的对象的同名域不同。但是,有时候你可能需要同类的所有对象共享一个域。这种共享的变量叫做类变量(class varible),它特定于类而不是类的对象。为什么要用类变量呢?我们以索尼Walkman工厂为例。每个Walkman对象都具有唯一的序列号。用对象的说法,就是每个Walkman对象具有唯一的序列号域。但是,工厂需要保留下一个被分配序列号的记录。你并不愿意在每个Walkman对象中保留这个值,只需在工厂中保留这个数值的拷贝,用对象的说法就是类变量。在Java中,可以通过把域声明为static获得特定于类的域,它们有时也叫静态域(static field)。例如,代表原点的Point对象很常用,所以在Point类中讲台作为一个静态域:public static Point origin = new Point();如果这个声明出现在Point类生命中,就会有一个称为Point.origin的数据,总是指向(0,0)处的对象。不管创建多少Point对象,或者一个也没有创建,这个静态域总是存在。x和y的值之所以是0,是因为如果没有显式地初始化为别的值,数值型域的默认值为0。现在读者可能就明白为什么命名常量都声明为静态的(static)的。本书中出现的“域”,一般是特定于对象的,虽然有时候为了明确起见称为“非静态域”。1.7.3垃圾收集器利用new 创建对象后,如果不想在用那个对象了,怎样去掉呢?答案很简单,不再引用它就是了。未被引用的对象将由垃圾收集器(garbage collector)自动回收。垃圾收集器在后台运行,并跟踪对象引用。当对象不再被引用时,垃圾收集器就把它从内存分配堆里除去,虽然真正的删除可能要等到合时的时候。1.8方法和参数由于前面定义的Point类的域声明是public,因此,凡是具有Point对象引用的代码都可以操纵该类对象。Point类是一个非常简单的类。有些类就是这样简单。事实上,定义类的目的可能只是为了满足包(协同操作的一组类)的内部需要,或我们仅需简单的数据容器。然而,面向对象的真正好处在于隐藏了内部数据操作的具体实现。类的操作通过方法(即操作对象数据以获得结果的指令)来声明,方法可以访问对其他对象隐藏了的实现细节。把数据隐藏在方法后,使别的对象不能访问,这是数据包装(data encapsulation)的基础。如果我们要给Point类增加clear方法,程序因该如下:public void clear() x = 0; y = 0;clear 方法没有参数,所以方法名后面是空的括号对()。此外,clear声明为void,因为它不返回任何值。在方法中,类的域和其他方法可以直接被命名我们只用简单地说x 和 y,而不必显式指出对象引用。1.8.1调用方法尽管在Point类中,我们看到类可以将它的域声明为公共访问的,但是,在一般情况下,设计得很好的类都要隐藏它们的数据,只需本类中的方法修改。我们可以利用类引用+句点+方法名的形式调用方法。自变量(argument)将括号内用逗号隔开的数值列表传递给方法,没有自变量的方法也要求有括号,这是括号内什么也没有。方法可能只返回单一的值。如果让方法返回多个值,必须创建另一个对象,它唯一的目的就是保存返回值,并返回这个对象。当调用一个方法时,执行流就离开当前的方法,从被调用方法的方法体开始执行。当被调用方法执行结束后,当前方法就从调用方法的那条语句之后开始执行。当我们开始执行方法体时,从方法的角度来看,方法调用的目标对象就是当前(current)或接受方法对象。传递给方法的自变量通过方法声明的参数进行访问。下面试一个叫做distance的方法,它是Point类的一部分。distance方法以另一个Point对象为参数,计算它自己和另一个点之间的距离,并返回返回一个双精度浮点结果。return语句使方法停止执行,并把执行权返回给调用它的方法。如果return语句有一个表达式部分,那么该表达式的值就是方法调用的返回值。表达式的类型必须与方法所声明的返回类型相匹配。在上面的例子中,我们用Math类的sqrt方法来计算两个x 和 y坐标的距离的平方和的平方根。基于前面创建的lowerLeft和upperRight对象,可以这样调用distance方法:double d = lower.distance(upperRight);这里,upperRight作为参数传递给distance,distance将把它看做参数that。这个语句执行后,变量d的值就是lowerLeft和upperRight之间的距离。1.8.2 this引用有时候,接受对象需要知道它自身的引用。例如,接受对象可能要将它自己加到对象列表上。方法具有一个叫做this的隐式引用,this是对当前(接收)对象的引用。通常将this用做其他需要对象引用方法的参数。this引用也可以用于显式地命名当前对象的成员。下面试Point的另一个名为move的方法,它把x和y设置为特定的值:move方法利用this指明引用那个x 和y。把参数命名为x和y是有道理的,因为我们要把x和y坐标值传递给方法。如果我们仅写x=x 我们就把参数x的值赋给它自己,而不是所需的x域。表达式this.x是指对象的x域,而不是move的x参数。1.8.3静态方法或类方法正如可以有特定于类的静态域,也可以由特定于类的方法,通常称为类方法(class method)。类方法常用于完成特定于类自身的操作,这些操作通常用于类的实例。类方法通过static关键字声明,所以叫做静态方法。与术语“域”一样,“方法”通常意味着特定于对象的方法,虽然有时候为了清楚起见叫做“非静态方法”。为什么需要静态方法呢?在以索尼的Walkman工厂为例/下一个序列号的记录保存在工厂中,而不是在每个Walkman中。返回工厂下一个序列号拷贝的方法必须是静态方法,而不能使操作于特定Walkman对象的方法。上例中distance的实现是用了静态方法Math.sqrt计算平方根。Math类支持许多通用数学操作的方法。这些方法都被声明为静态方法,因为它们并不在任何一个特定Math类的实例上运算,而是在类中形成相关的功能集。静态方法不能直接访问非静态成员。调用静态方法时,并没有特定的对象引用让它操作。但是在一般情况下,静态方法完成类方面的操作,非静态方法完成对象方法的操作。作用于对象域的静态方法,其工作方式就如同让Walkman厂家修改金门公园中慢跑者要带上的Walkman的序列号。1.9数组只有一个值的简单变量是有用的,但是对于许多应用程序还不够用。一个玩牌的程序需要许多Card对象作为一个整体来操纵。为了满足这个要求,Java提供了数组。数组是相同类型的集合。你可以通过简单的整形下标访问数组成员。在玩牌游戏中,Deck对象定义如下:首先要声明常量DECK_SIZE,它表示一副牌的张数。该常量是public的,所以任何人都能通过它得知deck中有多少牌。接下来,我们要声明一个引用所有牌的cards域。该域声明为private的,意味着只有当前类的方法可以访问它从而防止其他人直接操纵我们的牌。修饰符public 和private 是访问修饰符(access modifiers),因为它们控制了谁能访问类、域和接口。在声明中通过在类型名后跟方括号和,来声明一个cards域,它是作为card类型的数组。我们把cards初始化为DECK_SIZE大小的Card类型的数组,每个Card元素的初始值为null。数组长度在创建后就保持固定不变。print方法调用说明了数组元素的访问方法,即把所需元素的下标用方括号括起来,跟在数组名后。从程序中我们可以看出,数组对象具有一个length域,表明数组中的元素个数。数组的边界为0length-1之间,包含上下界。访问数组边界以外的元素是一种常见的错误,特别是在数组元素上执行循环时。为了捕获这种错误,所有的数组都要进行越界检查,确保下标在边界内。如果试图使用超出边界的下标,运行时系统将向程序报告该错误,方法会抛出IndexOutOfBoundsException异常。本章后面读者将了解更多有关异常的知识。长度为零的数组是空数组。以数组为自变量的方法一般要求接收到的数组非空,并将检查数组的长度。但是,在检查数组长度之前,首先必须保证数组非空。如果其中任意一个检查失败了,方法就会抛出IllegalArgumentException异常,报告该问题。例如,下面是一个球整形数组平均值的方法:该段代码能够正常工作,但由于使用了嵌套的if-else语句来验证数组非空,从而使方法的逻辑很混乱。为了避免两个if-else语句,可以使用OR运算符(|)来判断数组是否为空和长度为零。然而,这段代码不是正确的。即使值为null,程序还是会试图访问length域,因为正常的布尔运算符会计算两个操作数。于是,普遍的做法是采用逻辑运算,通过定义特别的运算符解决这个问题。条件运算符只有在计算了左边的操作数之后,还不能确定表达式的值,这时,才会计算右边的操作数。我们可以用条件或运算符(|)来改写代码:如果value的值为null,则条件布尔表达式的值就为真,从而不必访问length域。对于二进制布尔运算符域(&)、或(|)和异或(),当其操作数是布尔值时,它们就是逻辑运算符;当其操作数是整数时,它们就是位运算符。条件或(|)和条件与(&)是逻辑运算符,只能对布尔值进行运算。1.10字符串对象Java利用String(字符串)类类型处理字符串数据序列,并为它们的初始化提供语言级的支持。Java的String类提供了许多操作String对象的方法。我们已经在HelloWorld程序中看到过String常量。当编写类似System.out.println(“HelloWorld”);的语句时,Java 编译器实际上创建了一个新的String对象,把它初始化为指定的常量值,并把String对象作为自变量传递给println方法。在创建String对象时,不必指定它的长度。可以在一个语句中创建一个新的String对象,并加以初始化。这里,我们声明了一个名为myName的String对象饮用,应用一个String常量对它进行初始化。在初始化之后,通过String连接运算符(+),产生一个含有新值的String对象。最后,我们在标准输出流上打印myName的值。运行上述程序的输出是:Name = Petronius Arbiter连接符也可以用简化的+=运算符形式,将源字符与给定字符串合并,并作为结果返回给源字符串。String对象具有一个length方法,它会返回String中的字符串个数。其中,字符下标为0-length()-1,你可以通过charAt方法访问。charAt方法以整数下标作为自变量,返回指定下标的字符。在这种情况下,字符串类似于字符数组,但String对象不是字符数组,用户也不能把字符数组作为字符串引用。不过,通过将字符数组作为自变量传给String构造函数,你可以从字符数组中生成一个新的String对象,此外通过toCharArray方法也可以从字符串中生成字符数组。String对象的值是只读的,或者说不变的:String对象的内容永远不变。如果看到这样的语句:第二个赋值语句赋一个新值给变量str,实际上是将向其内容的”oak”的字符串对象的引用传递给了它。每次执行一个看上去好像修改String对象的操作,例如,上面所用的+=,实际上是产生了另一个只读的String对象。StringBuffer类用于可变的字符串,这将在第9章讨论,String类也将在那一章详细说明。equals方法是比较两个String对象是否相等的最简单方法:其他用于比较字符串或忽略大小写比较的方法也将在第9章中介绍。如果用=来比较字符串对象,实际上是比较oneStr和twoStr是不是对同一个对象的引用,而不是比较其字符串是否具有相同的内容。1.11继承一个类面向地向的最大好处之一就是可以继承(或子类化,subclass)现有类的行为,同时以子类实例的身份重用原有类的代码。原有的类称为超类(superclass)。当利用继承生成新的类时,新派生的类继承了超类所有的域和方法。如果子类没有特别要覆盖(override)超类的行为,子类就继承了超类的所有行为,因为子类继承了超类的域和方法。此外,子类可以添加新的域和方法,从而添加新的行为。以Walkman为例。基本模型只有一个插座(jack),允许一个人听磁带。后来的模型带有两个插座,所以两个人可以一起收听。在面向对象的世界里,双插座模型继承了基本模型,或者说是基本模型的子类。双插座模型继承了基本模型的特性和行为,并增加了它自己的行为。顾客告诉索尼公司,他们通过希望通过双插座模型在共享磁带的同时,还可以互相交谈。于是,索尼公司增强了双插座模型的功能,增加了双向交流功能,让人们在听音乐的时候还可以聊天。双向交流模型是双插座模型的子类,继承了它所有的行为,并加入了新的行为。索尼公司还制造了许多其他模型的Walkman,后续的模型继承了基本模型的功能,它们是基本模型的子类,并继承了基本模型的特性和行为。现在我们就看看继承类的例子。我们继承前面讲过的Point类,让它表示一个屏幕像素。新的Pixel 类除了x和y坐标之外,还需要一种颜色:Pixel类继承了超类Point的数据和行为。Poxel通过增加了名为color的域,扩展了数据。同时也通过覆盖Point的clear方法继承它的行为。Pixel对象可以用任意为Point对象开发的程序。如果有一个方法需要Point类型的参数,你可以传送一个Pixel对象给它,而不会有任何问题。同样,所有Point代码都可以给任何有Pixel对象的代码使用。这种特性叫做多态(polymorphism),也就是说,像Pixel这样单个对象可以有多种(poly)的形态(morph),即可用做Pixel对象,也可以用做Point对象。Pixel类的行为继承了Point类的行为。继承的行为可以是全新的(本例中增加颜色),也可以是在遵循原有要求的基础上对行为做一些限制。受限行为的例子是位于某种Screen对象内的Pixel对象,把x和y值限制在屏幕范围内。如果原来Point类对坐标没有任何限制,那么,带有限制范围的类将不会违背原有类的行为。子类通常为所继承的一个或多个方法提供新的实现,从而覆盖超类的行为。要实现这一点,子类需要定义签名和返回类型都与超类方法相同的方法。在Pixel的例子中,我们覆盖了clear方法,从而获得了Pixel要求的行为,Pixel类从Point类那里继承了clear方法只知道Point的域,但不知道在Pixel子类中声明的color域。1.11.1 调用超类的方法为了让Pixel类完成正确的“clear”(清空)行为,我们为clear方法提供了新的实现,但首先利用super引用调用了超类中的clear方法。super引用与前面描述的this引用有许多相似之处,只是super引用超类中的东西,而this引用当前对象中的东西。调用super.clear()方法就是要超类执行clear方法,而实际上是超类的对象。调用super.clear()后,我们增加了新的功能,把color设置为空值。这里,我们选择空引用null.如果在前面的示例中没有调用super.clear()会怎样呢?Poxel 的clear方法会把color设置为null值,但是Pixel 对象从Point继承的x和y 值则没有被清除。不清除Pixel对象的所有值,包括它的Point部分,这很可能是一个错误。当调用super.method()时,运行系统从上到下检查继承层次,直到找到第一个含有method方法的超类。例如,如果Point类不含clear方法,运行时系统就会到Point类的超类中查找这个方法,以此类推。对于我们使用的其他引用,调用的方法使用对象的实际类,而不是对象引用的类型。1.11.2Object类没有显示继承其他类的类隐士地继承了Object类。所有对象都是Object类的多态形式,因此,Object类是任何类对象引用的通用类型:在本例中,oref被正确地赋予了对Pixel和String对象的引用,即使这些类除了是Object类的子类以外,没有任何关联。Object类还定义了几个重要的方法,你将在第3章学习到。1.11.3类型的强制转换以下代码看起来似乎没有什么问题(如果不是特别有用的话),但结果却会导致编译错误:我们声明并初始化一个String引用,接着把它赋给一个通用的Object引用,然后再试图把对String引用在再次赋给String引用。结果却无法工作,这是为什么呢?问题在于,虽然String总是一个Object单Object不一定时String,即使在本例中,Object的内通确实是一个String,但编译器没有这么聪明。为了使编译器能够理解,必须说明obj所引用的对象确实是String然后才能把它赋给name。告诉编译器表达式的类型实际上是一种不同的类型,这被称为类型强制转换(type casting)或者类型转换(type conversion)。你可以通过在表达式前面的括号中添加表示类型的前缀来进行强制转换。当你这样做的时候,编译器不会自动相信你,因此,它要对此进行检查。好的编译器能在编译时就告诉你是否有错,否则就添加一个运行时检查,以验证强制转换是否允许。如果你侥幸通过了编译时,而运行时检查失败,那么,运行时系统降抛出ClassCastException异常,向用户报告该错误。Java语言是强制类型的,类型之间的赋值有很严格的规定。1.12接口有时候只需声明对象必须支持的方法,而不必提供那些方法的实现。只要它们的行为符合某种特定的准则成为约定(ccontract)方法的实现细节并不重要。这些声明定义了一种类型(type),任何实现这些方法的类就成为具有这种类型,而不管方法是如何实现的。例如,判断一个值是否包含在一个集合中,那些值的保存形式并不重要。你只希望方法在值的链表、散列表或其他任何数据结构上工作的一样好。为了支持这种情况,可以定义接口(interface)。接口与类相似,但是只有方法的声明。接口的设计者所声明的方法必须被实现接口的类所支持,并且,还声明了那些方法应该做什么。下面是一个Lookup接口,在值集合中查找一个值:interface LookupObject find (String name);Lookup 接口定义了一种方法find ,它以一个String类型的值作为参数,返回与改名字相关联的值;如果没有相关的值,则返回null。这里并没有给出这种方法的实现,即由实现接口的类对特定的实现负责,这里,我们只给出了一个方法的蓝图,并没有方法体。使用Lookup类型引用(指向实现Lookup接口的对象)的程序可以调用find方法,并得到预想的值,而不管对象的实际类型是什么;类可以实现你选择的许多接口。接口也可以声明static 或者final的命名常量。此外,接口还可以声明其他嵌套接口甚至类。接口的所有成员都隐式或显式的是public的,所以能够在任何可以访问接口的地方访问到它们。接口也可以通过关键字extends继承。一个接口可以继承一个或多个其他的接口,增加新的常量和方法,这些新常量和新方法必须由实现被继承接口的类实现。一个类的超类型(supertype)是它继承的类和实现的接口,包括那些类和接口的所有超类型。因而,对象不仅仅是它的特定类的实例,还是包括接口在内的任何超类型的实例。一个对象能够被多态地用于它的超类和超接口,其中包括它们任何的超类型。1.13异常当程序出错时怎么办?在许多语言中,错误条件由不正常的返回值加以标志,例如-1。程序员并不经常检查异常值,因为他们可能假定错误“不可能产生”。另一方面,向原本清晰度逻辑流程中加入错误检测和处理,这样会使逻辑模糊,正常程序变得难以理解。一个简单的任务,例如,把文件读入内存,需要大约7行代码。加入错误检测和报告后,程序扩展到40或40行以上。让正常的操作在程序中显得好像是大海中的一根针,这显然是不可取的。检查型异常(checked excentions)管理错误处理,检查型异常要求编程人员考虑在代码中可能产生错误的地方进行处理。如果一个检查型异常未被处理,它会在编译时就被注意到,而不会等到运行时,因为运行时会因为未检查的错误而复杂化。检测到错误条件的方法将抛出异常(throw)异常。异常可以由调用栈中前面的程序捕获(catch),这段程序可以根据需要处理异常,然后继续程序的执行。未被捕获的异常会终止线程的运行,但在线程终止前,线程的ThreadGroups将在第10章详细讨论。异常是一个具有类型、数据和方法的对象。用对象表示异常是有用的,因为异常对象含有报告或由特定异常修复的需要的数据类型和/或方法。异常对象一般从Exception类中派生,这个类有一个描述错误的字符串域。所有的异常都必须是Throwable类的子类,Throwable类是Exception类的超类。异常的一般范式是try-catch-finally:首先尝试(try)什么;如果它抛出异常,你可以捕获(catch)它;最后(finally),不管发生了什么,由正常的代码路径或异常代码路径进行清理工作。以下是返回从一个文件中读出的数据集的getDataSet方法。如果要读取的文件找不到或者发生了其他的I/O异常,该方法就抛出一个描述该错误的异常。首先,我们把数据集的名字转换成文件名。然后尝试打开文件,并用readDataSet方法读取数据。如果一切正常,readDataSet方法将返回一个双精度数组,我们把它返回给调用它的代码。如果打开或者读取文件导致I/O异常,就执行catch子句。catch子句创建一个新的BadDataSetException对象,并将其抛出,这样就把I/O异常转换成特定于getDataSet方法的异常。调用getDataSet的方法能够捕获并抛出异常finally子句中的代码总会执行,它关闭打开的文件(如果已经成功打开的话)。如果在close过程中发生异常,我们能捕获但却忽略它分号本身构成一个空子句,虽然该子句什么也没做。一般情况下忽略异常不是好主意,但这里是在成功读取了数据集之后,方法已经完成自己的任务栏。如果与文件有关的问题还存在,那么下次使用它时,就会报告异常,届时可以更好地处理它。你可以静finally子句用于总是执行的清除代码,甚至可以使用没有catch子句的try-finally语句。这样,保证即使在抛出未捕获异常的情况下,清除代码也能得到执行。如果方法的执行导致抛出检查型异常,它必须像getDataSet方法那样在throws子句中声明这些错误类型。方法只能抛出它声明的这些检查型异常,这就是为什么把这种异常称为检查型异常的原因。方法可以直接利用throw抛出异常,也可以调用抛出异常的方法间接抛出异常。RuntimeException、Error类型的异常以及它们的子类都是非检查型异常(unchecked exception),可以未经声明就能够在任何地方抛出。检查型异常代表这样一种情况:虽然是异常,但可以预期它的发生。如果真的发生了,就以某种方式处理比如读取文件时可能发生IOException异常。当你声明一个方法抛出的检查型异常,这意味着编译器能够确保方法只抛出它声明的异常。这种检查可以防止这样的错误:你的方法应该处理其他方法的异常,却没有处理。此外,调用你的方法的那个方法能够知道你的方法不会导致无法预料的异常。一般来说,非检查型异常表示程序逻辑中的错误,并且这中错误是无法在运行时修复的。例如,如果试图访问数组边界以外的内容,就会抛出IndexOutOfBoundException 异常,告诉用户程序计算的下标有错,或者所用的值不能作为下标。这些都是应该在代码中纠正的错误。如果假设用户在编写任何代码时都可能出错,要求声明并捕获由这些错误所导致的异常是不可能的所以称为非检查型异常。1.14包当你开发可重用代码时,名字冲突是一个主要问题。不管如何细心地挑选类和方法的名字,别人都可能把它用于别的目的。如果使用简单的描述性的名字,情况会变得更糟,其他人更可能使用这些名字。在许多程序设计语言中,解决名字冲突的标准方法是在类、类型、全局函数等前面使用包前缀(package prefix)。前缀的约定创建了明明上下文(naming context),确保一个暴包里的名字不会跟另一个包里的名字冲突。这些前缀一般有几个字符长,通常是包产生命的缩写,比如用Xt表示“X-Windows Toolkit”。当程序只适用少数的几个包时,前缀冲突的可能性不大。但是,因为前缀是缩写,名字冲突的可能性随着所使用包的增多而增大。Java采用了更正式包的概念,把具有类型和子包的集合作为它的成员。包是被命名的,并且能够导入(import)。包名具有层次性的,各组件之间用句点隔开。当使用包的一部分时,可以使用包的完
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 12月住院医师规范化培训《眼科》习题库+答案
- 中断负载均衡策略-洞察与解读
- 土壤养分时空分布-洞察与解读
- VR技术营销创新应用-洞察与解读
- 强化学习绘画路径优化-洞察与解读
- 新型冠状肺炎科普知识及预防须知22
- 品牌文化价值塑造-洞察与解读
- 安全班会课件及教案设计
- 教室玩耍安全教育课课件
- 三年(2023-2025)中考化学真题分类汇编(全国):专题20 工艺流程图题(解析版)
- 风电项目审批、开发、建设、运营所需手续全流程
- 尊重学术道德遵守学术规范学习通超星期末考试答案章节答案2024年
- 理财产品合同样本
- 小学全-英语单词+短语
- KJ9NA-NB监控系统中心站软件操作说明书213515
- DB11-T 2291-2024 建设工程电子文件与电子档案管理规程
- GB/T 5169.23-2024电工电子产品着火危险试验第23部分:试验火焰聚合物管形材料500 W垂直火焰试验方法
- MOOC 概率论与数理统计-南京邮电大学 中国大学慕课答案
- 招标代理服务服务方案
- 2023年大庆杜尔伯特蒙古族自治县事业单位人才引进考试真题及答案
- 2024届高考现代文阅读之小说叙事的对话性(含答案)
评论
0/150
提交评论