第2章 JSP开发基础
【本章导读】
Java Web开发人员从事JSP开发时需要理解B/S模式的基本原理,以及JSP的运行原理,这样有助于深刻认识开发程序时的设计思想,特别是在编写Web程序时,往往需要夹杂着写Java代码、HTML代码、JavaScript代码,没有理解基本的理论与设计思想,有时连阅读程序都会觉得困难,可见学习基本原理的重要性。
此外,Java Web开发人员要在页面中编写Java代码或编写Java类需要掌握Java编程的语法、面向对象编程的基本方法,还需要了解JDK类库,并熟悉一些常用的类、接口,以提高编程的效率。
【提示】
对本章内容已经比较熟悉的读者可以跳过此章,但强烈建议Java初学者仔细阅读本章的内容。
2.1 C/S与B/S
乍一听C/S(Client/Server,客户机/服务器)、B/S(Browser/Server,浏览器/服务器)这些名字,初学者可能觉得比较陌生,然而理解这些概念对于开发Java Web系统是有必要的,Java Web系统都是基于B/S模式实现的。
2.1.1 C/S模式
在软件开发领域里将C/S看作是一种网络应用程序的计算模式,也就是说将网络应用程序分为两部分,一部分为服务器端,另一部分为客户机端,一般情况下服务器端只有一个,而客户机端可以有多个。
图2-1 C/S计算模式
比较典型的情况就是服务器端是一种数据库管理系统,负责数据库中的数据管理工作,以及接收客户端传来的数据请求而对数据做特定的操作后返回给客户端操作结果。客户端是专门为某种需求而编制的应用系统,如果需要存、取、修改数据就通过发送指令(请求)给数据库服务器,数据库服务器再返回结果(响应)。
2.1.2 B/S模式
B/S模式实际上是C/S模式的一种特例,同样有服务器端和客户端,如图2-2所示。
图2-2 B/S计算模式
浏览器如IE(Internet Explorer)、Netscape、FireFox等工具软件,可以用来访问Web服务器;Web服务器是指安装了Web应用服务中间件软件的机器,支持Java语言的常用的Web应用服务中间件软件有Tomcat、Weblogic、Websphere等。
客户通过浏览器将请求发送给Web服务器,Web服务器接收到请求后即处理请示,如果不需要访问数据库就直接把响应回给客户,如果需要访问数据库就接着确定要访问哪个数据库服务器,再操作数据库中的数据得到操作结果,最后将结果组织成直观的网页形式的界面返回给客户端,显示在客户端的浏览器中。
采用B/S模式的应用情况比较多,如各种网站、企业的管理信息系统等。
2.1.3 两种模式的比较
(1)成本比较
B/S模式中客户端的浏览器是一种通用的工具软件,如Windows操作系统中就自带了IE浏览器,无须安装即可使用,也无须在客户端再安装软件,软件升级也比较方便,只需要升级Web服务器上的软件,但是B/S模式比C/S模式多需要一个Web应用服务中间件软件,所幸Tomcat是免费的。
C/S的客户端需要专门安装,需要定制开发,因此升级和维护比较麻烦。
(2)客户端负载比较
B/S模式的客户端称为“瘦客户机”,这是因为客户端的浏览器只负责发送请求,以及显示回应过来的数据,工作任务比较简单,负载较小;而C/S的客户端称为“胖客户机”,这是因为客户端的应用程序不仅要负责发送请示和显示数据,还可能有业务逻辑、数据运算逻辑等。
(3)可移植性比较
B/S模式下的客户端软件无须安装也就不存在可移植性的问题;C/S模式下的客户端软件需要考虑可移植性的问题,比如Windows2000下运行的程序在WindowsXP下是否能运行,UNIX下运行的程序是否在Windows系统下也能运行。
(4)用户界面比较
B/S模式下的界面就是一张张的网页,用户使用时就像浏览网页,因此能给用户以较好的体验;C/S模式下主要采用窗口界面或命令对话的形式。
(5)安全性比较
C/S模式下的软件一般属于专用软件,需要统一管理并派发,从管理上来说安全性相对好一些,但是客户端软件往往采用直接连接数据库的方式,可能会将数据库连接参数暴露出来,从而带来隐患;B/S模式下的安全问题主要集中在Web服务器上,由于客户端并不直接连接数据库,而是通过Web服务器来连接数据库,所以数据相对安全,但是B/S模式下尚需考虑许多安全性方面的问题,比如数据传输过程中的报文加密问题、用户登录时的验证问题等。
B/S模式是目前软件开发的主流方向,但也不能一概而论。对于安全性要求高且用户数量不大,建议使用C/S模式;如果用户交互性的情况比较多,用户基数相对较大的就采用B/S模式,当然建议读者优先考虑B/S模式。
2.2 JSP的运行原理
客户端是通过浏览器来访问JSP页面的,每个JSP页面的扩展名都是.jsp。访问时,客户在浏览器的地址栏输入(或通过链接)要访问的JSP页面,Web服务器接收到客户端的访问请求后,Web引擎会查找JSP页面。找到JSP页面后,作一个分析,如果还是第一次访问这个JSP页面,则先将JSP页面转换成Servlet(扩展名为.java),再将Servlet编译成字节码文件(扩展名为.class),编译成字节码文件后就可以在JVM(Java Virtual Machine,Java虚拟机)中运行了。如果JSP页面不是第一次被访问,则直接执行第一次被访问时生成的字节码文件,这样就不必再次转换成Servlet,也不必再次编译成字节码文件了,所以JSP页面的执行速度也会比较快。
客户端访问JSP页面的过程如图2-3所示。从中也可以看出,第一次访问JSP页面会慢一些,此后就会快多了。
图2-3 JSP页面的运行过程
【例2-1】第一个简单的JSP页面
本例将在Eclipse中创建本章所有示例所属的工程,并编写输出“这是一个简单的JSP页面”文字的JSP页面程序。
【提示】
此后,每章的源代码都会放在一个单独的Web工程中,如第1章的Web工程名为“ch01”,第2章为“ch02”。创建工程的方法参见第1章中的相关内容。
新建一个“Dynamic Web Project”工程,工程名为ch02,如图2-4所示。
图2-4 新建的“ch02”Web工程
在“WebContent”上单击右键,选择“New”→“JSP”,在“Create a new JavaServer Page”对话框中的“File name:”后输入文件名,这里请录入FirstJsp.jsp,修改自动生成的源代码,修改后的代码如下。
FirstJsp.jsp
<%@ page language="java" contentType="text/html; charset=gb2312" pageEncoding="gb2312"%> <html> <head> <title>标题</title> </head> <body> 这是一个简单的JSP页面! </body> </html>
程序的运行结果如图2-5所示。
图2-5 一个简单的JSP页面
为了在JSP页面中支持中文显示,在页面的第一句内容即做出了声明:
<%@ page language="java" contentType="text/html; charset=gb2312" pageEncoding="gb2312"%>
【提示】
如果要调试本例的程序但又觉得在Eclipse中不够直观,可以任意工具来编辑FirstJsp.jsp文件,再将此文件放到Tomcat安装目录的ROOT目录下,则就可以直接在浏览器中访问了。访问的地址如下:
如果要与图2-5中的访问路径相同,将“ch02”作为Web应用的名称,可以将这个Web应用在Eclipse工作区中的WebContent拷贝到Tomcat6安装目录的webapps子目录中,再修改文件夹名称为“ch02”即可,访问结果如图2-6所示。
图2-6 在Tomcat6中调试JSP程序
2.3 Java语法
在网页的静态页面(扩展名为.htm或.html)中加入Java语句就成了JSP动态页面(扩展名为.jsp)。如果要封装一些逻辑处理程序就要编写Java类,因此掌握Java语法成了Java Web程序员必学的知识内容。有许多Java Web程序员在用JSP开发了一些项目后,又回过头来学习Java语言基础,是因为随着学习和应用的深入,需要掌握更多的面向对象编程知识,需要封装一些更复杂的逻辑。
本节将学习Java编程的基础知识,包括Java的标识符、数据类型、运算符、注释符和控制语句。
2.3.1 标识符
标识符用来让编译器能够识别变量、类、方法、对象、接口、包等。书写Java中的标识符时需要注意如下的规则:
(1)首字符必须是字母、下画线“_”或美元符号“$”。
(2)除首字符以外的其他字符可以是字母、数字、下画线“_”和美元符号“$”,Java采用的是Unicode字符集,因此所有十六进制值大于00C0的Unicode码字符都可作为除首字符以外的其他字符的字符元素,但诸如“*、+、-”等运算符、分隔符、注释符号、转义字符及一些十六进制值小于0030的特殊字符(如回车、空格、换行等)等不能作为标识符的字符元素。
(3)标识符对大小写敏感,如“abc”和“Abc”是两个不同的标识符。
(4)Java中的关键字不能作为标识符。
【提示】
Java中默认情况下使用的是Unicode国际标准字符集,这意味着程序员也可以使用汉语、法语等作为标识符的元素,但为了增强程序的可读性和可移植性,建议不要这样做,提倡使用字母作为标识符。
表2-1是Java中的关键字,程序员在命名标识符请避免使用。
表2-1 Java中的关键字
2.3.2 数据类型
Java中的数据类型特别丰富。所谓数据类型就是按一定的逻辑组织和存储数据的方式而分类的类型。Java数据类型可分为两大类:基本数据类型和抽象数据类型,如图2-7所示。
图2-7 Java中的数据类型
1.整数
整数是最为常用的数据类型,有正整数、零、负整数,它的表示方式有以下三种。
● 十进制:用0~9之间的数字表示的数据,如198。
● 八进制:以0~7之间的数字表示的数据,前面以0作为标志表示是八进制的数据,如0348。
● 十六进制:以0~9之间的数字或a-f或A-F之间的字母表示的数据,前面以0X或0x作为标志表示十六进制的数据,如0X17EF,其中的a-f或A-F表示值为10-15。
表2-2是一个3种进制数据的对照表,供读者参考分析。
表2-2 整数的十进制、八进制、十六进制表示方式
整数又分为字节型(byte)、短整型(short)、整型(int)、长整型(long),它们的具体情况如表2-3所示。
表2-3 整数的4种类型
在表示长整型数据时,应在数尾加上l或L,建议用L,因为小写字母l和数字1,在编辑器中难以辨认。
2.实数
实数类型主要可以分为浮点型(float,也称为单精度型)和双精度(double)型。在精度要求不高时,采用单精度是非常方便的。实数类型有两种表示方式。
● 标准计数法:由整数部分、小数点和小数部分构成,如2.4、333.45等。
● 科学计数法:由十进制整数、小数点、小数和指数部分构成,指数部分由字母E或e跟上带正负号的整数表示,如134.23科学计数法表示为1.3423E+2。
实数类型的具体情况如表2-4所示。
表2-4 实数的两种类型
3.布尔型
布尔型(boolean)也称为逻辑型,它只有真(true)和假(false)两个值。布尔值被常用在需要作判断的场合,如if语句、while语句的条件表达式中。
4.字符型
字符型常量是指包括在单引号中的字符,如'a','A'。Java中的字符型数据采用的是Unicode字符集,每个字符用16位表示,即2个字节空间。Unicode定义的国际化字符集能表示迄今为止人类语言的所有字符,它是几十个字符集的统一,例如汉语、拉丁文、希腊语、阿拉伯语、古代斯拉夫语、希伯来语、日文片假名、匈牙利语等。
为了避免字符常量与Java语言中所使用的特定字符(如:单引号、双引号等)冲突及表示不可显示的字符(非图形字符),可使用反斜杠“\”作为前缀的转义字符来表示这些字符。常见的转义字符如表2-5所示。
表2-5 转义字符
【提示】
与C/C++不同,Java中的字符型数据不是整数,但是可以将字符型数据作为整型数据来操作。这怎么理解呢?来看下面的一段代码。
char three='3'; int four=4; char seven=(char)(three+four);
这三句话运行后,变量serven的结果值为'7'。
5.简单数据类型之间的转换
编程时经常需要转换数据类型,有时为了运算方便,编译器也会自动作一些转换,程序员有必要了解简单数据类型之间的转换规则,因为转换数据类型很容易出错,或造成数据精度的损失。基本数据类型中转换的优先关系如下所示。
byte→short→char→int→long→float→double
优先级低 → 优先级高
在复杂的表达式中,如果两个不同优先级的变量进行运算,编译器会自动把优先级低的变量转换为优先级高的变量的数据类型。但如果要将优先级高的变量转换成优先级低的变量的数据类型就得进行数据类型的强制转换了,这样有可能会损失数据的精度,使用时要特别注意。
强制类型转换的一般格式如下:
[变量=](与变量相同的类型)表达式;
例如:
float floatData=10.5; int intData=(int)floatData;
结果intData的值为10。
【例2-2】Java的基本数据类型
BasicDataType.jsp
<%@ page language="java" contentType="text/html; charset=gb2312"%> <html> <head> <title>基本数据类型</title> </head> <body> <% int one=1,two=2;//声明整型变量 char three='3';//声明字符型变量 float aFloat=3.0f;//声明单精度型变量 double aDouble=3.15;//声明双精度型变量 //------整型与字符型数据运算------ out.print("one+three="); out.print((char)(one+three)+"<br>"); out.print("one+three="); out.print((int)(one+three)+"<br>"); //------整型与单精度型数据运算------ out.print("one+aFloat="+(one+aFloat)+"<br>"); //------整型与双精度型数据运算------ out.print("two+aDouble="+(two+aDouble)+"<br>"); //------双精度型强制转换为整型------ out.print("aDouble强制转换为int型:"+(int)aDouble); %> </body> </html>
程序的运行结果如图2-8所示。
图2-8 Java的基本数据类型的使用
为什么是第2行显示的“one+three=52”呢?52是数字4的ASCII码。此外,从图2-8中也可以看出,double类型的数据强制转换为整型时有精度损失,变量aDouble原来的值为3.15,转换后为3,将小数部分截去了。
6.数组
数组用于表示相同类型数据的有序组合。数组中的每个成员称为数组元素。数组元素的数据类型可以是基本数据类型,也可以是抽象数据类型。根据数组元素的数据类型的不同,可称之为int数组、byte数组等。
数组可以是多维的,比如存储矩阵就要用到二维数组,存储立体空间中一个点的位置就需要三维数组。访问数组中的元素时通过下标(也称为索引)来引用,下标从0开始。
定义一个一维数组的语法如下:
类型[ ] 数组标识符 = new 类型[数组长度];
或
类型 数组标识符[ ] = new 类型[数组长度];
“类型”可是任一种基本数据类型或抽象数据类型。“数组长度”表示数组元素的个数。在数组对象创建时,系统将自动给每个数组元素赋初值。对于整数类型数组,系统将给每个数组元素赋0;实型数组,则赋0.0;字符型数组,则赋Unicode码值为0的不可见字符;boolean型数组,则赋false;抽象数据类型数组,则赋null值。但是系统赋予的初值有时并不是程序员所期望的,因此常要用赋值语句对数组元素逐个赋值,也可以采用初始化赋值的方法。初始化赋值的一般格式为:
类型[ ] 数组标识符={值,值……值};
或
类型 数组标识符[ ]={值,值……值};
在这两种格式中,数组长度由初始值数目决定。
【提示】
有时并不清楚数组的长度到底是多少,也不清楚有多少个初值,怎么办?这时可以使用Java中的java.util.ArrayList类或其他一些JDK类库中已封装好的一些集合类。
如果要声明二维数组采用如下所示的语法:
类型[ ][ ] 数组标识符 = new 类型[一维长度] [二维长度];
或
类型 数组标识符[ ] [ ] = new 类型[一维长度] [二维长度];
二维数组初始化赋值的一般格式为:
类型[ ] 数组标识符={{值,值……值},{值,值……值}……,{值,值……值}};
或
类型 数组标识符[ ]= {{值,值……值},{值,值……值}……,{值,值……值}};
【提示】
多维数组依照二维数组的声明方式类推,一般较少使用三维及三维以上的数组。
类、接口等抽象数据类型将在下一节中详细介绍。
2.3.3 运算符
Java中的运算符特别丰富,根据功能可以分为如下的几类。
● 算术运算符,如:+、—、*、/、%、++、--等。
● 关系运算符,如:>、>=、<、<=、==、!=等。
● 布尔逻辑运算符,如:与(&&)、或(||)、非(!)等。
● 位运算符,如:位与(&)、位或(|)、位非(~)、位异或(^)、左移(<<)、右移(>>)等。
● 赋值运算符,如:简单赋值(=)、复合算术赋值(+=,-=,*=,/=,%=)和复合位运算赋值(&=,|=,^=,>>=,<<=)等。
● 条件运算符,这是一个三目运算符,用于条件求值(?:)。
● 强制类型转换运算符。
● 数组下标运算符。
● 对象运算符,如:new、instance of等。
1.算术运算符
算术运算符又有单目运算符和双目运算符之分。双目运算符主要有+、-、*、/、% 5个运算符,如表2-6所示。
表2-6 算术双目运算符
单目运算符主要有-、++、-- 3个运算符,如表2-7所示。
表2-7 算术单目运算符
负数运算符(-)用于改变数值的符号(正数变负数,负数变正数),但不改变操作数变量本身的值。自增运算符(++),其中++op指先将变量加1后,再作其他运算(如赋值运算),而op++指变量先作其他运算后,再将其加1;自减运算符与此类似。如下面的程序:
int a=2,b=3; int c=--a+b; int d=a--+b;
则执行完后,a=0,b=3,c=4;d=4。以上语句相当于如下的语句:
int a=2,b=3; a=a-1; int c=a+b; int d=a+b; a=a-1;
2.关系运算符
关系运算符用于比较两个值的大小或相等关系,因此也称作比较运算符。表达式比较结果要么成立,结果为真(true);要么不成立,结果为假(false)。因此,Java的关系运算表达式结果用boolean型表示。
关系运算都是二元运算符, 主要有:>、>=、<、<=、==、!=,如表2-8所示。
表2-8 关系运算符
【提示】
关系运算符用于对象类型之间的比较时,比较的是“两个变量保存的内存地址是否相同”,而不是比较“两个变量所引用的对象内容是否相等”。对象内容的比较通常应采用对象的equals (obj1,obj2)方法。
3.布尔逻辑运算符
布尔逻辑运算的符号有“&&”、“||”、“!”,如表2-9所示。
表2-9 布尔逻辑运算符
4.位运算符
Java位运算符主要可分为两大类:位逻辑运算符和移位运算符。位逻辑运算符主要有:~、&、|、^,移位运算符主要有:>>、>>>、<<。在Web开发中,很少需要用到位运算功能。
5.条件运算符
条件运算符(Conditional Operator)是一个三目运算符(ternary),使用方式如下:
boolean表达式 ? 表达式成立执行的表达式 : 表达式失败执行的表达式
条件表达式的三个运算对象分别用“?”和“:”隔开,因此,条件表达式也常被称为?表达式。在?左边的boolean表达式可以是任何运算结果为boolean型的变量、常量、逻辑表达式或关系表达式等。如果boolean表达式结果为true,则执行“?”与“:”之间的表达式,并将该表达式结果作为整个条件表达式的结果;如果为false,则执行“:”右边的表达式,并将结果作为整个表达式的结果。“?”之后的表达式必须是同类型的表达式。
其他运算符比较简单,就不再赘述了。读者可根据本章的后续内容结合实例加深理解。
2.3.4 注释符
注释可以增强程序的可读性和可维护性,主要用于描述程序的功能、逻辑、调用接口、参数说明等,程序员要养成良好的习惯多写些注释。成熟的软件公司还会有一些注释书写规范,以形成良好的开发规则。
Java中的注释主要有三种。第一种:
/*注释内容*/
这种形式以“/*”起头,随后是注释内容,并可以跨越多行,直到遇到“*/”结束。为了让注释更易阅读,许多程序员喜欢在每一行之前都用“*”开头。
//注释内容
这种形式称为单行注释。以“//”开头,表示“//”后的内容都是注释。这种注释简单易用,常用于注释说明当前行代码或下一行代码。
/**注释内容*/
这种注释格式是Java语言特别为Java的文档自动生成器(JavaDoc)构造的,其中也可使用HTML标记。JavaDoc能够将其自动转换为HTML格式的注释文档。这种注释主要用于文件头、类或接口、方法、属性等声明之前,分别给文件、类或接口、方法、属性作注释。而在方法内部或其他地方一般不用这种形式,因为在使用JavaDoc文档生成器时,系统将自动把这类注释内容生成为Java文档。
2.3.5 控制语句
控制语句用于控制程序的流程,以实现程序的各种结构方式。它们由特定的语句定义符组成。Java语言控制语句主要可分成以下4类:
● 条件选择语句:如if语句、switch语句。
● 循环执行语句:如do while语句、while语句、for语句。
● 转向语句:如break语句、continue语句。
● 返回语句:如return语句。
1.if语句
if语句将程序执行的路径一分为二,条件表达式值为真时是一条路径,条件表达式值为假时是另一条路径。
if(判断表达式) 语句1; [else 语句2;]
else子句是可选的,也就是说也可以没有else子句。语句1和语句2可以是单个语句也可以是语句块,两者永远不可能同时被执行。如果语句1或语句2是语句块则要用花括号{}括起来。
【提示】
if语句可以嵌套使用,嵌套使用时要注意花括号的匹配问题,它们总是配对使用的。
2.switch语句
如果条件分支较多,就可以采用switch语句,将使程序更具可读性。应用switch语句的语法如下:
switch(表达式){ case 值1: 语句1; [break;] [case 值2: 语句2; [break;]] [……] [case 值n: 语句n; [break;]] [default: 语句d;] }
表达式必须为byte, short, int或char数据类型,每个case语句后的值必须是一个与表达式类型兼容的特定常量。语句的执行过程如下,用表达式的值与case后的值进行比较,如果找到一个与之匹配的,则执行相应的语句后退出switch语句,如果没找到,则执行default后的语句再退出switch语句。break语句是可选的,如果没有break语句,则执行完语句后将继续执行后面的语句。
3.while语句
while语句用于循环执行某一个循环体,使用的语法如下:
while(条件表达式){ //循环体 }
只要条件表达式的值为真,则继续执行循环体,如果为假,则退出循环。如果循环体只有一个语句,则花括号可以去掉。
4.do while语句
do while语句用于循环执行某一个循环体,使用的语法如下:
do{ //循环体 } while(条件表达式);
【提示】
不论条件表达式的值是真还是假,循环体至少会执行一次,并且先执行循环体再判断表达式,这是do while语句与while语句的区别。
5.for语句
for语句是一种功能强大而使用特别灵活的循环控制语句。使用for循环的通用格式如下:
for([初始化循环变量值];[条件表达式];[循环变量值变更处理]){ //循环体 }
如果循环体只有一个语句,则花括号可以略去。循环执行的过程如下:第一次执行时,先进行初始化,一般用来初始化条件表达式中变量的值;接着检查条件表达式,如果结果为真,则执行循环体,如果为假,则退出循环。每次执行完循环体都要执行循环变量值变更处理语句,可以进行增量也可以进行减量。
在初始化语句initialization中,如果要给多个变量赋值,则赋值表达式之间用逗号“,”隔开。
6.其他控制语句
break用于跳出循环或switch语句;continue用于结束循环语句中的当前循环,不再执行循环体中continue语句后的语句,而继续下一轮循环;return语句用于返回值,并退出当前方法而回到调用当前方法处继续执行。
2.4 Java面向对象基础
Java的核心思想就是面向对象编程,有许多Java Web程序员在直接学习JSP后就从事Java Web系统的开发,因为小型的Web系统并不需要设计和开发复杂的类、接口、继承关系等,随着面向对象的深入应用,开始要用Java类封装业务逻辑、数据处理逻辑等,开始要运用一些诸如Struts、Spring之类的框架技术,程序员也就更加需要对Java面向对象编程的思想有更为精深的理解与运用。因此,许多Java Web程序员在开发过多个Web系统后,又回过头来学习Java面向对象编程,可见学习面向对象编程基本思想的重要性。
在需求分析阶段,重点分析的是与系统相关的实体及其它们之间的联系;在系统设计阶段,通过设计Java类模型或对象模型来模拟现实世界中的实体及其它们之间的联系;然后再对每一个类进行详细设计。Java的类(或对象)模型实质是一个抽象数据类型,描述了(或者说封装了)与之对应实体的逻辑模型及定义在该模型上的一组操作。
Java Web程序员重要需要理解类、对象、接口、包等这些最为基本的概念以及它们是如何运用的。
2.4.1 类
Java语言中的类是Java程序的基本组成部分,是一种抽象的数据类型,它是对象的模板,对一组对象作抽象描述,对象是类的实例。
类的定义主要包含两种内容的描述:
(1)成员域,也称为类的属性或者类的数据成员。成员域描述类的内部特征。
(2)方法,方法描述类的运动规律,决定了类对象可以做的操作。方法通常用于改变类对象自身的状态,即对类的成员域进行操作。
定义一个类主要包括两个部分:类声明和类体。定义一个类的语法如下:
类声明{ 类体 }
类声明的格式如下:
[public|private][abstract][final] class 类名 [extends 父类名] [implements 接口名表]
关键字public作为类的存取修饰符,被声明为public的类称为公共类可以被其他包中的类存取,否则只能在定义它的包中使用。关键字abstract将类声明为抽象类,包含抽象方法的类称为抽象类,抽象方法在抽象类中不作具体实现,具体实现交由子类完成。关键字final将类声明为最终类,最终类不能被其他类所继承,没有子类。因此,一个类不能同时是抽象类又是最终类,也就是说abstract关键字和final关键字不能在类声明中同时使用。
父类名表示所定义的类继承于指定的父类。由于Java是单继承,所以在关键字extends之后,只能指定一个父类。如果未指定该项,Java默认其直接继承于java.lang.Object类。
接口名表,表示所定义类将实现接口名表中所指定的所有接口(interface),接口名之间用“,” 分隔。Java虽是单继承的,但可同时实现多个接口,所以在关键字implements后可根据需要指定任意多个接口。
【提示】
Java中不能对类作多重继承,也就是说一个类不能继承多个父类,那怎么办呢?可以通过多个接口来实现多重继承。
类体紧跟在类声明之后,用一对花括号“{ }”括起来。类主体定义类的成员域和方法。类体的通用格式如下:
[public][abstract][final] class 类名 [extends 超类名] [implements 接口名表]{ 成员域定义部分; 方法定义部分; }
1.成员域定义
成员域定义的语法格式如下:
[存取修饰符] [final] static [volatile] 类型 变量名 [ = 值 或表达式][,变量名 [= 值或表达式]……];
存取修饰符用于控制变量的访问权限,控制变量在不同类中的访问权限,可包括:public、protected、private、默认,默认也称为友元(firendly)。其访问权限,如表2-10所示。
表2-10 存取修饰符访问权限
说明:√ 表示可以访问。
关键字final表示将方法声明为最终变量,声明为final的变量,不可对其重新赋值,必须在声明时包含一个初始化语句来对其赋值,因此final常用于声明常量。声明为static的变量表示它的值将在它的所有对象实例间共享。如果一个变量是volatile型的,则说明它是并发控制中的异步变量,在每次使用volatile类型的变量时都要将它从存储器中重新装载并在使用后存回存储器中。
2.方法定义
方法定义其一般格式如下:
[存取修饰符] [final] [static][abstract][native][synchronized] [方法返回类型] 方法名( [形式参数表]) [throws 异常表]{ 可执行代码; }
存取修饰符用于控制方法的访问权限,表示方法的可使用范围,包括:public 、protected、private、默认(firendly)。关键字final可用在方法定义中以禁止子类用同名、同参数方法覆盖它。关键字static表明此方法是静态方法。关键字abstract将方法声明为抽象方法,抽象方法没有方法体,方法体交由子类实现。关键字native将方法声明为本地方法,说明方法是由平台相关的语言实现(如C语言),而不是用Java语言实现。它不是Java本身的方法,没有方法体。关键字synchronized将方法声明为同步方法。
3.this变量与super变量
this变量是实例方法的隐含变量,它引用的是调用该方法的当前对象。super变量与this变量一样,也是实例方法的隐含变量,但它引用的是父类。
4.方法重载
在Java语言中,用同名但拥有不同形式参数列表的两个或多个方法来实现方法重载。方法的名称、形式参数的个数、类型及其顺序形成了这个方法的签名(signature)。在类中每个方法的签名都必须唯一,这样Java编译就能够准确地判断程序中调用的是哪个方法。
方法的返回类型,对于方法的签名没有影响。Java编译器不能只通过方法的返回类型区别不同的方法,因为调用一个方法的时候,方法的返回类型不是必需的。
5.构造方法
构造方法的主要用途有两个:一是通知Java虚拟机创建类的对象,二是对创建的对象进行初始化。
构造方法与普通方法比较,主要有2个特点:
● 构造方法的名称要求与类名同名;
● 不允许给构造方法指定返回类型,即使void也不行,构造方法没有返回值。
如果在程序中没有显式地定义构造方法,Java编译器将自动提供一个构造方法,称为默认构造方法。这个构造方法没有参数,在方法体中也没有任何语句。
2.4.2 对象
创建一个新的对象使用new关键字,可使用如下的语句:
类名 对象名=new 构造函数或类名;
或
类名 对象名; 对象名=new 构造函数或类名;
使用第二种方法如果没有后面的带new关键字的语句,对象就还没有生成实例,也没有被分配内存空间。
对象的实例变量与方法的引用采用“.”运算符,引用的一般格式为:
对象引用变量.类成员
【提示】
对象赋值时如果直接用“=”,则表示对象的引用,如:
a=b;
如果a的成员变量值变更了,b的成员变量值也会变更。
【例2-3】Java Web开发中的面向对象编程
本实例将创建一个Java类,并在JSP页面中使用这个类。
先创建一个包,创建包的方法如图2-9所示,在接下来的“New Java Package”对话框中将包名命名为box。
图2-9 创建一个包
再创建一个Java类,菜单如图2-9所示,选择“class”子菜单,在“New Java Class”对话框中将类名设为box。box类的代码如下。
box.java
package box; public class box { public int length=0;//长 public int width=0;//宽 public int height=0;//高 public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } }
编写一个JSP页面,代码如下。
MyObject.jsp
<%@ page language="java" contentType="text/html; charset=gb2312"%> <%@ page import="box.box" %> <html> <head> <title>使用对象</title> </head> <body> <% box boxa=new box(); box boxb=boxa; out.print("--------------------------------<br>"); out.print("boxa的宽为:"+boxa.getWidth()+"<br>"); out.print("boxb的宽为:"+boxb.getWidth()+"<br>"); out.print("--------------------------------<br>"); boxb.setWidth(10); out.print("boxa的宽为:"+boxa.getWidth()+"<br>"); out.print("boxb的宽为:"+boxb.getWidth()+"<br>"); out.print("--------------------------------<br>"); boxb=new box(); boxb.setWidth(20); out.print("boxa的宽为:"+boxa.getWidth()+"<br>"); out.print("boxb的宽为:"+boxb.getWidth()+"<br>"); out.print("--------------------------------<br>"); %> </body> </html>
JSP页面的运行结果如图2-10所示。
图2-10 编写和使用对象
程序中使用了“=”赋值,这样boxb对象是boxa对象的引用,因此当修改boxb对象的width属性值为10时,boxa对象的width属性也被修改成10;当用new关键字创建了boxb对象时,修改boxb对象的width值将不再对boxa对象的width值造成影响。
2.4.3 接口
接口是一组常量和方法的集合,但其中的方法并不作任何具体的实现。接口定义的一般格式为:
[public] interface 接口名 [extends 父辈接口列表]{ 常量定义 方法声明 }
public说明该接口可被任何包中的类实现,如果没有public修饰符,则它只能被同一个包中的类实现。父辈接口列表用于声明当前接口继承于哪些接口,与类不同的是,类只能实现单继承,只能继承于某一个父类;而接口可同时继承于多个父辈接口,接口之间用“,”分隔。
在接口中定义的任何成员域都被认为是常量,系统将自动默认为public、static、final类型。接口方法的声明,一般格式为:
[public] [abstract] [方法返回类型] 方法名( [形式参数表]) [throws 异常表];
与抽象方法的声明类似,但在接口方法声明中只允许用public和abstract修饰符,且可省略,系统将自动默认其为public、abstract类型。这些接口方法声明,也可进行重载声明(即方法名相同,但参数不一致)。接口不是类,它无须构造函数,也无法声明构造函数。
【提示】
如果在两个或多个超接口中,存在相同的方法签名,则必须要求这些方法声明具有相同的方法返回值和修饰符,否则编译器将报错。
2.4.4 包
设计者可将一组高内聚、低耦合、关联性较强的模型元素(可以是类、接口、包)组织在一起,形成一个更高层次的单元——包。在Java语言中,需把程序编译单元(类、接口、包等)组织在包中,以方便Java程序的编写、设计。在Java语言中,包还是使Java编译单元具有唯一命名的机制,程序员把各编译单元组织在包中可确保包中的编译单元与其他包中的编译单元不产生命名冲突。因为包中的类名都由包名来限定,在程序中引用其他包中的类时,必须通过“import”关键字,将指定包中的类载入。
2.5 JDK类库
JDK中带有许多现成的类、接口可供程序员调用,有的书称之为Java API,使用JDK类库中的类、接口,程序员能够快速、方便地开发出简单、实用的应用程序。JDK类库十分复杂,在此也不可能一一介绍,下面将介绍一些Java Web开发中常用的类和接口。
类库中最重要的类是Object和Class,其中Object为类层次结构的根,而Class的实例表示正在运行的应用程序中的类。
2.5.1 包装类
在Java中,可直接把简单数据类型的变量表示为一个类。对应简单数据类型常用的有6个包装类,分别是Boolean, Character, Integer, Long, Float, Double。
有了包装类,就可以利用它们的构造函数来进行赋值操作,其构造函数如下所示。
public Boolean(boolean value)或public Boolean(String value)
public Chararcter(char value)
public Integer(int value)或public Integer (String value)
public Long(long value)或public Long (String value)
public Float(float value)或public Float (String value)或public Float (double value)
public Double(double value) 或public Double (String value)
1.Boolean包装类
Boolean包装类对应简单数据类型boolean,常用的方法有如下一些。
● public static boolean parseBoolean(String s);
将字符串s转换成boolean型数据,如果s为字符串"true"则返回true,否则返回false。
● public static String to String(boolean b);
将boolean型数据b转换为字符串,为true则转换为字符串"true",为false则转换为字符串"false"。
● public static Boolean valueOf(boolean b);
将boolean型数据b转换为包装类Boolean,得到的是包装类的实例。
● public static Boolean valueOf(String s);
将字符串s转换成包装类Boolean,得到的是包装类的实例。如果s为字符串"true"则返回的包装类实例值为true,否则返回的包装类实例值为false。
● public boolean booleanValue();
得到包装类对象的布尔值。
2.Chararcter包装类
Chararcter包装类对应简单数据类型char,常用的方法有如下一些。
● public char charValue();
得到Chararcter包装类实例对应的字符值。
● public static boolean isDigit(char ch);
判定指定字符是否为数字。如果一个字符代码不在 '\u2000' <= ch <= '\u2FFF' 范围内,则它为数字。如果字符为数字则返回true;否则返回false。
● public static boolean isLetter(char ch);
判定指定字符是否为字母。一个字符被认为是一个字母,当且仅当Unicode 2.0标准(Unicode规范数据文件中的"Lu","Ll","Lm","Lt"或"Lo"分类)指定它为字母。如果字符ch是字母则返回true;否则返回false。
● public static boolean isLetterOrDigit(char ch);
判定指定字符是否为字母或数字。一个字符的isLetterOrDigit()为true,当且仅当此字符的isLetter()或isDigit()为true。
● public static boolean isLowerCase(char ch);
判定指定的字符ch是否小写,如果字符ch为小写,则返回true;否则返回false。
● public static boolean isUpperCase(char ch);
判定指定的字符ch是否大写,如果字符ch为大写,则返回true;否则返回false。
● public static char toLowerCase(char ch);
将字符ch转换为小写,返回转换后的字符。
● public static char toUpperCase(char ch);
将字符ch转换为大写,返回转换后的字符。
● public String toString();
返回表示此字符值的串对象。将Character对象转换为一个串。结果是长度为1的串。此串的单一元素是表示此对象的基本char值。
● public static Character valueOf(char c);
返回一个表示指定char值的Character包装类实例。
3.Integer包装类
Integer包装类对应简单数据类型int,常用的方法有如下一些。
● public byte byteValue();
以byte类型返回该Integer包装类实例的值。
● public short shortValue();
以short类型返回该Integer包装类实例的值。
● public int intValue();
以int类型返回该Integer包装类实例的值。
● public long longValue();
以long类型返回该Integer包装类实例的值。
● public float floatValue();
以float类型返回该Integer包装类实例的值。
● public double doubleValue();
以double类型返回该Integer包装类实例的值。
● public static int parseInt(String s) throws NumberFormatException;
将字符串参数作为有符号的十进制整数进行分析。除了第一个字符可以是用来表示负值的ASCII减号'-'('\u002D')外,字符串中的字符都必须是十进制数字。返回得到的整数值。
● public String toString();
返回一个表示该Integer值的String对象。将该参数转换为有符号的十进制表示形式,并以字符串的形式返回。
4.Long包装类
Long包装类对应简单数据类型long,常用的方法有如下一些。
● public byte byteValue();
以byte类型返回该Long包装类实例的值。
● public short shortValue();
以short类型返回该Long包装类实例的值。
● public int intValue();
以int类型返回该Long包装类实例的值。
● public long longValue();
以long类型返回该Long包装类实例的值。
● public float floatValue();
以float类型返回该Long包装类实例的值。
● public double doubleValue();
以double类型返回该Long包装类实例的值。
● public static long parseLong(String s) throws NumberFormatException;
将string参数分析为有符号十进制long。字符串中的字符必须都是十进制数字,除非第一个字符是ASCII字符的减号'-' (\u002D'),它表示一个负值。返回得到的long值。
● public static Long valueOf(String s) throws NumberFormatException;
返回保持指定String的值的Long对象。该参数被解释为表示一个有符号的十进制long。此方法返回的Long对象的值等值于:
new Long(Long.parseLong(s))
● public static Long valueOf(long l);
返回表示指定long值的Long包装类实例。
● public static String toString(long i);
返回表示指定long的String对象,参数i被转换为有符号的十进制表示形式,并作为字符串返回。
5.Float包装类
Float包装类对应简单数据类型float,常用的方法有如下一些。
● public byte byteValue();
以byte类型返回该Float包装类实例的值。
● public short shortValue();
以short类型返回该Float包装类实例的值。
● public int intValue();
以int类型返回该Float包装类实例的值。
● public long longValue();
以long类型返回该Float包装类实例的值。
● public float floatValue();
以float类型返回该Float包装类实例的值。
● public double doubleValue();
以double类型返回该Float包装类实例的值。
● public static float parseFloat(String s) throws NumberFormatException;
将string参数分析为有符号十进制float。字符串中的字符必须都是十进制数字,除非第一个字符是ASCII字符的减号'-' (\u002D'),它表示一个负值。返回得到的float值。
● public static Float valueOf(String s) throws NumberFormatException;
返回保持指定String的值的Float对象。该参数被解释为表示一个有符号的十进制float。此方法返回的Float对象的值等值于:
new Float (Float.parseFloat (s))
● public static Float valueOf(float f);
返回表示指定float值的Float包装类实例。
● public static String toString(float f);
返回表示指定float的String对象,参数f被转换为有符号的十进制表示形式,并作为字符串返回。
6.Double包装类
Double包装类对应简单数据类型double,常用的方法有如下一些。
● public byte byteValue();
以byte类型返回该Double包装类实例的值。
● public short shortValue();
以short类型返回该Double包装类实例的值。
● public int intValue();
以int类型返回该Double包装类实例的值。
● public long longValue();
以long类型返回该Double包装类实例的值。
● public float floatValue();
以float类型返回该Double包装类实例的值。
● public double doubleValue();
以double类型返回该Double包装类实例的值。
● public static double parseDouble(String s) throws NumberFormatException;
将string参数分析为有符号十进制double。字符串中的字符必须都是十进制数字,除非第一个字符是ASCII字符的减号'-' (\u002D'),它表示一个负值。返回得到的double值。
● public static Double valueOf(String s) throws NumberFormatException;
返回保持指定String的值的Double对象。该参数被解释为表示一个有符号的十进制double。此方法返回的Double对象的值等值于:
new Double (Float.parseDouble (s))
● public static Double valueOf(double f);
返回表示指定double值的Double包装类实例。
● public static String toString(double f);
返回表示指定double的String对象,参数f被转换为有符号的十进制表示形式,并作为字符串返回。
【例2-4】使用包装类
CasingClass.jsp
<%@ page language="java" contentType="text/html; charset=gb2312"%> <html> <head> <title>使用包装类</title> </head> <body> <% String falseStr="false"; Boolean boolA=new Boolean(falseStr); out.print("boolA的值为:"+boolA.booleanValue()+"<br>"); Character charA=new Character('a'); out.print("charA是字母吗?"+Character.isLetter(charA.charValue())+"<br>"); out.print("charA是小写吗?"+Character.isLowerCase(charA.charValue())+"<br>"); Double doubleA=new Double(12.5456789123); out.print("doubleA转换为float后的值为:"+doubleA.floatValue()+"<br>"); out.print("doubleA转换为int后的值为:"+doubleA.intValue()+"<br>"); %> </body> </html>
程序的运行结果如图2-11所示。
图2-11 使用包装类
Character包装类的isLetter()方法和isLowerCase()方法是静态方法,因此直接用类名来调用。
doubleA的值为12.5456789123,当使用doubleA.floatValue()后得到的float值为12.545679,在第6位小数上作了四舍五入;当使用doubleA.intValue()后得到的int值为12,留下了整数部分,小数部分被截去。
2.5.2 String类
Java中将字符串作为一个对象来处理,即String,而不是一个简单的数据类型。了解和掌握String类的使用是有必要的,Web页面中信息的传递大多是基于字符串来进行的,如表单中填入的文本数据,链接地址中带的参数,网页中显示的文章标题。因此需要熟悉掌握String类的一些常用的方法。
【提示】
这里的函数也就是指方法。
1.构造函数
String类的构造函数有许多种形式,如下列举出常用的几种形式。
public String();
创建一个字符串对象,其中没有任何字符。
public String(byte bytes[]);
使用默认的字符集用字节数组bytes创建一个字符串对象,创建后的字符串长度可能与字节数组的长度不同,因为字符串是字符集编码,而字节数组中的数据是一个一个的字节。
public String(byte bytes[], int offset, int length);
使用默认的字符集用字节数组bytes的部分内容创建一个字符串对象,bytes数组中用来构造字符串的起点为offset(数组中,offset的起点值为0),长度为length(字节个数)。
public String(byte bytes[], int offset, int length, String charsetName) throws UnsupportedEncodingException;
使用指定的字符集charsetName用字节数组bytes的部分内容创建一个字符串对象,bytes数组中用来构造字符串的起点为offset,长度为length(字节个数)。如果指定的字符集不受支持则会抛出UnsupportedEncodingException异常,如果offset和length参数索引字符超出bytes数组的范围则会抛出IndexOutOfBoundsException异常。
public String(byte bytes[], String charsetName) throws UnsupportedEncodingException;
使用指定的字符集charsetName用字节数组bytes创建一个字符串对象。如果指定的字符集不受支持则会抛出UnsupportedEncodingException异常。
public String(char[] value);
使用默认的字符集用字符数组value创建一个字符串对象。
public String(char value[], int offset, int count) throws IndexOutOfBoundsException;
使用默认的字符集用字符数组value的部分内容创建一个字符串对象,value数组中用来构造字符串的起点为offset,总数为count。如果offset和count参数索引字符超出value数组的范围,则会抛出IndexOutOfBoundsException异常。
public String(StringBuffer buffer);
用字符串缓冲区参数中的字符序列来创建一个字符串对象。
2.求字符串长度的函数
public int length();
返回当前字符串的长度,长度等于字符串中16位Unicode字符数。
3.字符串连接
字符串的连接有两种方法,一种是使用“+”号,将返回连接后的字符串,当和其他数据类型连接时,返回的数据类型也为字符串;另一种是使用concat()方法。
public String concat(String str);
指定字符串str连到当前字符串的结尾。此方法并不修改当前字符串中的内容。
【例2-5】使用String类的构造函数、求长度函数、连接函数
UseString.jsp
<%@ page language="java" contentType="text/html; charset=gb2312"%> <html> <head> <title>使用String类</title> </head> <body> <% //------构造函数的使用------ String tempStr1=new String("中国系统分析员顾问团"); out.print(tempStr1+"<br>"); byte[] tempByte1=tempStr1.getBytes(); String tempStr2=new String(tempByte1); out.print(tempStr2+"<br>"); tempStr2=new String(tempByte1,9,5); out.print(tempStr2+"<br>"); tempStr2=new String(tempByte1,4,10); out.print(tempStr2+"<br>"); //------求字符串长度函数的使用------ out.print("tempStr1的长度为:"+tempStr1.length()+"<br>"); //------字符串连接函数的使用------ tempStr1=new String("中国"); tempStr2=new String("系统分析员"); out.print(tempStr1.concat(tempStr2)+"<br>"); %> </body> </html>
程序的运行结果如图2-12所示。
图2-12 使用String类的构造函数、求长度函数、连接函数
程序中,使用构造函数的多种形式创建了字符串。在使用字节数组构造字符串时,可以发现如果使用单数起点来创建字符串,显示的字符串内容可能是乱码。因为1个汉字是2个字节,1个汉字如果只取其中的1个字节,构造的字符串就有可能是乱码。字符串的求长度函数得到的是字符的字符,而非字节的个数,所以tempStr1的长度为10而非20。
4.字符串转换函数
public char[] toCharArray();
将当前字符串转换为1个字符数组,返回这个数组。
public String toLowerCase();
将当前字符串转换为小写,返回转换后的字符串。
public String toUpperCase();
将当前字符串转换为大写,返回转换后的字符串。
5.字符与字符串查找函数
public char charAt(int index) throws IndexOutOfBoundsException;
返回指定索引index处的char值,索引范围为从0到length() - 1,序列的第一个char值在索引0处,第二个在索引1处,依此类推,这类似于数组索引。如果index参数为负或小于此字符串的长度。
public String substring(int beginIndex) throws IndexOutOfBoundsException;
返回一个新的字符串,它是当前字符串的一个子字符串。该子字符串始于指定索引处的字符,一直到此字符串末尾。如果beginIndex为负或大于当前String对象的长度,则会抛出IndexOutOfBoundsException异常。
public String substring(int beginIndex,int endIndex) throws IndexOutOfBoundsException;
返回一个新字符串,它是当前字符串的一个子字符串。该子字符串从指定的beginIndex处开始,一直到索引endIndex- 1处的字符。因此该子字符串的长度为endIndex-beginIndex。如果beginIndex为负,或endIndex大于当前String对象的长度,或beginIndex大于endIndex都会抛出IndexOutOfBoundsException异常。
public int indexOf(String str);
返回第一次出现的指定子字符串str在当前字符串中的索引。如果字符串参数作为一个子字符串在此对象中出现,则返回第一个这样的子字符串的第一个字符的索引;如果没有找到则返回-1。
public int indexOf(String str, int fromIndex);
从当前字符串指定的索引处fromIndex开始,返回第一次出现的指定子字符串str在当前字符串中的索引,如果没有找到则返回-1。
public int lastIndexOf(String str);
返回在当前字符串中最右边出现的指定子字符串str的索引。如果在此对象中字符串参数作为一个子字符串出现一次或多次,则返回最后一个这样的子字符串的第一个字符;如果找不到则返回-1。
public int lastIndexOf(String str,int fromIndex);
从指定的索引处fromIndex开始向后搜索,返回在当前字符串中最后一次出现的指定子字符串str的索引。
public boolean matches(String regex) throws PatternSyntaxException;
验证当前字符串是否匹配给定的正则表达式regex,匹配则返回true,不匹配则返回false。如果正则表达式regex的语法有误则会抛出PatternSyntaxException异常。正则表达式常用的匹配符号如表2-11所示。
表2-11 常用的正则表达式匹配符号
表2-12给出了一些常用的正则表达式。
表2-12 常用的正则表达式
6.判定函数
public boolean endsWith(String suffix);
测试此字符串是否以指定的后缀结束。如果该参数表示的字符序列是当前对象表示的字符序列的后缀,则返回true;否则返回false。
public boolean equals(Object anObject);
比较此字符串与指定的对象。当且仅当该参数不为null,并且是表示与此对象相同的字符序列的String对象时,结果才为true。
public boolean equalsIgnoreCase(String anotherString);
将当前String与另一个String进行比较,不考虑大小写。如果两个字符串的长度相等,并且两个字符串中的相应字符都相等(忽略大小写),则认为这两个字符串是相等的。
public boolean startsWith(String prefix);
测试当前字符串是否以指定的前缀开始。如果该参数表示的字符序列是此字符串表示的字符序列的前缀,则为true;否则为false。
public boolean startsWith(String prefix,int toffset);
测试当前字符串是否以指定前缀开始,该前缀以指定索引开始。如果该参数表示的字符序列是当前对象从索引toffset处开始的子字符串,则返回true;否则返回false。如果toffset为负或大于当前String对象的长度,则结果为false。
7.其他字符串操作函数
public String trim();
返回字符串的副本,忽略前导空白和尾部空白。此方法用于截去字符串从头到尾的空白。
public String replace(char oldChar,char newChar);
返回一个新的字符串,它是通过用newChar替换当前字符串中出现的所有oldChar而生成的。如果oldChar在当前String对象表示的字符序列中没有出现,则返回对此String对象的引用。否则创建一个新的String对象,其中的内容为替换后的字符串。
public String replaceAll(String regex,String replacement) throws PatternSyntaxException;
使用给定的replacement字符串替换当前字符串匹配给定的正则表达式的每个子字符串。如果正则表达式的语法无效则会抛出PatternSyntaxException异常。
public String replace(CharSequence target,CharSequence replacement) throws PatternSyntaxException;
根据给定的正则表达式的匹配来拆分当前字符串。方法返回拆分后得到的字符串数组。如果正则表达式的语法无效则会抛出PatternSyntaxException异常。
【提示】
trim()、repacle()、repaceAll()方法并不修改当前字符串本身的内容,而是返回处理后的字符串。
【例2-6】String的转换、查找、判定函数的应用
UseString2.jsp
<%@ page language="java" contentType="text/html; charset=gb2312"%> <html> <head> <title>使用String类</title> </head> <body> <% //------字符串转换函数的使用------ String csai=new String("希赛顾问团csai"); out.print(csai.toUpperCase()+"<br>"); //------字符与字符串查找函数的使用------ out.print("第3个字符为:"+csai.charAt(5)+"<br>"); out.print("从第3个字符开始的子字符串为:"+csai.substring(2)+"<br>"); out.print("\"顾问团\"出现的位置是:"+csai.indexOf("顾问团")+"<br>"); //------判定函数------ out.print("是否以\"希赛\"开头:"+csai.startsWith("希赛")); %> </body>
程序的运行结果如图2-13所示。
图2-13 String的转换、查找、判定函数的应用
2.5.3 StringBuffer类
String类表示的字符串是定长的,而StringBuffer类提供了可变长的字符串,同时还提供了大量的字符串操作功能。在StringBuffer类的字符串中可以再插入字符,此时StringBuffer会自动增加内存空间,这在String中是无法做到的。
字符串缓冲区被编译器用来实现连接两个字符串的运算符+。例如,代码:
x = "a" + 4 + "c"
被编译成等价的:
x = new StringBuffer().append("a").append(4).append("c").toString()
在StringBuffer上的基本操作是append()和insert()方法,它们都被覆盖以接受任意类型的数据。每个方法都迅速地把所给的数据转换成一个字符串,并且把该字符串的字符添加或插入到字符串缓冲区中。append()方法常常把这些字符添加到缓冲区的末端;而insert()方法则把这些字符添加到指定的地方。
例如,如果z指的是一个字符串缓冲区内的对象,其当前的内容是"start",于是通过调用方法z.append("le")会使字符串缓冲区的内容变成"startle",而z.insert(4, "le")将使缓冲区的内容变成"starlet"。
1.构造函数
public StringBuffer(); public StringBuffer(int capacity); public StringBuffer(String str);
第一种形式,采用默认的构造函数,会给字符串预留16个字符的空间;第二种形式中的capacity用来设置缓冲区的大小;第三种形式用字符串str来初始化StringBuffer中的内容。
2.求字符串长度的函数
public synchronized int length();
返回字符串缓冲区中字符的个数。
public synchronized int capacity();
得到分配给字符串的容量。
【提示】
length()和capacity()是有所不同的,通常StringBuffer要比实际字符预留更多的空间,以允许增加字符。
如果初始化了字符串后,要更改缓冲区大小可用ensureCapacity()方法,这样可以为新增字符串预先分配空间,因为再分配空间的代价是比较大的,而且频繁地分配空间会产生碎片。此方法的语法格式如下:
public synchronized void ensureCapacity(int minimumCapacity);
这个方法用于保证缓冲区的容量至少等于指定的最小数。如果字符串缓冲区的当前容量少于该参数,则分配一个新的更大的内部缓冲区。新容量将取如下参数中较大的一个:
● 参数minimumCapacity。
● 旧容量的两倍加2。
如果参数minimumCapacity非正,该方法不作任何操作,只简单地返回。
如果要改变字符串的长度可用setLength()方法,语法格式如下:
public synchronized void setLength(int newLength) throws StringIndexOutOfBoundsException;
设置该字符串缓冲区中的字符串的长度。如果参数newLength小于该字符串缓冲区的当前长度,则该字符串缓冲区中的字符串将被截断来包含恰好等于由参数newLength给出的字符数。如果参数newLength大于或等于当前的长度,就用null字符('\u0000')补齐该字符串缓冲区中字符没有内容的那一部分,以使长度变为参数newLength指定的长度。
参数newLength必须大于或等于0,如果参数newLength设置非法则会抛出StringIndexOutOfBoundsException异常。
【提示】
注意区分字符串缓冲区的长度与字符串缓冲区中字符串的长度这两个概念,后者总是小于或等于前者。
3.向缓冲区中加入内容的函数
public StringBuffer append(boolean b); public synchronized StringBuffer append(char c); public StringBuffer append(double d); public StringBuffer append(float f); public StringBuffer append(int i); public StringBuffer append(long l);
将相应的简单数据类型的数据转换成一个字符串后再添加到字符串缓冲区的尾部。
public synchronized StringBuffer append(char str[]);
将字符数组str转换成一个字符串后再添加到字符串缓冲区的尾部。
public synchronized StringBuffer append(char str[],int offset,int len);
把字符数组str从下标offset开始的len个字符,依次添加到字符串缓冲区的尾部。
public synchronized StringBuffer append(Object obj);
将对象obj转换成一个字符串后再添加到字符串缓冲区的尾部。
public synchronized StringBuffer append(String str);
将字符串str添加到字符串缓冲区的尾部。
根据append()方法,相应地有insert()方法,用于在字符串缓冲区的字符串的指定位置处加入内容,方法的原型如下所示。
public StringBuffer insert(int offset,boolean b) throws StringIndexOutOfBoundsException; public synchronized StringBuffer insert(int offset, char c) throws StringIndexOutOfBoundsException; public synchronized StringBuffer insert(int index, char str[], int offset, int len) throws StringIndexOutOfBoundsException; public StringBuffer insert(int offset, double d) throws StringIndexOutOfBoundsException; public StringBuffer insert(int offset, float f) throws StringIndexOutOfBoundsException; public StringBuffer insert(int offset, int i) throws StringIndexOutOfBoundsException; public synchronized StringBuffer insert(int offset, Object obj) throws StringIndexOutOfBoundsException; public synchronized StringBuffer insert(int offset, String str) throws StringIndexOutOfBoundsException; public StringBuffer insert(int offset, long l) throws StringIndexOutOfBoundsException;
4.在缓冲区中查找字符或字符串的函数
public synchronized char charAt(int index) throws StringIndexOutOfBoundsException;
返回在字符串缓冲区特定位置的字符。偏移量index必须大于或等于0,而且小于字符串缓冲区的长度,否则将会抛出StringIndexOutOfBoundsException异常。
【提示】
字符串缓冲区的第一个字符的索引是0,下一个字符的索引是1,对于索引数组依次类推。
public synchronized void getChars(int srcBegin, int srcEnd, char dst[],int dstBegin) throws StringIndexOutOfBoundsException;
把字符从该字符串缓冲区拷贝到目标字符数组dst。第一个要复制的字符在下标srcBegin;最后一个要复制的字符在下标srcEnd-1。要复制的字符总数是srcEnd-srcBegin。 要复制到dst子数组的字符开始于下标dstBegin,结束于下标:
dstbegin + (srcEnd-srcBegin) - 1
如果有一个对该缓冲区无效的索引则会抛出StringIndexOutOfBoundsException异常。
public String toString();
转换为一个表示该字符串缓冲区数据的字符串。分配一个新的String对象,并且用字符串缓冲区所表示的字符序列进行初始化,返回这个String对象。
public String substring(int start) throws StringIndexOutOfBoundsException;
返回一个新的String,它包含当前字符序列所包含的一个字符子序列,该子字符串始于指定索引处start的字符,一直到当前字符串末尾。如果start小于0或大于此对象的长度则会抛出StringIndexOutOfBoundsException异常。
public String substring(int start, int end) throws StringIndexOutOfBoundsException;
返回一个新的String,它包含当前序列所包含的字符子序列,该子字符串包括从指定的start处开始,一直到索引end - 1处的字符。如果start或end为负数或大于length(),如果start大于end,都会抛出StringIndexOutOfBoundsException异常。
5.修改字符串的函数
public synchronized StringBuffer reverse();
将该字符串缓冲区的字符序列用其反向字符序列来替换。
public synchronized void setCharAt(int index, char ch) throws StringIndexOutOfBoundsException;
将该字符串缓冲区指定位置的字符设置成ch。
public synchronized StringBuffer replace(int start, int end, String str) throws StringIndexOutOfBoundsException;
方法replace()完成用一个字符串取代StringBuffer中的部分字符串的功能。该方法把StringBuffer中从下标startposition处开始到下标endposition处结束的字符串替换为str,返回替换后的StringBuffer对象。
public StringBuffer delete(int start,int end) throws StringIndexOutOfBoundsException;
将当前字符串缓冲区中的字符串索引从start处开始到end-1处的字符串删除掉。如果start为负、大于length()或大于end则会抛出StringIndexOutOfBoundsException异常。
public StringBuffer deleteCharAt(int index) throws StringIndexOutOfBoundsException;
删除位于index外的字符。如果index为负或大于等于length()则会抛出StringIndexOutOfBoundsException异常。
【例2-7】使用StringBuffer
UseStringBuffer.jsp
<%@ page language="java" contentType="text/html; charset=gb2312"%> <html> <head> <title>使用StringBuffer类</title> </head> <body> <% //------构造函数的使用------ StringBuffer strBuf 1=new StringBuffer(); StringBuffer strBuf 2=new StringBuffer("中国系统分析员顾问团csai长沙秘书处"); //------求长度函数的使用------ out.print("strBuf 1的长度为:"+strBuf 1.length()+",容量为:"+strBuf 1.capacity()+"<br>"); out.print("strBuf 2的长度为:"+strBuf 2.length()+",容量为:"+strBuf 2.capacity()+"<br>"); //------加入内容函数的使用------ strBuf 1.append("中国顾问团"); strBuf 1.insert(2,"系统分析员"); out.print("strBuf 1:"+strBuf 1+"<br>"); //------查询函数的使用------ out.print("strBuf 2的索引位置2的字符为:"+strBuf 2.charAt(2)+"<br>"); out.print("strBuf 2的索引位置2到7的字符串为:"+strBuf 2.substring(2,7)+"<br>"); //------修改字符串函数的使用------ strBuf 1.reverse(); out.print("strBuf 1反转后的内容为:"+strBuf 1+"<br>"); strBuf 2.delete(10,14); out.print("在strBuf 2中删除索引位置从10到14的字符串:"+strBuf 2); %> </body> </html>
程序的运行结果如图2-14所示。
图2-14 使用StringBuffer
从使用构造函数的语句中可以看出,strBuf 1是使用的没有带参数的构造函数,因此初始化后字符串长度为0,但容量为16,strBuf 2使用了字符串作为参数创建了一个StringBuffer对象,但字符串长度和容量并不相同;查找函数时,注意字符中的索引编号是从0开始的,而且截止位置实际上是“截止索引号-1”处的字符。
以上就是Java Web开发时最为常用的包装类和字符串处理类,其他还有一些常用的类,如Properties、ArrayList、Enumeration等类,在本书的后续内容中用到时再作详细的介绍。
2.6 小结
本章介绍了C/S模式和B/S模式的基本原理,并作了对比分析;解说了Java编程的语法,并引领读者进入Java面向对象编程的殿堂;初步介绍了JDK类库以及其他作为Java Web开发人员最为常用的包装类和字符串处理类。
B/S模式已成为目前应用开发的主流方向,Java业已成为Web开发的最为常用的动态编程语言之一。JSP页面采用先编译后运行的方式,运行的效率较高。
标识符用来让编译器能够识别变量、类、方法、对象、接口、包等。Java数据类型可分为两大类——基本数据类型和抽象数据类型。Java中的运算符特别丰富,主要有算术运算符、关系运算符、布尔逻辑运算符、位运算符、赋值运算符、条件运算符、强制类型转换运算符、数组下标运算符、对象运算符等。程序注释可以增强程序的可读性和可维护性,主要用于描述程序的功能、逻辑、调用接口、参数说明等。Java语言控制语句主要有4类——条件选择语句、循环执行语句、转向语句、返回语句。
Java Web程序员重要需要理解类、对象、接口、包等这些最为基本的概念以及它们是如何运用的。JDK中带有许多现成的类、接口可供程序员调用,使用JDK类库中的类、接口,程序员能够快速、方便地开发出简单、实用的应用程序。