JAVA编程资料Word格式.docx
《JAVA编程资料Word格式.docx》由会员分享,可在线阅读,更多相关《JAVA编程资料Word格式.docx(90页珍藏版)》请在冰豆网上搜索。
因此,Java程序绝对不会直接操作一个对象。
这种策略可以带来很多好处,最直接的好处就是能够高效地实现垃圾回收。
因为所有对象都通过引用访问:
当一个对象没有被引用时,它将被回收。
另一个好处是,每个Object类型的指针可以引用系统中的任何对象。
当然,通过引用访问对象将产生额外开销。
因为一个引用实际上是一个地址(即指针)。
于是对每个对象的访问不是直接进行的,而是通过地址间接完成的。
尽管现代CPU可以高效地处理间接访问,但是间接访问总是不如直接处理数据本身快,简单数据类型即是通过直接方式进行的。
尽管简单数据类型的处理非常高效,但是有些时候仍然需要使用跟某个简单类型等价的对象。
例如在运行时创建一个整型链表,并在不再使用时将其回收(垃圾回收)。
为了处理此类情况,Java为简单类型(如Integer和Double)定义了包装器(wrapper)。
包装器使得简单类型在必要时可参与到对象层次的操作中来。
Java关于对象和简单数据类型的平衡问题。
它支持编写高效程序,同时又完美地解决了允许实现对象模型,而不用担心对简单数据类型的性能会产生负面影响。
通过垃圾回收实现内存管理
垃圾回收作为一种内存管理技术已经存在了很长时间,但是Java使它焕发出崭新的活力。
在C++等语言中,内存必须人工管理,程序员必须显式地释放不再使用的对象。
这是问题产生的根源,因为忘记释放不再使用的资源,或者释放了正在使用的资源都是很常见的事情。
Java代替程序员完成了这些工作,从而防止了此类问题的发生。
在Java中,所有的对象都是通过引用访问的,这样,当垃圾回收器发现一个没有引用的对象时,就知道该对象已经不被使用,并且可以回收了。
如果Java允许对象的直接访问(与简单数据类型的访问方式类似),那么这种有效的垃圾回收方法将无法实现。
Java的垃圾回收策略在普遍意义上反映了Java的理念。
Java设计人员花费了大量的精力,来防止其他编程语言经常出现的典型问题,例如程序员经常忘记释放资源,或者错误地释放正在使用的资源。
因此,使用垃圾回收策略有效地避免了此类问题的发生。
完美的简单多线程模型
Java的设计者所提供的编程特性,包括对多线程多任务的语言级支持。
多任务具有两种类型:
基于进程的多任务和基于线程的多任务。
在基于进程的多任务中,最小的可调度单元是进程。
进程实际上就是正在执行的一个程序。
因此,基于进程的多任务就是允许计算机同时运行两个或多个程序的特性。
在基于线程的多任务中,最小的可调度单元是线程。
线程定义了一个程序内的某条执行路径。
因此,一个进程可以包含有两个或更多的执行线程,而多线程程序可以有两个或更多个可以并发执行的部分。
尽管基于进程的多任务通常是操作系统提供的功能,基于线程的多任务却可以极大地受益于程序设计语言级别的支持。
例如,C++没有对多线程编程提供内置支持,于是就必须依赖于操作系统来处理多线程任务。
这就意味着创建、启动、同步和结束线程都必须通过对操作系统的多次调用来实现。
因此C++中的多线程代码是不可移植的。
这也使得C++编程中的多线程没有得以广泛应用。
由于Java内置了对多线程的支持,哪些在其他语言中必须由手工完成的工作,现在都可以由Java自动处理。
Java多线程模型中最有特色的部分之一,就是其实现同步的方式。
同步建立在两种创新的特性之上。
首先,在Java中,所有的对象都有充当互斥锁的内置侦听器。
在给定的时刻,任何侦听器只能由一个线程拥有。
通过使用synchronized关键字对方法进行修饰,可以启用锁定特性。
在调用同步方法时,该对象就被锁定,而试图访问该对象的其他线程就只能等待。
其次,Java对同步的支持也可以从所有类的共同超类Object中体现。
Object声明了下面三个同步方法:
wait()、notify()和notifyAll()。
这些方法支持线程间通信。
因此,所有的对象都对线程间通信有内置的支持。
通过和同步方法结合使用,这些方法可以为线程的互操作提供高级别的控制。
通过将多线程作为语言的一个易于使用的内置特性,Java改变了人们关于程序基本体系结构的观念。
在Java出现之前,大部分程序员将程序看成是,只有一条执行路径的单块结构。
在Java出现之后,程序演变为多个可互操作的并发任务的集合。
这种并发机制的改变对于计算产生了深远的影响,但是其最重大的影响可能是使得软件组件的使用更为便捷。
完全集成的异常机制
异常(exception)的概念性框架出现在Java产生之前。
因此,在Java出现之前,其他编程语言就已经提出了异常的概念。
例如C++提出异常概念的时间,就比Java诞生的时间早了许多年。
异常在Java的最初设计中就已经引入了,而不是在Java产生之后才加入的,因此Java对于异常机制的实现就显得非常重要。
异常机制是在Java语言中完全集成的,是Java的基本特征之一。
Java异常机制的关键在于异常是必须使用的,而不是可选的。
通过异常处理错误是Java语言的规则,这与C++是有区别的。
例如,C++同样支持异常机制,但是这种机制并未完全集成到整个编程环境中。
考虑打开或者读取一个文件的操作。
在Java中,如果某个操作发生错误,将抛出一个异常。
而在C++中,打开或者读取文件的方法会返回一个专用错误代码,报告操作中发生的错误。
因为C++库仍然依赖于错误返回代码而不是异常,所以从本质上来说,C++并不支持异常,程序必须不断地进行人工检查,以避免可能出现的错误。
但是在Java中,程序员只需要简单地使用一个try/catch代码块,就可以自动捕捉到任何错误。
对多态性支持的改进
多态性(polymorphism)是面向对象编程的属性,它允许多个方法使用同一个接口。
Java从多个方面支持多态性,其中两个方面最为突出。
第一个是每个方法(标记为final的方法除外)都可以被子类重写;
第二个是设立interface关键字。
下面将给出这两方面的详细介绍。
由于超类中的方法可以在派生类中重写,因此创建类的层次结构非常简单。
在类的层次结构中,每个子类都将它的超类特化(specialization)。
大家知道,超类的一个引用可以引用它的任何一个子类,而且通过超类的引用调用某子类对象的一个方法时,会自动执行由该子类重写后的版本。
因此,可以用超类来定义对象的形式并提供对象的默认实现,而子类根据这种默认实现进行修改,以更好地适应具体情况的要求。
因此,在超类中定义的一个接口可以作为多个不同实现的基础。
当然,Java进一步采取了“一个接口,多个方法”的概念。
它定义了interface关键字,这样就可以将类的方法和类的实现完全分离。
尽管接口是抽象的,但是仍然可以声明接口类型的引用。
这个概念非常重要,因为它可以改进多态性的应用。
只要某个类实现一个接口,并且该接口提供了某种功能,那么任何需要这种功能的代码都可以使用这个类的对象。
例如,假设某个接口的名称为MyIF,考虑下面的方法:
voidmyMeth(MyIFob){
//...
}
任何实现了MyIF接口的对象都可以传递给myMeth()方法。
该对象的其他功能无需考虑。
myMeth()方法可以对任何实现了MyIF接口的对象进行操作。
通过字节码保证可移植性和安全性
通过字节码保证可移植性和安全性
尽管Java有许多功能强大的特性,但是如果没有字节码(bytecode)这一特征,那么Java只不过是编程技术发展进程中的一个足印。
字节码是Java语言的一个重要组成部分,它对程序员而言,几乎是透明的。
所有的Java程序员都知道,Java编译器的输出不是能够直接由CPU执行的机器指令,而是一种经过高度优化的可移植的指令集合,这种指令集合称为字节码,它只能由Java虚拟机(JavaVirtualMachine,JVM)执行。
最初,JVM只是一个简单的字节码解释器,现在,JVM也将字节码的on-the-fly编译技术应用到可执行代码中。
无论字节码的执行采用何种方式,它的优势对于Java的成功都是至关重要的。
字节码的第一个优势是可移植性。
无论计算机使用何种类型的CPU(或操作系统),只要具有JVM,那么由Java程序编译而成的字节码就可以在其中执行。
换而言之,只要为某个特定环境实现了JVM,那么每个Java程序都可以在该环境运行。
没有必要为每个不同的环境创建都单独执行代码,因为同一种字节码可以在所有环境中运行。
因此通过使用字节码,Java为程序员提供了“一次编写,随处运行”的能力。
字节码的第二个优势是安全性。
由于字节码在JVM的控制下执行,因此JVM可以防止执行恶意操作的Java程序。
保证主机安全的能力对于Java的成功是至关重要的,因为它允许创建Applet。
由于Applet是可以通过Internet动态下载的小程序,因此避免Applet破坏主机的机制是非常必要的。
字节码和JVM的结合,还保证了Applet的安全下载。
可以说,如果没有字节码,那么Web可能根本无法达到今天的地位和影响。
丰富的JavaAPI
从概念上来讲,计算机语言由两部分组成。
一是语言本身,由关键字和语法定义;
二是标准库,包含一组面向程序员的类、接口和方法。
尽管现在所有的主流编程语言都提供了大量的库,但是Java定义的库由于更为丰富和多样显得非常突出。
人们在最初创建Java时,它的库包括一组核心程序包,例如java.lang、java.io和。
随着Java不断发布新的版本,新的类和程序包也被不断加入。
如今的Java已经为程序员提供了功能极其强大的库函数。
从Java创建之初,Java函数库就与其他语言的函数库有所不同,其中一个关键不同之处,在于Java库对网络的支持。
在开发Java的时候,其他语言(例如C++)并没有提供(现在仍然没有提供)处理网络的标准函数。
Java通过提供相应的类,使得连接和使用Internet的处理非常方便,从而有力地推动了Internet革命的进程。
Java向所有的程序员开放了Internet,而不仅仅局限于精通网络编程的那部分人。
的功能改变了计算的形式。
Java核心库中的另外一个关键的程序包是java.awt,它支持抽象窗口工具集(AbstractWindowToolkit,AWT)。
程序员可以用AWT创建可移植的、基于GUI的代码。
也就是说,程序员用AWT类可以创建一个基于视窗的应用程序,并在程序中使用各种标准的GUI元素,例如滚动条、复选框和单选框等。
由于AWT的存在,程序员创建的GUI应用程序可以运行在任何支持Java虚拟机的环境中。
而这种层次上的GUI移植性,在Java之前是从来没有过的。
在Java中加入AWT彻底改变了程序员思考应用程序环境的方式。
在Java前时代,基于GUI的程序必须明确指出其执行环境。
这意味着任何Windows程序需要重新编译才能够在一台Apple机上运行。
Java通过一个可移植的GUI提供统一的编程环境。
后来Java:
Swing也被加入Java中,这是AWT的一个轻型实现。
Swing组件包含在javax.swing及其子程序包中。
Swing为程序员提供了一组丰富的GUI组件。
与AWT相比,它们的可移植性更高。
本书将通过许多例子来演示AWT和Swing如何为程序员提供函数,使他们具有编写高效、可移植的GUI应用程序的能力。
如今,Java库已经基于最初的核心库得到了极大发展。
Java的每个新版本都会提供一些新的库。
新的程序包不断增加,新的功能也不断加入已有的程序包中。
Java库的发展过程处于一个连续的状态,因为Java必须能够适应快速演变的计算环境。
这种在短期内适应和变化的能力也是Java的精髓之一。
Applet
Applet是Java最具有革命意义的特征之一,因为它能够创建可移植的、可动态下载的、能够在浏览器的限定下安全执行的程序。
人们如今已经普遍接受这一观点,但是在Java前时代,这种可执行内容(executablecontent)受到广泛置疑:
一个疑问是,恶意程序是否会对客户机造成伤害;
另外一个疑问是:
为某种类型的CPU和操作系统编译的代码在另一种系统上可能无法正常运行。
由于连接到Internet的CPU和操作系统种类繁多,那么对于某个程序,为每一类型的环境都创建一个独立的运行版本是不切实际的。
JavaApplet为以上两个问题提供了一套很好的解决方案。
通过使用Applet,Web程序员可以方便地在静态的HTML页面中添加动态的内容。
JavaApplet使网页变得生动起来,从此告别了静态网页的年代。
除了改变人们对于Web内容的思维方式之外,Applet还有一个重要的影响——它推动了组件软件开发的发展(也可能是一个副作用)。
由于Applet是小程序,因此它们通常代表很小的功能单元;
而软件组件正是基于这种思想。
只要按照Applet的思维考虑问题,那么就向Beans的思想迈出了一小步,甚至更多。
在面向组件的体系结构中,一个应用程序由一些互相作用的组件构成。
如今这种体系结构已经大量取代了以往编程模式中常见的统一模型。
继续变革
Java的精髓还体现在另外一个方面,尽管它实际上并不属于Java语言的一部分。
Java引入了一种乐于接受新思想的创新文化,以及一个能够迅速吸收这些新思想的过程。
虽然很多计算机语言变化缓慢,但是Java却处于不停地发展和适应的过程之中。
同时,这个过程通过Java社团(JCP)向整个Java团体公开。
JCP提供了一种机制,Java用户可以通过该机制协助决定Java语言、工具和相关技术的未来发展方向。
因此,实际使用Java语言的人们可以参与到它的发展中来。
从诞生之初开始,Java就为编程领域带来了变革——并且这个变革仍然没有停止。
Java仍然处于计算机语言发展的前沿,它在计算发展史上已占有了永恒的地位。
第二部分递归下降的表达式解析器
如何编写一个程序,使之接收包含数字表达式的字符串(如(10-5)*3)作为输入,并通过计算得到正确的输出结果呢?
也许只有少数的“大师级”程序员才能够做到这一点。
表达式解析过程将算术表达式转化为计算机可以识别的形式。
它也是所有需要进行表达式转换的软件的核心,这些软件包括语言编译器和解释器、电子制表软件等等。
表达式
由于解析器处理的对象是表达式,因此有必要介绍表达式的组成。
虽然表达式的类型多种多样,但本章只处理一种类型:
数值表达式。
数值表达式由下列元素组成:
●数字
●运算符+、–、/、*、^、%、=
●圆括号
●变量
其中,^运算符表示求幂运算(不是Java中规定的XOR运算),=是赋值运算符。
这些元素按照代数学的规则组合成表达式。
例如:
10–8
(100–5)*14/6
a+b–c
10^5
a=10–b
每个运算符的优先级如表2-1所列:
表2-1运算符优先级
最高优先级最低优先级+-(正负号)
^
*/%
+-
=
优先级相等的运算符按照从左到右的顺序计算。
本章介绍的解析器必须满足以下一些约束条件:
第一,所有变量都是单个字母(从A到Z的26个变量),字母不区分大小写(把a和A视为同一个变量)。
第二,假定所有的数字都是double类型,可以方便地修改解析器从而处理其他类型的值。
最后,为保证逻辑清晰和理解方便,解析器只进行基本的错误检查。
解析表达式
没有对表达式解析进行全面思考的人,会认为这个问题非常简单,但实际上并非如此。
为了更好理解这一点,计算下面的简单表达式:
10–2*3
众所周知,这个表达式的结果等于4。
编写一个程序计算某个特定的表达式非常容易,但问题在于如何创建一个程序,来为任意的表达式给出正确的答案。
首先考虑下面的运算法则:
a=getfirstoperand
while(operandspresent){
op=getoperator
b=getsecondoperand
a=aopb
该方法依次获得第一个操作数、运算符和第二个操作数以执行第一个操作;
然后获得下一个运算符和操作数以执行下一个操作,依此类推。
然而,如果采用这种基本方式计算上面的表达式,那么10–2*3的结果将是24(也就是8*3)而不是4。
这是因为上述过程忽略了运算符的优先级。
像这样从左到右依次获取操作数和运算符的方法是行不通的,因为代数学规定乘法运算必须先于减法运算完成。
也许有些初学者认为解决这个问题很容易,而且实际上在某些有限的条件下确实如此。
但是当表达式中增加了圆括号、求幂运算、变量、一元运算符等元素之后,问题只会变得更为复杂。
尽管编写处理表达式的代码有很多种方式,但是本章介绍的一种最容易由个人开发完成,称之为递归向下的解析器。
在阅读本章的过程中,读者会明白这样命名的原因(其他一些解析器的编写方法使用了一些复杂的表格,这些表格通常由另一些计算机程序生成。
这些解析器有时也称为表格驱动(table-driven)的解析器)。
表达式的解析
解析和计算表达式的方式有很多种。
在使用递归下降的解析器时,表达式被视为递归的数据结构——表达式由其本身来定义。
假定表达式只能使用+、-、*、/和圆括号,那么所有的表达式可以用下面的规则来定义:
表达式à
项[+项][–项]
项à
因数[*因数][/因数]
因数à
变量、数字或者(表达式)
方括号里面表示可选元素,而箭头à
表示箭头前面的元素由箭头后面的元素定义产生。
实际上,该规则通常被称为表达式的生成规则。
因此,对于项的定义可以这样表述:
“项由因数乘以因数或者因数除以因数产生。
”需要注意的是,运算符的优先级已经隐含在表达式的定义中。
下面举例说明表达式的解析过程。
10+5*B
有两个项:
10和5*B,第二项包括两个因数:
5和B,分别是一个数字和一个变量。
下面看另外一个例子。
14*(7–C)
有两个因数:
14和(7-C),分别是一个数字和一个圆括号表达式。
圆括号表达式包括两个项:
一个数字和一个变量。
上述过程形成了递归下降解析器的基础。
递归下降解析器是一组互相递归的方法,这些方法以一种链式方式实现生成规则。
在每个适当的步骤上,解析器以代数学规定的正确顺序执行指定的操作。
为了解释如何使用生成规则来解析表达式,采用下面的表达式来跟踪解析过程:
9/3–(100+56)
整个解析过程如下:
(1)获得第一项9/3;
(2)获得第一项的两个因数并完成除法运算,得到结果3;
(3)获得第二项(100+56)。
在这一步启动递归分析过程处理括号内的子表达式;
(4)获得其中的两项并完成加法运算,得到结果156;
(5)从第二项的递归计算过程中返回;
(6)3减去156,答案是-153。
如果读者对于递归的理解有些困难,不要沮丧。
递归是一个相当复杂的概念,需要慢慢来习惯。
对于表达式的这种递归过程,请记住两个基本概念:
第一,运算符的优先级隐含在生成规则的定义方式中;
第二,这种递归解析和计算表达式的方式跟人们计算数学表达式的方式非常相似。
本章接下来将介绍两个解析器:
第一个能够解析和计算仅由常量数据组成的double类型浮点表达式,该解析器说明递归下降解析方法的基本原理;
第二个则增加了对变量的处理。
表达式的分解
为了计算表达式的值,解析器需要分解出表达式的独立元素。
例如表达式:
A*B–(W+10)
包括下面这些独立元素:
A、*、B、–、(、W、+、10和)。
在解析术语中,这样的表达式元素被称为标识符(token),表示表达式中一个不可再分的独立单元。
在详细介绍解析器之前,先看看表达式的标识方法,因为它是解析的基础。
为了将表达式分离为单个标识符,需要设计一个过程,从头到尾地扫描表达式,并顺序地返回表达式的每个标识符。
该方法必须确定每个标识符的类型,而且必须识别表达式的结尾。
在本节介绍的解析器中,实现这些功能的方法名为getToken()。
本章介绍的两个解析器都封装在Parser类中。
尽管下文对这个类进行了详细描述,但是现在必须提前说明它的第一部分,以便读者理解getToken()方法的工作过程。
Parser类首先定义了一些final变量和域,如下:
classParser{
//Thesearethetokentypes.
finalintNONE=0;
finalintDELIMITER=1;
finalintVARIABLE=2;
finalintNUMBER=3;
//Thesearethetypesofsyntaxerrors.
finalintSYNTAX=0;
finalintUNBALPARENS=1;
finalintNOEXP=2;
finalintDIVBYZERO=3;
//Thistokenindicatesen