java基础教程

第一章Java语言的产生及其特点

1.1Java产生的历史与现状

1.1.1Java产生的历史

Java来自于Sun公司的一个叫Green的项目,其原先的目的是为家用消费电子产品开发一个分布式代码系统,这样我们可以把E-mail发给电冰箱、电视机等家用电器,对它们进行控制,和它们进行信息交流。开始,准备采用C++,但C++太复杂,安全性差,最后基于C++开发一种新的语言Oak(Java的前身),Oak是一种用于网络的精巧而安全的语言,Sun公司曾依此投标一个交互式电视项目,但结果是被SGI打败。可怜的Oak几乎无家可归,恰巧这时MarkArdreesen开发的Mosaic和Netscape启发了Oak项目组成员,他们用Java编制了HotJava浏览器,得到了Sun公司首席执行官ScottMcNealy的支持,触发了Java进军Internet。Java的取名也有一个趣闻,有一天,几位Java成员组的会员正在讨论给这个新的语言取什么名字,当时他们正在咖啡馆喝着Java(爪哇)咖啡,有一个人灵机一动说就叫Java怎样,得到了其他人的赞赏,于是,Java这个名字就这样传开了。

1.1.2Java的现状

Java是Sun公司推出的新的一代面向对象程序设计语言,特别适合于Internet应用程序开发,它的平台无关性直接威胁到Wintel的垄断地位。一时间,"连Internet,用Java编程",成为技术人员的一种时尚。虽然新闻界的报导有些言过其实,但Java作为软件开发的一种革命性的技术,其地位已被确立,这表现在以下几个方面:

1.计算机产业的许多大公司购买了Java的许可证,包括IBM,Apple,DEC,Adobe,SiliconGraphics,HP,Oracel,Toshiba,以及最不情愿的Microsoft。这一点说明,Java已得到了工业界的认可。

2.众多的软件开发商开始支持Java的软件产品。例如:Borland公司正在开发的基于Java的快速应用程序开发环境Latte,预计产品会在1996年中期发布。Borland公司的这一举措,推动了Java进入PC机软件市场。Sun公司自己的Java开发环境JavaWorkshop已经发布。数据库厂商如:Illustra,Sysbase,Versant,Oracle都在开发CGI接口,支持HTML和Java。今天是以网络为中心的计算时代,不支持HTML和Java,应用程序的应用范围只能限于同质的环境(相同的硬件平台)。

3.Intranet正在成为企业信息系统最佳的解决方案,而其中Java将发挥不可替代的作用。Intranet的目的是把Internet用于企业内部的信息系统,它的优点表现在:便宜,易于使用和管理。用户不管使用何种类型的机器和操作系统,界面是统一的Intrnet浏览器,而数据库、Web页面、应用程序(用Java编的Applet)则存在WWW服务器上,无论是开发人员,还是管理人员,抑或是用户都可以受益于该解决方案。Java语言正在不断发展和完善,Sun公司是主要的发展推动者,较通用的编译环境有JDK(JavaDevelopKit)与JWS(JavaWorkshop)。还有很多其他公司正在开发Java语言的编译器与集成环境,预计不久Java语言的正确性与效率都将会提高,用户用Java编程和现在用C++编程一样方便。

1.2Java语言的特点

Java到底是一种什么样的语言呢?Java是一种简单的面象对象的分布式的解释的健壮的安全的结构中立的可移植的性能很优异的多线程的动态的语言。

1.2.1简单

Java最初是为对家用电器进行集成控制而设计的一种语言,因此它必须简单明了。Java语言的简单性主要体现在以下三个方面:

1.Java的风格类似于C++,因而C++程序员是非常熟悉的。从某种意义上讲,Java语言是C及C++语言的一个变种,因此,C++程序员可以很快就掌握Java编程技术。

2.Java摒弃了C++中容易引发程序错误的地方,如指针和内存管理。

3.Java提供了丰富的类库。

1.2.2面向对象

面向对象可以说是Java最重要的特性。Java语言的设计完全是面向对象的,它不支持类似C语言那样的面向过程的程序设计技术。Java支持静态和动态风格的代码继承及重用。单从面向对象的特性来看,Java类似于SmallTalk,但其它特性、尤其是适用于分布式计算环境的特性远远超越了SmallTalk。

1.2.3分布式

Java包括一个支持HTTP和FTP等基于TCP/IP协议的子库。因此,Java应用程序可凭借URL打开并访问网络上的对象,其访问方式与访问本地文件系统几乎完全相同。为分布环境尤其是Internet提供动态内容无疑是一项非常宏伟的任务,但Java的语法特性却使我们很容易地实现这项目标。

1.2.4健壮

Java致力于检查程序在编译和运行时的错误。类型检查帮助检查出许多开发早期出现的错误。Java自已操纵内存减少了内存出错的可能性。Java还实现了真数组,避免了覆盖数据的可能。这种功能特征大大缩短了开发Java应用程序的周期。Java提供:Null指针检测数组边界检测异常出口字节代码校验

1.2.5结构中立

另外,为了建立Java作为网络的一个整体,Java将它的程序编译成一种结构中立的中间文件格式。只要有Java运行系统的机器都能执行这种中间代码。现在,Java运行系统有Solaris2.4(SPARC),Win32系统(Windows95和WindowsNT)等.Java源程序被编译成一种高层次的与机器无关的byte-code格式语言,这种语言被设计在虚拟机上运行,由机器相关的运行调试器实现执行。

1.2.6安全

Java的安全性可从两个方面得到保证。一方面,在Java语言里,象指针和释放内存等C++功能被删除,避免了非法内存操作。另一方面,当Java用来创建浏览器时,语言功能和一?copy;浏览器本身提?copy;的功能结合起来,使它更安全。Java语言在你的机器上执行前,要经过很多次的测试。它经过代码校验,检查代码段的格式,检测指针操作,对象操作是否过分以及试图改变一个对象的类型。

1.2.6.1Byte-code校验

如果byte-code通过代码校验,没有返回错误,我们可知道:代码没有堆栈上溢出和下溢出所有操作代码参数类型都是正确的没有发生非法数据转换,如将整数转换成指针。访问对象操作是合法的

1.2.6.2类装载

ClassLoader通过将本机类与网络资源类的名称分开,来保持安全性。因为调入类时总要经过检查,这样避免了特洛伊木马现象的出现。从网络上下载的类被调进一个与源相关的私有的名字域。当一个私有类访问另一个类时,build-in(本机类)首先被检查,然后检查相关的类。这样就避免了破坏本机类情况的出现。

1.2.7可移植的

同体系结构无关的特性使得Java应用程序可以在配备了Java解释器和运行环境的任何计算机系统上运行,这成为Java应用软件便于移植的良好基础。但仅仅如此还不够。如果基本数据类型设计依赖于具体实现,也将为程序的移植带来很大不便。例如在Windows3.1中整数(Integer)为16bits,在Windows95中整数为32bits,在DECAlpha中整数为64bits,在Intel486中为32bits。通过定义独立于平台的基本数据类型及其运算,Java数据得以在任何硬件平台上保持一致。Java语言的基本数据类型及其表示方式如下:byte8-bit二进制补码short16-bit二进制补码int32-bit二进制补码long64-bit二进制补码float32-bitIEEE754浮点数double32-bitIEEE754浮点数char16-bitUnicode字符

在任何Java解释器中,数据类型都是依据以上标准具体实现的。因为几乎目前使用的所有CPU都能支持以上数据类型、8~64位整数格式的补码运算和单/双精度浮点运算。Java编译器本身就是用Java语言编写的。Java运算系统的编制依据POSIX方便移植的限制,用ANSIC语言写成。Java语言规范中也没有任何"同具体实现相关"的内容。

1.2.8解释的

Java解释器(运行系统)能直接运行目标代码指令。链接程序通常比编译程序所需资源少,所以程序员可以在创建源程序上花上更多的时间。

1.2.9高性能

如果解释器速度不慢,Java可以在运行时直接将目标代码翻译成机器指令。Sun用直接解释器一秒钟内可调用300,000个过程。翻译目标代码的速度与C/C++的性能没什么区别。

1.2.10多线程

Java提?copy;的多线程功能使得在一个程序里可同时执行多个小任务。线程--有时也称小进程--是一个大进程里分出来的小的独立的进程。因为Java实现的多线程技术,所以比C和C++更键壮。多线程带来的更大的好处是更好的交互性能和实时控制性能。当然实时控制性能还取决于系统本身(UNIX,Windows,Macintosh等),在开发难易程度和性能上都比单线程要好。任何用过当前浏览器的人,都感觉为调一副图片而等待是一件很烦恼的事情。在Java里,你可用一个单线程来调一副图片,而你可以访问HTML里的其它信息而不必等它。

1.2.11动态

Java的动态特性是其面向对象设计方法的扩展。它允许程序动态地装入运行过程中所需要的类,这是C++语言进行面向对象程序设计所无法实现的。在C++程序设计过程中,每当在类中增加一个实例变量或一种成员函数后,引用该类的所有子类都必须重新编译,否则将导致程序崩溃。Java从如下几方面采取措施来解决这个问题。Java编译器不是将对实例变量和成员函数的引用编译为数值引用,而是将符号引用信息在字节码中保存下传递给解释器,再由解释器在完成动态连接类后,将符号引用信息转换为数值偏移量。这样,一个在存储器生成的对象不在编译过程中决定,而是延迟到运行时由解释器确定的。这样,对类中的变量和方法进行更新时就不至于影响现存的代码。解释执行字节码时,这种符号信息的查找和转换过程仅在一个新的名字出现时才进行一次,随后代码便可以全速执行。在运行时确定引用的好处是可以使用已被更新的类,而不必担心会影响原有的代码。如果程序连接了网络中另一系统中的某一类,该类的所有者也可以自由地对该类进行更新,而不会使任何引用该类的程序崩溃。Java还简化了使用一个升级的或全新的协议的方法。如果你的系统运行Java程序时遇到了不知怎样处理的程序,没关系,Java能自动下载你所需要的功能程序。

1.3与C和C++语言的异同

Java提供了一个功能强大语言的所有功能,但几乎没有一点含混特征。C++安全性不好,但C和C++还是被大家所接受,所以Java设计成C++形式,让大家很容易学习。Java去掉了C++语言的许多功能,让Java的语言功能很精炼,并增加了一些很有用的功能,Java去掉了以下几个C和C++功能和特征:指针运算结构typedefs#define需要释放内存全局变量的定义这种功能都是很容易引起错误的地方。

1.4Java的应用简介

1.4.1Web浏览

Web浏览是现在国际网甚至局域网的主要使用方式。文档能很容易地显示文本和各种图片,他还能提供超文本链接。这些浏览器调用HTML语言写的文档,HTML/WWW浏览器技术只限于文本和图象。如果你想播放一种声音或运行一个演示程序,你不得不下载那个文件并用你本机上的能理解和运行那个文件格式的程序来播放它。Java程序和它的浏览器HotJava,提供了可让你的浏览器运行程序的方法。你能从你的浏览器里直接播放声音。你还能播放页面里的动画。Java还能告诉你的浏览器怎样处理新的类型文件。当我们能在2400baud线上传输视频图象时,HotJava将能显示这些视频。

1.4.2网络应用系统

Java是一种与平台无关的语言,因此用Java开发的网络应用系统可以在各种平台上运行,大大增加了开发效率,减少重复劳动。而且,Java集成的网络功能充分有利于开发网络应用系统。

本章小结:

1.Java的产生与流行是当今internet发展的客观要求2.java是一门各方面性能都很好的编程语言,它的基本特点是简单、面象对象、分布式、解释的、健壮的、安全的、结构中立的、可移植的、性能很优异的、多线程的、动态的。?reg;分适合在internet环境上开发应用系统。3.java可以制作大部分网络应用程序系统,而且与当今流行的WWW浏览器结合得很好。

第二章Java程序开发与运行环境

2.1JDK环境

Java不仅提供了一个丰富的语言和运行环境,而且还提供了一个免费的Java开发工具集(JavaDevelopersKits,简称JDK)。编程人员和最终用户可以利用这个工具来开发java程序或调用Java内容。JDK包括以下工具:javacJava语言编译器,输出结果为Java字节码java,Java字节码解释器javapDisassembeler:Java字节码分解程序,本程序返回Java程序的成员变量及方法等信息。javaprof资源分析工具,用于分析Java程序在运行过程中调用了哪些资源,包括类和方法的调用次数和时间,以及各数据类型的内存使用情况等。javahC代码处理工具,用于从Java类调用C++代码javaAppletViewer小应用程序浏览工具,用于测试并运行Java小应用程序javaDebuggerAPIJava调试工具APIPrototypeDebuggerJava调试工具原型

Java开发环境还包括Java类库(包括I/O类库、用户界面类库、网络类库等)和HotJavaWWW浏览器。其中,HotJava浏览器提供了在WWW环境下运行Java代码的一个运行系统,而且还为WWW开发人员提供了一个Java开发框架。Java解释器是面向Java程序的一个独立运行系统,它可以一种稳定、高性能方式运行那些独立于平台的Java字节码,Java编译器则用于生成这种字节码。

2.1.1Java程序的编译

Java程序的编译程序是javac.exe。javac命令将Java程序编译成字节码,然后你可用java解释器java命令来解释执行这种Java字节码。Java程序源码必须存放在后缀为.java的文件里。Java程序里的每一个类,javac都将生成与类相同名称但后缀为.class文件。编译器把.class文件放在.java文件的同一个目录里,除非你用了-d选项。当你引用到某些自己定义的类时,必须指明它们的存放目录,这就需要利用环境变量参数CLASSPATH。环境变量CLASSPATH是由一些被分号隔开的路径名组成。如果传递给javac编译器的源文件里引用到的类定义在本文件和传递的其它文件中找不到,则编译器会按CLASSPATH定义的路径来搜索。例如:

CLASSPATH=.;C:\java\classes则编译器先搜索当前目录,如果没搜索到,则继续搜索C:\java\classes目录。注意,系统总是将系统类的目录缺省地加在CLASSPATH后面,除非你用-classpath选项来编译。javac_g是一个用于调试的未优化的编译器,功能与用法和javac一样。javac的用法如下:

javac[-g][-O][-debug][-depend][-nowarn][-verbose][-classpathpath][-nowrite][-ddir]file.java...

以下是每个选项的解释。

选项解释:

-classpathpath定义javac搜索类的路径。它将覆盖缺省的CLASSPATH环境变量的设置。路径是由一?copy;由逗号隔开的路径名组成,一般格式如下:.;<your_path>例如:.;C:\java\doc\classes;C:\tools\java\classes表示编译器遇到一个新类,它先在本文件中查找它的定义,如果没有,则在本文件所处目录下其它文件中查找它的定义,如果还没有,则继续搜索C:\java\doc\classes目录中的所有文件,以此类推。

-ddirectory指明类层次的根目录,格式如下:

javac-d<my_dir>MyProgram.java

这样将MyProgram.java程序里的生产的.class文件存放在my_dir目录里.

-g带调试信息编译,调试信息包括行号与使用java调试工具时用到的局部变量信息。如果编译没有加上-O优化选项,只包含行号信息。

-nowarn关闭警告信息,编译器将不显示任何警告信息。

-O优化编译static,final,private函数,注意你的类文件可能更大。

-verbose

让编译器与解释器显示被编译的源文件名和被加载的类名。

环境变量

CLASSPATH用来提?copy;给系统搜索用户定义的类的缺省路径。各路径由分号隔开,例如:

.;C:\java\doc\classes;C:\tools\java\classes表示编译器遇到一个新类,它先在本文件中查找它的定义,如果没有,则在本文件所处目录下其它文件中查找它的定义,如果还没有,则继续搜索C:\java\doc\classes目录中的所有文件,以此类推。

2.1.2Java程序的调试使用java调试器

jdb导游

在早期前Betal版的Java调试器jdb是命令行形式的,如用Sun公司的dbx调试器。用jdb来调试Java应用程序,在调试?reg;前,要确证你的应用程序是带标志-g编译的。例如:javac-gHelloWorld.java

help命令将显示jdb里的可用命令列表。

>help<命令列表>threads[threadgroup]--列出线程thread<threadid>--设置缺省线程Suspend[threadsid(s)]--将线程挂起resume[threadid(s)]--重新启动线程where[id]|a1|--打印线程的堆栈threadgroups--列出线程组号threadgroup<name>--设置当前线程组print<id>[id(s)]--打印对象或域dump<id>[id(s)]--打印所有对象信息locals--打印当前堆栈所有局部变量classes--列出当前所知的类methods<classid>--列出一个类的成员函数stopin<classid>.<method>--在一个成员函数里设置断点stopat<class.id>:<line>--在一行里设置断点up[nframes]--在线程堆栈里往上移down[nframes]--在线程堆栈里往下移clear<classid>:<line>--清除一个断点step--执行当前行cont--从断点处继续执行catch<class.id>--为指定的情况中断ignor<class.id>--为指定的情况忽略list[linenumber]--打印源程序use[Sourcefilepath]--显示或改变源路径memeory--报告内存使用情况loadclassname--加载Java类以便调试run<args>--开始执行加载的类!!--重复以上的命令help(?)--列出所有的命令exit(orquit)--离开调试器

2.1.3Java程序的执行

java-java语言解释器java命令解释java字节码

