Java语言案例教程


中等职业学校计算机系列规划教材 Java 语言案例教程 陈海宁 索 旺 编著 北京工业大学出版社 本书作为中等职业学校相关专业的教材,循序渐进地介绍了 Java 2 的基础编程知 识,包括 Java 语言的基本语法,Java 语言的类和对象,代码中的异常捕捉处理,数组 和字符串的应用,标准输入输出及目录文件的管理,图形用户界面的事件编程,多媒 体、多线程编程以及网络编程等。本书使用通俗而简洁的语言对各个实例进行描述, 图文并茂,有利于学生在比较少的课时内认识并掌握 Java 语言编程技术。 本书也适合 Java 语言初学者进行自学,可供对 Java 语言有一定了解的读者作为提 高技术水平之用。 图书在版编目(CIP)数据 Java 语言案例教程/陈海宁,索旺编著.—北京:北京 工业大学出版社,2007.6 ISBN 978-7-5639-1788-4 I.J... II.①陈...②索... III.JAVA 语言—程序设计— 专业学校-教材 IV.TP312 中国版本图书馆 CIP 数据核字(2007)第 069717 号 Java 语言案例教程 陈海宁 索 旺 编著 * 北京工业大学出版社出版发行 邮编:100022 电话:(010)67392308 各地新华书店经销 徐水宏远印刷厂印刷 * 2007 年 6 月第 1 版 2007 年 6 月第 1 次印刷 787mm×1 092mm 16 开本 14印张 337 千字 印数:1~4 000 册 ISBN 978-7-5639-1788-4/T·303 定价:19.50 元 内 容 提 要 前 言 为了贯彻《中共中央国务院关于深化教育改革推进素质教育的决定》的精神, 落实《面向 21 世纪教育振兴行动计划》所提出的职业教育课程改革和教材建设规 划,编者本着素质教育的思想,依据教育部 2001 年颁布的《中等职业学校计算机 及应用专业教学基本要求》,从社会发展对中、初级计算机人才的实际需求出发, 编写了这套中等职业学校计算机专业的教材。 Java 编程语言是由美国 SUN 公司研发的第一种在因特网上使用的、具有“硬 件/软件中立性”的编程语言。Java 语言是功能强大且最具吸引力的编程语言,它 所具有的纯面向对象、简单高效、与平台无关、安全、完整、支持多线程等特点, 成为网络上的世界语言。Java 语言是 20 世纪 90 年代最重要的技术发明。 全书共分 10 章,按照由浅入深的方式进行安排。第 1 章到第 4 章主要介绍 Java 语言的基础知识和面向对象的基本概念,以及如何配置相关的编程环境和必 要的工具,从而使读者对 Java 语言编程有一个基本的认识,为后面的 Java 语言开 发实例讲解做好铺垫。 从第 5 章到第 10 章,分专题分别讲述了流、多线程、Swing、多媒体、网络 和数据库编程。在讲述实例的过程中,重点向读者介绍 Java 语言编程思想。读者 可以一边学习实例,一边在本书的指导下培养自己面向对象软件开发的思想。在 每章的最后,编者对本章所讲述的重点做了小结。由于 Java 语言的应用十分广泛, 考虑到读者的实际需要,因此本书通过这些实用的小例子,讲述了 Java 语言在各 个领域的应用,而更重要的是向读者阐述了面向对象的编程思想。 全书内容覆盖 Java 语言的各个要点,知识面广泛,注重条理性,编程步骤清 晰、完善,而且易于操作。本书对基础概念的讲解比较全面,在内容安排上由浅 入深,既照顾到了初学者,也为欲了解面向对象程序开发基础知识的读者提供了 有益的参考。建议读者在学习过程中适当配合上机实践,这样会达到更好的学习 效果。 本教材是作者多年从事教学实践工作的心得之作。教材的结构体系为中等职 业教育教材改革进行了新的尝试。教材根据中等职业教育的特点,结合了中等职 业教育的实际情况,通过 Java 案例引入相应的教学内容,由表到里,由浅入深, 既便于教学,又便于学生自己阅读学习,能很好地调动学生的积极主动性,提高 教学效果。教材在内容的安排上层次清晰,结构严谨,便于理解,着重实际应用。 为了方便教师教学,我们免费为使用本套教材的师生提供电子教学参考资料包: ‹ PowerPoint 多媒体课件 ‹ 习题参考答案 ‹ 教材中的程序源代码 ‹ 教材中涉及的实例制作的各类素材 有需要的教师可以登录教学支持网站免费下载。在教材使用中有什么意见或 建议也可以直接和我们联系,电子邮件地址:scqcwh@163.com。 本书所选实例内容翔实、结构紧凑、条理清晰、覆盖知识点全面。但由于编 写时间较为仓促,书中难免会有疏漏和不足之处,恳请广大读者提出宝贵意见。 编 者 目 录 第 1 章 Java概述.............................................................................................................................1 1.1 Java 语言的发展史 ............................................................................................................1 1.2 Java 技术的特点................................................................................................................3 1.3 Java 语言与 C、C++语言 .................................................................................................5 1.4 Java 与.NET 技术 ..............................................................................................................6 1.5 J2ME...................................................................................................................................7 1.6 J2EE....................................................................................................................................8 1.7 网上资源 ..........................................................................................................................10 【本章小结】 ...........................................................................................................................12 【习题】...................................................................................................................................12 第 2 章 第一个 Java 程序..............................................................................................................14 2.1 JDK 的安装 ......................................................................................................................14 2.1.1 下载 JDK.............................................................................................................14 2.1.2 安装 JDK.............................................................................................................15 2.1.3 设置 Java 的环境变量 ........................................................................................16 2.2 Java 程序开发流程 ..........................................................................................................18 2.2.1 应用程序 .............................................................................................................18 2.2.2 小程序 .................................................................................................................21 2.2.3 小程序的生命周期 .............................................................................................23 2.3 常用的集成开发环境 ......................................................................................................24 【本章小结】 ...........................................................................................................................26 【习题】...................................................................................................................................27 第 3 章 Java语言基础知识...........................................................................................................28 3.1 简单数据类型 ..................................................................................................................28 3.1.1 标志符和保留字 .................................................................................................28 3.1.2 数据类型 .............................................................................................................29 3.1.3 简单数据类型 .....................................................................................................29 3.1.4 简单数据类型中各类型数据间的优先关系和相互转换 .................................31 3.2 运算符和表达式 ..............................................................................................................31 3.2.1 运算符 .................................................................................................................31 3.2.2 表达式 .................................................................................................................33 3.3 控制语句 ..........................................................................................................................34 Java 语言案例教程 2 3.3.1 分支语句 .............................................................................................................34 3.3.2 循环语句 .............................................................................................................35 3.3.3 跳转语句 .............................................................................................................36 3.4 数组..................................................................................................................................37 3.4.1 一维数组 .............................................................................................................37 3.4.2 多维数组 .............................................................................................................38 3.5 字符串的处理 ..................................................................................................................40 3.5.1 字符串的表示 .....................................................................................................40 3.5.2 访问字符串 .........................................................................................................41 3.5.3 修改字符串 .........................................................................................................42 3.5.4 其他操作 .............................................................................................................43 【本章小结】 ...........................................................................................................................43 【习题】...................................................................................................................................44 第 4 章 Java语言中的面向对象特性...........................................................................................46 4.1 面向对象技术基础 ..........................................................................................................46 4.1.1 面向对象的基本概念 .........................................................................................46 4.1.2 面向对象的基本特征 .........................................................................................47 4.2 Java 语言的面向对象特性 ..............................................................................................47 4.2.1 类 .........................................................................................................................47 4.2.2 对象 .....................................................................................................................53 4.2.3 面向对象特性 .....................................................................................................54 4.2.4 抽象类和接口 .....................................................................................................60 4.2.5 内部类 .................................................................................................................61 【本章小结】 ...........................................................................................................................63 【习题】...................................................................................................................................63 第 5 章 输入、输出流 ...................................................................................................................66 5.1 File 类...............................................................................................................................66 5.1.1 构造函数 .............................................................................................................66 5.1.2 重要的方法 .........................................................................................................67 5.2 字节输入流 ......................................................................................................................69 5.2.1 InputStream 类.....................................................................................................69 5.2.2 FileInputStream 类 ..............................................................................................70 5.3 字节输出流 ......................................................................................................................71 5.3.1 OutputStream 类..................................................................................................71 5.3.2 FileOutputStream 类............................................................................................72 5.4 RandomAccessFile 类......................................................................................................73 5.5 字符输入流 ......................................................................................................................76 目 录 3 5.5.1 InputStreamReader 类 .........................................................................................76 5.5.2 FileReader 类.......................................................................................................77 5.6 字符输出流 ......................................................................................................................78 5.6.1 OutputStreamWriter 类........................................................................................79 5.6.2 FileWriter 类........................................................................................................79 5.7 处理回压输入流 ..............................................................................................................81 5.7.1 PushbackInputStream 类 .....................................................................................81 5.7.2 PushbackReader 类..............................................................................................82 5.8 对象串行化 ......................................................................................................................83 5.8.1 ObjectInputStream 类..........................................................................................84 5.8.2 ObjectOutputStream 类 .......................................................................................85 5.8.3 串行化( Serializable)接口 ..............................................................................86 5.8.4 操作打印机 .........................................................................................................88 【本章小结】 ...........................................................................................................................89 【习题】...................................................................................................................................89 第 6 章 多线程编程.......................................................................................................................92 6.1 Java 中的线程 ..................................................................................................................92 6.2 Thread 类与 Runnable 接口 ............................................................................................93 6.2.1 Thread 类.............................................................................................................93 6.2.2 Runnable 接口.....................................................................................................95 6.3 多线程程序案例 ..............................................................................................................96 6.3.1 使用 Thread 类创建多线程................................................................................96 6.3.2 实现 Runnable 接口创建多线程 ........................................................................97 6.4 线程间的同步 ................................................................................................................101 6.4.1 同步代码段 .......................................................................................................103 6.4.2 同步方法 ...........................................................................................................104 6.5 线程间的通信 ................................................................................................................106 【本章小结】 .........................................................................................................................110 【习题】.................................................................................................................................110 第 7 章 Java Swing基础..............................................................................................................112 7.1 Swing 概述.....................................................................................................................112 7.2 Swing 组件与 AWT 组件的区别 ..................................................................................113 7.3 从一个简单的 Swing 程序开始 ....................................................................................113 7.4 重要的类 ........................................................................................................................123 7.4.1 JComponent 类..................................................................................................123 7.4.2 顶层容器 ...........................................................................................................125 7.4.3 布局管理 ...........................................................................................................128 Java 语言案例教程 4 7.4.4 Swing 高级组件简介 ........................................................................................131 【本章小结】 .........................................................................................................................137 【习题】.................................................................................................................................137 第 8 章 Java与多媒体.................................................................................................................139 8.1 图像文件的显示 ............................................................................................................139 8.2 声音文件的播放 ............................................................................................................141 8.3 Java 媒体框架 ................................................................................................................142 【本章小结】 .........................................................................................................................147 【习题】.................................................................................................................................148 第 9 章 网络编程.........................................................................................................................149 9.1 网络基础 ........................................................................................................................149 9.2 套接字............................................................................................................................151 9.2.1 TCP/IP 客户套接字 ..........................................................................................151 9.2.2 TCP/IP 服务器套接字 ......................................................................................151 9.3 UDP 数据报 ...................................................................................................................157 9.3.1 Java.net.DatagramPacket 类..............................................................................158 9.3.2 Java.net.DatagramSocket 类..............................................................................158 9.4 Java.net.URL 类.............................................................................................................163 【本章小结】 .........................................................................................................................165 【习题】.................................................................................................................................166 第 10 章 数据库编程...................................................................................................................167 10.1 JDBC 简介 ...................................................................................................................167 10.2 JDBC 的体系结构及主要接口 ...................................................................................167 10.2.1 JDBC 驱动程序的类型 ...................................................................................168 10.2.2 JDBC 的组成及其主要接口 ...........................................................................169 10.3 建立数据库和设置数据源 ..........................................................................................169 10.3.1 SQL Server 2000 的设置.................................................................................169 10.3.2 Access 的设置.................................................................................................170 10.4 JDBC 应用程序设计 ...................................................................................................170 10.4.1 加载 JDBC 驱动程序......................................................................................171 10.4.2 建立连接对象 .................................................................................................173 10.4.3 建立语句对象 .................................................................................................174 10.4.4 建立结果集对象 .............................................................................................176 10.5 JDBC 编程案例 ...........................................................................................................179 【本章小结】 .........................................................................................................................193 【习题】.................................................................................................................................193 目 录 5 第 11 章 课程设计 .......................................................................................................................194 11.1 中国象棋 ......................................................................................................................194 11.1.1 设计要求 .........................................................................................................194 11.1.2 总体设计 .........................................................................................................194 11.1.3 具体设计 .........................................................................................................194 11.1.4 提交成果 .........................................................................................................205 11.2 图书查询系统 ..............................................................................................................206 11.2.1 设计要求 .........................................................................................................206 11.2.2 总体设计 .........................................................................................................206 11.2.3 具体设计 .........................................................................................................206 11.2.4 提交成果 .........................................................................................................211 【本章小结】.........................................................................................................................211 第 1 章 Java 概述 【学习目标】 1.了解 Java 语言的发展历史。 2.了解 Java 技术的特点和 Java 平台的应用划分。 3.了解 Java 与.NET 技术的关系与作用。 4.学会利用网上资源。 1.1 Java 语言的发展史 1.Java 语言在因特网时代获得巨大成功 要把运行于 Windows 平台上的程序不做任何修改, 就直接拿到 UNIX 系统上运行是不行 的。因为程序的执行最终必须转换成为计算机硬件所能识别的机器指令来执行,专门为某一 种计算机硬件和操作系统编写的程序是不能在另一种计算机硬件上执行的,至少要做代码移 植工作。要想让程序能够在不同的计算机系统上运行,就要求程序设计语言能够跨越各种软 件和硬件平台,而 Java 语言满足了这一需求。 1995 年,Sun 公司正式向 IT 界推出了 Java 语言, Java 语言具有安全、跨平台、纯面向 对象、简单、适用于网络等显著特点。当时以网页为主要形式的因特网正在迅猛发展, Java 语言的出现迅速引起所有程序员和软件公司的极大关注, 程序员们纷纷尝试用 Java 语言编写 网络应用程序,并利用网络把程序发布到世界各地。包括 IBM、Oracle、微软、Netscape、 Apple、SGI 等大公司纷纷与 Sun 公司签订合同,从而获得授权使用 Java 平台技术。微软公 司主席比尔· 盖茨在经过系统地研究后认为“ Java 语言是长时间以来最卓越的程序设计语 言”。目前, Java 语言已经成为网络编程的首选语言。 在经历了以大型机为核心的集中计算模式和以个人计算机为主的分散计算模式之后,因 特网使得计算模式进入了网络计算时代。网络计算模式的一个特点是计算机的类型和操作系 统是多样的,例如 Sun 工作站的硬件是 SPARC 体系,软件是 UNIX 中的 Solaris 操作系统, 而个人计算机的硬件是 Intel 体系,操作系统是 Windows 或者 Linux,因此以前的编程语言基 本上只是适用于一种计算机系统,例如 COBOL、FORTRAN、Prolog、C、C++等;网络计 算模式的另一个特点是代码可以通过网络在不同的计算机上进行迁移,这样就迫切需要一种 跨平台的编程语言,使得用它编写的程序能够在网络中的各种计算机上正常运行, Java 语言 就是在这种需求下应运而生的。正是因为 Java 语言符合了因特网时代的发展要求,才使它获 得了巨大的成功。 Java 语言发展到今天,已经不仅是一种单纯的程序开发语言,而且发展成 为了一个以 Java 语言为核心的应用于各种计算平台的一整套解决方案。 Java 不仅包括语言、 标准、组件、计算模式,还包括设备、网络技术及软件开发思想体系等一系列相关技术。我 们统称为 Java 技术。 Java 语言案例教程 2 另外需要说明的是, Sun 公司是 Java 语言的创始者,同时也是 Java 技术发展的原动力和 推动者。读者可以访问 Sun 公司的 Java 网站(http://java.sun.com)来获得最新的 Java 信息。 2.Java 语言的产生 Java 语言来自于 Sun 公司的一个叫做 Green 的项目。1991 年,Sun 公司的一个研究小组 为了能够在消费电子产品上开发应用程序,开始积极寻找合适的编程语言。消费电子产品种 类繁多,包括 PDA、机顶盒、手机等,由于同一类消费电子产品所采用的处理芯片和操作系 统也不相同,也就存在着跨平台的问题。当时,最流行的编程语言是 C 和 C++, Sun 公司 的研究人员开始试用 C++来开发消费电子产品的应用程序,但是结果表明,对于消费电子 产品而言, C++语言过于复杂和庞大,并不适用,安全性也不令人满意。于是, Bill Joy 领 导的研究小组就着手设计和开发出一种语言, 称之为 Oak。该语言沿袭了许多 C 语言的语法, 并提高了安全性, 而且是纯面向对象的语言, 但是 Oak 语言在商业上并未获得成功。 到了 1995 年,由于因特网在世界上蓬勃发展, Sun 公司发现 Oak 语言所具有的跨平台、面向对象、安 全性高等特点非常符合因特网的需要,于是改进了该语言的设计,以达到如下几个目标: z 创建一种纯面向对象的程序设计语言,而不是面向过程的语言; z 提供一个解释执行的程序运行环境,使程序代码独立于平台; z 吸收 C 和 C++的优点,使程序员容易过渡; z 去掉 C 和 C++中影响程序鲁棒性的部分,使程序更安全,例如指针、内存申请和 释放; z 实现多线程功能,使得程序能够同时执行多个任务; z 提供动态下载程序代码的机制; z 提供代码校验机制以保证安全性。 最终,Sun 公司给该语言取名为 Java 语言,从而造就了一种成功的编程语言。 3.Java 平台 —— 是不断扩展的计算平台 Java 不仅是编程语言,还是一个开发平台, Java 技术给程序员提供了许多工具:编译器、 解释器、文档生成器和文件打包工具等。同时 Java 还是一个程序发布平台,有两种“发布环 境”,首先是 Java 运行时环境( Java runtime environment,JRE)包含了完整的类文件包,其 次是许多浏览器都提供了 Java 解释器和运行时环境。 目前 Sun 公司把 Java 平台划分成 J2EE、 J2SE、J2ME 三个平台,针对不同的市场目标和设备进行定位。 J2EE 是 Java 企业版( Java 2 Enterprise Edition),主要目的是为企业提供一个应用服务器的运行和开发平台。 J2EE 本身是 一个开放的标准,任何软件厂商都可以推出自己的符合 J2EE 标准的产品,使用户可以有多 种选择。IBM、Oracle、BEA、HP 等 29 家公司已经推出了自己的产品,其中尤以 BEA 公司 的 weglogic 产品和 IBM 公司的 websphare 最为著名。 J2EE 将逐步发展成为可以与微软的 .NET 战略相对抗的网络计算平台。 J2SE 是 Java 标准版( Java 2 Standard Edition),主要目的是为 台式机和工作站提供一个开发和运行的平台。我们在学习 Java 的过程中,主要是采用 J2SE 来进行开发。 J2ME(Java 2 Micro Edition),主要是面向消费电子产品,为消费电子产品提供 一个 Java 的运行平台,使得 Java 程序能够在手机、机顶盒、 PDA 等产品上运行。 第 1 章 Java概述 3 1.2 Java 技术的特点 1.Java 虚拟机 Java 虚拟机并非一台真正的计算机,而是软件模拟的计算机,可以在任何处理器上(无 论是在计算机中还是在其他电子设备中)安全并且兼容地执行保存在 .class 文件中的字节码 (Bytecode)。Java 虚拟机的“机器码”保存在 .class 文件中,有时也可以称之为字节码文件。 Java 程序的跨平台主要指字节码文件可以在任何具有 Java 虚拟机的计算机或者电子设备上运 行,Java 虚拟机中的 Java 解释器负责将字节码文件解释成为特定的机器码来运行。 Java 源程 序需要通过编译器编译才能成为 .class 文件(字节码文件或类文件) 。 但是,针对不同的软硬件平台需要做专门的 Java 虚拟机,既要考虑硬件:处理器的型号; 也要考虑软件:操作系统的种类。目前在 SPARC 结构、X86 结构、MIPS 和 PPC 等嵌入式处 理芯片上,在 UNIX、Linux、Windows 和部分实时操作系统上都有专门的 Java 虚拟机。 说明:字节码文件是与平台无关的二进制码,执行时由解释器解释成为本地机器码,解 释一句,执行一句。 2.内存自动回收机制 在程序的执行过程中,部分内存在使用过后没有释放,而处于废弃状态,如果不及时进 行无用内存的回收,就会造成内存泄露,进而导致系统崩溃。在 C++语言中,无用内存是由 程序员人工进行回收的,程序员需要在编写程序的时候把不再使用的对象内存释放掉;但是 这种人为的管理内存释放的方法却往往由于程序员的疏忽而致使内存无法回收,同时也增加 了程序员的工作量。而在 Java 运行环境中,始终存在着一个系统级的线程,专门跟踪内存的 使用情况,定期检测出不再使用的内存,并进行自动回收,避免了内存泄露,也大大减轻了 程序员的工作量。 3.代码安全性检查机制 安全和方便总难两全其美。 Java 语言的出现使得客户端计算机可以方便地从网络上下载 Java 程序到本机上运行,但是如何保证该 Java 程序不携带病毒或者不包含恶意攻击的代码 呢?Java 语言以代码安全性检查机制来保证执行的安全性,虽然有时候少数程序员会抱怨说 applet 连文件系统也不能访问,不是很方便,但是正是实行各种安全措施才确保了 Java 语言 的安全。 字节码的执行需要经过三个步骤,首先,由类装载器( class loader)负责把类文件加载 到 Java 虚拟机中, 在此过程检验该类文件是否符合类文件规范; 其次, 字节码校验器 (bytecode verifier)检查该类文件的代码中是否存在着某些非法操作,例如 applet 程序中访问本机文件 系统的操作;如果字节码校验器检验通过,则由 Java 解释器把该类文件解释成为机器码来执 行。Java 虚拟机采用的是“沙箱”运行模式,即把 Java 程序的代码和数据都限制在一定内存 空间里执行,不允许程序访问该内存空间以外的内存,如果是 applet 程序,还不允许访问客 户端计算机的文件系统。 4.Java 语言的特点 (1)简单、纯面向对象 Java 语言的简单首先体现在精简的系统上, Java 语言力图用最小的系统实现足够多的功 Java 语言案例教程 4 能;对硬件的要求不高,在小型的计算机上便可以良好地运行。和所有新一代的程序设计语 言一样, Java 语言也采用了面向对象技术并实行得更加彻底,所有的 Java 程序和 applet 程序 均是对象。其中,封装性实现了模块化和信息隐藏,继承性实现了代码的复用,用户可以建 立自己的类库。而且 Java 语言采用的是相对简单的面向对象技术,去掉了运算符重载、多继 承的复杂概念,而采用了单一继承、类强制转换、多线程、引用(非指针)等方式。无用内 存自动回收机制也使得程序员不必费心管理内存,使程序设计更加简单,同时大大减少了出 错的可能。 Java 语言采用了 C 语言中的大部分语法,熟悉 C 语言的程序员会发现 Java 语言 在语法上与 C 语言极其相似。 (2)强壮并且安全 Java 语言在编译及运行程序时,都要进行严格的检查。作为一种强制类型语言, Java 语 言在编译和连接时都进行大量的类型检查,防止不匹配问题的发生。如果引用一个非法类型、 或执行一个非法类型操作, Java 语言将在解释时指出该错误。在 Java 程序中不能采用地址计 算的方法通过指针访问内存单元,大大减少了错误发生的可能性;而且 Java 语言的数组并非 用指针实现, 这样就可以在检查中避免数组越界的发生。 无用内存自动回收机制也增加了 Java 语言的鲁棒性。 作为网络语言, Java 语言必须提供足够的安全保障,并且要防止病毒的侵袭。 Java 语言 在运行应用程序时,严格检查其访问数据的权限,比如不允许网络上的应用程序修改本地的 数据。下载到用户计算机中的字节代码在其被执行前要经过一个核实工具,一旦字节代码被 核实,便由 Java 解释器来执行,该解释器通过阻止对内存的直接访问来进一步提高 Java 的 安全性。同时 Java 极高的鲁棒性也增强了 Java 语言的安全性。 (3)可移植性 Java 语言采用了多种机制来保证可移植性:首先, Java 语言从本质上讲是属于解释型语 言,这意味着任何一台机器只要装备了 Java 解释器,即可以运行 Java 程序;其次, Java 语 言的数据类型在任何机器上都是一致的,不支持特定硬件环境的数据类型。 Java 语言的可移 植性使得应用软件能够在任意平台上运行,这大大加快了软件产品的开发过程,增强了软件 产品的可复用性。同时,也给软件产业带来一种崭新的开发模式。 (4)高性能 虽然 Java 语言是解释执行的,但它仍然具有非常高的性能,在一些特定的 CPU 上,Java 字节码可以快速的转换成为机器码进行执行。 而且 Java 字节码格式的设计就是针对机器码的 转换,实际转换时相当简便,自动的寄存器分配与编译器对字节码的一些优化可使之生成高 质量的代码。随着 Java 虚拟机的改进和“即时编译” (just in time)技术的出现使得 Java 的 执行速度有了更大的提高。 (5)解释执行、多线程并且是动态的 Java 语言的动态特性是其面向对象设计的延伸。 Java 程序的基本组成单元为类,而 Java 的类又是运行过程动态装载的, 这使得 Java 可以在分布环境中动态的维护应用程序及其支持 类库之间的一致性,而不像 C++那样,每次当其所支持类库升级之后,相应的应用程序都必 须重新编译。 第 1 章 Java概述 5 1.3 Java 语言与 C、C++语言 为了使 C、C++程序员快速地了解 Java 语言,现做以下 Java 与 C、C++语言的比较。 1.全局变量 Java 程序不能定义程序的全局变量,而类中的公共、静态变量就相当于这个类的全局变 量。这样就使全局变量封装在类中,保证了安全性,而在 C、C++语言中,不加封装的全局 变量往往会由于使用不当而造成系统的崩溃。 2.条件转移指令 C、C++语言中用 goto 语句实现无条件跳转,而 Java 语言没有 goto 语言,通过例外处理 语句 try、catch、finally 来取代它,提高了程序的可读性,也增强了程序的鲁棒性。 3.指针 指针是 C、C++语言中最灵活,但也是最容易出错的数据类型。用指针进行内存操作往往造 成不可预知的错误,而且,通过指针对内存地址进行显示类型转换后,可以访问类的私有成员, 破坏了程序的安全性。在 Java 语言中,程序员不能进行任何指针操作,同时 Java 语言中的数组 是通过类来实现的,很好地解决了数组越界这一 C、C++语言中不做检查的 弱点。 4.内存管理 在 C 语言中,程序员使用库函数 malloc()和 free()来分配和释放内存, C++语言中则是运 算符 new 和 delete。再次释放已经释放的内存块或者释放未被分配的内存块,会造成系统的 崩溃,而忘记释放不再使用的内存块也会逐渐耗尽系统资源。在 Java 语言中,所有的数据结 构都是对象,通过运算符 new 分配内存并得到对象的使用权。无用内存回收机制保证了系统 资源的完整,避免了由于内存管理不完善而导致的系统崩溃。 5.数据类型的一致性 在 C、C++语言中,不同的硬件平台,编译器对简单的数据类型,如 int、float 等分配不 同的字节数。例如: int 数据类型在 IBM PC 上为 16 位,在 VAX-11 上就为 32 位,从而导致 了代码数据的不可移植。在 Java 语言中,对数据类型的字节数分配总是固定的,而不管是在 何种的计算机平台上。因此就保证了 Java 语言数据的平台无关性和可移植性。 6.类型转换 在 C、C++语言中,可以通过指针进行任意的类型转换,不安全因素大大增加。而在 Java 语言中系统在对对象的处理过程中,要进行严格的相容性检查,防止不安全的转换。 7.头文件 在 C、C++语言中,使用头文件声明类的原型和全局变量及库函数等,在大的系统中, 维护这些头文件是非常困难的。 Java 语言不支持头文件,类成员的类型和访问权限都封装在 一个类中,运行时系统对访问进行控制,防止非法的访问。同时, Java 语言中用 import 语句 与其他类进行通信,以便访问其他类的对象。 8.结构和联合 在 C、C++语言中用结构和联合来表示一定的数据结构,但是由于其成员均为公有的, Java 语言案例教程 6 因此存在安全性问题。 Java 语言不支持结构和联合,通过类把数据结构及对该数据的操作 都封装在类里面。 9.预处理 C、C++语言有宏定义,用宏定义实现的代码往往影响程序的可读性,而 Java 语言不支 持宏定义。 为易于实现跨平台性, Java 语言被设计成为解释执行,字节码本身包含了许多编译时生 成的信息,这使连接过程更加简单。而多线程使应用程序可以同时进行不同的操作,处理不 同的事件。在多线程机制中,不同的线程处理不同的任务,互不干涉,不会由于某一任务处 于等待状态而影响了其他任务的执行,这样就可以容易地实现网络上的实时交互操作。 Java 语言在执行过程中,可以动态地加载各种类库,这一特点使之非常适合于网络运行,同时也 有利于软件的开发,因为即使是更新类库也不必重新编译使用这一类库的应用程序。 1.4 Java 与.NET 技术 2000 年 6 月,微软公司发布了 .NET 战略,该技术是与 Sun 公司的 Java One 技术相对应 的。抛开商业竞争的因素,从技术角度来讲,二者是相似的,它们都是因特网现状下的基于 网页的分布式计算方案。 .NET 是微软公司的 XML Web 服务( Web Service)平台, XML Web 服务是为应用程序提供数据和服务的逻辑单元。 XML Web 服务结合了组件技术和 Web 技术 的优秀方面,它是 .NET 编程模型的基石。 .NET 的核心概念就是“把软件当做服务” ,也就是把软件应用产品与商业、内容、信息 服务合并成一种事务,使之成为可以在网络上订阅使用的服务形式。人们设计、构造、实施、 运作、集成和使用软件的方式都可以通过网络完成,因此也就要按照使用这些服务的不同方 式支付相应的费用。 .NET 模式希望把计算模式从单机、客户机 /服务器和 Web 网站的方式彻 底地转向分布式计算。在 .NET 策略中有几个重要的概念: z XML 技术。 XML 被称为网络计算的世界语, 是一种代替 HTML 的可扩展标记语言。 XML 采用文本标记的形式定义各种可交换数据结构, 并且可以利用标准的网络协议 进行传输。正因为这些特性, XML 实际上代表了平台中性和进行网络计算的趋势。 z 网页服务。网页服务是 .NET 的核心概念,它是基于网络的分布式应用程序的基本构 造模块,而这些程序是以平台、对象模板和多语言方式构建的。网页服务建立在像 SOAP 和 XML 之类的开放的因特网标准之上,并且由此形成了可编程网络理念的 基础。 z 通用语言运行时( common language runtime,CLR)。它可以调用并运行任何编程语 言所写的代码。 CLR 具有交叉语言集成、自述组件、简单配制、版本化以及集成安 全服务等特点。这一点十分类似于 Java 虚拟机的概念。 z C#语言。 它是一种新型的编程语言, 为.NET 策略而设计。 它与 Java 语言比较类似。 从一个 Java 程序员的眼中看 .NET 策略,可以发现它继承了许多 Java 技术的思想精髓, 并且用微软的商业模式成功地加以包装。 .NET 的最初想法是希望进行接近操作系统平台的定 制开发,当然,这是指使用 Windows 系统(目前是 Windows XP、Windows ME 和 Windows 2000)。Visual Basic 和 C#是.NET 平台上最重要的开发语言,并且它们不能在其他平台上运 第 1 章 Java概述 7 作。微软声称有许多开发商在开发与 .NET 的 CLR 相合作的语言, 但直到今天, 我们看到 CLR 还只是一个 Windows 版的技术。这就说明存在一个重要的互用性问题, 因为每种编程语言 (根 据定义来划分)都有其各自特定的数据类型和数据结构。 图 1-1 简要的对 .NET 与 J2EE 技术进行了对比,从中不难发现 J2EE 与.NET 既有许多相 似之处,又存在不少区别。微软作为最成功的 PC 软件制造商,它推出的 .NET 策略必然以它 自己的商业模式加以推广,而作为优秀的跨平台分布式计算技术的 Java 语言,其发展前景更 加广阔,因此如何做到二者的互用性是一个非常重要的问题。 1.5 J2ME 手机作为现代最重要的通信手段,是最重要的嵌入式电子产品之一。目前, Java 在手机 设备上的应用方兴未艾, Java 正在成为手机应用软件开发的主流技术。 针对嵌入式设备及消费类电器的 J2ME 在推出之后,首先做出反应的就是全球各大手机 制造商,他们把 J2ME 引进到手机上, 使我们平常通信用的手机更加易用, 更加个性化。 J2ME 一经推出,摩托罗拉公司作为 J2ME 的主要支持者,第一个在自己生产的手机上移植了 KVM (千字节虚拟机) ,又领导全球手机厂家制定了 MIDP(动信息设备框架)规范。目前 KVM 和 MIDP 的手机软件开发体系已经成为 Java 手机程序开发的事实标准。 回顾移动电话的发展历史,不难发现移动电话上的应用软件的发展经历了三个阶段。传 统的移动电话通常只有通话和短消息功能,只能提供基本的语音服务。随后移动电话又增加 了一些简单的附加应用,如电话簿和电话铃声编辑功能等。而现在随着 WAP 技术的发展, 移动电话增加了访问因特网的功能,使用户可以直接在手机上以无线方式浏览网页。然而, 随着无线因特网新应用的出现,新的问题也随之而来。 首先,它面临的是开发瓶颈的问题。通常,手机类嵌入式系统普遍使用 C 语言和专用的 实时操作系统,开发速度慢,也没有动态加载应用程序的能力。在移动电话上开发应用程序 变得越来越困难,一方面单纯依靠手机厂商自身的软件开发能力难以满足市场的需求,而另 一方面,广大的软件开发商却又无法参与进来,来开发适用于移动电话的应用程序。这无疑 极大地制约了新应用的推广与普及。 其次,移动电话访问因特网只能通过 WAP 方式,而 WAP 采用 Browser/Server 方式访问 因特网功能有限。现在的 WAP 解决方案要求手机通过 WAP 网关才能访问因特网,而且只能 图 1-1 .NET 与 Java 的比较 Java 语言案例教程 8 访问 WML 而不是主流的 HTML,也不能显示复杂格式的图形。此外,因为现有的 WAP 解 决方案不够智能,而且不能访问本地存储区,如果进行在线交易会增加服务器负荷,反应速 度慢,使无线因特网应用受到了很大的限制。 Java 的应用则使上述问题迎刃而解。因为 Java 语言是跨平台运行的,这一特性使第三方 软件开发商可以很容易地介入进来开发应用程序,也可以很方便地将应用程序安装移植到移 动电话上,开发周期也大大缩短,而且还能支持应用程序的动态下载和升级。Java 还提供了 HTTP 协议,使移动电话能以客户机/服务器方式直接访问因特网的全部信息,不同的客户机 访问不同的文件,此外还能访问本地存储区,提供最高效率的在线交易。 基于 KVM 的 Java 除了能够更好地增强完善移动电话上已有的应用外,还进一步增加了 字典、图书、游戏、遥控家电和定时提醒等新的应用,并能访问电子邮件、即时消息、股票 和电子地图等信息。 最近 Java 技术团体的成员开始大力推动 Java 在小型设备上的应用。他们已经瞄准了那 些专门为手机开发应用的独立软件开发商。近日,Java 组织就发布了“Java 规范要求 JSR185”, 这是一个专门为手机应用开发商设计的融合了目前主要的 Java API 的体系结构,包括 CLDC、 CLDC 1.0、CDC、MIDP、MIDP 2.0、无线信息传送 API 以及无线媒体传送 API 等。用于测 试产品兼容性的测试包也即将推出。 目前已经有多家手机制造商表示支持 JSR185 规范,包括摩托罗拉、诺基亚、索尼爱立 信以及日本的 NTT DoCoMo 等。 1.6 J2EE J2EE 是一种全新概念的模型,与传统的互联网应用程序模型相比有着不可比拟的优势。 当今许多企业都需要扩展他们的业务范围,降低自身经营成本,缩短他们和客户之间的响应 时间,为此这就需要一种简捷、快速的服务。典型地说,提供这些服务的应用软件必须同企 业信息系统(EIS)相结合,并向更为广阔的用户提供新的服务。这些服务要具备以下特点: z 高可用性。来满足现在的全球商业环境。 z 安全性。保护用户的隐私和企业数据的安全。 z 可依赖性和可扩展性。保证商业交易的正确和迅捷。 通常这些服务是由分布的应用程序组成的,包括前端客户端和后端数据源以及它们之间的 一层或几层,这些中间层提供了把商业功能和数据与 EIS 相结合的功能。这些中间层把客户端 从复杂的商业逻辑中分离出来,利用成熟的因特网技术使用户在管理上所花费的时间最小。 J2EE 降低了开发这种中间层服务的成本和复杂程度,因而使得服务可以被快速地展开, 并能够更轻松地面对竞争中的压力。J2EE 通过定义一种标准的结构来实现它的优势,具体内 容如下: z J2EE Application Programming Model。一种用于开发多层次,瘦型客户用户程序的 标准设计模型。 z J2EE Platform。一个标准的平台,用来整合 J2EE 的应用程序,指定一系列的接口和 方法。 z J2EE Compatibility Test Suite。一套兼容测试组件,用来检测产品是否同 J2EE 平台兼容。 第 1 章 Java概述 9 z J2EE Reference Implementation。用来示范 J2EE 的能力。 J2EE 是被设计成为顾客、雇员、供应商、合作者提供企业级服务,这样的应用程序天生 具有复杂性,它们要访问各种类型的数据并分发于大量的客户端。为了更好地控制、管理这 些应用程序,就需要引入中间层,中间层描述了一个被企业的信息技术部门紧紧控制的环境。 J2EE 应用程序依靠 EIS 层来存储企业的商业数据。 这些数据和用来管理它的系统是企业信息 的核心。 最初,双层结构(客户机 /服务器)应用程序模型许诺将提高伸缩性与提供更广阔的功能, 但不幸的是,直接向用户提供 EIS 服务的复杂性和在每台用户计算机上安装和维护商业逻辑 所引起的管理上的问题成了很大的阻碍。而这些在双层结构中的阻碍在三层结构中已不存在 了。如今既要求开发服务的商业功能,还要开发访问数据库和其他资源的代码,这是很复杂 的,因为每一种多层结构的服务器都有它自己的应用模型,因此组建一支富有经验的开发队 伍将是困难的。另外,随着服务规模的扩大,为了降低开支和加快响应速度,也要经常对其 底层代码进行修改。 J2EE 应用模型定义了一种建筑模型来让多层应用程序实现服务,并消除了以上问题,提 供了可伸缩的、易访问的和易于管理的方法。 J2EE 应用模型把实现多层结构服务的工作划分 为两部分:一是开发者实现商业和表达逻辑;二是由 J2EE 平台提供标准的系统服务。开发 者可以依赖于这个平台为在开发中间层服务中遇到的系统级硬件问题提供解决方案。 J2EE 应用模型使中间层应用程序具有编译一次、任意运行的特点,这种标准模型最小化 了培训开发人员的费用。 J2EE 应用模型通过在建立多层应用程序中最小化其复杂程度,为简 化和加速应用程序的开发迈出了重要的一步。 J2EE 应用模型起始于 Java 语言和 Java 虚拟机,它们提供的不断提高的便携性、安全性 和开发人员能力的提高是应用程序模型的基础。应用程序模型通常包括 JavaBean 组件模型, JavaBean 使编写普通功能的代码更加容易,利用 JavaBean 的开发工具可以可视地定制、组合 这些组件。 J2EE 平台定义了一种标准的、公开的存取控制规则,当程序在企业平台上开发时就已被程 序员定义和解释了。 J2EE 提供一个标准的、注册机制以便应用程序不用将这些注册机制和逻辑 相混合,相同的工作要执行于大量的不同的环境中而并不需要改变源代码。例如: J2EE 应用 程序开发人员可以指定几个安全级别(如用户,管理员,超级用户) ,使应用程序在执行限制 操作之前能够容易判断限制级。 J2EE 应用程序模型的一个主要优点便是在中间层的多层应用程序。在 J2EE 平台,中间 层商业功能是由企业 JavaBean(Enterprise JavaBean,EJB)实现的。这些企业 Java Beans 允 许服务开发者集中于商业逻辑,并且让 EJB Server(企业 JavaBean 服务器)做处理、传送和 升级服务的复杂工作。 Java Server Pages(JSP)技术和 Servlets(服务接口)向客户层提供了 易于访问的因特网型服务的中间层功能。 JSP 技术使用户接口开发者更加容易地向任何浏览 器用户提供动态页面的服务。 Servlets 让基于 Java 技术的开发者有更大的自由在 Java 语言中 提供完全的动态服务。 J2EE 平台支持几种类型的客户。许多 J2EE 服务被设计为浏览器用户服务,这些服务通过 动态生成网页和表单来同客户端实现交互,而 JSP 和 Servlets 是通过让这些商业数据通过某种 方式格式化,使客户端更容易来同它工作。这些客户端可以是运行于浏览器中的 JavaApplet 和 基于 Java 技术的程序。要注意的是,安全是多层结构的关键部分,在 J2EE 中安全性总是通过 Java 语言案例教程 10 平台和管理员来解决的。在大多数案例中,服务方或客户方都不需要开发者与安全逻辑。 J2EE 应用程序模型的一个重要的目标就是使应用程序最小化。 实现这一点的一种方法是 提高在 J2EE 平台上运行普通任务的负荷,这些普通任务包括强制一个应用程序的安全目标, 执行它的交易处理,链接它所需要的组件。 J2EE 提供了一种简单的、公开的方式来说明这些行为。这些说明被分散地放在各部分代 码中和开发描述中。开发描述是应用程序包的一部分,这些基于 XML 的说明使应用程序开 发者不用修改任何组件就可以改变应用程序的作用。 SUN 公司关于 J2EE 的官方网站如图 1-2 所示,读者可以在上面找到官方的最新消息。 1.7 网 上 资 源 在因特网上,关于 Java 的有关资源非常丰富,以下几个是比较有名的网站。 1.http://www.javaworld.com/ 这是一个 Java 网络杂志,提供关于 Java 最新信息,如图 1-3 所示。 图 1-3 JavaWorld 的网站 图 1-2 J2EE 的官方网站 第 1 章 Java概述 11 2.http://java.sun.com/applets/ 这是由 Sun 公司提供的有关 Applets 的资源,如图 1-4 所示。 3.http://www.javalobby.org/ 这是一个收藏了很多 Java 资源的网站,如图 1-5 所示。 4.http://www.javashareware.com/ 这是网上关于 shareware 的收藏地,如图 1-6 所示。 图 1-4 Applets 的官方网站 图 1-5 Javalobby 的网站 Java 语言案例教程 12 【本章小结】 本章对 Java 语言及其发展历史作了简单介绍, Java 是 SUN 公司开发的一种程序设计语 言,和传统的 C++语言相比,具有纯面向对象、简单、强健、安全、平台独立、多线程、内 存管理和资源自动回收等特点。接着简要对比了 Java 技术和.NET 技术,同时还介绍了目前 Java 应用得比较广的两个领域: J2ME 和 J2EE。最后,向读者推荐了几个比较流行的 Java 网 络资源,以方便读者能获得更丰富的 Java 知识。 【习题】 一、选择题 1.Java 是什么?( ) A.一种虚拟机 B.一种语言 C.一个类库 D.以上选项都是 2.Java 语言采用什么机制来删除对象?( ) A.Java 语言的 delete 关键字 B.析构方法 C.由程序员手工删除对象 D.内存自动回收机制 3.Java 语言是怎样实现其健壮性的?( ) A.禁止指针操作 B.比 C++ 语言更严格的类型检查 C.带边界检查的真正数组 D.以上选项都是 4.针对嵌入式设备及消费类电器的 Java 开发平台是( )。 A.J2SE B.J2EE C.J2ME D.以上都是 5.JVM 的哪部分用来保证所有字节码都是有效的?( ) A.解释器 B.Bytecode 校验器 C.Class 加载器 D.以上都是 二、判断题 1.与 C++不同,Java 能自动删除对象,回收内存空间。 图 1-6 Javashareware 的网站 第 1 章 Java概述 13 2.Java 语言和 C++ 语言都支持结构化编程。 3.J2EE 用于开发在消费设备(如手机)上运行的 Java 程序。 4.在 Java 语言中,程序员能进行任意的指针操作。 5.类是 Java 程序的基本组成单元。 三、简答题 1.Java 的平台是什么?其运行原理与一般的操作平台有什么不同? 2.试列出 5 个 Java 语言的特性,并具体说明。 3.试比较 Java 语言和 C++ 语言。 4.Java 语言主要有哪些方面的应用? 第 2 章 第一个 Java 程序 【学习目标】 1.了解 Java 的开发环境及开发工具,掌握如何安装和配置开发环境。 2.熟悉不同类型的 Java 程序以及如何编译、运行 Java 程序。 3.了解常用的集成开发环境。 2.1 JDK 的安装 2.1.1 下载 JDK Java 的官方站点是 http://java.sun.com,所以要获取最新版的 JDK (Java Developer Kit, Java 开发工具 ),就应该到 http://java.sun.com 去查找。其官方网站如图 2-1 所示。 JDK 就是 Java 的 SDK(Software Development Kit,软件开发工具)中的一部分,有标准 版和企业版之分。标准版可以免费使用、编写软件并发行;而企业版虽然也可以免费使用、 编写软件,但如果你想出于商业目的发行用企业版编写的软件,那么就得支付一定的费用。 为了学习和编写 Java 程序,通常需要下载两个文件包: Java 2 SDK 和 Java 2 SDK 文档。 Java 2 SDK 包含了 Java 开发工具、 Java 运行环境 JRE,以及 Java API 源码等,可以根据需 要选择 Windows 版或者 Linux 版。而 Java 2 SDK 文档是一个压缩包,以网页的形式提供了 有关 Java API 非常详尽的开发文档,是 Java 程序开发的最佳参考资料。 图 2-1 JDK 的官方网站 第 2 章 第一个 Java 程序 15 说明:JRE 基本上就是没有开发工具和示例的 SDK,它比 SDK 小,一般被下载和安装 在运行 Java 程序的计算机,而非开发程序的计算机上( Java 程序可以运行在开发程序的计算 机上,因为 SDK 包含了 JRE 的副本) 。 2.1.2 安装 JDK 安装 JDK 将以 JDK 1.4.2(Java 2 SDK, v 1.4.2 Release)为例。 1.系统需求 (1)在 Windows 下安装 JDK 的系统需求 操作系统: Windows 95/98/ME Windows NT 4.0(需要安装 Service Pack 5 以上) Windows 2000 专业版/服务器版 Windows XP 家庭版/专业版 内存: 基本图形界面应用程序需要 32MB Applet 需要 48MB 较复杂的图形界面应用程序需要更大的内存 CPU:奔腾(Pentium)166 及更快的 CPU 硬盘空间: 70MB 以上 (2)在 Linux 下安装 JDK 的系统需求 Linux 核心(Kernel)2.2.12 版本及以上 glibc 2.2.2-11 版本及以上 16 位真彩 KDE 或者 Gnome 最少 32MB 内存,推荐使用 48MB 内存 至少 75MB 硬盘空间 2.在 Windows 下安装 JDK 下载的 Windows 版本的 JDK 安装程序是一个以 .exe 结尾的可执行文件,如 JDK 1.4.2 预 发行版的 Windows 安装程序就是 j2sdk-1_4_2-rc-win.exe。直接运行该安装程序,并根据向导 的提示就可以安装好 JDK1.4.2 RC for Windows。需要注意的是,如果是在 Windows 2000/XP 下安装,需要先取得管理员权限。在安装过程中需要设置安装的位置,请记住这个位置,因 为在后面设置 PATH 和 CLASSPATH 中需要用到这个路径。 提示:在安装 JDK 或 SDK 的任何版本前,最好先卸载以前安装的 JDK 或 SDK 的任何一 个版本。 3.在 Linux 下安装 JDK Linux 版的 JDK 安装程序有两种格式: GNUZIP Tar 格式( j2sdk-1_4_2-rc-linux-i386.bin) 和 RedHat RPM 格式( j2sdk-1_4_2-rc-linux-i386-rpm.bin)。下面分别对使用这两个安装文件 Java 语言案例教程 16 的安装步骤进行说明。 (1)安装 GNUZIP Tar 格式的安装程序 j2sdk-1_4_2-rc-linux-i386.bin 是一个自解压文件,可直接运行(命令: ./j2sdk-1_4_2- rc-linux-i386.bin)。运行它之后会显示一个许可信息,同意之后,安装程序会将 JDK 解压在 当前路径下的一个名为 j2sdk1.4.2 的目录中。所以,如果你想把 JDK 安装到某个位置,就可 以把该安装程序文件拷贝到那个位置,再运行安装程序;或者运行安装程序后,将解压出来 的 j2sdk1.4.2 目录移动到你想要的位置。 使用这种格式的安装程序,优点是安装位置可自主决定。 (2)安装 RedHat RPM 格式的安装程序 相对于安装 GNUZIP Tar 格式的安装程序,安装 RedHat RPM 格式的安装程序要麻烦 一些。 j2sdk-1_4_2-rc-linux-i386-rpm.bin 也是一个自解压文件,同样会首先显示许可协议。不 过它解压出来的不是一个目录,而是一个 .rpm 文件( j2sdk-1_4_2-rc-linux-i386.rpm)。稍后, 将使用 rpm 命令进行安装,在此之前,需要获得 root 权限。 如果安装过 JDK 1.4 的测试版本(beta, beta2, bate3),那么应该先卸载它们。如果你不 知道是否安装过,可以使用 rpm-qa 来检查,例如: rpm -qa | grep j2sdk-1.4 如果 Linux 中安装有 JDK 1.4 测试版,它就会被列出来。那么,接下来就应该使用 rpm-e 来删除它,例如: rpm -e j2sdk-1.4.0-beta rpm -e j2sdk-1.4.0-beta2 rpm -e j2sdk-1.4.0-beta3 直到现在,我们才能安装最新下载的 JDK 1.4.2,使用 rpm-ivh,例如: rpm -ivh j2sdk-1_4_2-rc-linux-i386.rpm 2.1.3 设置 Java 的环境变量 通常,需要设置三个环境变量: JAVA_HOME、PATH 和 CLASSPATH。 JAVA_HOME:该环境变量的值就是 Java 所在的目录,一些 Java 版的软件和一些 Java 的 工具需要用到该变量,设置 PATH 和 CLASSPATH 的时候,也可以使用该变量以方便设置。 PATH:指定一个路径列表,用于搜索可执行文件。执行一个可执行文件时,如果该文件不 能在当前路径下找到,则依次寻找 PATH 中的每一个路径,直至找到。或者找完 PATH 中的路 径也不能找到, 则报错。Java 语言的编译命令 (javac),执行命令(java)和一些工具命令 (javadoc,jdb 等)都在其安装路径下的 bin 目录中。因此,应该将该路径添加到 PATH 变量中。 CLASSPATH:也指定一个路径列表,用于搜索 Java 语言编译或者运行时需要的类。在 CLASSPATH 列表中除了可以包含路径外,还可以包含 .jar 文件。Java 查找类时会把这个 .jar 文件当作一个目录来进行查找。通常,我们需要把 JDK 安装路径下的 jre\lib\rt.jar 包含在 CLASSPATH 中。 PATH 和 CLASSPATH 都指定路径列表,列表中的各项(即各个路径)之间使用分隔符 分隔。在 Windows 下,分隔符是分号“ ;”,而在 Linux 下,分隔符是冒号“ :”。 第 2 章 第一个 Java 程序 17 下面分别说明三个环境变量在 Windows 和 Linux 下如何设置,不过在此之前,需要做个 假设。假设 JDK 在 Windows 下的安装路径是 C:\jdk\,在 Linux 下的安装路径是 /usr/local/jdk/。 那么,安装后的 JDK 至少会包括如下内容: C:\jdk (/usr/local/jdk) |-- bin |-- demo |-- include |-- lib |-- jre | |-- bin | |-- lib | |-- javaws 1.在 Windows 下设置 在 Windows 98 和 Windows ME 下使用 set 命令设置环境变量,为了使每一次启动计算机 都设置这些环境变量,应该在系统盘根目录下的 autoexec.bat 文件中进行设置,例如: set JAVA_HOME=C:\jdk set PATH=%JAVA_HOME%\bin;C:\Windows;C:\Windows\Command set CLASSPATH=%JAVA_HOME%\jre\lib\rt.jar; 有些版本的 Windows 不能用“ %变量名%”来替换环境变量的内容,那么就只好直接写 C:\jdk 而不是 “%JAVA_HOME%”了。另外,C:\Windows 和 C:\Windows\Command 是 Windows 自动加入的,所以可以从设置中去掉。如果在 autoexec.bat 中已经设置了 PATH,那只需将 “%JAVA_HOME%\bin”加到原来设置 PATH 的那条语句中就行了。 CLASSPATH 也可以根据需要设置或者加入其他的路径,比如你想把自己写的一些类放 在 C:\java 中,就可以把 C:\java 也添加到 CLASSPATH 中去,例如: set CLASSPATH=%JAVA_HOME%\jre\lib\rt.jar;C:\java; 注意,在 CLASSPATH 中包含了一个当前目录“ .”。包含了该目录后,就可以到任意目 录下去执行需要用到该目录下某个类的 Java 程序,即使该路径并未包含在 CLASSPATH 中 也可以。原因很简单,虽然没有明确地把该路径包含在 CLASSPATH 中,但 CLASSPATH 中 的“.”在此时就代表了该路径。 在 Windows 2000 或者 Windows XP 中,需要用右键单击桌面上的 “我的电脑” ,选择“属 性”,弹出“系统特性”窗口,选择“高级”( XP 直接在属性中选择高级),然后选择“环 境变量”,在“环境变量”窗口中编辑 java_home 和 classpath,如图 2-2 和图 2-3 所示。 图 2-2 JAVA_HOME 的配置 图 2-3 CLASSPATH 的配置 Java 语言案例教程 18 提示:环境变量设置完成后,在 DOS 窗口下,敲入 java-version 并回车后,如果能得到 所安装的 J2SDK 的版本信息,则表示环境变量设置正确。反之则表示环境变量设 置失败。 2.在 Linux 下设置 Linux 下使用“变量名 =变量值”设置变量,并使用 export 命令将其导出为环境变量。为 了使每一次登录都自动设置好这些变量,你需要在 ~/.bash_profile 里或者 ~/.bashrc 里进行设 置,例如: export JAVA_HOME=/usr/local/jdk export PATH=$JAVA_HOME/bin:$PATH export CLASSPATH=$JAVA_HOME/jre/lib/rt.jar:. 设置 PATH 时用的$JAVA_HOME 是指替换变量 JAVA_HOME 的值到$JAVA_HOME 所 在位置。如上句实际就是 export PATH=/usr/local/jdk/bin:$PATH。这句中$PATH 也是同样的 作用,不过这里的 PATH 是指以前设置的 PATH 变量的值,而非本次设置 PATH 变量的值。 2.2 Java 程序开发流程 Java 程序分为两种: Application(应用程序)和 Applet(小程序)。各自编写的方法不 同,其比较列表见表 2-1。 表 2-1 Application 和 Applet 程序编写比较表 Application Applet 程序格式特征 有 main() 有 init(),start(),stop(),destroy() 存储文件格式 .java 文件 .java 文件 编译程序代码 Javac.exe Javac.exe 产生文件格式 .class 文件 .class 文件 运行程序方式 使用 java.exe 直接运行 先建立一个嵌入此 .class 的.html 文件,再使用 appletviewer.exe 或浏览器查看此 .html 文件 下面简要介绍一下 Application 和 Applet 的开发流程,并对每一种 Java 程序,给出一个 实例。 2.2.1 应用程序 Application 具体开发流程如图 2-4 所示。 下面,给出一个最简单的 Java 应用程序的例子。 第 2 章 第一个 Java 程序 19 1.编辑 在文本编辑器中输入以下程序代码: public class Hellojava { public static void main ( String args[ ] ) { System.out.println("Hello, java"); } } Java 源程序中语句所涉及的小括号、大括号及标点符号都是英文状态下输入的括号和标 点符号,比如 ''Hello, java''中的引号必须是英文程序状态下的引号,而引号里面的符号不受汉 字字符或英文字符的限制。 文本编辑器 Java 原始文件 (.java) 编译器 Javac 错误 Java 类文件 ( l ) 解释器 Java 程序结束 正确 否 否 是 图 2-4 Application 开发流程 Java 语言案例教程 20 就这个程序来谈谈 Java 程序的特点: z 一个 Java 源程序是由若干个类组成的。如果学过 C 语言,就会知道一个 C 源程序 是由若干个函数组成的。上面的这个 Java 应用程序简单到只有一个类,类的名字叫 Hellojava。 z class 是 Java 语言的关键字,用来定义类。 public 也是关键字,说明 Hellojava 是一 个 public 类(后面将会系统地学习类的定义和使用) 。第一个大括号和最后一个大 括号以及它们之间的内容叫做类体。 z public static void main (String args[ ])是类体中的一个方法,之后的两个大括号以 及之间的内容叫做方法体。 一个 Java 应用程序必须有一个类且只有一个类含有这样 的 main 方法,这个类称为应用程序的主类。 public、static 和 void 分别是对 main 方 法的说明。在一个 Java 应用程序中 main 方法必须被说明为 public static void。 z String args[ ]声明一个字符串类型的数组 args[ ](注意 String 的第一个字母是大写 的),它是 main 方法的参数。 main 方法是程序开始执行的位置。 现在将源文件保存到 C:\code\中,并命名为 Hellojava.java。注意不可写成 hellojava.java, 因为 Java 语言是区分大小写的。 源文件的命名规则是:如果源文件中有多个类,那么只能有一个类是 public 类;如果有 一个类是 public 类,那么源文件的名字必须与这个类的名字完全相同,扩展名是 .java。如果 源文件没有 public 类,那么源文件的名字只要和某个类的名字相同,并且扩展名是 .java 就可 以了。 注意:Java 语言是大小写严格区分的程序设计语言。书写时一定注意大小写不能混淆。 2.编译 当创建了 Hellojava.java 这个源文件后,就要使用 Java 编译器(javac.exe)对其进行编译: C:\code\> javac Hellojava.java 编译完成后生成一个 Hellojava.class 文件,该文件是字节码文件,它被存放在与源文件 相同的目录中。 如果 Java 源程序中包含了多个类,那么用编译器 javac 编译完源文件后将生成多个扩展 名为.class 的文件,每个扩展名是 .class 的文件中只存放一个类的字节码,其文件名与该类的 名字相同。这些字节码文件将被存放在与源文件相同的目录中。如果对源文件进行了修改, 那么就必须重新编译,再生成新的字节码文件。 注意:如果在安装 SDK 1.4.2 时没有另外指定目录, javac.exe 和 java.exe 被存放在 C:\j2sdk1.4.2_05 目录。如果想在任何目录下都能使用编译器和解释器,那么应在 DOS 提示 符下输入: path c:\j2sdk1.4.2_05。 3.运行 使用 Java 解释器(java.exe)运行程序: C:\code\> java Hellojava,屏幕将显示如图 2-5 所示的信息。 第 2 章 第一个 Java 程序 21 当 Java 应用程序中有多个类时, java 命令后的类名必须是包含了 main 方法的那个类名, 即主类的名字。 2.2.2 小程序 Applet 的具体开发流程如图 2-6 所示。 同样,下面我们给出一个最简单的 Applet 的例子。 1.编写 在文本编辑器中输入以下程序代码: import java.applet.*; import java.awt.*; public class Hello extends Applet { public void paint (Graphics g) { g.setColor(Color.red); g.drawString("Hello, java", 5, 30); } } 一个 Java Applet 也是由若干个类组成的,但是一个 Java Applet 不再需要 main 方法,不 过必须有且只有一个类扩展了 Applet 类,即它是 Applet 类的子类。这个子类叫做这个 Java Applet 的主类, Java Applet 的主类必须是 public 的。Applet 类是系统提供的类。当保存上面 的源文件时,必须命名为 Hello.java,并将这个文件保存在 C:\code 目录下。 注意:上述源程序中使用了 import 语句,这是因为要使用系统提供给我们的 Applet 类。 Applet 类在包 java.applet 中。包 java.applet 中有很多类,Java 把一些相关的类放在一起,并 称做一个包,这里 java.applet 是一个包的包名。 java.awt 也是一个包,提供了 GUI 设计所使 用的类和接口, Graphics 是包 java.awt 中的一个类。 图 2-5 Hellojava 的运行结果 Java 语言案例教程 22 2.编译 在 DOS 提示符下输入以下内容: C:\code\> javac Hello.java 编译成功后, C:\code 目录下会生成一个 Hello.class 文件。如果源文件有多个类,将生成 多个.class 文件, 都和源文件在同一文件夹里。 如果对源文件进行了修改, 那么必须重新编译, 再生成新的字节码文件。 3.运行 Java Applet 必须由浏览器来运行, 因此必须编写一个超文本文件 (含有 applet 标记的 html 文件),再调用浏览器来运行这个 Java Applet。 图 2-6 Applet 的开发流程 文本编辑器 Java 原始文件 (.java) 编译器 Javac 错误 Java 类文件 (.class) appletviewer.exe 或浏览器 程序结束 否 否 是 Html 文件 文本编辑器 正确 第 2 章 第一个 Java 程序 23 下面是一个最简单的超文本文件,通知浏览器运行我们编写的 Java Applet。我们可以使 用记事本编辑这个文件。 超文本中的标记 和通知浏览器运行一个 Java Applet,code 通知浏览 器运行哪个 Java Applet。code 后面的“=”指出主类的字节码文件,当然这个字节码文件的 扩展名是 .class,而它的名字和源文件的名字必须是相同的。 width、height 规定了这个 Java Applet 的宽度和高度,单位是像素。要想让浏览器运行一个 Java Applet, 标记中的 code、height、width 都是必需的。另外还有一些可选的项,如 vspace,设置小程序 与其周围对象的垂直距离; hspace,设置水平距离, 等等。 现在,把上面编辑的文件命名为 Hello.html(扩 展名必须是.html,但是名字不必是 Hello,可以任意 起一个名字)。把 Hello.html 保存在 C:\code,即和 Hello.class 在同一目录里。如果不是这样,必须在文 件 Hello.html 中增加选项 codebase,来指定小程序中 的.class 文件所在的目录。 现在可以使用 JDK 提供的 appletviewer 来调试小 程序,在 DOS 命令行执行: C:\code\>appletviewer Hello.html 结果如图 2-7 所示。 提示:同样可以直接在浏览器中打开文件 Hello.html 来运行小程序。 2.2.3 小程序的生命周期 Applet 的生命周期分为四个阶段,各阶段分别由 init,start,stop 和 destroy 四种方法来 具体体现,如图 2-8 所示。这些方法都是 Applet 类的成员,可以继承这些方法,也可以重写 这些程序,覆盖原来定义的方法。 1.public void init() 此方法通知 Applet,方法已经被装入系统,在第一次调用 start 方法之前总是先调用它。 图 2-8 Applet 的生命周期 start() init() stop() destroy() 图 2-7 Applet 的运行结果 Java 语言案例教程 24 init 方法是 Applet 运行的起点。如果需要执行初始化任务,可以在 Applet 的子类中重载该方 法,例如,在 init 方法中创建线程,而在 destroy 方法中消灭( destroy)它们。 2.public void start() 此方法通知 Applet 开始执行, 当调用 init 方法或者在网页中再次访问时被调用。 在 Applet 的子类中重载该方法,将每次访问该网页需执行的操作放入其中,例如,一个含有动画的 Applet 可以使用 start 方法恢复动画。 3.public void stop() 此方法通知 Applet 停止执行,当含有该 Applet 的网页被其他页代替时调用该方法,也正 是在 Applet 被消灭之前。在 Applet 的子类中重载该方法,将每次网页不再可见时需执行的操 作放入其中。 4.public void destroy() 此方法通知 Applet,它正在被收回,应该释放已分配给它的所有资源, stop 方法总是在 该方法调用之前被调用。 注意:Applet 类中没有提供 init,start,stop 和 destroy 方法的任何实现( implementation), 并且,它们都是被浏览器或 Applet 查看器(appletviewer)调用。 2.3 常用的集成开发环境 除了 JDK 这个最基本的开发环境外,另外有很多第三方开发了许多集成开发环境( IDE) 它们提供图形操作界面,大大提高了开发效率,但是这些集成开发环境都是非独立产品,都 是架构在 JDK 基础上的。 下面是目前比较流行的一些开发工具及下载地址。 (1)JBuilder:http://www.borland.com/ (2)NetBean:http://www.netbeans.org/ (3)Sun One Studio:http://wwws.sun.com/software/sundev/jde/index.html (4)Eclipse:http://www.eclipse.org/ (5)WSAD:http://www-3.ibm.com/software/awdtools/studioappdev/about/V5.html (6)JCreator:http://www.jcreator.com/ (7)Intellij IDEA:http://www.intellij.com (8)JDeveloper:http://otn.oracle.com/products/jdev/content.html (9)BlueJ:http://www.bluej.org/index.html (10)RealJ:http://www.realj.com/ (11)Gel:http://www.gexperts.com/ (12)CodeGuide:http://www.omnicore.com/index.jsp (13)UltraEdit:http://www.ultraedit.com/ (14)EditPlus:http://www.editplus.com/ Borland JBuilder 是全球第一个跨平台 Java 集成开发环境, 可以用于构建符合工业标准的 Java 应用系统,开发 EJB、Web、XML 以及数据库等各类应用程序。双向、可视化设计工具 第 2 章 第一个 Java 程序 25 使得我们可以快速地构建各种 J2EE 应用程序,并部署至多种应用程序服务器,包括 BEA WebLogic、IBM WebSphere、Sun ONE Application Server、Oracle 10g Application Server 以及 整合于 JBuilder 的 Borland Enterprise Server 和 Apache Tomcat;在 JBuilderX 中还可以使用 JBoss 作为开发调试时的应用程序服务器。 JBuilder 开发界面对于 Delphi 和 C++ Builder 开发人员来说,可能会感觉十分的亲切,默 认的界面除了不像 Delphi 那样各个窗体是独立的以外,其他的地方几乎和 Delphi 一模一样。 其界面如图 2-9 所示。 图 2-9 JBuilder 的运行界面 但是,由于 JBuilder 本身就是用 Java 编写的,效率不高,速度较慢,对机器的硬件要求 较高。如果机器配置不高,或者对 Java 不是太熟悉,建议使用下面两种集成开发环境: Gel 和 NetBeans。 Gel 是目前比较流行的一个小型的集成编译环境,能实现简单的项目管理、拼写检查、 关键字高亮、括号对齐,集成 JavaDoc、代码控制等功能。其运行界面如图 2-10 所示。 图 2-10 Gel 的运行界面 Java 语言案例教程 26 下面简要介绍一下 SUN 公司官方推荐的 NetBeans。NetBeans 3.6 是 NetBeans 集成开发 环境的下一个主要修订版。 3.6 版本进行了许多重大改进,包括对编辑器、窗口导航、 Web 和 J2EE 开发以及新任务列表功能进行了许多改进, 并且更新了对 Ant 构建工具和 JUnit 测 试框架的支持。 不管是构建基于 Java 技术的组件、网络应用程序、胖桌面客户机应用程序还是无线应用 程序,NetBeans 3.6 都提供了一个完整而强大的解决方案。 NetBeans 3.6 包含下列新特性和功能: z 全新的视窗系统,提供 Windows 平台的本机外观。 z 增强了导航和工作流。 z 2 层(tier)的 J2EE 1.4 支持(Servlet 2.4 和 JSP 2.0)。 z 通过支持 JSR-45 改进了 JSP 调试。 z 可以与 Sun Java System Application Server, Platform Edition 8 捆绑在一起。 z 代码编辑器功能增强,如智能括号和代码折叠。 z 通过 JUnit 支持集成测试。 z 通过与 JavaHelp 2.0 集成改进了在线帮助。 z 改进了 CVS 支持。 z 改进了任务列表窗口。 z 对 J2SE 1.5 (Tiger) Beta 版提供基本支持。 z 可以与 J2SE 1.4.2 捆绑在一起。 其运行界面如图 2-11 所示。 【本章小结】 本章是学习 Java 语言编程的第一步, 即如何搭建、 配置 Java 开发环境和编写第一个 Java 程序。Java 开发工具包 SDK 可以从 SUN 公司的官方网站下载,但是下载后需要安装和配置 Java 开发环境,才能正常编译、运行 Java 程序。通过第一个 Java 应用程序和 Java Applet 程 序,我们知道了 Java 程序主要分为两类: Application 和 Applet,并了解了它们开发流程和运 图 2-11 NetBeans 的运行界面 第 2 章 第一个 Java 程序 27 行方式。对 Java Applet 的生命周期也作了简单介绍。 最后,介绍了很多第三方公司的 Java 集成开发环境,其中包括比较流行的 Gel、NetBeans 和 JBuilder 等。 【习题】 一、选择题 1.在 Java 2 SDK 文件包中包含有( )。 A.Java 开发工具 B.Java 运行环境 C.Java API 码 D.以上选项都是 2.下面哪个方法是 Java Applet 运行的起点?( ) A.init() B.start() C.stop() D.destroy() 3.下面哪一个可以用来说明应用程序的 main 方法?( ) A.public static void B.static void C.void D.以上选项都不是 4.JDK 提供的用于调试小程序的程序是( )。 A.Java B.Javac C.Appletviewer D.以上选项都不是 5.全球第一个跨平台 Java 集成开发环境是( )。 A.Eclipse B.Sun NetBeans C.Borland JBuilder D.以上选项都不是 二、判断题 1.JRE 只支持运行 Java 程序,而不支持开发 Java 程序。 2.Java 程序主要分三类:Application、Applet 和 Servlet。 3.如果在 Windows 2000/XP 下安装 J2SDK,并不需要先取得管理员权限。 4.如果源文件中有一个类是 public 类,那么源文件的名字必须与这个类的名字完全相同。 5.Eclipse 是目前比较流行的一种 Java 集成开发环境。 三、简答题 1.怎样区分 Application 和 Applet? 2.开发和运行 Java 程序需要经过哪些过程? 四、实践题 1.下载安装 J2SDK,并设置其环境变量。 2.试写出 Application 和 Applet 的开发流程。 3.编写一个 Java Application 程序,在屏幕上显示如下信息: ****************************************************** Welcome to Java world,<你的姓名>! ****************************************************** 4.把上题改写为 Java Applet 程序并运行。 第 3 章 Java 语言基础知识 【学习目标】 1.掌握 Java 语言的数据类型和数据类型之间的转换。 2.掌握 Java 语言的运算符和表达式。 3.掌握 Java 语言的控制语句。 4.掌握 Java 语言数组的定义、建立及其使用方法。 5.掌握 Java 语言字符串 String 和 StringBuffer 的处理。 3.1 简单数据类型 3.1.1 标志符和保留字 1.标志符 程序员对程序中的各个元素加以命名时使用的命名记号称为标志符。 Java 语言中,标志 符是以字母、下画线“ _”、美元符号“ $”开始的一个字符序列,后面可以跟字母、下画线、 美元符号、数字。例如, identifier,userName,User_Name,_sys_val,$change 为合法的标 志符,但 2mail room#,%class 为非法的标志符。 注意:虽然一个标志符使用美元符号或下画线开始是合法的,但还是不要使用货币符号 或下划线开始,因为这样难于阅读,并且容易产生输入错误。 2.保留字 还有一些标志符具有专门的意义和用途,不能当做一般的标志符使用,称这些标志符为 保留字,也称为关键字。下面列出了 java 语言中的所有保留字: abstract,break,byte,byvalue,boolean,case,catch,class,char,const,continue, default,double,do,else,extends,false,final,float,for,finally,future,gemeric,oto, if,import,implements,inneroperatorint,interface,instanceof,long,length,native,new, null,package,private,protected,public,rest,return,switch,synchronized,short,static, super,threadsafe,try,true,this,transient,throw,throws,void,while。 注意:Java 语言中的保留字均用小写字母表示。 第 3 章 Java语言基础知识 29 3.1.2 数据类型 1.Java 语言中的数据类型划分 Java 语言的数据类型有简单数据类型和复合数据类型两种。 简单数据类型包括: z 整数类型( Integer):byte,short,int,long z 浮点类型( Floating):float,double z 字符类型( Textual):char z 逻辑类型( Logical):boolean 复合数据类型包括: z 类 z 接口 z 数组 2.常量和变量 常量:用保留字 final 来声明。其意义格式如下: final typeSpecifier varName=value[,varName[=value]…]; 如:final int NUM=100; 变量:它是 java 程序中的基本存储单元,它的定义包括变量名、变量类型和作用域几个 部分。其定义格式如下: typeSpecifier varName=value[,varName[=value]…]; 如:int count; char c='a'; 变量的作用域指明可访问该变量的一段代码,声明一个变量的同时也就指明了变量的作 用域。按作用域来分,变量可以有下面几种:局部变量、类变量、方法参数和异常处理参数。 在一个确定的域中,变量名应该是唯一的。局部变量在方法或方法的一个块代码中声明,它 的作用域为它所在的代码块(整个方法或方法中的某块代码) 。类变量在类中声明,而不是在 类的某个方法中声明,它的作用域是整个类。方法参数传递给方法,它的作用域就是这个方 法。异常处理参数传递给异常处理代码,它的作用域就是异常处理代码部分。 3.1.3 简单数据类型 1.布尔型(boolean)数据 布尔型数据只有两个值 true 和 false,且它们不对应于任何整数值。布尔型变量的定义如下: boolean b=true; 2.字符型(char)数据 字符常量:字符常量是用单引号括起来的一个字符,如 'a','A'。 字符型变量:类型为 char,它在机器中占 16 位,其范围为 0~65 535。字符型变量的定 义如下: char c='a'; /*指定变量 c 为 char 型,且赋初值为 a*/ Java 语言案例教程 30 3.整型数据 整型常量: z 十进制整数:如 123,-456,0。 z 八进制整数:以 0 开头,如 0123 表示十进制数 83,-011 表示十进制数- 9。 z 十六进制整数:以 0x 或 0X 开头,如 0x123 表示十进制数 291,-0X12 表示十进制 数-18。 整型变量的位数及其范围见表 3-1。 表 3-1 整型变量的位数及其范围 数据类型 所占位数 数的范围 byte 8 -27~27-1 short 16 -215~215-1 int 32 -231~231-1 long 64 -263~263-1 注意:在表示一个长整型数时,最好使用 L(大写)后缀而非 l(小写)后缀,这是为了 与数字 1 区分。 4.浮点型(实型)数据 实型常量: z 十进制数形式:由数字和小数点组成,且必须有小数点,如 0.123, 1.23, 123.0。 z 科学计数法形式:如: 123e3 或 123E3,其中 e 或 E 之前必须有数字,且 e 或 E 后 面的指数必须为整数。 z float 型的值,必须在数字后加 f 或 F,如 1.23f。 实型变量的位数及其范围见表 3-2。 表 3-2 实型变量的位数及其范围 数据类型 所占位数 数的范围 float 32 3.4e-038~3.4e+038 double 64 1.7e-038~1.7e+038 例 3-1 简单数据类型的例子。 public class Assign { public static void main (String args [ ] ) { int x , y ; //定义 x,y 两个整型变量 float z = 1.234f ; //指定变量 z 为 float 型,且赋初值为 1.234 double w = 1.234 ; //指定变量 w 为 double 型,且赋初值为 1.234 boolean flag = true ; //指定变量 flag 为 boolean 型,且赋初值为 true char c ; //定义字符型变量 c String str ; //定义字符串变量 str String str1 = " Hi " ;//指定变量 str1 为 String 型,且赋初值为 Hi 第 3 章 Java语言基础知识 31 c = ' A ' ; //给字符型变量 c 赋值'A' str = " bye " ; //给字符串变量 str 赋值"bye" x = 12 ; //给整型变量 x 赋值为 12 y = 300; //给整型变量 y 赋值为 300 } } 3.1.4 简单数据类型中各类型数据间的优先关系和相互转换 1.不同类型数据间的优先关系 简单数据类型中不同类型数据间的优先关系如下(由低到高) : byte,short,char→ int → long → float → double 2.自动类型转换 整型、实型、字符型数据可以混合运算。运算中,不同类型的数据先从低级到高级转化 为同一类型,然后进行运算。数据转换的规则见表 3-3。 表 3-3 数据转换的优先级 操作数 1 类型 操作数 2 类型 转换后的类型 byte、short、char int int byte、short、char、int long long byte、short、char、int、long float float byte、short、char、int、long、float double double 3.强制类型转换 高级数据类型要转换成低级数据类型,需用到强制类型转换,如: int i; byte b=(byte)i; /*把 int 型变量 i 强制转换为 byte 型*/ 3.2 运算符和表达式 3.2.1 运算符 对各种类型的数据进行加工的过程成为运算,表示各种不同运算的符号称为运算符,参 与运算的数据称为操作数。运算符按操作数的数目来分,可有以下几种: z 一元运算符: ++,--, !,.,&,|,^,&&,||,>>,<<,>>>,==,!=,>,<, >=,<=,= z 二元运算符: +,-,*,/,%,x z 三元运算符:?: 基本的运算符按功能划分,有下面几类。 Java 语言案例教程 32 1.算术运算符 算术运算符包括: +,-,*,/,%,++,--。 例如: 3+2; a-b; i++; --i; 2.关系运算符 关系运算符包括: >,<,>=,<=,==,!=。 例如: count>3; I==0; n!=-1; 3.逻辑运算符 逻辑运算符包括: !,&&,|| 。 例如: flag=true; !(flag); flag&&false; 4.位运算符: 位运算符包括: >>,<<,>>>,&,|,^,~。 例如: a=10011101; b=00111001; 则有如下结果: a<<3,其结果为 11101000。 a>>3,其结果为 11110011 a>>>3,其结果为 00010011。 a&b,其结果为 00011001; a|b,其结果为 10111101。 ~a,其结果为 01100010; a^b,其结果为 10100100。 5.赋值运算符及其扩展赋值运算符 赋值运算符及其扩展赋值运算符包括: =,+=,-=,*=,/= 等。 例如: i=3; i+=3; //等效于 i=i+3; 6.条件运算符 条件运算符用“?”表示。 例如: result=(sum= =0 ? 1 : num/sum); 第 3 章 Java语言基础知识 33 7.其他 除以上基本运算符以外,还包括分量运算符“ .”、下标运算符“ []”、实例运算符 “instanceof”、内存分配运算符“ new”、强制类型转换运算符(类型)和方法调用运算符等。 例如: System.out.println("hello world"); int array1[]=new int[4]; 3.2.2 表达式 表达式是由操作数和运算符按一定的语法形式组成的符号序列。一个常量或一个变量名 字是最简单的表达式,其值即是该常量或变量的值;表达式的值还可以用做其他运算的操作 数,形成更复杂的表达式。 1.表达式的类型 表达式的类型由运算以及参与运算的操作数的类型决定,可以是简单类型,也可以是复 合类型。例如, 布尔型表达式: x&&y||z。 整型表达式: num1+num2。 2.运算符的优先次序 表达式的运算按照运算符的优先顺序从高到低进行,同级运算符从左到右进行。运算符 的优先次序如下: (1).,[], () (2)++, -- ! ,~ ,instanceof (3)new ,type (4)*, / ,% (5)+ ,- (6)>>, >>>, << (7)>,< ,>= ,<= (8)= = ,!= (9)& (10)^ (11)| (12)&& (13)|| (14)?: (15)= ,+=, -=, *= ,/= ,%=, ^= (16)&=, |= ,<<= ,>>= ,>>>= Java 语言案例教程 34 3.3 控 制 语 句 Java 语言通过控制语句来执行不同的程序流,完成一定的任务。程序流是由若干个语句 所组成的,语句可以是单一的一条语句,如 c=a+b,也可以是用大括号括起来的一个复合语 句。Java 语言中的控制语句有以下几类。 z 分支语句: if…else,switch。 z 循环语句: while,do…while,for。 z 与程序转移有关的跳转语句: break,continue,return。 z 例外处理语句: try…catch…finally,throw。 z 注释语句: //,/* */, /** */。 3.3.1 分支语句 分支语句提供了一种控制机制,使得程序的执行可以跳过某些语句不执行,而转去执行 特定的语句。 1.条件语句 if…else 条件语句的定义格式如下: if(boolean-expression) statement1; [else statement2;] z 条件表达式须是关于表达式或逻辑表达式。 z 当程序执行到 if 语句时,先判断条件表达式,如果值为“真” ,则执行语句组 1,然 后跳过 else 语句,继续执行后面的语句。如果条件表达式为“假” ,则跳过语句组 1, 执行 else 语句。 z 如果语句组不是单独一个语句,就要用大括号{}括起来。 2.多分支语句 switch 多分支语句的定义格式如下: switch (expression){ case value1 : statement1; break; case value2 : statement2; break; case valueN : statementN; break; [default : defaultstatement; ] } z 表达式( expression)的返回值类型必须是以下几种类型之一: int,byte,char,short。 … 第 3 章 Java语言基础知识 35 z case 子句中的值( valueN)必须是常量,而且所有 case 子句中的值应是不同的。 z default 子句是可选的。 z break 语句用来在执行完一个 case 分支后,使程序跳出 switch 语句,即终止 switch 语句的执行(在一些特殊情况下,多个不同的 case 值要执行一组相同的操作,这时 可以不用 break)。 注意:一个 case 语句不用 break 语句结束会引起下一个 case 语句的执行,这常常会产生 难以查找的逻辑错误。 3.3.2 循环语句 循环语句的作用是反复执行一段代码,直到满足终止循环的条件为止。 Java 语言提供的 循环语句有: z while 语句 z do…while 语句 z for 语句 1.while 语句 while 语句的定义格式如下: [initialization] while (termination){ body; [iteration;] } z 条件表达式的值必须是布尔量。 z 当执行到 while 语句时,先判断条件表达式的值,如果为“真” ,则执行循环体,然 后返回条件表达式进行再次判断,如此反复,直到条件表达式的值为“假”时跳出 while 循环。 2.do…while 语句 do…while 语句的定义格式如下: [initialization] do { body; [iteration;] } while (termination); z 条件表达式的值必须是布尔量。 z 当程序执行到 do…while 语句时,首先无条件执行一次循环,然后计算条件表达式, 如果值为“真” ,则再执行循环体,然后到条件表达式进行再次判断,如此反复,直 到条件表达式的值为“假”时跳出 do…while 循环。 3.for 语句 for 语句的定义格式如下: Java 语言案例教程 36 for (initialization; termination; iteration){ body; } z for 语句执行时,首先执行初始化操作,然后判断终止条件是否满足,如果(即结果 为“真”)满足,则执行循环体中的语句,最后执行迭代部分。完成一次循环后,重 新判断终止条件。 z 初始化、终止以及迭代部分都可以为空语句 (但分号不能省略 ),三者均为空的时候, 相当于一个无限循环。 z 在初始化部分和迭代部分可以使用逗号语句,来进行多个操作。逗号语句是用逗号 分隔的语句序列。例如: for( i=0, j=10; i> D.! 6.二元运算符有多少个操作符?( ) A.1 B.2 C.3 D.4 7.数组索引是什么?( ) A.一个标志数组元素的整数表达式 B.一个标志数组元素的数字表达式 C.A 和 B D.以上选项都不是 8.下面哪种数据类型不能用于 switch 判断语句的表达式?( ) A.字节型 B.字符型 C.整型 D.布尔型 9.StringBuffer 类对象的初始容量为多少个字节?( ) A.16 B.32 C.64 D.8 10.“System.out.println("Welcome to JavaWorld!".substring(11,15));”将打印出什么结果? ( ) A.JavaW B.Java C.JavaWorld D.以上选项都不是 第 3 章 Java语言基础知识 45 二、判断题 1.标志符可以以数字开头。 2.null 是 Java 语言的一个关键字。 3.Java 语言的数据类型分为简单类型和复合类型, String 就是一种简单类型。 4.Java 语言中常量用 abstract 关键字来修饰。 5.float 型的值,必须在数字后加 f 或 F,如 4.56f。 6.条件运算符“ ?:”是一个三元运算符。 7.“>>>”运算符执行一个带符号的右移操作。 8.在 switch 判断语句中必须指定默认的 case 子句。 9.Java 语言会将一个 else 与其最近的 if 相关联。 10.字符串的第一个字符位于索引为 1 的位置上。 三、实践题 1.进行以下操作: (1)将 0X0A0A 转化为八进制以及十进制。 (2)将 00177 转化为十进制以及十六进制。 (3)将 378 转化为八进制以及十六进制。 2.下列程序执行后, a,b 的值各是什么? int i, a; float b; i=3; a=6/5*i; b=6/5.0f*i; 3.用 for 循环输出下面结果: * *** ***** ******* ********* 4.编写一个程序,可以对任意输入的 3 个数进行排序,按从大到小顺序输出,并同时输 出其平均值。 5.分别编写 Application 和 Applet 求 1!+2!+…+20! 的值。 6.编写一个程序求 1 000 内的全部素数。 第 4 章 Java 语言中的面向对象特性 【学习目标】 1.掌握面向对象程序设计技术和 Java 语言的面向对象特性。 2.掌握如何定义一个类和如何创建一个对象,了解类与对象的关系。 3.掌握 Java 的继承机制和实现多态的方法。 4.掌握 Java 的抽象类和接口的定义和使用。 5.掌握 Java 的内部类和匿名类的定义和使用。 4.1 面向对象技术基础 4.1.1 面向对象的基本概念 1.面向对象的基本思想 面向对象是一种新兴的程序设计方法,或者说是一种新的程序设计规范,其基本思想是 使用对象、类、继承、封装、消息等基本概念来进行程序设计。从现实世界中客观存在的事 物(即对象)出发来构造软件系统,并且在系统构造中尽可能运用人类的自然思维方式。开 发一个软件是为了解决某些问题,这些问题所涉及的业务范围称做该软件的问题域。其应用 领域不仅仅是软件,还有计算机体系结构和人工智能等。 2.对象的基本概念 对象是系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位。一个对象 由一组属性和对这组属性进行操作的一组服务组成。从更抽象的角度来说,对象是问题域或 实现域中某些事物的一个抽象,它反映该事物在系统中需要保存的信息和发挥的作用;它是 一组属性和有权对这些属性进行操作的一组服务的封装体。客观世界是由对象和对象之间的 联系组成的。 主动对象是一组属性和一组服务的封装体,其中至少有一个服务不需要接收消息就能主 动执行(称做主动服务) 。 3.类的基本概念 把众多的事物归纳、划分成一些类是人类在认识客观世界时经常采用的思维方法。分类 的原则是抽象。类是具有相同属性和服务的一组对象的集合,它为属于该类的所有对象提供 了统一的抽象描述,其内部包括属性和服务两个主要部分。在面向对象的编程语言中,类是 一个独立的程序单位,它应该有一个类名并包括属性说明和服务说明两个主要部分。类与对 象的关系就如模具和铸件的关系,类的实例化结果就是对象,而对一类对象的抽象就是类。 第 4 章 Java语言中的面向对象特性 47 4.消息 消息就是向对象发出的服务请求,它应该包含下述信息:提供服务的对象标志、服务标 志、输入信息和回答信息。服务通常被称为方法或函数。 4.1.2 面向对象的基本特征 1.封装性 封装性就是把对象的属性和服务结合成一个独立的相同单位,并尽可能隐蔽对象的内部 细节。它包含两个含义: (1)把对象的全部属性和全部服务结合在一起, 形成一个不可分割的独立单位 (即对象) 。 (2)信息隐蔽,即尽可能隐蔽对象的内部细节,对外形成一个边界〔或者说形成一道屏 障〕,只保留有限的对外接口使之与外部发生联系。 封装的原则在软件上的反映是: 要求使对象以外的部分不能随意存取对象的内部数据 (属 性),从而有效地避免了外部错误对它的“交叉感染” ,使软件错误能够局部化,大大减少查 错和排错的难度。 2.继承性 特殊类的对象拥有其一般类的全部属性与服务,称作特殊类对一般类的继承。例如,轮 船、客轮;人、大人。一个类可以是多个一般类的特殊类,它从多个一般类中继承了属性与 服务,这称为多继承。例如,客轮是轮船和客运工具的特殊类。在 Java 语言中,通常我们称 一般类为父类(或超类) ,特殊类为子类。 3.多态性 对象的多态性是指在一般类中定义的属性或服务被特殊类继承之后,可以具有不同的数 据类型或表现出不同的行为。这使得同一个属性或服务在一般类及其各个特殊类中具有不同 的语义。例如: “几何图形”的“绘图”方法, “椭圆”和“多边形”都是“几何图形”的子 类,但其“绘图”方法功能不同。 面向对象程序设计方法通常有以下几种: OOA——Object Oriented Analysis 面向对象的分析 OOD——Object Oriented Design 面向对象的设计 OOI——Object Oriented Implementation 面向对象的实现 4.2 Java 语言的面向对象特性 4.2.1 类 类是 Java 语言中一种重要的复合数据类型,是组成 Java 程序的基本要素。它封装了一 类对象的状态和方法,是这一类对象的原形。一个类的实现包括两个部分:类声明和类体。 Java 语言案例教程 48 1.类声明 类声明的定义格式如下: [public][abstract|final]class className [extends superclassName] [implements interface-NameList] {…} 其中,关键字 public,abstract,final 说明了类的属性, className 为类名,superclassName 为该类父类的名字, interfaceNameList 为类所实现的接口列表。 注意:Java 语言没有限制 public,abstract 和 final 的次序。可以写成 public abstract,public final,abstract public 或者 final public(对编译器而言都是一样的) 。还有,按照惯例,类名的 首字母要大写。 2.类体 类体的定义格式如下: class className {[public | protected | private ] [static] [final] [transient] [volatile] type variableName; //成员变量 [public | protected | private ] [static] [final | abstract] [native] [synchronized] returnType methodName([paramList]) [throws exceptionList] {statements} //成员方法 } 3.成员变量 成员变量的声明方式如下: [public | protected | private ] [static] [final] [transient] [volatile] type variableName; //成员变量 其中,static 静态变量(类变量) ,相对于实例变量; final 常量;transient 暂时性变量, 用于对象存档; volatile 贡献变量,用于并发线程的共享; public、protected 和 private 是限定 词,具体内容在 4.2.3 节详述,这里不再赘述。 提示:用 final 关键字修饰的成员变量为常量,如果在声明一个常量时不初始化这个常量, 编译器将报告错误。 4.成员方法 方法的实现包括两部分内容:方法声明和方法体。 (1)方法声明 方法的声明方式如下: [public | protected | private ] [static] 第 4 章 Java语言中的面向对象特性 49 [final | abstract] [native] [synchronized] returnType methodName([paramList]) [throws exceptionList] //方法声明 {statements} //方法体 对方法声明中的限定词的含义说明如下。 static:类方法,可通过类名直接调用。 abstract:抽象方法,没有方法体。 final:方法不能被重写。 native:集成其他语言的代码。 synchronized:控制多个并发线程的访问。 (2)方法的调用 方法的调用包括方法名、返回类型和外部参数。其中参数的类型可以是简单数据类型, 也可以是复合数据类型(又称引用数据类型) 。 提示:按照惯例,方法名的首字母小写,而后面其他单词的首字母大写。如果方法不返回 值,则使用 void 作为返回类型。void 表示这个方法什么也不返回。 对于简单数据类型来说, Java 要实现的是值传递,方法接收参数的值,但不能改变这些 参数的值。如果要改变参数的值,则要用引用数据类型,因为引用数据类型传递给方法的是 数据在内存中的地址,方法中对数据的操作可以改变数据的值。 例 4-1 阅读程序,明确简单数据类型与引用数据类型的区别。 import java.io.*; public class PassTest{ float ptValue; public static void main(String args[]) { int val; PassTest pt=new PassTest(); val=11; System.out.println("Original Int Value is:"+val); pt.changeInt(val); //值参数 System.out.println("Int Value after Change is:" +val); /*参数值的修改,没有影响值参数的值 */ pt.ptValue=101f; System.out.println("Original ptValue is:"+pt.ptValue); pt.changeObjValue(pt); //引用类型的参数 System.out.println("ptValue after Change is:"+pt.ptValue); /* 引用参数值的修改,改变了引用参数的值 */ } public void changeInt(int value){ value=55; //在方法内部对值参数进行了修改 } Java 语言案例教程 50 public void changeObjValue(PassTest ref){ ref.ptValue=99f; //在方法内部对引用参数进行了修改 } } 运行结果如图 4-1 所示。 (3)方法体 方法体是对方法的实现,它包括局部变量的声明以及所有合法的 Java 指令。方法体中声 明的局部变量,其作用域在该方法内部。若局部变量与类的成员变量同名,则类的成员变量 被隐藏。 例 4-2 阅读程序,理解局部变量 z 和类成员变量 z 的作用域的不同。 import java.io.*; class Variable{ int x=0,y=0,z=0; //类的成员变量 void init(int x,int y) { this.x=x; this.y=y; int z=5; //局部变量 System.out.println("** in init**"); System.out.println("x="+x+" y="+y+" z="+z); } } public class VariableTest{ public static void main(String args[]){ Variable v=new Variable(); System.out.println("**before init**"); System.out.println("x="+v.x+" y="+ v.y+" z="+v.z); v.init(20,30); System.out.println("**after init**"); 图 4-1 PassTest 的运行结果 第 4 章 Java语言中的面向对象特性 51 System.out.println("x="+v.x+ " y="+ v.y+" z="+v.z); } } 运行结果如图 4-2 所示。 上例中用到了 this,这是因为 init() 方法的参数名与类的成员变量 x,y 的名字相同,而 参数名会隐藏成员变量,所以在方法中,为了区别参数和类的成员变量,必须使用 this。this 用在一个方法中引用当前对象,它的值是调用该方法的对象。返回值需与返回类型一致,或 者完全相同,或是其子类。当返回类型是接口时,返回值必须实现该接口。 提示:不能在类方法(static 修饰的方法)中使用 this 关键字,因为类方法与类相关联,而 不与对象相关联。 5.方法重载 方法重载是指多个方法享有相同的名字,但是这些方法的参数必须不同,或者是参数的 个数不同,或者是参数类型不同。返回类型不能用来区分重载的方法。 参数类型的区分度一定要充分,例如不能是同一简单类型的参数,如 int 与 long。 例 4-3 方法重载示例。 import java.io.*; class MethodOverloading{ void receive(int i) { System.out.println("Receive one int data"); System.out.println("i="+i); } void receive(int x, int y) { System.out.println("Receive two int datas"); System.out.println("x="+x+" y="+y); 图 4-2 VariableTest 的运行结果 Java 语言案例教程 52 } } public class MethodOverloadingTest{ public static void main(String args[]) { MethodOverloading mo=new MethodOverloading(); mo.receive(1); mo.receive(2,3); } } 运行结果(编译器会根据参数的个数和类型来决定当前所使用的方法)如图 4-3 所示。 提示:要想得到更多方法重载的例子,查看 J2SDK API 文档 String 类的 valueOf ( )方法 和 StringBuffer 类的 append ( )方法。 6.构造方法 z 构造方法是一个特殊的方法。 Java 语言中的每个类都有构造方法,用来初始化该类 的一个对象。 z 构造方法具有和类名相同的名称,而且不返回任何数据类型。 z 重载经常用于构造方法。 z 构造方法只能由 new 运算符调用。 例 4-4 构造方法示例。 class Point{ int x,y; Point(){ x=0; y=0; } Point(int x, int y){ 图 4-3 MethodOverloadingTest 的运行结果 第 4 章 Java语言中的面向对象特性 53 this.x=x; this.y=y; } } 注意:如果一个构造方法的声明带一个返回类型或将构造方法声明为抽象的,编译器将 报告出错。在这里把 void 也看成是一种返回类型,所以在声明构造方法时也不能用 void 关 键字。 4.2.2 对象 类实例化可生成对象,对象通过消息传递来进行交互。消息传递 ,即激活指定的某个对象 的方法以改变其状态或让它产生一定的行为。一个对象的生命周期包括三个阶段:生成、使 用和消除。 1.对象的生成 对象的生成包括声明、实例化和初始化。其定义格式为: type objectName=new type([paramlist]); 声明:“type objectName”声明一个对象。声明并不为对象分配内存空间,而只是分配一 个引用空间; 对象的引用类似于指针, 是 32 位的地址空间, 它的值指向一个中间的数据结构, 它存储有关数据类型的信息以及当前对象所在的堆的地址,而对于对象所在的实际的内存地 址是不可操作的,这就保证了安全性。 实例化:运算符 new 为对象分配内存空间,它调用对象的构造方法,返回引用;一个类 的不同对象分别占据不同的内存空间。 说明:虽然 Java 语言规范中并没有把 new 称为运算符,但也可以把 new 看成是一种运 算符。 生成:执行构造方法,进行初始化;根据参数不同调用相应的构造方法。 2.对象的使用 通过运算符“ .”可以实现对对象变量的访问和对象方法的调用。可以通过设定访问权限 来限制其他对象对对象变量和对象方法的访问。 (1)对象变量的调用 格式:objectReference.variable 其中,objectReference 是一个已生成的对象,也可以是能生成对象的表达式。例如: p.x= 10; tx=new Point( ).x; (2)对象方法的调用 格式:objectReference.methodName([paramlist]); 例如: p.move(30,20); new Point( ).move(30,20); 3.对象的清除 当不存在对一个对象的引用时,该对象成为一个无用对象。 Java 的垃圾回收线程自动扫 Java 语言案例教程 54 描对象的动态内存区,把无用对象作为垃圾收集起来并释放。其定义格式为: System.gc( ); 当系统内存用尽或调用 System.gc( )要求垃圾回收时,垃圾回收线程与系统同步运行。 4.2.3 面向对象特性 Java 语言有三个典型的面向对象的特性:封装性、继承性和多态性。 1.封装性 Java 语言中,对象就是对一组变量和相关方法的封装,其中变量表明了对象的状态,方 法表明了对象具有的行为。通过对象的封装,实现了模块化和信息隐藏。通过对类的成员施 以一定的访问权限,即使用限定词实现了类中成员的信息隐藏。 Java 语言有四种限定词,提供了四种不同的访问权限。 (1)private 在类中被限定为 private 的成员,只能被这个类本身访问。 如果一个类的构造方法声明为 private,则其他类不能生成该类的一个实例。 (2)default 在类中不加任何限定词的成员属于缺省( default)访问权限,可以被这个类本身和同一 个包中的类所访问。 (3)protected 在类中被限定为 protected 的成员,可以被这个类本身、它的子类(包括同一个包中以及 不同包中的子类)和同一个包中的所有其他的类所访问。 (4)public 在类中被限定为 public 的成员,可以被所有的类访问。 表 4-1 列出了这些限定词的作用范围。 表 4-1 Java 中类的限定词的作用范围 同一个类 同一个包 不同包的子类 不同包非子类 private √ default √ √ protected √ √ √ public √ √ √ √ 2.继承性 通过继承实现代码复用。 Java 语言中所有的类都是通过直接或间接地继承 java.lang.Object 类得到的。继承而得到的类称为子类,被继承的类称为父类。子类不能继承 父类中访问权限为 private 的成员变量和方法。 子类可以重写父类的方法及命名与父类同名的 成员变量。但 Java 语言不支持多重继承,即一个类从多个超类派生的能力。 (1)子类的创建 格式: class SubClass extends SuperClass { 第 4 章 Java语言中的面向对象特性 55 } 注意:对于用 final 修饰的类是无法创建其子类的。 (2)成员变量的隐藏和方法的重写 子类通过隐藏父类的成员变量和重写父类的方法,可以把父类的状态和行为改变为自身 的状态和行为。 例如: class SuperClass{ int x; … void setX( ){ x=0; } … } class SubClass extends SuperClass{ int x; // 隐藏了父类的变量 x void setX( ) { // 重写了父类的方法 setX() x=5; } … } 注意:子类中重写的方法和父类中被重写的方法要具有相同的名字、相同的参数表和相 同的返回类型,只是函数体不同。而对于父类中用 final 修饰的方法,不能在子类中被重写。 (3)父类的访问 Java 语言通过 super 来实现对父类成员的访问, super 用来引用当前对象的父类。 super 的使用有三种情况: ①访问父类被隐藏的成员变量,如: super.variable; ②调用父类中被重写的方法,如: super.Method([paramlist]); ③调用父类的构造函数,如: super([paramlist]); 例 4-5 对父类的引用。 import java.io.*; class SuperClass{ int x; SuperClass( ) { x=3; System.out.println("in SuperClass : x=" +x); } void doSomething( ) { System.out.println("in SuperClass.doSomething()"); } } … … Java 语言案例教程 56 class SubClass extends SuperClass { int x; SubClass( ) { super( ); //调用父类的构造方法 x=5; //super( ) 要放在方法中的第一句 System.out.println("in SubClass :x="+x); } void doSomething( ) { super.doSomething( ); //调用父类的方法 System.out.println("in SubClass.doSomething()"); System.out.println("super.x="+super.x+" sub.x="+x); } } public class Inheritance { public static void main(String args[]) { SubClass subC=new SubClass(); subC.doSomething(); } } 注意:编译器不允许在子类非构造方法中调用父类的构造方法。并且,子类在构造方法 中使用 super 调用父类的构造方法时, super 语句要作为子类构造方法的第一条语句。 运行结果如图 4-4 所示。 3.多态性 在 Java 语言中,多态性体现在两个方面:以方法重载实现的静态多态性(编译时多态) 和以方法重写实现的动态多态性(运行时多态) 。 (1)编译时多态 在编译阶段,编译器会根据参数的不同来静态确定相应的方法。 图 4-4 Inheritance 的运行结果 第 4 章 Java语言中的面向对象特性 57 (2)运行时多态 由于子类继承了父类所有的属性(私有的除外) ,因此子类对象可以作为父类对象使用。 程序中凡是使用父类对象的地方,都可以用子类对象来代替。一个对象可以通过引用子类的 实例来调用子类的方法。 z 重写方法的调用原则: Java 运行时系统根据调用该方法的实例,来决定调用哪个方 法。对子类的一个实例,如果子类重写了父类的方法,则运行时系统调用子类的方 法;如果子类继承了父类的方法(未重写) ,则运行时系统调用父类的方法。 在例 4-6 中,父类对象 a 引用的是子类的实例,所以, Java 运行时调用子类 B 的 callme 方法。 例 4-6 重写方法的调用。 import java.io.*; class A{ void callme( ) { System.out.println("Inside A's callme()method"); } } class B extends A{ void callme( ) { System.out.println("Inside B's callme() Method"); } } public class Dispatch{ public static void main(String args[]) { A a=new B(); a.callme( ); } } 运行结果如图 4-5 所示。 图 4-5 Dispatch 的运行结果 Java 语言案例教程 58 z 重写方法时应遵循的原则:改写后的方法不能比被重写的方法有更严格的访问权限 (可以相同) ;改写后的方法不能比被重写的方法产生更多的例外。 4.其他 (1)final 关键字 final 关键字可以修饰类、类的成员变量和成员方法,但其作用各不相同。 z final 修饰成员变量: final 修饰变量,则成为常量。定义格式如下: final type variableName; 修饰成员变量时,定义时同时给出初始值,而修饰局部变量时不做要求。 z final 修饰成员方法: final 修饰方法,则该方法不能被子类重写。定义格式如下: final returnType methodName(paramList){ } z final 修饰类:final 修饰类,则类不能被继承。定义格式如下: final class finalClassName{ } (2)static 关键字 用 static 关键字可以声明类变量和类方法,其定义格式如下: static type classVar; static returnType classMethod({paramlist}) { } 如果在声明时不用 static 关键字修饰,则声明为实例变量和实例方法。 (3)实例变量和类变量 每个对象的实例变量都分配内存,通过该对象来访问这些实例变量,不同的实例变量是 不同的。 类变量仅在生成第一个对象时分配内存,所有实例对象共享同一个类变量,每个实例对 象对类变量的改变都会影响到其他的实例对象。类变量可通过类名直接访问,无需先生成一 个实例对象,也可以通过实例对象访问类变量。 (4)实例方法和类方法 实例方法可以对当前对象的实例变量进行操作,也可以对类变量进行操作,实例方法由 实例对象调用。 但类方法不能访问实例变量,只能访问类变量。类方法可以由类名直接调用,也可由实 例对象进行调用。类方法中不能使用 this 或 super 关键字。 例 4-7 关于实例成员和类成员的例子。 class Member { static int classVar; int instanceVar; static void setClassVar(int i) { classVar=i; … … … 第 4 章 Java语言中的面向对象特性 59 // instanceVar=i; // 类方法不能访问实例变量 } static int getClassVar() { return classVar; } void setInstanceVar(int i) { classVar=i; //实例方法不但可以访问类变量,也可以访问实例变量 instanceVar=i; } int getInstanceVar( ) { return instanceVar; } } public class MemberTest{ public static void main(String args[]) { Member m1=new member(); Member m2=new member(); m1.setClassVar(1); m2.setClassVar(2); System.out.println("m1.classVar="+m1.getClassVar()+ "m2.ClassVar="+m2.getClassVar()); m1.setInstanceVar(11); m2.setInstanceVar(22); System.out.println("m1.InstanceVar="+m1.getInstanceVar()+ "m2.InstanceVar="+m2.getInstanceVar()); } } 运行结果如图 4-6 所示。 (5)java.lang.Object 类 java.lang.Object 类处于 Java 开发环境的类层次的根部,其他所有的类都是直接或间接地 继承了此类。该类定义了一些最基本的状态和行为。下面,是一些常用的方法。 图 4-6 MemberTest 的运行结果 Java 语言案例教程 60 equals():比较两个对象 (引用)是否相同。 getClass():返回对象运行时所对应的类的表示,从而可得到相应的信息。 toString():用来返回对象的字符串表示。 finalize():用于在垃圾收集前清除对象。 notify(),notifyAll(),wait():用于多线程处理中的同步。 说明:在定义一个类时,如果没有用 extends 关键字指明这个类的直接父类,则这个类 直接继承于 Object 类,即 Object 类就自动成为它的直接父类。在 Java 语言中,Object 类可 以看成是所有类的父类,也体现了一切都是对象的含义。 4.2.4 抽象类和接口 1.抽象类 在 Java 语言中,当用 abstract 关键字来修饰一个类时,这个类叫做抽象类;当用 abstract 关键字来修饰一个方法时,这个方法叫做抽象方法。其定义格式如下: abstract class abstractClass{…} //抽象类 abstract returnType abstractMethod([paramlist]) //抽象方法 抽象类必须被继承,抽象方法必须被重写。抽象方法只需声明,无需实现;抽象类不能被 实例化,抽象类不一定要包含抽象方法。若类中包含了抽象方法,则该类必须被定义为抽象类。 注意:不能直接实例化一个抽象类的对象,否则编译器会报错。 2.接口 接口是抽象类的一种,只包含常量和方法的定义,而没有变量和方法的实现,且其方法 都是抽象方法。它的用途体现在下面几个方面: z 通过接口实现不相关类的相同行为,而无需考虑这些类之间的关系。 z 通过接口指明多个类需要实现的方法。 z 通过接口了解对象的交互界面 ,而无需了解对象所对应的类。 (1)接口的定义 接口的定义包括接口声明和接口体。 接口声明的定义格式如下: [public] interface interfaceName[extends listOfSuperInterface] { … } 用 interface 关键字修饰一个类,表明该类为接口。 extends 子句与类声明的 extends 子句 基本相同,不同的是一个接口可有多个父接口,用逗号隔开,而一个类只能有一个父类。 说明:Java 语言虽然不支持类的多重继承,但通过接口在一定程度也代替了类实现了多 重继承。 接口体包括常量定义和方法定义。 常量定义的格式为: type NAME=value; 该常量被实现该接口的多个类共享 ; 具有 public,final,static 属性。 方法的定义格式为: (具有 public 和 abstract 属性) returnType methodName([paramlist]); 第 4 章 Java语言中的面向对象特性 61 说明:在 Java 接口的定义中,即使没有明确的说明,常量都默认具有 public,final,static 属性,方法都默认具有 public 和 abstract 属性。 (2)接口的实现 在类的声明中用 implements 子句来表示一个类使用某个接口,在类体中可以使用接口中 定义的常量, 而且必须实现接口中定义的所有方法。 一个类可以实现多个接口, 在 implements 子句中用逗号分开。 (3)接口类型的使用 接口作为一种引用类型来使用。任何实现该接口的类的实例都可以存储在该接口类型的 变量中,通过这些变量可以访问在该接口中类所实现的方法。 4.2.5 内部类 1.内部类的定义和使用 内部类是在一个类的内部嵌套定义的类,它可以是其他类的成员,也可以在一个语句块 的内部定义,还可以在表达式内部匿名定义。 提示:也可以在类中嵌套接口。 内部类有如下特性: z 一般用在定义它的类或语句块之内 ,在外部引用它时必须给出完整的名称。名字不能 与包含它的类名相同。 z 可以使用包含它的类的静态和实例成员变量 ,也可以使用它所在方法的局部变量。 z 可以定义为 abstract。 z 可以声明为 private 或 protected。 z 若被声明为 static,就变成了顶层类,不能再使用局部变量。 z 若想在内部类中声明任何 static 成员,则该内部类必须声明为 static。 例 4-8是一个说明内部类如何使用的例子, 其中, 定义了两个内部类: MouseMotionHandler 和 MouseEventHandler,分别用来处理鼠标移动事件和鼠标点按事件。 例 4-8 内部类使用的示例。 import java.awt.*; import java.awt.event.*; public class TwoListenInner { private Frame f; private TextField tf; public static void main(String args[]) { TwoListenInner that=new TwoListenInner(); that.go(); } public void go() { f=new Frame("Two listeners example"); Java 语言案例教程 62 f.add("North",new Label("Click and drag the mouse")); tf=new TextField(30); f.add("South",tf); f.addMouseMotionListener(new MouseMotionHandler()); f.addMouseListener(new MouseEventHandler()); f.setSize(300,300); f.setVisible(true); } public class MouseMotionHandler extends MouseMotionAdapter { public void mouseDragged(MouseEvent e){ String s="Mouse dragging:X="+e.getX()+"Y="+e.getY(); tf.setText(s); } } public class MouseEventHandler extends MouseAdapter { public void mouseEntered(MouseEvent e){ String s="The mouse entered"; tf.setText(s); } public void mouseExited(MouseEvent e){ String s="The mouse left the building"; tf.setText(s); } } } 运行结果如图 4-7 所示。 运行一下这个程序,看一看它的运行结果。当将鼠 标移入窗体时,文本框中会出现: “The mouse entered”; 当在窗体中拖曳鼠标时,文本框中会出现: “Mouse dragging:X=154 Y=177”;当鼠标离开时, 文本框中出现: “The mouse left the building”。 2.匿名类的定义和使用 匿名类是一种特殊的内部类,它是在一个表达式内 部包含一个完整的类定义。通过对例 4-8 中 go()部分语 句的修改,可以看到匿名类的使用情况。 public void go() { f=new Frame("Two listeners example"); f.add("North",new Label("Click and drag the mouse")); tf=new TextField(30); f.add("South",tf); 图 4-7 TwoListenInner 的运行结果 第 4 章 Java语言中的面向对象特性 63 f.addMouseMotionListener(new MouseMotionHandler() { /*定义了一个匿名类,类名没有显式地给出,只是该类是 MouseMotionHandler 类的子类*/ public void mouseDragged(MouseEvent e) { String s="Mouse dragging:X="+e.getX()+"Y ="+e.getY(); tf.setText(s); } }); f.addMouseListener(new MouseEventHandler()); f.setSize(300,300); f.setVisible(true); } 注意:匿名类不能有构造方法,因此初始化它们的唯一方法是使用实例初始化程序。 3.内部类的优缺点 z 优点:减小字节码文件的大小。 z 缺点:使程序结构不清楚。 【本章小结】 本章主要讲述 Java 语言中面向对象的特性。 从面向对象技术的基础知识出发, 介绍了 Java 语言中类的构成,成员变量、成员方法和构造方法的定义与使用,对象的创建、使用和清除, 抽象类、接口和内部类的特点实现等,并且从封装、继承和多态等角度全面阐述了使用 Java 语言进行面向对象程序设计的主要方法和实现过程。通过本章的学习,掌握面向对象技术和 Java 语言面向对象程序设计,可以说是 Java 语言编程的精髓之处。 【习题】 一、选择题 1.将变量和方法都集成到一个类中的过程称为( )。 A.继承 B.信息隐藏 C.多态 D.封装 2.new Book ( )完成什么功能?( ) A.只为 Book 对象分配内存 B.只初始化 Book 对象的实例域 C.返回对已经初始化和新建的 Book 对象的引用 D.以上的选项都是 3.this 的作用是什么?( ) A.方法调用链 Java 语言案例教程 64 B.访问被同名局部变量或者参数遮蔽的实例域 C.调用同一类中的构造方法 D.上面的选项都是 4.接口中定义的方法默认为下面哪种属性?( ) A.类方法 B.抽象方法 C.私有方法 D.以上选项都不是 5.下面哪种说法是正确的?( ) A.Java 语言支持类的多重继承 B.Java 语言支持接口的多重继承 C.Java 语言支持接口的单一继承 D.以上说法都不对 6.下面哪种说法是错误的?( ) A.子类的构造方法只能从父类调用 B.用 super ( )可以调用父类的构造方法 C.在子类的构造方法调用父类构造方法,调用语句必须是第一条语句 D.以上说法都不对 7.下面哪一个不是 OOP 的基本特征?( ) A.多态 B.组合 C.继承 D.封装 8.下面哪一个修饰符不是接口中常量的默认修饰符?( ) A.transient B.final C.static D.public 9.下面哪个嵌套类是用 static 关键字声明的?( ) A.匿名内部类 B.嵌套顶层类 C.局部内部类 D.实例内部类 10.嵌套类的顶层类可以访问( )。 A.类成员 B.实例成员 C.局部变量 D.以上都不是 二、判断题 1.抽象类的抽象方法在该类的直接非抽象子类必须实现。 2.方法全部为抽象的抽象类与接口相同。 3.Object 类是所有 Java 类的直接或间接父类。 4.在 Java 语言中,类和接口都支持多重继承。 5.在子类构造方法中用 super 调用父类构造方法,调用语句可以放在子类构造方法的任 意位置。 6.用 final 关键字声明的类无法派生子类。 7.用 static 关键字声明的方法称为类方法,类方法可以访问类中的任意变量。 8.在同一个类中,可以定义具有相同名字和参数列表但返回类型不同的方法。 9.方法重载是在一个类中实现,而方法重写是在父类与子类之间实现的。 10.构造方法不能返回任意数据类型,所以 Book 类的构造方法这样声明: void Book( ) {…} 第 4 章 Java语言中的面向对象特性 65 三、问答题 1.举例说明 Java 语言中限定词的区别。 2.子类将继承父类的哪些成员变量和方法?子类在什么情况下隐藏父类的成员变量和 方法? 3.使用接口有哪些注意事项? 4.什么是内部类,有哪些特征? 5.内部类的优缺点是什么? 6.this 和 super 两个关键字有什么区别? 四、实践题 1.编写一个表示学生的类: Student,包括域(学号、班号、姓名、性别、年龄)和方 法(获得学号、获得班号、获得姓名、获得性别、修改年龄) 。 2.编写一个复数类,具有实部、虚部成员变量,可以完成加、减、乘、获得实部和虚部 等方法,并编写一个主类进行测试。 3.编写一个类,包含一个无参数的构造方法,打印类已创建的信息;再重载一个带有 String 类型参数的构造方法,打印参数的信息,并创建一个主类来验证。 第 5 章 输入、输出流 【学习目标】 1.了解 Java 语言中流、输入、输出流的概念。 2.了解 File 类的属性和主要方法,掌握对文件和目录的操作。 3.掌握 Java 语言中常见的输入、输出流类及其主要方法。 4.掌握用输入、输出流实现文件的读、写以及如何随机读写文件。 5.了解对象串行化的概念和目的,掌握实现对象串行化的方法。 Java 语言的输入和输出多以流的方式进行,其特点是数据的发送和获取都是沿数据序列顺序 进行,每个数据必须等待它前面的数据发送或读入后才能被读写。将可从中读出一系列数据的对 象称为“输入流”,而将能向其中写入一系列数据的对象称为“输出流”。 Java 语言的输入、输出 都是通过继承抽象类 InputStream 和 OutputStream、Reader 和 Writer 来实现的,InputStream 和 OutputStream 类用于处理字节流(例如处理二进制文件), Reader 和 Writer 类则用于处理字符流 (例如处理文本文件)。但是用户并不能直接使用这些类,而需要从这些类派生新类。 说明:Java 2 标准版在 Java1.4 版本以后,发布了全新的 I/O 类库,称为 NIO。NIO 包含了 一些新特性:非阻塞 I/O、字符转换、缓冲以及通道等。由于种种原因,本书没有列入相关的内 容,有兴趣的读者可以阅读 JavaWorld 论文“Master Merlin’s new I/O classes”去了解 Java NIO: http://www.javaworld.com/javaworld/jw-09-2001/jw-0907-merlin.html Java 语言提供了许多独立的类,分别用来选择缓冲、提前扫描、随机访问、文本格式化, 等等。文件类封装了对计算机文件系统进行操作的功能。流类关心的是文件的内容,而文件 关心的是文件类在磁盘上的存储。 一般来说,可以使用一个流类的构造函数来创建一个输入流或者输出流。有了流对象之 后,就可以使用它的 read 方法和 write 方法来传送数据。处理完之后,一般利用 close 方法来 关闭这个对象。 在开始介绍流的内容之前,先介绍一个重要的类——File 类,它并不是一个流类,而且 也不能处理文件的内容,但是它允许用户判断关于文件的名称、大小、日期等描述信息。 5.1 File 类 5.1.1 构造函数 1.File(File parent,String child) 用一个父路径名(parent)和一个子路径名字符串(child)构造一个文件实例。 第 5 章 输入、输出流 67 2.File(String pathname) 将指定的路径名字符串(pathname)转换成一个抽象的路径名,并构建一个文件实例。 3.File(String parent,String child) 用一个父路径名字符串(parent)和一个子路径名字符串(child)构造一个文件实例。 注意:File 类是用于对文件的名称和属性进行操作,并不能用来读写数据文件。File 类 不提供对数据文件进行读和写的能力。 5.1.2 重要的方法 1.对文件名的操作 l String getName():获取该抽象名指定的文件或者目录的名称。 l String toString():获取该抽象路径名的路径名字符串。 l String getParent():获取该抽象路径名的父路径名字符串,如果没有父路径则返回空 (null)。 l File getParentFile():获取该抽象路径名的父路径抽象路径名,如果没有父路径则返 回空(null)。 l String getAbsolutePath():获取该抽象路径名绝对路径名字符串。 l String getCanonicalPath():获取该抽象路径名规范路径名字符串。 l File getCanonicalFile():获取该抽象路径名的规范形式。 l File getAbsoluteFile():获取该抽象路径名的绝对形式。 l boolean renameTo(File dest):重新命名由该路径名指定的文件。 l Static File createTempFile(String prefix,String suffix,File directory):在指定的目录 下构造一个新的空文件。参数 prefix 指定文件名,suffix 指定文件扩展名,directory 指定目录。 l Static File createTempFile(String prfix,String suffix):在默认的临时文件目录下构造 一个新的空文件。 2.测试文件的属性 l boolean exists():检测由该抽象路径名指定的文件是否存在。 l boolean isFile():如果由该抽象路径名指定的文件是一个一般的文件,则返回真。 l boolean isHidden():如果由该抽象路径名指定的文件是一个隐藏文件,则返回真。 l boolean isDirectory():如果由该抽象路径名指定的文件是一个目录,则返回真。 l boolean isAbsolute():如果该抽象路径名是绝对路径,则返回真。 3.获得文件的信息以及删除文件 l long lastModified():获取由该抽象路径名指定的文件最后一次更新的时间。 l long length():获取由该抽象路径名指定的文件的长度。 l boolean delete ():删除文件或者目录。 l void deleteOnExit():当虚拟机执行结束时,请求删除指定的文件或目录。 Java 语言案例教程 68 4.对目录的操作 l boolean mkdir():构造由该抽象路径名命名的目录。 l boolean mkdirs():构造由该抽象路径名命名的目录,包括任何必需的,但是不存在 的父目录。 l String[] list():获取存有指定目录中的文件和目录名称的字符串数组。 l String[] list(FilenameFilter filter):获取存有符合指定的过滤器条件的文件和目录名称 (filter)的字符串数组。 l File[] listFiles():获取指定该目录中文件的抽象路径名的数组。 l File[] listFiles(FileFilter filter):获取指定该目录中符合指定过滤器条件的文件和目录 的抽象路径名的数组。 l File[] listFiles(FilenameFilter filter):获取指定该目录中符合指定过滤器条件的文件和 目录的抽象路径名的数组。 l static File[] listRoots():列出有效的文件系统根目录。 5.设置文件的属性 l boolean setLastModified(long time):设置文件或者目录的最后更新时间。 l boolean setReadOnly():标记由该路径名命名的文件或者目录,使之为只读。 6.其他一些重要方法 l URL toURL():将该抽象路径名转换成 URL(统一资源定位器)。 l boolean equals(Object o):比较该路径名与指定的对象是否相等。 l int hashCode():计算该抽象路径名的哈希散列函数算法代码。 下面的例子,使用了几个 File 类的方法,来获取源文件的一些信息。 例 5-1 几个File 类方法的示例。 import java.io.File; class file { public static void main(String args[]) { File file1 = new File("file.java"); System.out.println("File: " + file1.getName() + (file1.isFile() ? " is a file" : " is not a file"));//选择运算符,如果 file 是一个文件,则该表 达式的值为" is a file",反之为" is not a file" System.out.println("Size: " + file1.length()); System.out.println("Path: " + file1.getPath());//getPath()方法返回条件的 全路径名 System.out.println("Absolute Path: " + file1.getAbsolutePath()); System.out.println("File was last modified: " + file1.lastModi fied()); System.out.println(file1.exists() ? "File exists" : "File does not exist"); System.out.println(file1.canRead() ? "File can be read from" : 第 5 章 输入、输出流 69 "File cannot be read from");//canRead()方法测试文件是否能读 System.out.println(file1.canWrite() ? "File can be written to" : "File cannot be written to");//canWrite()方法测试文件是否能写 System.out.println(file1.isDirectory() ? "File is a directory" : "File is not a directory"); } } 程序运行的结果如图 5-1 所示,注意文件最后一次修改时间用的是从 1/1/70 开始的毫秒 数表示的。 5.2 字 节 输 入 流 5.2.1 InputStream 类 字节输入流(InputStream)和字节输入出流(OutputStream)都是抽象基类,所有面向 字节的流类都是从其继承来的,而它们继承于 Object 类。 1.构造函数 InputStream()构造一个 InputStream 对象。 2.类的其他方法 l int available():获取可以从该输入流中读取的字节数量。 l void close():关闭该输入流。 l void mark(int readlimit):标记该输入流的当前位置。 l boolean markSupported():如果该输入流支持 mark 和 reset 方法,则返回真。 l abstract int read():从该输入流中读取数据的下一个字节。 l int read(byte[] b):从该输入流中读取一定数量的字节,并将它们保存在缓存数组 b 中。 l int read(byte[] b,int off,int len):从该输入流的指定位置读取指定字节的数据,并读入 到数组 b 中。 l void reset():在该输入流上最后调用 mark 方法的位置重新配置输入流。 l long skip(long n ):略过该输入流 n 个字节的数据。 图 5-1 file 运行结果 Java 语言案例教程 70 说明:在调用 read 方法时,如果数据不可获得,则会处于阻塞(等待)状态。InputStream 类声明了一个 available 方法,可返回一个整数,指出可以直接读取的字节数。 5.2.2 FileInputStream 类 FileInputStream 类是为了处理来源于文件中的面向字节输入而设计的,通过使用 FileInputStream 类你可以访问文件的几个字节或者整个文件。它是由 InputStream 类派生出 来的。 说明:使用 FileInputStream 和 FileOutStream 类来读和写二进制数据,比如图像文件、声 音文件、视频文件和配置文件等,也可以用来读和写基于 ASCII 的文本文件。 1.构造函数 l FileInputStream(File file):通过打开一个链接到实际的文件,构造一个 FileInputStream 对象。 l FileInputStream(FileDescriptor fdObj):通过使用文件描述符构造一个 FileInputStream 对象。 l FileInputStream(String name): 通过打开一个实际的文件,构造一个 FileInputStream 对象。 2.类的其他方法 l FileDescriptor getFD() :获取 FileDescriptor 对象。 l Close 方法重写 Object 中的 Protected void finalize(),在确认该文件没有任何引用时, 关闭文件。 l InputStream 类中除 mark 和 reset 之外的其他方法。 下面的例子,应用程序打开自身的源代码文件,读取显示 100 个字节的代码,然后跳过 100 字节后显示后面的 100 个字节代码。 例 5-2 Fileinputstream 对象的使用。 import java.io.*; class fileinputstream { public static void main(String args[]) throws Exception { try{ FileInputStream fileinputstream = new File Input Stream("fileinputstream.java"); System.out.println("Available bytes: " + fileinputstream.available()); System.out.println("Reading 100 bytes...."); byte bytearray[] = new byte[100];//创建一个长度为 100 的字节数组 if (fileinputstream.read(bytearray) != 100) { System.out.println("Could not get 100 bytes");//在屏幕上输出信息 } 第 5 章 输入、输出流 71 System.out.println(new String(bytearray, 0, 100)); System.out.println("Skipping 100 bytes..."); fileinputstream.skip(100);//从输入流中跳过和放弃 100 字节数据 System.out.println("Reading 100 bytes...."); if (fileinputstream.read(bytearray) != 100) { System.out.println("Could not get 100 bytes"); } System.out.println(new String(bytearray, 0, 100)); fileinputstream.close();//关闭输入流 } catch(IOException e) {System.out.println("File read Error");} } } 由于 I/O 操作对于错误非常敏感,例如要打开的文件可能不存在,因此需要进行异常处 理,程序执行的结果如图 5-2 所示。 说明:如果在异常处理中使用了 finally 子句,则通常都在 finally 中关闭文件。这样不管 异常是否发生,文件都会被关闭(所有等待写入该文件的数据立刻写入该文件)。 显示地关闭 打开的文件是个好习惯。如果文件打开了但没有被关闭,那么文件就不可能第二次打开,并 且程序运行会失败。 5.3 字 节 输 出 流 5.3.1 OutputStream 类 由字节输出流(OutputStream 类)派生出来的类可以处理输出流。 1.构造函数 l OutputStream():构造一个 OutputStream 对象。 图 5-2 fileinputstream 运行结果 Java 语言案例教程 72 2.类的其他方法 l void close():关闭该输出流。 l void flush():刷新该输出流,并写入任何等待缓存的要输出的字节数据。 l void write(byte[] b):从指定的字节数组写该数组长度(b.length)个字节到该输出流。 l void write(byte[] b,int off,int len):从偏移量位置(off 指定)开始,从指定的字节数 组写一定数量的字节(由 len 指定)到该输出流。 l abstract void write(int b):将指定的字节写到该输出流。 5.3.2 FileOutputStream 类 用户可以利用 FileOutputStream 类将数据一个字节一个字节地写入文件。下面是该类的 继承关系图: java.lang.Object |____java.io.OutputStream |____java.io.FileOutputStream 1.构造函数 l FileOutputStream(File file):构造一个输出文件流,以写入指定的 File 对象。 l FileOutputStream(FileDescriptor fdObj):构造一个输出文件流,以写入指定的文件描 述符。 l FileOutputStream(String name):构造一个输出文件流,以写入指定的名称所指定的文 件。 l FileOutputStream(String name,boolean append):构造一个输出文件流,并添加到指 定的名称所指定的文件中。 提示:用 FileOutputStream 构造方法创建文件时通常删除已经存在的同名文件,因此如 果要向已存在的文件中添加字节,就要使用 FileOutputStream(String name, boolean append)构造方法来完成,注意将参数 append 的值设为 true。 2.类的其他方法 l FileDescriptor getFD() :获取文件描述符 FileDescriptor 对象。 l close 法:重写 Object 类中的 Protected void finalize(),即确认该文件没有任何引用时, 调用它的 close 方法。 下面的这个例子,用了三种方式将一串文本写入文件。 例 5-3 字节输出流示例。 import java.io.*; class fileoutputstream { public static void main(String args[]) throws Exception { byte data[] = "This is a test of FileOutputStream.".getBytes(); 第 5 章 输入、输出流 73 //getBytes()为 string 类的方法,功能是将字符串解码为字节序列,并将结果存放到 data 数组中 FileOutputStream fileoutputstream1 = new FileOutputStream("file1.txt"); //创建输出文件流 fileoutputstream1,并与 file1.txt 关联 for (int loop_index = 0; loop_index < data.length; loop_index++) { fileoutputstream1.write(data[loop_index]);//将 date 数组中的数据写入文件流中 } FileOutputStream fileoutputstream2 = new FileOutputStream("file2.txt"); //创建输出文件流 fileoutputstream2,并与 file2.txt 关联 fileoutputstream2.write(data); FileOutputStream fileoutputstream3 = new FileOutputStream("file3.txt"); //创建输出文件流 fileoutputstream3,并与 file3.txt 关联 fileoutputstream3.write(data, 5, 10); fileoutputstream1.close(); fileoutputstream2.close(); fileoutputstream3.close(); } } 5.4 RandomAccessFile 类 在 处理文件时,常常不是从头到尾顺序读的,而是随机地存取文件的数据。 RandomAccessFile 类既不是 InputStream 类的子类,也不是 OutputStream 类的子类,但是它 却可以对文件进行读写。当对一个文件进行读写操作时,只要创建一个指向该文件的 RandomAccessFile 对象即可,这样既可从这个对象中读取该文件的数据,也可以通过这个对 象把数据写入该文件。 1.构造函数 l RandomAccessFile(File file,String mode):构造一个随机文件流,对文件(由 file 指 定)进行操作,参数 mode 决定对文件的访问权限,r 代表只读,rw 代表可读可写。 l RandomAccessFile(String name,String mode):构造一个随机文件流 i,参数 name 确 定一个文件名,参数 mode 决定对文件的访问权限,r 代表只读,rw 代表可读可写。 注意:如果对以只读方式创建的 RandomAccessFile 对象调用“写”方法,将会出错,转 而调用 IOException 对象。因此,若只试图读一个随机访问文件,则最好以只读模式打开, 而不要以读写模式打开。那样,即使碰巧写入了文件修改的代码,文件也不会被修改。 2.类的其他方法 l void close():关闭该随机访问文件流对象。 l FileDescriptor getFD():获取与该流相连的文件描述符对象。 l Long getFilePointer():获取该文件中的当前偏移量。 Java 语言案例教程 74 l int read():从该文件中读取一个字节的数据。 l int read(byte[] b):从该文件中读取指定字节 b.length 的数据到一个字节数组中。 l int read(byte[]b,int off,int len):从该文件中读取一定数量(由 len 指定,单位字节的 数据至一个字节数组中。 l boolean readBoolean():从该文件中获取一个 boolean 参数。 l byte readByte():从文件中读取一个字节。 l char readChar():从文件中读取一个 Unicode 字符。 l double readDouble():从该文件中读取一个 double 参数。 l float readFloat():从该文件中读取一个 float 参数。 l void readFully(byte[] b):从当前的文件指针位置开始,读取该文件一定数量(由 b.length 指定)的数据到字节数组。 l void readFully(byte[]b,int off,int len):从当前文件的偏移量位置(由 off 指定)开始, 读取该文件一定数量(由 len 指定)的数据至字节数组。 l int readInt():从该文件中读取一个 int 型值。 l String readLine():从该文件中读取一行文本。 l long readLong():从该文件中读取一个 long 型值 l short Getshort():从该文件中读取一个 short 型值。 l int readUnsignedByte():从该文件中读取一个不带符号的字节型值。 l int readUnsignedShort():从该文件中读取一个不带符号的 short 型值。 l String readUTF():从该文件中读取一个 UTF(Unicode Text Format)字符串。 l void seek(long pos):设置文件指针偏移量。 l void setLength(long newLength):设置该文件的长度。 l int skipBytes(int n):略过 n 个字节。 l void write(byte[] b):将指定的字节写入该文件。 l void write(byte[]b,int off,int len):从该文件的偏移量位置(由 off 指定)开始,从指 定的字节数组中写一定数量(由 len 指定)的数据。 l void write(int b):将指定的字节写入该文件中。 l void writeBoolean(boolean v):将一个 boolean 型参数视为一个字节的数值写入该文件。 l void writeByte(int v):将一个 byte 型参数视为一个字节的数值写入该文件。 l void writeBytes(String s):将 String 型参数视为一个字节的数据写入该文件。 l void writeChar(int v):将参数视为 2 个字节的数值写入该文件,从高位字节开始。 l void writeChars(String s):将 String 型参数视为一个字符的数据写入该文件。 l void writeDouble(double v):向文件写入一个 double 型值的数据。 l void writeFloat(float v):向文件写入一个 float 型值的数据。 l void writeInt(int v):向文件写入一个 int 型值的数据。 l void writeLong(long v):向文件写入一个 long 型值的数据。 l void writeShort(int v):向文件写入一个 short 型值的数据。 l void writeUTF(String str):向文件写入一个 UTF 字符串。 下面这个例子,在屏幕上显示自己的源代码。 例 5-4 随机访问文件示例。 第 5 章 输入、输出流 75 import java.io.*; class randomAccessfile { public static void main(String args[]) { try{ RandomAccessFilefile=newRandomAccessFile("randomAccessfile.java","rw"); //创建从 randomAccessfile.java 文件中读取和写入的随机存取文件流 long filePoint=0;//filePoint 用来保存文件指针 long fileLength=file.length(); while(filePoint */ public class sleep extends Applet implements ActionListener { Toolkit toolkit; Button button; public void init() { toolkit=getToolkit();//获得一个工具包对象,工具包中含有发声的 beep()方法。 button=new Button("确定"); add(button); button.addActionListener(this); } public void actionPerformed(ActionEvent e) { if(e.getSource()==button) { for(int i=0;i<=9;i++) { toolkit.beep();//发出声音 try {Thread.sleep(500);} catch(InterruptedException e1){} } } 第 6 章 多线程编程 95 } } 程序运行结果如图 6-1 所示。 6.2.2 Runnable 接口 由于 Java 中的继承为单继承,这意味着如果你从 Thread 类继承,便不能从其他类继承, 因此在很多时候使用 Runnable 接口来实现多线程。 Runnable 接口中只有一个 run 方法,使用 Runnable 接口的用户自定义类必须实现此方法。 用户把想要新线程执行的代码放到此方法中。在创 建 了新 的 Runnable 对象后,用户可以把该 对象传递给 Thread 类的构造函数来创建新进程。 例如,先创建如下所示的类: public chass m;mykinplements Runnable{ int public void run(){ while(true){ system.ont.print/n(H:tiff) } } } 然后再创建一个线程: Runnable r=new wgkl(); Thread t=new >hread(r); 提示:到底是选择继承 Thread 类还是实现 Runnable 接口来创建线程,这要取决于类的 层次结构。因为 Java 中的类是单一继承的,所以,当一个类已经有明确的父类时, 则只能用实现 Runnable 接口的方式来创建线程了。 图 6-1 sleep 运行结果 Java 语言案例教程 96 6.3 多线程程序案例 6.3.1 使用 Thread 类创建多线程 用这种方法实现多线程,用户需要创建 Thread 类的子类,并重新定义自己的 run 方法, 当创建线程并执行 start 方法时,run 方法自动执行。 例 6-2 用Thread 类创建多线程案例。 public class MT_1 { static Boy boy; static Girl girl; public static void main(String args[]) { boy=new Boy(); //创建两个线程,然后调用 start()方法, girl=new Girl(); //run()方法将自动被执行 boy.start(); girl.start(); } } class Boy extends Thread { public void run() { for(int i=0;i<=5;i++) { System.out.println("I am a boy"); try{sleep(500);} catch(InterruptedException e){} } } } class Girl extends Thread { public void run() { for(int i=0;i<=5;i++) { System.out.println("I am a girl"); try{sleep(300);} 第 6 章 多线程编程 97 catch(InterruptedException e){} } } } 程序运行结果如图 6-2 所示。 6.3.2 实现 Runnable 接口创建多线程 下面这个例子和例 6-2 的执行结果相同,请读者分析两者实现方式的不同。 例 6-3 Runnable 接口创建多线程案例 1。 class Boy implements Runnable //在 Boy 类中实现 Runnable 接口,在构造方法中将 此创建的线程与自己联系起来 { Thread boy; Boy() { boy=new Thread(this); boy.start(); } public void run() { for(int i=0;i<=5;i++) { System.out.println("I am a boy"); try{Thread.sleep(500);} catch(InterruptedException e){} } } } class Girl implements Runnable//在 Girl 类中实现 Runnable 接口,在构造方法中 将此创建的线程与自己联系起来 { Thread girl; Girl() { girl=new Thread(this); girl.start(); } public void run() { 图 6-2 MT_1 运行结果 Java 语言案例教程 98 for(int i=0;i<=5;i++) { System.out.println("I am a girl"); try{Thread.sleep(300);} catch(InterruptedException e){} } } } class MT_2//在主类中让两线程运行 { public static void main(String args[]) { Boy boy=new Boy(); Girl girl=new Girl(); } } 在上面的例子中,当创建了新的 Runnable 对象后,将它传给了 Thread 的构造函数,这 一步是通过 boy=new Thread(this) 以及 girl=new Thread(this) 语句实现的,this 是线程的目标 对象,它必须实现 Runnable 接口。 下面看一个小程序的例子,这个小程序可以显示当前的系统时间,并每隔一秒钟刷新一次。 例 6-4 用Runnable 接口创建多线程案例 2。 import java.awt.*; import java.applet.*; import java.util.Date; /* */ public class DigitalClock extends Applet implements Runnable { Font font = new Font ("TimesRoman", Font. BOLD, 14);//创建一个字体对象,用 于设置显示出时间的字体 Date date; Thread thRunner; public void run() { while(true) 第 6 章 多线程编程 99 { date=new Date(); repaint();//重绘 try { Thread.sleep(1000); } catch(InterruptedException e){} } } public void paint(Graphics g) { g.setFont(font); g.drawString(date.toString(),10,50); } public void start() { if (thRunner==null) { thRunner=new Thread(this) ; thRunner.start(); } } public void stop() { if (thRunner!=null) //浏览器在离开该页面时 终止该线程的执行 { thRunner.stop() ; thRunner=null; } } } 程序运行结果如图 6-3 所示。 下面是一个用多线程画图的例子,它创建了两个线程,其中一个用方形的笔,另一个用 圆型的笔。 例 6-5 用Runnable 接口创建多线程案例 3。 import java.applet.*; import java.awt.*; import java.awt.event.*; /* */ public class threaddraw extends Applet implements Runnable { Thread thread1 ,thread2; Graphics mypen; int x,y; public void init() { thread1=new Thread(this); thread2=new Thread(this); x=10; y=240; mypen=getGraphics(); } public void start() { thread1.start();thread2.start(); } public void run() { while(true) if(Thread.currentThread()==thread1)//判断当前正在执行的线程是否是 Thread1, 如果是就用方形笔画图 { x=x+1; if(x>240) { x=0; mypen.clearRect(10,10,300,100); } mypen.setColor(Color.green); mypen.fillRect(x,10,10,50); try{thread1.sleep(60); } catch(InterruptedException e){} } else if(Thread.currentThread()==thread2)// 判 断 当 前 正 在执行的线程是否是 Thread2,如果是就用圆形笔画图 第 6 章 多线程编程 101 { y=y-1; if(y==20) { y=240; mypen.clearRect(10,110,300,100); } mypen.setColor(Color.black); mypen.fillOval(y-10,110,50,50); try{thread2.sleep(60); } catch(InterruptedException e){} } } public void stop() { thread1=null;thread2=null; } } 程序运行结果如图 6-4 所示。 要创建Applet多线程程序,一 般 需要如下几个步骤: ①Applet 类实现 Runnable 接口。 ②定义一个线程数据成员,用以保存该 Applet 的 线程。 ③增加 start 方法,让它产生一个线程并能启动它。 ④创建一个包含启动 Applet 实际代码的 run 方法。 run 方法可以包含需要做任何事情的类线程,如初始化 代码、Applet 循环;或者需要在它自身线程中运行的事 情。它是 Applet 真正的核心。 ⑤添加 stop 方法,以使浏览器在离开页面时终止该 线程的执行。 6.4 线程间的同步 在大多数实际运行的多线程应用程序中,两个或者多个线程需要访问同一个对象,如果每 个线程都调用一个方法,修改该对象的状态,那么这两个或者多个线程将会互相影响对方的运 行,从而使程序运行的结果不可预知而使程序失去可再现性。因此,在实际的应用中,必须对 线程访问的资源加以限制。阻止其他线程进行某一操作直到本线程完成该操作,这就称为 “同步”。 图6-4 threaddraw 运行结果 Java 语言案例教程 102 说明:在 Java 多线程中使用相同的锁来实现同步工作,这意味着只能选择一个对象进行 访问和操作,这在多线程中非常普遍。 下面的例子,四个线程都处理同一个数据对象,该对象打印出开始消息和结束消息。在理 想的情况下,每个线程都应该在其他线程完成后才开始自己的工作。 例 6-6 四个线程的同步问题。 class synchronize1 { public static void main(String args[]) { Shared shared = new Shared(); CustomThread thread1 = new CustomThread(shared, "one"); CustomThread thread2 = new CustomThread(shared, "two"); CustomThread thread3 = new CustomThread(shared, "three"); CustomThread thread4 = new CustomThread(shared, "four"); try { thread1.join(); thread2.join(); thread3.join(); thread4.join(); } catch(InterruptedException e) {} } } class CustomThread extends Thread { Shared shared; public CustomThread(Shared shared, String string) { super(string); this.shared = shared; start(); } public void run() { shared.doWork(Thread.currentThread().getName());//得到先前执行线程的 名字 } } class Shared { void doWork(String string) { 第 6 章 多线程编程 103 System.out.println("Starting " + string); try { Thread.sleep((long) (Math.random() * 500)); } catch (InterruptedException e) {} System.out.println("Ending " + string); } } 程序执行的结果如图 6-5 所示,可以看出每个任务都与其他的任务互相重叠。在实际的 问题中,这可能会引起很大的错误,例如在一个银行的管理系统中,两个线程同时对一个账 目进行操作,在一个线程操作未完成时,另一个线程就操作,这样造成账目错误。 有以下两种方法可以避免这种情况的产生,以使多线程按照正常的顺序执行。 6.4.1 同步代码段 要同步一段代码,就 要 使用 synchronize 关键字来声明要禁止访问的对象。现将上一个例 子进行相应修改。 例 6-7 同步代码段以实现多线程。 class synchronize2 { public static void main(String args[]) { Shared shared = new Shared(); CustomThread thread1 = new CustomThread(shared, "one"); CustomThread thread2 = new CustomThread(shared, "two"); CustomThread thread3 = new CustomThread(shared, "three"); CustomThread thread4 = new CustomThread(shared, "four"); try { thread1.join(); thread2.join(); thread3.join(); thread4.join(); 图 6-5 synchronize1 运行结果 Java 语言案例教程 104 } catch(InterruptedException e) {} } } class CustomThread extends Thread { Shared shared; public CustomThread(Shared shared, String string) { super(string); this.shared = shared; start(); } public void run() { synchronized(shared) { shared.doWork(Thread.currentThread().getName()); } } } class Shared { void doWork(String string) { System.out.println("Starting " + string); try { Thread.sleep((long) (Math.random() * 500)); } catch (InterruptedException e) {} System.out.println("Ending " + string); } } 注意例子中加阴影的语句,我们将 shared 对象用 synchronized 关键字修饰以 限制对它的访问,执行的结果如图 6-6 所 示,可以看出每个线程都在其他线程结束 后开始自己的任务,程序按照预想的顺序 执行。下面看一下实现同步的另一种方法。 6.4.2 同步方法 在我们定义方法时可以使用 synchronize 关键字,以 阻塞其他线程的通路,使 某 一时刻只 图 6-6 synchronize2 运行结果 第 6 章 多线程编程 105 有一个线程可以使用该方法。下面的例子,注意例中有阴影的语句,它用同步方法来实现上 例的正确同步。 例 6-8 同步方法以实现多线程。 class synchronize3 { public static void main(String args[]) { Shared shared = new Shared(); CustomThread thread1 = new CustomThread(shared, "one"); CustomThread thread2 = new CustomThread(shared, "two"); CustomThread thread3 = new CustomThread(shared, "three"); CustomThread thread4 = new CustomThread(shared, "four"); try { thread1.join(); thread2.join(); thread3.join(); thread4.join(); } catch(InterruptedException e) {} } } class CustomThread extends Thread { Shared shared; public CustomThread(Shared shared, String string) { super(string); this.shared = shared; start(); } public void run() { shared.doWork(Thread.currentThread().getName()); } } class Shared { synchronized void doWork(String string) { System.out.println("Starting " + string); try { Thread.sleep((long) (Math.random() * 500)); Java 语言案例教程 106 } catch (InterruptedException e) {} System.out.println("Ending " + string); } } 提示:只在方法中的代码块上同步比在整个方法上同步要好,因为这样使临界区的长度缩 小为方法的一小部分,从而减少线程等待时间,提高性能,并且,还能降低线程死 锁的可能性。 6.5 线程间的通信 当一个线程在工作时,它可能需要与其他的线程进行协调工作,例如由一个线程进行数 据的写入操作,而另一个线程进行数据的读出操作,显然在没有数据或者写数据线程在工作 时,读线程是不应该进入缓冲区读取的,这便是一个典型的生产者/消费者问题。 为了协调两个线程,特别在一个线程使用另一个线程的输出时,使用 wait 方法、notify 方法和 notifyAll 方法。 l wait:使线程休眠直到调用 notify 方法或者 notifyAll 方法。 l notify:启动同一个对象上第一个调用 wait 的线程。 l notifyAll:启动同一个对象上调用 wait 的所有线程。 通常的步骤是:读取线程调用 wait 方法,然后写入线程在准备好要读取的数据后调用 notify 方法。请看下面的例子。 例 6-9 线程间通信示例。 class Shared { int data = 0; synchronized void doWork() //向 data 中写入数据,然后调 notify 方法启动另一线 程读数据 { try { Thread.sleep(1000); } catch(InterruptedException e) {} data = 1; notify(); } synchronized int getResult() {//先使线程休眠,直到另一线程调用 notify 方法来启 动它读取 data 中的数据 try { wait(); } catch(InterruptedException e) {} 第 6 章 多线程编程 107 return data; } } class CustomThread1 extends Thread { Shared shared; public CustomThread1 (Shared shared, String string) { super(string); this.shared = shared; start(); } public void run() { System.out.println("The result is " + shared.getResult()); } } class CustomThread2 extends Thread { Shared shared; public CustomThread2 (Shared shared, String string) { super(string); this.shared = shared; start(); } public void run() { shared.doWork(); } } class wait { public static void main(String args[]) { Shared shared = new Shared(); CustomThread1 thread1 = new CustomThread1(shared, "one"); CustomThread2 thread2 = new CustomThread2(shared, "two"); } } 在这个例子中,写入线程调用 doWork 方法。这个方法很费时,而调用 getResult 方法读 取结果,必须要等待写入进程完成后才可以读取,因此要调用 wait 方法来等待请求读入,写 Java 语言案例教程 108 入进程在完成写入操作后调用 notify 方法,相当于向读入进程发送一个信号,这样读入进程 便可以进行操作。读者可以结合操作系统原理中有关 P、V 操作以及信号量机制来理解这两 个方法的作用。 注意这个程序的执行过程,读入线程确实进行了等待。具体运行结果如图 6-7 所示。 下面的这个例子,模拟银行存取款的情况,当然这种情况在实际中可能是存在的,只是 作为一个例子来讨论。假设有姐弟三人共同使用一个账号存取款,当一个人要取款时,如果 余额不足,他就会等待别人存款,直到余额满足要求,他才取款成功。 例 6-10 银行存取款案例。 import java.applet.*; import java.awt.*; import java.awt.event.*; class account { private int money; synchronized void save(int NO)//存线 { try{ Thread.sleep(1000); }catch(InterruptedException e) {} this.money+=NO; System.out.println(Thread.currentThread().getName() +"存款成功,金额为"+NO+"余额为"+this.getMoney()); notify(); } synchronized void get(int NO)//取钱 { while(this.money screenSize.height) frameSize.height = screenSize.height; if (frameSize.width > screenSize.width) frameSize.width = screenSize.width; frame.setLocation((screenSize.width - frameSize.width) / 2, (screenSize. 图 7-2 一个简单的 Swing 程序单击按钮后 图 7-1 一个简单的 Swing 程序单击按钮前 第 7 章 Java Swing 基础 115 height - frameSize.height) / 2); frame.show(); } static public void main(String[] args) { try { //使用平台的默认外观和行为 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch(Exception e) { e.printStackTrace(); } new HelloWorldSwing(); } } //主窗口类 class WelcomeFrame extends JFrame { public WelcomeFrame() { enableEvents(AWTEvent.WINDOW_EVENT_MASK); try { jbInit(); } catch(Exception e) { e.printStackTrace(); } } //初始化 private void jbInit() throws Exception { //获得框架的内容面板 contentPane = (JPanel) this.getContentPane(); //设置布局管理器为空,通过绝对位置显示组件 contentPane.setLayout(null); jLabel1.setFont(new java.awt.Font("SansSerif", 1, 22)); jLabel1.setText("HelloWorld Swing"); jLabel1.setBounds(new Rectangle(50, 30, 200, 30)); Java 语言案例教程 116 jButton1.setFont(new java.awt.Font("SansSerif", 0, 12)); jButton1.setText("Click me!"); jButton1.setBorder(BorderFactory.createRaisedBevelBorder()); jButton1.setBounds(new Rectangle(50, 70, 200, 50)); //为按钮添加事件监视器 jButton1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { jButton1_actionPerformed(e); } }); this.setSize(new Dimension(WIDTH, HEIGHT)); this.setTitle("HelloWorld-Swing"); contentPane.add(jLabel1,null); contentPane.add(jButton1,null); } //按钮事件响应代码 void jButton1_actionPerformed(ActionEvent e) { if(flag) jLabel1.setText("Have a Good Day!"); else jLabel1.setText("HelloWorld Swing"); flag = !flag; } JPanel contentPane; JLabel jLabel1 = new JLabel(); JButton jButton1 = new JButton(); boolean flag = true; public static final int WIDTH = 300; public static final int HEIGHT = 200; } 这是一个非常简单的例子,可以通过它来说明编写一个 Swing 程序的基本过程。 1.导入 Swing 包 编写 Swing 程序,首先必须导入 Swing 包: import javax.swing.*; 对于大多数 Swing 程序,可能还需要导入两个主要的 AWT 包: import java.awt.*; import java.awt.event.*; 第 7 章 Java Swing 基础 117 导入 java.awt 包和 java.awt.event 包的目的是在 Swing 程序中也要使用字体类、颜色类等 这些图形界面中的一些基本类,而这些类都包含在 java.awt 包中,其相关事件处理方面的包 则在 java.awt.event 包中。 2.设置外观和行为 Swing 允许编程人员指定程序运行时的外观和行为: UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 在这个例子中使用的是平台默认的外观和行为,这样不会让使用者感到陌生。同时,也可以 使用: UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAnd Feel"); UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel"); 将外观和行为设置成 motif 或者 metal 型。 以下是不同外观和行为的打开对话框,如图 7-3、图 7-4 与图 7-5 所示。 图 7-3 Windows 型的打开对话框 图 7-4 Metal 型的打开对话框 图 7-5 Motif 型的打开对话框 Java 语言案例教程 118 下面的例子演示动态地改变程序的外观和行为的样式。运行效果如图 7-6 所示。 (a)Motif 型外观 (b)Metal 型外观 (c)Windows 型外观 例 7-2 动态地改变程序的外观和行为。 import java.awt.*; import java.awt.event.*; import javax.swing.*; /** 通过单选按钮动态地改变程序的外观和行为 */ public class StyleDemo { boolean packFrame = true; public StyleDemo() { WelcomeFrame frame = new WelcomeFrame(); //设置关闭按钮属性 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //是否用紧凑模式显示组件 if (packFrame) frame.pack(); else frame.validate(); frame.setTitle("StyleDemo"); // 使窗口显示在屏幕中间 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension frameSize = frame.getSize(); if (frameSize.height > screenSize.height) frameSize.height = screenSize.height; if (frameSize.width > screenSize.width) 图 7-6 动态地改变程序的外观和行为 第 7 章 Java Swing 基础 119 frameSize.width = screenSize.width; frame.setLocation((screenSize.width - frameSize.width) / 2, (screenSize. height - frameSize.height) / 2); frame.show(); } static public void main(String[] args) { try { //使用平台的默认外观和行为 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch(Exception e) { e.printStackTrace(); } new StyleDemo(); } } class WelcomeFrame extends JFrame { public WelcomeFrame() { enableEvents(AWTEvent.WINDOW_EVENT_MASK); try { jbInit(); } catch(Exception e) { e.printStackTrace(); } } //初始化 private void jbInit() throws Exception { //获得框架的内容面板 contentPane = (JPanel)this.getContentPane(); contentPane.setLayout(new FlowLayout()); Java 语言案例教程 120 JLabel label = new JLabel("选择观感:"); metalRadioButton = new JRadioButton(metal); metalRadioButton.setMnemonic('m'); metalRadioButton.setActionCommand(metalClassName); motifRadioButton = new JRadioButton(motif); motifRadioButton.setMnemonic('o'); motifRadioButton.setActionCommand(motifClassName); windowsRadioButton = new JRadioButton(windows); windowsRadioButton.setMnemonic('w'); windowsRadioButton.setActionCommand(windowsClassName); ButtonGroup group = new ButtonGroup(); group.add(metalRadioButton); group.add(motifRadioButton); group.add(windowsRadioButton); RadioListener optionListener = new RadioListener(); metalRadioButton.addActionListener(optionListener); motifRadioButton.addActionListener(optionListener); windowsRadioButton.addActionListener(optionListener); contentPane.add(label); contentPane.add(metalRadioButton); contentPane.add(motifRadioButton); contentPane.add(windowsRadioButton); } class RadioListener implements ActionListener { public void actionPerformed(ActionEvent e) { String lnfName = e.getActionCommand(); try { UIManager.setLookAndFeel(lnfName); SwingUtilities.updateComponentTreeUI(contentPane); pack(); } catch (Exception ex) { JRadioButton button = (JRadioButton)e.getSource(); updateState(); System.err.println("样式转换失败"+lnfName); } 第 7 章 Java Swing 基础 121 } public void updateState() { String lnfName = UIManager.getLookAndFeel().getClass().getName(); if (lnfName.indexOf(metal) >= 0) { metalRadioButton.setSelected(true); } else if (lnfName.indexOf(windows) >= 0) { windowsRadioButton.setSelected(true); } else if (lnfName.indexOf(motif) >= 0) { motifRadioButton.setSelected(true); } else { System.err.println("未知 L&F: " + lnfName); } } } JPanel contentPane; static String metal= "Metal"; static String metalClassName = "javax.swing.plaf.metal.MetalLookAndFeel"; static String motif = "Motif"; static String motifClassName="com.sun.java.swing.plaf.motif.Motif Look AndFeel"; static String windows = "Windows"; static String windowsClassName="com.sun.java.swing.plaf.windows.Windows LookAndFeel"; JRadioButton metalRadioButton; JRadioButton motifRadioButton; JRadioButton windowsRadioButton; public static final int WIDTH = 300; public static final int HEIGHT = 200; } 3.设置顶层容器 Swing 的每一个 GUI 程序都至少有一个顶层的 Swing 容器。一个顶层的 Swing 容器提供 了对 Swing 组件所需要的绘制和事件处理的支持。对于大多数程序而言,顶层的 Swing 容器 是 JFrame,JDialog 或 JApplet(对于 Applet)。每一个 JFrame 对象实现一个主窗口,每一个 JDialog 对象实现一个二级窗口(二级窗口依赖于另一个窗口),每一个 JApplet 对象在浏览器 窗口中实现一个 Applet 的显示域。 在例 7-1 中,HelloWorldSwing 只有一个顶层容器:JFrame。当用户关闭这个 Frame 时, Java 语言案例教程 122 程序将退出: frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 在这个 Swing 程序中,共创建了四个 Swing 组件:主窗口 frame,它是顶层窗口,主要 是提供一个放置其他组件的空间;contentPane 是一个中间层容器,目的是简化 jButton1 和 jLabel1 位置的确定,利于组件的布局;JButton1 和 Jlabel1 属于原子型的组件,它们不能再 含有其他任何组件。通常,这些原子型组件被用于接收用户的输入。Swing API 提供了许多 原子型组件,掌握这些组件的特性和用法是学习 Swing 编程最主要的内容。 每一个顶层容器间接地含有一个中间层容器,称为内容面板(content pane)。内容面板 直接或间接地含有所有可见的组件,但是,如果顶层容器有一个菜单条,那么,菜单条通常 被放置在内容面板之外的某个位置。 增加一个组件到一个容器,可以使用 add 方法。add 方法至少应有一个参数,即需要增 加的组件,而且,有时可能还需要另外一个参数来提供布局信息。例如,在例 7-1 中 frame 采用的是 null 布局,这时需要指定每个组件的相对于主窗口左上角的绝对位置。 注意:与 AWT 组件不同,Swing 组件不能直接添加到顶层容器中,它必须先用 getContentPane()方法来获得一个与 Swing 顶层容器相关联的内容面板,再将分别将组件添加 到这个内容面板中。 4.设置组件 通过设置组件的属性状态等完成图形界面的初始化。例如: jLabel1.setFont(new java.awt.Font("SansSerif", 1, 22)); jLabel1.setText("HelloWorld Swing"); jButton1.setFont(new java.awt.Font("SansSerif", 0, 12)); jButton1.setText("Click me!"); jButton1.setBorder(BorderFactory.createRaisedBevelBorder()); this.setSize(new Dimension(WIDTH, HEIGHT)); this.setTitle("HelloWorld-Swing"); 这里设置了 jLabel1 和 jButton1 的字体、显示的内容和主窗口的大小、标题等属性。 说明:Swing 组件也都是 AWT 的 Component 类的直接子类或间接子类。 5.增加组件 组件需要添加到容器后才能显示出来: contentPane.setLayout(null); jLabel1.setBounds(new Rectangle(50, 30, 200, 30)); jButton1.setBounds(new Rectangle(50, 70, 200, 50)); contentPane.add(jLabel1,null); contentPane.add(jButton1,null); null 布局可以灵活地设置各个组件的位置,但手工编写代码比较麻烦,最好通过一些可 视化的工具来辅助完成布局。同 时 ,在 null 布局时主窗口不能使用 pack()方法来实现紧凑。 布局管理部分将在后面详细讲解。 6.添加事件处理代码 通过添加事件监视器来响应用户的操作,例如: 第 7 章 Java Swing 基础 123 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 当用户单击关闭按钮时,程序退出。 jButton1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { jButton1_actionPerformed(e); } }); 当用户单击 jButton1 这个按钮时,由 jButton1_actionPerformed()这个方法来响应: void jButton1_actionPerformed(ActionEvent e) { if(flag) jLabel1.setText("Have a Good Day!"); else jLabel1.setText("HelloWorld Swing"); flag = !flag; } 该方法将 jLabel1 中的内容在“Have a Good Day!”和“Hello World Swing”间相互转换。 Swing 程序中的事件处理和 AWT 基本一致。熟悉了 Swing 程序的基本编写步骤后,再 学习一些在 Swing 中比较重要的几个类。 7.4 重 要 的 类 7.4.1 JComponent 类 JComponent 类是所有轻量级组件的父类。JPanel、JScollPane、JButton 和 JTable 等都继 承于 JComponent 类。但是,JFrame 和 JDialog 并非如此,JFrame 类是 java.awt 包中 Frame 类的子类,JDialog 是 java.awt 包中 Dialog 类的子类。JComponent 类的子类见表 7-1,这些组 件的使用都非常类似,不再做详细的讲解。 表 7-1 JComponent 类的子类 类 名 功 能 JButton 负责创建按钮对象 JComboBox 负责创建组合多选框对象 JCheckBox 负责创建复选框对象 JFileChooser 负责创建文件选择器对象 JInternalFrame 负责创建内部窗口对象 Java 语言案例教程 124 续表 类 名 功 能 JLable 负责创建标签对象 JMenu 负责创建菜单对象 JMenuBar 负责创建菜单条对象 JMenuItem 负责创建菜单项对象 JPanel 负责创建面板对象 JPasswordField 负责创建密码域对象 JPopupMenu 负责创建弹出式菜单 JProgressBar 负责创建进程条 JRadioButton 负责创建单选按钮 JScrollBar 负责创建滚动条 JScrollPane 负责创建滚动窗口 JSlider 负责创建滑动条 JSplitPane 负责创建拆分窗格 JTable 负责创建表 JTextArea 负责创建文本区 JTextPane 负责创建文本窗口 JToolBar 负责创建工具条 JToolTip 负责创建工具提示对象 JTree 负责创建树对象 JComponent 类继承于 Container 类,而 Container 类又继承于 Component 类。Component 类包括了从布 局提示到绘制以及事件支持的所有内容。Container 类 支持在容器中添加并放置组件,因此所有的轻量级组 件也都是容器。 下面介绍 JComponent 的特性。 1.工具提示 在 setToolTipText 方法中指定一个字符串来为用户提供有关该组件的一些帮助。当鼠标 停留在组件上时,指定的字符串将出现在靠近该组件的一个小窗口中。例如,在 HelloWorldSwing 这个程序中添加以下代码,则运行时的效果如图 7-7 所示。 jButton1.setToolTipText("Click me!"); 2.边框 setBorder 方法允许用户设置组件的边框。例如,在 HelloWorldSwing 这个程序中对按钮 分别使用不同的边框,其效果如图 7-8 所示。 jButton1.setBorder(BorderFactory.createRaisedBevelBorder()); 图 7-7 工具提示 第 7 章 Java Swing 基础 125 jButton1.setBorder(BorderFactory.createLoweredBevelBorder()); jButton1.setBorder(BorderFactory.createCompoundBorder()); 图 7-8 边框示例 3.可变外观和行为 每一个 JComponent 对象都有一个对应的 ComponentUI 对象,该对象将为组件执行所有 的绘制、事件处理、确定大小等操作。通过 UIManager.setLookAndFeel 方法设置组件的外观 和行为。可参考前面的例 7-2。 JComponent 类还有自定义属性、布局的支持、无障碍功能的支持、拖放的支持、双缓冲 和键盘绑定等特性,有的将在本章后面详细介绍,其他的不再做更多的讲述。 7.4.2 顶层容器 JFrame、JDialog 和 JApplet 都是重量容器,可以由它们创建顶层容器。由它们本身或它 们的子类创建的窗体是 Swing 窗体。不能把组件直接添加到 Swing 窗体中,而应当把组件添 加到内容面板中(内容面板也是重量容器)。 不能为 Swing 窗体设置布局,而应当为 Swing 窗体的内容面板设置布局。内容面板的默 认布局是 BorderLayout 布局。Swing 窗体通过调用 getContentPane 方法得到它的内容面板。 以下是 JApplet 和 JDialog 的例子。这两个例子的外观和行为如图 7-9 和图 7-10 所示。 图 7-9 JApplet 示例 (b)LoweredBevelBorder 边框效果 (a)RaisedBevelBorder 边框效果 (c)CompoundBorder 边框效果 Java 语言案例教程 126 例 7-3 JApplet 示例。 import javax.swing.*; import java.awt.*; public class JAppletDemo extends JApplet { JButton jButton1; JLabel jLabel1; public void init() { jButton1 = new JButton("Click me!");//创建一个按钮,该按钮上显示"Click me!" jLabel1 = new JLabel("JAppletDemo");//创建一个标签,内容为"JAppletDemo" getContentPane().add(jLabel1,BorderLayout.NORTH); getContentPane().add(jButton1,BorderLayout.SOUTH); } } 编写一个 HTML 页,添加以下内容: 例 7-4 JDialog 示例。 import javax.swing.*; import java.awt.*; import java.awt.event.*; public class JDialogDemo { boolean packFrame = true; public JDialogDemo() { MainFrame frame = new MainFrame(); //设置关闭按钮属性 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //是否用紧凑模式显示组件 if (packFrame) frame.pack(); 图 7-10 JDialog 示例 第 7 章 Java Swing 基础 127 else frame.validate(); // 使窗口显示在屏幕中间 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension frameSize = frame.getSize(); if (frameSize.height > screenSize.height) frameSize.height = screenSize.height; if (frameSize.width > screenSize.width) frameSize.width = screenSize.width; frame.setLocation((screenSize.width-frameSize.width)/2,(screenSize. height - frameSize.height) / 2); frame.show(); } static public void main(String[] args) { new JDialogDemo(); } } class MainFrame extends JFrame { public MainFrame() { enableEvents(AWTEvent.WINDOW_EVENT_MASK); try { jbInit(); } catch(Exception e) { e.printStackTrace(); } } //初始化 private void jbInit() throws Exception { //获得框架的面板 contentPane = (JPanel) this.getContentPane(); //设置布局管理器为空,通过绝对位置显示组件 jButton1.setFont(new java.awt.Font("SansSerif", 0, 12)); jButton1.setText("关于"); Java 语言案例教程 128 //为按钮添加事件监视器 jButton1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { jButton1_actionPerformed(e); } }); this.setSize(new Dimension(WIDTH, HEIGHT)); this.setTitle("MainFrame"); contentPane.add(jButton1,null); } //按钮事件响应 void jButton1_actionPerformed(ActionEvent e) { AboutDialog about = new AboutDialog(this,"JDialogDemo 对话框"); about.setSize(300,150); about.show(); } JPanel contentPane; JButton jButton1 = new JButton(); public static final int WIDTH = 200; public static final int HEIGHT = 100; } class AboutDialog extends JDialog { public AboutDialog(JFrame mainFrame,String s) { super(mainFrame,s); JButton jButton1 = new JButton("确定"); getContentPane().add(jButton1,BorderLayout.CENTER); } } 7.4.3 布局管理 布局管理就是确定组件的尺寸和位置的过程。缺省地,每一个容器都有一个布局管理器, 组件可以向布局管理器提供自己希望的尺寸和排列方式,但是,这些组件的实际尺寸及其位 置最终还是由布局管理器来确定。 第 7 章 Java Swing 基础 129 一些 AWT 和 Swing 类提供了常用的布局管理器: l BorderLayout l BoxLayout l CardLayout l FlowLayout l GridBagLayout l GridLayout l SpringLayout 布局管理器可以同时显示多个组件。因此,当使用 add 方法将组件加进一个容器时,必 须考虑该容器的布局管理器。 说明:因为 Swing 中的组件都是放在内容面板中的,所以在 Swing 中设置布局管理器是 针对于内容面板的。另外,与 AWT 相比 Swing 新增加了一个 BoxLayout 布局管理器。 由于与 AWT 基本一样,下面仅以一个例子来演示 BorderLayout 布局管理器的使用。完 成效果如图 7-11 所示。 例 7-5 BorderLayout 布局管理器示例。 import java.awt.*; import javax.swing.*; public class BorderLayoutDemo { boolean packFrame = true; public BorderLayoutDemo() { MainFrame frame = new MainFrame(); //设置关闭按钮属性 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //是否用紧凑模式显示组件 if (packFrame) frame.pack(); else frame.validate(); // 使窗口显示在屏幕中间 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension frameSize = frame.getSize(); if (frameSize.height > screenSize.height) 图 7-11 BorderLayout 布局管理器示例的最终效果 Java 语言案例教程 130 frameSize.height = screenSize.height; if (frameSize.width > screenSize.width) frameSize.width = screenSize.width; frame.setLocation((screenSize.width - frameSize.width) / 2, (screenSize. height - frameSize.height) / 2); frame.show(); } static public void main(String[] args) { new BorderLayoutDemo(); } } class MainFrame extends JFrame { public MainFrame() { enableEvents(AWTEvent.WINDOW_EVENT_MASK); try { jbInit(); } catch(Exception e) { e.printStackTrace(); } } //初始化 private void jbInit() throws Exception { contentPane = (JPanel) this.getContentPane(); contentPane.add(new JButton("Button 1 (NORTH)"), BorderLayout.NORTH); contentPane.add(new JButton("2 (CENTER)"), BorderLayout.CENTER); contentPane.add(new JButton("Button 3 (WEST)"), BorderLayout.WEST); contentPane.add(new JButton("Long_Name Button 4 (SOUTH)"), BorderLayout.SOUTH); contentPane.add(new JButton("Button 5 (EAST)"), BorderLayout.EAST); 第 7 章 Java Swing 基础 131 this.setTitle("BorderLayoutDemo"); } JPanel contentPane; public static final int WIDTH = 300; public static final int HEIGHT = 200; } 7.4.4 Swing 高级组件简介 1.JTree 类 在应用程序中常常需要用图形的方式显示众多结点集合以及它们之间的层次关系,所以 常常使用 JTree 类。JTree 类也是 Swing 中比较复杂的组件之一。一个 JTree 类并没有真正包 含用户的数据,它只是提供了数据的一个视图,可以通过查询数据模型来获得数据。 提示:JTree 类和接口放在 javax.swing.tree 包中。 下面是一个使用 JTree 类的例子。其外观和行为如图 7-12 所示。 例 7-6 JTree 类使用的示例。 //JTreeDemo import javax.swing.*; import java.awt.*; import java.awt.event.*; import javax.swing.tree.*; import javax.swing.event.*; /** */ //主程序类 public class JTreeDemo { boolean packFrame = true; public JTreeDemo() { MainFrame frame = new MainFrame(); //设置关闭按钮属性 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //是否用紧凑模式显示组件 if (packFrame) frame.pack(); else 图 7-12 JTree 类的使用 Java 语言案例教程 132 frame.validate(); // 使窗口显示在屏幕中间 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension frameSize = frame.getSize(); if (frameSize.height > screenSize.height) frameSize.height = screenSize.height; if (frameSize.width > screenSize.width) frameSize.width = screenSize.width; frame.setLocation((screenSize.width - frameSize.width) / 2, (screenSize. height - frameSize.height) / 2); frame.show(); } static public void main(String[] args) { new JTreeDemo(); } } //主窗口类 class MainFrame extends JFrame { public MainFrame() { enableEvents(AWTEvent.WINDOW_EVENT_MASK); try { jbInit(); } catch(Exception e) { e.printStackTrace(); } } //初始化 private void jbInit() throws Exception { //获得框架的内容面板 contentPane = (JPanel) this.getContentPane(); DefaultMutableTreeNode root = new DefaultMutableTreeNode("Company"); JTree tree = new JTree(root); DefaultMutableTreeNode dept = new DefaultMutableTreeNode("R&D"); 第 7 章 Java Swing 基础 133 root.add(dept); DefaultMutableTreeNode group = new DefaultMutableTreeNode("Hardware"); dept.add(group); DefaultMutableTreeNode team = new DefaultMutableTreeNode("Wireless Hardware"); group.add(team); team = new DefaultMutableTreeNode("BoardBand Hardware"); group.add(team); group = new DefaultMutableTreeNode("Software"); dept.add(group); team = new DefaultMutableTreeNode("Embedded System"); group.add(team); team = new DefaultMutableTreeNode("Application"); group.add(team); tree.getSelectionModel().setSelectionNode( TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); //设置每个结点的行高 tree.setRowHeight(20); tree.setPreferredSize(new Dimension(250,200)); contentPane.add(tree); this.setTitle("JTreeDemo"); }//设置布局管理器为空,通过绝对位置显示组件 JPanel contentPane; public static final int WIDTH = 300; public static final int HEIGHT = 200; } 说明:JTree 类的一个实例就代表一棵树,而一棵树由许多结点组成。JTree 中的每一个 结 点 都 是 接 口,这既可以由编程者自己实现,也可以直接利用 Swing 类 提供的 DefaultMutableTreeNode 方法来实现。 2.JTable 类 在应用程序中经常使用表来显示和编辑数据。表有很多复杂的属性,因此也是最复杂的 Swing 组件之一。下面是 JTable 类使用的一个实例,完成效果如图 7-13 所示。 图 7-13 表的使用 Java 语言案例教程 134 提示:JTable 类和接口放在 javax.swing.table 包中。 例 7-7 JTable 类使用的示例。 import javax.swing.*; import javax.swing.table.*; import javax.swing.border.*; import java.awt.*; import java.awt.event.*; public class JTableDemo { boolean packFrame = true; public JTableDemo() { MainFrame frame = new MainFrame(); //设置关闭按钮属性 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //是否用紧凑模式显示组件 if (packFrame) frame.pack(); else frame.validate(); // 使窗口显示在屏幕中间 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension frameSize = frame.getSize(); if (frameSize.height > screenSize.height) frameSize.height = screenSize.height; if (frameSize.width > screenSize.width) frameSize.width = screenSize.width; frame.setLocation((screenSize.width - frameSize.width) / 2,(screenSize. height - frameSize.height) / 2); frame.show(); } static public void main(String[] args) { new JTableDemo(); } } class MainFrame extends JFrame { public MainFrame() { enableEvents(AWTEvent.WINDOW_EVENT_MASK); try 第 7 章 Java Swing 基础 135 { jbInit(); } catch(Exception e) { e.printStackTrace(); } } //初始化 private void jbInit() throws Exception { //获得框架的内容面板 contentPane = (JPanel) this.getContentPane(); final String[] names = {"First Name", "Last Name", "Favorite Color", "Favorite Number", "Vegetarian"}; final Object[][] data = { {"Mark", "Andrews", "Red", new Integer(2), new Boolean(true)}, {"Tom", "Ball", "Blue", new Integer(99), new Boolean(false)}, {"Alan", "Chung", "Green", new Integer(838), new Boolean(false)}, {"Jeff", "Dinkins", "Turquois", new Integer(8), new Boolean(true)}, {"Amy", "Fowler", "Yellow", new Integer(3), new Boolean(false)}, {"Brian", "Gerhold", "Green", new Integer(0), new Boolean(false)}, {"James", "Gosling", "Pink", new Integer(21), new Boolean(false)}, {"David", "Karlton", "Red", new Integer(1), new Boolean(false)}}; TableModel dataModel = new AbstractTableModel() { // 这些方法需要实现 public int getColumnCount() { return names.length; } public int getRowCount() { return data.length;} public Object getValueAt(int row, int col) {return data[row][col];} // 这些方法在 bbstraltTablemode 类里实现,可以覆盖它们 // AbstractTableModel 会运作,但是能精简它们 public String getColumnName(int column) {return names[column];} public Class getColumnClass(int c) {return getValueAt(0, c).getClass();} public boolean isCellEditable(int row, int col) {return true;} public void setValueAt(Object aValue, int row, int column) { System.out.println("Setting value to: " + aValue); data[row][column] = aValue; } }; //创建表 JTable tableView = new JTable(dataModel);//关闭自动调整尺寸两项,这样可以编 程设置列宽,使每列得到满意的宽度 Java 语言案例教程 136 tableView.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); // 创建一个组合框 JComboBox comboBox = new JComboBox(); comboBox.addItem("Red"); comboBox.addItem("Orange"); comboBox.addItem("Yellow"); comboBox.addItem("Green"); comboBox.addItem("Blue"); comboBox.addItem("Indigo"); comboBox.addItem("Violet"); TableColumn colorColumn = tableView.getColumn("Favorite Color"); // 用这个组合框作为最喜欢颜色列中的编辑器 colorColumn.setCellEditor(new DefaultCellEditor(comboBox)); // 为颜色列设置粉红色背景和工具提示 DefaultTableCellRenderer colorColumnRenderer = new DefaultTableCellRenderer(); colorColumnRenderer.setBackground(Color.pink); colorColumnRenderer.setToolTipText("Click for combo box"); colorColumn.setCellRenderer(colorColumnRenderer); //为颜色列头部设置工具提示 TableCellRenderer headerRenderer = colorColumn.getHeaderRenderer(); if (headerRenderer instanceof DefaultTableCellRenderer) ((DefaultTableCellRenderer)headerRenderer).setToolTipText("Hi Mom!"); //设置 Vegetarian 列的宽 TableColumn vegetarianColumn = tableView.getColumn("Vegetarian"); vegetarianColumn.setPreferredWidth(100); //以不同颜色显示最喜欢的数列中的各类 TableColumn numbersColumn = tableView.getColumn("Favorite Number"); DefaultTableCellRenderer numberColumnRenderer = new DefaultTableCellRenderer() { public void setValue(Object value) { int cellValue = (value instanceof Number) ? ((Number)value).intValue() : 0; setForeground((cellValue > 30) ? Color.black : Color.red); setText((value == null) ? "" : value.toString()); } }; numberColumnRenderer.setHorizontalAlignment(JLabel.RIGHT); numbersColumn.setCellRenderer(numberColumnRenderer); numbersColumn.setPreferredWidth(110); //完成表示的建立 JScrollPane scrollpane = new JScrollPane(tableView); 第 7 章 Java Swing 基础 137 scrollpane.setBorder(new BevelBorder(BevelBorder.LOWERED)); scrollpane.setPreferredSize(new Dimension(430, 200)); contentPane.add(scrollpane); this.setTitle("JTableDemo"); } JPanel contentPane; public static final int WIDTH = 300; public static final int HEIGHT = 200; } 这个例子只是简单地给出了 JTable 类的基本实现。表是 Swing 中公认的最复杂的组件之 一,所以读者还应该对照 API 手册详细了解这些类的属性、接口及其实现。 说明:为了解决 JTable 设计中的种种复杂问题,Swing 提供各种表格模型( 如 :TableModel、 TableColumnModel 和 ListSelectionModel)以增加表设计的弹性。其中 TableModel 是一个接 口,在这个接口里面定义了若干个方法,包括存取表单元格(cell)的内容、计算表的列数等 基本存取操作,从而让程序员可以简单地利用 TableModel 来制作所想要的表。 【本章小结】 本章简要地介绍 Java 语言中有关图形用户界面编程方面的知识。Java 语言所提供的 AWT 和 Swing 类库对图形用户界面编程提供了强有力的支持。在图形用户界面编程方面,要学会 如何使用组件、容器、布局管理器,并掌握图形用户界面的事件处理模型。本章重点讲解 Swing,包括 Swing 程序的功能、编写 Swing 程序的基本过程以及 Swing 中两个高级组件 ——树和表。Swing 编程的详细内容,请读者参考关于 Swing 的专门的书籍。 【习题】 一、选择题 1.Java 程序要使用基本的 Swing GUI 组件,应用程序必须要引入下面哪个包?( ) A.java.awt B.javax.swing C.java.lang D.javax.tree 2.关于 AWT 和 Swing 的差别,下面说法中哪个是错误的?( ) A.AWT 和 Swing 的按钮和标签都可以显示图像 B.通过调用 Swing 组件上的方法或创建一个子类,可以很容易改变 Swing 组件的行 为和外观 C.Swing 组件可以不是矩形,例如,按钮可以是椭圆形 D.Accessibility 技术(例如触摸屏)可以很容易地从 Swing 组件得到信息 3.Swing 中内容面板的默认布局是( )。 A . BorderLayout 布局 B.FlowLayout 布局 C.GridLayout 布局 D.BoxLayout 布局 4.相对于 AWT,在 Swing 中新增加的布局管理器是( )。 A . BorderLayout B.BoxLayout C.CardLayout D.FlowLayout Java 语言案例教程 138 5.下面关于 JTree 的说法,哪一种是错误的?( ) A.一个 JTree 类并没有真正包含数据,它只是提供了数据的一个视图 B.树可以通过查询数据模型获得数据 C.处理 JTree 的类和接口是放在 javax.swing 包中的 D.JTree 类用于在应用程序中以图形的方式显示众多结点集合以及它们之间的层次 关系 二、判断题 1.AWT 组件是借助于本地的同位体组件,所以 AWT 组件又称为重量组件。 2.每一个顶层容器间接地含有一个中间层容器,称为内容面板(content pane)。 3 . JComponet 类是所有轻量级组件的父类。JPanel、JScollPane、JButton、JTable、JFrame 和 JDialog 等都继承了 JComponet 类。 4.JTree 中的每一个结点都必须实现 TreeNode 这个接口。 5.并不是每一个 Swing 的 GUI 程序都有一个顶层的 Swing 容器。 三、简答题 1.设计和实现图形用户界面的编程工作主要有哪几个方面? 2.简要说明 Swing 和 AWT 之间的区别和联系。 3.试述简单的 Swing 程序的编写过程。 四、实践题 1.选择一款可视化的编程工具(JBuilder、NetBeans 等),用它们创建应用程序的窗口, 看看它们是如何安排代码的。 2.编写 GUI 版本的“HelloWorld!” 程序,在窗口上绘制“HelloWorld!”,包括 Swing 版本和 AWT 版本。 3.用 Swing 编写一个小型计算器,包括 0~9、.、+、-、*、/、=等按钮,能实现简 单的加、减、乘、除运算。 4.编写一个树型组件,以树型方式显示目录。 5.编写一个数据库应用系统,将查询返回的结果放入一个表里。 第 8 章 Java 与多媒体 【学习目标】 1.掌握图像文件的显示。 2.掌握 Java 媒体框架(JMF)的运用。 3.学会在 Java 程序中显示图像和播放音频。 8.1 图像文件的显示 在 Graphics 类中提供了不少绘制图形的方法,但如果用它们在程序运行过程中实时地绘 制一幅较复杂的图形,就好比是在用斧头和木块去制造航天飞机。因此,对于复杂图形,大 部分都是事先用专用的绘图软件绘制好,或者是用其他截取图像的工具(如扫描仪、视效卡、 数码相机等)获取图像的数据信息,再将它们按一定的格式存为图像文件。当程序运行时, 只要找到图像文件存储的位置,将它装载到内存里,然后在适当的时机将它显示在屏幕上就 可以了。 目前 Java 支持 3 种主要的图像类型:BMP、JPEG 和 GIF。如果是其他格式的图片,则 需要先转换成这三种格式。 为了能显示一幅图像,首先要将图像文件加载到内存里。Applet 类提供了一个重要的方 法,用于从文件加载图像: public Image getImage(URL url) public Image getImage(URL url,String name) 其中,第一种方式要指明图像文件的绝对 URL 地址,第 二种方式则要指明图像文件的基地址 以及图像文件名。当 Applet 与图像文件处于同一日录下时,可以用 getCodeBase 方法获取基 地址。同样,如果图像与包含 Applet 的 HTML 文件在同一目录下时,可以用 getDocumentBase 方法获得其基地址。 这个方法返回一个 Image 类的对象,Image 类在 java.awt 包中。图像加载后,就可以使 用 paint 方法绘制它了。 Graphics 类提供了一个 drawImage 方法,它能将 Image 对象中的图像显示在屏幕的特定 位置上,就像显示文本一样方便。drawImage 方法的调用格式如下: boolean drawImage(Image img, int x, int y, ImageObserver observer); 其中,img 参数就是要显示的 Image 对象。x 和 y 参数是该图像左上角的坐标值。observer 参 数则是一个 ImageObserver 接口,它用来跟踪图像文件装载的情况,通常将该参数置为 this, 即传递本对象的引用去实现这个接口。 除了将图像文件原样输出以外,drawImage 方法的另外一种调用格式还能指定图像显示 区域的大小: Java 语言案例教程 140 boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) 这种格式比第一种格式多了两个参数:width 和 height,即表示图像显示的宽度和高度。 若图像实际的宽度和高度与这两个参数值不一样,则 Java 系统会自动将它进行缩放,以适合 所指定的矩形区域。 有时,为了不使图像因缩放而变形失真,可以将原图的宽和高均按相同的比例进行缩小 或放大。那么怎样知道原图的大小呢?只需调用 Image 类中的两个方法就可以分别得到原图 的宽度和高度。它们的调用格式如下: int getWidth(ImageObserver observer); int getHeight(ImageObserver observer); 同 drawImage 方法一样,通常用 this 作为 observer 的参数值。 下面是运行 Java 程序显示图像的一个实例,运行效果如图 8-1 所示。 例 8-1 显示图像示例。 import java.awt.Graphics; import java.awt.Image; public class ImageDemo extends java.applet.Applet{ Image img; public void init(){ try{ img=getImage(getCodeBase(),"1107.gif");//获取 applet 目录下的 1107.gif 图 } catch(Exception e) { System.out.println(e); } } 图 8-1 在Java 程序中显示图像 第 8 章 Java 与多媒体 141 public void paint(Graphics g){ int w=img.getWidth(this); int h=img.getHeight(this); g.drawImage(img,20,10,this); //原图 g.drawImage(img,200,10,w/2,h/2,this); //缩小一半 g.drawImage(img,20,200,w*2,h/3,this); //宽扁图 g.drawImage(img,350,10,w/2,h*2,this); //瘦高图 } } 8.2 声音文件的播放 用 Java 语言可以直接编写播放 au、aiff、wav、midi、rfm 音频格式的程序。在 安 装 了 JMF (Java 媒体框架)后,可支持的音频格式更多。要在 applet 中播放声音,可以用 Applet 的静 态方法: newAudioClip(URL url,String name) 或 Applet 类的实例方法: getAudioClip(URL url,String name) 来获得一个 AudioClip 类型的对象。然后调用 play 方法播放这个音频对象。也可以直接使用 Play 方法: void play(URL url) void play(URL url, String name) 从而加载和播放一起完成。当直接使用这个方法时,音频只会 播放一遍。而在使用 AudioClip 类型的对象时,可以通过 loop 方法和 stop 方法来控制音频的循环播放和停止播放。 下面是播放声音文件的实例,运行效果如图 8-2 所示。 例 8-2 声音文件播放的示例。 import java.applet.*; import java.awt.*; import java.awt.event.*; public class AudioDemo extends Applet implements ActionListener { AudioClip clip; Button b_play,b_loop,b_stop; public void init() { clip = getAudioClip(getDocumentBase(),"space.au"); b_play = new Button("play");b_play.addActionListener(this);//注册监 听器,监听 b_play 图 8-2 播放声音文件 Java 语言案例教程 142 b_loop = new Button("loop");b_loop.addActionListener(this); b_stop = new Button("stop");b_stop.addActionListener(this); add(b_play); add(b_loop); add(b_stop); } public void stop() { if (clip!=null) clip.stop(); } public void actionPerformed(ActionEvent e) { if(e.getSource()==b_play) clip.play(); else if(e.getSource()==b_loop) clip.loop(); else if(e.getSource()==b_stop) clip.stop(); } } 8.3 Java 媒体框架 Java 媒体框架(JMF,Java Media Framework)可以让基于 JAVA 的应 用实现音频、视频 的捕捉、处理、播放、传输等功能。利用它,可以实现音、视频播放,网络视频聊天、视频 会议等功能。可以从 Sun 公司的网站上下载 JMF 包。 说明:目前,JMF 有 Windows,Solaris,Linux 等版本,以及可运行在任何装有 Java 虚 拟机的计算机上的一个纯 Java 版本。为了增加性能,需要下载一个与你操作系统相适应的版 本。作为选择,也可以选择纯 Java 版本,或者跨平台版本的 JMF。这些版本没有使用操作系 统特有的库文件。这对于没有 JMF 版本的操作系统,是一个不错的选择。 建立一个播放视频的多媒体程序的基本步骤如下: (1)创建一个播放器 try{ URL url = new URL (getDocumentBase(),file); Player player = Manager.createPlayer(url); } catch(IOException e){} 在上述代码中,首先为视频文件 file 创建一个 URL 对象,然后用 javax.media 包中的 Player 第 8 章 Java 与多媒体 143 类声明一个播放器的对象:player,并使用 javax.media 包中的 Manager 类调用它的静态方法, 向这个方法传递参数 url,从而为媒体文件创建这个播放器:player。 说明:javax.media 包是由 JMF 定义的多个包之一。javax.media 包是一个核心包,包括 了 Manager 类和 Player 接口等。 (2)向播放器注册控制监视器 player.addControllerListener(监视器); 因此,创建监视器的类必须使用 ControllerListener 接口,并实现该接口中的一个方法: public void controllerUpdate(ControllerEvent e) (3)让播放器对文件进行预提取 player.prefetch(); 播放器进行媒体预提取时,将不断地获得媒体文件的有关信息,每得到一个新的信息都 将导致 ControllerEvent 事件的发生,并且将调用方法 controllerUpdate 方法做处理,比如获得 用于播放媒体文件的可视组件、控制组件等。 (4)启动播放器 player.start(); (5)停止播放器 player.stop(); 当播放器停止后,播放器应释放内存中的资源: player.deallcate(); 下面是一个简单的媒体播放器的例子,运行效果如图 8-3 所示。 例 8-3 简单的媒体播放器示例。 import javax.media.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.io.*; 图 8-3 播放视频文件 Java 语言案例教程 144 public class JMediaPlayer { boolean packFrame = false; public JMediaPlayer() { MainFrame frame = new MainFrame(); //设置关闭按钮属性 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //是否用紧凑模式显示组件 if (packFrame) frame.pack(); else frame.validate(); // 使窗口显示在屏幕中间 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension frameSize = frame.getSize(); if (frameSize.height > screenSize.height) frameSize.height = screenSize.height; if (frameSize.width > screenSize.width) frameSize.width = screenSize.width; frame.setLocation((screenSize.width - frameSize.width) / 2, (screenSize. height- frameSize.height) / 2); frame.show(); } static public void main(String[] args) { new JMediaPlayer(); } } class MainFrame extends JFrame implements ControllerListener { public MainFrame() { enableEvents(AWTEvent.WINDOW_EVENT_MASK); try { jbInit(); } catch(Exception e) { 第 8 章 Java 与多媒体 145 e.printStackTrace(); } } //初始化 private void jbInit() throws Exception { contentPane = (JPanel) this.getContentPane(); addWindowListener(new WindowAdapter (){ public void windowClosing (WindowEvent e) { dispose (); } public void windowClosed (WindowEvent e) { if (player != null) player.close (); System.exit (0); } }); JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); JMenu fileMenu = new JMenu("文件"); JMenuItem openMenuItem = new JMenuItem("打开",KeyEvent.VK_O); JMenuItem exitMenuItem = new JMenuItem("退出",KeyEvent.VK_E); fileMenu.add(openMenuItem); fileMenu.add(exitMenuItem); openMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { open(); } }); exitMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dispose(); System.exit(0); } }); menuBar.add(fileMenu); this.setTitle("JMediaPlayerDemo"); this.setSize(new Dimension(WIDTH, HEIGHT)); Java 语言案例教程 146 } public void open()//打开媒体文件 { final JFileChooser fc=new JFileChooser(); int returnVal = fc.showOpenDialog(this); if(returnVal == JFileChooser.APPROVE_OPTION) { File file = fc.getSelectedFile(); if (player != null) player.close(); try { player = Manager.createPlayer(file.toURL()); } catch(Exception e) { System.out.println(e); } player.addControllerListener (this); player.prefetch (); } } public synchronized void controllerUpdate(ControllerEvent e) {//调用 player.prefetch ()时 ControllerEvent 事件出现 if (e instanceof ControllerClosedEvent)//如果播放器关闭时 { if (vc != null)//如果视觉文件还存在 { contentPane.remove (vc); vc = null; //如果控制面板部件还存在 } if (cc != null) { contentPane.remove (cc); cc = null; } return; } if (e instanceof EndOfMediaEvent)//当媒体播放结束时 第 8 章 Java 与多媒体 147 { return; } if (e instanceof PrefetchCompleteEvent) //如果读取媒体文件内容结束时 { player.start (); //开始播放该媒体 return; } if (e instanceof RealizeCompleteEvent) //当实例化媒体文件完成时 { vc = player.getVisualComponent(); //获取播放器对象的视觉文件 if (vc != null) //如果视觉文件存在 contentPane.add (vc); cc = player.getControlPanelComponent();//获取播放器的控制面板部件 if (cc != null)//如果控制面板部件存在 contentPane.add(cc, BorderLayout.SOUTH); pack (); } } public void update (Graphics g) { paint (g); } Player player = null; JPanel contentPane; Component vc, cc; public static final int WIDTH = 300; public static final int HEIGHT = 200; } 【本章小结】 Java 语言的内置类库对多媒体技术的支持能力相当强,尤其是对文本、图形、图像、声 音等媒体的处理与展示均提供了极其方便而又丰富的接口。本章介绍了如何使用 Image 类来 显示图形图像,使用 play 方法播放声音文件,对 Java 媒体框架也作了说明,并列举了相应 的程序实例来说明如何利用 JMF 来编写媒体播放器。对于 Java 在多媒体方面更多的应用, 可参阅更多的资料。 Java 语言案例教程 148 一、问答题 1.目前 Java 支持的主要图像类型有哪几种? 2.如何用 Image 类来进行图像处理? 3.如何用 Java 多媒体框架来开发多媒体程序? 二、实践题 1.编写一个图片查看器。 2.编写一个可以播放音频的程序,要求使用多线程技术,在其中一个线程中播放音频。 3.仿照例 8-3 编写一个视频播放器,完善它的功能,添加循环播放的功能。 【习题】 第 9 章 网 络 编 程 【学习目标】 1.了解网络编程基础以及 Internet 寻址。 2.掌握 InetAddress 类在网络编程中的运用。 3.掌握 TCP/IP 套接字(Socket)的基本使用方法;学会建立套接字连接,实现客户端 与服务器端的通信。 4.掌握 Java 程序中使用 UDP 数据报进行网络编程。 5.了解 URL 的构成,掌握 Java.net.URL 类的使用。 与其他很多的编程语言不同,Java 语言从一开始就内置了对网络编程的支持,因此,它 更容易编写网络应用程序。Java 语言提供了很多的类与接口供用户用来处理各种网络协议, 并且它们还在不断扩展。 9.1 网 络 基 础 在学习本章之前,先来了解一下网络的一些基础知识。网络是计算机和其他设备的集合, 可以从一个设备向另一个设备传送信息,网络上的每个设备称为一个结点。这些结点可能是 计算机、路由器、网桥、交换机等。网络上的每个结点用网址来唯一标志。 1.因特网寻址 因特网上的每台计算机都有自己的唯一的地址,这就是 IP 地址,IP 地址是唯一标志连 接到因特网的计算机的数字地址。IP 地址由 32 位的二进制数组成,为了方便记忆,通常使 用点分十进制来表示,例如 202.41.224.33。但是这样的数字仍然难以记忆,人们希望的是一 种能够反映地址实际意义的表示方法,也就是我们平常所说的网址,例如 www.cctv.com。为 了解决以上问题,发明了域名系统(DNS)。当用户在浏览器上输入网址时,然后通过域名系 统解析出 IP 地址,从而完成访问。 2.客户/服务器模式 服务器(server)就是能够提供资源的某些主机。客户(client)就是简单的任何有权访 问特定服务器的实体。客户需要连接到服务器上,并向服务器请求信息,而服务器则向客户 发送信息。 3.InetAddress 类 java.net.InetAddress 类封装了 IP 地址和它的域名,这个类用两个字段表示一个 Internet 地址:hostName(String)和 address(int)。hostName 包含了主机名,address 包含了 32 位的 IP 地址,但是它们并不是公共的,不能直接访问。 Java 语言案例教程 150 InetAddress 类没有提供构造函数,它提供了三个静态方法来初始化 InetAddress 对象。 l static InetAddress getByName(String host):将一个域名或 IP 地址传递给该方法,获 得一个 InetAddress 对象。 l static InetAddress getLocalHost():返回一个本地主机的 InetAddress 对象。 l static InetAddress[] getAllByName(String host):返回代表由一个特殊名称分解的所有 地址的 InetAddress 类数组 InetAddress 类提供了一些读取方法,可以用来查询实例的状态。 l public String getHostName():返回这个地址的域名。 l public byte[] getAdderess():按照网络字节顺序,返回 32 位 IP 地址的 4 字节数组。 注意:用 byte 类型数组来存放 IP 地址,byte 的范围是-128~127,而 IP 地址中的数字 范围都是 0~255,所以若返回的值是负数则必须加上 256 以得到正确的值。 l public String getHostAddress():返回这个地址的点分十进制字符串表示。 例 9-1 InetAddress 类示例。 import java.net.*; public class DomainName { public static void main(String args[]) { try{ InetAddress address=InetAddress.getByName("www.163.com"); String domain_name=address.getHostName(); String IP_name=address.getHostAddress(); System.out.println(domain_name+IP_name); } catch(UnknownHostException e) { System.out.println("无法找到 www.163.com"); } } } 运行结果如图 9-1 所示。 图 9-1 DomainName 运行结果 第 9 章 网 络 编 程 151 9.2 套 接 字 套接表示网络通信的端点,是网络协议的编程抽象。IP 地址标示因特网上的计算机,而 端口号标示正在计算机上运行的某一个程序。端口号与 IP 地址组合就得出了一个套接字。端 口号是一个介于 0~65 535 的整数值。其中 0~1 023 端口号被预定义的通信服务占用了,如果 不是使用这些服务,那么就不要使用这些端口号,以免发生端口冲突。 9.2.1 TCP/IP 客户套接字 TCP/IP 套接字用于在主机和因特网之间建立可靠的、双向的、点对 点 的流式连接。一个 ICP/IP 套接字可以用来建立 Java 的输入/输出系统到计算机上的程序的连接。计算机可以是 本地机,也可以是因特网上的任何计算机。TCP/IP 套接字分为客户套接字和服务器套接字。 客户套接字用于建立客户到服务器的连接,服务器套接字用于建立服务器到客户的连接。 创建一个 TCP/IP 客户套接字对象,就隐式建立了一个客户到服务器的连接。在 Java 语 言中,没有显式的用于建立该连接的方法或构造函数。下面是用来生成客户套接字的两个构 造函数。 l Socket(String hostName,int port):创建一个本地主机与给定名称的主机和端口的套 接字对象。 l Socket(InetAddress ipAddress,int port):用一个预先存在的 InetAddress 对象和端口 创建一个套接字对象。 提示:如果在创建套接字对象时发生 I/O 错误,那么,将转而创建 IOException 对象。 使用下面的方法,可以检查套接字的地址和端口的信息。 l inetAddress getInetAddress( ):返回和套接字对象相关的 InetAddress。 l int getPort( ):返回与该套接字对象连接的远程端口。 l int getLocalPort( ):返回与该对象连接的本地端口。 9.2.2 TCP/IP 服务器套接字 TCP/IP 服务器套接字的构造函数有两个参数,一个是接受连接的端口号;另一个是最大队 列长度,用于设置排队等待上述端口的最长时间(该项可选)。最大队列的长度默认值是 50。 下面是 TCP/IP 服务器套接字的构造函数: l ServerSocket(int port):在指定端口创建最大队列长度为 50 的服务器套接字对象。 提示:监听端口的范围是从 0~65 535,如果 port 值为 0,则表示监听所有端口。 l ServerSocket(int port,int maxQueue):在指定端口创建一个给定最大队列长度的服 务器套接字对象。 Java 语言案例教程 152 l ServerSocket(int port,int maxQueue,InetAddress localAddress):在指定端口创建一 个给定最大队列长度的服务器套接字对象。在一个多地址主机上,localAddress 指定 该套接字对象所对应的 IP 地址。 当服务器的套接字连接建立后,可以使用 accpet 方法接收客户的套接字对象。 下面的例子,客户端和服务器端分别向对方发送了一条消息。 例 9-2 客户和服务器互发消息示例。 (1)客户端代码: import java.io.*; import java.net.*; public class Client { public static void main(String args[]) { String s=null;Socket mysocket; DataInputStream in=null; DataOutputStream out=null; try{ mysocket=new Socket("localhost",4331);//创建一个本地主机与 localhost 主机 4331 端口的套接字对象 in=new DataInputStream(mysocket.getInputStream()); out=new DataOutputStream(mysocket.getOutputStream()); out.writeUTF("I am Client"); while(true)//从 in 流中读取用 VTF 格式编码的字符串,直到结束 { s=in.readUTF(); if (s!=null) break; } mysocket.close();//关闭套接字对象 } catch(IOException e){System.out.println("无法连接");} System.out.println(s); } } (2)服务器端代码: import java.io.*;import java.net.*; public class Server { public static void main(String args[]) { ServerSocket server=null; Socket you=null;String s=null; DataOutputStream out=null;DataInputStream in=null; try{ 第 9 章 网 络 编 程 153 server=new ServerSocket(4331);}//在 4331 端口创建最大队列长度为 50 的服务 器套接字对象 catch(IOException e1){System.out.println("ERRO:"+e1); } try{ you=server.accept(); in=new DataInputStream(you.getInputStream()); out=new DataOutputStream(you.getOutputStream()); while(true)//从 in 流中读取用 VTF 格式编码的字符串,直到结束 { s=in.readUTF(); if (s!=null) break; } out.writeUTF("I am Server");//在 out 流中用 VTF 格式写"I am Server" you.close(); } catch (IOException e) {System.out.println("ERRO:"+e);} System.out.println(s); } } 为了调试的方便,服务器地址用的是本机 localhost。运行要先运行服务器端,再运行客 户端。 运行结果如图 9-2 和图 9-3 所示。 下面的例子,客户端输入两个数传送给服务器,由服务器比较两个数的大小后传送给客 户端。这就是 B/S 模式的简单模拟。 例 9-3 B/S 模式的简单模拟示例。 (1)客户端代码: import java.net.*; import java.io.*; 图 9-2 Server 运行结果 图 9-3 Client 运行结果 Java 语言案例教程 154 import java.awt.*; import java.awt.event.*; import java.applet.*; /* */ public class compute_client extends Applet implements Runnable,ActionListener { Button compare ;TextField Inputfield,Outputfield; Socket socket=null; DataInputStream in=null; DataOutputStream out=null; Thread thread; public void init()//对界面进行初始化 { setLayout(new GridLayout(2,2)); Panel p1=new Panel(),p2=new Panel(); compare=new Button("比较"); Inputfield=new TextField(12);Outputfield=new TextField(12); p1.add(new Label("输入比较的两个数,用逗号或者空格分隔")); p1.add( Inputfield); p2.add(new Label("比较结果:")); p2.add(Outputfield); p2.add(compare); compare.addActionListener(this); add(p1);add(p2); } public void start()//开始执行 Applet { try { socket = new Socket(this.getCodeBase().getHost(), 4331); in = new DataInputStream(socket.getInputStream()); out = new DataOutputStream(socket.getOutputStream()); } catch (IOException e){} 第 9 章 网 络 编 程 155 if (thread == null) { thread = new Thread(this); thread.setPriority(Thread.MIN_PRIORITY); thread.start(); } } public void run()//实现 run()方法,使线程完成相应的功能 { String s=null; while(true) { try{s=in.readUTF(); } catch (IOException e) {Outputfield.setText("与服务器已断开");break;} Outputfield.setText(s); } } public void actionPerformed(ActionEvent e)//对事件进行相应的处理 { if (e.getSource()==compare) { String s=Inputfield.getText(); if(s!=null) { try{out.writeUTF(s);} catch(IOException e1){} } } } } (2)服务器端代码 import java.io.*; import java.net.*; import java.util.*; import java.sql.*; public class compute_server { public static void main(String args[]) { ServerSocket server=null;Server_thread thread; Socket you=null; while(true) Java 语言案例教程 156 { try{ server=new ServerSocket(4331);} catch(IOException e1) {System.out.println("正在监听");} try{ you=server.accept();} catch (IOException e){System.out.println("正在等待客户");} if(you!=null) {new Server_thread(you).start(); } else {continue;} } } } class Server_thread extends Thread//继承 thread 类,定义服务器线程类 { Socket socket;Connection Con=null;Statement Stmt=null; DataOutputStream out=null;DataInputStream in=null; int n=0;String s=null; Server_thread(Socket t) { socket=t; try {in=new DataInputStream(socket.getInputStream()); out=new DataOutputStream(socket.getOutputStream()); } catch (IOException e) {} } public void run()//服务器线程需实现的功能 { while(true) { double a[]=new double[2] ; int i=0;try{s=in.readUTF(); StringTokenizer fenxi=new StringTokenizer(s," ,"); while(fenxi.hasMoreTokens()) { String temp=fenxi.nextToken(); try{a[i]=Double.valueOf(temp).doubleValue();i++;} catch(NumberFormatException e) {out.writeUTF("请输入数字字符");} } if(a[0]>a[1]) out.writeUTF(" " +"第一个数大"); 第 9 章 网 络 编 程 157 else { if(a[0]==a[1]) out.writeUTF(" " +"两个数相等"); else out.writeUTF(" "+"第二个数大"); } sleep(2); } catch(InterruptedException e){} catch (IOException e) {System.out.println("客户离开"); Thread.currentThread().yield();break; } } } } 运行结果如图 9-4 所示。 9.3 UDP 数据报 UDP 数据报是一种无连接、不可靠的通信协议,它尽最大努力交付,但是不能保证数据 正确到达目的主机,也不能保证数据按照所发出去的顺序到达,UDP 在通信前不需要建立虚 拟链路,因此它的处理速度快。当需要较快地传输信息,并能容忍一定的错误时,就可以使 用 UDP 数据报,例如在进行视频直播,过时的数据已经无用,可以容忍一定程度的数据丢失, 这时,就可以使用 UDP 数据报。UDP 数据报本身也不提供差错检测,对它的差错控制要由 它的上一层即应用层的程序来控制。UDP 通信的基本模式过程是先将数据打包封装,然后将 数据发往目的地,目的地接收数据,然后进行处理。 图 9-4 compute_client 运行结果 Java 语言案例教程 158 Java 语言主要通过两个类来支持 UDP 网络编程:Java.net.DatagramPacket 与 Java.net.DatagramSocket。 9.3.1 Java.net.DatagramPacket 类 Java.net.DatagramPacket 类用来存储数据报的头和数据内容,它的实例可以用来发送和接 收 UDP 数据报,它包括了三个方面的属性:IP 地址(标示目的主机)、 UDP 端口(寻找目标 进程)和字节数组中存储数据(传输的数据)。 1.构造函数 l DatagramPacket(byte[] buf,int length):构 造 一个对象,指定了接收数据缓冲区和信息 的容量大小。 l DatagramPacket(byte[] buf,int offset,int length):同上,只是在存储数据缓冲区设置了 一个偏移量。 l DatagramPacket(byte[] buf,int length, InetAddress address,int port):构造一个对象,它 指定了数据缓冲区的大小,并将数据发送到指定的主机的端口上。 l DatagramPacket(byte[] buf,int offset,int length,InetAddress address,int port):构 造 一个对象, 它指定了数据缓冲区的大小,设置了偏移量,并将数据发送到指定主机的端口上。 2.类的其他方法 l InetAddress getAddress():获取数据报的目的地址。 l int getPort():获取数据报的目标端口号。 l byte[] getData():获取包含在数据报中的字节数组数据。 l int getOffset():获取包含在数据报中的数据偏移量。 l int getLength():获取包含在数据报中的有效数据长度。 9.3.2 Java.net.DatagramSocket 类 Java.net.DatagramSocket 类的实例可以作为客户端和服务器端的数据报套接,同 TCP 套 接不同,可以使用同一套接实例将数据发送到任何主机指定端口目标,以及从任何主机指定 端口接收报文。 1.构造函数 l DatagramSocket():在匿名端口上创建一个套接对象。 l DatagramSocket(int port):在指定端口上创建一个套接对象。 l DatagramSocket(int port,InetAddress address):在指定的网络地址与端口上创建一个 套接对象。 2.类的其他方法 l int getLocalPort():返回该套接对象正在监听的本地端口号。 l InetAddress getLocalAddress():返回与该套接对象相关联的本地地址。 l int getPort():当套接对象连接时,返回它的远程连接端口,否则返回-1。 第 9 章 网 络 编 程 159 l InetAddress getInetAddress():当套接对象连接时,返回远程主机地址,否则返回 null。 l void receive(DatagramPacket p):接收一个数据报并存在 p 中。 l void send(DatagramPacket p):发送一个数据报 p。 l void connect(InetAddress address ,int port):将套接配置成只向指定目标发送数据报。 l void close():关闭套接对象。 下面的例子,两个主机之间互相发送和接收数据报,为了方便起见,用本地机模拟。 例 9-4 UDP 数据报示例。 (1)host1 代码: import java.net.*; import java.awt.*; import java.awt.event.*; class host1_Frame extends Frame implements Runnable,ActionListener { TextField out_message=new TextField("发送数据报到 host2"); TextArea in_message=new TextArea(); Button b=new Button("发送数据报到 host2"); byte data[]=new byte[8192]; DatagramPacket pack=null; host1_Frame() {super("host1"); setSize(200,200); setVisible(true); b.addActionListener(this); add(out_message,"South"); add(in_message,"Center"); add(b,"North"); pack=new DatagramPacket(data,data.length); Thread thread=new Thread(this); thread.start();//线程负责接收数据报 } //单击按钮发送数据报 public void actionPerformed(ActionEvent event) {byte buffer[]=out_message.getText().trim().getBytes(); try{InetAddress address=InetAddress.getByName("localhost"); //数据报的目标端口是 888 DatagramPacket data_pack= new DatagramPacket(buffer,buffer.length, address,888); DatagramSocket mail_data=new DatagramSocket(); in_message.append("数据报目标主机地址:"+data_pack.getAddress()+"\n"); in_message.append("数据报目标端口是:"+data_pack.getPort()+"\n"); in_message.append("数据报长度:"+data_pack.getLength()+"\n"); Java 语言案例教程 160 mail_data.send(data_pack); } catch(Exception e){} } //接收数据报 public void run() {DatagramSocket mail_data=null; try{//使用端口 666 来接收数据报(因为 host2 发来的数据报的目标端口是 666) mail_data=new DatagramSocket(666); } catch(Exception e){} while(true) { if(mail_data==null) break; else try{mail_data.receive(pack); int length=pack.getLength(); //获取收到的数据的实际长度 InetAddress adress=pack.getAddress();//获取收到的数据报的始发地址 int port=pack.getPort();//获取收到的数据报的始发端口 String message=new String(pack.getData(),0,length);//获取收到的数据报 中的数据 in_message.append("收到数据长度:"+length+"\n"); in_message.append("收到数据来自:"+adress+"端口:"+port+"\n"); in_message.append("收到数据是:"+message+"\n"); } catch(Exception e){} } } } public class host1 {public static void main(String args[]) { host1_Frame host1_win=new host1_Frame(); host1_win.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) {System.exit(0);} }); host1_win.pack(); } } 第 9 章 网 络 编 程 161 (2)host2 代码 import java.net.*;import java.awt.*; import java.awt.event.*; class host2_Frame extends Frame implements Runnable,ActionListener { TextField out_message=new TextField("发送数据报到 host1:"); TextArea in_message=new TextArea(); Button b=new Button("发送数据报到 host1:"); byte data[]=new byte[8192]; DatagramPacket pack=null; host2_Frame() { super("Host2"); setSize(200,200); setVisible(true); b.addActionListener(this); add(out_message,"South"); add(in_message,"Center"); add(b,"North"); pack=new DatagramPacket(data,data.length);//用来接收数据报 Thread thread=new Thread(this); thread.start();//线程负责接收数据报 } //单击按钮发送数据报 public void actionPerformed(ActionEvent event) {byte buffer[]=out_message.getText().trim().getBytes(); try{InetAddress address=InetAddress.getByName("localhost"); //数据报的目标端口是 666 DatagramPacket data_pack= new DatagramPacket(buffer,buffer.length, address,666); DatagramSocket mail_data=new DatagramSocket(); in_message.append("数据报目标主机地址:"+data_pack.getAddress()+"\n"); in_message.append("数据报目标端口是:"+data_pack.getPort()+"\n"); in_message.append("数据报长度:"+data_pack.getLength()+"\n"); mail_data.send(data_pack); } catch(Exception e){} } public void run() {DatagramSocket mail_data=null; try{//使用端口 888 来接收数据报(因为 host1 发来的数据报的目标端口是 888) mail_data=new DatagramSocket(888); Java 语言案例教程 162 } catch(Exception e){} while(true) { if(mail_data==null) break; else try{ mail_data.receive(pack); int length=pack.getLength(); //获取收到的数据报的实际长度 InetAddress adress=pack.getAddress();//获取收到的数据报的始发地址 int port=pack.getPort();//获取收到的数据报的始发端口 String message=new String(pack.getData(),0,length);//获取收到的数据报中的数据 in_message.append("收到数据长度:"+length+"\n"); in_message.append("收到数据来自:"+adress+"端口:"+port+"\n"); in_message.append("收到数据是:"+message+"\n"); } catch(Exception e){} } } } public class host2 {public static void main(String args[]) {host2_Frame host2_win=new host2_Frame(); host2_win.addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e) {System.exit(0);} }); host2_win.pack(); } } 运行结果如图 9-5 所示。 (a)Host 1 向 Host 2 发送 (b)Host 2 向 Host 1 发送 图 9-5 主机间发送UDP 数据报的运行结果 第 9 章 网 络 编 程 163 9.4 Java.net.URL 类 URL(统一资源定位符)是对可以从因特网上得到的资源的位置和访问方法的一种简洁 表示。URL 给资源的位置提供一种抽象的识别方法,并用这种方法给资源定位。URL 相当于 一个文件名在网络范围的扩展,因此 URL 是与因特网相连的机器上任何可访问对象的一个指 针。其格式如下: <信息服务类型>://<信息资源地址>/<文件路径> <信息服务类型>是指因特网的协议名,包括 ftp(文件传输)、 http(超文本传输服务)、 telnet(运程登录服务)等。 以下是一些 URL 的例子: http://www.ninu.edu.cn ftp://ftp.w3.org/pub/wwww/doc telnet://odyssens-circe.com:70 1.构造函数 l URL(String spec):从 String 对象创建一个 URL 对象。 l URL(String protocol,String host,int port,String file):从标明的协议名称、主机名、端 口号、文件名创建一个 URL 对象。 l URL(String protocol,String host,int port,String file,URLStreamHandler handler):从标明 的协议名称、主机名、端口号、文件名和处理器创建一个 URL 对象。 l URL(String protocol,String host,String file):从标明的协议名称、主机名和文件名创 建一个绝对的 URL 对象。 l URL(URL context,String spec):通过在标明的上下文中解析 spec 来创建一个 URL。 spec 将作为 URL 解析的 string。 l URL(URL context,String spec,URLStreamHandler handler):通过在标明的上下文解析 spec 来构造一个 URL。spec 将作为 URL 解析的 string,handler 是 URL 的流处理。 2.类的其他方法 l boolean equals(Object obj):比较两个 URL。 l String getAuthority():返回这个 URL 的主体部分。 l Object getContext():获得该 URL 的目录。 l Object getContext(Class [] classes):返回该 URL 的目录。 l String getFile():返回该 URL 的文件名称。 l String getHost():返回该 URL 的主机名称。 l String getPath():返回该 URL 的路径名称。 l int getPort():获得该 URL 的端口号。 l String getProtocol():获得该 URL 的协议名称。 l String getQuery():获得该 URL 的查询地址。 l String getRef():获得该 URL 的 anchor 参考。 l String getUserInfo():返回该 URL 的 userInfo 部分。 Java 语言案例教程 164 l int hashcode():构造一个适合哈希(散列函数)表索引的整数。 l URLConnection openConnection():获得一个 URLConnection 对象,该对象代表 URL 指定的偏僻对象的连接。 l InputStream openStream():打开该对象的一个连接,并返回从该连接读入的一个输 入流。 l boolean sameFile(URL other):比较两个 URL 除去 ref 字段的部分。 l protected void set(String protocol,String host,int port,String file,String ref) :设置 URL 的字段。 l protected void set(String protocol,String host,int port,String authority,String userInfo, String path,String query,String ref):设置 URL 指定的 8 个字段。 l Static void setURLStreamHandlerFactory(URLStreamHandlerFactory fac):设置应用程 序的 URLStreamHandlerFactory,URLStreamHandlerFactory 实例用于从协议名称构 造流协议处理程序。 l String toExternalForm():以标准的 URL 格式构造该 URL 的字符串表示。 l String toString():构造该 URL 的字符串表示。 下面的例子,程序以 URL 为参数读取其内容,本例的 URL 对象是一个本机的文件,所 以协议为 file,当然,可以使用 http 协议来读取网络上的内容,建议最好为文本文件,否则 会显示终端控制符。 例 9-5 URL 使用示例。 import java.net.*; import java.io.*; public class urltest { public static void main(String [] args) { if(args.length !=1) { System.err.println("参数不正确!"); System.exit(1); } URL url= null; try{ url=new URL(args[0]); }catch(MalformedURLException e){ System.err.println(e.getMessage()); System.exit(1); } BufferedReader reader=null; try{ reader=new BufferedReader(new InputStreamReader (url.openStream ())); 第 9 章 网 络 编 程 165 //将缓冲指定从 URL 输入 String line=reader.readLine(); while(line!=null)//读入每行并输出,直到结束 { System.out.println(line); line=reader.readLine(); } } catch(IOException e){ System.err.println("无法得到"+e.getMessage()); System.exit(1); } finally{ try{reader.close();}catch(Throwable e){} } } } 运行结果如图 9-6 所示。 【本章小结】 用 Java 语言编写网络程序实现网络的底层通信,本质上是用 Java 程序实现网络通信协 议所规定的功能和操作。本章简要介绍了 Java 网络编程的一些基础知识,包括:如何使用 URL 类实现 WWW 连接继而获得网络资源;如何使用 Socket 实现基于 TCP 和 UDP 的网络 通信,进而实现多用户的通信。网络是 Java 语言的一个重要组成部分,限于篇幅,本章只能 介绍其中的一小部分,读者可以通过 Java 文档和其他的书籍来了解更多的方面。 图 9-6 urltest 运行结果 Java 语言案例教程 166 【习题】 一、选择题 1.关于 InetAddress 类,下面哪种说法是错误的?( ) A.java.net.InetAddress 封装了 IP地址和它的域名,这个类用两个字段表示一个 Internet 地址 B.InetAddress 类采用了工厂设计模式,所以没有提供构造方法 C.InetAddress 类有构造方法,可以直接实例化对象 D.构造方法 static InetAddress getLocalHost( ) 返回一个本地主机的 InetAddress 对象 2.在网络编程中,套接字是指( )。 A . IP 地址 B.端口号 C.IP 地址+端口号 D.以上说法都不对 3.在 Java 网络编程中用于客户端和服务器端的套接字类分别是( )。 A . ServerSocket Socket B.Socket ServerSocket C.Socket Socket D.ServerSocket ServerSocket 4.在 Java 网络编程中,支持 UDP 协议的类是( )。 A . DatagramPacket 和 DatagramSocket B.只有DatagramSocket C.Socket D.只有DatagramPacket 5.下面关于 URL 类的方法的说明中,哪一项是错误的?( ) A.URL(String spec)是 URL 类的一个构造方法 B.方法 String getHost()是返回该 URL 的主机名称 C.方法 String getFile()是返回该 URL 的文件名称 D.URL 类没有提供方法获得该 URL 的端口号 二、问答题 1.利用 Socket 进行网络通信时,其过程包括哪些步骤? 2.怎样建立 Socket 连接?建立连接时客户端和服务器端的作用有什么区别? 3.怎样使用 UDP 数据报发送数据? 4.什么是 URL?说明 URL 的构成。 5.怎样用 URL 对象读取网络资源? 三、实践题 1.编写一个时间服务器/客户端程序,当服务器收到客户端的请求后,将当前的系统时 间以一定的格式发送给客户端,客户端解析这个格式,并以可读的格式在标准输出设备上打 印接收到的时间。 2.编写一个实现聊天室功能的 Applet。 第 10 章 数 据 库 编 程 【学习目标】 1.了解 JDBC 的体系结构。 2.熟悉 JDBC 的主要接口。 3.掌握使用 JDBC 访问数据库的一般方法和技巧。 4.学会使用 Java 语言编写数据库程序。 10.1 JDBC 简介 Java 数据库连接体系结构(JDSB)是用于 Java 应用程序连接数据库的标准方法。JDBC 对 Java 程序员而言是 API,对实现与数据库连接的服务提供商而言是接口模型。作为 API, JDBC 为程序开发提供标准的接口,并为数据库厂商及第三方中间件厂商实现与数据库的连 接提供了标准方法。JDBC 使用已有的 SQL 标准并支持与其他数据库连接标准,如 ODBC 之 间的桥接。JDBC 实现了所有这些面向标准的目标,并且具有简单、严格类型定义且性能高 的接口。 简单地说,JDBC 的功能如下: (1)与数据库建立连接。 (2)向数据库发送 SQL 语句。 (3)处理数据库返回的结果。 10.2 JDBC 的体系结构及主要接口 JDBC 主要包括以下类:连接(Connection)、语句(Statement)和结果集(Resultset)。 JDBC 应用程序的工作方式是这样的:首先加载数据库的 JDBC 驱动程序(JDBC 驱动程序由 DriverManager 类管理), 然后建立连接(Connection), 由 连接建立语句对象(语句对象有三 种:Statement、PreparedStatement 和 CallableStatement)及结果集(Resultset)对象(结果集 代表从数据库中取出的记录), 通过语句对象和结果集对象进行各种数据库操作,最后关闭 连接。 说明:JDBC 中连接(Connection)、语句(Statement)和结果集(Resultset)实际上都是 接口,而不是类。不过在这里,没有严格区分接口和实现它的类。当“等到一个连接”时, 实际上意味着得到了一些实现 Connection 接口的类的实例。 Java 语言案例教程 168 10.2.1 JDBC 驱动程序的类型 JDBC 驱动程序有四种类型。选择何种类型的驱动程序主要取决于程序的应用范围。正 确选择合适的驱动程序,使之符合数据库程序的设计,是提高程序性能必须考虑的一个方面。 要想理解这几种驱动程序,首先要了解数据库驱动程序的基本原理。 数据库驱动程序用来解决应用程序与数据库管理系统通信的问题。早期的数据库产品, 提供一个通过网络与数据库通信的网络库( network libraries)。 网络库由客户端组件和数据库 服务端组件组成。在 Windows 系统中,网络库通常以 DLL(Dynamic-Link Library,动态链 接库)的形式存在。这种形式对于数据库客户端程序开发而言,产生一个问题,每一种数据 库管理系统有自己的一套 API,因而开发出的程序不能独立于数据库管理系统。为了解决这 个问题,数据库管理系统厂商决定提供一个建立在网络库之上的、统一的高层 API,开发人 员调用高层 API,从而屏蔽了与数据库管理系统产品紧密相关的网络库。Microsoft 推出的 ODBC(Open Database Connectivity,开放数据库互连)API 就是这样的一个产品。 JDBC 是一个面向 Java 开发人员的类似于 ODBC 的数据库管理系统访问接口。不同于 ODBC 的是,JDBC 只支持 Java 应用程序。在使用 JDBC 的 Java 应用程序中,客户端程序只 调用 JDBC,而真正与数据库管理系统通信则由数据库管理系统厂商提供的 JDBC 驱动程序 来完成。 目前,比较常见的 JDBC 驱动程序可分为下面四个种类。 1.JDBC-ODBC 桥 JDBC-ODBC 桥把 JDBC 操作翻译成对应的 ODBC 调用。它的优点是可以访问 ODBC 能 访问的所有数据库管理系统,缺点是执行效率比较低。采用这种方式必须将 ODBC 二进制代 码(在许多情况下,还包括数据库客户端代码)加载到使用该驱动程序的每个客户机上。 2.本地 API 这种类型的驱动程序把客户机 API 上的 JDBC 调用转换为 Oracle、Sybase、Informix、 DB2 或其他数据库管理系统的调用。 注意,像 JDSB-ODBC 桥驱动程序一样,这种类型的驱动程序要求将某些二进制代码加 载到每台客户机上。 3.网络纯 Java 驱动程序 这种驱动程序将 JDBC 转换为与数据库管理系统无关的网络协议,之后这种协议又被某 个服务器(中间件服务器)转换为指定的数据库管理系统协议。这种数据库驱动程序安装在 中间件服务器上,而不是在客户机上。网络服务器中间件能够将 Java 客户机连接到多种不同 的数据库管理系统上。所用的具体协议取决于提供者。通常,这是最为灵活的 JDBC 驱动程 序。所有这种解决方案的提供者都可能提供适合于因特网或企业内部网用的产品。 4.本地协议纯 Java 驱动程序 这种驱动程序把 JDBC 调用直接转换为数据库管理系统所使用的网络协议。它允许从客 户机上直接调用数据库管理系统服务器,是实现因特网访问的一个很实用的解决方法。它完 全用 Java 实现,不需要其他驱动程序和客户端网络库。此类驱动程序是由数据库管理系统厂 商提供的,能够提供对于该数据库管理系统最优化的访问。 第 10 章 数据库编程 169 提示:后两种类型的驱动程序是使用 JDBC 访问数据库的首选方法,它们提供了 Java 的 所有优点,包括自动安装。而前面两种驱动程序则只是在直接的纯 Java 驱动程序 还没有上市前所使用的过渡方案。 10.2.2 JDBC 的组成及其主要接口 JDBC API 是 Java 平台的一部分,其 包括在 Java 2 标准版和 Java2 企业版中。JDBC 主要 分为两个包:java.sql 和 javax.sql。J2SE 和 J2EE 平台均包含这两个包。java.sql 是 JDBC 的核 心API,其中包括了建立连接、执行SQL语句、取得查询结果集、数据类型转换等功能。javax.sql 是 JDBC 的扩展 API,其中包括了 DataSource 接口、数据库连接池支持与实现、行集等功能。 JDBC API 中的主要接口见表 10-1。 表 10-1 JDBC API 中的主要接口 接 口 作 用 java.sql.DriverManager 处理驱动程序的加载和建立新数据库连接 java.sql.Connection 处理与数据库的连接 java.sql.Statement 将 SQL 语句发送到数据库中 java.sql.ResultSet 处理数据库返回的结果集 10.3 建立数据库和设置数据源 下面简单地介绍一下建立数据库和设置数据源的过程,本章后面所有的代码示例都使用 本小节所建立的数据库或数据源。这 里 所使用的数据库管理系统是 Windows 平台上比较常见 的两个:SQL Server 2000 和 Access,其他厂商的数据库管理系统的设置与它们类似。 下面的 student 表(如图 10-1 所示)描述了一组学生的学籍信息。 10.3.1 SQL Server 2000 的设置 如果使用的是 Windows 2003 或 Windows XP SP2 及以上版本的操作系统,需要安装 SQL Server 2000 的 SP3 补丁包后才能正常使用。 图 10-1 student 表 Java 语言案例教程 170 设置 SQL Server 2000 的操作步骤如下: ①启动 SQL Server 企业管理器,新建一个数据库, 名称处填 JDBCtest,其他位置保持默认设置即可。 ②在 JDBCtest 数据库中新建一个 student 表,表的 结构与图 10-1 一致。 ③)新建一个数据库用户,用户名设为:jdbcuser, 密码为:jdbcpwd。在 JDBCtest 中添加此用户相应的访 问权限,如图 10-2 所示。 ④设置 ODBC 数据源,数据源名设为 StuInfo,登 录 ID 用上一步骤中所添加的用户,测试连接来确认其 工作是否正常。 ⑤在 http://www.microsoft.com/china/sql/downloads/2000/jdbc.asp 上下载 SQL Server 2000 JDBC 驱动程序,安装后将 msbase.jar、mssqlserver.jar 和 msutil.jar 这三个文件的路径添加到 CLASSPATH 中,这样便能通过 JDBC 直接访问 SQL Server 数据库。 10.3.2 Access 的设置 Access 没有对应的 JDBC 驱动,只能用 JDBC-ODBC 桥的方式来访问。设置 Access 的操 作步骤如下: ①新建一个 Access 文件,文件名设为 jtest.mdb。对 Access 而言,一个文件对应一个数 据库。 ②新建一个 student 表,结构与图 10-1 一致。 ③在 ODBC 中添加此数据源,数据源名设置为 StuInfo2。默认授权处的登录 ID 和密码 分别设为 jdbcuser 和 jdbcpwd。 ④设置好后的 ODBC 数据源管理器如图 10-3 所示。 10.4 JDBC 应用程序设计 在建数据库和设置数据源后,就可以进行 JDSB 应用程序的编写了。 图 10-2 SQL 用户权限设置 图 10-3 ODBC 数据源的设置 第 10 章 数据库编程 171 10.4.1 加载 JDBC 驱动程序 在建立数据库和设置数据源后,就可以进行 JDBC 应用程序的编写了。DriverManager 类是 JDBC 的管理层,作用于用户和驱动程序之间。它加载数据库驱动程序,跟踪可用的驱动程序, 并在数据库和相应驱动程序之间建立连接。另外,DriverManager 类也处理诸如驱动程序登录时 间限制及登录和跟踪消息的显示等事务。主要成员方法见表 10-2。 表 10-2 DriverManagr 主要成员方法 方 法 作 用 static void deregisterDriver(Driver driver) 从数据库驱动列表中删除指定的驱动程序 static Connection getConnection(String url) 尝试通过 url 创建数据库连接 static Connection getConnection(String url, Properties info) 尝试通过 url 和属性信息创建数据库连接 static Connection getConnection(String url, String user, String password) 尝试通过 url、用户名及密码创建数据库连接 static Driver getDriver(String url) 尝试通过 url 找到对应的数据库驱动程序 static Enumeration getDrivers() 获得驱动列表的枚举对象 static int getLoginTimeout() 获得数据库连接的超时设置 static void registerDriver(Driver driver) 注册驱动到数据库驱动列表 static void setLoginTimeout(int seconds) 设置连接超时时长(单位秒) 使用 DriverManager 建立与数据库的连接,通常需要以下步骤:了 解 JDSB 驱动程序类的 名字;注册该类驱动程序,加载该类驱动程序;建立连接。 首先,在使用 JDSB 前,必须找到该驱动程序供应商所使用的 JDSB 驱动程序类的名字, 例如: com.microsoft.jdbc.sqlserver.SQLServerDriver sun.jdbc.odbc.JdbcOdbcDriver 其次,通过调用方法 Class.forName,显示地加载驱动程序类。由于这与外部设置无关, 因此推荐使用这种加载驱动程序的方法。同时在加载该实例时,自动进行注册。通过调用 DriverManager.registerDriver 方法,对驱动程序类进行注册。正常情况下无须直接调用 DriverManager.registerDriver,而是在加载驱动程序时由驱动程序自动调用。 最后,连接数据库。一般的应用程序只需用到 DriverManager.getConnection,该方法将建 立与数据库的连接。JDBC 允许用户调用 DriverManager 的方法 getDriver、getDrivers 和 registerDriver 及 Driver 的方法 connect。但多数情况下,最好让 DriverManager 类自己管理建 立连接的细节。 例 10-1 显示 DriverManager 中的 Driver 列表。 DriverManagerTest.java import java.sql.*; import java.util.*; Java 语言案例教程 172 /** 测试 JDBC 驱动程序的加载 */ public class DriverManagerTest { public static void main(String[] args) { try { /*通过 Class.forName 显式加载 SQL Server 和 JDBC-ODBC 桥的 JDBC 驱动*/ /*这个过程它们将自动调用 DriverManager.registerDriver 方法对自己进行注册*/ Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver"); Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); } catch(ClassNotFoundException e) { System.out.println(e); } /*将当前 DriverManager 中的 Driver 列表枚举*/ Enumeration enum=DriverManager.getDrivers(); while(enum.hasMoreElements()) { Driver d=(Driver)enum.nextElement(); System.out.println(d.toString()); } } } 当驱动程序在 DriverManager 注册后,可以用下列类似的代码打开数据库的连接: Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver"); String user="jdbcuser"; String password="jdbcpwd"; String url="jdbc:microsoft:sqlserver://localhost:1433;DataBaseName= JDB Ctest"; Connection conn=DriverManager.getConnection(url,user,password); 当调用 DriverManager.getConnection 方法发出连接请求时,DriverManager 将检查每个驱 动程序,查看它是否可以建立连接。有 时 可 能有多个 JDBC 驱动程序可以与给定的 JDSB URL (用于指定数据库的位置)连接。例如,与给定远程数据库连接时,可以使用 JDBC-ODBC 桥驱动程序、JDBC 到通用网络协议驱动程序或数据库厂商提供的驱动程序。在这种情况下, 测试驱动程序的顺序至关重要,因为 DriverManager 将使用它所找到的第一个可以成功连接 到给定 JDBC URL 的驱动程序。首先 DriverManager 试图按注册的顺序使用每个驱动程序 第 10 章 数据库编程 173 (jdbc.drivers 中列出的驱动程序总是先注册)。 它 将 跳 过代码不可信任的驱动程序,除非加 载它们的源与试图打开连接的代码的源相同。它通过轮流在每个驱动程序上调用 Driver.connect 方法,并向它们传递用户给定的 URL 来对驱动程序进行测试,然后连接第一 个认出该 JDSB URL 的驱动程序。这种方法初看起来效率不高,但由于不可能同时加载数十 个驱动程序,因此每次连接实际只需几个过程调用和字符串比较。 10.4.2 建立连接对象 要建立与数据库的连接,首先要创建指定该数据库的 JDSB URL。连接通常是通过数据 库的 JDSB URL 对象,利用 DriverManager 的 getConnection 方法建立的。数据库 JDBC URL 对象与网络资源的统一资源定位对象类似,其构成格式如下: jdbc:subProtocol:subName://hostname:port; DatabaseName=××× 其中,jdbc 表示当前通过 Java 的数据库连接进行数据库访问;subProtocol 表示所使用的 驱动程序;subName 表示当前驱动程序支持的数据库连接机制;hostname 表示主机名;port 表示相应的连接端口;DatabaseName 是要连接的数据库的名称。 按照上述构造规则,可以构造如下类型的 JDSB URL: jdbc:microsoft:sqlserver ://localhost:1433;DatabaseName= JDBCtest 该数据库的 JDSB URL 表示利用 Microsoft 提供的机制,选择名称为 sqlserver 的驱动 通过 1433 端口访问本机上的 JDBCtest 数据库。 说明:如果数据库是通过 Internet 来访问的,则在 JDBC URL 中应将网络地址作为子名 称的一部分包括进去,并且必须遵循的标准 URL 命名约定://主机名:端口/子协议。 Connection 对象代表与数据库的连接。连接过程包括所执行的 SQL 语句和在该连接上所 返回的结果。一个应用程序可与单个数据库有一个或多个连接,或者可与许多数据库有连接。 建立、打开连接的标准方法都是调用 DriverManager.getConnection 方法。Connection 主要成 员方法见表 10-3。 表 10-3 Connection 主要成员方法 方 法 作 用 void clearWarnings() 清除有关连接的所有的警告信息 Statement createStatement() 创建一个 Statement 对象 void commit() 提交数据库的改动并释放所有被当前连接所持有的数据库锁 void rollback() 回滚当前事务的所有改动并释放所有被当前连接所持有的数据库锁 boolean isClosed() 判断连接是否已关闭 boolean isReadOnly() 判断当前连接是否为只读模式 void setReadOnly(boolean readOnly) 设置当前连接为只读模式 void close() 立即释放当前连接的数据库对象和 JDBC 资源 Java 语言案例教程 174 建立连接时应捕获 SQLException 异常。例如,在建立与 Access 数据库连接时可用以下 代码: try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); } catch(ClassNotFoundException e) {System.out.println(e);} try { String url="jdbc:odbc:StuInfo2"; String user="jdbcuser"; String password="jdbcpwd"; Connection conn = DriverManager.getConnection( url, user, password ); } catch(SQLException e) {System.out.println(e);} 上述代码通过 JDBC-ODBC 桥连接数据库,使用这种方法有一个缺陷,就是需要设置 ODBC 源。下面提供另一种 JDBC-ODBC 桥连接数据库的方式,使用直接连接,无须设置 ODBC 数据 源: try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); } catch(ClassNotFoundException e) {System.out.println(e);} try { String url="jdbc:odbc:driver={Microsoft Access Driver (*.mdb)};DBQ=jtest.mdb"; String user=jdbcuser; String password=jdbcpwd; Connection conn = DriverManager.getConnection( url, user, password ); } catch(SQLException e) {System.out.println(e);} 10.4.3 建立语句对象 Statement 对象用于将SQL语句发送到数据库中。实 际上有三种Statement 对象,Statement、 第 10 章 数据库编程 175 PreparedStatement(它从 Statement 继承而来)和 CallableStatement(它从 PreparedStatement 继承而来)。 它 们 都 专 用于发送特定类型的 SQL 语句:Statement 对象用于执行不带参数的简 单 SQL 语句;PreparedStatement 对象用于执行带或不带 IN 参数的预编译 SQL 语句; CallableStatement 对象用于执行存储过程。Statement 接口提供了执行语句和获取结果的基本 方法。PreparedStatement 接口添加了处理 IN 参数的方法;CallableStatement 接口添加了处理 OUT 参数的方法。Statement 的主要成员方法见表 10-4。 表 10-4 Statement 主要成员方法 方 法 作 用 void addBatch(String sql) 在 Statement 语句中增加用于数据库操作的 SQL 批处 理语句 void cancel() 取消 Statement 中的 SQL 语句指定的数据库操作命令 void clearBatch() 清除 Statement 中的 SQL 批处理语句 void clearWarnings() 清除 Statement 中由 SQL 语句引起的警告信息 void close() 关闭 Statement 语句指定的数据库连接 boolean execute(String sql) 执行 SQL 语句 int[] executeBatch() 执行多个 SQL 语句 ResultSet executeQuery(String sql) 进行数据库查询,返回结果集 int executeUpdate(String sql) 进行数据库更新 Connection getConnection() 对数据库的连接 int getFetchDirection() 从数据库表中获取行数据的方向 int getFetchSize() 返回数据库结果集行数 int getMaxFieldSize() 返回数据库结果集最大字段数 int getMaxRows() 返回数据库结果集最大行数 boolean getMoreResults() 获取 Statement 的下一个结果集 int getQueryTimeout() 获取查询超时的时长 ResultSet getResultSet() 获取结果集 int getUpdateCount() 获取更新记录的数量 void setCursorName(String name) 设置数据库游标的名称 void setFetchDirection(int dir) 设置数据库表中行数据的方向 void setFetchSize(int rows) 设置数据库结果集行数 void setMaxFieldSize(int max) 设置数据库结果集最大字段数 void setMaxRows(int max) 设置数据库结果集最大行数 void setQueryTimeout(int seconds) 设置查询超时时长 Statement 对象用 Connection 类的 createStatement 方法创建,例如, Statement stmt = conn.createStatement(); Statement 接口提供了 3 种执行 SQL 语句的方法:executeQuery、executeUpdate 和 execute。 使用哪一个方法由 SQL 语句所产生的内容决定。executeQuery 方法用于产生单个结果集的 Java 语言案例教程 176 SQL 语句,如 SELECT 语句。executeUpdate 方法用于执行 INSERT、UPDATE、DELETE 及 DDL(数据定义语言)语句,例如 CREATE TABLE 和 DROP TABLE。executeUpdate 的返回 值是一个整数,表示它执行的 SQL 语句所影响的数据库表的行数(更新计数)。 execute 方法 用于执行返回多个结果集或多个更新计数的语句。例如: String sql = "insert into student" + "学号,姓名,性别,年龄,政治面貌,籍贯,所在系) " + "values(2002032006,'林峰','男',21,'团员','湖北','网络工程系') "; stmt.executeUpdate(sql); 应注意,继承了 Statement 接口的 PreparedStatement 接口有自己的 executeQuery、 executeUpdate 和 execute 方法。Statement 对 象本身不包含 SQL 语句,因而必须给 Statement.execute 方法提供 SQL 语句作为参数。PreparedStatement 对象并不将 SQL 语句作为 参数提供给这些方法,因为它们已经包含预编译 SQL 语句。CallableStatement 对象继承了 PreparedStatement 对象的这些方法。对于 PreparedStatement 或 CallableStatement 对象,如果 使用方法时指定参数将触发 SQLException 异常。 当连接处于自动提交模式时,其中所执行的语句在完成时将自动提交或还原。语句在已执行 且所有结果返回时,即认为已完成。对于返回一个结果集的 executeQuery 方法,在检索完 ResultSet 对象的所有行时该语句完成。对于 executeUpdate 方法,当它执行时语句即完成。但在少数调用 方法 execute 方法的情况中,在检索所有结果集或它生成的更新计数之后,语句才完成。 有些数据库管理系统将存储过程中的每条语句视为独立的语句;而另外一些则将整个存 储过程视为一个复合语句。在启用自动提交时,这种差别就变得非常重要,因为它影响什么 时候调用 commit 方法。在前一种情况中,每条语句单独提交;在后一种情况下,所有语句 同时提交。 Statement 对象可由 Java 垃圾收集程序自动关闭。而作为一种好的编程风格,应在不需要 Statement 对象时显式地关闭它们。这将立即释放数据库资源,有助于避免潜在的内存问题。 10.4.4 建立结果集对象 数据库结果集(ResultSet)包含符合 SQL 语句中条件的所有行,并且它通过一套 get× ××()方法(这些 get 方法可以访问当前行中的不同列)提供了对这些行中数据的访问。 ResultSet.next 方法用于移动 ResultSet 中的一行,使下一行成为当前行。其 主要成员方法见表 10-5。 表 10-5 ResultSet 主要成员方法 方 法 作 用 boolean absolute(int row) 将游标移动到结果集对象的某一行 void afterLast() 将游标移动到结果集对象的末尾 void beforeFirst() 将游标移动到结果集对象的头部 boolean first() 将游标移动到结果集对象的第一行 Array getArray(int row) 获取结果集中的某一行并将其存入一个数组 第 10 章 数据库编程 177 续表 方 法 作 用 boolean getBoolean(int columnIndex) 获取当前行中某一列的值,返回一个布尔型值 byte getByte(int columnIndex) 获取当前行中某一列的值,返回一个字节型值 short getShort(int columnIndex) 获取当前行中某一列的值,返回一个短整型值 int getInt(int columnIndex) 获取当前行中某一列的值,返回一个整型值 long getLong(int columnIndex) 获取当前行中某一列的值,返回一个长整型值 double getDouble(int columnIndex) 获取当前行中某一列的值,返回一个双精度型值 float getFloat(int columnIndex) 获取当前行中某一列的值,返回一个浮点型值 String getString(int columnIndex) 获取当前行中某一列的值,返回一个字符串 Date getDate(int columnIndex) 获取当前行中某一列的值,返回一个日期型值 Object getObject(int columnIndex) 获取当前行中某一列的值,返回一个对象 Statement getStatement() 获得产生该结果集的 Statement 对象 URL getURL(int columnIndex) 获取当前行中某一列的值,返 回一个 java.net.URL 型值 boolean isBeforeFirst() 判断游标是否在结果集的头部 boolean isAfterLast() 判断游标是否在结果集的末尾 boolean isFirst() 判断游标是否在结果集的第一行 boolean isLast() 判断游标是否在结果集的最后一行 boolean last() 将游标移动到结果集的最后一行 boolean next() 将游标移动到当前行的下一行 boolean previous() 将游标移动到当前行的前一行 ResultSet 维护指向其当前数据行的游标。每调用一次 next 方法,游标向下移动一行。最 初它位于第一行之前,因此第一次调用 next 将把游标置于第一行上,使它成为当前行。随着 每次调用 next 方法,游标向下移动一行,按照从上至下的次序获取 ResultSet 中的数据行。 在 ResultSet 对象或其父辈 Statement 对象关闭之前,游标一直保持有效。 在 SQL 中,结果集的游标是有名字的。如果数据库管理系统允许定位更新或定位删除, 则需要将游标的名字作为参数提供给更新或删除命令。可通过调用 getCursorName 方法获得 游标名。 注意:不是所有的数据库管理系统都支持定位更新和删除。可使用 DatabaseMetaData. supportsPositionedDelete 和 supportsPositionedUpdate 方法来检查特定连接是否支持这些操作。 当支持这些操作时,数据库管理系统/驱动程序必须确保适当锁定选定行,以使定位更新不会 导致更新异常或其他并发问题。 Statement.executeQuery 方法返回一个类型为 ResultSet 的对象,可以用它遍历查询的结果: ResultSet rs = stmt.execuiteQuery(“Select * FROM Student”); ResultSet 对象一次只能看到一行数据,获得一行数据后,ResultSet 对象可以以位置索引 号或列的名称为参数通过 getxxx()方法获得列(字段)值。通常,使用循环结构来获得数据 Java 语言案例教程 178 库结果集的全部列值,例如, while(rs.next()) { stuname=rs.getString(2); stuno=rs.getString(1); stusex=rs.getString(“性别”); } 使用列号作参数,其效率要高些,但是使用列名作参数,便于阅读和维护。 注意:数据库表中的列号和行号都是从 1 开始的,这一点与数组索引是不同的。 当方法类型与列的类型不匹配时,getxxx 方法会进行合理的类型转换。例如当调用 rs.getString(“年龄”);时,就可以将“年龄”列的整型值转换为一个字符串。 需要注意的是,SQL 数据类型与 Java 语言数据类型并不完全相同。表 10-6 显示了基本 SQL 数据类型与 Java 语言数据类型的对应关系。 表 10-6 SQL 数据类型及其对应的 Java 语言数据类型 SQL 数据类型 Java 数据类型 INTEGER int SMALLINT short NUMERIC Java.sql.Numeric FLOAT double REAL float DOUBLE double CHARACTER String VARCHAR String BOOLEAN boolean DATE Java.sql.Date TIME Java.sql.Time TIMESTAMP Java.sql.Timestamp BLOB Java.sql.Blob CLOB Java.sql.Clob ARRAY Java.sql.Array 说明:ResultSet 也可以获取任意大的 LONGVARBINARY 或 LONGVARCHAR 数据。 getBytes 方法和 getString 方法将数据返回为大的块(最大为 Statement.getMaxFieldSize 的返 回值)。 但 是,以较小的固定块获取非常大的数据会更方便,而这可通过让 ResultSet 类返回 java.io.Input 流来完成。从该流中可分块读取数据。但需要注意的是:必须立即访问这些流, 因为在下一次对 ResultSet 调用 get×××方法时它们将自动关闭。 … 第 10 章 数据库编程 179 10.5 JDBC 编程案例 在 JDK 所附带的 示例中有一个很好的 JDBC 连接数据库的例子(文件路径: demo\jfc\TableExample)。其运行时的效果如图 10-4 和图 10-5 所示(本例连接的数据源是 Access 数据库)。 现 在 ,编写一个真正实用的 JDBC 程序。先介绍一种通过读取属性文件打开数据库连接 的方法,这样可以通过属性文件改变数据库连接的信息而不用重新编译源程序。新建一个 database.properties 文件,输入以下属性信息: jdbc.drivers=com.microsoft.jdbc.sqlserver.SQLServerDriver jdbc.url=jdbc:microsoft:sqlserver://localhost:1433;DataBaseName=JDBCT est jdbc.username=jdbcuser jdbc.password=jdbcpwd 读取属性文件和建立数据库连接: Properties props = new Properties(); FileInputStream in = new FileInputStream("database.properties"); props.load(in); in.close(); String drivers = props.getProperty("jdbc.drivers"); if(drivers != null) { 图 10-4 Demo 的连接对话框 图 10-5 Demo 执行 SQL 的结果窗口 Java 语言案例教程 180 //System.setProperty("jdbc.drivers",drivers);//通过系统调用设置 jdbc 驱动 Class.forName(drivers); //显示的注册驱动 } String url = props.getProperty("jdbc.url"); String username = props.getProperty("jdbc.username"); String password= props.getProperty("jdbc.password"); Connection conn=DriverManager.getConnection(url,username,password); 下面的例子演示了建立数据库连接、操作数据库、建表、插入数据的完整过程。 例 10-2 数据库编程示例。 //JDBCTest.java import java.sql.*; import java.io.*; import java.util.*; /** 操作数据库,建表,插入数据的测试 */ public class JDBCTest { public static void main(String[] args) { try { Connection conn = getConnection(); Statement stmt = conn.createStatement(); /*删表*/ //stmt.execute("DROP TABLE Student"); /*建表*/ stmt.execute("CREATE TABLE Student(" +"学号 varchar(10) not null primary key," +"姓名 varchar(16) not null," +"性别 char(2) ," +"年龄 tinyint," +"政治面貌 varchar(16)," +"籍贯 varchar(16)," +"所属系 varchar(32))"); /*插入一条记录*/ String sql = "insert into student(" + "学号,姓名,性别,年龄,政治面貌,籍贯,所属系)" + "values(2002032006,'林峰','男',21,'团员','湖北','网络工程系') "; stmt.executeUpdate(sql); 第 10 章 数据库编程 181 ResultSet result = stmt.executeQuery("select * from student"); while(result.next()) { System.out.println(result.getString(1)); System.out.println(result.getString("姓名")); } result.close(); /*关闭连接*/ stmt.close(); conn.close(); } catch (SQLException e) { while(e != null) { e.printStackTrace(); e=e.getNextException(); } } catch (IOException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException,IOException { Properties props = new Properties(); FileInputStream in = new FileInputStream("database.properties"); props.load(in); in.close(); String drivers = props.getProperty("jdbc.drivers"); if(drivers != null) { System.setProperty("jdbc.drivers",drivers);//通过系统调用设置 jdbc 驱动 /*也可通过 Class.forName 的方式注册驱动*/ } String url = props.getProperty("jdbc.url"); String username = props.getProperty("jdbc.username"); String password = props.getProperty("jdbc.password"); Java 语言案例教程 182 return DriverManager.getConnection(url,username,password); } } 下面结合前面的内容给出一个完整的学籍管理系统的程序,这个程序运行的情况如图 10-6 所示。 该系统允许用户通过学号查询某个学生的信息。当在学号框中输入学号,单击查询按钮 后,该学生的信息将显示在对应的文本框中;也可对该生的信息作相关的修改,单击修改按 钮后数据库将更新该生信息;单击删除按钮将删除该生的信息。数据库中所有学生的情况随 时显示在右边的文本区中。 例 10-3 学籍管理系统。 (1)按照前面所介绍的内容配置好数据源。 (2)数据库的配置文件 database.properties 的内容: jdbc.drivers=com.microsoft.jdbc.sqlserver.SQLServerDriver jdbc.url=jdbc:microsoft:sqlserver://localhost:1433;DataBaseName=JDBCT est jdbc.username=jdbcuser jdbc.password=jdbcpwd (3)学籍管理系统主程序。 //StudentDB.java import java.sql.*; import java.io.*; import java.util.*; import javax.swing.*; import java.awt.*; import java.awt.event.*; /** 学生信息管理系统,主要演示了 JDBC 的基本使用方法 */ public class StudentDB { public static void main(String[] args) 图 10-6 学籍管理系统的运行情况 第 10 章 数据库编程 183 { QueryFrame frame = new QueryFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //使窗口显示在中央 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension frameSize = frame.getSize(); if (frameSize.height > screenSize.height) frameSize.height = screenSize.height; if (frameSize.width > screenSize.width) frameSize.width = screenSize.width; frame.setLocation( (screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2); frame.setVisible(true); frame.show(); } } class QueryFrame extends JFrame { public QueryFrame()//查询窗口 { setTitle("学籍管理系统"); setSize(WIDTH,HEIGHT); stuno = new JTextField(); stuname = new JTextField(); stuage = new JTextField(); stusex = new JComboBox(); stupolitical = new JComboBox(); stuhometown = new JTextField(); studept = new JTextField(); stuno_d = new JTextField(); stusex.addItem(""); stusex.addItem("男"); stusex.addItem("女"); stupolitical.addItem("无"); stupolitical.addItem("团员"); stupolitical.addItem("党员"); b_insert = new JButton("添加"); b_delete = new JButton("删除"); b_query = new JButton("查询"); Java 语言案例教程 184 b_modify = new JButton("修改"); b_insert.addActionListener(new //事件监听器,监听数据插入 ActionListener() { public void actionPerformed(ActionEvent event) { insert(); } }); b_query.addActionListener(new //文件监听器,监听对数据的查询 ActionListener() { public void actionPerformed(ActionEvent event) { query(); } }); b_delete.addActionListener(new //事件监听器,监听对数据的删除 ActionListener() { public void actionPerformed(ActionEvent event) { delete(); } }); b_modify.addActionListener(new //事件监听器,监听对数据的修改 ActionListener() { public void actionPerformed(ActionEvent event) { modify(); } }); result = new JTextArea(); result.setEditable(false);//使文本框只读 p_insert = new JPanel();//创建面板,用来进行数据的插入 p_insert.setLayout(new GridLayout(9,2)); p_insert.add(new Label("学 号:")); p_insert.add(stuno); p_insert.add(new Label("姓 名:")); p_insert.add(stuname); p_insert.add(new Label("年 龄:")); p_insert.add(stuage); 第 10 章 数据库编程 185 p_insert.add(new Label("性 别:")); p_insert.add(stusex); p_insert.add(new Label("政治面貌:")); p_insert.add(stupolitical); p_insert.add(new Label("籍 贯:")); p_insert.add(stuhometown); p_insert.add(new Label("所 属 系:")); p_insert.add(studept); p_insert.add(b_query); p_insert.add(b_insert); p_insert.add(b_delete);p_insert.add(b_modify); JScrollPane pane = new JScrollPane(result); JPanel show=new JPanel();//创建面板对象,用来显示内容 show.setLayout(new BorderLayout()); show.add(pane,BorderLayout.CENTER); JSplitPane split; split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,true,p_insert,show); getContentPane().add(split,BorderLayout.CENTER); /*当窗口关闭时,关掉数据库的连接*/ this.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent event) { try { stmt.close(); conn.close(); } catch(SQLException e) { while(e != null) { e.printStackTrace(); e=e.getNextException(); } JOptionPane.showMessageDialog(null, "数据库出错!请检查数据文件", "异常警告", JOptionPane.WARNING_MESSAGE); } } }); /*打开数据库连接*/ try { conn = getConnection(); stmt = conn.createStatement(); } Java 语言案例教程 186 catch(SQLException e) { while(e != null) { e.printStackTrace(); e=e.getNextException(); } JOptionPane.showMessageDialog(null, "数据库出错!\n 请检查数据文件", "异常警告", JOptionPane.WARNING_MESSAGE); JOptionPane.showMessageDialog(null, "运行失败,程序将退出!","警告", JOptionPane.WARNING_MESSAGE); System.exit(0); } catch(IOException e) { e.printStackTrace(); JOptionPane.showMessageDialog(null, "数据库连接出错!\n 请检查配置文件和 数据库设置","异常警告",JOptionPane.WARNING_MESSAGE); JOptionPane.showMessageDialog(null, "运行失败,程序将退出!","警告", JOptionPane.WARNING_MESSAGE); System.exit(0); } executeQuery(); } /** 单 击插入按钮时的动作,插入时学号和姓名不能为空 */ private void insert() { String sno = stuno.getText().trim(); String name = stuname.getText().trim(); if(sno.equals("")||name.equals("")) { JOptionPane.showMessageDialog(null, "学号和姓名不能为空", "警告", JOptionPane.WARNING_MESSAGE); return; } try 第 10 章 数据库编程 187 { Integer age =stuage.getText().trim().equals("")?null:new Integer (stuage.getText().trim()); ResultSet rs = stmt.executeQuery("SELECT * FROM Student where 学号="+sno); if(rs.next()) { JOptionPane.showMessageDialog(null, "此学号已经被注册.", "警告", JOptionPane.WARNING_MESSAGE); return; } stmt.execute( "insert into student(学号,姓名,性别,年龄,政治面貌,籍贯,所属系) values('"+sno+"','"+name+"','"+stusex.getSelectedItem()+"',"+age+", '"+stupolitical.getSelectedItem()+"','"+stuhometown.getText()+"', '"+studept.getText()+"')"); JOptionPane.showMessageDialog(null, "增加信息成功!"); } catch(Exception ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(null, "插入记录时出错.\n 请检查数据格式是否正 确!","异常警告", JOptionPane.WARNING_MESSAGE); } executeQuery(); } /** 单 击 查询按钮时的动作,通过学号查询学生的信息并显示在对应的文本框中 */ private void query() { String sno=stuno.getText().trim(); if(sno.equals("")) { JOptionPane.showMessageDialog(null, "学号不能为空", "警告", Java 语言案例教程 188 JOptionPane.WARNING_MESSAGE); return; } try { ResultSet rs = stmt.executeQuery("SELECT * FROM Student where 学号="+sno); if(rs.next()) { stuname.setText(rs.getString("姓名")); stuage.setText(rs.getString("年龄")); stusex.setSelectedItem(rs.getString("性别")); stupolitical.setSelectedItem(rs.getString("政治面貌")); stuhometown.setText(rs.getString("籍贯")); studept.setText(rs.getString("所属系")); rs.close(); } else { stuname.setText(""); stuage.setText(""); stusex.setSelectedItem(""); stupolitical.setSelectedItem("无"); stuhometown.setText(""); studept.setText(""); JOptionPane.showMessageDialog(null, "未找到该生信息", "警告", JOptionPane.WARNING_MESSAGE); return; } } catch(SQLException ex) { while(ex != null) { ex.printStackTrace(); ex=ex.getNextException(); } JOptionPane.showMessageDialog(null, "查询时出错.", "异常警告", JOptionPane.WARNING_MESSAGE); } 第 10 章 数据库编程 189 executeQuery(); } /** 单 击 删除按钮时的动作,通过学号删除学生的信息 */ private void delete() { String sno = stuno.getText().trim(); if(sno.equals("")) { JOptionPane.showMessageDialog(null, "请输入要删除学生记录的学号", "警告", JOptionPane.WARNING_MESSAGE); return; } try { ResultSet rs = stmt.executeQuery("SELECT * FROM Student where 学号= "+sno ); if(!rs.next()) { JOptionPane.showMessageDialog(null, "此学号未注册!"); return; } if (JOptionPane.showConfirmDialog(this, "确实要删除该生信息吗?\n 删除的信息将不能恢复,继续?", "删除确定", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE) == 0) { stmt.execute("DELETE FROM Student where 学号="+sno); JOptionPane.showMessageDialog(null, "删除成功!"); stuno.setText(""); stuname.setText(""); stuage.setText(""); stusex.setSelectedItem(""); stupolitical.setSelectedItem("无"); stuhometown.setText(""); studept.setText(""); } } catch(SQLException ex) Java 语言案例教程 190 { while(ex != null) { ex.printStackTrace(); ex=ex.getNextException(); } JOptionPane.showMessageDialog(null, "删除记录时出错.", "异常警告", JOptionPane.WARNING_MESSAGE); } executeQuery(); } /** 单 击修改按钮时的动作,通过学号更新学生的信息 */ private void modify() { String sno = stuno.getText().trim(); String name = stuname.getText().trim(); if(sno.equals("")||name.equals("")) { JOptionPane.showMessageDialog(null, "学号和姓名不能为空!"); return; } try { Integer age = stuage.getText().trim().equals("")?null:new Integer (stuage.getText().trim()); ResultSet rs = stmt.executeQuery("SELECT * FROM Student where 学号="+sno); if(!rs.next()) { JOptionPane.showMessageDialog(null, "没有该生信息,无法更新!"); return; } String sql=new String( "update student set 姓名='"+name+"',性别='"+stusex.getSelectedItem()+"', 年龄="+age+",政治面貌='"+stupolitical.getSelectedItem()+"', 籍贯='"+stuhometown.getText()+"', 所属系='"+studept. getText()+"'" 第 10 章 数据库编程 191 +" where 学号="+sno ); stmt.execute(sql); JOptionPane.showMessageDialog(null, "修改信息成功!"); } catch(Exception ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(null, "修改记录时出错.\n 请检查数据格式是 否正确!","异常警告", JOptionPane.WARNING_MESSAGE); } executeQuery(); } public static Connection getConnection() throws SQLException,IOException { Properties props = new Properties(); FileInputStream in = new FileInputStream("database.properties"); props.load(in); in.close(); String drivers = props.getProperty("jdbc.drivers"); if(drivers != null) { /*通过系统调用设置 jdbc 驱动*/ /*也可通过 Class.forName 的方式注册驱动*/ System.setProperty("jdbc.drivers",drivers); } String url = props.getProperty("jdbc.url"); String username = props.getProperty("jdbc.username"); String password = props.getProperty("jdbc.password"); return DriverManager.getConnection(url,username,password); } /** 将 数据库中的信息显示在面板上 */ private void executeQuery() { ResultSet rs = null; result.setText(""); Java 语言案例教程 192 result.append("学号 姓名 性别 年龄 政治面貌 籍贯 所属系\n"); try { rs = stmt.executeQuery("SELECT * FROM student"); while(rs.next()) { result.append(rs.getString("学号")+"\t"); result.append(rs.getString("姓名")+"\t"); result.append(rs.getString("性别")+"\t"); result.append(rs.getString("年龄")+"\t"); result.append(rs.getString("政治面貌")+"\t"); result.append(rs.getString("籍贯")+"\t"); result.append(rs.getString("所属系")+"\t"); result.append("\n"); } rs.close(); } catch(SQLException e) { while(e != null) { e.printStackTrace(); e=e.getNextException(); JOptionPane.showMessageDialog(null, "数据库出错!请检查数据文件", "异常警告", JOptionPane.WARNING_MESSAGE); } } } public static final int WIDTH = 600; public static final int HEIGHT = 250; private Connection conn; private Statement stmt; private JPanel p_insert; private JTextField stuno; private JTextField stuname; private JTextField stuage; private JComboBox stusex; private JComboBox stupolitical; private JTextField stuhometown; 第 10 章 数据库编程 193 private JTextField studept; private JTextField stuno_d; private JTextArea result; private JButton b_insert; private JButton b_query; private JButton b_delete; private JButton b_modify; } 【本章小结】 本章介绍了 JDBC 的体系结构和接口,以及四种 JDBC 驱动程序类型。在此基础上介绍 了使用 JDBC 的基本方法,包括建立数据库、设置数据源、连接数据源等。并 讲 解 了 MS SQL 和 Access 下 JDBC 编程方式。最后还通过一个的学籍管理系统的示例给出了使用 JDBC 实现 数据库编程的基本方法。JDBC 是 Java 访问数据库的主要途径。通过标准化的接口,Java 程 序能对不同的数据库系统进行访问,并且其代码格式也较为统一。对本章未涉及的接口和方 法需要读者自主学习和思考。 【习题】 一、简答题 1.简述 JDBC 的体系结构。 2.JDBC 的驱动程序类型分几类,试说明每一类。 3.JDBC URL 与 URL 的区别是什么? 4.如何在 SQL Server 和 Access 上建立数据库和设置数据源? 5.在 Java 程序中如何使用 JDBC 完成数据库操作? 二、实践题 1.仿照本章的例子编写一个简单的图书管理系统。 2.完 善 本章给出的学籍管理系统的功能,新建一个成绩表来记录每名学生的成绩,加入 查询学生成绩的功能。 第 11 章 课 程 设 计 11.1 中 国 象 棋 11.1.1 设计要求 编写一个中国象棋程序,能进行中国象棋游戏,并能保存棋谱和演示已有的棋谱。至少 实现以下功能中的一项: ①进行象棋游戏,并编制成棋谱。 ②保存棋谱:可将当前的棋盘情况保存在文件中。 ③悔棋,可以返回一步或者几步前的状态。 ④打开已有的棋谱,并演示整个棋谱。 ⑤你能想到的其他功能。 11.1.2 总体设计 分析设计要求所要满足的功能,确定整个程序由以下几个类构成。 l 主类 Chess:负责搭建整个程序的框架,并包含 main 方法,作为程序的入口。 l 对弈棋盘 ChessBoard 类:作为构建棋盘的类,负责绘制棋盘、棋子等,并设定和实 现象棋下棋规则。 l 棋子 ChessPiece 类:棋子类主要是定义棋子的相关属性和方法。 l 棋点 ChessPoint 类:负责判断棋盘每个点上是否有棋子,并定义移动棋子的方法。 l 走棋法则 Rule 类:按照象棋的规则判断棋子在棋盘上移动时是否犯规。 l 步骤 MoveStep 类:描述每一步走棋的情况。 l 记录棋谱 MakeChessManual 类:记录棋谱,即记下双方的每一步走棋,并记录每一 步是否有棋子被吃掉。 l 棋谱演示 Demon 类:打开保存过的棋谱文件,根据棋谱文件,自动演示此棋谱。 整个程序的界面开发基于 Swing,通过对这个程序的设计开发,熟悉如何使用 Swing 来 开发应用程序。 11.1.3 具体设计 整个程序的运行效果如图 11-1 所示,在 本小节中将按照上面的各个类来讲解部分源程序 代码。 第 11 章 课 程 设 计 195 图 11-1 中国象棋程序主界面 1.主类 Chess 部分源程序 import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.LinkedList; //定义主框架 Chess public class Chess extends JFrame implements ActionListener { //定义相应的组件,包括棋盘 ChessBoard、Demon、MakeChessManual 以及菜单等 ChessBoard board=null; Demon demon=null; MakeChessManual record=null; Container con=null; JMenuBar bar; JMenu fileMenu; JMenuItem 制作棋谱,保存棋谱,演示棋谱; JFileChooser fileChooser=null; LinkedList 棋谱=null; //定义 Chess 类的构造方法,在构造方法中进行初始化工作。并往 Chess 容器中添加相应的组 件,同时进行布局 public Chess() { bar=new JMenuBar(); fileMenu=new JMenu("中国象棋"); Java 语言案例教程 196 制作棋谱=new JMenuItem("制作棋谱"); 保存棋谱=new JMenuItem("保存棋谱"); 演示棋谱=new JMenuItem("演示棋谱"); fileMenu.add(制作棋谱); fileMenu.add(保存棋谱); fileMenu.add(演示棋谱); bar.add(fileMenu); setJMenuBar(bar); setTitle(制作棋谱.getText()); 制作棋谱.addActionListener(this); 保存棋谱.addActionListener(this); 演示棋谱.addActionListener(this); board=new ChessBoard(45,45,9,10); record=board.record; con=getContentPane(); JSplitPane split=new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true,board, record); split.setDividerSize(5); split.setDividerLocation(460); con.add(split,BorderLayout.CENTER); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); setVisible(true); setBounds(60,20,670,540); fileChooser=new JFileChooser(); con.validate(); validate(); } //实现 actionPerformed()方法,在 actionPerformed()方法中需要处理用户不同的动作, //包括了制作棋谱、保存棋谱和演示棋谱等,注意不要忘了异常处理 public void actionPerformed(ActionEvent e) { if(e.getSource()==制作棋谱) {…} if(e.getSource()==保存棋谱) {…} if(e.getSource()==演示棋谱) 第 11 章 课 程 设 计 197 { … } } //定义 main 方法作为程序的入口 public static void main(String args[]) { new Chess(); } } 2.对弈棋盘 ChessBoard 类部分源程序 import javax.swing.*; import java.awt.*; import java.awt.event.*; //定义棋盘类 ChessBoard public class ChessBoard extends JPanel implements MouseListener,Mouse MotionListener { //定义相应的组件和相应的棋子 public ChessPoint point[][]; public int unitWidth,unitHeight; int x 轴长,y 轴长; int x,y; boolean move=false; public String 红方颜色="红色",黑方颜色="黑色"; ChessPiece 红车 1,红车 2,红马 1,红马 2,红相 1,红相 2,红帅,红士 1,红士 2, 红兵 1,红兵 2,红兵 3,红兵 4,红兵 5,红炮 1,红炮 2; ChessPiece 黑车 1,黑车 2,黑马 1,黑马 2,黑象 1,黑象 2,黑将,黑士 1,黑士 2, 黑卒 1,黑卒 2,黑卒 3,黑卒 4,黑卒 5,黑炮 1,黑炮 2; int startX,startY; int startI,startJ; public boolean 红方走棋=true,黑方走棋=false; Rule rule=null; public MakeChessManual record=null; //定义 ChessBoard 类的构造方法,在此构造方法中对棋盘内的棋子进行布局 public ChessBoard(int w,int h,int r,int c) {…} //定义 paintComponent 方法绘制棋盘 public void paintComponent(Graphics g) { super.paintComponent(g); for(int j=1;j<=y 轴长;j++) Java 语言案例教程 198 { g.drawLine(point[1][j].x,point[1][j].y,point[x 轴长][j].x,point[x 轴 长][j].y); } for(int i=1;i<=x 轴长;i++) { if(i!=1&&i!=x 轴长) { g.drawLine(point[i][1].x,point[i][1].y,point[i][y 轴长-5].x, point[i] [y 轴长-5].y); g.drawLine(point[i][y 轴长-4].x,point[i][y 轴长-4].y,point[i][y 轴 长].x,point[i][y 轴长].y); } else { g.drawLine(point[i][1].x,point[i][1].y,point[i][y 轴长].x,point[i] [y 轴长].y); } } g.drawLine(point[4][1].x,point[4][1].y,point[6][3].x,point[6][3].y); g.drawLine(point[6][1].x,point[6][1].y,point[4][3].x,point[4][3].y); g.drawLine(point[4][8].x,point[4][8].y,point[6][y 轴长].x,point[6][y 轴 长].y); g.drawLine(point[4][y 轴长].x,point[4][y 轴长].y,point[6][8]. x,point [6][8].y); for(int i=1;i<=x 轴长;i++) { g.drawString(""+i,i*unitWidth,unitHeight/2); } int j=1; for(char c='A';c<='J';c++) { g.drawString(""+c,unitWidth/4,j*unitHeight); j++; } } //实现 mousePressed 方法,负责处理鼠标按下事件 public void mousePressed(MouseEvent e) {…} public void mouseMoved(MouseEvent e) 第 11 章 课 程 设 计 199 { } //实现 mouseDragged 方法,负责处理鼠标拖动事件 public void mouseDragged(MouseEvent e) {…} //实现 mouseReleased 方法,负责处理放开鼠标事件 public void mouseReleased(MouseEvent e) {…} public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mouseClicked(MouseEvent e) { } } 3.棋子 ChessPiece 类部分源程序 import javax.swing.*; import java.awt.*; import java.awt.event.*; //定义棋子 ChessPiece 类,包括一些基本属性和常用方法 public class ChessPiece extends JLabel { String name; Color backColor=null,foreColor; String 颜色类别=null; ChessBoard board=null; int width,height; //定义 ChessPiece 类的构造方法 public ChessPiece(String name,Color fc,Color bc,int width,int height, ChessBoard board) { this.name=name; this.board=board; this.width=width; this.height=height; foreColor=fc; backColor=bc; Java 语言案例教程 200 setSize(width,height); setBackground(bc); addMouseMotionListener(board); addMouseListener(board); } //定义 ChessPiece 类的常用方法 public void paint(Graphics g) {…} public int getWidth() {…} public int getHeight() {…} public String getName() {…} public Color 获取棋子颜色() {…} public void set 棋子类别(String 类别) {…} public String 棋子类别() {…} } 4.棋点 ChessPoint 类的部分源程序 public class ChessPoint { int x,y; boolean 有棋子; ChessPiece piece=null; ChessBoard board=null; //定义 ChessPoint 类的构造方法: public ChessPoint(int x,int y,boolean boo) {…} //定义 ChessPoint 类的一些常用方法 public boolean isPiece() {…} public void set 有棋子(boolean boo) {…} public int getX() {…} public int getY() {…} 第 11 章 课 程 设 计 201 public void setPiece(ChessPiece piece,ChessBoard board) {…} public ChessPiece getPiece() {…} public void reMovePiece(ChessPiece piece,ChessBoard board) {…} } 5.走棋法则 Rule 类的部分源程序 import javax.swing.*; import java.awt.*; import java.awt.event.*; //定义 Rule 类,以及相应的变量和构造方法 public class Rule { ChessBoard board=null; ChessPiece piece=null; ChessPoint point[][]; int startI,startJ,endI,endJ; public Rule(ChessBoard board,ChessPoint point[][]) { this.board=board; this.point=point; //定义 Rule 类的一个重要方法,判断用户每走一步棋是否满足象棋的走棋规则。对 不同//的棋 子定义不同的走棋规则 public boolean movePieceRule(ChessPiece piece,int startI,int startJ,int endI,int endJ) { this.piece=piece; this.startI=startI; this.startJ=startJ; this.endI=endI; this.endJ=endJ; int minI=Math.min(startI,endI); int maxI=Math.max(startI,endI); int minJ=Math.min(startJ,endJ); int maxJ=Math.max(startJ,endJ); boolean 可否走棋=false; //定义各种棋子的走棋规则 if(piece.getName().equals("车")) Java 语言案例教程 202 {…} else if(piece.getName().equals("马")) {…} else if(piece.getName().equals("象")) {…} else if(piece.getName().equals("相")) {…} else if(piece.getName().equals("炮")) {…} else if(piece.getName().equals("兵")) {…} else if(piece.getName().equals("卒")) {…} else if(piece.getName().equals("士")) {…} else if((piece.getName().equals("帅"))||(piece.getName().equals("将"))) {…} } 6.步骤 MoveStep 类的源程序 import java.awt.Point; public class MoveStep implements java.io.Serializable { public Point pStart,pEnd; public MoveStep(Point p1,Point p2) { pStart=p1; pEnd=p2; } } 7.记录棋谱 MakeChessManual 类的部分源程序 import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.LinkedList; //定义 MakeChessManual 类的变量以及构造方法 public class MakeChessManual extends JPanel implements ActionListener { JTextArea text=null; JScrollPane scroll=null; 第 11 章 课 程 设 计 203 ChessBoard board=null; ChessPoint[][] point; LinkedList 棋谱=null; LinkedList 吃掉的棋子=null; JButton buttonUndo; int i=0; public MakeChessManual(ChessBoard board,ChessPoint[][] point) { this.board=board; this.point=point; text=new JTextArea(); scroll=new JScrollPane(text); 棋谱=new LinkedList(); 吃掉的棋子=new LinkedList(); buttonUndo=new JButton("悔棋"); buttonUndo.setFont(new Font("隶书",Font.PLAIN,18)); setLayout(new BorderLayout()); add(scroll,BorderLayout.CENTER); add(buttonUndo,BorderLayout.SOUTH); buttonUndo.addActionListener(this); } public char numberToLetter(int n) { char c='\0'; switch(n) { case 1 : c='A'; break; case 2 : c='B'; break; case 3 : c='C'; break; case 4 : c='D'; break; case 5 : c='E'; break; case 6 : c='F'; break; case 7 : c='G'; break; case 8 : c='H'; break; case 9 : c='I'; break; case 10 : c='J'; break; } return c; } //定义记录棋谱方法,记下每一步棋 Java 语言案例教程 204 public void 记录棋谱(ChessPiece piece,int startI,int startJ,int endI,int endJ) {…} public void 记录吃掉的棋子(Object object) {…} public LinkedList 获取棋谱() {…} //实现 actionPerformed 方法 public void actionPerformed(ActionEvent e) {…} } 8.棋谱演示 Demon 类的部分源程序 import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.*; //定义 Demon 类以及相关的变量和方法 public class Demon extends JPanel implements ActionListener,Runnable { public JButton replay=null,next=null,auto=null,stop=null; LinkedList 棋谱=null; Thread 自动演示=null; int index=-1; ChessBoard board=null; JTextArea text; JTextField 时间间隔=null; int time=1000; String 演示过程=""; JSplitPane splitH=null,splitV=null; //定义 Demon 类的构造方法 public Demon(ChessBoard board) { this.board=board; replay=new JButton("重新演示"); next=new JButton("下一步"); auto=new JButton("自动演示"); stop=new JButton("暂停演示"); 自动演示=new Thread(this); replay.addActionListener(this); next.addActionListener(this); 第 11 章 课 程 设 计 205 auto.addActionListener(this); stop.addActionListener(this); text=new JTextArea(); 时间间隔=new JTextField("1"); setLayout(new BorderLayout()); JScrollPane pane=new JScrollPane(text); JPanel p=new JPanel(new GridLayout(3,2)); p.add(next); p.add(replay); p.add(auto); p.add(stop); p.add(new JLabel("时间间隔(秒)",SwingConstants.CENTER)) ; p.add(时间间隔); splitV=new JSplitPane(JSplitPane.VERTICAL_SPLIT,pane,p); splitH=new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,board,splitV); splitV.setDividerSize(5); splitV.setDividerLocation(400); splitH.setDividerSize(5); splitH.setDividerLocation(460); add(splitH,BorderLayout.CENTER); validate(); } //定义 Demon 类中的一些方法 public void set 棋谱(LinkedList 棋谱) {…} public char numberToLetter(int n) {…} public void actionPerformed(ActionEvent e) {…} public synchronized void run() {…} public void 演示一步(int index) {…} public void 演示结束(String message) {…} } 11.1.4 提交成果 ①提交全部的源代码,要求符合编码规范。 Java 语言案例教程 206 ②撰写需求分析文档。 ③撰写系统详细设计文档(类的划分及相互关系,系统的流程图)。 ④撰写程序配置文档。 ⑤撰写用户手册。 11.2 图书查询系统 11.2.1 设计要求 编写一个图书查询系统,要求使用 B/S(浏览器/服务器)模式。具体要求如下: ①查询指定的书目和读者的借阅情况。 ②读者可以在线预订某一书籍,一个读者最多可以预订三本书,一本书最多可以被四个 读者预订。 ③你能想到的其他功能。 11.2.2 总体设计 系统的总体结构分为两大模块——客户端和服务器端,分 别 对 应源文件 SearchClient.java 和 SearchServer.java。客户端是用户进行图书查询的界面,用户在客户端输入相应的查询条件, 并提交到服务器端,由服务器端到相应的数据库内去查询,并将查询的结果返回到客户端。 由于本系统是基于 B/S 模式,因此客户端程序是基于浏览器的,在设计开发客户端时可 以选择基于 JSP,也可以选择使用 Java Applet。在此,客户端选择使用 Java Applet。 11.2.3 具体设计 1.客户端设计 由于客户端采用Java Applet,因此在客户端界面设计方面可以选择使用AWT或者Swing, 本例使用 AWT 编写用户图形界面。客户端运行效果如图 11-2 所示。 图 11-2 图书查询系统客户端 第 11 章 课 程 设 计 207 客户端部分源程序如下: import java.net.*; import java.io.*; import java.awt.*; import java.awt.event.*; import java.applet.*; //定义客户端的主类 SearchClient public class SearchClient extends Applet implements Runnable,ActionListener { //定义客户端的组件,包括 TextField、Choice、Checkbox、Button 等 TextField searchContent; Choice choice=null; Checkbox 完全一致,前方一致,后方一致,中间包含; CheckboxGroup group=null; Button search; TextArea showSearchResult; Label tips; Socket socket=null; DataInputStream in=null; DataOutputStream out=null; Thread thread; //初始化操作,将定义好的组件实例化后添加到 Applet 容器中,并进行一定的布局 public void init() { searchContent=new TextField(18); search=new Button("查询"); choice=new Choice(); choice.add("书名"); choice.add("作者"); choice.add("出版社"); choice.select(0); group=new CheckboxGroup(); 完全一致=new Checkbox("完全一致",true,group); 前方一致=new Checkbox("前方一致",false,group); 后方一致=new Checkbox("后方一致",false,group); 中间包含=new Checkbox("中间包含",false,group); showSearchResult=new TextArea(8,40); tips=new Label("正在连接到服务器,请稍等...",Label.CENTER); tips.setForeground(Color.red); Java 语言案例教程 208 tips.setFont(new Font("TimesRoman",Font.BOLD,24)); Panel box1=new Panel(); box1.add(new Label("搜索内容:",Label.CENTER)); box1.add(searchContent); box1.add(choice); box1.add(search); Panel box2=new Panel(); box2.add(new Label("选择查询条件:",Label.CENTER)); box2.add(完全一致); box2.add(前方一致); box2.add(后方一致); box2.add(中间包含); Panel box3=new Panel(); box3.add(new Label("查询结果:",Label.CENTER)); box3.add(showSearchResult); add(tips); add(box1); add(box2); add(box3); search.addActionListener(this); } //启动 Applet 程序中所包含的线程 public void start() { //判断是否已经有建立好了的 socket 连接,是否有已经打开的输入/输出流,如果有,先关闭 if(socket!=null&&in!=null&&out!=null) { try { socket.close(); in.close(); out.close(); } catch(Exception ee) { } } //与服务器端建立 Socket 连接,同时打开相应的输入/输出流,注意需要进行相应的异常//处 理,如果连接成功,则返回相应的信息,同时开启相应的线程 try { 第 11 章 课 程 设 计 209 socket=new Socket(this.getCodeBase().getHost(), 6666); in=new DataInputStream(socket.getInputStream()); out=new DataOutputStream(socket.getOutputStream()); } catch (IOException ee) { tips.setText("连接失败"); } if(socket!=null) { InetAddress address=socket.getInetAddress(); tips.setText("连接:"+address+"成功"); } if(thread==null) { thread=new Thread(this); thread.start(); } } //当关闭客户端页面时,关闭 Applet。在关闭 Applet 时需要关闭所建立的 Socket,并停止 线程 public void stop() {…} //定义线程中的 run 方法,在 run 方法中需要读取 Socket 流中的数据,并将数据显示在客户 端的界面上 public void run() { … } // 实现 actionPerformed 方法处理 action 事件,根据用户选择的查询范围来提交查询条件, 并注意异常处理 public void actionPerformed(ActionEvent e) {…} } 2.服务器端设计 服务器端主要负责监听客户端的查询请求,一旦收到客户端的查询请求,就根据客户端 所提交的查询条件到数据库内进行查询,并将符合条件的查询结果返回给客户端,并由客户 端显示给用户。 服务器端由两个类构成,一个是守护线程类,负责监听客户端的请求,一旦收到客户端 请求,就实例化另一个 Server_thread 类对象来负责具体处理查询事务。 服务器端部分源程序如下: 守护线程类:DatabaseServer,负责监听 6666 端口,并建立相应的 Socket Java 语言案例教程 210 import java.io.*; import java.net.*; import java.sql.*; import java.util.StringTokenizer; public class DatabaseServer { public static void main(String args[]) { ServerSocket server=null; Server_thread thread; Socket you=null; while(true) { try { server=new ServerSocket(6666); } catch(IOException e1) { System.out.println("正在监听"); } try { you=server.accept(); } catch (IOException e) { } if(you!=null) { new Server_thread(you).start(); } else {continue;} } } } //应用线程类 Server_thread,负责具体的查询事务处理 class Server_thread extends Thread { //定义相关的变量 第 11 章 课 程 设 计 211 Socket socket; Connection con=null; Statement stmt=null; ResultSet rs; DataOutputStream out=null; DataInputStream in=null; String s=null; int number=0; //定义 Server_thread 类的构造方法,在构造方法中实例化输入/输出流,并使用 JDBC //与数据库建立连接,同时也要注意捕获并处理异常 Server_thread(Socket t) {…} //实现 Server_thread 类中的 run 方法,在 run 方法中,接收客户端发送的查询信息,并对 查询信息进行分析,获得相应的查询条件。根据查询条件在数据库内查询并获得结果集。然后将结果集中的 每条记录的相关信息返回 public void run() {…} } 11.2.4 提交成果 ①提交全部的源代码,要求符合编码规范。 ②撰写需求分析文档。 ③撰写系统详细设计文档(类的划分及相互关系,系统的流程图)。 ④撰写程序配置文档。 ⑤撰写用户手册。 【本章小结】 本章通过讲解两个 Java 程序——中国象棋程序和基于 B/S 模式的图书查询系统,让读者 了解和掌握如何使用 Java 语言开发相对比较复杂的程序。相信通过这两个程序的学习,大家 对如何使用 Swing 和 AWT 开发程序界面、如何进行使用 Java 进行网络通信、如何处理输入 /输出以及如何连接数据库等技能有了进一步的掌握。希望读者们有机会编写更多的 Java 程 序进行练习,以便能熟练的使用 Java 语言开发程序。
还剩219页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

xiaojun207

贡献于2015-07-05

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf