《JAVA编程资料.doc》由会员分享,可在线阅读,更多相关《JAVA编程资料.doc(67页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、 第一部分 Java 精 髓1991年,Sun Microsystems公司开始研究一种新的计算机语言,这种语言最后撼动了传统编程的基础。起初,这种语言被命名为Oak,到1995年正式命名为Java。Java在两个方面改变了编程的过程。第一,Java集成了有利于编制Internet程序的特性。第二,Java发展了计算机语言的精髓。因此,Java的重要性体现在两点:对Internet的内嵌支持和对计算机语言发展的推动。这两点中的任何一点都足以使Java成为一种出色的语言;但是只有将这两点成功地结合起来,Java才能成为一种伟大的语言,才能确定它在计算机历史中的地位。 简单数据类型和对象:完美的平
2、衡设计一种面向对象语言所面临的最大挑战,就是如何平衡对象和简单数据类型之间的抉择。从纯理论的观点来看,每种数据类型都应该是一个对象,并且都应该从一个共同的父对象派生而来。这就使得所有数据类型以相同的基本模式运作,共享一个公共的基类属性集合。现在的问题在于,如果将简单数据类型(如int和double)作为对象处理,那么对象机制所引起的额外开销会导致性能(performace)的下降。由于简单数据类型通常用于循环控制和条件语句,所以这些额外开销将带来广泛的负面影响。诀窍就是如何在“一切都是对象”的理想和“性能衡量”的现实之间找到正确的平衡点。Java非常巧妙地解决了对象与简单数据类型之间的问题。首
3、先,Java定义了8种简单类型:byte、short、int、long、char、float、double和boolean。这些类型能够直接转换为二进制代码。因此,CPU可以直接处理int类型的变量,而无需任何额外开销。在Java中,处理简单数据类型和其他语言一样快速高效。因此,由int型变量所控制的for循环可以高速运行,而不受任何对象化所带来的负面影响。除了这些简单数据类型,Java中的其他数据类型都从一个共同超类(Object类)派生而来。因此,所有这些数据类型都共享从父类继承而来的方法和属性集。例如,所有对象都有toString()方法,因为toString()是父类Object中定义
4、的方法。由于简单数据类型不是对象,因此Java可自由地以略有不同的方式处理对象和非对象。这就是Java的真正精髓所在。在Java中,所有对象都通过引用访问,而非直接访问;只有简单数据类型才可以直接访问。因此,Java程序绝对不会直接操作一个对象。这种策略可以带来很多好处,最直接的好处就是能够高效地实现垃圾回收。因为所有对象都通过引用访问:当一个对象没有被引用时,它将被回收。另一个好处是,每个Object类型的指针可以引用系统中的任何对象。当然,通过引用访问对象将产生额外开销。因为一个引用实际上是一个地址(即指针)。于是对每个对象的访问不是直接进行的,而是通过地址间接完成的。尽管现代CPU可以高
5、效地处理间接访问,但是间接访问总是不如直接处理数据本身快,简单数据类型即是通过直接方式进行的。尽管简单数据类型的处理非常高效,但是有些时候仍然需要使用跟某个简单类型等价的对象。例如在运行时创建一个整型链表,并在不再使用时将其回收(垃圾回收)。为了处理此类情况,Java为简单类型(如Integer和Double)定义了包装器(wrapper)。包装器使得简单类型在必要时可参与到对象层次的操作中来。Java关于对象和简单数据类型的平衡问题。它支持编写高效程序,同时又完美地解决了允许实现对象模型,而不用担心对简单数据类型的性能会产生负面影响。 通过垃圾回收实现内存管理 垃圾回收作为一种内存管理技术已
6、经存在了很长时间,但是Java使它焕发出崭新的活力。在C+等语言中,内存必须人工管理,程序员必须显式地释放不再使用的对象。这是问题产生的根源,因为忘记释放不再使用的资源,或者释放了正在使用的资源都是很常见的事情。Java代替程序员完成了这些工作,从而防止了此类问题的发生。在Java中,所有的对象都是通过引用访问的,这样,当垃圾回收器发现一个没有引用的对象时,就知道该对象已经不被使用,并且可以回收了。如果Java允许对象的直接访问(与简单数据类型的访问方式类似),那么这种有效的垃圾回收方法将无法实现。Java的垃圾回收策略在普遍意义上反映了Java的理念。Java设计人员花费了大量的精力,来防止
7、其他编程语言经常出现的典型问题,例如程序员经常忘记释放资源,或者错误地释放正在使用的资源。因此,使用垃圾回收策略有效地避免了此类问题的发生。 完美的简单多线程模型Java的设计者所提供的编程特性,包括对多线程多任务的语言级支持。多任务具有两种类型:基于进程的多任务和基于线程的多任务。在基于进程的多任务中,最小的可调度单元是进程。进程实际上就是正在执行的一个程序。因此,基于进程的多任务就是允许计算机同时运行两个或多个程序的特性。在基于线程的多任务中,最小的可调度单元是线程。线程定义了一个程序内的某条执行路径。因此,一个进程可以包含有两个或更多的执行线程,而多线程程序可以有两个或更多个可以并发执行
8、的部分。尽管基于进程的多任务通常是操作系统提供的功能,基于线程的多任务却可以极大地受益于程序设计语言级别的支持。例如,C+没有对多线程编程提供内置支持,于是就必须依赖于操作系统来处理多线程任务。这就意味着创建、启动、同步和结束线程都必须通过对操作系统的多次调用来实现。因此C+中的多线程代码是不可移植的。这也使得C+编程中的多线程没有得以广泛应用。由于Java内置了对多线程的支持,哪些在其他语言中必须由手工完成的工作,现在都可以由Java自动处理。Java多线程模型中最有特色的部分之一,就是其实现同步的方式。同步建立在两种创新的特性之上。首先,在Java中,所有的对象都有充当互斥锁的内置侦听器。
9、在给定的时刻,任何侦听器只能由一个线程拥有。通过使用synchronized关键字对方法进行修饰,可以启用锁定特性。在调用同步方法时,该对象就被锁定,而试图访问该对象的其他线程就只能等待。其次,Java对同步的支持也可以从所有类的共同超类Object中体现。Object声明了下面三个同步方法:wait()、notify()和notifyAll()。这些方法支持线程间通信。因此,所有的对象都对线程间通信有内置的支持。通过和同步方法结合使用,这些方法可以为线程的互操作提供高级别的控制。通过将多线程作为语言的一个易于使用的内置特性,Java改变了人们关于程序基本体系结构的观念。在Java出现之前,大
10、部分程序员将程序看成是,只有一条执行路径的单块结构。在Java出现之后,程序演变为多个可互操作的并发任务的集合。这种并发机制的改变对于计算产生了深远的影响,但是其最重大的影响可能是使得软件组件的使用更为便捷。完全集成的异常机制 异常(exception)的概念性框架出现在Java产生之前。因此,在Java出现之前,其他编程语言就已经提出了异常的概念。例如C+提出异常概念的时间,就比Java诞生的时间早了许多年。异常在Java的最初设计中就已经引入了,而不是在Java产生之后才加入的,因此Java对于异常机制的实现就显得非常重要。异常机制是在Java语言中完全集成的,是Java的基本特征之一。J
11、ava异常机制的关键在于异常是必须使用的,而不是可选的。通过异常处理错误是Java语言的规则,这与C+是有区别的。例如,C+同样支持异常机制,但是这种机制并未完全集成到整个编程环境中。考虑打开或者读取一个文件的操作。在Java中,如果某个操作发生错误,将抛出一个异常。而在C+中,打开或者读取文件的方法会返回一个专用错误代码,报告操作中发生的错误。因为C+库仍然依赖于错误返回代码而不是异常,所以从本质上来说,C+并不支持异常,程序必须不断地进行人工检查,以避免可能出现的错误。但是在Java中,程序员只需要简单地使用一个try/catch代码块,就可以自动捕捉到任何错误。对多态性支持的改进多态性(
12、polymorphism)是面向对象编程的属性,它允许多个方法使用同一个接口。Java从多个方面支持多态性,其中两个方面最为突出。第一个是每个方法(标记为 final的方法除外)都可以被子类重写;第二个是设立interface关键字。下面将给出这两方面的详细介绍。由于超类中的方法可以在派生类中重写,因此创建类的层次结构非常简单。在类的层次结构中,每个子类都将它的超类特化(specialization)。大家知道,超类的一个引用可以引用它的任何一个子类,而且通过超类的引用调用某子类对象的一个方法时,会自动执行由该子类重写后的版本。因此,可以用超类来定义对象的形式并提供对象的默认实现,而子类根据这
13、种默认实现进行修改,以更好地适应具体情况的要求。因此,在超类中定义的一个接口可以作为多个不同实现的基础。当然,Java进一步采取了“一个接口,多个方法”的概念。它定义了interface关键字,这样就可以将类的方法和类的实现完全分离。尽管接口是抽象的,但是仍然可以声明接口类型的引用。这个概念非常重要,因为它可以改进多态性的应用。只要某个类实现一个接口,并且该接口提供了某种功能,那么任何需要这种功能的代码都可以使用这个类的对象。例如,假设某个接口的名称为MyIF,考虑下面的方法:void myMeth(MyIF ob) / .任何实现了MyIF接口的对象都可以传递给myMeth()方法。该对象的
14、其他功能无需考虑。myMeth()方法可以对任何实现了MyIF接口的对象进行操作。 通过字节码保证可移植性和安全性通过字节码保证可移植性和安全性 尽管Java有许多功能强大的特性,但是如果没有字节码(byte code)这一特征,那么Java只不过是编程技术发展进程中的一个足印。字节码是Java语言的一个重要组成部分,它对程序员而言,几乎是透明的。所有的Java程序员都知道,Java编译器的输出不是能够直接由CPU执行的机器指令,而是一种经过高度优化的可移植的指令集合,这种指令集合称为字节码,它只能由Java虚拟机(Java Virtual Machine,JVM)执行。最初,JVM只是一个简
15、单的字节码解释器,现在,JVM也将字节码的on-the-fly编译技术应用到可执行代码中。无论字节码的执行采用何种方式,它的优势对于Java的成功都是至关重要的。字节码的第一个优势是可移植性。无论计算机使用何种类型的CPU(或操作系统),只要具有JVM,那么由Java程序编译而成的字节码就可以在其中执行。换而言之,只要为某个特定环境实现了JVM,那么每个Java程序都可以在该环境运行。没有必要为每个不同的环境创建都单独执行代码,因为同一种字节码可以在所有环境中运行。因此通过使用字节码,Java为程序员提供了“一次编写,随处运行”的能力。字节码的第二个优势是安全性。由于字节码在JVM的控制下执行
16、,因此JVM可以防止执行恶意操作的Java程序。保证主机安全的能力对于Java的成功是至关重要的,因为它允许创建Applet。由于Applet是可以通过Internet动态下载的小程序,因此避免Applet破坏主机的机制是非常必要的。字节码和JVM的结合,还保证了Applet的安全下载。可以说,如果没有字节码,那么Web可能根本无法达到今天的地位和影响。丰富的Java API 从概念上来讲,计算机语言由两部分组成。一是语言本身,由关键字和语法定义;二是标准库,包含一组面向程序员的类、接口和方法。尽管现在所有的主流编程语言都提供了大量的库,但是Java定义的库由于更为丰富和多样显得非常突出。人们
17、在最初创建Java时,它的库包括一组核心程序包,例如java.lang、java.io和。随着Java不断发布新的版本,新的类和程序包也被不断加入。如今的Java已经为程序员提供了功能极其强大的库函数。从Java创建之初,Java函数库就与其他语言的函数库有所不同,其中一个关键不同之处,在于Java库对网络的支持。在开发Java的时候,其他语言(例如C+)并没有提供(现在仍然没有提供)处理网络的标准函数。Java通过提供相应的类,使得连接和使用Internet的处理非常方便,从而有力地推动了Internet革命的进程。Java向所有的程序员开放了Internet,而不仅仅局限于精通网络编程的那
18、部分人。的功能改变了计算的形式。Java核心库中的另外一个关键的程序包是java.awt,它支持抽象窗口工具集(Abstract Window Toolkit,AWT)。程序员可以用AWT创建可移植的、基于GUI的代码。也就是说,程序员用AWT类可以创建一个基于视窗的应用程序,并在程序中使用各种标准的GUI元素,例如滚动条、复选框和单选框等。由于AWT的存在,程序员创建的GUI应用程序可以运行在任何支持Java虚拟机的环境中。而这种层次上的GUI移植性,在Java之前是从来没有过的。在Java中加入AWT彻底改变了程序员思考应用程序环境的方式。在Java前时代,基于GUI的程序必须明确指出其执
19、行环境。这意味着任何Windows程序需要重新编译才能够在一台Apple机上运行。Java通过一个可移植的GUI提供统一的编程环境。后来Java:Swing也被加入Java中,这是AWT的一个轻型实现。Swing组件包含在javax.swing及其子程序包中。Swing为程序员提供了一组丰富的GUI组件。与AWT相比,它们的可移植性更高。本书将通过许多例子来演示AWT和Swing如何为程序员提供函数,使他们具有编写高效、可移植的GUI应用程序的能力。如今,Java库已经基于最初的核心库得到了极大发展。Java的每个新版本都会提供一些新的库。新的程序包不断增加,新的功能也不断加入已有的程序包中。
20、Java库的发展过程处于一个连续的状态,因为Java必须能够适应快速演变的计算环境。这种在短期内适应和变化的能力也是Java的精髓之一。Applet Applet是Java最具有革命意义的特征之一,因为它能够创建可移植的、可动态下载的、能够在浏览器的限定下安全执行的程序。人们如今已经普遍接受这一观点,但是在Java前时代,这种可执行内容(executable content)受到广泛置疑:一个疑问是,恶意程序是否会对客户机造成伤害;另外一个疑问是:为某种类型的CPU和操作系统编译的代码在另一种系统上可能无法正常运行。由于连接到Internet的CPU和操作系统种类繁多,那么对于某个程序,为每一
21、类型的环境都创建一个独立的运行版本是不切实际的。Java Applet为以上两个问题提供了一套很好的解决方案。通过使用Applet,Web程序员可以方便地在静态的HTML页面中添加动态的内容。Java Applet使网页变得生动起来,从此告别了静态网页的年代。除了改变人们对于Web内容的思维方式之外,Applet还有一个重要的影响它推动了组件软件开发的发展(也可能是一个副作用)。由于Applet是小程序,因此它们通常代表很小的功能单元;而软件组件正是基于这种思想。只要按照Applet的思维考虑问题,那么就向Beans的思想迈出了一小步,甚至更多。在面向组件的体系结构中,一个应用程序由一些互相作
22、用的组件构成。如今这种体系结构已经大量取代了以往编程模式中常见的统一模型。继续变革Java的精髓还体现在另外一个方面,尽管它实际上并不属于Java语言的一部分。Java引入了一种乐于接受新思想的创新文化,以及一个能够迅速吸收这些新思想的过程。虽然很多计算机语言变化缓慢,但是Java却处于不停地发展和适应的过程之中。同时,这个过程通过Java社团(JCP)向整个Java团体公开。JCP提供了一种机制,Java用户可以通过该机制协助决定Java语言、工具和相关技术的未来发展方向。因此,实际使用Java语言的人们可以参与到它的发展中来。从诞生之初开始,Java就为编程领域带来了变革 并且这个变革仍然
23、没有停止。Java仍然处于计算机语言发展的前沿,它在计算发展史上已占有了永恒的地位。第二部分 递归下降的表达式解析器如何编写一个程序,使之接收包含数字表达式的字符串(如(10-5)*3 )作为输入,并通过计算得到正确的输出结果呢?也许只有少数的“大师级”程序员才能够做到这一点。表达式解析过程将算术表达式转化为计算机可以识别的形式。它也是所有需要进行表达式转换的软件的核心,这些软件包括语言编译器和解释器、电子制表软件等等。表达式由于解析器处理的对象是表达式,因此有必要介绍表达式的组成。虽然表达式的类型多种多样,但本章只处理一种类型:数值表达式。数值表达式由下列元素组成: 数字 运算符+、/、*、
24、%、= 圆括号 变量其中,运算符表示求幂运算(不是Java中规定的XOR运算),是赋值运算符。这些元素按照代数学的规则组合成表达式。例如: 10 8 (100 5) * 14/6 a + b c 105 a = 10 b每个运算符的优先级如表2-1所列:表2-1 运算符优先级最高优先级最低优先级 + - (正负号) * / % + - =优先级相等的运算符按照从左到右的顺序计算。本章介绍的解析器必须满足以下一些约束条件:第一,所有变量都是单个字母(从A到Z的26个变量),字母不区分大小写(把a和A视为同一个变量)。第二,假定所有的数字都是double类型,可以方便地修改解析器从而处理其他类型的
25、值。最后,为保证逻辑清晰和理解方便,解析器只进行基本的错误检查。解析表达式没有对表达式解析进行全面思考的人,会认为这个问题非常简单,但实际上并非如此。为了更好理解这一点,计算下面的简单表达式: 102*3众所周知,这个表达式的结果等于4。编写一个程序计算某个特定的表达式非常容易,但问题在于如何创建一个程序,来为任意的表达式给出正确的答案。首先考虑下面的运算法则:a = get first operandwhile(operands present) op = get operatorb = get second operanda = a op b该方法依次获得第一个操作数、运算符和第二个操作数
26、以执行第一个操作;然后获得下一个运算符和操作数以执行下一个操作,依此类推。然而,如果采用这种基本方式计算上面的表达式,那么10 2 * 3的结果将是24(也就是8*3)而不是4。这是因为上述过程忽略了运算符的优先级。像这样从左到右依次获取操作数和运算符的方法是行不通的,因为代数学规定乘法运算必须先于减法运算完成。也许有些初学者认为解决这个问题很容易,而且实际上在某些有限的条件下确实如此。但是当表达式中增加了圆括号、求幂运算、变量、一元运算符等元素之后,问题只会变得更为复杂。尽管编写处理表达式的代码有很多种方式,但是本章介绍的一种最容易由个人开发完成,称之为递归向下的解析器。在阅读本章的过程中,
27、读者会明白这样命名的原因(其他一些解析器的编写方法使用了一些复杂的表格,这些表格通常由另一些计算机程序生成。这些解析器有时也称为表格驱动(table-driven)的解析器)。表达式的解析解析和计算表达式的方式有很多种。在使用递归下降的解析器时,表达式被视为递归的数据结构 表达式由其本身来定义。假定表达式只能使用+、-、*、/和圆括号,那么所有的表达式可以用下面的规则来定义: 表达式 项 +项 项 项因数*因数 /因数 因数变量、数字或者(表达式)方括号里面表示可选元素,而箭头表示箭头前面的元素由箭头后面的元素定义产生。实际上,该规则通常被称为表达式的生成规则。因此,对于项的定义可以这样表述:
28、“项由因数乘以因数或者因数除以因数产生。”需要注意的是,运算符的优先级已经隐含在表达式的定义中。下面举例说明表达式的解析过程。表达式 10 + 5 * B有两个项:10和5*B,第二项包括两个因数:5和B,分别是一个数字和一个变量。下面看另外一个例子。表达式 14 * (7 C)有两个因数:14和(7-C),分别是一个数字和一个圆括号表达式。圆括号表达式包括两个项:一个数字和一个变量。上述过程形成了递归下降解析器的基础。递归下降解析器是一组互相递归的方法,这些方法以一种链式方式实现生成规则。在每个适当的步骤上,解析器以代数学规定的正确顺序执行指定的操作。为了解释如何使用生成规则来解析表达式,采
29、用下面的表达式来跟踪解析过程: 9/3 (100 + 56)整个解析过程如下:(1) 获得第一项9/3;(2) 获得第一项的两个因数并完成除法运算,得到结果3;(3) 获得第二项(100+56)。在这一步启动递归分析过程处理括号内的子表达式;(4) 获得其中的两项并完成加法运算,得到结果156;(5) 从第二项的递归计算过程中返回;(6) 3减去156,答案是-153。如果读者对于递归的理解有些困难,不要沮丧。递归是一个相当复杂的概念,需要慢慢来习惯。对于表达式的这种递归过程,请记住两个基本概念:第一,运算符的优先级隐含在生成规则的定义方式中;第二,这种递归解析和计算表达式的方式跟人们计算数学
30、表达式的方式非常相似。本章接下来将介绍两个解析器:第一个能够解析和计算仅由常量数据组成的double类型浮点表达式,该解析器说明递归下降解析方法的基本原理;第二个则增加了对变量的处理。 表达式的分解为了计算表达式的值,解析器需要分解出表达式的独立元素。例如表达式: A * B (W + 10)包括下面这些独立元素:A、*、B、(、W、+、10和)。在解析术语中,这样的表达式元素被称为标识符(token),表示表达式中一个不可再分的独立单元。在详细介绍解析器之前,先看看表达式的标识方法,因为它是解析的基础。为了将表达式分离为单个标识符,需要设计一个过程,从头到尾地扫描表达式,并顺序地返回表达式的
31、每个标识符。该方法必须确定每个标识符的类型,而且必须识别表达式的结尾。在本节介绍的解析器中,实现这些功能的方法名为getToken()。本章介绍的两个解析器都封装在Parser类中。尽管下文对这个类进行了详细描述,但是现在必须提前说明它的第一部分,以便读者理解getToken()方法的工作过程。Parser类首先定义了一些final变量和域,如下:class Parser / These are the token types.final int NONE = 0;final int DELIMITER = 1;final int VARIABLE = 2;final int NUMBER =
32、 3;/ These are the types of syntax errors.final int SYNTAX = 0;final int UNBALPARENS = 1;final int NOEXP = 2;final int DIVBYZERO = 3;/ This token indicates end-of-expression.final String EOE = 0;private String exp; / refers to expression stringprivate int expIdx; / current index into the expressionp
33、rivate String token; / holds current tokenprivate int tokType; / holds tokens type在表达式解析的过程中,每个标识符必须有一个与之相关的类型。因此Parser类首先定义了几个常数,表明标识符的各个不同类型。本章中所介绍的解析器只用到3种类型:变量、数值和分隔符,它们分别由常量VARIABLE、NUMBER和DELIMITER表示。DELIMITER既可以是运算符,也可以是括号。此外,NONE类型仅仅作为未定义标识符的一个占位符。接下来,Parser定义了另外几个错误常量,它们代表解析和计算表达式的过程中可能发生的不
34、同类型的错误。SYNTAX代表所有导致非正则表达式的错误;UNBALPARENS表示括号不对称的错误;如果解析器执行时没有表达式被提交,就会报告一个NOEXP错误;DIVBYZERO则表示除数为零的错误。final变量EOE标志解析器已达到表达式的结尾。被解析的表达式保存在一个字符串中,exp变量则存储对该字符串的一个引用。这样,exp就可以指向一个形如“10+4”的字符串。而此字符串中的下一个标识符的索引保存在expIdx变量中,初始索引值为0。当前获得的标识符存储在token变量中,其类型则存储在tokType变量中。这些域的属性都是private类型,因为它们只允许由解析器使用并且不能被
35、外部代码修改。下面给出getToken()方法的完整代码。这个方法每次调用获得表达式中的下一个标识符。exp指向包含这个表达式的字符串,其索引由expIdx表示。也就是说,每次调用getToken()都将获得expexpIdx表示的下一个标识符。然后getToken()将这个标识符存入token域,将标识符类型存入tokType域中。getToken()方法调用isDelim()方法,其代码如下:/ Obtain the next token.private void getToken()tokType = NONE;token = ;/ Check for end of expression
36、.if(expIdx = exp.length() token = EOE;return;/ Skip over white space.while(expIdx = exp.length() break;tokType = VARIABLE;else if(Character.isDigit(exp.charAt(expIdx) / is numberwhile(!isDelim(exp.charAt(expIdx) token += exp.charAt(expIdx);expIdx+;if(expIdx = exp.length() break;tokType = NUMBER;else
37、 / unknown character terminates expressiontoken = EOE;return;/ Return true if c is a delimiter.private boolean isDelim(char c)if( +-/*%=().indexOf(c) != -1)return true;return false;下面详细分析getToken()方法。getToken()首先执行一些必要的初始化工作,然后查看expIdx是否等于exp.length(),以此判断是否已到达表达式的结尾。由于expIdx是表达式中的一个索引,因此,如果该索引等于该表达
38、式字符串的长度,则表明整个表达式已经解析完毕。如果表达式中还能找到未被处理过的标识符,那么getToken()方法将继续处理标识符的过程。首先跳过下一个标识符之前的全部空格。如果表达式以空格结尾,那么跳过这些空格之后必须返回一个EOE标志。否则,在跳过空格之后,expexpIdx可能是以下三种字符中的一种:数字、变量或者运算符。根据expexpIdx之后字符类型的不同,getToken()方法对当前标识符的处理有所不同。如果该字符之后的一个字符是运算符,那么就将当前标识符保存在token变量中作为字符串返回,同时将tokType置为DELIMITER。如果接下来的字符是一个字母,那么就将它作为
39、一个变量处理,把它保存在token中作为一个字符串返回,同时将tokType置为VARIABLE。如果接下来的字符是一个阿拉伯数字,那么要读出整个数字,并将它以字符串的形式保存在token中,同时将tokType设置为NUMBER。最后,如果下一个字符不是以上三种中的任何一种,那么token将被置为EOE。为了更清楚地分析getToken()中的代码,在这里省略了大部分的错误检查代码,并作出一些假设。例如,对于任何尚未确认的字符,只要在它之前有一个空格,即可作为表达式的结束字符。同时,在这个版本中,虽然变量的长度可以是任意的,但是只有第一个字母有意义。当然,对于特定的需求,应用程序可以增加更多
40、的错误检查或者其他一些细节。为了更好地理解标识符的分析过程,首先看看下列表达式返回的每个标识符及其类型(见表2-2):A + 100 (B * C) /2表2-2 标识符的类型 请记住,标识符总是以字符串的形式保存,即使当它仅包含单个字母时也是如此。最后提醒一点:Java包含了一些非常有用的内嵌的标识符处理功能,例如由StringTokenizer类所支持的多种功能。但是对于解析器而言,最好使用getToken()等专门的标识符处理方法来完成标识符处理工作。一个简单的表达式解析器下面是解析器的第一个版本。这个解析器可以计算仅由数字、运算符和括号组成的表达式。尽管getToken()方法可以处理
41、变量,但是这个版本并不对变量做任何处理。在读者明白这个简单解析器的工作原理之后,再增加处理变量的功能。/* This module contains the recursive descent parser that does not use variables.*/ Exception class for parser errors.class ParserException extends Exception String errStr; / describes the error public ParserException(String str) errStr = str; publi
42、c String toString() return errStr; class Parser / These are the token types. final int NONE = 0; final int DELIMITER = 1; final int VARIABLE = 2; final int NUMBER = 3; / These are the types of syntax errors. final int SYNTAX = 0; final int UNBALPARENS = 1; final int NOEXP = 2; final int DIVBYZERO =
43、3; / This token indicates end-of-expression. final String EOE = 0; private String exp; / refers to expression string private int expIdx; / current index into the expression private String token; / holds current token private int tokType; / holds tokens type / Parser entry point. public double evaluate(String expstr) throws ParserException double result; exp = expstr; expIdx = 0; getToken(); if(token.equals(EOE) handleErr(NOEXP); / no expression present / Parse and evaluate the expr