语法:java[options]classname<args>java_g[options]classname<args>

描述:java命令由java编译器javac输出的Java字节码。

classname参数是要执行的类名称。注意任意在类名称后的参数都将传递给要执行类的main函数。

java执行完main函数后推出,除非main函数创建了一个或多个线程。如果main函数创建了其它线程,java总是等到最后一个线程推出才推出。

选项:

-cs,-checksource当一个编译过的类调入时,这个选项将比较字节码更改时间与源文件更改时间,如果源文件更改时间靠后,则重新编译此类并调入此新类。

-classpathpath定义javac搜索类的路径。它将覆盖缺省的CLASSPATH环境变量的设置。路径是由一?copy;由逗号隔开的路径名组成,一般格式如下:.;<your_path>例如:.;C:\java\doc\classes;C:\tools\java\classes表示解释器遇到一个新类,它先在本文件中查找它的定义,如果没有,则在本文件所处目录下其它文件中查找它的定义,如果还没有,则继续搜索C:\java\doc\classes目录中的所有文件,以此类推。

-mxx设置最大内存分配池,大小为x,x必须大于1000bytes。缺省为16兆。

-msx设置垃圾回收堆的大小为x,x必须大于1000bytes。缺省为1兆。

-noasyncgc关闭异步垃圾回收功能。此选项打开后,除非显式调用或程序内存溢出,垃圾内存都不回收。本选项不打开时,垃圾回收线程与其它线程异步同时执行。

-ssx每个Java线程有两个堆栈,一个是java代码堆栈,一个是C代码堆栈。-ss选项将线程理C代码用的堆栈设置成最大为x。

-ossx每个Java线程有两个堆栈,一个是java代码堆栈,一个是C代码堆栈。-oss选项将线程理java代码用的堆栈设置成最大为x。

-v,-verbose让java解释器在每一个类被调入时,在标准输出打印相应信息。

环境变量

CLASSPATH用来提?copy;给系统搜索用户定义的类的缺省路径。各路径由分号隔开,例如:

.;C:\java\doc\classes;C:\tools\java\classes表示解释器遇到一个新类,它先在本文件中查找它的定义,如果没有,则在本文件所处目录下其它文件中查找它的定义,如果还没有,则继续搜索C:\java\doc\classes目录中的所有文件,以此类推。

2.2JWS环境

JavaWorkShop是SUN公司的一个新产品,它是一个集成的java语言开发环境,它包括以下工具:

lPortfolio和Project管理器l源文件编辑器lBuild管理工具l调试器l项目测试l?copy;展在线超文本连接到帮助文件

这?copy;工具在JavaWorkShop的第一页都有类似Web页面的超级连接,如图:

注意,JavaWorkShop采用的是当今浏览器的界面风格,你想作什么,只需找到相应的超级连接就可以了,具体编译及调试功能是用嵌如到HTML文档里的Applet实现的,因此,对习惯于用internet浏览方式的用户来说,这种界面很容易接受。

2.2.1Portfolio和Project管理器

Protfolios是一?copy;java应用程序或Applet的集中。它让你更好的管理更多的projects。一个project是portfolio里的一个子集,它包含了以下信息:

1.怎样编译本项目2.怎样调试和浏览本项目3.怎样运行本项目4.怎样发布本项目

2.2.2源文件编辑器

源文件编辑器可以从build管理器、调试器和源文件浏览器里的超级连接进入。在这个模块里,你可以输入源文件。

2.2.3Build管理工具

本模块是项目的编译器,你可以点build按钮直接进入本模块,如果某个文件出错,错误信息会提?copy;一个超级连接,直接指到出错的源文件地点。

2.2.4VisulJava(图形界面构造?copy;

顾名思义,本模块能让你可视化建造一?copy;复杂界面,如果你用过VisualBasic,你会发现它们很相似。

2.2.5调试器

调试器能让你很方便地跟踪程序的执行与发现程序的错误。

本章小结:

Java语言有两个开发环境,一个是免费的JDK,是命令行方式的。还有一个JavaWorkShop,是开发java程序的集成环境。本章简要介绍了它们的使用方法。

第三章Java程序设计基础

3.1Java编程概况

现在你可以复习一下Java语言的背景材料,它的基本结构象C/C++,但任何用面向过程语言编写过程序的人都可以了解Java语言的大部分结构.

3.1.1程序结构

Java语言的源程序代码由一个或多个编译单元(compilationunit)组成,每个编译单元只能包含下列内容(空格和注释除外):*一个程序包语句(packagestatement)*入口语句(importstatements)*类的声明(classdeclarations)*界面声明(interfacedeclarations)每个Java的编译单元可包含多个类或界面,但是每个编译单元最多只能有一个类或者界面是公共的。Java的源程序代码被编译之后,便产生了Java字节代码。Java的字节代码由一种不依赖于机器的指令组成,这种指令能被Java的运行系统(runtimesystem)有效地解释。Java的运行系统工作起来如同一台虚拟机。在当前的Java实现中,每个编译单元就是一个以.java为后缀的文件。每个编译单元有若干个类,编译后,每个类生成一个.class文件。.class文件是Java虚拟机能够识别的代码。

3.1.2注释

三种类型如下://注释一行/*一行或多行注释*//**文档注释**/

文档注释一般放在一个变量或函数定义之前,指示在任何自动生成文档系统中调入。这种注释都是声明条目的描述.。

3.1.3标识符

变量,函数,类和对象的名称都是标识符,程序员需要标识和使用的东西都需要标识符。在Java语言里,标识符以字符或_,$开头,后面可以包含数字,标识符是大小写有区别的,没有长度限制。

有效的标识符mynameict_networkHello_sys_path$bill

例子:inta_number;char_onechar;float$bill;

关键词abstractcontinuefornewswitch

booleandefaultgotonullsynchronized

breakdoifpackagethis

bytedoubleimplementsprivatethreadsafe

byvalueelseimportprotectedthrow

caseextendsinstanceofpublictransient

catchfalseintreturntrue

charfinalinterfaceshorttry

classfinallylongstaticvoid

constfloatnativesuperwhile

其它保留词以下单词被保留使用:castfuturegenericinner

operatorouterrestvar

3.1.4数据类型

Java使用五种基本类型:integer(整数),floating(浮点数),point(指针),Boolean(布尔变量),CharacterorString(字符或字符?reg;)。integer整数下边给出的数据表示都是整数的例子:4,15,089,0xAD00

整数长度数据类型表示

8bitsbyte

16bitsshort

32bitsint

64bitslong

floating浮点数下边给出的数据表示都是浮点数的例子:6.37,3.7E15,3e8

浮点数长度数据类型表示

32bitsfloat

64bitsdouble

Boolean布尔变量下边是布尔变量的两种可能取值:truefalse

Character字符下边给出的都是字符的例子:a\t(tab)\u????(unicode)

String字符?reg;下边给出的都是字符?reg;的例子:"Thisisastringliteral""中国科学院计算所"

数组你可以定义任意类型的数组.chars[];这是字符型数组;int[]array;这是整型数组;你还可以定义数组的数组.intblock[][]=newint[2][3];数组边界在运行时被检测,避免堆栈溢出和内存崩溃.

在Java里,数组实际上是一个对象,数组有一个成员变量:length。你可以用这个成员函数来查看任意数组的长度.inta[][]=newint[10][3]a.length/*10*/a[0].length/*3*/

创建数组在Java里创建数组,你可使用两种基本方法?reg;一。创建一个空数组:intlist[]=newint[50];或你可以用初始数值填充数组.Stringnames[]={"Chenji","Yuan","Chun","Yang"};相当于下面功能:Stringnames[];names=newString[4];names[0]=newString("Chenji");names[1]=newString("Yuan");names[2]=newString("Chun");names[3]=newString("Yang");

在编译时你不能象下例那样创建静态数组。intname[50];//将产生一个编译错误

你也不能用new操作去填充一个没定义大小的数组。intname[];for(inti=0;i<9;i++){name[i]=i;}

3.1.5表达式

Java语言的表达式和C语言非常类似。

运算符运算符(operator)优先级从高到底排列如下:.[]()++--!~instanceof*/%+-<<>>>>><><=>\==!=&^&&||?:=op=,

整数运算符在整数运算时,如果操作数是long类型,则运算结果是long类型,否则为int类型,绝不会是byte,short或char型。这样,如果变量i被声明为short或byte,i+1的结果会是int。如果结果超过该类型的取值范围,则按该类型的最大值取模。单目整数运算符是:

运算符操作-单目非~位补码++加1--减1

++运算符用于表示直接加1操作。增量操作也可以用加运算符和赋值操作间接完成。++lvalue(左值?copy;表示lvalue+=1,++lvalue也表示lvalue=lvalue+1(只要lvalue没有副作用)。--运算符用于表示减1操作。++和--运算符既可以作为前缀运算符,也可以做为后缀运算符。双目整数运算符是:

运算符操作**+加-减*乘/除%取模&位与|位或^位异或<<左移>>右移(带符号)>>>添零右移

整数除法按零舍入。除法和取模遵守以下等式:(a/b)*b+(a%b)==a整数算术运算的异常是由于除零或按零取模造成的。它将引发一个算术异常。下溢产生零,上溢导致越界。例如:加1超过整数最大值,取模后,变成最小值。一个op=赋值运算符,和上表中的各双目整数运算符联用,构成一个表达式。整数关系运算符<,>,<=,>=,==和!=产生boolean类型的数据。

布尔运算符布尔(boolean)变量或表达式的组合运算可以产生新的boolean值。单目运算符!是布尔非。双目运算符&,|和^是逻辑AND,OR和XOR运算符,它们强制两个操作数求布尔值。为避免右侧操作数冗余求值,用户可以使用短路求值运算符&&和||。用户可以使用==和!=,赋值运算符也可以用&=、|=、^=。三元条件操作符?:和C语言中的一样。

浮点运算符浮点运算符可以使用常规运算符的组合:如单目运算符++、--,双目运算符+、-、*和/,以及赋值运算符+=,-=,*=,和/=。此外,还有取模运算:%和%=也可以作用于浮点数,例如:a%b和a-((int)(a/b)*b)的语义相同。这表示a%b的结果是除完后剩下的浮点数部分。只有单精度操作数的浮点表达式按照单精度运算求值,产生单精度结果。如果浮点表达式中含有一个或一个以上的双精度操作数,则按双精度运算,结果是双精度浮点数。

数组运算符数组运算符形式如下:<expression>[<expression>]可给出数组中某个元素的值。合法的取值范围是从0到数组的长度减1。取值范围的检查只在运行时刻实?copy;。

?reg;运算符?reg;以String对象实现。运算符"+"完成并?reg;操作,如果必要则自动把操作数转换为String型。如果操作数是一个对象,它可定义一个方法toString()返回该对象的String方式,例如floata=1.0print("Thevalueofais"+a+"\n");+运算符用到?reg;上的例子Strings="a="+a;+=运算符也可以用于String。注意,左边(下例中的s1)仅求值一次。s1+=a;//s1=s1+a//若a非String型,自动转换为String型。

对象运算符双目运算符instanceof测试某个对象是否是指定类或其子类的实例。例如:if(myObjectinstanceofMyClass){MyClassanothermyObject=(MyClass)myObject;…}是判定myObject是否是MyClass的实例或是其子类的实例。

强制和转换Java语言和解释器限制使用强制和转换,以防止出错导致系统崩溃。整数和浮点数?reg;间可以来回强制转换,但整数不能强制转换成数组或对象。对象不能被强制为基本类型。

3.1.6Java流控制

下面几个控制结构是从C语言借鉴的。

分支结构

if/else分支结构

if(Boolean){statemanets;}else{statements;}

switch分支结构

switch(expr1){caseexpr2:statements;break;caseexpr3:statements;break;default:statements;break;}

循环结构for循环结构

for(initexpr1;testexpr2;incrementexpr3){statements;}

While循环结构

While(Boolean){statements;}

Do循环结构

do{statements;}while(Boolean);

一般顺序控制

break[label]continue[label]reutrnexpr;label:statement;

for循环例子下面是一个程序例子,画几条线,分别用红,绿,蓝颜色,这段程序可能是Java函数的一部分:

intcount;for(count=1;count<=12;count++){switch(count%3)}case0:setColor(Color.red);break;case1:setColor(Color.blue);break;case2:setColor(Color.green);break;}g.drawLine(10,count*10,80,count*10);}

3.2Java变量和函数的实例

Java的类包含变量和函数。数据变量可以是一?copy;原始的类型,如int,char等。成员函数是一?copy;可执行的过程。例如,下面程序里:publicclassClassOne{inti;publicClassOne(){i=10;}

publicvoidAdd_i(intj){i=i+j;}}

ClassOne包含一个变量i和两个成员函数,ClassOne(intfirst)和Add_i(intj)。

成员函数成员函数是一?copy;可被其它类或自己类调用的处理子程序。一个特殊的成员函数叫构造函数,这个函数名称一般与本类名程相同。它没有返回值。

构造函数和成员函数当你在Java里定义一个类时,你可定义一个或多个可选的构造函数,当创建本类的一个对象时用某一个构造函数来初始化本对象。用前面的程序例子来说明,当ClassOne类创建一个新实例时,所有成员函数和变量被创建(创建实例)。构造函数被调用。ClassOnemc:mc=newClassOne();

关键词new用来创建一个类的实例,一个类用new初始化?reg;前并不占用内存,它只是一个类型定义,当mc对象初始化后,mc对象里的i变量等于10。你可以通过对象名来引用变量i。(有时称?reg;为实例变量)mc.i++;//mc实例变量加1因为mc有ClassOne类的所有变量和成员函数,我们可以使用同样的语法来调用成员函数Add_i:Add_i(10);现在mc.i变量等于21.

结束函数Java并不支持析构函数(C++里的定义),因为java本身提?copy;对象无用时自动清除的功能,同时它也提?copy;了一个自动拉圾箱的成员函数,在清除对象时被调用:Protectedvoidfinalize(){close();}

3.3对象有效范围和废物自动回收

对象有一定的生命期并在它的生命期间使用资源,当一个对象不再被使用时,它应释放内存,避免内存溢出。在Java里,收集和释放内存是一个叫自动废品回收站的线程的责任。这个线程监视对象有效范围并给一个走出有效范围的对象作上标识。

例如:Strings;//没有分配内存s=newString("oldstring");//分配内存s="newstring";//重新分配内存(创建新对象)

我们将在以后访问String类时将更加明白它的工作过程,但它的快速工作过程是这样的:1.创建一个新的String类对象并填充以"oldstring"2.创建另一个String对象并填充以"newstring"注意我们创建了两个对象。Stirng对象"oldstring"Stirng对象"newstring"

在第三条语句里,第一个包括"oldstring"的叫做s的对象已走出了有效范围,没有任何方法可以再访问他,我们现在有一个新的对象也叫s,包含"newstring"。在下一个废品回收线程,前一个对象将被标识并清除。

3.4子类

子类是利用存在的对象创建一个新对象的机制,比如,如果你有一个Horse类,你可以创建一个Zebra子类,Zebra是Horse的一种。

classZebraextendsHorse{intnumber_OF_stripes:}

关键词extends来定义对象有的子类.Zebra是Horse的子类。Horse类里的所有特征都将拷贝到Zebra类里,而Zebra类里可以定义自己的成员函数和实例变量。Zebra称为Horse的派生类或继承。另外,你也许还想覆盖基类的成员函数。用ClassOne说明,下面是一个派生类覆盖Add_i功能的例子.

importClassOne;publicclassNewClassextendsClassOne{publicvoidAdd_i(intj){i=i+(j/2);}}

当NewClass类的实例创建时,变量i初始化值为10,但调用Add_i产生不同的结果。NewClassmnc;mnc=newNewClass();mnc.Add_i(10);

访问控制Java里当你创建一个新类时,你可以标明变量和成员函数的访问层次。

publicpublicvoidAnyOneCanAccess(){}public实例变量和成员函数可以任意其它类调用。

protectedprotectedvoidOnlySubClasses(){}protected实例变量和成员函数只能被其子类调用.

privateprivateStringCreditCardNumber;private实例变量和成员函数只能在本类里调用.

friendlyvoidMyPackageMethod(){}缺省的,如果没有定义任何防火控制,实例变量或函数缺省定义成friendly,意味着可以被本包里的任意对象防问,但其它包里的对象不可防问。

静态成员函数和变量有?copy;时候,你创建一个类,希望这个类的所有实例都公用一个变量。也就是说,所有这个类的对象都只有实例变量的同一个拷贝。这种方法的关键词是static,例如:

classBlock{staticintnumber=50;}

所有从Block类创建的对象的number变量值都是相同的。无任在哪个对象里改变了number的值,所有对象的number都跟着改变。同样的,你可以定义static成员函数,但这个成员函数不能访问非static函数和变量。

