Java程序设计基础
Java程序设计基础
第1章 Java入门
1.1 Java概述
1.1.1 Java的起源
Java是由Sun Microsystems公司的工程师James Gosling和他的团队在1991年开发的,最初被称为Oak,后来改名为Java。它的设计目标是创建一种可以在不同平台上运行的独立的程序设计语言,同时具有高度的可移植性和安全性。Java首次公开发布是在1995年,随后迅速流行并成为一种广泛应用的编程语言。
1.1.2 Java的特点
- 面向对象:Java是一种面向对象的编程语言,支持封装、继承和多态等面向对象的特性。
- 平台无关性:Java程序在不同的操作系统上可以运行,只需要在不同的平台上安装相应的Java虚拟机(JVM)即可。
- 简单性:Java语法简单清晰,易于学习和使用,适合初学者入门。
- 安全性:Java具有内建的安全特性,可以防止未经授权的访问和恶意代码的执行。
- 多线程:Java具有强大的多线程支持,可以实现并发编程和多任务处理。
- 健壮性:Java具有强类型机制、异常处理、垃圾回收机制,可以自动管理内存,减少内存泄漏的风险。
- 强大的标准库:Java拥有丰富的标准类库和第三方库,可以快速开发各种类型的应用程序。
- 可移植性:Java程序可以在多个平台上运行,不受特定硬件和操作系统的限制。
- 高性能:Java虚拟机(JVM)优化执行速度和资源利用效率,提供高性能的运行环境。
1.1.3 开发Java的平台版本
- Java SE:标准版,定位在个人计算机上的应用,是Java平台的核心。
- JavaEE:企业版,定位在服务器端的应用。
- JavaME:微型版,定位在消费性电子产品的应用。
1.2 Java开发环境安装与配置
1.3 Java程序示例
1.4 Eclipse集成开发工具安装与配置
第2章 Java编程基础
2.1 基本语法
2.1.1 注释
1 | // //单行注释 |
2.1.2 标识符
在程序设计语言中存在的任何一个成分(如变量、常量、方法和类)都需要一个名字来表示它的存在和唯一性,这个名字就是标识符。用户可以为自己程序中的每一个成分取一个唯一的名字(标识符)。
标识符命名规则
1. Java的标识符可以由字母、数字、下划线和美元符号组成,但必须==以字母、下划线或美元符号开头==。
2. Java的标识符严格==区分大小写==,如age和AGE是不同的。
3. 标识符不能是Java的保留关键字,但可以包含关键字。
4. 注意:name、cha_1、$money、publicname都是合法的标识符,而a b、3_6、m%n、int都是不合法标识符。
标识符命名一些约定
1. 类名和接口名的第一个字母大写,如String、System、Applet、FirstByCMD等。
2. 方法名第一个字母小写,如main( )、print( )、println( )等。
3. 常量(用关键字final修饰的变量)全部用大写,单词之间用下划线隔开,如TEXT_CHANGED_PROPERTY。
4. 变量名或一个类的对象名等首字母小写。
5. 标识符的长度不限,但在实际命名时不宜过长,遵循“==见名知意==”的原则。
2.1.3 关键字
类型 | 举例 |
---|---|
访问控制 | private、protected、public |
类、接口、方法、变量和代码块修饰符 | abstract、class、extends、final、implements、interface、native、new、static、volatile、strictfp、synchronized、transient |
程序控制 | break、continue、return、do、while、if、else、for、switch、case、default、instanceof |
错误处理 | try、catch、throw、throws |
包相关 | import、package |
基本类型 | boolean、byte、char、 double、float、int、long、short、null、true、false |
2.1.4 常量
常量是程序运行过程中值不能改变的量。例如数字5、字符‘m’、布尔值True等。在Java中常量分为整型常量、浮点数常量、布尔常量、字符常量等,语法格式
1 | //final 常量类型 常量名=值; |
注意: 常量名一般使用大写字符。程序中使用常量可以提高代码的可维护性。
2.2 变量
2.2.1 变量的声明及初始化
1. Java变量是程序中最基本的存储单元,其要素包括变量名、变量类型和作用域。声明变量的语法格式:
1 | 变量类型 变量名=变量值; |
2. 使用变量前必须对变量赋值,首次对变量赋值称为初始化变量。
1 | 变量名=表达式; |
3. 变量名必须是已经声明过的,表达式由值、运算符、变量组成,表达式的最终运算结果是一个值。
1 | int i; |
4. 可以在声明变量的同时初始化变量。变量初始化后还可以对变量重新赋值,重新赋值后,新的值将会覆盖原来的值。
1 | int j=5; |
2.2.2 变量的数据类型
1. 基本数据类型
名称 | 关键字 | 占用字节数 | 取值范围 |
---|---|---|---|
字节型 | byte | 1 | -2^7~2^7-1 |
短整型 | short | 2 | -2^15~2^15-1 |
整型 | int | 4 | -2^31~2^31-1 |
长整型 | long | 8 | -2^63~2^63-1 |
单精度型 | float | 4 | -3.4x10^38~3.4x10^38 |
双精度型 | double | 8 | -1.7x10^308~1.7x10^308 |
字符类型 | char | 2 | 0~65535 |
布尔类型 | boolean | 1 | true或false |
(1) 默认情况下整数字面值是int型,如果要指定long型的整数字面值,必须在数值的后面加大写或小写字母L。
(2) 默认情况下,浮点型字面值是double型。如果要指定float型浮点数,必须在浮点数后面加后缀f或F。例如,0.1f、-3.14F。
2. 引用数据类型
在Java里面除去基本数据类型的其他类型都是引用数据类型,自己定义的class类都是引用类型,可以像基本类型一样使用,如下图today变量所示。
2.2.3 变量的类型转换
1. 自动类型转换
由低向高转换,自动转换;
例如:double a=5; (把整数5自动转换为更高精度的double类型5.0,赋值给变量a)
2. 强制类型转换
由高向低转换,需强制转换.实现强制类型转换的语法格式如下:
1 | (数据类型)数据 或 (数据类型)(表达式); |
在执行强制类型转换时,可能会导致数据溢出或精度降低。
2.2.4 变量的作用域
- 成员变量: 在类体内定义的变量称为成员变量,它的作用域是整个类,也就是说在这个类中都可以访问到定义的这个成员变量。(成员变量可以有修饰符,系统会给成员变量默认值。)
- 局部变量: 在一个方法或方法内的代码块中定义的变量称为局部变量,局部变量在方法或代码块被执行时创建,在方法或代码块结束时被销毁。局部变量在进行取值前必须被初始化,否则会编译错误。(局部变量不能有修饰符,没有默认值,必须由用户赋值。)
2.3 运算符
2.3.1 算术运算符
运算符 | 功能 | 举例 | 运算结果 | 结果类型 |
---|---|---|---|---|
+ | 加法运算 | 10+7.5 | 17.5 | double |
- | 减法运算 | 10-7.5F | 2.5F | float |
** | 乘法运算 | 3*7 | 21 | int |
/ | 除法运算 | 22/3L | 7L | long |
% | 求余运算 | 10%3 | 1 | int |
++ | 自加1运算 | int x=7,y=5; int z=(++x)====y; int z=(x++)====y; |
x=8,z=40; x=8,z=35; |
与操作数的类型相同 |
– | 自减1运算 | int x=7,y=5; int z=(–x)====y; int z=(x–)====y; |
x=6,z=30; x=6,z=35; |
与操作数的类型相同 |
==注意:==在使用进行自增自减时,注意它们与操作数的位置关系对表达式运算符结果的影响。++或–出现在变量前面时,先执行自增或自减运算,再执行其他运算。出现在变量后面时,先执行其他运算,再执行自增或自减运算。 |
2.3.2 赋值运算符
赋值运算符的符号为“=”,它的作用是将数据、变量、对象赋值给相应类型的变量。复合赋值运算符是在赋值运算符“=”前加上其他运算符。
运算符 | 功能 | 举例 | 运算结果 |
---|---|---|---|
+= | 加等于 | a=3;b=2;a+=b;即a=a+b; | a=5 b=2 |
-= | 减等于 | a=3;b=2;a-=b;即a=a-b; | a=1 b=2 |
*= | 乘等于 | a=3;b=2;a*=b;即a=a * b; | a=6 b=2 |
/= | 除等于 | a=3;b=2;a/=b;即a=a/b; | a=1 b=2 |
%= | 模等于 | a=3;b=2;a%=b;即a=a%b; | a=1 b=2 |
2.3.3 关系运算符
关系运算符用于比较大小,运算结果为boolean型,当关系表达式成立时,运算结果为true,否则运算结果为false。
注意:要注意关系运算符“ == ”和赋值运算符“=”的区别!
运算符 | 功能 | 举例 | 运算结果 |
---|---|---|---|
> | 大于 | ‘a’>’b’ | false |
< | 小于 | 2<3.0 | true |
== | 等于 | ‘X’== 88 | true |
!= | 不等于 | true !=true | false |
>= | 大于或等于 | 6.6>=8.8 | false |
<= | 小于过等于 | ‘M’<=88 | true |
2.3.4 逻辑运算符
逻辑运算符用于对boolean类型结果的表达式进行运算,运算结果总是boolean类型的。
运算符 | 描述 | 示例 | 结果 |
---|---|---|---|
& | 与 | false&true | flase |
| | 或 | false|rue | true |
^ | 异或 | false^true | true |
! | 非 | !true | false |
&& | 逻辑与 | false&&true | false |
|| | 逻辑或 | false||true | true |
2.3.5 位运算符
- 逻辑位运算符:逻辑位运算符用来对操作数进行按位运算,包括“~”(按位取反)、“&”(按位与)、“|”(按位或)和“^”(按位异或)。
- 移位运算符:移位运算符一般是相对于二进制数据而言的,包括“<<”(左移运算符,num<<1,相当于num乘以2)、“>>”(右移运算符,num>>1,相当于num除以2)和“>>>”(无符号右移,忽略符号位,空位都以0补齐)。
2.3.6 其他运算符
- 字符串连接运算符 “+”:语句“String s=”He” + “llo”;”的执行结果为”Hello”,“+”除了可用于字符串连接,还能将字符串与其他的数据类型相连成为一个新的字符串。
- 三目运算符 ?:三目运算符就是能操作三个数的运算符,如X ? Y : Z,X为boolean类型表达式,先计算X的值,若为true,整个三目运算的结果为表达式Y的值,否则整个运算结果为表达式Z的值。
2.3.7 运算符的优先级
当在一个表达式中存在多个运算符进行混合运算时,会根据运算符的优先级别来决定运算顺序,优先级最高的是括号“()”,它的使用与数学运算中的括号一样,只是用来指定括号内的表达式要优先处理。
优先级 | 运算符 | 结合性 |
---|---|---|
1 | ()[] | 从左到右 |
2 | !+(正)-(负)~++- | ==从右到左== |
3 | */% | 从左到右 |
4 | +(加)-(减) | 从左到右 |
5 | << >> >>> | 从左到右 |
6 | < <= > >= | 从左到右 |
7 | == != | 从左到右 |
8 | &(按位与) | 从左到右 |
9 | ^(按位或) | 从左到右 |
10 | | | 从左到右 |
11 | && | 从左到右 |
12 | || | 从左到右 |
13 | ?: | ==从右到左== |
14 | = += -= ==*=== /= %= &= |= ^= ~= <<= >>= >>>= | ==从右到左== |
2.4 流程控制
2.4.1 顺序结构
若是在程序中没有给出特别的执行目标,系统则默认自上而下一行一行地执行该程序。
1 | System.out.prtintln("A"); |
2.4.2 选择结构
选择结构也被称为分支结构。选择结构有特定的语法规则,代码要执行具体的逻辑运算进行判断,逻辑运算的结果有两个,所以产生选择,按照不同的选择执行不同的代码。
if语句
简单的if条件语句
对某种条件做出相应的处理。通常表现为“如果满足某种情况,那么就进行某种处理”,语法格式如下:
1 | if(表达式){ |
if-else条件语句
条件语句的最通用的形式。通常表现为“如果满足某种条件,就做某种处理,否则做另一种处理”,语法格式如下:
1 | if(条件表达式){ |
if…else if…else多分支语句。
用于针对某一事件的多种情况进行处理。通常表现为“如果满足某种条件,就进行某种处理,否则如果满足另一种条件才执行另一种处理”,语法格式如下:
1 | if(表达式1){ |
swich语句
一种简单明了的多选一语句,在Java语言中,可以用switch语句将动作组织起来进行多选一,语法格式如下:
1 | switch(表达式){ |
- switch语句中表达式的值必须是整型或字符型,即int、short、byte和char型。switch会根据表达式的值,执行符合常量表达式的语句序列。
- 在case后的各常量表达式的值不能相同,否则会出现错误。
- 在case后允许有多个语句,可以不用{ }括起来。当然也可作为复合语句用{ }括起来。
- 各case和default语句的前后顺序可以变动,而不会影响程序执行结果,但把default语句放在最后是一种良好的编程习惯。
- break语句用来在执行完一个case分支后,使程序跳出switch语句,即终止switch语句的执行。
2.4.3 循环结构
while语句
while的循环方式为利用一个条件来控制是否要继续反复执行这个语句,语法格式:
while语句在循环的每次迭代前检查表达式。如果条件是true,则执行循环,如果条件是false,则该循环体不执行。
1 | while(表达式){ |
do-while循环语句
do ··· while与while循环语句的区别是,while循环语句先判断条件是否成立再执行循环体,而do···while循环语句则先执行一次循环后,再判断条件是否成立。也即do···while至少执行一次
1 | do{ |
for循环语句
for循环是使用最广泛的一种循环,并且灵活多变,主要适用在已知循环次数的情况下,进行循环操作
1 | for(表达式1--初始化;表达式2--条件;表达式3--迭代) |
(1)for后的圆括号中通常含有3个表达式,各表达式之间用“;”隔开。
(2)初始化、条件以及迭代部分都可以为空语句,三者均为空时,相当于死循环。
2.4.4 跳转语句
break语句
- 可以终止循环或其他控制结构。它在for、while或do…while循环中,用于强行终止循环。只要执行到break语句,就会终止循环体的执行。break语句在switch多分支语句里也适用。
- break语句通常适用于在循环体中通过if判定退出循环条件,如果条件满足,程序还没有执行完循环时使用break语句强行退出循环体,执行循环体后面的语句。
- 如果是双重循环,而break语句处在内循环,那么在执行break语句后只能退出内循环,如果想要退出外循环,要使用带标记的break语句。
continue语句
应用在for、while和do…while等循环语句中,如果在某次循环体的执行中执行了continue语句,那么本次循环就中断结束,即不再执行本次循环中continue语句后面的语句,而进行下一次循环。
2.5 数组
2.5.1 一维数组
数组的声明和创建
- 数组的声明语法格式有两种:
1 | //数组元素类型 数组名[]或数组元素类型[] 数组名 |
- 声明数组变量后,并没有在内存中为数组分配存储空间。只有使用关键字new创建数组后,数组才拥有一片连续的内存单元,创建数组的格式:
1 | 变量名 = new 数据类型[长度]; |
- 变量名必须是已声明过的数组引用变量,长度指定了数组元素的个数。
1 | a=new int[5]; |
- 也可以在声明数组的同时创建数组。
1 | int[ ] a=new int[5]; |
数组的初始化
数组分配了内存空间后就可以使用这些空间放置数据了,第一次存放数据元素的过程称为初始化。
数组的初始化有两种,一种是在创建数组空间的同时给出各数组元素的初值。另一种是直接给数组的每个元素指定初始值,系统自动根据所给出的数据个数为数组分配相应的存储空间,这样可以省略空间的new运算符。
1 | int[] nums={1,2,3}; |
访问数组元素
在Java中,数组下标从0开始,数组元素个数length是数组类中唯一的数据成员变量。使用new关键字创建数组时系统自动给length赋值。数组一旦创建完毕,其大小就固定下来。程序运行时可以使用length进行边界检查。一般我们使用for 循环语句来遍历数组中的全部元素。
2.5.2 二维数组
- 二维数组的声明与一维数组相似,只是需要给出两对方括号,语法格式:
1 | 类型标识符 数组名[][]; |
- 在初始化二维数组时,可以只指定数组的行数而不给出数组的列数,每一行的长度由二维数组引用时决定,但不能只指定列数而不指定行数。不指定行数只指定列数是错误的。
1 | int table[][]; |
- 可以在声明数组的同时创建数组
1 | int table[][]=new int[2][3]; |
- 声明二维数组也可以同时赋初值
1 | int table[][]={{1,2,3},{4,5,6}}; |
2.6 方法
Java中的“方法”(Method)也可被称为“函数”(Function)。对于一些复杂的代码逻辑,如果希望重复使用这些代码,并且做到“随时任意使用”,那么就可以将这些代码放在一个大括号“{}”当中,并且起一个名字。使用代码的时候,直接找到名字调用即可。定义一个方法的语法格式如下:
1 | [修饰符] 返回值类型 方法名称(参数类型 参数名1,参数类型 参数名2,…){ |
- 访问修饰符:方法允许被访问的权限范围,可以是 public、protected、private 甚至可以省略,其中 public 表示该方法可以被其他任何代码调用,其他几种修饰符的使用在后面章节中会详细讲解。
- 返回值类型:方法返回值的类型,如果方法不返回任何值,则返回值类型指定为 void ;如果方法具有返回值,则需要指定返回值的类型,并且在方法体中使用 return 语句返回值。
- 方法名:定义的方法的名字,必须使用合法的标识符。
- 参数列表:传递给方法的参数列表,参数可以没有也可以有多个,多个参数间以逗号隔开,每个参数由参数类型和参数名组成,以空格隔开。
第3章 面向对象(上)
3.1 面向对象概述
3.1.1 面向对象过程与面向对象
所谓面向对象是指面向客观事物之间的关系。面向对象是一种思想,与之相对应的是面向过程,为了更好地理解面向对象,可以用面向过程来与之比较。
面向过程就是先分析出解决问题所需要的步骤,然后用方法把这些步骤依次实现,使用这些方法的时候再依次调用;面向对象是把构成问题的事物分解成各个对象,建立对象的目的不是完成某一个步骤,而是描述某个事物在整个解决问题的步骤中的行为。
例如完成一个五子棋程序的开发,面向过程的设计思路是首先分析解决问题所需要的步骤:开始游戏、黑子先走、绘制画面、判断输赢、轮到白子走、绘制画面、判断输赢、返回第二步,重复这个过程,直至游戏结果,输出最后的结果。面向过程的设计是把上面每个步骤用不同的方法来实现。
面向对象的设计则是用另外的思路来解决问题的。整个五子棋程序包括:黑白双方的玩家对象,这两方的行为是一模一样的;棋盘对象,负责绘制画面;规则系统,负责判定犯规、输赢等。第一类对象(玩家对象)负责接收用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,第二类对象接收到棋子布局的变化后要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。
可以明显地看出,面向对象是以功能来划分问题的,而不是步骤。同样是绘制棋局,这样的行为在面向过程的设计中被分散在了多个步骤中,很可能出现不同的绘制版本,因为通常设计人员会考虑到实际情况进行各种各样的简化。而在面向对象的设计中,绘图只可能在棋盘对象的相关调用中出现,
从而保证了绘图的统一。
3.1.2 面向对象特点
1.封装
对于面向对象而言,封装是将方法和属性一起包装到一个程序单元中。这些程序单元以类的形式实现。在面向对象中,当定义对象时,往往将相互关联的数据和功能绑定在一起。就像将药用胶囊中的化学药品包装起来一样,这种做法称为封装。封装的好处之一就是隐藏信息。
2.继承
在面向对象中,将已存在的类(称其为“父类”)的定义作为基础来建立新类的技术称为继承,建立新类时可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。继承是实现软件复用的重要手段。
3.多态
多态是指允许不同类的对象对同一消息做出响应。多态包括参数化多态和包含多态。支持多态的语言具有灵活、抽象、行为共享和代码共享的优势,很好地解决了应用程序方法同名的问题。多态也弥补了类单继承带来的功能不足的问题。
3.2 类与对象
面向对象的编程思想是力图使在程序中对事物的描述与该事物在现实中的形态保持一致。为了做到这一点,面向对象的思想中提出两个概念,即类和对象。其中,类是对某一类事物的抽象描述,而对象用于表示现实中该类事物的个体。类可以看成一类对象的模板或者图纸,对象可以看成该类的一个具体实例。
例如我们要创造一个人(张三),怎么创造呢?类就是图纸,规定了人的详细信息,然后根据图纸即可将人造出来。图纸(类)就是人(对象)的抽象,各式各样的人的固有属性和行为表达出的抽象概念就是人类,张三、李四这些具体的事物就是对象。
3.2.1 类的定义
类的定义也称为类的声明。类中含有两部分,分别是成员变量和成员方法。类定义的一般语法格式如下。
1 | [<访问修饰符>] class <类名> [extends <父类名>] [implements <接口名称> ] |
3.2.2 对象的创建与使用
1.声明对象
对象是类的实例,属于某个已经声明的类。因此,在声明对象之前,一定要先定义该对象的类。
声明对象的语法格式如下。
1 | 类名 对象名; |
在声明对象时,只是在栈内存中为其建立一个引用,并设置其初始值为 nul,表示不指向任何内存空间。因此,使用时还需要为对象分配内存。
2.为对象分配内存
为对象分配内存也称为实例化对象。在Java中使用关键字new来实例化对象。为对象分配内存的语法格式如下。
1 | 对象名= new构造方法名([参数列表]) |
3.对象的使用
对象创建后,如果要访问对象里的某个成员变量或方法,可以通过下面的语法格式来实现。
1 | 对象.成员变量 |
3.3 构造方法
由前面学到的知识可知,实例化一个对象后,如果要为这个对象中的属性赋值,则必须要直接访问对象的属性或调用setXxx()方法。如果需要在实例化对象的同时就为这个对象的属性赋值,可以通过构造方法来实现。
构造方法是类中的一种特殊的方法,它是产生对象时需要调用的方法。可以根据需要定义类的不同的构造方法,进行特定的初始化工作。构造方法有以下特点。
(1)方法名和类名一样。
(2)不能用static、final等修饰。
(3)没有返回值。
(4)在对象实例化的时候调用,用关键字new来实例化。
(5)如果一个类没有构造方法,系统会提供一个默认的构造方法。默认构造方法的参数列表和方法体均为空,所生成的对象的各属性的值也为0或空。
(6)如果类里定义了一个或多个构造方法,那么系统将不提供默认的构造方法,而使用类里定义的构造方法。
3.4 参数传递
参数传递是Java程序设计中方法调用的重要步骤,通常我们把传给方法的参数称为“实参”,方法的参数列表中列出的参数称为“形参”。
Java 中是“按值”传递实参的。当形参为基本数据类型时,调用方法时会将实参“值”复制给形参。方法返回时,形参的值并不会带回给实参,即在方法内对形参的任何修改都不会影响实参的值。
当形参为引用数据类型时,则调用方法时传递给形参的是一个地址,即实参指向对象的首地址。方法返回时,这个地址也不会被改变,但地址内保存的内容是可以改变的。因此,当从方法中退出时,所修改的对象内容可以保留下来。
3.5 方法重载
在一个类中,出现多个名称相同但参数个数或参数类型不同的方法,这种情况称为方法重载。Java在执行具有重载关系的方法时,将根据调用参数的个数和类型区分具体执行的是哪个方法。重载的方法之间并不一定必须有联系,但是为了提高程序的可读性,一般只重载功能相似的方法。
方法重载一般分为构造方法重载和成员方法重载。
3.6 this关键字
对象被创建后,JVM就会给这个对象分配一个引用自身的指针,这个指针的名字就是this。因此,我们可以把 this 看作一个变量,它的值是对当前对象的引用。this 只能在类的非静态方法中使用,静态方法和静态的代码块中绝对不能出现 this,并且this 只与特定的对象关联,而不与类关联,同一个类的不同对象有不同的this。this关键字主要有以下3种应用。
(1)通过this 调用本类中的成员变量。需要对类中的数据进行初始化时,可以通过this来赋值,而无须随便定义一个变量来赋值。this可以用来区分成员变量和局部变量(重名问题),使用this更有利于开发人员阅读与理解代码。
默认的语法格式是:
1 | this.成员变量 |
(2)通过this调用本类中的其他成员方法,其默认的语法格式如下。
1 | this.成员方法(参数列表) |
(3)通过 this 调用本类中的其他构造方法(调用时必须将相关语句放在构造方法的首行),其默认的语法格式如下。
1 | this(参数列表) |
3.7 static关键字
关键字static在Java类中用于声明静态变量和静态方法,被static关键字修饰的变量和方法不需要创建对象去调用,直接根据类名就可以访问。
static关键字主要有以下4个使用场景。
1.修饰成员变量
被static修饰的成员变量叫作静态成员变量,也叫作类变量,说明这个变量是属于某个类的,而不是属于对象的;没有被 static 修饰的成员变量叫作实例变量,说明这个变量是属于某个具体的对象的。
调用静态变量的语法格式如下。
1 | 类名称.静态成员变量//静态成员变量 |
2.修饰成员方法
static的另一个使用场景是修饰成员方法。相比于修饰成员属性,修饰成员方法对数据的存储并不会产生多大的变化,因为我们从之前的程序可以看出,方法本来就是存放在类的定义中的。static修饰成员方法最大的作用就是可以使用“类名.方法名”的方式操作方法,避免了先使用 new 创建对象的烦琐和资源消耗。我们可能会经常在帮助类中看到它的使用。
调用静态方法的语法格式如下。
1 | 类名称.静态方法()//静态方法 |
3.定义静态代码块
定义静态代码块通常来说是为了对静态变量进行一些初始化操作,定义静态代码块的语法格式如下。
1 | public class类名称{ |
4.静态导包
静态导包其实就是import static,用来导入某个类或者某个包中的静态方法或者静态变量。示例代码如下。
1 | import static java.lang.Math.PI; |
使用static关键字有以下几点说明。
(1)在静态方法里不能访问非静态的变量或者方法,也不能出现 this关键字。
(2)静态代码块只在类加载的时候执行一次,以后再也不执行了。
(3)static不能修饰局部变量。
(4)创建一个对象的时候,先初始化静态变量、执行静态代码块,再初始化属性,最后执行构造方法。
3.8 包
在 Java 语言中,为了对同一个项目中的多个类和接口进行分类和管理,防止命名冲突,以及将若干相关的类组合成较大的程序单元,会把这些类放在同一个文件夹下进行管理,此文件夹称为包。
定义包的语法格式如下。
1 | package pkg1[.pkg2[.pkg3…]]; |
这条语句必须放在源文件的开头,并且语句前面无空格。包名一般全部用小写字母。Java中对包的管理类似于操作系统中对文件系统目录的管理。在包语句中用圆点(.)实现包之间的嵌套,表明包的层次。编译后的.class文件必须放在与包层次相对应的目录中。
由于包创建了新的名字空间(Namespace),所以不会跟其他包中的任何名字产生命名冲突。使用包这种机制,更容易实现访问控制,并且让定位相关类变得更加简单。
3.9 import 语句
当一个 Java 类需要另一个类的对象作为自己的成员或方法中的局部变量时,如果这两个类在同一个包下(同文件夹下)当然没问题,但是如果不在同一个包下,就需要用imp0rt语句来导入其他包中的类。
在一个Java源文件中可以有多条import语句,它们必须写在package语句(假如有package语句的话)和源文件中类的定义之间。
import语句主要有以下两个使用场景。
1.引入Java提供的类
在编写源文件的时候,除了自己编写的类外,经常需要使用 Java 提供的许多类,这些类可能在不同的包中。为了能够使用Java提供给我们的类,可以使用import语句引入包中的类。
Java为我们提供了大约130多个包,其中比较常见的包有以下几个。
- java.lang包中包含所有的基本语言类。
- javax.swing包中包含抽象窗口工具集中的图形、文本、窗口GUI类。
- java.io包中包含所有的输入输出类。
- java.util包中包含实用类。
- java.sql包中包含操作数据库的类。
- java.nex包中包含所有实现网络功能的类。
- java.applet包中包含所有实现Javaapplet的类。
如果要引入一个包中的全部类,则可以用通配符( * )来代替,示例代码如下。
1 | import java.util.*; |
上述代码表示引入java.util包中所有的类。而如果要引入包中的某个类,示例代码如下。
1 | import java.util.Date; |
上述代码表示只是引入java.util包中的Date类。
2.引入自定义包中的类
用户也可以使用import语句引入非类库中的有包名的类,示例代码如下。
1 | import p2.otherPackage;//导入p2包下的otherPackage类 |
3.10 访问权限
根据类的封装性,设计者既要为类提供与其他类或者对象联系的方法,又要尽可能地隐藏类中的实现细节。为了实现类的封装性,设计者要为类及类中成员变量和成员方法分别设置必要的访问权限,使所有子类、同一包中的类、本类等不同关系的类具有不同的访问权限。
访问修饰符是一组限定类、属性或方法是否可以被程序里的其他部分访问和调用的修饰符,这些修饰符提供了不同级别的访问权限。访问修饰符有public、protected和private,它们都是Java的关键字,其权限如表3-1所示。
修饰符 | 包外 | 子类 | 包内 | 类内 |
---|---|---|---|---|
public | yes | yes | yes | yes |
protected | no | yes | yes | yes |
private | no | no | no | yes |
无修饰符 | no | no | yes | yes |
第4章 面向对象(下)
4.1 类的继承
4.1.1继承的概念
继承是类的三大特性之一,是Java中实现代码重用的重要手段之一。
继承是使用已存在的类的定义作为基础建立新类的技术,它允许创建分等级层次的类。被继承的类称为超类或父类(superclass),继承超类或父类产生的新类称为子类或派生类(subclass)。
Java中只支持单继承,即每个类只能有一个父类。继承表达的是is a的关系,或者说一种特殊和一般的关系。例如DOG is a Pet。同样,我们可以让学生继承人、让苹果继承水果、让三角形继承几何图形。
类的定义中,通过关键字extends实现继承,格式如下。
1 | class 子类名 extends 超类名 |
注意:子类继承了其父类非私有的成员变量和成员方法。
4.1.2成员变量的隐藏
在定义子类时,我们仍然可以声明成员变量,一种特殊的情况是:子类中的成员变量如果和父类中的成员变量同名,那么即使他们类型不一样,只要名字一样。父类中的成员变量就会被隐藏。那么在子类中,父类的成员变量就不能被简单的引用来访问。
4.1.3方法重写
在Java中,子类可继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想进行一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖。方法覆盖要满足以下条件。
(1)发生在父类与子类之间。
(2)方法名字相同。
(3)参数列表相同。
(4)子类方法返回值必须是父类方法返回值类型的子类或它本身。
(5)子类方法的访问控制权限必须跟父类一样或者比父类更广。
4.1.4super关键字
super关键字和前面所学的this关键字很像,this关键字指向的是当前对象的调用,super关键字指向的是当前对象的父类。
super关键字主要有两种用途:调用父类的构造方法和调用父类被隐藏的成员变量或被覆盖的成员方法。
- 调用父类的构造方法
子类必须调用父类的构造方法,在子类的构造方法中使用super关键字来调用,其语法格式如下。
1 | super.([参数列表]); |
如果父类的构造方法中包括参数,则参数列表为必选项,用于指定父类构造方法的入口参数。
JAVA规定super([参数列表])方法只能出现在子类构造方法第一行。因为子类的实例化依赖于父类的实例化,在构建子类时,必须要有父类实例,只能有了父类的实例,子类才能够初始化自己。就好像人类世界里,都是要先有父亲,再有孩子一样。
2. 调用父类被隐藏的成员变量或被覆盖的成员方法
如果想在子类中调用父类中被隐藏的成员变量和被覆盖的成员方法,也可以使用super关键字,其语法格式如下。
1 | super.成员变量名 |
- super 关键字和this 关键字的区别
(1)所用实例不同
(2)调用构造不同
(3)访问的成员不同
(4)使用方式不同
(5)从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。
4.2 final 关键字
在Java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量);
final的含义是最终;
final变量是常量,不能改变;
final方法不能重写;
final类不能被继承;
目的就是不让他们发生改变
- final修饰类
该类成为最终类,无法被继承。例如,public final class Math extends Object(数学类,最终类)。 - final修饰方法
这个方法将成为最终方法,无法被子类重写。但是,该方法仍然可以被继承。 - final修饰变量
(1)如果修饰的是基本类型,说明这个变量的所代表数值永不能变(不能重新赋值)
(2)如果修饰的是引用类型,该变量的引用不能变,但引用所代表的对象内容是可变的。
==注意:==
(1)final不能修饰抽象类。因为抽象类需要被继承才有作用,而final修饰的类不能被继承。
(2)final不能用来修饰构造器。因为构造方法既不能被继承,也不能被重写,用final修饰毫无意义。
(3)final修饰的成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。
(4)final修饰的局部变量必须声明时赋值,如果不赋值,虽然声明是不会出错,但调用时会编译出错。
4.3 抽象类
抽象类是对问题领域进行分析后得出的抽象概念,是对一批看上去不同,但是本质上相同的具体概念的抽象。
例如:定义一个动物类Animal,该类提供一个行动方法action(),但不同的动物行动方式是不一样的,马儿是跑,鸟儿是飞,此时就可以将Animal定义成抽象类,该类既能包含action()方法,又无须提供其方法实现(没有方法体)。这种只有方法声明,没有方法实现的方法称为“抽象方法”。
抽象类是为了给子类提供概念模板,而本身不能实例化对象。
像 Animal类这种定义了方法但没有定义具体实现的类称为抽象类。在Java中可以通过关键字abstract把一个类定义为抽象类。
其语法格式如下。
1 | abstract class 类名{ |
每一个未被定义具体实现的方法也标记为abstract,这样的方法称为抽象方法。
其语法格式如下。
1 | public abstract 返回值类型 方法名(参数); |
(1)abstract修饰类时,则该类成为一个抽象类。抽象类不可生成对象(但可以有构造方法留给子类使用),必须被继承使用。
(2)abstract永远不会和private、static、final同时出现。
(3)一个类有抽象方法,它就必须声明为抽象类。
(4)一个子类继承一个抽象类,必须实现该抽象类里所有抽象的方法,不然就要把自己声明为抽象类。
(5)一个类里没有任何抽象方法,它也可以声明为抽象类。
4.4 接口
接口是体现抽象类功能的另一种方式,可将其想象为一个“纯”抽象类。接口里面全部是由全局常量和公共的抽象方法所组成。接口是解决Java无法使用多继承的一种手段,但是接口在实际中更多的作用是制定标准的。
4.4.1接口的定义
使用关键字 interface来定义一个接口。接口的定义和类的定义很相似,分为接口声明和接口体,其语法格式如下。
1 | [修饰符] interface 接口名 [extends 父接口名列表] |
注意:接口的命名规则和类一样,公共接口的文件名必须与接口名相同。
4.4.2接口的实现
接口在定义后,就可以定义类来实现接口。在类中实现接口可以使用关键字implements,其基本格式如下:
1 | [修饰符] class <类名> [extends 父类名] |
接口的特点
(1)接口中的方法会被隐式地指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
(2)接口中可以含有变量,但是接口中的变量会被隐式地指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
(3)类与接口是一种实现的关系,关键字是implements。
(4)一个类可以实现多个接口,必须覆盖实现接口中所有的方法。不然,该类就必须声明为抽象的。
(5)一个类可以同时继承一个类和实现多个接口。
(6)不能用接口来创建对象,接口没有构造方法。
(7)接口也可以继承另一个接口,使用extends关键字。
(8)接口生成的也是.class文件。
4.4.3抽象类和接口的区别
抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
一个类只能继承一个抽象类,而一个类却可以实现多个接口。
在接口体里一般只包含静态常量、抽象方法、内部类、内部接口等定义,但从Java 8版本开始允许接口中定义默认方法、类方法;默认方法:方法头前有Default修饰。类方法:方法头前有static修饰
4.5 多态
多态性是Java的一个重要特征。
多态是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
简单来说,即同一个变量,同一个方法,执行出不同的结果。
4.5.1 上转型对象
上转型对象语法格式如下:
1 | 父类名称 对象名称 = new 子类名称() |
4.6 内部类
类的内部还有类。
在类中定义另一个类,这样的类称作内部类,而包含内部类的类称为内部类的外嵌类。内部类存在的意义在于可以自由地访问外部类的任何成员(包括私有成员),使用内部类可以使程序更加的简洁(以牺牲程序的可读性为代价),便于命名规范和划分层次结构。
注意:
(1)内部类在外部使用时,无法直接实例化,需要借由外部类信息才能完成实例化。
(2)内部类的访问修饰符,可以任意,但是访问范围会受到影响。
(3)内部类可以直接访问外部类的成员;如果出现同名属性或者方法,优先访问内部类中定义的。
4.6.1 成员内部类
成员内部类和成员变量一样,属于类的全局成员。
只有创建了成员内部类的实例,才能使用成员内部类的变量和方法。
4.6.2 局部内部类
局部内部类和局部变量一样,都是在方法内定义的,其有效范围只在方法内部有效。
局部内部类被定义在方法中;
局部内部类的作作用域仅在方法内。
4.6.3 静态内部类
静态内部类和静态变量类似,它都使用static关键字修饰。所以,在学习静态内部类之前,必须熟悉静态变量的使用方法。
静态内部类Apple可以在不创建外部类对象的情况下直接使用。
4.6.4 匿名内部类
匿名类就是没有名称的内部类,它经常被应用于Swing程序设计中的事件监听处理之中。
匿名类经常用来创建接口的唯一实现类,或者创建某个类的唯一子类,使代码更简洁。
第5章 异常处理
5.1 何谓异常
Java程序在运行过程中会遇到各种情况,例如文件读取失败、磁盘不足、网络连接断开、停电、数组越界等。
Java语言提供了完善的异常处理机制。正确运用这套机制,有助于提高程序的健壮性。
所谓程序的健壮性,就是指程序在多数情况下能够正常运行,返回预期的正确结果;如果偶尔遇到异常情况,程序也能采取周到的解决措施。
而不健壮的程序则没有事先充分预计到可能出现的异常,或者没有提供强有力的异常解决措施,导致程序在运行时,经常莫名其妙地终止,或者返回错误的运行结果,而且难以检测出现异常的原因。
5.1.1 异常的概述
Java程序在运行过程中会遇到各种异常情况,如文件读取失败、磁盘不足、网络断开、停电、数组越界等。如果预先就估计到了可能出现的异常,并且准备好了处理异常的措施,那么就会降低突发性异常发生时造成的损失。
1 | public class Demo { |
如上,我们定义了一个简单的int值,在程序上看,这样的写法是正确的,但是从算数角度上看,这样的值是不存在的,是错误的。
但是在Java编程过程中,这样的写法并不会报错,这种时候就需要我们使用Java的强大的异常处理功能,以此保证我们的程序在运行过程中不会因为这样的错误而导致程序崩溃。
5.1.2 异常处理过程
Java的异常处理机制是采用面向对象的方式来处理的:
(1)抛出异常:在执行一个方法时,如果发生异常,则这个方法生成代表该异常的一个对象,停止当前执行路径,并把异常对象提交给JRE。
(2)捕获异常:JRE得到该异常后,寻找相应的代码来处理该异常。JRE在方法的调用栈中查找,从生成异常的方法开始回溯,直到找到相应的异常处理代码为止。
5.2 异常类型
在 Java 中所有异常类型都是内置类Java.lang.Throwable 类的子类,即 Throwable 位于异常类层次结构的顶层。Throwable 类下有两个异常分支 Exception 类和 Error类。
5.2.1 Error类
Error类定义了在通常环境下不希望被程序捕获的异常。Error 类型的异常用于 Java 运行时由系统显示与运行时系统本身有关的错误。堆栈溢出是这种错误的一例。本章不讨论关于 Error 类型的异常处理,因为它们通常是灾难性的致命错误,不是程序可以控制的。
5.2.2 Exception类
Exception类用于用户程序可能出现的异常情况,它也是用来创建自定义异常类型类的父类。Exception类又分为运行时异常和非运行时异常,这两种异常有很大的区别,也称为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。
RuntimeException是发生在程序运行期,预先不可预见的发生。编译器未要求一定要进行捕获。
如果运行期没有处理,RuntimeException会一直往上层抛。最后由JVM来处理,JVM会打印堆栈信息然后结束应用。
对于可能发生的RuntimeException,建议根据堆栈信息,检查代码是否有误并进行更改,如果情况复杂无法全部解决,可以对RuntimeException进行捕获并进行业务恢复处理。
IOException:是Exception的子类。
I/O:inputstream/outputstream。
常常出现的场景:一般在读写数据的时候会出现这种问题。
Java内部读写数据实现逻辑:Java内部数据的传输都是通过流,或者byte来进行传递的。
一个文本文件。你可以通过in流写入到Java中,同时也可以通过out流从Java(计算机内存中)返还给具体的文件。
故: IOException是输入或输出异常(即写读异常)。
5.3 异常处理语句
Java语言中的异常处理包括声明异常、抛出异常、捕获异常和处理异常4个环节 ,通过5个关键字try、catch、throw、throws、finally进行管理。
5.3.1 try…catch…finally语句
Java通过try-catch-finally语句可以实现捕获和处理异常,它的语法一般格式如下:
1 | try{ |
有关try-catch-finally语句的说明
(1)如果try中没有出现异常,try语句块将执行结束,然后跳过catch语句,执行try/catch后面的语句。
(2)如果try中产生了异常并被catch捕获,将跳过try中的其余语句,把异常对象赋给catch中的变量,然后执行catch中的语句,最后执行try/catch后面的语句。
(3)每个try块后可以伴随一个或多个catch块。当try中抛出异常后,按顺序检查catch中的异常类,当异常类与抛出的异常对象匹配时,就把异常对象赋给catch中的变量,并执行这个catch语句块。
(4)如果try中产生了异常但没有被catch捕获,将终止程序执行,由虚拟机捕获并处理异常。
(5)每次抛出异常后只有一个catch语句能执行。
(6)finally语句块是不管有没有出现异常都要执行的内容。
(7)超类的catch能捕获子类异常,超类catch语句必须放在子类catch语句的后面。
5.3.2 throws语句
若某个方法内可能会发生异常,但不想在当前方法中来处理这个异常,那么可以将该异常抛出,然后在调用该方法的代码中捕获该异常并进行处理。
注意:将异常抛出,可通过throws关键字来实现。throws关键字通常被应用在声明方法时,用来指定方法可能抛出的异常,多个异常可用逗号分隔。
一般格式如下:
1 | public void test() throws IOException |
一旦方法声明了抛出异常,throws关键字后异常列表中的所有异常要求调用该方法的程序对这些异常进行处理(通过try—catch—finally等)。
如果方法没有声明抛出异常,仍有可能会抛出异常,但这些异常不要求调用程序进行特别处理。
5.3.3 throw语句
使用throw关键字也可抛出异常,与throws不同的是,throw用于方法体内,并且抛出一个异常类对象,而throws用在方法声明中来指明方法可能抛出的多个异常类。
在方法体内通过throw抛出异常后,如果想由上一级代码来捕获并处理异常,则同样需要在抛出异常的方法中使用throws关键字,在方法的声明中指明要抛出的异常。
如果想在当前的方法中捕获并处理throw抛出的异常,则必须使用try…catch语句。
一般格式如下:
1 | throw new ThrowedException |
5.4 自定义异常
5.4.1 创建自定义异常
自定义异常类大体可分为以下几个步骤:
(1)创建自定义异常类。
(2)在方法中通过throw抛出异常对象。
(3)若在当前抛出异常的方法中处理异常,可使用try…catch语句捕获并处理;否则,在方法的声明处通过throws指明要抛出给方法调用者的异常,继续进行下一步操作。
(4)在出现异常的方法调用代码中捕获并处理异常。
5.4.2 自定义异常使用
自定义的异常类必须继承自Throwable类,才能被视为异常类,通常是继承Throwable的子类Exception或Exception类的子孙类。除此之外,与创建一个普通类的语法相同。自己创建一个API中没有的异常。
语法:
1 | class 自定义异常类 extends 已有的异常类 { |
第6章 Java API
6.1 Java API入门
API(Application Programming Interface),即应用程序编程接口,是一些预先定义的函数, 目的是提供应用程序与开发人员基于某软件或硬件的以访问一组例程的能力,又无须访问源码或理解内部工作机制的细节。Java API就是指JDK提供的类库。
6.2 字符串相关类(String类和StringBuffer类)
6.2.1 String类
String类是不可变字符串类,因此用于存放字符串常量。一个String字符串一旦创建,其长度和内容就不能再被更改了。每一个String字符串对象创建的时候,就需要指定字符串的内容。
String类是final的,不可被继承。
String类的本质是字符数组char[],并且其值不可改变。
Java运行时会维护一个String池,String池用来存放运行时产生的各种字符串,并且池中的字符串的内容不重复。而一般对象并不存在这个缓冲池,所创建的对象也仅仅存在于方法的堆栈区。
String创建的方式
- String str =“123”;
- 使用这种方式来创建一个字符串对象str时,Java运行时(运行中JVM)会拿着这个str在String池中找是否存在内容相同的字符串对象,如果不存在,则在池中创建一个字符串str,否则,不在池中添加。
- String str = new String(“123”);
- 产生两个对象使用包含变量的表达式来创建String对象,则不仅会检查维护String池,还会在堆栈区创建一个String对象。
String对象可以通过“+”串联。串联后会生成新的字符串。例如,String str= “hello” + “world!”;
String的比较有两种方式:用“= =”比较的是内存地址,用equals方法比较的是对象内容。
String类有以下一些常用方法。 - public charAt(int index):返回字符串中第index个字符。
- public int length( ):返回字符串的长度。
- public int indexOf(String str):返回字符串中出现str的第一个位置。
- public String replace(char oldChar,char newChar):在字符串中用newChar字符替换oldChar字符。
- public boolean equalsIgnoreCase(String another):比较字符串与another是否一样(忽略大小写)。 - public int indexOf(String str,int fromIndex):返回字符串中从fromIndex开始出现str的第一个位置。
- public boolean startsWith(String prefix):判断字符串是否以prefix字符串开头。
- public boolean endsWith(String suffix):判断字符串是否以prefix字符串结尾。
- public String toUpperCase( ):返回一个字符串的大写形式。
- public String toLowerCase( ):返回一个字符串的小写形式。
- public String substring(int beginIndex):返回该字符串从beginIndex开始到结尾的子字符串。
- public String substring(int beginIndex,int endIndex):返回该字符串从beginIndex开始到endIndex结尾的子字符串。
- public String trim( ):返回将该字符串去掉开头和结尾空格后的字符串。
public static String valueOf(…):可以将基本类型数据转换为字符串。
public String[] split(String regex):可以将一个字符串按照指定的分割符分割,返回分割后的字符串数组。
6.2.2 StringBuffer类
StringBuffer类用于内容可以改变的字符串,可以将其他各种类型的数据增加、插入到字符串中,也可以转置字符串中原来的内容。一旦通过StringBuffer生成了最终想要的字符串,就应该使用StringBuffer.toString( )方法将其转换成String类,随后,就可以使用String类的各种方法操纵这个字符串了。
StringBuffer类常用的方法。
- 增加:append、insert。
- 修改:reverse、setCharAt、replace。
- 删除:delete、deleteCharAt。
- 查询:indexOf、charAt、getChars、substring。
6.3 基本数据类型包装类
6.3.1 基本数据类型包装类
Java是一种面向对象语言,Java中的类将方法与数据连接在一起,并构成了自包含式的处理单元。但在Java中不能自定义基本类型对象,为了能将基本类型视为对象来处理,并能连接相关方法,Java为每个基本类型提供了包装类,这样便可以把这些基本类型转换为对象来处理了。
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
6.3.2 包装类常用的方法与变量
数值类型的包装类都继承抽象类Number,并继承了Number定义的返回不同类型数值的方法。以下是包装类的常用方法与变量。
public static final int MAX_VALUE:最大的int值。
public static final int MIN_VALUE:最小的int值。
public long longValue( ):返回封装数据的long型值。
public double doubleValue( ):返回封装数据的double型值。
public int intValue( ):返回封装数据的int型值。
public static int parseInt (Strings) throws NumberFormatException:将字符串解析成int型数据,返回该数据。
6.4 Math类
Math类提供了一系列方法用于科学计算,其方法的参数和返回值类型一般为double型。Math类所有的成员都有static修饰符,因此可以用类名直接调用。Math有两个域变量,一个表示自然对数的底数E,另一个表示圆周率PI。
Math类的常用方法
- static double abs(double a):返回绝对值,重载方法,参数可以是int、long、float。
- static double sin(double a):返回正弦。
- static double asin(double a):返回反正弦。
- static double sqrt(double a):返回平方根。
- static double pow(double a,double b):返回a的b次幂。
- static double max(double a,double b):返回最大值。
- static double min(double a,double b):返回最小值。
- static double random( ):返回0.0到1.0的随机数。
- static long round(double a):double型的数据a转换为long型(四舍五入)。
6.5 日期和时间相关类
Java 的日期和时间类位于 java.util 包中。利用日期时间类提供的方法,可以获取当前的日期和时间,创建日期和时间参数,计算和比较时间。Java语言提供了三个类来处理日期和时间。
- java.util.Date:包装了一个long类型数据,表示与GMT(格林尼治标准时间)的1970年1月1日00:00:00这一时刻所相距的毫秒数。
- java.text.DateFormat:对日期进行格式。
- java.util.Calendar:可以灵活地设置或读取日期中的年、月、日、时、分和秒等信息。
6.5.1 Date类
Date类在java.util包中。以毫秒数来表示特定的时间和日期。使用Date类的无参数构造方法创建的对象可以获取本地当前时间。Date对象表示时间的默认顺序是:星期、月、日、小时、分、秒、年。例如,Sat Apr 28 21:59:38 CST 2001。
计算机时间的“公元”设置在1970年1月1日0时(格林尼治时间),据此可以使用Date带参数的构造方法:Date(long time)。
6.5.2 SimpleDateFormat类
DataFormat的子类SimpleDateFormat可以实现日期的格式化。SimpleDateFormat有一个常用构造方法:public SimpleDateFormat(String pattern),该构造方法可以用参数pattern指定的格式创建一个对象。
pattern中应当含有一些特殊意义字符,这些特殊的字符被称作元字符,如下所示
- y或yy:表示用2位数字输出年份;yyyy表示用4为数字输出年份。
- M或MM:表示用2位数字或文本输出月份,如果想用汉字输出月份,pattern中应连续包含至少3个M,例如,MMM。
- d或dd:表示用2位数字输出日。
- H或HH:表示用2位数字输出小时。
- m或mm:表示用2位数字输出分。
- s或ss:表示用2位数字输出秒。
- E:表示用字符串输出星期。
6.5.3 Calendar类
Calendar类在java.util包中。使用Calendar类的static方法getInstance( )可以初始化一个日历对象。
Calendar calendar= calendar.getInstance( );
Calendar类的常用方法
- set(int year,int month,int date)。
- set(int year,int month,int date,int hour,int minute)。
- set(int year,int month,int date,int hour,int minute,int second):将日历翻到任何一个时间,当参数year取负数时表示公元前。
- public int get(int field):可以获取有关年份、月份、小时、星期等信息,参数field的有效值由Calendar的静态常量指定,例如,calendar.get(Calendar.MONTH);返回一个整数,如果该整数是0表示当前日历是在一月,该整数是1表示当前日历是在二月等。 - public long getTimeInMillis( ):可以将时间表示为毫秒。
6.6 数字类型处理相关类
在我们的日常开发过程中,我们会经常性的使用到数字类型的数据,同时,也会有众多的对数字处理的需求,针对这个方面的问题,在Java语言中提供解决方法的类就是数字处理类。Java语言提供了以下类来处理数字类型。
6.6.1 NumberFormat类
对数字结果格式化
NumberFormat类有如下常用方法
- public static final NumberFormat getInstance( ):实例化一个NumberFormat对象。
- public void setMinimumFractionDigits(int newValue):设置某个数的小数部分中所允许的最小数字位数。
- public final String format(double numer):格式化数字numer。
- public void
- setMaximumIntegerDigits(int newValue):设置某个数字的整数 部分中所允许的最大数字位数。
- public void setMaximumFractionDigits(int newValue):设置某数的小数部分中所允许的最大数字位数。
- public void setMinimumIntegerDigits(int newValue):设置某个数字的整数部分中所允许的最小数字位数。
6.6.2 BigDecimal类
用来处理大十进制数
如果对计算的结果精确度要求比较严格,就不能直接用float、double进行计算,要使用BigDecimal来计算。
6.7 Random类
java.util.Random类提供了一系列用于产生随机数的方法。有两种产生随机数的方法:
第一种Math类的random( )方法虽然也能产生随机数,但是它只能产生0.0~1.0随机数;
第二种Random类则可以十分方便地产生自己需要的各种形式的随机数。
Random类常用方法如下
- Random( ):创建一个新的随机数生成器。
- next(int bits):生成下一个伪随机数。
- nextInt( ):返回下一个伪随机数,它是此随机数生成器的序列中均匀分布的int值。
- nextLong( ):返回下一个伪随机数,它是从此随机数生成器的序列中取出的、均匀分布的long值。
- setSeed(long seed):使用单个long种子设置此随机数生成器的种子。
6.8 Scanner类
Java语言中的Scanner类是java.util包中的一个类。常用于控制台的输入,当需要使用控制台输入时即可调用这个类。
Scanner类有以下常用方法 。
- hasNextXxx( ):判断是否还有下一个输入项,其实Xxx可以是Int,Double等。如果需要判断是否包含下一个字符串,则可以省略Xxx。
- nextXxx( ):获取下一个输入项。Xxx的含义和上一个方法中的Xxx相同。
- nextLine( ):扫描器执行当前行,并返回输入回车之前的所有字符。
第7章 集合框架
7.1 集合框架入门
7.1.1 集合简介
集合框架是为了表示和操作集合而规定的一种统一标准的体系结构,是Java中重要内容之一。无论是最基本的JavaSE应用程序开发,还是企业级的Java EE程序开发,集合都是开发过程中常用的部分。
集合是用来存储和管理其他对象的对象,即对象的容器。
集合不是数组的一类,和数组有着本质的区别。
集合与数组的区别
(1)集合可以扩容,长度可变,可以存储多种类型的数据。数组长度不可变,只能存储单一类型的元素。
(2)集合可以说是一个特殊的数组,数组是只可以进行修改数据和查询数据,但是无法进行增加元素和删除元素。而集合可以进行增、删、改、查的动作。
(3)数组元素既可以是基本类型的值,也可以是对象,而集合里只能保存对象。
7.1.2 集合分类
Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是List和Set。
Map:双列集合类的根接口,用于存储具有键(Key)、值(Value)映射关系的元素。 因此,给定一个键和其相对应的值,就可以把键存放在Map对象中,并且可以通过键来检索。
图中有8个集合接口(短虚线表示),表示不同集合类型,是集合框架的基础。8个实现类(实线表示),是对集合接口的具体实现。
Collection与Map的区别?
Collection和Map的区别在于容器中每个位置保存的元素个数。Collection每个位置只能保存一个元素(对象)。例如List,它以特定的顺序保存一组元素,元素可重复;Set则是元素不能重复的无序集合。Map保存的是“键值对”,就像一个小型数据库。我们可以通过“键”找到该键对应的“值”。
注意:Map接口不是collection的子接口。
7.2 Collection接口
Collection接口是List接口和Set接口的父接口,通常情况下不被直接使用,不过Collection接口定义了一些通用的方法,通过这些方法可以实现对集合的基本操作,因为List接口和Set接口实现了Collection接口,所以这些方法对List集合和Set集合是通用的。
注意:Collection接口通过调用被定义的方法实现对集合的基本操作。
常用方法
- boolean add(E e):添加一个元素。
- boolean addAll(Collection c):添加一个集合中的所有元素。
- void clear( ):清空、删除所有。
- boolean remove(Object o):删除一个元素。
- boolean removeAll(Collection c):删除同属于某一集合中的所有元素。
- boolean contains(Object o):判断是否包含指定元素。
- boolean containsAll(Collection c):判断是否同时包含另一集合中的所有元素。
- boolean isEmpty( ):判断该集合是否为空。
- int size( ):获取元素个数。
- boolean retainAll(Collection c):取两个集合中的共有元素即交集。
- Iterator< E > iterator( ):迭代器。
- Object[] toArray( ) 或T[] toArray(T a):将集合变成数组。
Collection仅仅只是一个接口,我们真正使用的时候,却是使用该接口下的某个实现类。作为集合的接口,它定义了所有属于该集合的类都应该具有的一些方法。ArrayList(列表)类就是Collection集合的一种实现类。
7.3 Iterator接口(迭代器)
迭代器(Iterator)本身就是一个对象,它的作用就是遍历并选择集合序列中的对象,而客户端的程序员不必知道或关心该序列底层的结构。此外,迭代器通常被称为“轻量级”对象,创建它的代价小。但是它也有一些限制,例如某些迭代器只能单向移动。通过调用Collection. iterator( )方法即可获得该集合的迭代器。
注意:Iterator接口通过调用被定义的方法实现对集合的基本操作。
常用方法
(1)next( ):获得集合序列中的下一个元素。
(2)hasNext( ):检查序列中是否有元素。
(3)remove( ):将迭代器新返回的元素删除。
7.4 List接口
7.4.1 概述
List就是列表的意思,它继承了Collection接口,定义了一个允许重复项的有序集合。该接口不但能够对列表的一部分进行处理,还添加了面向位置的操作。List是按对象的进入顺序进行保存对象的,而不做排序或编辑操作。它除了拥有Collection接口的所有方法外还拥有一些其他方法。
Collection接口的方法List接口可以随意调用,而List接口的独有方法Collection不可以调用。
实现List接口的类有ArrayList类和LinkedList类。
常用方法
- void add(int index,Object element):在指定位置index上添加元素element。
- boolean addAll(int index,Collection c):将集合c的所有元素添加到指定位置index。
- Object get(int index):返回List中指定位置的元素。
- int indexOf(Object o):返回第一个出现元素o的位置,否则返回-1。
- int lastIndexOf(Object o):返回最后一个出现元素o的位置,否则返回-1。
- Object remove(int index):删除指定位置上的元素
- Object set(int index,Object element):用元素element取代位置index上的元素,并且返回旧的元素。
- ListIterator listIterator( ):返回一个列表迭代器,用来访问列表中的元素
- ListIterator listIterator(int index):返回一个列表迭代器,用来从指定位置index开
- List subList(int fromIndex,int toIndex):返回从指定位置fromIndex(包含)到toIndex(不包含)范围中各个元素的列表视图。
7.4.2 ArrayList类
ArrayList类是一个长度可变的对象引用数组,类似于动态数组,即ArrayList可以动态增减大小,数组列表以初始长度创建,当长度超过时,集合自动增大;当删除对象时,集合自动变小。访问和遍历对象时,它提供更好的性能。
适用于需要增加或减少集合元素时,能动态的增加或减少长度,减少数据赘余。
常用方法
- toArray( ):从ArrayList中得到一个数组。
- public boolean add(Object o):将指定的元素追加到此列表的尾部。
- public Object remove(Object o):从此列表中移除指定元素的单个实例(如果存在),此操作是可选的。
- public Object get(int index):返回此列表中指定位置上的元素。
- public int size( ):返回此列表中的元素数。
- void ensureCapacity(int minCapacity):将ArrayList对象容量增加minCapacity,在向一个ArrayList对象添加大量元素的程序中,可使用ensureCapacity方法增加capacity。这可以减少增加重分配的数量。
- void trimToSize( ):整理ArrayList对象容量为列表当前大小。程序可使用这个操作减少ArrayList对象存储空间。
7.4.3 LinkedList类
LinkedList类是一个有序集合,将每个对象存放在独立的链接中,每个链接中还存放着序列中下一个链接的索引。在Java中,所有的链接列表实际上是双重的,即每个链接中还存放着对它的前面的链接的索引。
LinkedList类适合处理数据序列中数据数目不定,且频繁进行插入和删除操作。每当插入或删除一个元素时,只需要更新其他元素的索引即可,不必移动元素的位置,效率很高。
LinkedList类两端处理特有方法
- Object removeFirst( ):删除并且返回列表开头的元素。
- Object removeLast( ):删除并且返回列表结尾的元素。
- LinkedList( ):构建一个空的链接列表。
- LinkedList(Collection c):构建一个链接列表,并且添加集合c的所有元素。
- void addFirst(Object o):将对象o添加到列表的开头。
- void addLast(Object o):将对象o添加到列表的结尾。
- Object getFirst( ):返回列表开头的元素。
- Object getLast( ):返回列表结尾的元素。
7.5 Set接口
7.5.1 概述
Java中的Set和数学上直观的集(set)的概念是相同的。Set最大的特性就是不允许在其中存放重复的元素。Set可以用来过滤集合中存放的元素,从而得到一个没有重复元素的集合。
Set接口继承Collection接口,其特点是它不允许集合中存在重复项,并且容器中对象不按特定方式排序。Set接口定义了以下一些常用方法。
常用方法
- boolean add(Object o):如果Set中尚未存在指定元素,则添加该元素。
- boolean contains(Object o):判断Set容器中是否包含指定元素,包含则返回true。
- boolean equals(Object o):比较指定对象是否与Set容器对象相等,相等返回true。
- boolean isEmpty( ):判断Set容器是否为空,空则返回true。
- boolean add(Object o):如果Set中尚未存在指定元素,则添加该元素。
- void clear( ):删除Set容器中的所有元素。
- int size( ):返回Set容器中的元素个数。
7.5.2 HashSet类
HashSet类底层是用HashMap实现的,线程不同步,外部无序地遍历成员。它创建一个类集,该类集使用散列表进行存储,而散列表使用散列法的机制来存储信息。散列法使其访问速度很快。所以,在不需要放入重复数据并且不关心放入顺序以及元素是否要求有序的情况下,选择使用HashSet类。
HashSet类是一个实现Set接口的具体类,可以用来存储互不相同的元素,不保证容器的迭代顺序,不保证顺序恒久不变,元素是没有顺序的,HashSet类允许null元素。
为了保证一个类的实例对象能在HashSet中正常存储,要求这个类的两个实例对象用equals( )方法比较的结果相等时,它们的哈希码也必须相等,所以为HashSet类的各个对象重新定义hashCode( )方法和equals( )方法。
7.5.3 TreeSet类
TreeSet类底层数据结构是二叉树,线程不同步,外部有序地遍历成员。在存储了大量的需要进行检索的排序信息的情况下,TreeSet是一个很好的选择。TreeSet类不仅实现了Set接口,还实现了SortedSet接口。
- 常用构造函数1
TreeSet(Comparator< ?super E >comparator):构造一个空的Set集合,它根据指定比较器进行排序。插入到该Set集合中的所有元素都必须能够由指定比较器进行相互比较。其中,comparator表示将用来对此Set进行排序的比较器。如果该参数为null,则使用元素的自然排序。
TreeSet( ):构造一个新的、空的Set集合,该Set集合根据其元素的自然顺序进行排序,插入该Set的所有元素都必须实现Comparable接口。另外,所有元素都必须是可互相比较的:对于Set中的任意两个元素e1和e2,需要先执行e1.compareTo(e2),如果用户试图将违反此约束的元素添加到Set(例如,用户试图将字符串元素添加到其元素为整数的Set中),则add( )方法调用将抛出ClassCastException。 - 常用构造函数2
TreeSet(SortedSet< E >s):构造一个与指定有序Set具有相同映射关系和相同排序的新TreeSet集合。其中,s表示一个有序Set,其元素将组成新的Set。
TreeSet(Collection< ?extends E > c):构造一个包含指定Collection元素的新的Set集合,按照其元素的自然顺序进行排序。插入该Set的所有元素都必须实现Comparable接口。另外,所有这些元素都必须是可相互比较的。其中,c表示一个集合,其元素将组成新的Set。 - TreeSet类支持的排序方式
自然排序:TreeSet会调用集合元素的compareTo(Object obj)方法来比较两个元素之间的大小关系,然后将集合元素按升序排列,这种方式就是自然排序。Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现该接口的类必须实现该方法,实现了该接口的类的对象就可以比较大小。
定制排序:TreeSet的自然排序是根据集合元素的大小,TreeSet将它们以升序排列。如果需要实现定制排序,例如以降序排列,则可以使用Comparator接口的帮助。
该接口里包含一个int compare(T o1,T o2)方法,该方法用于比较o1和o2的大小:如果该方法返回正整数,则表明o1大于o2;如果该方法返回0,则表明o1等于o2;如果该方法返回负整数,则表明o1小于o2。
7.6 Map接口
7.6.1 概述
数学中的映射关系在Java中就是通过Map来实现的。它表示里面存储的元素是一对(pair),我们通过一个对象,可以在这个映射关系中找到另外一个和这个对象相关的东西。
Map接口不是Collection接口的继承。而是从用于维护键-值关联的接口层次结构入手。Map接口中元素都是键与值成对存储的,因而需保证键的唯一性。
因为映射中键的集合必须是唯一的,所以使用Set来支持。因为映射中值的集合可能不唯一,所以使用Collection来支持。最后一个方法返回一个实现Map.Entry接口的元素Set。Map.Entry接口是Map接口中的一个内部接口,该内部接口的实现类存放的是键值对。
常用方法
- Object put(Object key,Object value):添加或替换一对元素,返回键对应的旧值,若无旧值则返回null(注意,不是返回新值)。当存储的键不存在时即是添加,键已存在时则为替换,新值会替换旧值并返回旧值。
- void putAll(Map m):添加一堆元素。
- void clear( ):清空。
- Object remove(Object key):删除指定键,返回对应的值。
- boolean isEmpty( ):判断是否为空。
- Object get(Object key):根据key(键)取得对应的值。
- boolean containsKey(Object key):判断Map中是否存在某键(key)。
- boolean containsValue(Object value):判断Map中是否存在某值(value)。
- int size( ):返回Map中键-值对的个数。
- boolean isEmpty( ):判断当前Map是否为空。
- public Set keySet( ):返回所有的键(key),并使用Set容器存放。
- public Collection values( ):返回所有的值(Value),并使用Collection存放。
- public Set entrySet( ):返回一个实现Map.Entry接口的元素Set。
7.6.2 HashMap类
HashMap类底层是哈希表数据结构。key不能重复,如果重复的话,后加进来的记录会覆盖前面的记录(底层是用set集合保存)。key是无序的,value可以重复。key和value都可以为null。
HashMap是基于HashCode的,在所有对象的超类Object中有一个HashCode( )方法,但是它和equals( )方法一样,并不能适用于所有的情况,这样就需要重写自己的HashCode( )方法。
7.6.2 TreeMap类
对于Map的使用和实现,需要注意存放“键值对”中的对象的equals( )方法和hashCode( )方法的覆写。如果需要排序,必须要实现Comparable接口中的compareTo( )方法。Map中的“键”是不能重复的,而对重复的判断是通过调用“键”对象的equals( )方法来决定的。HashMap中查找和存取“键值对”是需要同时调用hashCode( )方法和equals( )方法来完成。
TreeMap类不仅实现了Map接口,还实现了java.util.SortMap接口,因此集合中的映射关系具有一定的顺序。但是在添加、删除和定位映射关系上,TreeMap类比HashMap类的性能差一些。TreeMap类实现的Map集合中的映射关系是根据键值对象按一定的顺序排列的。因此,不允许键对象是null。
TreeMap中是根据键(Key)进行排序的。如果要使用TreeMap来进行正常的排序,Key中存放的对象必须实现Comparable接口。
第8章 GUI编程
8.1 GUI概述
早期,电脑向用户提供的是单调、枯燥、纯字符状态的“命令行界面(CLI)”。后来,Apple公司率先在电脑的操作系统中实现了图形化的用户界面(Graphical User Interface,简称GUI)。在这图形用户界面风行于世的今天,一个应用软件没有良好的GUI是无法让用户接受的。而Java语言也深知这一点的重要性,它提供了一套可以轻松构建GUI的工具。
GUI又称为图形用户接口,指的就是采用图形方式显示的计算机操作用户界面,例如我们点击QQ图标,就会弹出一个QQ登录界面的对话框。这个QQ登陆页面就能够被称作图形化的用户界面。
Java将GUI界面封装为类,其中若干类放在了以下两个包里:
java.awt包:抽象窗口工具包(Abstract Window Toolkit,AWT),它依赖于本地操作系统的GUI,缺乏平台独立性,属重量级控件。包中主要包括界面组件、布局管理器、事件处理模型及图形和图像工具等等。
javax.swing包:在AWT的基础上建立的一套图形界面系统,提供了更多的组件,完全由java实现,增强了移植性,属轻量级控件。Swing中的类是从AWT继承的,有些Swing类直接扩展AWT中对应的类。例如JFrame(窗体,框架)、JPanel(面板,容器)及JButton(按钮)等。
注意:Swing与AWT之间的最明显的区别是界面组件的外观,AWT在不同平台上运行相同的程序,界面的外观和风格可能会有一些差异。然而,一个基于Swing的应用程序可能在任何平台上都会有相同的外观和风格。
8.2 GUI编程步骤
- 创建容器:要开发一个GUI应用程序,首先需要创建一个用于容纳所有其他GUI组件元素的载体,Java中称为容器。只有先创建了这些容器,才能把其他的组件加到容器中。
- 添加组件:组件就是平时所说的控件,用于和用户交互。为了与用户交互,则需要在容器上添加各种组件/控件。这需要根据具体的功能要求来决定用什么组件。
- 安排组件:与传统的Windows环境下的GUI软件开发工具不同,为了更好地实现跨平台,Java程序中各组件的位置、大小一般不是以绝对量来衡量,而是以相对量来衡量。因此,在组织界面时,除了要考虑所需的组件种类外,还需要考虑如何安排这些组件的位置与大小。这一般是通过设置布局管理器(Layout Manager)及其相关属性来实现的。
- 处理事件:为了完成一个GUI应用程序所应具备的功能,除了适当地安排各种组件产生美观的界面外,还需要处理各种界面元素事件,以便真正实现与用户的交换,完成程序的功能。在Java程序中这一般是通过实现适当的事件监听者接口来完成的。
8.3 容器
Java容器(Container)实际上是Component的子类,因此容器类对象本身也是一个组件,具有组件的所有性质,另外还具有容纳其他组件和容器的功能。Java组件容器包含顶层容器和中间容器两类。
顶层容器有三种,分别是JFrame(框架窗口,即通常的窗口)、JDialog(对话框)、JApplet(用于设计嵌入在网页中的java小程序),顶层容器是容纳其他组件的基础,即设计图形化程序必须要有顶层容器。
中间容器是可以包含其他相应组件的容器,但是中间容器和组件一样,不能单独存在,必须依附于顶层容器。常见的中间容器有:
- JPanel:最灵活、最常用的中间容器。
- JScrollPane:与JPanel类似,但还可在大的组件或可扩展组件周围提供滚动条。
- JTabbedPane:包含多个组件,但一次只显示一个组件。用户可在组件之间方便地切换。
- JToolBar:按行或列排列一组组件(通常是按钮)。
8.3.1 JFrame
JFrame 用来设计类似于 Windows 系统中窗口形式的界面。JFrame 是 Swing 组件的顶层容器,该类继承了 AWT 的 Frame 类,支持 Swing 体系结构的高级 GUI 属性。
常用构造方法有
- JFrame( ):构造一个初始时不可见的新窗体。
- JFrame(String title):创建一个具有 title 指定标题的不可见新窗体。
常用方法 - getContentPane( ):返回此窗体的 contentPane 对象。
- getDefaultCloseOperation( ):返回用户在此窗体上单击“关闭”按钮时执行的操作。
- setDefaultCloseOperation(int operation) :设置用户在此窗体上单击“关闭”按钮时默认执行的操作。
- setContentPane(Container contentPane) :设置 contentPane 属性。
- setIconImage(Image image) :设置要作为此窗口图标显示的图像。
- setJMenuBar( JMenuBar menubar) :设置此窗体的菜单栏。
- setLayout(LayoutManager manager) :设置 LayoutManager 属性。
8.3.2 JPanel
JPanel 是一种中间层容器,它能容纳组件并将组件组合在一起,但它本身必须添加到其他容器中使用。
常用构造方法有
- JPanel( ):使用默认的布局管理器创建新面板,默认的布局管理器为 FlowLayout。
- JPanel(LayoutManagerLayout layout):创建指定布局管理器的 JPanel 对象。
常用方法 - void remove(Component comp) :从容器中移除指定的组件。
- void setFont(Font f) :设置容器的字体
- void setBackground(Color c) :设置组件的背景色
JFrame与JPanel的区别如下。 - JFrame可以独立存在,可被移动,可被最大化和最小化,有标题栏、边框,可添加菜单栏,默认布局是BorderLayout。
- JPanel不能独立运行,必须包含在另一个容器里。JPanel没有标题,没有边框,不可添加菜单栏,默认布局是FlowLayout。
- 一个JFrame可以包含多个JPanel,一个JPanel可以包含另一个JPanel,但是JPanel不能包含JFrame。
8.4 组件
Java图形用户界面的最基本组成部分是组件,组件是一个可以以图形化的方式显示在屏幕上并能与用户进行交互的对象,例如一个按钮,一个标签等。组件不能独立地显示出来,必须将组件放在一定的容器中才可以显示出来。
组件 | 描述 |
---|---|
JLabel | 标签 |
JButton | 按钮 |
JRadioButton | 单选按钮 |
JCheckBox | 复选框 |
JToggleButton | 开关按钮 |
JTextField | 文本框 |
JPasswordField | 密码框 |
JTextArea | 文本区域 |
JComboBox | 下拉列表框 |
JList | 列表 |
JProgressBar | 进度条 |
JSlider | 滑块 |
8.4.1 标签组件
标签JLabel组件是用来显示单行文本信息的组件,一般用来显示固定的提示信息。
常用构造方法有
- JLabel( ):创建无图像并且标题为空字符串的 JLabel。
- JLabel(Icon image):创建具有指定图像的 JLabel。
- JLabel(String text):创建具有指定文本的 JLabel。
- JLabel(String text,Icon image,int horizontalAlignment):创建具有指定文本、图像和水平对齐方式的 JLabel,horizontalAlignment 的取值有 3 个,即 JLabel.LEFT、JLabel.RIGHT 和 JLabel.CENTER。
常用方法 - void setText(Stxing text):定义 JLabel 将要显示的单行文本。
- void setIcon(Icon image):定义 JLabel 将要显示的图标。
- int getText( ):返回 JLabel 所显示的文本字符串。
- Icon getIcon( ):返回 JLabel 显示的图形图像。
8.4.2 按钮组件
按钮JButton组件是最简单的按钮组件,只是在按下和释放两个状态之间进行切换,可以通过捕获按下并释放的动作执行一些操作,从而完成和用户的交互。
常用构造方法有
- JButton( ):创建一个无标签文本、无图标的按钮。
- JButton(Icon icon):创建一个无标签文本、有图标的按钮。
- JButton(String text):创建一个有标签文本、无图标的按钮。
- JButton(String text,Icon icon):创建一个有标签文本、有图标的按钮。
常用方法 - void setText(String text):设置按鈕的文本。
- void setMargin(Insets m):设置按钮边框和标签之间的空白。
- void setVerticalAlignment(int alig):设置图标和文本的垂直对齐方式
- void setHorizontalAlignment(int alig):设置图标和文本的水平对齐方式。
- void setEnable(boolean flag):启用或禁用按扭。
8.4.3 文本组件
文本组件常用于数据的输入,主要有单行文本编辑框JTextField和多行文本区编辑框JTextArea。这2个类的很多方法是从JTextComponent继承的。
- JTextField是一个轻量级组件,用来接受用户输入的单行文本信息。
常用构造方法有
- JTextField( ):创建一个默认的文本框。
- JTextField(String text):创建一个指定初始化文本信息的文本框。
- JTextField(int columns):创建一个指定列数的文本框。
- JTextField(String text,int columns):创建一个既指定初始化文本信息,又指定列数的文本框。
常用方法如下。 - void setColumns(int columns):设置文本框最多可显示内容的列数。
- void setFont(Font f):设置文本框的字体。
- void setHorizontalAlignment(int alignment):设置文本框内容的水平对齐方式。
- JTextArea组件实现一个文本域,文本域可以接受用户输入的多行文本。
常用构造方法有
- JTextArea( ):创建一个默认的文本域。
- JTextArea(int rows,int columns):创建一个具有指定行数和列数的文本域。
- JTextArea(String text):创建一个包含指定文本的文本域。
- JTextArea(String text,int rows,int columns):创建一个既包含指定文本,又包含指定行数和列数的多行文本域。
常用方法如下。 - void append(String str):将字符串 str 添加到文本域的最后位置。
- void setColumns(int columns):设置文本域的行数。
- void setRows(int rows):设置文本域的列数。
- int getColumns( ):获取文本域的行数。
- void setLineWrap(boolean wrap):设置文本域的换行策略。
- int getRows( ):获取文本域的列数。
8.4.4 菜单组件
菜单组件是为软件系统提供一种分类和管理软件命令的形式和手段。菜单由菜单栏(JMenuBar)、下拉菜单(JMenu)和菜单项(JMenuItem)组成。
- 菜单栏:要添加菜单,需要首先创建一个菜单栏对象(JMenubar),再创建菜单对象(JMenu)放入菜单栏中,然后向菜单里增加选项(JMenuItem)。
- 下拉菜单:JMenu类用来实现下拉菜单。下拉菜单(JMenu)是一个包含菜单项(JMenuItem)的弹出窗口,用户选择菜单栏(JMenuBar)上的选项时会显示该菜单项(JMenuItem)。
- 菜单项:JMenuItem用来实现菜单中的选项。菜单项本质上是位于列表中的按钮,当用户选择“按钮”时,将执行与菜单项关联的操作。
8.5 布局管理器
8.5.1 边框布局管理器
BorderLayout(边框布局管理器)是 Window、JFrame 和 JDialog 的默认布局管理器。边框布局管理器将窗口分为 5 个区域:North、South、East、West 和 Center。其中,North 表示北,将占据面板的上方;South表示南,将占据面板的下方;East表示东,将占据面板的右侧;West 表示西,将占据面板的左侧;中间区域 Center 是在东、南、西、北都填满后剩下的区域。
BorderLayout 布局管理器的构造方法:
- BorderLayout( ):创建一个 Border 布局,组件之间没有间隙。
- BorderLayout(int hgap,int vgap):创建一个 Border 布局,其中 hgap 表示组件之间的横向间隔;vgap 表示组件之间的纵向间隔,单位是像素。
边框布局管理器的特点是,组件会随设置固定在某一区域,即使拉伸窗体也不会改变位置,但是大小会随窗体的拉伸发生改。
注意:边框布局管理器并不要求所有区域都必须有组件,如果四周的区域(North、South、East 和 West 区域)没有组件,则由 Center 区域去补充。如果单个区域中添加的不止一个组件,那么后来添加的组件将覆盖原来的组件,所以,区域中只显示最后添加的一个组件。
8.5.2 流式布局管理器
FlowLayout(流式布局管理器)是 JPanel 和 JApplet 的默认布局管理器。FlowLayout 会将组件按照从上到下、从左到右的放置规律逐行进行定位。与其他布局管理器不同的是,FlowLayout 布局管理器不限制它所管理组件的大小,而是允许它们有自己的最佳大小。
FlowLayout 布局管理器的构造方法如下:
- FlowLayout( ):创建一个布局管理器,使用默认的居中对齐方式和默认 5 像素的间隔。
- FlowLayout(int align):align 表示组件的对齐方式,对齐的值必须是 FlowLayout.LEFT、FlowLayout.RIGHT 和 FlowLayout.CENTER,指定组件在这一行的位置是居左对齐、居右对齐或居中对齐。
- FlowLayout(int align, int hgap,int vgap):指定对齐方式,指定水平和垂直间隔的像素。
注意:流布局管理器的缺点是,组件的位置会因为用户对窗体的拉伸动作导致移位。
8.5.3 网格布局管理器
GridLayout(网格布局管理器)为组件的放置位置提供了更大的灵活性。它将区域分割成行数(rows)和列数(columns)的网格状布局,组件按照由左至右、由上而下的次序排列填充到各个单元格中。
GridLayout布局管理器的构造方法:
- GridLayout(int rows,int cols):创建一个指定行(rows)和列(cols)的网格布局。布局中所有组件的大小一样,组件之间没有间隔。
- GridLayout(int rows,int cols,int hgap,int vgap):创建一个指定行(rows)和列(cols)的网格布局,并且可以指定组件之间横向(hgap)和纵向(vgap)的间隔,单位是像素。
注意:GridLayout 布局管理器总是忽略组件的最佳大小,而是根据提供的行和列进行平分。该布局管理的所有单元格的宽度和高度都是一样的。
8.5.4 卡片布局管理器
CardLayout(卡片布局管理器)能够帮助用户实现多个成员共享同一个显示空间,并且一次只显示一个容器组件的内容。
CardLayout 的构造方法:
- CardLayout( ):构造一个新布局,默认间隔为0。
- CardLayout(int hgap, int vgap):创建布局管理器,并指定组件间的水平间隔(hgap)和垂直间隔(vgap)
注意:卡片式布局管理器的特点是,其对组件的布局就像一堆卡片,需要一张一张地翻开,每一张卡片相当于一个界面。它适合于有多个显示界面的情况,可以结合其他轻量级容器来实现。
8.5.5 绝对定位
以上的布局管理器都是依靠专门的类完成的,在Java中也可以通过绝对定位的方式进行布局。运用绝对定位方式时需要注意以下几点。
JFrame的布局方式要设置为null。例如:frame.setLayout(null);
需要设置x坐标,y坐标,width组件宽度,height组件高度。
例如:组件.setBounds(x,y,w,h);
8.6 GUI事件处理
8.6.1 事件的概念
在事件处理的过程中,主要涉及三类对象。
Event(事件):用户对组件的一次操作称为一个事件,以类的形式出现。例如,键盘操作对应的事件类是KeyEvent。
- Event Source(事件源):事件发生的场所,通常就是各个组件,例如按钮Button。
- Event Handler(监听器):时刻监听事件源上所有发生的事件类型,一旦该事件类型与自己所负责处理的事件类型一致,就马上进行处理。
例如,如果鼠标单击了按钮对象 JButton,则该按钮JButton 就是事件源,而 Java 运行时系统会生成 ActionEvent事件类的对象,该对象中描述了单击事件发生时的一些信息。之后,监听器对象将接收由 Java 运行时系统传递过来的事件对象 ActionEvent,并进行相应的处理。事件处理流程图如右图所示。
由于同一个事件源上可能发生多种事件,因此,Java 采取了授权模型(Delegation Model),事件源可以把在其自身上所有可能发生的事件分别授权给不同的监听器来处理。例如,在 JPanel 对象上既可能发生鼠标事件,也可能发生键盘事件,该 JPanel 对象可以授权给监听器 a 来处理鼠标事件,同时授权给监听器 b 来处理键盘事件。
8.6.2 常见事件
常见事件类一般包含在java.awt.event包中。它们的层次结构如下图所示。
事件类、对应的事件监听器接口以及对应的方法和含义
8.6.3 常见事件监听器
- 动作事件监听器
- 动作事件监听器是 Swing 中比较常用的事件监听器,很多组件的动作都会使用它监听,像按钮被点击等。
- 事件名称:ActionEvent。
- 事件监听接口: ActionListener。
- 事件相关方法:addActionListener( ) 添加监听,removeActionListener( ) 删除监听。
- 涉及事件源:JButton、JList、JTextField 等。
- 焦点事件监听器
焦点事件监听器在实际项目中应用也比较广泛,例如将光标离开文本框时弹出对话框,或者将焦点返回给文本框等。
- 事件名称:FocusEvent。
- 事件监听接口: FocusListener。
- 事件相关方法:addFocusListener( ) 添加监听,removeFocusListener( ) 删除监听。
- 涉及事件源:Component 以及派生类。
- 选择事件监听器
改变选项状态事件由ItemEvent类定义,最常用的是当用鼠标左键单击复选框、单选按钮或组合框选项引起选择状态发生变化时,可触发选项事件,可以通过实现选择事件监听器处理相应的动作事件。
- 事件名称:ItemEvent。
- 事件监听接口: ItemListener。
- 事件相关方法:addItemListener()添加监听,removeItemListener()删除监听。
- 涉及事件源:JRadioButton等。
第9章 I/O流与文件
9.1 I/O流入门
9.1.1 I/O流的概念
输入/输出(I/O)处理是程序设计中非常重要的环节,如从键盘输入数据,从文件中读取数据或向文件中写数据等。Java把所有的输入/输出以流的形式进行处理,这里的流是指连续的、单向的数据传输的一种抽象,即由源到目的地通信路径传输的一串字节。发送数据流的过程称为写,接受数据流的过程称为读。当程序需要读取数据的时候,就会开启一个通向数据源的流。当程序需要写入数据的时候,就会开启一个通向目的地的流。
Java中定义了字节流和字符流以及其他的流类来实现输入/输出处理。
- 字节流:从InputStream和OutputStream类派生出来的一系列类称为字节流类,这类流以字节(byte)为基本处理单位。
- 字符流:从Reader和Writer类派生出的一系列类称为字符流类,这类流以16位的Unicode编码表示的字符为基本处理单位 。
9.1.2 I/O流类的层次结构
- 字节输入流层次结构
- 字节输出流层次结构
- 字符输入流层次结构
- 字符输出流层次结构
9.2 File类
File类是一个与流无关的类。File类提供了一种与机器无关的方式来描述一个文件对象的属性,每个File类对象表示一个磁盘文件或目录,其对象属性包含了文件或目录的相关信息,如名称、长度和文件个数等,调用File类的方法可以完成对文件或目录的管理操作(如创建和删除等)。File类仅描述文件本身的属性,不具有从文件读取信息或向文件存储信息的能力。
- 创建File对象的常用构造方法
- File(File parent,String child):根据parent抽象路径名和child路径名字符串创建一个新File实例。
- File(String pathname):通过将给定路径名字符串转换为抽象路径名来创建一个新File实例。
- File(String parent,String child):根据parent路径名字符串和child路径名字符串创建一个新File实例。
- File类的常用方法
- getName( ):获取文件的名字
- getPath( ):获取文件的相对路径字符串。
- exists( ):判断文件或文件夹是否存在。
- canRead( ):判断文件是否可读的。
- isFile( ):判断文件是否是一个正常的文件,而不是目录。
- idDirectory( ):判断是不是文件夹类型。
- canWrite( ):判断文件是否可被写入。
- createNewFile( ):创建一个新文件。
- length( ):获取文件的长度。
9.3 构造方法
9.3.1 字节输入流父类(InputStream)
InputStream类是字节输入流的抽象类,它是所有字节输入流的父类,其各种子类实现了不同的数据输入流。
InputStream的常用方法
- available( ):返回当前输入流的数据读取方法可以读取的有效字节数量。
- read(byte[] bytes):从输入数据流中读取字节并存入数组bytes中。
- read(byte[] bytes, int off, int len):从输入数据流读取len个字节,并存入数组bytes中。
- read( ):从当前数据流中读取一个字节。若已到达流结尾,则返回-1。
- reset( ):将当前输入流重新定位到最后一次调用mark( )方法时的位置。
- mark(int readlimit):在输入数据流中加入标记。
- markSupported( ):测试输入流中是否支持标记。
- close( ):关闭当前输入流,并释放任何与之关联的系统资源。
9.3.2 字节输出流父类(OutputStream)
OutputStream类是字节输出流的抽象类,它是所有字节输出流的父类,其子类实现了不同数据的输出流。
OutputStream的常用方法
- write(int b):将指定的字节写入此输出流。
- write(byte[] b):将b.length个字节从指定的字节数组写入此输出流。
- write(byte[] b,int off,int len):将指定字节数组中从偏移量off开始的len个字节写入此输出流。
- flush( ):刷新此输出流并强制写出所有缓冲的输出字节。
- close( ):关闭此输出流并释放与此流有关的所有系统资源。
9.3.3 FileInputStream类与FileOutputStream类
FileInputStream和FileOutputStream分别是抽象类InputStream和OutputStream类的子类。FileInputStream兼容抽象类InputStream的所有成员方法,它实现了文件的读取,是文件字节输入流,该类适用于比较简单的文件读取。FileOutputStream兼容抽象类OutputStream的所有成员方法,它实现了文件的写入,能够以字节形式写入文件中。
9.3.4 DataInputStream类与DataOutputStream类
数据字节输入流DataInputStream类和数据字节输出流DataOutputStream类提供直接读或写基本数据类型数据的方法,在读或写某种基本数据类型时,不必关心它的实际长度是多少字节。
9.3.5 BufferedInputStream类与BufferedOutputStream类
类BufferedInputStream和类BufferedOutputStream是带缓存的输入流和输出流。使用缓存,就是在实例化类BufferedInputStream和类BufferedOutputStream对象时,会在内存中开辟一个字节数组用来存放数据流中的数据。借助字节数组,在读取或者存储数据时可以以字节数组为单位把数据读入内存或以字节数组为单位把数据写入指定的文件中,从而大大提高数据的读/写效率。
BufferedInputStream是套在某个其他的InputStream外,起着缓存的功能,用来改善里面InputStream的性能,它自己不能脱离InputStream单独存在。所以把BufferedInputStream套在FileInputStream外可以改善FileInputStream的性能。
FileInputStream与BufferedInputStream区别:
FileInputStream是字节流,BufferedInput Stream是字节缓冲流
使用BufferedInputStream读取资源比FileInputStream读取资源的效率高
BufferedInputStream的read方法能读取尽可能多的字节,
而FileInputStream对象的read( )方法会出现阻塞。
9.3.6 ObjectInputStream类与ObjectOutputStream类
对某个对象进行读写操作,我们需要使用对象流。对象流分为对象输入流ObjectInputStream和对象输出流ObjectOutputStream。
ObjectInputStream / ObjectOutputStream 是以“对象”为数据源,但是必须将传输的对象进行序列化操作。序列化以后的对象可以保存到磁盘上,也可以在网络上传输, 使得不同的计算机可以共享对象。
如何序列化?
某类实现Serializable接口,则该类创建的对象是可序列化对象。此过程就是序列化的过程。User类定义的对象为序列化对象
9.3.7 PrintStream类
PrintStream是打印输出流,它可以直接输出各种类型的数据。
- print(String str):打印字符串。
- print(object obj):打印一个对象。
- println(String str):打印一个字符串并结束该行。
- println(object obj):打印一个对象并结束该行。
9.4 字符流
9.4.1 字符输入流父类(Reader)
Reader类是字符输入流的抽象类,所有字符输入流的实现都是它的子类。它定义了操作字符输入流的各种方法。
字符流按传输方向分为字符输入流Reader 。
常用方法
- read( ):读入一个字符。若已读到流结尾,则返回值为-1。
- read(char[]):读取一些字符到char[]数组内,并返回所读入的字符的数量。若已到达流结尾,则返回-1。
- skip(long n):跳过参数n指定的字符数量,并返回所跳过字符的数量。
- close( ):关闭该流并释放与之关联的所有资源。在关闭该流后,再调用read( )、ready( )、mark( )、reset( )或skip( )方法将抛出异常。
Reader的常用子类如下图所示。
9.4.2 字符输出流父类(Writer)
Writer类是字符输出流的抽象类,所有字符输出流的实现都是它的子类。它定义了操作字符输出流的各种方法。
字符输出流Writer(如Java程序把数据写到文件中)。
Writer的常用子类如下图所示。
常用方法
- write(int c):将字符c写入输出流。
- write(String str):将字符串str写入输出流。
- write(char[] cbuf):将字符数组的数据写入到字符输出流。
- flush( ):刷新当前输出流,并强制写入所有缓冲的字节数据。
- close( ):向输出流写入缓冲区的数据,然后关闭当前输出流,并释放所有与当前输出流有关的系统资源。
9.4.3 FileReader类与FileWriter类
FileReader类和FileWriter类分别是抽象类Reader和Writer类的子类。
FileReader类兼容抽象类Reader的所有成员方法,可以进行读取字符串和关闭流等操作。
FileWriter类兼容抽象类Writer的所有成员方法,可以进行输出单个或多个字符、强制输出和关闭流等操作。
9.4.4 InputStreamReader类与OutputStreamWriter类
InputStreamReader是字节流通向字符流的桥梁。它可以根据指定的编码方式,将字节输入流转换为字符输入流。
OutputStreamWriter是字节流通向字符流的桥梁。写出字节,并根据指定的编码方式,将之转换为字符流。
注意: InputStreamReader和OutputStreamWriter相当于在字节流的基础上加了桥梁作用,转变成了字符流,所以构造它们时要先构造普通文件字节流。
9.4.5 BufferedReader类与BufferedWriter类
BufferedReader类是Reader类的子类,使用该类可以以行为单位读取数据。Buffered Reader类中提供了一个ReaderLine( )方法,Reader类中没有此方法,该方法能够读取文本行。
BufferedWriter类是Writer类的子类,该类可以以行为单位写入数据。BufferedWriter类提供了一个newLine( )方法,Writer类中没有此方法。该方法是换行标记。
9.4.6 PrintWriter类
常用方法
- print(String str):将字符串型数据写至输出流。
- flush( ):强制性地将缓冲区中的数据写至输出流。
- println(String str):将字符串和换行符写至输出流。
- println( ):将换行符写至输出流。
PrintWriter是打印输出流,该流把Java语言的内构类型以字符表示形式送到相应的输出流中,可以以文本的形式浏览。
9.5 随机访问文件类
使用RandomAccessFile类可以读取任意位置数据的文件。RandomAccessFile类既不是输入流类的子类,也不是输出流类的子类。
创建RandomAccessFile对象时还需要指定一个mode参数,该参数指定RandomAccessFile的访问模式,一共有4种模式。
常用方法
- length( ):获取文件的长度。
- readByte( ):从文件中读取一个字节。
- readChar( ):从文件中读取一个字符。
- readInt( ):从文件中读取一个int值。
- readLine( ):从文件中读取一个文本行。
- seek(long pos):设置文件指针位置。
- write(byte bytes[]):把bytes.length个字节写到文件。
- writeInt(int v):向文件中写入一个int值。
- writeChars(String str):向文件中写入一个作为字符数据的字符串。
- close( ):关闭文件。
第10章 多线程
10.1 线程入门
多线程是Java的特点之一,掌握多线程编程技术,可以充分利用CPU的资源,更容易解决实际中的问题。本章主要介绍Java线程的相关知识和应用,内容包括线程的基本概念、实现多线程的两种机制(继承Thread类和实现Runnable接口)、线程的4种状态、线程的调度和优先级等。
目前主流的操作系统都是多任务、多线程的,即操作系统能够同时执行多项任务。随着计算机软、硬件技术的不断提高,怎样提高系统的综合效率,是软件应用开发人员应到考虑的问题。为了真正提高系统效率,可以采用多线程技术。Java语言提供了多线程机制。合理设计和利用多线程,可以充分利用计算机资源,提高程序执行效率。
10.1.1 操作系统与进程
进程(Process)是程序在操作系统执行的过程。进程直观上理解就是正在进行的程序,而每个进程包含一个或者多个线程。也就是说一个进程是由若干线程组成的,在程序执行期间,真正执行的是线程,而进程只是负责给该进程中的线程分配执行路径。
10.1.2 进程与线程
所以,线程就是进程中负责程序执行的控制单元(执行路径),一个进程可以有多个执行路径,称为多线程。就像我们再使用QQ给多个好友聊天一样,每一个聊天过程都是一个线程,这些线程都属于QQ这个进程。
开启多线程就是为了同时运行多部分代码。
每一线程都有自己运行的内容,这个内容可以称为线程要执行的任务。
进程是静态的概念,实际运行的都是线程。
10.2 创建线程
10.2.1 继承Thread类创建线程
java.lang.Thread是Java中用来表示线程的类,如果将一个类定义为Thread类的子类,那么这个类的对象就可以用来表示线程。
继承Thread类创建线程的步骤如下:
- 创建一个类继承Thread类,重写run( )方法,将所要完成的任务代码写进run( )方法中。
- 创建Thread类的子类的对象。
- 调用该对象的start( )方法,该start( )方法表示开启线程,
- 然后交给CPU自动调用run( )方法,程序中不需要调用run( )方法
10.2.2 实现Runnable接口创建线程
Runnable是Java中用以实现线程的接口,从根本上讲,任何实现线程功能的类都必须实现这个接口。前面所用到的Thread类就是因为实现了Runnable接口,所以继承它的类才具有了相应的线程功能。
实现Runnable接口创建线程的步骤如下:
- 创建一个类并实现Runnable接口。
- 重写run( )方法,将所要完成的任务代码写进run( )方法中。
- 创建实现Runnable接口的类对象,将该对象当做Thread类构造方法的参数传入。
- 使用Thread类的构造方法创建一个对象,并调用start( )方法即可运行该线程。
10.3 线程状态
线程从产生到消亡,一共有五个状态。
- Newborn 状态
线程被创建但没有开始执行的初始状态。直到调用Thread类的run( )方法后,线程会进入Runnable状态。 - Runnable状态
线程处在准备就绪的状态,随时可被调用执行。(注意:对线程调用run( )方法后,不代表线程立即执行,Runnable状态的线程会进入一个等待队列中等待执行) - Running 状态
线程正在运行的状态,表示线程已经拥有处理器的控制权。正常情况下,当run( )方法运行结束后,该线程就进入Dead状态。 - Blocked状态
线程的堵塞状态。处于阻塞状态的线程必须由某些事件唤醒,至于是何种事件,则取决于阻塞发生的原因。 - Dead 状态
死亡或终止状态,表示线程已经退出运行的状态。进入这一状态的线程有多方面的原因,可能是线程执行完毕,正常终止也可能是一个线程被另一个线程强行中断。
线程各状态及状态间的转换
在线程的转换关系中,箭头表示可转换的方向,其中,单箭头表示状态只能单向的转换,比如线程只能从新建状态转换到就绪状态,反之则不能,双箭头表示两种状态可以互相转换,比如就绪状态和运行状态可以互相转换。
10.4 线程的常用方法
线程控制中一些常用方法
- Thread. currentThread():获取当前线程对象
- getPriority():获取当前线程的优先级
- setPriority():设置当前线程的优先级。(注意:线程优先级高,被CPU调度的概率大,但不代表一定会运行,还有小概率运行优先级低的线程。)
- isAlive():判断线程是否处于活动状态 (线程调用start后,即处于活动状态)
- join():调用join方法的线程强制执行,其他线程处于阻塞状态,等该线程执行完后,其他线程再执行。有可能被外界中断产生InterruptedException 中断异常。 - sleep():在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。休眠的线程进入阻塞状态。
- yield():调用yield方法的线程,会礼让其他线程先运行。
- interrupt():中断线程。 - wait():导致线程等待,进入堵塞状态。该方法要在同步方法或者同步代码块中才使用的。
- sleep():在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。休眠的线程进入阻塞状态。
- notify():唤醒当前线程,进入运行状态。该方法要在同步方法或者同步代码块中才使用的。
- notifyAll():唤醒所有等待的线程。该方法要在同步方法或者同步代码块中才使用的。
10.5 线程的同步
当使用多个线程来访问同一个数据时,这个数据在被一个线程访问完成前不允许被其他线程访问。这就叫同步。
当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
为了避免多线程共享资源发生冲突的情况的发生,只要在线程使用资源时给该资源上一把锁就可以了,访问资源的第一个线程为资源上锁,其他线程若想使用这个资源必须等到锁解除为止,锁解开的同时另一个线程使用该资源并为这个资源上锁。
为了处理这种共享资源竞争,可以使用同步机制。所谓同步机制指的是两个线程同时操作一个对象时,应该保持对象数据的统一性和整体性。Java语言提供synchronized关键字,为防止资源冲突提供了内置支持。共享资源一般是文件、输入/输出端口,或者是打印机。
Java语言中有两种同步形式,即同步方法和同步代码块。
10.5.1 同步代码块
Java语言中设置程序的某个代码段为同步区域。
语法格式为:
1 | synchronized(someobject){ |
其中,somobject代表当前对象,同步的作用区域是synchronized关键字后大括号以内的部分。在程序执行到synchronized设定的同步化区块时锁定当前对象,这样就没有其他线程可以执行这个被同步化的区块。
10.5.2 同步方法
同步方法将访问这个资源的方法都标记为synchronized,这样在需要调用这个方法的线程执行完之前,其他调用该方法的线程都会被阻塞。可以使用如下代码声明一个synchronized方法。
1 | //定义取和的同步方法 |
10.6 线程的死锁
因为线程可以阻塞,并且具有同步控制机制可以防止其他线程在锁还没有释放的情况下访问这个对象,这时就产生了矛盾,例如,线程A在等待线程B,而线程B又在等待线程A,这样就造成了死锁。
造成死锁必须同时满足的4个条件
- 互斥条件:线程使用的资源必须至少有一个是不能共享的。
- 请求与保持条件:至少有一个线程必须持有一个资源并且正在等待获取一个当前被其他线程持有的资源。因为要发生死锁,这4个条件必须同时满足,所以要防止死锁的话,只需要破坏其中一个条件即可。
- 非剥夺条件:分配的资源不能从相应的线程中被强制剥夺。
- 循环等待条件:第一个线程等待其他线程,后者又在等待第一个线程。
10.7 线程的通信
线程通信是协调线程之间的运行,主要是为了解决死锁的问题。
例如,有一个水塘,对水塘操作无非包括“进水”和“排水”,这两个行为各自代表一个线程,当水塘中没有水时,“排水”行为不能再进行,当水塘水满时,“进水”行为不能再进行。
在Java语言中用于线程间通信的方法是前面提到过的wait( )与notify( )方法,拿水塘的例子来说明,线程A代表“进水”,线程B代表“排水”,这两个线程对水塘都具有访问权限。假设线程B试图做“排水”行为,然而水塘中却没有水。这时候线程B只好等待一会。线程B可以使用如下代码。
1 | // 如果水塘没有水 |
在由线程A往水塘注水之前,线程B不能从这个队列中释放,它不能再次运行。当线程A将水注入水塘中后,应该由线程 A来通知线程B水塘中已经被注入水了,线程B才可以运行。此时,水塘对象将等待队列中第一个被阻塞的线程在队列中释放出来,并且重新加入程序运行。水塘对象可以使用如下代码。
1 | water.notify( ); |
将“进水”与“排水”抽象为线程A和线程B。“水塘”抽象为线程A与线程B共享对象water,上述情况即可看作线程通信,线程通信可以使用wait( )与notify( )方法。notify( )方法最多只能释放等待队列中的第一个线程,如果有多个线程在等待,可以使用notifyAll( )方法,释放所有线程。另外,wait( )方法除了可以被notify( )方法调用终止以外,还可以通过调用线程的interrupt( )方法来中断,通过调用线程的interrupt( )方法来终止,wait( )方法会抛出一个异常。因此,如同sleep( )方法,也需要将wait( )方法放在try…catch语句块中。
在实际应用中,wait( )方法与notify( )方法必须在同步方法或同步块中调用,因为只有获得这个共享对象,才可能释放它。为了使线程对一个对象调用wait( )方法或notify( )方法,线程必须锁定那个特定的对象,这个时候就需要同步机制进行保护。
例如,当“排水”线程得到对水塘的控制权时,也就是拥有了water这个对象,但水塘中却没有水,此时,water.isEmpty( )条件满足,water对象被释放,所以“排水”线程在等待。可以使用右方代码在同步机制保护下调用wait( )方法。
1 | synchronized(water){ |
当“进水”线程将水注入水塘后,再通知等待的“排水”线程,告诉它可以排水了,“排水”线程被唤醒后继续做排水工作。
notify( )方法通知“排水”线程,并将其唤醒,notify( )方法与wait( )方法相同,都需要在同步方法或同步块中才能被调用。
右边是在同步机制下调用notify( )方法的代码。
1 | synchronized(water){ |
第11章 网络编程
11.1 网络编程入门
网络编程指的就是网络上的主机通过不同的进程,以编程的方式实现网络数据传输。
Java是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持,程序员使用Java可以轻松地开发出各种类型的网络程序。本章介绍Java网络编程的相关基本概念,包括TCP/UDP介绍、IP地址封装、Socket编程和UDP编程。
要开发Java网络应用程序,就必须对网络的基础知识有一定的了解。Java的网络通信可以使用TCP、UDP等协议,在学习Java网络编程之前,先简单了解一下有关协议的基础知识。
11.1.1 TCP
TCP的全称是Transmission Control Protocol,也就是传输控制协议,主要负责数据的分组和重组。它与IP协议组合使用,称为TCP/IP。
TCP适合于可靠性比较高的运行环境,因为TCP是严格的、安全的。它以固定连接为基础,提供计算机之间可靠的数据传输,计算机之间可以凭借连接交换数据,并且传送的数据能够正确抵达目标,传送到目标后的数据仍然保持数据送出时的顺序。
11.1.2 UDP
UDP的全称是User Datagram Protocol,也就是用户数据报协议,和TCP不同,UDP是一种非持续连接的通信协议,它不保证数据能够正确抵达目标。
虽然UDP可能会因网络连接等各种原因,无法保证数据的安全传送,并且多个数据包抵达目标的顺序可能和发送时的顺序不同,但是它比TCP更轻量一些,TCP的认证会耗费额外的资源,可能导致传输速度的下降。在正常的网络环境中,数据都可以安全地抵达目标计算机中,所以使用UDP更加适合一些对可靠性要求不高的环境,如在线影视、聊天室等。
11.2 IP地址
Java提供了IP地址的封装类InetAddress。它封装了IP地址,并提供了相关常用方法,例如,解析IP地址的主机名称、获取本机IP地址的封装、测试IP地址是否可达等。
IP地址是每个计算机在网络中的唯一标识,它是32位或128位的无符号数字,使用4组数字表示一个固定的编号,例如“192.168.128.255”就是局域网络中的编号。IP地址,它是一种低级协议,UDP和TCP都是在它的基础上构建的。
InetAddress类的常用方法
- getLocalHost( ):返回本地主机的InetAddress对象。
- getByName(String host):获取指定主机名称的IP地址。
- getHostName( ):获取此主机名。
- getHostAddress( ):获取主机IP地址。
- isReachable(int timeout):在timeout指定的毫秒时间内,测试IP地址是否可达。
11.3 套接字编程
11.3.1 什么是套接字
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个双向链路的一端称为一个套接字(Socket)。套接字(Socket)通常用来实现客户方和服务方的连接。套接字(Socket)是TCP/IP协议的一个十分流行的编程界面,一个套接字(Socket)由一个IP地址和一个端口号唯一确定。
但是,套接字(Socket)所支持的协议种类也不只TCP/IP一种,因此两者之间是没有必然联系的。在Java环境下,套接字(Socket)编程主要是指基于TCP/IP协议的网络编程。
11.3.2 套接字通讯的过程
Server端Listen(监听)某个端口是否有连接请求,Client端向Server端发出Connect(连接)请求,Server端向Client端发回Accept(接受)消息。一个连接就建立起来了。Server端和Client端都可以通过Send( )、Write( )等方法与对方通信。
对于一个功能齐全的Socket,都要包含以下基本结构,其工作过程包含以下4个基本的步骤。
- 创建Socket。
- 打开连接到Socket的输入/出流。
- 按照一定的协议对Socket进行读/写操作。
- 关闭Socket。
11.3.3 客户端套接字
Socket类是实现客户端套接字的基础。它采用TCP建立计算机之间的连接,并包含了Java语言所有对TCP有关的操作方法,例如建立连接、传输数据、断开连接等。
Socket类定义了多个构造方法,它们可以根据InetAddress对象或者字符串指定的IP地址和端口号创建实例。下面介绍一下Socket常用的4个构造方法。
- Socket(InetAddress address,int port):使用address参数传递的IP封装对象和port参数指定的端口号创建套接字实例对象。Socket类的构造方法可能会产生UnknownHost Exception和IOException异常,在使用该构造方法创建Socket对象时必须捕获和处理这两个异常。
1 | try { |
- Socket(String host,int port):使用host参数指定的IP地址字符串和port参数指定的整数类型端口号创建套接字实例对象。
1 | try { |
- Socket(InetAddress address,int port,InetAddress localAddr,int localPort):创建一个套接字并将其连接到指定远程地址的指定远程端口。
1 | try { |
- Socket(String host,int port,InetAddress localAddr,int localPort):创建套接字并将其连接到指定远程主机上的指定远程端口。
1 | try { |
Socket对象创建成功以后,代表和对方的主机已经建立了连接,可以接收和发送数据了。Socket提供了两个方法分别获取套接字的输入流和输出流,可以将要发送的数据写入输出流,实现发送功能,或者从输入流读取对方发送的数据,实现接收功能。
接收数据:
Socket对象从数据输入流中获取数据,该输入流中包含对方发送的数据,这些数据可能是文件、图片、音频或视频。所以在实现接收数据之前,必须使用getInputStream( )方法获取输入流。
语法格式为:socket.getInputStream( )
发送数据:
Socket对象使用输出流,向对方发送数据,在实现数据发送之前,必须使用getOutput Stream( )方法获取套接字的输出流。
语法格式为:socket.getOutputStream( )
11.3.4 服务器端套接字
服务器端的套接字是ServerSocket类的实例对象,用于实现服务器程序,ServerSocket类将监视指定的端口,并建立客户端到服务器端套接字的连接,也就是客户负责呼叫任务。
创建服务器端套接字可以使用4种构造方法
- ServerSocket( ):默认构造方法,可以创建未绑定端口号的服务器套接字。服务器套接字的所有构造方法都需要处理IOException异常。
1 | try { |
- ServerSocket(int port):将创建绑定到port参数指定端口的服务器套接字对象,默认的最大连接队列长度为50,也就是说如果连接数量超出50个,将不会再接收新的连接请求。
1 | try { |
- ServerSocket(int port,int backlog):使用port参数指定的端口号和backlog参数指定的最大连接队列长度创建服务器端套接字对象,这个构造方法可以指定超出50个的连接数量,例如300。
1 | try { |
- ServerSocket(int port,int backlog,InetAddress bindAddr):使用port参数指定的端口号和backlog参数指定的最大连接队列长度创建服务器端套接字对象,如果服务器有多个IP地址,可以使用bindAddr参数指定创建服务器套接字的IP地址。
1 | try { |
接受套接字连接
当服务器建立ServerSocket套接字对象以后,就可以使用该对象的accept( )方法接受客户端请求的套接字连接。
语法格式为:serverSocket.accept( )
accept( )方法将阻塞当前线程,直到接收到客户端的连接请求为止,该方法之后的任何语句都不会被执行,必须有客户端发送连接请求;accept( )方法返回Socket套接字以后,当前线程才会继续运行,accept( )方法之后的程序代码才会被执行。
该方法被调用之后,将等待客户的连接请求,在接收到客户端的套接字连接请求以后,该方法将返回Socket对象,这个Socket对象是已经和客户端建立好连接的套接字,可以通过这个Socket对象获取客户端的输入/输出流来实现数据发送与接收。
该方法可能会产生IOException异常,所以在调用accept( )方法时必须捕获并处理该异常。例如:
1 | try { |
11.3.5 开发Socket
- 服务器端开发
- 在服务器端建立ServerSocket对象,并且为服务器指定端口号。
1 | ServerSocket serverSocket = new ServerSocket(9999); |
- 建立一个Socket对象,用来监听并响应客户端的请求。
1 | //对客户端进行回应:并指明用户(socket) |
- 通过流读取客户端发送的信息。
1 | //建立读消息的对象 |
- 根据客户端发送的信息,判断客户端的请求,并响应(双向连接需要发送数据)。
1 | //建立发送消息的对象 |
- 关闭流对象。
1 | osw.close( );os.close( );br.close( );socket.close( ); |
- 客户端开发
- 创建Socket对象,建立于服务器的连接。
1 | //申请与服务器的连接 |
- 建立流并发送请求信息。
1 | //建立发送消息的对象 |
- 建立读取信息的流,并读取服务器端的信息。
1 | BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream( ))); |
- 关闭相关流对象。
1 | in.close(); |
11.4 数据报编程
Java语言可以使用TCP和UDP两种通信协议实现网络通信,其中TCP通信由Socket套接字实现,而UDP通信需要使用DatagramSocket类实现。
UDP传递信息的速度更快,但是没有TCP的高可靠性,当用户通过UDP发送信息之后,无法确定能否正确地传送到目的地。虽然UDP是一种不可靠的通信协议,但是大多数场合并不需要严格的、高可靠性的通信,它们需要的是快速的信息发送,并能容忍一些小的错误,那么使用UDP通信来实现会更合适一些。
UDP将数据打包,也就是通信中所传递的数据包,然后将数据包发送到指定目的地,对方会接收数据包,然后查看数据包中的数据。
11.4.1 DatagramPacket类
DatagramPacket类是UDP所传递的数据包,即打包后的数据。数据包用来实现无连接包投递服务。每个数据包仅根据包中包含的信息从一台计算机传送到另一台计算机,传送的多个包可能选择不同的路由,也可能按不同的顺序到达。
DatagramPacket类提供了多个构造方法用于创建数据包的实例。下面介绍最常用的两个。
- DatagramPacket(byte[] buf,int length):用来创建数据包实例,这个数据包实例将接收长度为length的数据包。
- DatagramPacket(byte[] buf,int length,InetAddress address,int port):创建数据包实例,用来将长度为length的数据包发送到address参数指定地址和port参数指定端口号的主机。length参数必须小于等于buf数组的长度。
11.4.2 DatagramSocket类
DatagramSocket类最常用的3个构造方法
- DatagramSocket()
默认的构造方法,该构造方法将使用本机任何可用的端口创建数据报套接字实例。在创建DatagramSocket类的实例时,有可能会产生SocketException异常,所以在创建数据报套接字时,应该捕获并处理该异常。 - DatagramSocket(int port)
创建数据报套接字并将其绑定到port参数指定的本机端口,端口号取值必须在0~65535(包括两者)。 - DatagramSocket(int port,InetAddress laddr)
创建数据报套接字,将其绑定到laddr参数指定的本机地址和port参数指定的本机端口号。本机端口号取值必须在0~65535(包括两者)。