classBlock{staticintnumber=50;intlocalvalue;staticvoidadd_local(){localvalue++;//没有运行}staticvoidadd_static(){number++;//运行}}

3.5this和super

访问一个类的实例变量时,this关键词是指向这个类本身的指针,在前面ClassOne例子中,我们可以增加构造函数如下:

publicclassClassOne{inti;publicClassOne(){i=10;}

publicClassOne(intvalue)this.i=value;}

publicvoidAdd_i(intj){i=i+j;}}

这里,this指向ClassOne类的指针。如果在一个子类里覆盖了父类的某个成员函数,但又想调用父类的成员函数,你可以用super关键词指向父类的成员函数。

importClassOne;publicclassNewClassextendsClassOne{publicvoidAdd_i(intj){i=i+(j/2);super.Add_i(j);}}

下面程序里,i变量被构造函数设成10,然后15,最后被父类(ClassOne)设成25。

NewClassmnc;mnc=newNewClass();mnc.Add_i(10);

3.6类的类型

至今为止,我用在类前面只用了一个public关键词,其实它有下面4种选择:

abstract一个abstract类必须至少有一个虚拟函数,一个abstract类不能直接创建对象,必须继承子类后才能。

final一个final类声明了子类链的结尾,用final声明的类不能再派生子类。

publicpublic类能被其它的类访问。在其它包里,如果想使用这个类必须先import,否则它只能在它定义的package里使用。

synchronicable这个类标识表示所有?copy;类的成员函数都是同步的。

3.7抽象类

面向对象的一个最大优点就是能够定义怎样使用这个类而不必真正定义好成员函数。如果程序由不同的用户实现时是很有用的,这不需用户使用相同的成员函数名。

在java里Graphics类里一个abstract类的例子如下:publicabstractclassGraphics{publicabstractvoiddrawLine(intx1,inty1,intx2,inty2);publicabstractvoiddrawOval(intx,inty,intwidth,intheight);publicabstractvoiddrawRect(intx,inty,intwidth,intheight);...}

在Graphics类里声明了几个成员函数,但成员函数的实际代码是在另外一?copy;地方实现的。

publicclassMyClassextendsGraphics{publicvoiddrawLine(intx1,inty1,intx2,inty2){<画线程序代码>}}

当一个类包含一个abstract成员函数,这个类必须定义为abstract类。然而并不是abstract类的所有的成员函数都是abstract的。Abstract类不能有私有成员函数(它们不能被实现),也不能有静态成员函数。

3.8接口

当你确定多个类的操作方式都很相象时,abstract成员函数是很有用的。但如果你需要使用这?copy;abstract成员函数,必须创建一个新类,这样有时很繁琐。接口提?copy;了一种抽象成员函数的有利方法。一个接口包含了在另一个地方实现的成员函数的收集。成员函数在接口里定义为public和abstract。接口里的实例变量是public,static和final。接口和抽象的主要区别是一个接口提?copy;了封装成员函数协议的方法而不必强迫用户继承类。

例子:publicinterfaceAudiClip{//Startplayingtheclip.voidplay();//Playtheclipinaloop.voidloop();//Stopplayingtheclipvoidstop();}

想使用AudioClip接口的类使用implenents关键词来提?copy;成员函数的程序代码。classMyClassimplementsAudioClip{voidplay(){<实现代码>}voidloop<实现代码>}voidstop<实现代码>}}

优点一个接口类可以被任意多的类实现,每个类可以共享程序接口而不必关心其它类是怎样实现的。classMyOtherClassimplementsAudioClip{voidstop(){<实现代码>}...}

内部成员函数Java还提?copy;了调用C和C++函数的方法。用native关键词来定义C和C++的函数。

publicclassDate{intnow;publicDate(){now=time();}privatenativeinttime();

static{System.loadLibrary("time");}}

一?copy;Java代码写好后,就需要以下步骤执行:1.用javah来创建头文件(.h)2.用javah来创建stub文件3.用C和C++写native成员函数的代码4.编译stub文件和.C文件成一个动态可加载库5.用java运行java程序或appletviewer运行applet

注意:Native成员函数超出了类的范围。

3.9包(Packages)

包(Package)由一组类(class)和界面(interface)组成。它是管理大型名字空间,避免名字冲突的工具。每一个类和界面的名字都包含在某个包中。按照一般的习惯,它的名字是由"."号分隔的单词构成,第一个单词通常是开发这个包的组织的名称。

定义一个编译单元的包编译单元的包由package语句定义。如果使用package语句,编译单元的第一行必须无空格,也无注释。其格式如下:packagepackageName;若编译单元无package语句,则该单元被置于一个缺省的无名的包中。

使用其它包中的类和界面在Java语言里提?copy;一个包可以使用另一个包中类和界面的定义和实现的机制。用import关键词来标明来自其它包中的类。一个编译单元可以自动把指定的类和界面输入到它自己的包中。在一个包中的代码可以有两种方式来定义来自其它包中的类和界面:*在每个引用的类和界面前面给出它们所在的包的名字;//前缀包名法acme.project.FooBarobj=newacme.project.FooBar();*使用import语句,引入一个类或一个界面,或包含它们的包。引入的类和界面的名字在当前的名字空间可用。引入一个包时,则该包所有的公有类和界面均可用。其形式如下://从acme.project引入所有类importacme.project.*;这个语句表示acme.project中所有的公有类被引入当前包。以下语句从acme.project包中进入一个类Employec_List。//从acme.project而引入Employee_Listimportacme.project.Employee_list;Employee_Listobj=newEmployee_List();在使用一个外部类或界面时,必须要声明该类或界面所在的包,否则会产生编译错误。

import(引用)类包(classpackage)用import关键词调入,指定package名字如路径和类名,用*匹配符可以调入多于一个类名。

importjava.Date;importjava.awt.*;

如果java源文件不包含package,它放在缺省的无名package。这与源文件同目录,类可以这样引入:importMyClass。

Java系统包:Java语言提?copy;了一个包含窗口工具箱,实用程序,一般I/O,工具和网络功能的包。

java.applet这个包包含量了一?copy;设计applet的类,用一个类Applet和三个接口.AppletContext;AppletStub;和AudioClip.

java.awt另一个窗口工具箱包.awt,包含了一?copy;产生装饰物和GUI成员的类。这个package包括:Button,Checkbox,Choice,Component,Graphics,Menu,Pane1,TextArea和TextField。

java.ioI/Opackage包含文件输入/输出类,FileInputStream和FileOutputStream.

java.lang这个包包含Java语言类,包含:对象,线程,异常出口,系统,整数,原点,数学,字符等。

java.net这个类支持TCP/IP网络协议,并包含Socket类,URL和URL相联系的类。

java.util这个类包含一?copy;程序的同步类,它包含Date,Dictionary类等。

3.10异常

当在Java程序中发生一个错误时,例如:一个变元的值非法,代码会发现这个错误,并引发一个异常(exception)。在缺省的情况下,异常会输出一个错误消息,然后中止线程的执行。但是,程序自己可以定义异常处理段(exceptionhandler)来截获(catch)异常,并从错误中恢复。有一?copy;异常是由Java解释器在运行时刻引发的。实际上,任何类都可以定义属于自己的异常,并使用throw语句引发它们。一个throw(引发?copy;语句是由throw关键字和一个对象构成。按常规,该对象应该是Exception类的实例或其子类的实例。throw语句会引起执行转向相应的异常处理段。当一个throw语句执行时,它下面的所有代码不再执行了,它所在的方法也不再返回值。下面的例子将演示如何创建一个Exception的子类,然后引发一个异常。classMyExceptionextendsException{}classMyClass{voidoops(){if(/*不出现错误*/){…}else{/*出错*/

}else{/*出错*/thrownewMyException();}}}为了定义一个异常处理段,程序必须用try语句把可能产生异常的代码成组。在try语句后面跟上一个或多个catch(截获?copy;语句,每个异常对应一个catch语句。每个catch语句中包含着异常处理段。例如:try{p.a=10;}catch(NullPointerExceptione){println("pwasnull");}catch(Exceptione){println("othererrorsoccured");}catch(Objectobj){println("Whothrewthatobject?");}catch语句和一个方法定义类似,只不过该方法只有一个参数,且无返回类型。参数可以是一个类或一个界面。当一个异常发生时,嵌套的try/catch语句会寻找出与该异常类相匹配的参数。如果一个参数和指定异常匹配则:*该参数和指定的异常是同一个类,或*该参数是指定异常的子类,或*如果参数是一个界面,指定异常类实现了这个界面。第一个参数和异常匹配的try/catch语句,则与其匹配的catch语句执行。在catch语句执行完后,程序的执行被恢复。但已不可能恢复到异常发生处再次执行。例如:print("now");try{print("is");thrownewMyException();print("a");}catch(MyExceptione){print("the");}print("time\n");打印为"nowisthetime"。正如这个例子所示,异常应该主要用于错误处理,若用于其它方面会使代码晦涩难?reg;。异常处理段是可以嵌套的,允许异常处理可以发生在多个地方。嵌套异常处理通常用于当第一个处理程序无法完全从错误中恢复过来的时候,而不得不执行一?copy;清除代码。为了把异常处理控制传递给更高层的处理段,可以再一次对截获对象实?copy;throw操作。注要再次实?copy;throw异常的方法,throw语句执行完后,会终止执行。try{f.open();}catch(Exceptione){f.close();throwe;}

定局语句finally(定局?copy;语句是用于保证无论在异常是否发生的情况下,某?copy;代码被执行。下例说明finally语句的用法:try{//做某?copy;动作;}finally{//此后清除;}和以下代码类似try{//做某?copy;动作}catch(Objecte){//此后清除;throwe;}

}//此后清除;即使try块中包含return,break,continue,throw语句,finally语句也会被执行。例如:下面的代码"finally"总是被输出,而"aftertry"仅在a!=10时被输出。try{if(a==10){return;}}finally{print("finally\n");}print("aftertry\n");

运行时刻异常本节列出的清单是Java解释器引发的各种异常。当运行时刻发现各种错误,由解释器引发异常。

ArithmeticException如果程序试图除0,或用0取模,会产生ArithmeticException(算术异常?copy;,其它算术操作不会产生异常。有关Java如何处理其它算术错误的信息,见"整数运算符"和"浮点运算符"两节。例如:下面的代码将会引发ArithmeticException异常:classArith{publicstaticvoidmain(Stringargs[]){intj=0;j=j/j;}}

NullPointerException当程序试图访问一个空对象中的变量或方法,或一个空数组中的元素时则引发NullPointerException(空指针异常?copy;。例如,访问长度为0的数组a[0]。有以下类声明,运行时会引发NullPointerException异常:classNull{publicstaticvoidmain(Stringargs[]){Stringo=null;inta[]=null;o.length();a[0]=0;}}有趣的是,如果我们引发一个空对象,也会产一NullPointerException异常。

IncompatibleClassChangeException当一个类的定义被改变,而引用该类的其它类没有被重新编译时,会产生这一异常。有四种类更改会导致运行时刻引发IncompatibleClassChangException异常。*一个类中的变量声明由static变成非static,而其它访问该类这一变量的类没有被重新编译。*一个类中的变量声明由非static变成static,而其它访问该类这一变量的类没有被重新编译。*类中声明的某个域被删除,而其它访问该域的类没有被重新编译。*类中声明的某个方法被删除,而其它访问该方法的类没有被重新编译。

ClassCastException如果试图把对象o强制成ClassC,而o既不是ClassC的实例,也不是ClassC子类的实例,这时便会产生ClassCastException。classClassCast{publicstaticvoidmain(Stringargs[]){Objecto=newObject();Strings=(string)o;s.length();}}

}

NagativeArraySizeException如果一个数组的长度是负数,则会引发NagativeArraySizeException(数组负下标?copy;异常。例如下面类定义的代码在运行时引发这一异常:classNegArray{publicstaticvoidmain(Stringargs[]){inta[]=newint[-1];a[0]=0;}}

OutOfMemoryException当系统无法再向应用程序提?copy;内存时,会引发OutOfMemoryException(内存溢出?copy;异常。这种异常只能出现在创建新对象的时候,即new被调用的时候。例如,下面一段代码在运行时刻会引发OutOfMemoryException异常:classLink{inta[]=newint[1000000];Linkl;}ClassOutOfMem{publicstaticvoidmain(Stringargs[]){

publicstaticvoidmain(Stringargs[]){Linkroot=newlink();Linkcur=root;while(true){cur.l=newLink();cur=cur.l;}}}

NoClassDefFoundException如果一个类被引用,但在运行时刻,系统没有找到被引用的类,这时会引发NoClassDefFoundException(未找到类定义?copy;异常。例如,NoClass类的声明如下:classNoClass{publicstaticvoidmain(Stringargs[]){Cc=newC();}}当NoClass运行时,如果解释器找不到C类,则会产生NoClassDefFoundException。注意,在NoClass被编译时C类一定要存在。

IncompatibleTypeException如果试图为一界面作实例,则会引发IncompatibleTypeException(类型不兼容?copy;异常。例如,下面的代码会引发一个IncompatibleTypeException。InterfaceI{}classIncompType{publicstaticvoidmain(Stringargs[]){Ir=(I)new("I");}}

ArrayIndexOutOfBoundsException试图访问数组中的一个非法元素时,会引发ArrayIndexOutOfBoundsException(数组索引越界?copy;异常。例如:ClassArrayOut{publicstaticvoidmain(Stringargs[]){inta[]=newint[0];a[0]=0;}}

publicstaticvoidmain(Stringargs[]){inta[]=newint[0];a[0]=0;}}UnsatisfiedLinkException如果一个方法被声明为本机,但该方法在运行时刻却不能连接到一个例程体上去时,会产生UnsatisfiedLinkException(无法连接?copy;异常。例如:ClassNoLink{staticnativevoidfoo();publicstaticvoidmain(Stringargs[]){foo();}}

InternalExceptionInternalException(内部?copy;异常是不能被引发的。只有在运行失败作一致性检查时,才会引发这个异常。

本章小结

1.Java语言的基本结构象C/C++。2.Java语言的源程序代码由一个或多个编译单元(compilationunit)组成。

第四章Java应用程序的基本框架

学习一门新语言最好是先看几个简单的程序例子。下面我们将看到几个非常基本的程序例子。

4.1Java应用程序的运行环境

Java应用程序是指可以独立运行在Java虚拟机上的程序,它是一种中间代码(byte-code?copy;。比如你的应用程序叫my.java,程序里有一个名称为app1的类,用Javac或其它编译器,编译后将会生成app1.class,则在命令行状态下输入:javaapp1就可以运行此程序。注意,用java命令运行的类必须有main函数,否则不能执行。与普通java应用程序不同的另一种另一种Java程序叫JavaApplet。我们把它译成Java小程序,这种程序后缀也是.class,但它不能直接在java虚拟机上运行,也就是输入java*.class不能运行,这种程序里可以没有main函数,它必须由某个浏览器来运行,比如Appletviewer或Netscape2.02以上等。这种程序我们将在后面章节中介绍。

4.2最简单的Java程序解释

让我们来看一看最简单的Java应用程序例子,来理解一下它的结构:Filename:1.java

classmyfirst{publicstaticvoidmain(Stringargs[]){System.out,println("ThisismyfirstJavaApplication");}}

这就是一个完整的Java应用程序,将它编译:Javac1.java在当前目录下,它将生成myfirst.class文件,Javamyfirst屏幕上将会输出:ThisismyfirstJavaApplication

让我们来一步一步分析每句话含义(1?copy;classmyfirst这一行用关键词class来定义名为myfirst的新类,myfirst是新类的名称,必须是一个有效的标识符,有效标识符定义请见程序设计基础章节。类的说明包括数据说明和成员函数说明,都放在类后面的大括号里面。一般类定义如下:class类名称{数据定义;函数定义;}

(2)publicstaticvoidmain(Stringargs[])public是一个表示访问权限的关键字,表示此成员函数是公有的,可以被其他类直接调用,包括java解释器。相对应的关键字有private和protected,friend。private表示只能被本类访问,protected表示只能被子类访问,friend是缺省的访问权限,表示能被本包(package)中任意类访问,对其它包中的类是不可访问的。static表示main成员函数在myfirst类的所有对象中是唯一的,因此如果本程序生成另一个myfirst类对象,调用的main函数将是同一个函数。void表示main函数没有返回值,如果有返回类型值,则可加上interger或boolean诸如此类,对于有返回值的函数,其函数实体的最后应加上return语句。main这个函数是这运行应用程序的入口点,因此编写应用程序是必须有main()函数,且是唯一的。

(3?copy;System.out.println一句这一句是main函数里的功能语句,是调用Java里System包里的out类的println成员函数,是标准输入输出。

4.3Java应用程序参数的传递

Java语言很类似C和C++语言。在C语言里,通过在命令行输入参数,C程序可由main函数读入这?copy;参数,java程序也一样,请看下面程序:Filename:2.java

classMy2{publicstaticvoidmain(Stringargs[]){intarc=args.length;if(arc>0){for(inti=0;i<arc;i++)System.out.println(args[i])}else{System.out.println("Applicationhavenoargs!");}}}

编译:javac2.java将在本目录下生成My2.class文件。

运行:javaMy2输出:Applicationhavenoargs!

运行:javaMy2arg1arg2arg3输出:arg1arg2arg3这说明,java里参数的传递是这样的,命令行里类名后第一个参数放在args[0]里,第二个参数放在args[1]里,以此类推。

4.4建立窗口Java程序基本框架

Frame介绍在Java语言里,Frame类的功能是创建图形用户界面(GUI)的基本窗口。在典型浏览器里,Frame类是显示GUI的父类。

类的层次结构如下:java.lang.Objectjava.awt.Componentjava.awt.Containerjava.awt.Windowjava.awt.Frame

Frame是从没有边框和菜单条的空白窗口?copy;充来的。这种窗口主要用在弹出式窗口方式下。Frame给窗口一个边框,一个布局和一个窗口条。

你可以通过创建自己的多个Frame来创建图形用户界面的应用程序。我们已知道怎样建立独立的java应用程序,那么怎样将各种迷人的图形结合在一块呢?我们通过建立一个Frame来实现这个功能。Frame让我们访问窗口,和applet工作区有许多相同的地方。下面是一个小的应用程序,它弹出一个框架并且在框架里显示信息。(信息可以是命令行参数,也可以是缺省的信息?copy;。如果你在窗口里点一下,程序退出。

基本框架程序importjava.awt.*;

/**Memo.java*Asimplestand-alonegraphicalapplication*/

publicclassMemoextendsFrame{publcStringmotd;

publicMemo(Strings){//setourtitlesuper("MemoFrame");

motd=s;resize(300,300);}

publicMemo(){this("ThisisaMemo");}

publicvoidpaint(Graphicsg){g.drawString(motd,15,15);g.drawString("ClickanywheretoExit",15,25);}

publicvoidstart(){show();}

publicbooleanmouseDown(Evente,intx,inty){//hidethewindowhide();//freeupsystemresourcesdispose();//Quittheapp.System.exit(0);returnfalse;}

publicstaticvoidmain(Stringargs[]){Memom;if(args.length>){m=newMemo(args[0]);}else{m=newMemo();}m.start();}}

将Frame联系起来l熟悉的函数paint()和mouseDown(),看起来有点眼熟。这?copy;函数与applet的函数一样。实际上,一个frame包含各种GUI组件与applet的形式一样。另一个熟悉的函数是start()。这个函数并不必要,因为我们没有覆盖任何已存在的函数。但你想继承applet编程的风格,你还是可以使用start(),stop(),init()与destroy()等函数。

l新函数我们熟悉的函数start()调用了show()。show()函数是Window类的继承,它显示Fame及其所有组件。在mouseDown()函数里我们看到两个函数:hide()和dispose()。hide()只简单地使Frame不可见。你可以在任何时候调用它来隐藏窗口。dispose()函数释放由Frame占有的系统资源。只有在你不需要Frame时才调用它。

l构造函数Memo例子还包含了一个新函数Memo()。其实这个类有两个Memo()函数!任何与类名相同的函数都是构造函数。它在创建一个新对象时被调用。你可以认为它是一个对象的init()初始化函数。为什么需要两个构造函数呢?有了两个构造函数,我们可以利用多态性的优点,有两种方法创建一个新Memo对象。我们可以简单的创建使用缺省信息的Memo对象:m=newMemo();或者,我们可以自己提?copy;信息:m=newMemo("Ourmessage");

Frame控制本程序的最后功能是在main()里建立并显示frame。它由以下两步实现://Step1m=newMemo();

//Step2m.start();第一步初始化memo对象。我们象操作其它对象一样操作m。为显示frame,我们需要调用show()。这在第二步里实现。另一个要注意的是程序的健壮性:我们基本的frame里没有包含处理标准WINDOW_DESTROY消息的函数。这样,你将不能在窗口的控制菜单里选中"Quit"或"Exit"。为了增加这个功能,你需要增加以下的处理程序:

publicbooleanhandleEvent(Evente){if(e.id==Event.WINDOW_DESTROY){dispose();System.exit(1);returntrue;}else{//Goaheadanddowhatwenormallywouldhavedonereturnsuper.handleEvent(e);}}

菜单图形界面依靠菜单来指导用户操作。设计独立的Java应用程序时,Java提?copy;创建和使用菜单的直接方法。象其它组件一样,new将创建一个菜单:

MenuoptionMenu;optionsMenu=newMenu("Options");

菜单项一?copy;你创建了一个菜单,你可以使用add()来组建菜单项:

optionsMenu.add(newMenuItem("Option1");optionsMenu.add(newMenuItem("Option2");

菜单事件当你选中某个菜单项时,你创建此菜单项的字符?reg;将在事件中返回。你可以象测试按钮选择一样测试菜单选择:

publicbooleanaction(Evente,Objectarg){...if(e.targetinstanceofMenuItem){System.out.println((String)arg);}...}

其它菜单项除了上面描述的简单菜单项外,你还可增加CheckBox菜单项,分割线,以及子菜单。下面是一?copy;例子:Menum,n;m=newMenu("Examples");m.add(newMenuItem("Basic"));m.add(newMenuItem("Simple"));

//addaseparatorm.add(newMenuItem("-"));

//addaCheckboxitemm.add(newCheckboxMenuItem("Check"));

//addasubmenun=newMenu("MoreExamples");n.add(newMenuItem("SubBasic"));n.add(newMenuItem("SubSimple"));m.add(n);

菜单条你创建好菜单后,你应将?reg;放在应用程序的菜单条上:mb=newMenubar();

mb.add(m);mb.add(optionsMenu);

然后你可为applet设置菜单条:setMenuBar(mb);

4.5独立应用程序例子

为了看一个更复杂的独立的图形界面应用程序,下面有一个数字转换的例子:

importjava.awt.*;

publicclassd2xextendsFrame{intdecimalValue=0;StringbaseXValue=newString("0");TextFielddDisplay,xDisplay;

//d2xconstructorpublicd2x(){super("DecimalConverter");//setthetitleoftheframeMenuBarmb=newMenuBar();Buttond2Binary=newButton("Binary");Buttond2Octal=newButton("Octal");Buttond2Hex=newButton("Hex");Buttond2Base36=newButton("Base36");Panelp1=newPanel();Panelp2=newPanel();Panelp3=newPanel();

//addasimplemenuMenum=newMenu("Application");m.add(newCheckboxMenuItem("Base36Active");m.add(newMenuItem("Exit"));

//addmenutomenubarmb.add(m);setMenuBar(mb);//installthismenubarintheframe

//Addbuttonstotheirownpanelp3.setLayout(newFlowLayout());p3.add(d2Binary);p3.add(d2Octal);p3.add(d2Hex);p3.add(d2Base36);

//AddtextfieldsLabeldLabel=newLabel("EnterDeecimal:");LabelxLabel=newLabel("ConvertedValue:");dDisplay=newTextField(integer.toString(decimalValue),7);xDisplay=newTextField(baseXValue,32);xDisplay.setEditable(false);p1.setLayout(newFlowLayout(FlowLayout.LEFT));p2.setLayout(newFlowLayout(FlowLayout.LEFT));p1.add(dLabel);p1.add(dDisplay);p2.add(xLabel);p2.add(xDisplay);

//Addthepanelsadd("North",p1);add("Center",p2);add("South",p3);}//endd2xconstructor

publicvoidstart(){resize(400,150);show();}

publicvoidupdateXDisplay(){xDisplay.setText(baseXValue);}

publicbooleanhandleEvent(Eventevt){if(evt.targtintanceofMenuItem){if("Exit".equals(((MenuItem)evt.target).getLabel())){hide();dispose();System.exit(0);returnfalse;}retruntrue;}elseif(evt.targetinstanceofButton){Stringwhick=((Button)evt.target).getLabel();if(whick.equals("Binary")){decimalValue=Integer.parseInt(dDisplay.getText());baseXValue=Interger.toString(decimalValue,2);}if(whick.equals("Octal")){decimalValue=Integer.parseInt(dDisplay.getText());baseXValue=Interger.toString(decimalValue,8);}if(whick.equals("Hex")){decimalValue=Integer.parseInt(dDisplay.getText());baseXValue=Interger.toString(decimalValue,16);}if(whick.equals("36")){decimalValue=Integer.parseInt(dDisplay.getText());baseXValue=Interger.toString(decimalValue,36);}updateXDisplay();returntrue;}returnfalse;}

publicstaticvoidmain(Stringargs[]){d2xm=newd2x();m.start();}}

输出结构如图:

本章小结:

1.java有两种类型的应用程序,一种是直接运行在java虚拟机上,用java命令执行;另一种运行在浏览器里,由浏览器调用执行,一般称它为Applet小程序。本书主要讨论第一种应用程序。2.java应用程序是由类组成的,而且用java命令行执行的类必须有main入口函数。3.与C语言相似,java程序也可由命令行传递给main函数参数。4.基本窗口java程序的基本类是Frame。利用它可以很方便地建立图形用户界面程序。

第五章Java的类

类是Java语言面向对象编程的基本元素,它定义了一个对象的结构和行为。在Java程序里,你要表达的概念封装在某个类里。一个类定义了一个对象的结构和它的功能接口,功能接口称为成员函数。当Java程序运行时,系统用类的定义创建类的实例,类的实例是真正的对象。类定义的一般形式如下:

classclassnameextendssuperclassname{typeinstance-variable1;typeinstance-variable2;.................................typeinstance-variableN;typemethodname1(parameter-list){method-body;}typemethodname2(parameter-list){method-body;}....................................................typemethodnameN(parameter-list){method-body;}}

这里,classname和superclassname是合法的标识符。关键词extends用来表明classname是superclassname派生的子类。有一个类叫做Object,它是所有Java类的根。如果你想定义Object的直接子类,你可以省略extends子句,编译器会自动包含它。下面是一个简单的类的定义。

classUniversity{}

5.1对象实例

类名可以作为变量的类型来使用,如果一个变量的类型是某个类,那么它将指向这个类的实例,称为对象实例。所有对象实例和它们的类型(某个类?copy;的子类的实例都是相容的。就象可以把byte型的值赋给int型的变量一样,你可以把Object的子类的任何实例赋给一个Object型的变量。一个实例是类模板的单独的拷贝,带有自己的称为实例变量的数据集。每个实例也可以作为一个对象。当你定义一个变量的类型是某个类时,它的缺省值是null,null是Object的一个实例。对象null没有值,它和整数0不同。下面这个例子中,声明变量u的类型是类University。

Universityu;

这里,变量u的值是null。

5.2实例变量

Java通过在类定义的大括号里声明变量来把数据封装在一个类里。这里的变量称为实例变量。下面的例子定义了一个叫做University的类,它有两个实例变量:name和city。

classUniversity{Stringname,city;}

5.3new操作符

操作符new用来生成一个类的实例,下面这个例子生成了类University的一个实例,存放在变量u中。

Universityu=newUniversity();

在此例中,变量u指向这个对象,但并不真正包含这个对象。你可以用多个变量指向同一个对象。下面的例子中,创建了一个University的对象,但创建了两个指向它的变量。

Universityu=newUniversity();Universityu2=u;

对u2所指向的对象的任何改动都会对u所指向的对象起作用,因为它们是同一个对象。对u和u2的赋值只是把它们指向这个对象,既没有分配内存,也没有复制这个对象的任何部分。对u的再赋值只是简单地去掉了u和原来对象的联系,并不影响对象本身,下面的例子说明了这种情况。

Universityu=newUniversity();Universityu2=u;u=null;

尽管u被赋值为null,u2仍指向原来由操作符new创建的对象。在前面的例子里,我们生成了一个对象并且指向了它两次。这就允许两个变量改变同一个对象。创建一个新的对象时,可直接对它的实例变量赋值。每个对象都有它所属类的实例变量的拷贝,每个对象的实例变量都是和其他对象的实例变量分离的,所以改变一个对象的实例变量不会影响其他对象的实例变量。下面的例子创建了两个University的对象,并对它们分别赋值:

classTwoUniversity{publicstaticvoidmain(Stringargs[]){Universityu1=newUniversity();Universityu2=newUniversity();u1.name="北?copy;大学";u1.city="北?copy;";u2.name="清华大学";u2.city="北?copy;";System.out.println("大学:"+u1.name+"城市:"+u1.city);System.out.println("大学:"+u2.name+"城市:"+u2.city);}}

这个例子创建了两个University的对象,并且对它们的name、city分别赋了不同的值,这说明这两个对象是真正分离的。下面是该程序运行后的输出结果。

C:\>javaTwoUniversity大学:北?copy;大学城市:北?copy;大学:清华大学城市:北?copy;

5.4点(.?copy;操作符

点(.?copy;操作符用来接收一个对象的实例变量和成员函数。下面是用点操作符来接收实例变量的一般形式。

objectreference.variablename

这里objectreference是一个对象实例,variablename是这个对象里你想接收的实例变量。下面的程序段说明了怎样用点操作符来给实例变量赋值。

u.name="北?copy;大学";u.city="北?copy;";

下面说明怎样用点操作符来得到实例变量的值。

System.out.println("大学:"+u.name+"城市:"+u.city);

通过向类University里加入一个成员函数main,我们创建了一个完整的例子,它使用了new操作符来创建一个University,用点操作符来赋值,然后打印结果。

classUniversity{Stringname,city;publicstaticvoidmain(Stringargs[]){Universityu=newUniversity();u.name="北?copy;大学";u.city="北?copy;";System.out.println("大学:"+u.name+"城市:"+u.city);}}

运行这个程序后,就会得到下面的结果。

C:\>javaUniversity大学:北?copy;大学城市:北?copy;

5.5成员函数定义

成员函数,是类的功能接口,是类定义里的一个子程序,在类的定义里和实例变量处于同一级别。你必须通过一个类的实例来调用成员函数。成员函数可以不用点操作符而直接使用实例变量。成员函数带有输入参数,具有某种类型的返回值。成员函数定义的一般形式如下:

typemethodname(formal-parameter-list){method-body;}

这里type指的是成员函数的返回值的类型,如果没有返回值,就用无值(void?copy;类型。methodname可以是任何合法的标识符,但不能与当前的类名相同。formal-parameter-list是用逗号分隔的类型、标识符对的序列。如果没有参数,括号里就是空的。还是用我们的University的例子,下面的成员函数用来初始化两个实例变量。成员函数是在类的大括号?reg;内定义的,和实例变量所处的范围相同。

classUniversity{Stringname,city;voidinit(Stringa,Stringb){name=a;city=b;}}

注意,我们这里直接给name和city赋值,而没有象以前那样用u1.name。这是因为每个成员函数都在类的个别实例内执行。我们创建的类的实例具有它自己的实例变量,所以成员函数可直接使用它们。

5.6成员函数调用

可以用点(.?copy;操作符来调用一个类的实例的成员函数。成员函数调用的一般形式如下:

objectreference.methodname(parameter-list);

这里,objectreference是指向某个对象的变量,methodname是objectreference所属类的一个成员函数,parameter-list是用逗号分隔的变量或表达式的序列,它们要与该成员函数的定义的参数个数及类型匹配。在这个例子里,我们可以对任何University对象调用成员函数init来给name和city赋值。下面的程序段说明了怎样完成这个工作。

Universityu=newUniversity();u.init("北?copy;大学","北?copy;");

这个例子创建了University的一个实例,存放在u中。通过点操作符来调用这个实例的init成员函数,把"北?copy;大学"和"北?copy;"分别传递给参数a和b。在init成员函数内部,name和city直接指向u所指向的对象的实例变量。把name赋值为"北?copy;大学",city赋值为"北?copy;",然后返回。在这个例子里,init被定义为无值(void?copy;返回类型。在进行这个成员函数调用后,u指向这个name值和city值改变了的University对象。

5.7this

Java有一个特殊的实例值叫this,它用来在一个成员函数内部指向当前的对象。在前面的例子里,我们调用u.init,一?copy;进入init成员函数内部,this就会指向u所指向的对象。在Java里,在同一个范围定义两个相同名字的局部变量是不可以的。有趣的是,局部变量、成员函数的参数可以和实例变量的名字相同。前面我们没有用name和city作为成员函数init的参数名字,因为这样它们在成员函数的范围里就把实例变量name和city隐藏了,即name指向参数name,隐藏了实例变量name。this让我们可以直接指向对象本身。下面是另一个版本的init,用name和city作为参数名字,用this来接收当前对象的实例变量。

voidinit(Stringname,Stringcity){this.name=name;this.city=city;}

下面是带有新的init初始成员函数的TwoUniversity例子。

classUniversity{Stringname,city;voidinit(Stringname,Stringcity){this.name=name;this.city=city;}}

classTwoUniversityInit{publicstaticvoidmain(Stringargs[]){Universityu1=newUniversity();Universityu2=newUniversity();u1.init("北?copy;大学","北?copy;");u2.init("清华大学","北?copy;");System.out.println("大学:"+u1.name+"城市:"+u1.city);system.out.println("大学:"+u2.name+"城市:"+u2.city);}}

5.8构造函数(Constructor?copy;

每创建一个类的实例都去初始化它的所有变量是乏味的。如果一个对象在被创建时就完成了所有的初始工作,将是简单的和简洁的。因此,Java在类里提?copy;了一个特殊的成员函数,叫做构造函数(Constructor?copy;。一个构造函数是对象被创建时初始对象的成员函数。它具有和它所在的类完全一样的名字。一?copy;定义好一个构造函数,创建对象时就会自动调用它。构造函数没有返回类型,即使是void类型也没有。这是因为一个类的构造函数的返回值的类型就是这个类本身。构造函数的任务是初始一个对象的内部状态,所以用new操作符创建一个实例后,立刻就会得到一个清楚、可用的对象。下面这个例子里,用构造函数取代了成员函数init。

classUniversity{Stringname,city;University(Stringname,Stringcity){this.name=name;this.city=city;}}

classUniversityCreate{publicstaticvoidmain(Stringargs[]){Universityu=newUniversity("北?copy;大学","北?copy;");System.out.println("大学:"+u.name+"城市:"+u.city);}}

new语句中类名后的参数是传给构造函数的。

5.9成员函数重载

对于几个意义相近的成员函数,有时使用相同的名字便于理解。因此,Java语言实现了成员函数重载,即可以创建几个名字相同、参数不同的成员函数。成员函数重载提?copy;了Java的多态行为。下面的例子用到了重载。

classUniversity{Stringname,city;University(Stringname,Stringcity){this.name=name;this.city=city;}University(){name="北?copy;大学";city="北?copy;";}}

classUniversityCreateAlt{publicstaticvoidmain(Stringargs[]){Universityu=newUniversity();System.out.println("大学:"+u.name+"城市:"+u.city);}}

这个例子创建了一个University对象,调用了第二个构造函数。下面是它的运行结果。

C:\>javaUniversityCreateAlt大学:北?copy;大学城市:北?copy;

一个构造函数可以调用另一个构造函数来创建实例。例如:

classUniversity{Stringname,city;University(Stringname,Stringcity){this.name=name;this.city=city;}University(){this("北?copy;大学","北?copy;");}}

第二个构造函数调用了第一个构造函数来完成实例的初始化。你也可以用重载来创建一般的成员函数。下面这个例子里有University类的两个版本的samecity成员函数。samecity判断一个大学是否在一个城市里或一个大学和另一个大学是否在同一个城市里。一个成员函数用city作参数,另一个用University对象作参数。

classUniversity{Stringname,city;University(Stringname,Stringcity){this.name=name;this.city=city;}booleansamecity(Stringcity){if(city.equals(this.city))returntrue;elsereturnfalse;}booleansamecity(Universityu){returnsamecity(u.city);}}

classUniversityCity{publicstaticvoidmain(Stringargs[]){Stringcity="上海";Universityu1=newUniversity("北?copy;大学","北?copy;");Universityu2=newUniversity("清华大学","北?copy;");System.out.println("u1="+u1.name+","+u1.city);System.out.println("u2="+u2.name+","+u2.city);System.out.println("city="+city);System.out.println("u1.samecity(u2)="+u1.samecity(u2));System.out.println("u1.samecity(city)="+u1.samecity(city));}}

下面是该程序的运行结果。

C:\>javaUniversityCityu1=北?copy;大学,北?copy;u2=清华大学,北?copy;city=上海u1.samecity(u2)=trueu1.samecity(city)=false

5.10继承

第二个基本的面向对象机制是继承。继承是关于有层次关系的类?reg;间的概念。一个类的后代可以继承它的祖先的所有变量和成员函数,就象创建自己的一样。一个类的直接父亲叫做它的超类(superclass?copy;。一?copy;你创建了一个象University这样的类,创建它的子类是很简单的。一个类的子类是它的继承了实例变量和成员函数的特殊的版本。在这个例子里,我们把University类派生为含有叫做country的第三个元素的子类。

classUniversityWorldextendsUniversity{Stringcountry;UniversityWorld(Stringname,Stringcity,Stringcountry){this.name=name;this.city=city;this.country=country;}UniversityWorld(){this("北?copy;大学","北?copy;","中国");}}

关键词extends用来表示我们要创建University的子类。name和city不需再在UniversityWorld中进行声明,因为它们是从University中继承的。Java允许在UniversityWorld中声明变量name和city,但这会隐藏University中的name和city,是与使用子类的目的相矛盾的,应当避免。在UniversityWorld的实例中,name、city和country的地位是一样的。

5.11super在UniversityWorld的例子里,有一段代码和它的超类University的重复,这段代码是初始化name和city的,

this.name=name;this.city=city;

就象在University例子中用this指向第一个构造函数一样,在Java里有另一个变量叫做super,它直接指向超类的构造函数。下面这个例子用super来初始化变量name和city,然后打印出这个对象的内容。

classUniversityWorldextendsUniversity{Stringcountry;UniversityWorld(Stringname,Stringcity,Stringcountry){super(name,city);//调用了构造函数University(name,city)this.country=country;}publicstaticvoidmain(Stringargs[]){UniversityWorldu=newUniversityWorld("北?copy;大学","北?copy;","中国");System.out.println("大学:"+u.name+"城市:"+u.city+"国家:"+u.country);}}

下面是运行结果。

C:\>javaUniversityWorld大学:北?copy;大学城市:北?copy;国家:中国

5.12成员函数的覆盖

这个University的新的子类继承了它的超类的成员函数samecity。但这个成员函数samecity判断的是两个城市的名字,这是不够的,因为有可能两个两个名字一样的城市属于不同的国家,我们要用同时判断城市和国家的成员函数来覆盖它。下面就是实现覆盖的例子。

classUniversity{Stringname,city;University(Stringname,Stringcity){this.name=name;this.city=city;}booleansamecity(Stringcity){if(city.equals(this.city))returntrue;elsereturnfalse;}booleansamecity(Universityu){returnsamecity(u.city);}}

classUniversityWorldextendsUniversity{Stringcountry;UniversityWorld(Stringname,Stringcity,Stringcountry){super(name,city);this.country=country;}booleansamecity(Stringcity,Stringcountry){if(city.equals(u.city)&&country.equals(u.country))returntrue;elsereturnfalse;}booleansamecity(UniversityWorldother){returndistance(other.city,other.country);}}

classUniversityWorldCity{publicstaticvoidmain(Stringargs[]){Stringcity="上海";Stringcountry="中国";UniversityWorldu1=newUniversityWorld("北?copy;大学","北?copy;","中国");UniversityWorldu2=newUniversityWorld("清华大学","北?copy;","中国");System.out.println("u1="+u1.name+","+u1.city+","+u1.country);System.out.println("u2="+u2.name+","+u2.city+","+u2.country);System.out.println("city="+city+",country="+country);System.out.println("u1.samecity(u2)="+u1.samecity(u2));System.out.println("u1.samecity(city,country)="+u1.samecity(city,country));}}

下面是输出结果。

C:\>javaUniversityWorldCityu1=北?copy;大学,北?copy;,中国u2=清华大学,北?copy;,中国city=上海,country=中国u1.samecity(u2)=trueu1.samecity(city,country)=false

5.13动态成员函数发送

当你用点操作符调用一个对象实例的成员函数时,对象实例所属的类在编译时要被检查,以确保调用的成员函数在该类中是存在的。在运行时,对象实例可以指向所声明类型的子类的实例。在这?copy;情况下,如果子类覆盖了要调用的成员函数,Java就用实例来决定调用哪一个成员函数。如下面的例子,两个类是子类和超类的关系,子类覆盖了超类的成员函数。

classA{voidcallme(){System.out.println("在A的callme成员函数里");}}

classBextendsA{voidcallme(){System.out.println("在B的callme成员函数里");}}

classDispatch{publicstaticvoidmain(Stringargs[]){Aa=newB();a.callme();}}

有趣的是,在成员函数main里,我们把变量a声明为类型A,然后把类B的一个实例存放到它上面。我们在a上调用成员函数callme,Java编译器确定在类A确实有成员函数callme,但是在运行时,由于a事实上是B的实例,所以调用B的callme,而不调用A的。下面是运行结果:

C:\>javaDispatch在B的callme成员函数里

5.14final

在缺省情况下,所有的成员函数和实例变量都可以被覆盖。如果你希望你的变量或成员函数不再被子类覆盖,可以把它们声明为final。这意味着将来的实例都依赖这个定义。例如:

finalintFILE_NEW=1;finalintFILE_OPEN=2;finalintFILE_SAVE=3;fianlintFILE_SAVEAS=4;finalintFILE_QUIT=5;

final变量用大写标识符是一个一般的约定。

5.15静态

如果你想要创建一个可以在实例的外部调用的成员函数,那么你只需声明它为静态的(static?copy;,它就会正常运行。静态成员函数只能直接调用其他静态成员函数,而不能以任何方式使用this或super。你也可以把变量声明为静态的。如果你想初始化一个静态变量,你可以用static声明一个恰好在类调用时执行一次的程序块。下面的例子是一个带有一个静态成员函数,几个静态变量,和一个静态初始块的类。

classStatic{staticinta=3;staticintb;staticvoidmethod(intx){System.out.println("x="+x);System.out.println("a="+a);System.out.println("b="+b);}static{System.out.println("静态初始块");b=a*4;}publicstaticvoidmain(Stringargs[]){method(42);}}

一?copy;这个类被调用,所有的静态变量都被初始化,a被赋为3,然后运行static块,这将打印出一段消息,并且把b赋为a*4,即12。然后解释器调用main成员函数,它调用了成员函数method,参数x为42。这三个println语句打印了两个静态变量a、b和局部变量x。下面是运行结果:

C:\>javaStatic静态初始块x=42a=3b=12

一个静态成员函数可以通过它所属的类名来调用。象调用实例变量一样,你可以用点操作符通过类名来调用静态成员函数和静态变量。Java就是这样实现了全局函数和全局变量。下面的例子里,我们创建了带有一个静态成员函数和两个静态变量的类。第二个类可以通过名字直接来调用第一个类的静态成员函数和静态变量。

classstaticClass{staticinta=42;staticintb=99;staticvoidcallme(){System.out.println("a="+a);}}

classStaticByName{publicstaticvoidmain(Stringargs[]){StaticClass.callme();System.out.println("b="+staticClass.b);}}

下面是运行结果:

C:\>javastaticByNamea=42b=99

5.16抽象

有时你需要定义一个给出抽象结构、但不给出每个成员函数的完整实现的类。如果某个成员函数没有完整实现,必须要由子类来覆盖,你可把它声明为抽象(abstract?copy;型。含有抽象型成员函数的类必须声明为抽象的。为了把一个类声明为抽象的,你只需在类定义的class关键词前放置关键词abstract。这?copy;类不能直接用new操作符生成实例,因为它们的完整实现还没有定义。你不能定义抽象的构造函数或抽象的静态成员函数。抽象类的子类或者实现了它的超类的所有抽象的成员函数,或者也被声明为抽象的。下面例子是一个带有抽象成员函数的类,其后是一个实现了该成员函数的类。

abstractclassA{abstractvoidcallme();voidmetoo(){system.out.println("在A的metoo成员函数里");}}

classBextendsA{voidcallme(){System.out.println("在B的callme成员函数里");}}

classAbstract{publicstaticvoidmain(Stringargs[]){Aa=newB();a.callme();a.metoo();}}

下面是运行结果:

C:\>javaAbstract在B的callme成员函数里在A的metoo成员函数里

本章小结

1.类是Java语言面向对象编程的基本元素,它定义了一个对象的结构和功能。2.Java通过在类定义的大括号里声明变量来把数据封装在一个类里,这里的变量称为实例变量。3.成员函数,是类的功能接口,是类定义里的一个子程序,在类的定义里和实例变量处于同一级别。

第六章Java图形用户接口

对一个优秀的应用程序来说,良好的图形用户接口是必不可少的。缺少良好的图形用户接口,将会给用户理解和使用应用程序带来很多不便。很难想象用户为了学会使用一个应用程序,去记一大堆命令。Java提?copy;了生成一个良好的图形用户接口所需要的一?copy;基本元件:面板(Panel?copy;、按钮(Button?copy;、标?copy;(Label?copy;、画板(Canvases?copy;、滚动条(Scrollbar?copy;、列表框(List?copy;、文本域(TextField?copy;、文本区(TextArea?copy;。

6.1面板

面板提?copy;了建立应用程序的空间。你可以把图形元件(包括其他面板?copy;放在一个面板上。Applet类提?copy;了一个基本的面板。

6.1.1布局管理

Java提?copy;了几种布局:顺序布局(FlowLayout?copy;、边界布局(BorderLayout?copy;和网格布局(GridLayout?copy;。

6.1.1.1顺序布局

顺序布局(FlowLayout?copy;是最基本的一种布局,面板的缺省布局就是顺序布局。顺序布局指的是把图形元件一个接一个地?reg;平地放在面板上。下面是一个顺序布局的例子:

importjava.awt.*;importjava.applet.Applet;

publicclassmyButtonsextendsApplet{Buttonbutton1,button2,button3;publicvoidinit(){button1=newButton("确定");button2=newButton("打开");button3=newButton("关闭");add(button1);add(button2);add(button3);}}

该程序生成的布局如下:

图6.1

6.1.1.2边界布局

边界布局包括五个区:北区、南区、东区、西区和中区。这几个区在面板上的分布规律是"上北下南,左西右东"。下面是一个边界布局的例子:

importjava.awt.*;importjava.applet.Applet;

publicclassbuttonDirextendsApplet{

ButtonbuttonN,buttonS,buttonW,buttonE,buttonC;

publicvoidinit(){setLayout(newBorderLayout());buttonN=newButton("?reg;");buttonS=newButton("火");buttonE=newButton("木");buttonW=newButton("金");buttonC=newButton("土");add("North",buttonN);add("South",buttonS);add("East",buttonE);add("West",buttonW);add("Center",buttonC);}}

下面是该程序运行的结果:

图6.2

6.1.1.3网格布局

网格布局把面板分成一个个的网格,你可以给出网格的行数和列数。下面是一个网格布局的例子:

importjava.awt.*;importjava.applet.Applet;

publicclassbuttonGridextendsApplet{Buttonbutton1,button2,button3,button4,button5,button6,button7,button8;

publicvoidinit(){setLayout(newGridLayout(4,2));button1=newButton("乾");button2=newButton("坤");button3=newButton("艮");button4=newButton("震");button5=newButton("坎");button6=newButton("离");button7=newButton("巽");button8=newButton("兑");add(button1);add(button2);add(button3);add(button4);add(button5);add(button6);add(button7);add(button8);}}

下面是该程序运行的结果:

图6.3

6.2按钮

6.2.1按钮事件

用户点一下按钮,就会有一个按钮事件发生。你可以通过覆盖一个applet的action成员函数来捕捉按钮事件。

publicbooleanaction(Evente,Objecto){if(e.targetinstanceofButton){system.out.println((string)o);}else{System.out.println("Non-buttonevent");}returntrue;}

6.2.2按钮类型

Java提?copy;了标准的按压式按钮,同时也提?copy;了选择式按钮和标记式按钮。

6.2.2.1选择式按钮

选择式按钮提?copy;了从几个选项中选一个选项的功能。下面是从几个市中选一个市的例子,市名放在选择式按钮中:

CityChooser=newChoice();

CityChooser.addItem("北?copy;");CityChooser.addItem("上海");CityChooser.addItem("天津");

add(CityChooser);

图6.4

6.2.2.2标记式按钮

标记式按钮的状态作为标记框事件的对象参数返回。下面是一个标记式按钮的例子:

CheckboxfillStyleButton;fillStyleButton=newCheckbox("Solid");

publicbooleanaction(Evente,Objectarg){if(e.targetinstanceofCheckbox){System.out.println("Checkbox:"+arg);}returntrue;}

图6.5

6.2.2.3按键式按钮

按键式按钮是一组按钮,用户可以选中其中一个,同时这一组中的其他按钮将被关闭。下面是一个按键式按钮的例子:publicclassCheckBoxextendsApplet{CheckboxGroupcbg;

publicvoidinit(){cbg=newCheckboxGroup();add(newCheckbox("one",cbg,true));add(newCheckbox("two",cbg,false));add(newCheckbox("three",cbg,false));}}

图6.6

6.2.3自包含按钮

Java语言的面向对象特性使我们能够创建完全自包含的按钮。在自包含按钮里,你可以在?copy;展按钮类里建立事件控制函数。下面是一个自包含按钮的例子:

importjava.awt.*;importjava.applet.Applet;

classokButtonextendsButton{

publicokButton(){setLabel("Ok");}

publicbooleanaction(Evente,Objectarg){System.out.println("OKButton");returntrue;}}

publicclassbuttontestextendsApplet{okButtonmyOkButton;

publicvoidinit(){myOkButton=newokButton();add(myOkButton);}}

图6.7

6.3标?copy;

标?copy;是一种放到面板上的静止的正文。下面是一个标?copy;的例子:importjava.awt.*;importjava.applet.Applet;

publicclasslabelextendsApplet{

publicvoidinit(){setLayout(newFlowLayout(FlowLayout.CENTER,10,10));Labellabel1=newLabel("你好!");Labellabel2=newLabel("另一个标?copy;");add(label1);add(label2);}}

下面是运行结果:

图6.8

6.4列表框

列表框使用户易于操作大量的选项。创建列表框的方法和Choicebutton有?copy;相似。列表框的所有条目都是可见的,如果选项很多,超出了列表框可见区的范围,则列表框的旁边将会有一个滚动条。首先,创建列表框:Listl=newList(4,false);这个成员函数创建了一个显示4行的列表框。第二个参数"false"表示这个列表框是单选的,如果是"true",则表示是多选的。下面增加列表框的选项:

l.addItem("北?copy;大学");l.addItem("清华大学");l.addItem("吉林大学");l.addItem("复?copy;大学");l.addItem("南开大学");l.addItem("天津大学");l.addItem("南?copy;大学");add(l);

图6.9

6.4.1在列表框中进行选择

可以用成员函数getSelectedItem()或getSelectedItems()来接收在列表框中被选的选项。在单选列表框里,"双击"一个选项就可以触发一个可被action()成员函数捕捉到的事件。publicbooleanaction(Evente,Objectarg){...if(e.targetinstanceofList){System.out.println("Listentry:"+arg);}...}

6.4.2多选列表框

对于多选列表框,要使你的选择产生作用,需要使用其他的外部事件。例如,你可以使用按钮事件:

图6.10

publicbooleanaction(Evente,Objectarg){...if(e.targetinstanceofButton){...if("Ok".equals(arg)){string[]selected;selected=l.getSelectedItems();for(intI=0;I<selected.length;I++){System.out.println(selected[i]);}}}}

6.5文本域

文本域一般用来让用户输入象姓名、信用卡号这样的信息,它是一个能够接收用户的键盘输入的小块区域。

6.5.1创建文本域

在创建文本域时,有四种类型?copy;你选择:空的、空的并且具有指定长度、带有初始文本内容的和带有初始文本内容并具有指定长度的。下面是生成这四种文本域的代码:

TextFieldtf1,tf2,tf3,tf4;

//空的文本域tf1=newTextField();//长度为20的空的文本域tf2=newTextField(20);//带有初始文本内容的文本域tf3=newTextField("你好");//带有初始文本内容并具有指定长度的文本域tf4=newTextField("你好",30);add(tf1);add(tf2);add(tf3);add(tf4);

图6.11

6.5.2文本域事件

当用户在文本域里敲"回车"键时,就产生了一个文本域事件。象其他事件一样,你可以以在成员函数action()中捕捉到这个事件。

publicbooleanaction(Evente,Objectarg){...if(e.targetinstanceofTextField){System.out.println("TextField:"+arg);}...}

6.6文本区

文本区可以显示大段的文本。

6.6.1创建文本区

与文本域类似,创建文本区时也有四种类型?copy;选择,但如果指定文本区的大小,必须同时指定行数和列数。

TextAreata1,ta2;//一个空的文本区ta1=newTextArea();

//一个带有初始内容、大小为5x40的文本区ta2=newTextArea("你好!",5,40);

可以用成员函数setEditable()来决定用户是否可对文本区的内容进行编辑。

//使文本区为只读的ta2.setEditable(false)

图6.12

6.6.2接收文本区的内容

可以用成员函数getText()来获得文本区的当前内容。例如:System.out.println(ta1.getText());文本区本身不产生自己的事件。但你可以用外部事件来接收文本区的内容:

publicbooleanaction(Evente,Objecto){if(e.targetinstanceofButton){if("send".equals(o)){StringtextToSend=ta1.getText();System.out.println("sending:"+textTosend);mySendFunction(textToSend);}}else{...}}

6.7画板

画板能够捕捉到?copy;露事件、鼠标事件和其他类似的事件。基本的画板类不处理这?copy;事件,但你可以?copy;展它来创建有你所需功能的画板类。

6.7.1创建画板

importjava.awt.*;importjava.applet.Applet;

publicclasssuperGUIextendsApplet{...myCanvasdoodle;...publicvoidinit(){...//建立我们的画板doodle=newmyCanvas();doodle.reshape(0,0,100,100);leftPanel.add("Center",doodle);...}}

classmyCanvasextendsCanvas{publicvoidpaint(Graphicsg){g.drawRect(0,0,99,99);g.drawString("Canvas",15,40);}}

6.7.2画板事件

你可以覆盖一般的事件处理成员函数。下面是一个包含了mouseDown事件处理的例子:

importjava.awt.*;importjava.applet.Applet;

publicclasscanvasextendsApplet{

Buttonb1;

publicvoidinit(){//SetourlayoutasaBorderstylesetLayout(newBorderLayout(15,15));b1=newButton("Test");myCanvasc1=newmyCanvas(100,100);//addthecanvasandthebuttontotheappletadd("Center",c1);add("South",b1);}

publicbooleanaction(Evente,Objectarg){System.out.println("Event:"+arg);returntrue;}

publicbooleanmouseDown(Evente,intx,inty){System.out.println("Mouseworks:("+x+","+y+")");returntrue;}}

classmyCanvasextendsCanvas{privateintwidth;privateintheight;

publicmyCanvas(intw,inth){width=w;height=h;reshape(0,0,w,h);}

publicvoidpaint(Graphicsg){g.setColor(Color.blue);g.fillRect(0,0,width,height);}

publicbooleanmouseDown(Evente,intx,inty){if((x<width)&&(y<height)){System.out.println("Canvasmouseworks:("+x+","+y+")");returntrue;}returnfalse;//NotourmouseDown}}

6.8滚动条

在某?copy;程序中,需要调整线性的值,这时就需要滚动条。滚动条提?copy;了易于操作的值的范围或区的范围。

6.8.1创建滚动条

当创建一个滚动条时,必须指定它的方向、初始值、滑块的大小、最小值和最大值。

publicScrollbar(intorientation,intinitialValue,intsizeOfSlider,intminValue,intmaxValue);

下面是一个例子:

ScrollbarredSlider;publicvoidinit(){redSlider=newScrollbar(Scrollbar.VERTICAL,0,1,0,255);add(redSlider);}

图6.13

6.8.2滚动条事件

和其他接口元件一样,滚动条产生一个你可以控制的事件,但和其他事件不同,你必须直接使用成员函数handleEvent(),而不能使用成员函数action().

publicbooleanhandleEvent(Evente){if(e.targetinstanceofScrollbar){System.out.println("Scrollbar:"+((Scrollbar)e.target).getValue());returntrue;}returnsuper.handleEvent(e);}

6.8.3滚动条的值的显示

如果你想显示滑块所在位置的值,需要加一个自己的文本域。下面是一个例子。

importjava.awt.*;importjava.applet.Applet;

publicclassredSliderextendsApplet{Scrollbarredslider;TextFieldredvalue;Labelredlabel;

publicvoidinit(){setLayout(newGridLayout(1,3));redslider=newScrollbar(Scrollbar.HORIZONTAL,0,1,0,255);redvalue=newTextField("0",5);redvalue.setEditable(false);redlable=newLabel("Red(0-255)");add(redlabel);add(redslider);add(redvalue);}

publicbooleanhandleEvent(Evente){if(e.targetinstanceofScrollbar){redvalue.setText(Integer.toString(((Scrollbar)e.target).getValue()));returntrue;}returnsuper.handleEvent(e);}

publicbooleanaction(Evente,Objectarg){System.out.println("Event"+arg);returntrue;}}

图6.14

本章小结

1.Java提?copy;了生成一个良好的图形用户接口所需要的一?copy;基本元件:面板(Panel?copy;、按钮(Button?copy;、标?copy;(Label?copy;、画板(Canvases?copy;、滚动条(Scrollbar?copy;、列表框(List?copy;、文本域(TextField?copy;、文本区(TextArea?copy;。2.大部分元件都有自己的事件,你可以捕捉并处理它们。

第七章多线程

7.1多线程的概念

多线程编程的含义是你可将程序任务分成几个并行的子任务。特别是在网络编程中,你会发现很多功能是可以并发执行的。比如网络传输速度较慢,用户输入速度较慢,你可以用两个独立的线程去完成这?copy;功能,而不影响正常的显示或其他功能。多线程是与单线程比较而言的,普通的WINDOWS采用单线程程序结构,其工作原理是:主程序有一个消息循环,不断从消息队列中读入消息来决定下一步所要干的事情,一般是一个子函数,只有等这个子函数执行完返回后,主程序才能接收另外的消息来执行。比如子函数功能是在读一个网络数据,或读一个文件,只有等读完这?copy;数据或文件才能接收下一个消息。在执行这个子函数过程中你什么也不能干。但往往读网络数据和等待用户输入有很多时间处于等待状态,多线程利用这个特点将任务分成多个并发任务后,就可以解决这个问题。

7.1.1Java线程的模型

Java的设计思想是建立在当前大多数操作系统都实现了线程调度。Java虚拟机的很多任务都依赖线程调度,而且所有的类库都是为多线程设计的。实时上,Java支持Macintosh和Ms-dos的平台?reg;所以迟迟未出来就是因为这两个平台都不支持多线程。Java利用多线程实现了整个执行环境是异步的。在Java程序里没有主消息循环。如果一个线程等待读取网络数据,它可以运行但不停止系统的其他线程执行。用于处理用户输入的线程大多时间是等待用户敲键盘或击鼠标。你还可以使动画的每一帧?reg;间停顿一秒而并不使系统暂停。一?copy;线程启动后,它可以被挂起,暂时不让它执行。挂起的线程可以重新恢复执行。任何时间线程都可以被停止,被停止的线程就不能再重新启动。Java语言里,线程表现为线程类,线程类封装了所有需要的线程操作控制。在你心里,必须很清晰地区分开线程对象和运行线程,你可以将线程对象看作是运行线程的控制面板。在线程对象里有很多函数来控制一个线程是否运行,睡眠,挂起或停止。线程类是控制线程行为的唯一的手段。一?copy;一个Java程序启动后,就已经有一个线程在运行。你可通过调用Thread.currentThread函数来查看当前运行的是哪一个线程。一?copy;你得到一个线程的控制柄,你就可以作一?copy;很有趣的事情,即使单线程也一样。下面这个例子让你知道怎样操纵当前线程。Filename:testthread

classtestthread{publicstaticvoidmain(Stringargs[]){Threadt=Thread.currentThread();t.setName("ThisThreadisrunning");System.out.println("Therunningthread:"+t);try{for(inti=0;i<5;i++){System.out.println("Sleeptime"+i);Thread.sleep(1000);}

}catch(InterruptedExceptione){System.out.println("threadhaswrong");}

}}

执行结果:javatestthreadTherunningthread:Thread[ThisThreadisrunning,5,main]Sleeptime0Sleeptime1Sleeptime2Sleeptime3Sleeptime4

7.1.2启动接口

一个线程并不激动人心,多个线程才有实际意义。我们怎样创建更多的线程呢?我们需要创建线程类的另一个实例。当我们构造了线程类的一个新的实例,我们必须告诉它在新的线程里应执行哪一段程序。你可以在任意实现了启动接口的对象上启动一个线程。启动接口是一个抽象接口,来表示本对象有一?copy;函数想异步执行。要实现启动接口,一个类只需要有一个叫run的函数。下面是创建一个新线程的例子:

Filename:twothread.java

classtwothreadimplementsRunnable{twothread(){Threadt1=Thread.currentThread();t1.setName("Thefirstmainthread");System.out.println("Therunningthread:"+t1);Threadt2=newThread(this,"thesecondthread");System.out.println("creatanotherthread");t2.start();try{System.out.println("firstthreadwillsleep");Thread.sleep(3000);}catch(InterruptedExceptione){System.out.println("firstthreadhaswrong");}System.out.println("firstthreadexit");}publicvoidrun(){try{for(inti=0;i<5;i++){System.out.println("Sleeptimeforthread2:"+i);Thread.sleep(1000);}

}catch(InterruptedExceptione){System.out.println("threadhaswrong");}

System.out.println("secondthreadexit");}publicstaticvoidmain(Stringargs[]){newtwothread();}}

执行结果:javatwothread

Therunningthread:Thread[Thefirstmainthread,5,main]creatanotherthreadfirstthreadwillsleepSleeptimeforthread2:0Sleeptimeforthread2:1Sleeptimeforthread2:2firstthreadexitSleeptimeforthread2:3Sleeptimeforthread2:4secondthreadexit

main线程用newThread(this,"thesecondthread")创建了一个Thread对象,通过传递第一个参数来标明新线程来调用this对象的run函数。然后我们调用start函数,它将使线程从run函数开始执行。

7.1.3同步

因为多线程给你提?copy;了程序的异步执行的功能,所以在必要时必须还提?copy;一种同步机制。例如,你想两个线程通讯并共享一个复杂的数据结构,你需要一种机制让他们相互牵制并正确执行。为这个目的,Java用一种叫监视器(monitor)的机制实现了进程间的异步执行。可以将监视器看作是一个很小的盒子,它只能容纳一个线程。一?copy;一个线程进入一个监视器,所有其他线程必须等到第一个线程退出监视器后才能进入。这?copy;监视器可以设计成保护共享的数据不被多个线程同时操作。大多数多线程系统将这?copy;监视器设计成对象,Java提?copy;了一种更清晰的解决方案。没有Monitor类;每个对象通过将他们的成员函数定义成synchronized来定义自己的显式监视器,一?copy;一个线程执行在一个synchronized函数里,其他任何线程都不能调用同一个对象的synchronized函数。

7.1.4消息

一?copy;你的程序被分成几个逻辑线程,你必须清晰的知道这?copy;线程?reg;间应怎样相互通讯。Java提?copy;了wait和notify等功能来使线程?reg;间相互交谈。一个线程可以进入某一个对象的synchronized函数进入等待状态,直到其他线程显式地将它唤醒。可以有多个线程进入同一个函数并等待同一个唤醒消息。

7.2Java线程例子

7.2.1显式定义线程

在我们的单线程应用程序里,我们并没有看见线程,因为Java能自动创建和控制你的线程。如果你使用了理解Java语言的浏览器,你就已经看到使用多线程的Java程序了。你也许注意到两个小程序可以同时运行,或在你移动滚动条时小程序继续执行。这并不是表明小程序是多线程的,但说明这个浏览器是多线程的。多线程应用程序(或applet)可以使用好几个执行上下文来完成它们的工作。多线程利用了很多任务包含单独的可分离的子任务的特点。每一个线程完成一个子任务。

但是,每一个线程完成子任务时还是顺序执行的。一个多线程程序允许各个线程尽快执行完它们。这种特点会有更好的实时输入反应。

7.2.2多线程例子

下面这个例子创建了三个单独的线程,它们分别打印自己的"HelloWorld":

//Defineoursimplethreads.Theywillpauseforashorttime//andthenprintouttheirnamesanddelaytimesclassTestThreadextendsThread{privateStringwhoami;privateintdelay;

//Ourconstructortostorethename(whoami)//andtimetosleep(delay)publicTestThread(Strings,intd){whoami=s;delay=d;}

//Run-thethreadmethodsimilartomain()//Whenrunisfinished,thethreaddies.//Runiscalledfromthestart()methodofThreadpublicvoidrun(){//Trytosleepforthespecifiedtimetry{sleep(delay);}catch(InterruptedExceptione){}//NowprintoutournameSystem.out.println("HelloWorld!"+whoami+""+delay);}}/***Multimtest.Asimplemultithreadthestprogram*/publicclassmultitest{publicstaticvoidmain(Stringargs[]){TestThreadt1,t2,t3;//Createourtestthreadst1=newTestThread("Thread1",(int)(Math.readom()*2000));t2=newTestThread("Thread2",(int)(Math.readom()*2000));t3=newTestThread("Thread3",(int)(Math.readom()*2000));

//Starteachofthethreadst1.start();t2.start();t3.start();}}

7.2.3启动一个线程

程序启动时总是调用main()函数,因此main()是我们创建和启动线程的地方:

t1=newTestThread("Thread1",(int)(Math.readom()*2000));

这一行创建了一个新的线程。后面的两个参数传递了线程的名称和线程在打印信息?reg;前的延时时间。因为我们直接控制线程,我们必须直接启动它:t1.start();

7.2.4操作线程

如果创建线程正常,t1应包含一个有效的执行线程。我们在线程的run()函数里控制线程。一?copy;我们进入run()函数,我们便可执行里面的任何程序。run()好象main()一样。一?copy;run()执行完,这个线程也就结束了。在这个例子里,我们试着延迟一个随机的时间(通过参数传递?copy;:sleep(delay);

sleep()函数只是简单地告诉线程休息多少个毫秒时间。如果你想推迟一个线程的执行,你应使用sleep()函数。当线程睡眠是sleep()并不占用系统资源。其它线程可继续工作。一?copy;延迟时间完毕,它将打印"HelloWorld"和线程名称及延迟时间。

7.2.5暂停一个线程

我们经常需要挂起一个线程而不指定多少时间。例如,如果你创建了一个含有动画线程的小程序。也许你让用户暂停动画至到他们想恢复为止。你并不想将动画线程仍调,但想让它停止。象这种类似的线程你可用suspend()函数来控制:t1.suspend();

这个函数并不永久地停止了线程,你还可用resume()函数重新激活线程:t1.resume();

7.2.6停止一个线程

线程的最后一个控制是停止函数stop()。我们用它来停止线程的执行:t1.stop();

注意:这并没有消灭这个线程,但它停止了线程的执行。并且这个线程不能用t1.start()重新启动。在我们的例子里,我们从来不用显式地停止一个线程。我们只简单地让它执行完而已。很多复杂的线程例子将需要我们控制每一个线程。在这种情况下会使用到stop()函数。如果需要,你可以测试你的线程是否被激活。一个线程已经启动而且没有停止被认为是激活的。t1.isAlive()如果t1是激活的,这个函数将返回true.

7.2.7动画例子

下面是一个包含动画线程的applet例子:

importjava.awt.*;importjava.awt.image.ImageProducer;importjava.applet.Applet;

publicclassatest3extendsAppletimplementsRunnable{Imageimages[];MediaTrackertracker;intindex=0;Threadanimator;

intmaxWidth,maxHeight;//Ouroff-screencomponentsfordoublebuffering.ImageoffScrImage;GraphicsoffScrGC;

//Canwepaintyes?booleanloaded=false;

//Initializetheapplet.Setoursizeandloadtheimagespublicvoidinit()[//Setupourimagemonitortracker=newMediaTracker(this);

//SetthesizeandwidthofourappletmaxWidth=100;maxHeight=100;

images=newImage[10];//Setupthedouble-bufferandresizeourapplettry{offScrImage=createImage(maxWidth,maxHeight);offScrGC=offScrImage.getGraphics();offScrGC.setColor(Color.lightGray);offScrGC.fillRect(0,0,maxWidth,maxHeight);resize(maxWidth,maxHeight);}catch(Exceptione){e.printStackTrace();}

//loadtheanimationimagesintoanarrayfor(inti=0;i<10;i++){StringimageFile=newString("images/Duke/T"+String.valueOf(i+1)+".gif");images[i]=getImage(getDocumentBase(),imageFile)://Registerthisimagewiththetrackertracker.addImage(images[i],i);}try{//Usetrackertomakesurealltheimagesareloadedtracker.waitForAll();}catch(InterruptedExceptione){}loaded=true;}

//Paintthecurrentframe.publicvoidpaint(Graphicsg){if(loaded){g.drawImage(offScrImage,0,0,this);}}

//Start,setupourfirstimagepublicvoidstart(){if(tracker.checkID(index)){offScrGC.drawImage(images[index],0,0,this);}animator=newThread(this);animator.start();}

//Run,dotheanimationworkhere.//Grabanimage,pause,grabthenext...publicvoidrun(){//GettheidofthecurrentthreadThreadme=Thread.currentThread();

//Ifouranimatorthreadexist,andisthecurrentthread...while((animatr!=null)&&(animator==me)){if(tracker.checkID(index)){//ClearthebackgroundandgetthenextimageoffScrGC.fillRect(0,0,100,100);offScrGCdrawImage(images[index],0,0,this);index++;//Loopbacktothebeginningandkeepgoingif(index>=images.length){index=0;}}//Delayheresoanimationlooksnormaltry{animator.sleep(200);}catch(InterruptedExceptione){}//Drawthenextframerepaint();}}}

7.3多线程?reg;间的通讯

7.3.1生产者和消费者

多线程的一个重要特点是它们?reg;间可以互相通讯。你可以设计线程使用公用对象,每个线程都可以独立操作公用对象。典型的线程间通讯建立在生产者和消费者模型上:一个线程产生输出;另一个线程使用输入。

buffer

让我们创建一个简单的"AlphabetSoup"生产者和相应的消费者.

7.3.2生产者

生产者将从thread类里派生:classProducerextendsThread{privateSoupsoup;privateStringalphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ";

publicProducer(Soups){//Keepourowncopyofthesharedobjectsoup=s;}

publicvoidrun(){charc;//Throw10lettersintothesoupfor(inti=0;i<10;i++){c=alphabet.charAt((int)(Math.random()*26));soup.add(c);//printarecordofosradditionSystem.out.println("Added"+c+"tothesoup.");//waitabitbeforeweaddthenextlettertry{sleep((int)(Math.random()*1000));}catch(InterruptedExceptione){}}}}

注意我们创建了Soup类的一个实例。生产者用soup.add()函数来建立字符池。

7.3.3消费者

让我们看看消费者的程序:classConsumerextendsThread{privateSoupsoup;

publicConsumer(Soups){//keepourowncopyofthesharedobjectsoup=s;}

publicvoidrun(){charc;//Eat10lettersfromthealphabetsoupfor(intI=0;i<10;i++){//graboneletterc=soup.eat();//PrintouttheletterthatweretrievedSystem.out.println("Atealetter:"+c);//try{sleep((int)(Math.raddom()*2000));}catch(InterruptedExceptione){}}}}

同理,象生产者一样,我们用soup.eat()来处理信息。那么,Soup类到底干什么呢?

7.3.4监视

Soup类执行监视两个线程?reg;间传输信息的功能。监视是多线程中不可缺少的一部分,因为它保持了通讯的流?copy;。让我们看看Soup.java文件:classSoup{privatecharbuffer[]=newchar[6];privateintnext=0;//FlagstokeeptrackofourbufferstatusprivatebooleanisFull=false;privatebooleanisEmpty=true;

publicsyschronizedchareat(){//Wecan'teatifthereisn'tanythinginthebufferwhile(isEmpty==true){try{wait();//we'llexitthiswhenisEmptyturnsfalse}catch(InterruptedExceptione){}}//decrementthecount,sincewe'regoingtoeatoneletternext--;//Didweeatthelastletter?if(next==0){isEmpty=true;}//Weknowthebuffercan'tbefull,becausewejustateisFull=false;notify();//returnthelettertothethreadthatiseatingreturn(buffer[next]);}

//methodtoaddletterstothebufferpublicsynchronizedvoidadd(charc){//Waitarounduntilthere'sroomtoaddanotherletterwhile(isFull==true){try{wait();//ThiswillexitwhenisFullturnsfalse}catch(InterruptedExceptione){}}//addthelettertothenextavailablespotbuffer[next]=c;//Changethenextavailablespotnext++;//Arewefull;if(next==6){isFull=true;}isEmpty=false;notify();}}

soup类包含两个重要特征:数据成员buffer[]是私有的,功能成员add()和eat()是公有的。

数据私有避免了生产者和消费者直接获得数据。直接访问数据可能造成错误。例如,如果消费者企图从空缓冲区里取出数据,你将得到不必要的异常,否则,你只能锁住进程。同步访问方法避免了破坏一个共享对象。当生产者向soup里加入一个字母时,消费者不能吃字符,诸如此类。这种同步是维持共享对象完整性的重要方面。notify()函数将唤醒每一个等待线程。等待线程将继续它的访问。

7.3.5联系起来

现在我们有一个生产者,一个消费者和一个共享对象,怎样实现它们的交互呢?我们只需要一个简单的控制程序来启动所有的线程并确信每一个线程都是访问的同一个共享对象。下面是控制程序的代码,SoupTest.java:classSoupTest{publicstaticvoidmain(Stringargs[]){Soups=newSoup();Producerp1=newProducer(s);Consumerc1=newConsumer(s);

p1.start();c1.start();}}

7.3.6监视生产者

生产者/消费者模型程序经常用来实现远程监视功能,它让消费者看到生产者同用户的交互或同系统其它部分的交互。例如,在网络中,一组生产者线程可以在很多工作站上运行。生产者可以打印文档,文档打印后,一个标志将保存下来。一个(或多个?copy;消费者将保存标志并在晚上报告白天打印活动的情况。另外,还有例子在一个工作站是分出几个独立的窗口。一个窗口用作用户输入(生产者?copy;,另一个窗口作出对输入的反应(消费者?copy;。

7.4线程API列表

下面是一?copy;常用的线程类的方法函数列表:

类函数:以下是Thread的静态函数,即可以直接从Thread类调用。

currentThread返回正在运行的Thread对象yield停止运行当前线程,让系统运行下一个线程sleep(intn)让当前线程睡眠n毫秒

对象函数:以下函数必须用Thread的实例对象来调用。

startstart函数告诉java运行系统为本线程建立一个执行环境,然后调用本线程的run()函数。run是运行本线程的将要执行的代码,也是Runnable接口的唯一函数。当一个线程初始化后,由start函数来调用它,一?copy;run函数返回,本线程也就终止了。stop让某线程马上终止,系统将删除本线程的执行环境suspend与stop函数不同,suspend将线程暂停执行,但系统不破坏线程的执行环境,你可以用resume来恢复本线程的执行resume恢复被挂起的线程进入运行状态setPriority(intp)给线程设置优先级getPriority返回线程的优先级setName(Stringname)给线程设置名称getName取线程的名称

本章小结:

1.多线程是java语言的重要特点,java语言用Thread类封装了线程的所有操作。2.线程的接口名为Runnable3.线程?reg;间同步机制为synchronized关键词4.线程?reg;间通讯靠wait与notify消息

第八章Java的"异常"

"异常"指的是程序运行时出现的非正常情况。在用传统的语言编程时,程序员只能通过函数的返回值来发出错误信息。这易于导致很多错误,因为在很多情况下需要知道错误产生的内部细节。通常,用全局变量errno来存储"异常"的类型。这容易导致误用,因为一个errno的值有可能在被处理?reg;前被另外的错误覆盖掉。即使最优美的C语言程序,为了处理"异常"情况,也常求助于goto语句。Java对"异常"的处理是面向对象的。一个Java的Exception是一个描述"异常"情况的对象。当出现"异常"情况时,一个Exception对象就产生了,并放到产生这个"异常"的成员函数里。

8.1基础

Java的"异常"处理是通过5个关键词来实现的:try,catch,throw,throws和finally。用try来执行一段程序,如果出现"异常",系统抛出(throws?copy;一个"异常",你可以通过它的类型来捕捉(catch?copy;它,或最后(finally?copy;由缺省处理器来处理。下面是"异常"处理程序的基本形式:

try{//程序块}catch(ExceptionType1e){//对ExceptionType1的处理}catch(ExceptionType2e){//对ExceptionType2的处理throw(e);//再抛出这个"异常"}finally{}

8.2"异常"的类型

在"异常"类层次的最上层有一个单独的类叫做Throwable。这个类用来表示所有的"异常"情况。每个"异常"类型都是Throwable的子类。Throwable有两个直接的子类。一类是Exception,是用户程序能够捕捉到的"异常"情况。我们将通过产生它的子类来创建自己的"异常"。另一类是Error,它定义了那?copy;通常无法捕捉到的"异常"。要谨慎使用Error子类,因为它们通常会导致灾难性的失败。在Exception中有一个子类RuntimeException,它是程序运行时自动地对某?copy;错误作出反应而产生的。

8.3不捕捉"异常"

"异常"对象是Java在运行时对某?copy;"异常"情况作出反应而产生的。例如,下面这个小程序包含一个整数被0除的"异常"。

classExc0{publicstaticvoidmain(Stringargs[]){intd=0;inta=42/d;}}

当Java执行这个除法时,由于分母是0,就会构造一个"异常"对象来使程序停下来并处理这个错误情况,在运行时"抛出"(throw?copy;这个"异常"。说"抛出"是因为它象一个滚烫的马铃薯,你必须把它抓住并立即处理。程序流将会在除号操作符处被打断,然后检查当前的调用堆栈来查找"异常"。一个"异常"处理器是用来立即处理"异常"情况的。在这个例子里,我们没有编一个"异常"处理器,所以缺省的处理器就发挥作用了。缺省的处理器打印Exception的字符?reg;值和发生"异常"的地点。下面是我们的小例子的输出。

C:\>javaExc0java.lang.arithmeticException:/byzeroatExc0.main(Exc0.java:4)

8.4try与catch

通常我们希望自己来处理"异常"并继续运行。可以用try来指定一块预防所有"异常"的的程序。紧跟在try程序后面,应包含一个catch子句来指定你想要捕捉的"异常"的类型。例如,下面的例子是在前面的例子的基础上构造的,但它包含一个try程序块和一个catch子句。

classexc1{publicstaticvoidmain(stringargs[]){try{intd=0;inta=42/d;}catch(arithmeticexceptione){system.out.println("divisionbyzero");}}}

catch子句的目标是解决"异常"情况,把一?copy;变量设到合理的状态,并象没有出错一样继续运行。如果一个子程序不处理某个"异常",则返到上一级处理,直到最外一级。

8.5多个catch子句

在某?copy;情况下,同一段程序可能产生不止一种"异常"情况。你可以放置多个catch子句,其中每一种"异常"类型都将被检查,第一个与?reg;匹配的就会被执行。如果一个类和其子类都有的话,应把子类放在前面,否则将永远不会到达子类。下面是一个有两个catch子句的程序的例子。

classMultiCatch{publicstaticvoidmain(Stringargs[]){try{inta=args.length;System.out.println("a="+a);intb=42/a;intc[]={1};c[42]=99;}catch(ArithmeticExceptione){System.out.println("divby0:"+e);}catch(ArrayIndexOutOfBoundsExceptione){system.out.println("arrayindexoob:"+e);}}}

如果在程序运行时不跟参数,将会引起一个0做除数的"异常",因为a的值为0。如果我们提?copy;一个命令行参数,将不会产生这个"异常",因为a的值大于0。但会引起一个ArrayIndexOutOfBoundexception的"异常",因为整型数组c的长度是1,却给c[42]赋值。下面是以上两种情况的运行结果。

C:\>javaMultiCatcha=0divby0:java.lang.arithmeticexception:/byzeroC:\>javaMutiCatch1a=1arrayindexoob:java.lang.ArrayIndexOutOfBoundsException:42

8.6try语句的嵌套

你可以在一个成员函数调用的外面写一个try语句,在这个成员函数内部,写另一个try语句保护其他代码。每当遇到一个try语句,"异常"的框架就放到堆栈上面,直到所有的try语句都完成。如果下一级的try语句没有对某种"异常"进行处理,堆栈就会展开,直到遇到有处理这种"异常"的try语句。下面是一个try语句嵌套的例子。

classMultiNest{staticvoidprocedure(){try{intc[]={1}:c[42]=99;}catch(ArrayIndexOutOfBoundsexceptione){System.out.println("arrayindexoob:"+e);}}publicstaticvoidmain(Stringargs[]){try{inta=args.length;system.out.println("a="+a);intb=42/a;procedure();}catch(arithmeticExceptione){System.out.println("divby0:"+e);}}}

成员函数procedure里有自己的try/catch控制,所以main不用去处理ArrayIndexOutOfBoundsException。

8.7throw语句

throw语句用来明确地抛出一个"异常"。首先,你必须得到一个Throwable的实例的控制柄,通过参数传到catch子句,或者用new操作符来创建一个。下面是throw语句的通常形式。

throwThrowableInstance;

程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中从里向外寻找含有与其匹配的catch子句的try块。下面是一个含有throw语句的例子。

classThrowDemo{staticvoiddemoproc(){try{thrownewNullPointerException("de3mo");}catch(NullPointerExceptione){System.out.println("caughtinsidedemoproc");throwe;}}publicstaticvoidmain(Stringargs[]){try{demoproc();}catch(NullPointerExceptione){system.out.println("recaught:"+e);}}}

8.8throws语句

throws用来标明一个成员函数可能抛出的各种"异常"。对大多数Exception子类来说,Java编译器会强迫你声明在一个成员函数中抛出的"异常"的类型。如果"异常"的类型是Error或RuntimeException,或它们的子类,这个规则不起作用,因为这?copy;在程序的正常部分中是不期待出现的。如果你想明确地抛出一个RuntimeException,你必须用throws语句来声明它的类型。这就重新定义了成员函数的定义语法:

typemethod-name(arg-list)throwsexception-list{}

下面是一段程序,它抛出了一个"异常",但既没有捕捉它,也没有用throws来声明。这在编译时将不会通过。

classThrowsDemo1{staticvoidprocedure()[System.out.println("insideprocedure");thrownewIllegalAccessException("demo");}publicstaticvoidmain(Stringargs[]){procedure();}}

为了让这个例子编译过去,我们需要声明成员函数procedure抛出了IllegalAccessException,并且在调用它的成员函数main里捕捉它。下面是正确的例子:

classThrowsDemo{staticvoidprocedure()throwsIllegalAccessException{System.out.println("insideprocedure");thrownewIllegalAccessException("demo");}publicstaticvoidmain(Stringargs[]){try{procedure();}catch(IllegalAccessExceptione){System.out.println("caught"+e);}}}

下面是输出结果:

C:\>javaThrowsDemoinsideprocedurecaughtjava.lang.IllegalAccessException:demo

8.9finally

当一个"异常"被抛出时,程序的执行就不再是线性的,跳过某?copy;行,甚至会由于没有与?reg;匹配的catch子句而过早地返回。有时确保一段代码不管发生什么"异常"都被执行到是必要的,关键词finally就是用来标识这样一段代码的。即使你没有catch子句,finally程序块也会在执行try程序块后的程序?reg;前执行。每个try语句都需要至少一个与?reg;相配的catch子句或finally子句。一个成员函数返回到调用它的成员函数,或者通过一个没捕捉到的"异常",或者通过一个明确的return语句,finally子句总是恰好在成员函数返回前执行。下面是一个例子,它有几个成员函数,每个成员函数用不同的途径退出,但执行了finally子句。

classFinallyDemo{staticvoidprocA(){try{System.out.println("insideprocA");thrownewRuntimeException("demo");}finally{System.out.println("procA'sfinally");}}staticvoidprocB(){try{System.out.println("insideprocB");return;}finally{System.out.println("procB'sfinally");}}publicstaticvoidmain(Stringargs[]){try{procA();}catch(Exceptione);procB();}}

下面是这个例子的运行结果:

C:\>javaFinallyDemoinsideprocAprocA'sfinallyinsideprocBprocB'sfinally

本章小结

1."异常"指的是程序运行时出现的非正常情况。2.在"异常"类层次的最上层的类叫Throwable,它有两个直接的子类:Exception和Error。3.Java的"异常"处理通过5个关键词来实现:try,catch,throw,throws和finally。

第九章Java输入输出操作

9.1Java输入输出流

所有的程序语言都提?copy;与本机文件系统交互的方式;Java也不例外。我们将看看Java是怎样处理标准文件输入输出的(包括stdin,stout,stderr)。当你在网络上开发小程序时,你必须注意直接文件输入输出是不安全因素的关键。大多数用户设置他们的浏览器,可让你自由的访问他们的文件系统,但有?copy;不让你访问。当然,如果你开发你内部的应用程序,你也许需要直接访问文件。

标准输入输出Unix的用户,或其他基于命令行系统的用户(如DOS),都知道标准输入输出的含义。标准输入文件是键盘,标准输出文件是你的终端屏幕。标准错误输出文件也指向屏幕,如果有必要,它也可以指向另一个文件以便和正常输出区分。

系统类Java通过系统类达到访问标准输入输出的功能。上面提到的三个文件在这个系统类中实现:StdinSystem.in作为InputStream类的一个实例来实现stdin,你可以使用read()和skip(longn)两个成员函数。read()让你从输入中读一个字节,skip(longn)让你在输入中跳过n个字节。

StoutSystem.out作为PrintStream来实现stdout,你可以使用print()和println()两个成员函数。这两个函数支持Java的任意基本类型作为参数。

StderrSystem.err同stdout一样实现stderr。象System.out一样,你可以访问PrintStream成员函数。

9.2标准输入输出例子

这里有一个例子,功能象Unix里的cat或type:

importjava.io.*classmyCat{publicvoidmain(Stringargs[])throwsIOException{intb;intcount=0;while((b=System.in.read())!=-1){count++;System.out.print((char)b);}System.out.println();//blanklineSystem.err.println("counted"+count+"totalbytes.");}}

9.3普通输入输出类

除了基本的键盘输入和屏幕输出外,我们还需要联系文件的输入输出。我们将学习下面几个类:lFileInputStreamlDataInputStreamlFileOutputStreamlDataOutputStream

作为参考,再列出一?copy;特定应用的类:lPipedInputStreamlBufferedInputStreamlPushBackInputStreamlStreamTokenizerlPipedOutputStreamlBufferedOutputStreamlRandomAccessFile

我们不在此讨论这?copy;类,但你可以在JAVA_HOME/src/java/io目录里查看每个类的成员函数定义。

9.4文件

在我们进行文件操作时,需要知道一?copy;关于文件的信息。File类提?copy;了一?copy;成员函数来操纵文件和获得一?copy;文件的信息。

9.4.1创建一个新的文件对象

你可用下面三个方法来创建一个新文件对象:

FilemyFile;myFile=newFile("etc/motd");

myFile=newFile("/etc","motd");//moreusefulifthedirectoryorfilenamearevariables

FilemyDir=newfile("/etc");myFile=newFile(myDir,"motd");

这三种方法取决于你访问文件的方式。例如,如果你在应用程序里只用一个文件,第一种创建文件的结构是最容易的。但如果你在同一目录里打开数个文件,则第二种或第三种结构更好一?copy;。

9.4.2文件测试和使用

一?copy;你创建了一个文件对象,你便可以使用以下成员函数来获得文件相关信息:

文件名lStringgetName()lStringgetPath()lStringgetAbslutePath()lStringgetParent()lbooleanrenameTo(FilenewName)

文件测试lbooleanexists()lbooleancanWrite()lbooleancanRead()lbooleanisFile()lbooleanisDirectory()lbooleanisAbsolute()

一般文件信息llonglastModified()llonglength()

目录用法lbooleanmkdir()lString[]list()

9.4.3文件信息获取例子程序

这里是一个独立的显示文件的基本信息的程序,文件通过命令行参数传输:

importjava.io.*;classfileInfo{FilefileToCheck;publicstaticvoidmain(Stringargs[])throwsIOException{if(args.length>0){for(inti=0;i<args.length;i++){fileToCheck=newFile(args[i]);info(fileToCheck);}}else{System.out.println("Nofilegiven.");}}publicvoidinfo(Filef)throwsIOException{System.out.println("Name:"+f.getName());System.out.println("Path:"=f.getPath());if(f.exists()){System.out.println("Fileexists.");System.out.print((f.canRead()?"andisReadable":""));System.out.print((f.cnaWrite()?"andisWriteable":""));System.out.println(".");System.out.println("Fileis"+f.lenght()="bytes.");}else{System.out.println("Filedoesnotexist.");}}}

9.5输入流

InputStreamSequenceInputStreamFileInputStreamPipedInputStreamByteArrayInputStreamFileterInputStreamStringBufferInputStream

DataInputStreamLineNumberInputStreamPushbackInputStreamBufferedInputStream有好几个类是专门用来处理文件输入的。下面是文件输入类的层次结构:

9.5.1FileInputStream对象

FileInputStream典型地表示一种顺序访问的文本文件。通过使用FileInputStream你可以访问文件的一个字节、几个字节或整个文件。

9.5.2打开FileInputStream

为一个文件打开输入流FileInputStream,你必须将文件名或文件对象传送给结构:

FileInputStreammyFileStream;myFileStream=newFileInputStream("/etc/motd");

你还可以象下边这样从FileInputStream里读文件信息:

FilemyFile;FileInputSteammyFileStream;myFile=newFile("/etc/motd");myFileStream=newFileInputStream(myFile);

一?copy;FileInputStream输入流打开,你就可以从里面读取信息了。read()成员函数有以下几种选项:

lintread()//readsonebyte//return-1atendofstreamlintread(byteb[])//fillsentirearray,ifpossible//returnsnumberofbytesread//returns-1ifendofstreamisreached

lintread(byteb[],intoffset,intlen)//readslenbytesintobstartingatb[offset]//Returnsnumberofbytesread,//or-1ifendofstreamisreached.

9.5.3关闭FileInputStream

当你完成一个文件的操作,你可选两种方法关闭它:显式关闭和隐式关闭,隐式关闭是自动垃圾回收时的功能。

显式关闭如下:myFileStream.close();

9.6例程:显示一个文件

如果文件的访问权限足够,你可以在TextArea对象里显示文件内容。

下面是显示文件的程序片断:

FileInputStreamfis;TextAreata;publicvodinit(){byteb[]=newbyte[1024];intI;//makeitbigenoughorwaituntilyou//knowthesizeofthefileStrings;try{fis=newFileInputStream("/etc/motd");}catch(FileNotFoundExceptione){/*dosomethingappropriate*/}try{I=fis.read(b);}catch(IOExceptione){/*dosomethingappropriate*/}s=newString(b,0);ta=newTextArea(s,5,40);add(ta);}

9.7DataInputStreams

DataInputStreams与FileInputStreams差不多。Data流可以直接读任意一种变量类型,如浮点数,整数和字符等。一般来说,对二进制文件使用DataInputStream流。

9.7.1打开和关闭DataInputStreams

打开和关闭DataInputStreams对象时,其方法与FileInputStreams相同:

DataInputStreamsmyDataStream;FileInputStreamsmyFileStream;

//getafilehandlemyFileStream=newFileInputStream("/usr/db/stock.dbf");//open,or"chain"adatainputfilemyDataStream=newDataOutputStream(myFileStream);

//Nowwecanusebothinputstreamstoaccessourfile//j(Ifwewantto...)myFileStream.read(b);I=myDataStrea.readInt();

//closethedatafrielexplicityly//Alwaysclosethe"topmost"filestreammyDataStream.close();myFileStream.close();

9.7.2读DataInputStreams

当你从DataInputStreams流里访问文件时,你可以使用与FileInputStream流相同的成员函数read()。但你也可以使用其他访问方法来读取不同种类的数据:

lbytereadByte()lintreadUnsignedByte()lshortreadShort()lintreadUnsighedShort()lcharreadChar()lintreadIntllongreadLong()lfloatreadFloat()ldoublereadDouble()lStringreadLine()

以上每一个成员函数都读取相应的数据对象。象StringreadLine()成员函数,你可使用\n,\r,\r\n,或EOF作为字符?reg;结束符。

读一个长整型,例如:

longserialNo;...serialNo=myDataStream.readLong();

9.8URL输入流

除了基本文件访问外,Java还提?copy;了通过网络使用URL访问对象的功能。在下面这个例子里,我们用getDocumentBase()成员函数并显式指定URL对象来访问声音和图象。

StringimageFile=newString("images/Duke/T1.gif");images[0]=getImage(getDocumentBase(),imageFile();

如果我们愿意,可以直接使用URL:URLimageSource;imageSource=newURL("http://555-1212.com/~info");images[0]=getImage(imageSource,"Duke/T1.gif");

我们可以为相应的URL打开输入流。例如,下面的程序里包括一个数据文件:InputStreamis;bytebuffer[]=newbyte[24];is=newURL(getDocumentBase(),dataname).openStream();

现在我们可以使用is,就象使用FileInputStream对象一样:is.read(buffer.0,buffer.length);

注意:有?copy;用户设置了他们的浏览器安全属性,可以不让你的程序访问他们的文件。

9.9OutputStreams

上面我们谈到了读数据,那么如何实现写数据呢?象输入流一样,输出流也有类似的层次结构:

OutputStream

FileOutputStreamPipedOutputStreamByteArrayOutputStreamFilterOutputStream

DataOutputStreamPrintStreamBufferedOutputStream

我们将分析FileOutputStream和DataOutputStream类来完成我们碰到的输出流问题。其它的输出流包含了更多的信息和成员函数。象输入流的源文件一样,这?copy;文件在$JAVA_HOME/src/java/io目录下。

9.9.1FileOutputStream类

FileOutputStream对象用于向一个文本文件写数据。象输入文件一样,你得先打开这个文件后才能写这个文件。

9.9.2打开一个FileOutputStream对象

要打开一个FileOutputStream对象,象打开一个输入流一样,你可以将字符?reg;或文件对象作为参数:FileOutputStreammyFileStream;myFileStream=newFileOutputStream("/etc/motd");

象输入流一样,你也可这样使用:FilemyFile;FileOutputStreammyFileStream;myFile=newFile("/etc/motd");myFileStream=newFileOutputStream(myFile);

9.9.3写入一个流

一?copy;文件被打开,你便可以使用write()函数向文件里写一?copy;数据。就象输入流的read()函数一样,你可有三种方法:lvoidwrite(intb);//writesoutonebytelvoidwrite(byteb[]);//writesoutentirearraylvoidwrite(byteb[],intoffset,intlength);//writeoutlengthbytesofb[],startingatb[offset]

9.9.4关闭一个FileOutputStream对象

关闭输出流和关闭输入流方法一样,你可以使用显式方法:myFileStream.close();你也可以让系统自动关闭它。

9.10例子:存储信息

下面有一个程序,让用户输入一?copy;姓名和电话号码。每一个姓名和号码将加在文件里。用户通过点"Done"按钮来告诉系统整个列表已输入完毕。

一?copy;用户输入完整个列表,程序将创建一个输出文件并显示或打印出来。例如:

555-1212,Tom123-456-7890,PeggyL.234-5678,Marc234-5678,Ron876-4321,Beth&Brian33.1.42.45.70,Jean-Marc

下面是程序的源代码:importjava.io.*;

//Phones.java//Asimpledatabasecreationprogram

classPhones{staticFileOutputStreamfos;publicstaticfinalintlineLength=81;publicstaticvoidmain(Stringargs[])throwsIOExciption{byte[]phone=newbyte[lineLength];byte[]name=newbyte[lineLenght];intI;fos=newFileOutputStream("phone.numbers");while(true){System.err.println("Enteraname(enter'done'toquit)");readLine(name);if("done".equalsIgnoreCase(newString(name,0,0,4))){break;}System.err.println("Enterthephonenumber");readLine(phone);for(i=0;phone[i]!=0;i++){fos.write(phone[i]);}fos.write(',');for(i=0;name[i]!=0;I++){fos.write(name[i]);}fos.write('\n');}fos.close();}

privatestaticvoidreadLine(byteline[])throwsIOException{inti=0,b=0;

while((i<lineLengh-1))&&((b=System.ini.read())!='\n')){line[i++]=(byte)b;}line[i]=(byte)0;}}

9.11BufferedOutput流

如果你处理的数据量很多,或向文件写很多次小数据,你可以使用一个BufferedOutput流。BufferedOutput流提?copy;和FileOutputStream类同样的写操作方法,但所有输出全部存放在一个缓冲区里。当你填满缓冲区,它将一次性写入磁盘。或者你主动将缓冲区写入磁盘。

9.11.1创建BufferedOutput流

如果要创建一个BufferedOutput流,首先需要一个FileOutput流。然后将缓冲区链接到FileOutput流:FileOutputStreammyFileStream;BufferedOutputStreammyBufferStream;//getafilehandlemyFileStream=newFileOutputStream("/usr/db/stock.dbf");//chainabufferedoutputstreammyBufferSSstream=newBufferedOutputStream(myFileStream);

9.11.2更新和关闭BufferedOutput流

和普通FileOutput流一样,向BufferedOutput流里的每一次写操作和写入磁盘操作并不是一一对应的。要想在程序结束?reg;前将缓冲区里的数据写入磁盘,除非填满缓冲区,否则只有显式调用flush()函数://forceleft-overdatatodiskmyBufferStream.flush();//closethedatafileexplicitly//Alwaysclosethe"topmost"filestreammyBufferStream.close();myFileStream.close();

9.12DataOutput流

和DataInputStream对应,Java还提?copy;了DataOutput流。使用DataOutput流,我们可以向文件写入二进制数据。

9.12.1打开和关闭DataOutput流对象

打开和关闭DataOutput流对象与打开、关闭FileOutput流对象方法一样:DataOutputStreammyDataStream;FileOutputStreammyFileStream;BufferedOutputStreammyBufferStream;

//getafilehandlemhyFileStream=newFileOutputStream("/usr/db/stock.dbf");//chainabufferedoutputstream(forefficiency);myBufferStream=newBufferedOutputStream(myFileStream);//chainadataoutputfilemyDataStream=newDataOutputStream(myBufferStream);

//Nowwecanusebothinputstreamstoaccessourfile//(iiIfwewantto...)myBufferStream.write(b);myDataStream.writeInt(i);

//closethedatafileexplicitly//Alwayscolsethe"topmost"filestreammyDataStream.close();myBuffersStream.close();myFileStream.close();

9.12.2向DataOutput流写数据

FileOutput流里的write()函数各种方法都适用于DataOutput流。你还可以看到DataInput流的类似函数方法:lvoidwriteBoolean(booleanv)lvoidwriteByte(intv)lvoidwriteShort(intv)lvoidwriteChar(intv)lvoidwriteInt(intv)lvoidwriteFloat(floatv)lvoidwriteDouble(doublev)lvoidwriteBytes(strings)lvoidwriteChars(strings)

对字符?reg;来说,有两种选择:byte和char。记住byte是8位数据而char是16位数据。如果你想利用Unicode字符的优点,你应使用writeChars()函数。

9.12.3输出记数

在使用二进制数据输出时常用的另外一个函数是size()。这个函数返回写入文件数据的总字节数。你也可用size()函数将数据文件分成四字节为单位的块,例如:...intbytesLeft=myDataStream.size()%4;for(intI=0;I<bytesLeft;I++){myDataStrea.write(0);}...

9.13随机访问文件

我们读文件常常不是从头至尾顺序读的。你也许想将一文本文件当作一个数据库,读完一个记录后,跳到另一个记录,它们在文件的不同地方。Java提?copy;了RandomAccessFile类让你操作这种类型的输入输出。

9.13.1创建随机访问文件

打开随机访问文件有两种方法:l用文件名myRAFile=newRandomAccessFile(Stringname,Stringmode);l用文件对象myRAFile=newRandomAccessFile(Filefile,Stringmode);

mode参数决定了访问文件的权限,如只读'r'或读写'wr'等。

例如,我们打开一个数据库更新数据:RandomAccessFilemyRAFile;myRAFile=newRandomAccessFile("/usr/db/stock.dbf","rw");

9.13.2访问信息

RandomAccessFile对象的读写操作和DataInput/DataOutput对象的操作方式一样。你可以使用在DataInputStream和DataOutputStream里出现的所有read()和write()函数。

还有几个函数帮助你在文件里移动指针:llonggetFilePointer();返回当前指针lvoidseek(longpos);将文件指针定位到一个绝对地址。地址是相对于文件头的偏移量。地址0表示文件的开头。llonglength();返回文件的长度。地址"length()"表示文件的结尾。

9.13.3增加信息

你可以使用随机访问文件来设置成增加信息模式:myRAFile=newRandomAccessFile("/tmp/java.log","rw");myRAFile.seek(myRAFile.length());//Anysubsequentwrite()swillbeappendedtothefile

9.13.4追加信息例子下面是一个在已存在文件后面追加字符?reg;的例子:importjava.io.IOException;importjava.io.RandomAccessFile;

classraTest{publicstaticvoidmain(Stringargs[])throwsIOException{RandomAccessFilemyFAFile;Strings="InformationtoAppend\nHimom!\n";//openourrandomaccessfilemyRAFile=newRandomAccessFile("/tmp/java.log","rw");//movetotheendofthefilemyRAFile.seek(myRAFile.length());//Startappending!myRAFile.writeBytes(s);

myRAFile.close();}}

本章小结

1.Java通过系统类达到访问标准输入输出的功能。2.你可以创建、读、写文件。

posted @ 2016-11-03 16:47  蚂蚁窝2  阅读(1090)  评论(0编辑  收藏  举报