JavaSE 核心编程

Preview:

DESCRIPTION

JavaSE 核心编程. 课程整体目标. 掌握 Java 语言基础知识 理解面向对象的编程思想 运用 Java 编写命令行程序 运用 JDBC 编写数据库访问程序 运用 Swing 组件编写图形用户界面程序 运用 Swing 组件和 JDBC 实现 C/S 结构的应用程序. 本章目标. 了解 Java 语言 了解 Java 程序的类型 理解 Java 虚拟机( JVM ) 掌握 Java 程序运行环境的配置 编写第一个基于 Java 的程序. Java 简介. 掌握 Java 程序运行环境的配置 编写第一个基于 Java 的程序. Java 释义. - PowerPoint PPT Presentation

Citation preview

JavaSE 核心编程

课程整体目标

掌握 Java 语言基础知识理解面向对象的编程思想运用 Java 编写命令行程序运用 JDBC 编写数据库访问程序运用 Swing 组件编写图形用户界面程序运用 Swing 组件和 JDBC 实现 C/S 结构的应用程序

本章目标

了解 Java 语言了解 Java 程序的类型理解 Java 虚拟机( JVM )掌握 Java 程序运行环境的配置编写第一个基于 Java 的程序

Java 简介

掌握 Java 程序运行环境的配置编写第一个基于 Java 的程序

Java 释义“Java” 一词的本意是地名——“爪哇”;爪哇岛位于南太平洋,是印度尼西亚的一部分。

爪哇岛

关于爪哇全世界三大咖啡产地:– 巴西– 印尼爪哇岛– 中国海南岛

由此可见, Java 的原意是指一个咖啡的产地,也可以说是一种咖啡的品牌。

Java 语言简介Java 是一种程序设计语言,由 Sun Microsystem 公司于 1995 年推出;早在 1991 年, Sun 公司的 James Gosling 等人开始开发名为 Oak 的程序设计语言,希望用来控制嵌入在有线电视机顶盒和 PDA 等设备中的微处理器;1994 年, Oak 语言正式更名为Java ;Java 语言凭借其独有的安全性、可移植性和平台无关性,迅速走红。

Java 与 Internet

Internet 使 Java 成为网络上最流行的编程语言;同时, Java 对 Internet 的影响也意义深远;在 Java 出现以前,几乎没有工具可以在Internet 上执行动态交互程序;使用 Java 语言编写的 Applet 程序可以对用户的输入和操作作出响应。

Java 程序的类型 -Applet

Applet 是用 Java 编写,在 Internet 上运行的程序,可以在任何客户端系统上下载,在浏览器中运行

Java 程序的类型 -Application

Application 是指在计算机操作系统支持下运行的程序,它既可以基于图形界面,又可以基于控制台

Java 的平台无关性Java 是一种既面向对象又可以跨平台的语言,即:编写一次,随处运行;Java 不仅适用于单机应用程序和基于网络的程序,也可用于创建消费类设备的附件程序,如移动电话、掌上设备等。

Java

源程序代码

Windows

Linux

Solaris

JVM

Java 虚拟机Java 字节码(可跨平台)

编译 运行

Java 虚拟机Java 虚拟机( Java Virtual Machine )是可运行 Java 字节码的虚拟计算机系统;使用 Java 语言编写的程序,实际上是运行在 JVM之上,而不是运行在操作系统上;它有一个解释器组件,可以实现 Java 字节码和计算机操作系统之间的通信。用户 USER

字节码程序JVM ( Java 虚拟机)

操作系统硬 件

Java 虚拟机的运行过程

网 络

JIT 代码生成器

.java

源代码文件

.class

字节码文件

编译时 运行时

硬件

类加载器字节码校验器

解释器

Java 开发工具包开发和运行 Java 程序,必须依赖和使用由 Sun 公司提供的 Java 开发工具包( Java Development Kit )

操作系统Java 虚拟机

Java基础类库客户端编译器浏览器插件

开发工具集成开发环境

Java Platform 2

JDK ( Java 开发工具包)从 1.3 版本以后, Sun公司将其正式更名为 Java Platform 2 ;为适应不同的用途,又将其细分为三个版本:– J2SE ( Standard Edition ):标准版,适用于普通应用程序的开发;– J2EE ( Enterprise Edition ):企业版,适用于大型企业级应用程序的开发;– J2ME ( Micro Edition ):微型版,适用于移动设备或嵌入式设备的程序开发。

下载安装 JDK

打开浏览器,在地址栏键入: http://java.sun.com/javase/downloads/index.jsp ,登录到 SUN官方网站,免费下载JDK软件和文档下载完毕,即可安装 JDK ;

bin目录:存放可执行文件; lib目录:存放 Java 的类库文件; demo目录:存放演示程序; jre目录:存放 Java 运行环境文件。

配置环境变量安装 JDK 后,还要配置环境变量才能开发和调试Java 程序,右击 [我的电脑 ][属性 ][高级 ][ 环境变量 ]

编辑 Path变量 新建 classpath变量

开发 Java 程序的步骤创建 Java 源程序– Java 源程序可以使用任何文本编辑器创建与编辑,一般用 .java 作为扩展名,其实就是一个使用 Java 语言编写的文本文件;

编译源程序– Java 编译器读取 Java 源程序并翻译成 Java 虚拟机能够明白的指令集合,并以字节码的形式保存在文件中,通常,字节码文件以 .class 作为扩展名;

运行 class (字节码)文件– Java 解释器读取字节码文件,取出指令并翻译成计算机能够执行的机器代码,完成运行过程。

编写第一个 Java 程序打开记事本,新建一个文本文件,将文件名改为Hello.java ,在文件中键入如下内容并保存:

class Hello

{

public static void main(String[] args)

{

System.out.println("Hello world!");

}

}

Java 源程序

扩展名为 .java的源文件

使用记事本进行编辑

使用 javac 编译器编译源文件接下来需要将源程序进行编译;进入命令提示行,切换到源文件所在的目录,执行javac 命令,使用 javac 编译器编译源文件;javac 命令的格式是: javac 完整的源文件名

切换到源文件所在的目录切换到源文件所在的目录

切换到源文件所在的目录

class (字节码)文件顺利通过编译后将生成一个扩展名为 .class 的字节码文件(类描述文件)。

通过编译后生成的扩展名为 .class 的字节码文件扩展名为 .java的源文件

使用 java 解释器运行字节码文件进入命令提示行,在字节码文件所在的目录下,执行 java 命令,使用 java 解释器运行字节码文件java 命令的格式是: java 类名称这里只需要类名称,而不是带扩展名的文件名

执行 java 命令运行字节码文件

程序运行效果

分析程序 3-1

关键字 class 用来定义一个类, Hello 是类的名称;在 Java 程序中,所有的语句都必须放在某个类中;整个类及其所有成员都包含在一对花括号中(即{ 和 } 之间),它们标志着类定义块的开始和结束。定义类的一般语法:class 类名{

……}

分析程序 3-2

程序必须从 main函数开始执行;关键字 public 是访问修饰符,用来控制类成员的可见范围及作用域;关键字 static允许在不创建类的实例的情况下,调用 main函数;关键字 void 用来说明 main函数是没有返回值的;args 是字符串数组,用来从命令行接收参数;main函数的一般形式如下:public static void main(String[] args){

……}

分析程序 3-3

String[] 是字符串类型的数组;在 Java 中,类名称一般首字母大写;使用 System.out.println() 将信息输出到控制台上,即打印出来。

Java API 文档Java API 文档描述了 Java 的许多内置功能,以及提供各种类的帮助说明,它是程序员经常查阅的资料可登录 Sun 公司的官方网站免费下载

Java 文件中的注释与其它编程语言一样,也可以在 Java 文件添加注释, Java 文件中的注释有三种:– 单行注释:以 // 开始,在行尾结束–多行注释:以 /* 开始,以 */ 结束,可以有多行– 文档注释:以 /** 开始,以 */ 结束,可以有多行

可以使用 javadoc 命令,将文档注释从源代码中提取出来,生成 HTML 文件,形成类似于 Java API 文档的帮助说明文件。

使用 javadoc 命令生成 API 文档javadoc 命令的格式是: javadoc 完整的源文件名 执行 javadoc 命令提取文档注释的内容

生成的文档

Java 语言的特点 2-1

简单– 去掉了 C 和 C++ 中许多复杂功能,如指针、运算符重载等,没有 goto 语句,没有 struct 和

union 等面向对象– Java 是完全面向对象的编程语言,比 C++ 更彻底,纯度更高健壮性– 没有指针,避免对指针的误操作造成程序崩溃– 程序员分配的内存空间,无需释放,由 Java 虚拟机的垃圾回收机制来统一完成此项工作,避免了内存泄漏

Java 语言的特点 2-2

安全性– 由于 Java取消了指针运算,有效地提高了程序的安全性– Java 程序运行在 Java 虚拟机上,虚拟机可以有效地过滤掉恶意代码,防止程序员有意编写的病毒程序

分布性– Java 程序可以跨平台,跨操作系统,完全支持网络应用程序的设计多线程– Java 程序使用一个称为“多线程”的进程同时处理多项任务

使用 Eclipse 来开发

总结Java 是面向对象的、跨平台的程序设计语言;Java 程序是运行在 Java 虚拟机之上的;要下载安装 JDK ,才可以开发和运行 Java 程序;JDK 提供一系列的工具,这些工具位于 JDK 安装路径的 bin目录下,常用的有:javac :编译java :运行javadoc :提取文档

可以使用任何文本编辑器编写 Java 源程序;

作业熟悉 JDK目录,以及 JDK 环境变量使用记事本编写 Hello World 程序使用记事本编写九九乘法表使用记事本编写空心菱形

Java 语言基础

Java 是面向对象并且可以跨平台的编程语言Java 程序是运行在 JVM ( Java 虚拟机)之上的要运行和开发 Java 程序,必须下载和安装由 Sun 公司提供的 JDK ( Java 开发工具包)配置环境变量: Path 和 classpath三个常用的 JDK 工具: javac 、 java 和 javadoc

本章相关词汇(蓝色为关键字)

单 词 说 明byte 数据类型,字节型boolean 数据类型,布尔型true 真,布尔型的字面值false 假,布尔型的字面值null 空

本章目标

Java 中的变量和数据类型Java 中变量的作用域规则数据类型转换Java 中的输出语句Java 中的运算符和表达式

Java 语言基础

Java 中的变量和数据类型

变量

变量是内存中的一块存储区域,是存储数据的基本单元;声明变量的语法:数据类型 标识符 [= 值 ];如:int num;float money = 123.45f;char ch1, ch2 = ‘A’;

数据类型

数据类型用来确定要存储在内存单元中的数据的类型;在 Java 中,数据类型被区分成两个种类:原始数据类型引用数据类型

原始数据类型原始数据类型是最简单的数据形式,与 C 的基本数据类型很相似:

数据类型 大小位 取值范围 说 明

boolean 布尔型 1 true/false 用于存储真值 / 假值byte 字节型 8 -128~127 数据存储在内存中的最原始形态char 字符型 16 ‘\u0000’~‘\uFFFF’ 用于存储字符,以 Unicode 编码方式short 短整型 16 -32768~32767 略(参见 P20 表 2.1 )int 整 型 32 -231~231-1 略(参见 P20 表 2.1 )long 长整型 64 -263~263-1 略(参见 P20 表 2.1 )float 浮点型 32 略 略(参见 P20 表 2.1 )double 双精度 64 略 略(参见 P20 表 2.1 )

引用数据类型

在 Java 中,除了上述 8 种原始数据类型的变量以外,其余的全都属于引用数据类型,其中包括:– 数组– 对象存储在引用类型变量中的值是该变量表示的值的地址;可以借用 C 语言中指针的概念来理解引用数据类型。

回顾 C 语言中的内存分配

C 语言中内存分配的两种形式:栈内存:– 特点:和作用域同生命周期,自动申请,自动释放。– 申请方式:局部变量,形式参数。– 栈内存中的空间一般都有名称,通过变量名访问其存储的数据。堆内存(即动态内存分配):– 特点:手工申请,手工释放,生命周期由程序员决定。– 申请方式:由 malloc函数或 calloc函数申请, realloc函数进行调整, free函数负责释放。– 堆内存中的空间一般没有名称,只能通过指针访问。

Java 中的内存分配

Java 中的原始数据类型与 C 语言中的基本数据类型一样,其变量都是在栈中分配空间;而除了 8 种原始数据类型以外,其它的任何数据都不可以在栈中分配,只能到堆中开辟空间,而在堆中分配的空间又只能通过指针访问;通过指针访问堆内存的方式,在 Java 中被称作引用数据类型;可以认为, Java 中的引用就类似于 C 语言中的指针,只是对指针进行了一定程度上的包装,避免了因直接操作指针而造成的数据意外损坏,从而导致程序错误的情况。

标识符命名规则

变量、函数、数组、对象、类和接口等等都需要命名,这些名称统称为标识符;Java 中对于标识符的命名规则作了如下限制:– 只能由英文字母、数字、下划线“ _” 和“ $”符号组成;– 必须以英文字母、“ _”或“ $” 开头,即不能以数字开头;– 除“ _” 和“ $” 以外,不能包含其它任何特殊字符;– 不能与关键字冲突;– 严格区分大小写。

变量的作用域和生命周期变量 的作用域指变量起作用的范围,说明变量在什么部分可以被访问;变量的生命周期是指变量在内存中存在的周期,即什么时候分配空间,什么时候销毁。

C Java

变量声明

程序中所有使用到的变量应该在程序开始部分预先进行声明。

可以在程序的任何部分声明,即:何处使用,何处声明。

作用域规则

变量的作用域属于声明它的函数范围之内,变量的最小作用域是函数。

变量的作用域属于声明它的代码块,变量的最小作用域是包含它的一对 {} 之间。

/* 有 1 , 2 , 3 , 4四个数字,能组成多少个不相同且无重复的三位数? */public class ScopeVariable{ public static void main(String[] args) { int count = 0; for (int i = 1; i <= 4; i++) // 在需要使用变量时声明 { for (int j = 1; j <= 4; j++) { for (int k = 1; k <= 4; k++) { if (i != j && i != k && j!= k) { int temp = i * 100 + j * 10 + k; System.out.print(temp + "\t"); count++; } } } } /* 思考:在此处可以访问到变量 k吗? */ System.out.println("\n 有: " + count + " 个符合要求的数 "); }}

作用域示例

数据类型转换

程序中经常会遇到要将数据类型进行转换的情况(如:在一个表达式中存在不同类型的操作数时), Java 的数据类型转换比 C 控制得更为严格;数据类型转换有两种情况:自动类型转换强制类型转换

自动类型转换

自动类型转换也称作隐式类型转换;将一种类型的变量赋值给另一种类型的变量时,只要满足以下条件,就会发生自动类型转换:– 两种类型之间相互兼容;– 目标类型大于源类型(即类型提升);如:char ch = 'A';int num = ch;float f = num;double d = f;//类型转换自动发生,无需显式说明

强制类型转换

强制类型转换也称作显式类型转换;如果被转换的值的数据类型大于其目标类型,将会丢失部分信息;如:int x = 65;char ch = x;这样的语句将报错,因为 char 类型小于 int 类型,编译器无法将其自动转换,只能进行强制转换:int x = 65;char ch = (char)x;

类型提升规则

不难看出,在有类型提升的情况下,编译器将自动进行类型转换,否则就要进行强制类型转换;类型提升应遵循如下规则:两种类型必须兼容,且目标类型大于源类型,例如:byte 、 char 和 short 类型都可以提升为 int 型。对于表达式而言,其最终结果将会与表达式中最高的数据类型相同。

输出语句

如果要将某些信息打印到屏幕上,就必须使用输出语句;使用 System.out.println() 实现换行输出效果;使用 System.out.print() 实现输出但不换行效果;无论输出什么类型的数据,只需将数据作为参数传递给它们即可,而无需像 C 语言中那样,使用 %s 、 %d 之类的控制字符,如:int a = 10;float f = 3.14f;System.out.println(a);System.out.print(f);System.out.println('X');

使用转义字符控制输出格式有些时候,我们可能会需要对输出格式进行控制,这就使用到了转义字符;以下是一些常用的转义字符:

转义字符 说 明\n 换行符,将光标移到下一行\r 回车符,将光标移到当前行的开头\t 制表符,将光标移到下一制表位\\ 输出反斜线\' 输出单引号\" 输出双引号

运算符和表达式

运算符是通过一定的运算规则操作一个或多个操作数,并生成结果的特定符号;运算符和操作数的有效组合称为表达式;Java 提供了一系列丰富的运算符,包括:– 算术运算符– 关系运算符– 逻辑运算符– 条件运算符– 赋值运算符

算术运算符算术运算符主要用于数学计算,一般使用数值作为操作数:

运算符 说 明一元

- 一元减,即取负++ 自增,如: ++a 等价于 a = a + 1-- 自减,如: --a 等价于 a = a – 1

二元

+ 加法,返回两个操作数的和- 减法,返回两个操作数的差* 乘法,返回两个操作数的积/ 除法,返回两个操作数的商% 取模,返回两个操作数整除后的余数

关系运算符关系运算符可以测试两个操作数之间的关系(但不会改变操作数的值),关系表达式的结果为 boolean 型true/false:

运算符 说 明== 等于,检查两个操作数是否相等!= 不等于,检查两个操作数是否不相等> 大于,检查左操作数是否大于右操作数>= 大于等于,检查左操作数是否大于或等于右操作数< 小于,检查左操作数是否小于右操作数<= 小于等于,检查左操作数是否小于或等于右操作数

逻辑运算符逻辑运算符与 boolean 型操作数一起使用:

运算符 说 明Java C

一元 ! 逻辑非( NOT ) 逻辑取非( NOT )

二元

& 逻辑与( AND )与 C 语言中的 && 相同 按位与

| 逻辑或( OR )与 C 语言中的 || 相同 按位或

&&短路与如左操作数为 false ,则不运算右操作

数逻辑与( AND )

||短路或如左操作数为 ture ,则不运算右操作

数逻辑或( OR )

位运算符

• 常见的位运算符有:– & 、 | 、 ^ 、 ~–参见示例 : BitwiseOperatorTest

• 还有移位运算符– << 、 >> 、 >>>–参见示例 : ShiftTest

短路与和短路或

Java 提供了两个在其它计算机语言中没有的逻辑运算符,这就是逻辑与和逻辑或的短路版本;对于短路与( && ),如果其左侧为 false ,则不再计算其右侧,整个表达式的结果为 false ,只有左侧为 true时才计算右侧;对于短路或( || ),如果其左侧为 true ,则不再计算其右侧,整个表达式的结果为 true ,只有左侧为 false时,才计算右侧。

int a = 10;int b = 20;

if (++a == 12 & ++b == 22){ ……}

System.out.println(a);System.out.println(b);

/*这里使用的是逻辑与,试分析最后的输出结果 */

int a = 10;int b = 20;

if (++a == 12 && ++b == 22){ ……}

System.out.println(a);System.out.println(b);

/*这里使用的是短路与,试分析最后的输出结果 */

1121

1120

短路与示例

赋值运算符

赋值运算符的运算顺序是从右至左的:运算符 说 明

= 赋值,将右侧的值赋给左侧的变量+= 相加并赋值,如: a += b 等价于 a = a + b

-= 相减并赋值,如: a -= b 等价于 a = a – b

*= 相乘并赋值,如: a *= b 等价于 a = a * b

/= 相除并赋值,如: a /= b 等价于 a = a / b

%= 取模并赋值,如: a %= b 等价于 a = a % b

条件运算符 ? :

条件运算符是三元运算符;它的一般形式是:表达式 1 ? 表达式 2 : 表达式 3根据表达式 1 的结果来判断执行表达式 2还是表达式3 ,如果表达式 1 的结果为 true ,则执行表达式 2 ,否则执行表达式 3 ;条件运算符在某些情况下能够替代小型的 if…else 语句。

运算符的优先级很多情况下,一个表达式由多个运算符组成,优先级决定运算符的计算顺序:

优先级 运 算 符1 括号 : () 和 []

2 一元运算符: - 、 ++ (前置)、 -- (前置)、!3 算术运算符: * 、 / 、 % 、 + 和 -

4 关系运算符: > 、 >= 、 < 、 <= 、 == 和 !=

5 逻辑运算符: & 、 | 、 && 和 ||

6 条件运算符: ? :

7 赋值运算符: = 、 *= 、 /= 、 %= 、 += 和 -=

总结

Java 中的变量和数据类型Java 中变量的作用域规则数据类型转换Java 中的输出语句Java 中的运算符和表达式

流程控制与数组

Java 中的数组new 关键字

控制流语句

一般情况下,程序是从上往下依次顺序执行的;但很多时候,我们需要改变程序执行的流程,这就使用到了控制流语句;控制流语句包括:–判断语句–循环语句–跳转语句

判断语句

判断语句也称作条件语句或分支语句;判断语句主要用来控制程序在何种情况下执行某一部分;判断语句包括:– if 语句– if…else 语句– if…else if 语句– switch…case 语句

循环语句

循环语句也称作迭代语句;循环语句可用于重复执行相同的操作;循环语句包括:– while 语句– do…while 语句– for 语句

跳转语句

跳转语句允许以非线性的方式执行程序,可以将控制权转到程序的其它部分;跳转语句包括:– break 语句– continue 语句Java抛弃了 C 语言中的 goto 语句。

数组

数组用于将相同类型的数据存储在连续的存储单元中;可以通过指定数组的名称和长度来声明数组;一旦声明数组的大小,就不能再修改;数组元素通过数组名和索引来访问,索引从 0 开始;数组可以是一维,也可以是多维。

回顾 C 语言中的数组

在 C 语言中,从存储方式上讲,声明数组有两种情况;在栈内存中分配空间,如:int ary[10]; //声明有 10 个整型元素的数组在堆内存中分配空间,如: int *p = NULL; //声明整型指针// 指针指向堆空间p = (int*)malloc(10 * sizeof(int));C 语言允许有栈数组,也允许有堆数组。

new 关键字

在 Java 中,不允许将数组分配在栈内存中,只能分配在堆内存中;Java 中没有类似于 malloc 、 calloc 、 realloc 和 free等函数,取而代之的是 new 关键字;new 是 Java 中内置的操作符;new 操作符用来从堆中分配指定类型的内存空间,并返回相应类型的引用,其作用类似于 malloc函数。

声明数组示例

public class ArrayDemo { public static void main(String[] args) { //声明整型数组的引用,类似于 C 中的 int *p; int[] ary; // 为数组引用在堆内存中分配实际的内存空间 // 类似于 C 中的 p = (int*)malloc(10 * sizeof(int)); ary = new int[10]; // 使用循环为数组元素赋值 for (int i = 0; i < ary.length; i++) { ary[i] = i; } // 使用循环将数组元素逐个输出到控制台 for (int i = 0; i < ary.length; i++) { System.out.println(ary[i]); } }}

Java 中声明数组的几种方式在 Java 中声明数组有如下方式:// 仅声明数组的引用,但没有分配内存空间float[] fAry;//声明数组的同时,根据指定的长度分配内存,但数组中没有值char[] chAry = new char[10];//声明数组并分配内存,同时将其初始化int[] ary1 = new int[]{1, 2, 3, 4, 5};//与前一种方式相同,仅仅只是语法相对简略int[] ary2 = {1, 2, 3, 4, 5};

数组之间赋值在 C 语言中,不可以将数组直接赋值给另一个数组;在 Java 中,语法上允许这样做,但实际得到的效果是两个数组引用指向同一块内存。int[] ary1 = {2, 4, 6, 8, 10};int[] ary2;ary2 = ary1; //允许这样赋值

10

8

6

4

2堆栈

ary1

ary2

数组赋值示例public class ArrayDemo { public static void main(String[] args) { int[] ary1 = {2, 4, 6, 8, 10}; //声明并初始化数组 1 int[] ary2; //声明数组 2 ary2 = ary1; // 将数组 1赋值给数组 2 ary2[3] = 1024; // 通过数组 2修改其中一个元素的值 //打印出数组 1 中的元素 System.out.println(" 数组 1 中的元素: "); for (int i = 0; i < ary1.length; i++) { System.out.println(ary1[i]); } //打印出数组 2 中的元素 System.out.println(" 数组 2 中的元素: "); for (int i = 0; i < ary2.length; i++) { System.out.println(ary2[i]); } }}

关于数组长度

在 Java 中,程序员可以不再关注数组的长度,数组自带的 length属性将负责保管数组的长度;C 语言不检查数组的边界,数组索引完全由程序员掌握; Java 对数组下标越界情况强加约束;如果试图访问不在有效索引以内的元素,将会引发运行错误。

Java 中的垃圾回收机制

在 C 语言中,被malloc或 calloc函数分配的堆内存在使用完毕以后,必须使用 free函数进行释放,否则将会造成内存泄漏;同样的,在 Java 中由 new 分配的内存也将存在内存泄漏的可能;但释放内存的任务是由 Java 虚拟机来完成的,程序员可以不再关心此项工作;如果某块内存空间没有任何引用指向它,那么虚拟机就将其认定为垃圾;虚拟机并不是随时随地地立即回收垃圾,而是在系统比较空闲的时候,才会执行回收任务。

命令行参数

在命令行使用 java 命令执行程序时,可以将一些参数传递给main函数;main函数携带的 String[] (字符串数组)类型的形参args 正是为了接收这些来自命令行的参数;可以从命令行传递任意多个字符串给main函数,其间使用空格隔开,也可以不传递任何参数;这些传递进来的多个字符串最后以字符串数组的形式,供main函数中加以使用。

命令行参数示例public class ArgumentDemo{ public static void main(String[] args) { int len = args.length; //获得命令行参数的个数 if (len > 0) //判断是否有命令行参数 { // 如果有,将其逐一打印到控制台 System.out.println(len + " 个命令行参数,具体如下: "); for (int i = 0; i < args.length; i++) { System.out.println(args[i]); } } else { //否则给出提示信息 System.out.println("您没有传递任何命令行参数。 "); } }}

总结

Java 中数组是引用数据类型,只能存放在堆内存中;数组必须由 new 关键字为其分配内存;释放内存的工作由 Java 虚拟机的垃圾回收机制来完成;可以利用命令行参数接收来自用户的输入。

作业

利用命令行参数输入一个整数,并判断该数字是否为奇数,如果是奇数,则用其作行数打印空心菱形– PS :将字符串转换为数字的方法

• int num=Integer.parseInt(String number);利用命令行参数输入三个整数,并对三个数字进行降序排列。– PS :将字符串转换为数字的方法

• int num=Integer.parseInt(String number);利用命令行参数输入多个参数,并赋值给一数组,同时判断该数组是否为回文数组– PS:例如数组 {“123”,”222”,”333”,”222”,”123”}就是回文数组,即元素倒置过后与原元素一样

逻辑趣味思考题

有两个桶,一个容量为 5升,一个容量为 3升,现在用桶子来乘水,请您用这两个桶准确的乘出 4升水来。有 10堆鱼,每堆鱼 10条,正常情况下每条鱼 10斤,但是现在这 10堆鱼中有 1堆鱼全是每条 9斤,现在给你一把秤(假设这把秤可以称很重很重的东西),请问您能通过一次称量就找出这堆缺斤少量的鱼吗?

回顾

Java 中的数据类型分为:原始数据类型和引用数据类型;在数据类型转换中,由低类型转至高类型,将会自动(隐式)类型转换,而从高类型转至低类型,则必须强制(显式)类型转换;Java 中的数组是引用数据类型,必须由 new 关键字在堆中分配内存;Java 虚拟机的垃圾回收机制将自动释放不再使用的内存;使用命令行参数接收来自用户的输入。

相关词汇

单 词 说 明 Class 类,种类 public 公共的,公有的 private 私有的,私人的 object 对象,物体 encapsulation 封装,包装 attribute 属性,特征 method 方法 member 成员 constructor 构造方法,构造器

类和对象 I

了解什么是 面向对象面向对象的 基本概念– 类– 对象– 封装– 继承– 多态如何定义 类,如何创建 对象成员运算符访问控制权限: public 和 private构造方法

类和对象 I

了解什么是 面向对象面向对象的 基本概念– 类– 对象– 封装– 继承– 多态如何定义 类,如何创建 对象成员运算符访问控制权限: public 和 private构造方法

什么是面向对象?

Object Oriendted Programming对象 以…为导向的 程序设计

面向对象编程 就是使用对象进行程序设计,简写成 OOP 。

面向对象的编程语言

如今,面向对象的编程思想已经成为主流;面向对象的编程语言正大行其道,最具代表性的有:C++JavaC#连数据库也开始面向对象了,如 Oracle 。

面向对象的编程思想

OOP

旨在计算机程序中模拟现实世界中的概念

在计算机程序中用相似的实体模拟现实世界中的实体

在 OOP 中,现实世界的所有事物全都被视为对象

设计和实现软件系统的方法

C 语言的编程方式

C 语言是结构化的编程语言( Structured Programming ,简作 SP );强调精巧的数据结构和优越的算法,关注程序的运行效率;不适合于大规模的程序开发;程序模块之间协调性差,程序的重用性、安全性、健壮性和可维护性都存在较大的缺陷。

SP 和 OOP 对比

SP OOP设计思路 自顶向下、层次化、分解 自底向上、对象化、综合程序单元 函数模块 对象设计方法 程序 = 算法 + 数据结构 程序 = 对象 = 数据 + 方法优点 相互独立,代码共享 接近人的思维方式

模拟客观世界缺点 数据与程序不一致

维护困难客观世界的无序性概念不成熟

面向对象的三大原则

封 装 继 承 多 态

对象对象是面向对象编程的核心部分,是实际存在的具体实体,具有明确定义的状态和行为;对象其实就是“数据”和“函数”的封装体,其中:– 数据表示自身的状态,也称作“属性”或“成员数据”;– 函数表示自身的功能,也称作“方法”或“成员函数”。

姓名:布兰尼职衔:收银员年龄: 35体重: 60千克操作:收款打印帐单

收银员对象

顾客姓名:朱丽叶年龄: 28体重: 52千克操作:购买商品

顾客对象状态

行为

类人们为了更好地认识世界,将现实生活中的事物(对象)划分成类;同一类中的事物总是具有一些共性;类以共同的特性和行为定义实体;类是具有相同属性和和行为的一组对象的集合。

属性

事物的特性在类中用变量表示;每个对象的每个属性都拥有其特定的值;属性名称由类的所有对象共享;对象或实体拥有的特征在类中表示时称为属性。

学生对象

姓 名年 龄住 址……

属 性

方法

事物的行为和动作在类中用函数表示;每个对象都有相同的动作和行为;对象执行的操作在类中表示为方法。

学生对象

吃 饭方 法睡 觉

上 课……

类和对象的区别

类是用来描述实体的“模板”或“原型”;对象是实际的实体,每一个对象都是类的一个具体实例;类用来定义对象所有的属性和方法,同一类的所有对象都拥有相同的特征和操作;可以将类理解成生产产品的模具,而对象则是根据此模具生产的一个个产品。

类与结构

最简单的理解方式:

区别在于:结构体一般只有数据(属性)而没有函数(方法)。

类结构体

结构体变量 对象

C Java

封装将某些东西包装在一起,然后以新的完整形式呈现出来;隐藏属性、方法或实现细节的处理方式称为封装;封装其实就是有选择性地公开或隐藏某些信息,它解决了数据的安全性问题。

一个人类的对象

姓名:张三体重: 50kg……密码: ******

属性

走路吃饭……

方法

可以公开无所谓

不可以公开

继承

继承就是重用现有的类来生成新类的一种特征;通俗地讲就是从现有的类(即父类或基类)创建新类(子类或派生类)的过程;现实生活中,继承可以达到财产重用的目的,而在 Java中,继承可以使代码重用。

多态

多态是指同一函数在不同的类中有不同的实现;多态的好处就是使类更灵活,更便于扩充。

抽象

把相同的或相似的对象归为一类的这个过程就是抽象,所以,抽象就是分析问题的方法;抽象的基本原则:– 只关心主要问题,而不关心次要问题;– 只关心主要矛盾,而不关心次要矛盾;– 只关心相同的东西,而不关心不同的东西;– 只关心问题是什么,能够完成什么,而不关心怎样去完成。抽象的过程其实就是面向对象编程的核心思想。

在 Java 中定义类/*定义学生类 */class Student{ String name; //姓名 int age; // 年龄 float weight; //体重 /*吃饭的方法 */ void dining() { System.out.println("吃饱了 ..."); weight++; } /* 走路的方法 */ void walk() { System.out.println(" 走累了 ..."); weight--; }}

成员变量

成员函数

定义类的语法

class 类名{ 成员列表// 包括成员变量和成员函数}

在 Java 中定义类struct 结构体名{ 成员列表//只有成员变量,没有成员函数};

在 C 中定义结构体

习惯上,类名的首字母大写,如: Student

在 Java 中创建对象/*Test 类,用来容纳 main方法 */public class Test{ /*main方法,程序入口 */ public static void main(String[] args) { Student std; //声明 Student 类型的引用 std = new Student(); // 创建 Student 类型的对象 std.name = "张三 "; // 为姓名赋值 std.age = 18; // 为年龄赋值 std.weight = 50; // 为体重赋值 std.dining(); //调用成员方法 System.out.println(std.weight); std.walk(); System.out.println(std.weight); }}

创建对象的语法

和数组相似,对象也是引用数据类型,只能使用 new 运算符从堆中分配内存;创建对象的一般语法:类名 引用名 = new 类名 () ;使用已经定义好的类,创建该类对象的过程称为“实例化”。

注意:这里要有括号

成员运算符 .

在 C 语言中,必须要先声明结构体变量,才可以访问结构体中的成员;同样的,只有先实例化类的对象,才可以访问到类中的成员(属性和方法);使用成员运算符( . )来访问成员属性或成员方法;一般语法是:对象名 . 成员名如: std.age = 18; // 为成员属性赋值 std.dining(); //调用成员方法

访问权限: public 和private

C 语言中结构体的成员可以从任何地方进行访问,这将给数据的安全留下极大的隐患;为了避免从类外部直接访问类成员而造成的数据损坏, Java 对类成员的访问制定了约束;关键字 public 和 private 是访问修饰符,用来说明某个成员是否可以从类外部进行访问;public修饰的成员可以在任何地方进行访问,不受任何约束;private修饰的成员只能够被本类中的其它成员访问,而不能从类的外部进行访问。

访问权限

无法从类的外部访问私有成员;其它类的私有成员对于当前类也是隐藏的。

可以从类外部访问

类属性或方法私有

属性或方法公有

不可从类外部访问

访问权限示例class Student { private String name; //姓名,私有的,不可以从类外部直接访问 private int age; // 年龄,私有的,不可以从类外部直接访问 private float weight; //体重,私有的,不可以从类外部直接访问 //吃饭的方法,公有的,可以从任何地方访问 public void dining() { System.out.println("吃饱了 ..."); weight++; //dining方法是类内部成员,可以直接访问本类私有成员 } // 走路的方法,公有的,可以从任何地方访问 public void walk() { System.out.println(" 走累了 ..."); weight--; //walk方法是类内部成员,可以直接访问本类私有成员 }}public class Test { public static void main(String[] args) { Student std = new Student(); // 实例化一个 Student 对象 std.age = 18; //试图从类外部访问私有成员,将会报出一个错误 std.dining(); //允许访问公有成员 }}

访问权限(续)

加上访问修饰符有时候可能会给操作数据带来不便,但可以在很大程度上保证数据的安全;一般地,我们会将成员属性声明为 private ,而将成员方法声明为 public ,但这样做并不是绝对的;有时候,类外部可能要操作到某些私有数据成员,那么就可以增加一个公有的方法,再由这个方法来操作私有数据,避免因类外部的误操作而造成的数据损坏;因为 main方法要由类外部的虚拟机来调用,所以 main方法必须声明成 public 。

修改 Student 类class Student { //定义学生类 private String name; //姓名,私有 private int age; //年龄,私有 private float weight; //体重,私有 public void setName(String name) { //为姓名赋值的方法,公有 this.name = name; } public void setAge(int a) { //为年龄赋值的方法,公有 age = a; } public void setWeight(float w) { //为体重赋值的方法,公有 weight = w; } public void display() { //将所有信息打印出来的方法,公有 System.out.println("姓名:" + name + ",年龄:" + age + ",体重:" + weight); } public void dining() {……} //吃饭的方法,公有,代码略 public void walk() {……} //走路的方法,公有,代码略 }

public class Test { public static void main(String[] args) { Student std = new Student(); //实例化学生类对象

std.setName("张三"); //为姓名赋值 std.setAge(18); //为年龄赋值 std.setWeight(55); //为体重赋值 std.dining(); //调用吃饭的方法 std.display(); //将信息打印出来

}}

对象初始化

在上例中,只能逐个地为数据成员赋值,如果想在对象实例化的同时就初始化成员属性,就使用到了构造方法;构造方法是特殊的成员方法,它与类同名,在对象实例化时由虚拟机自动调用;请注意:构造方法没有返回值类型,也不能有返回值。

构造方法示例/*定义 ConstructorDemo 类,对构造方法进行测试 */class ConstructorDemo{ /* 构造方法,方法名与类名完全一致 无需指定返回值类型,也不能有返回值 */ public ConstructorDemo() { System.out.println("这是构造方法 "); }}

/*Test 类,用来容纳 main方法一般将包含有 main方法的类声明为 public*/public class Test{ /*main方法,程序入口 */ public static void main(String[] args) { /* 实例化 ConstructorDemo 类的对象 */ ConstructorDemo cd = new ConstructorDemo(); }}

构造方法

正是由于在实例化对象的同时会自动调用构造方法,所以构造方法一般用来给数据成员分配资源或初始化数据成员;构造方法的一般形式:访问权限 类名(形参列表) { 方法体 }因为是由虚拟机来调用构造方法,所以构造方法一般应定义成 public 。

为 Student 类添加构造方法class Student { //定义学生类 private String name; //姓名,私有 private int age; // 年龄,私有 private float weight; //体重,私有 // 构造方法,根据传递进来的参数,为数据成员赋值 public Student(String n, int a, float w) { // 分别为每个数据成员赋初始值 name = n; age = a; weight = w; } public void setName(String n) {……} // 为姓名赋值的方法,公有,代码略 public void setAge(int a) {……} // 为年龄赋值的方法,公有,代码略 public void setWeight(float w) {……} // 为体重赋值的方法,公有,代码略 public void display() {……} // 将所有信息打印出来的方法,公有,代码略 public void dining() {……} //吃饭的方法,公有,代码略 public void walk() {……} // 走路的方法,公有,代码略}public class Test { public static void main(String[] args) { //利用构造方法,为数据成员指定初始值 Student std = new Student("张三 ", 18, 55); std.display(); // 将信息打印出来 }}

构造方法(续)

每个对象在生成时都必须执行构造方法,而且只能执行一次;如果构造方法调用失败,那么对象也无法创建;不可以显式地直接调用构造方法;在没有定义构造方法的情况下,类会自动产生一个无参数的默认构造方法,这个默认的构造方法什么都不做;一旦显式地定义了构造方法,默认构造方法自动消失。

总结

对象由状态(属性)和行为(方法)组成;类是具有相同属性和方法的对象的集合;封装可以隐藏对象实现的具体细节;必须先实例化类的对象,才可以访问到其中的成员;成员运算符用于访问对象的成员;成员可以定义为公有,也可以定义为私有;构造方法一般用来初始化对象中的数据成员;如果不定义构造方法,将有默认的构造方法,一旦定义,默认的构造方法自动消失。

练习

编写一个包含名为 Calculator 类的程序。定义两个整型属性以及对该两个变量的 setter 和 getter ,编写用于对两个属性执行加、减、乘、除运算方法。在 main方法里创建该类的对象并访问这些方法。编写一个名为 Box 的类,含整型的 length,width, height , volume四个属性,要求在构造方法中将其初始化,并定义一个计算体积的 calcVolume方法和一个输出体积的 print方法,在 main方法中进行调用

思考

有三筐水果,一筐装的全是苹果,第二筐装的全是橘子,第三筐是橘子与苹果混在一起。筐上的标签都是骗人的,(比如,如果标签写的是橘子,那么可以肯定筐里不会只有橘子,可能还有苹果)你的任务是拿出其中一筐,从里面只拿一只水果,然后正确写出三筐水果的标签。一列时速 15英里的火车从洛杉矶出发,驶向纽约。另外一列时速 20英里的火车从纽约出发,驶向洛杉矶。如果一只鸟以每小时 25英里的速度飞行,在同一时间从洛杉矶出发,在两列火车之间往返飞行,到火车相遇时为止,鸟飞了多远?

回顾

对象由属性(数据)和方法(函数)组成;类是具有相同属性和方法的对象的集合;封装可以隐藏对象实现的细节部分;对象是引用数据类型,使用 new 运算符创建对象实例,使用成员运算符访问对象中的成员;访问修饰符用来控制访问权限,可以有效地保证数据安全;构造方法一般用来初始化对象中的数据成员

相关词汇

单 词 说 明this 这,这个static 静态的package 包裹,包import 引入,导入overloaded 重载,超载overloaded method 方法重载

类和对象 II

方法重载重载 构造方法对象 在内存中的 存放形式this 关键字静态成员, static 关键字包– 打包, package 关键字– 导入包, import 关键字

类和对象 II

方法重载重载 构造方法对象 在内存中的 存放形式this 关键字静态成员, static 关键字包– 打包, package 关键字– 导入包, import 关键字

提示

在 Java 中,函数无需 C 语言中的前向声明,只要直接定义即可;Java 中,函数被称为方法;定义一个方法的一般形式:[ 访问权限 ] 返回值类型 方法名称 ( 形参列表 ) {方法主体 ;}我们更关注的是方法原型,对于方法原型的三个主要组成部分应当明确区分:– 返回值类型– 方法名称– 形式参数

在 Java 中定义方法/*Java 中的方法示例代码片段*/

/*定义求平方值的方法 */public int square(int x){ return (x * x);}

返回值类型 方法名称

形式参数

案例/* 编写求绝对值的方法来求整数的绝对值 *//*AbsoluteValue 类,其中包含求绝对值的方法Java 中的任何方法都必须在类中定义 */public class AbsoluteValue{ /*求绝对值的方法 */ public int abs(int x) { return ((x >= 0) ? x : -x); } /*main方法,程序入口 */ public static void main(String[] args) { // 必须先实例化类的对象,才可以调用到其中的成员方法 AbsoluteValue obj = new AbsoluteValue(); int a = -8, b; b = obj.abs(a); //调用 abs方法求 a 的绝对值 System.out.println(a + " 的绝对值是 " + b); }}

方法的定义

方法的调用

案例分析

在上面的案例中,我们已经定义了求整数绝对值的方法;但有时候,我们可能还需要求浮点数的绝对值,请问:可以继续使用这个方法吗?事实上是行不通的,这个方法只能求出整数的绝对值,对于浮点数,还需另外再定义求浮点数绝对值的方法。

修改案例public class AbsoluteValue { /*求整数绝对值的方法 */ public int absOfInt(int x) { return ((x >= 0) ? x : -x); } /*求浮点数绝对值的方法 */ public float absOfFloat(float x) { return ((x >= 0) ? x : -x); } public static void main(String[] args) { AbsoluteValue obj = new AbsoluteValue(); int a = -8, b; b = obj.absOfInt(a); System.out.println(a + " 的绝对值是 " + b);

float c = -3.14f, d; d = obj.absOfFloat(c); System.out.println(c + " 的绝对值是 " + d); }}

如果还要求其它类型的绝对值,就需要定义更多的方法,这对于调用者来说,将会是非常麻烦的事情。

方法重载

用于定义一组方法,这些方法具有相同的名称,并执行类似的操作,但是却使用不同的参数列表;编译器会根据调用时传递的实际参数自动判断具体调用的是哪个重载方法,如:/*abs方法的重载 */

int abs(int x) {……}

float abs(float x) {……}

double abs(double x) {……}

long abs(long x) {……}

方法重载的三大原则方法名相同参数不同(可以有三方面的不同)– 数量不同– 类型不同– 顺序不同同一作用域温馨提示:方法重载跟方法的返回值类型没有任何关系。也就是说,只有返回值不同的方法不能构成重载。/*错误的方法重载 */void fun(int x) {……};int fun(int x) {……};

方法重载示例public class AbsoluteValue { public int abs(int x) { //重载方式 1 ,求整数的绝对值 return ((x >= 0) ? x : -x); } public float abs(float x) { //重载方式 2 ,求浮点数的绝对值 return ((x >= 0) ? x : -x); } public double abs(double x) { //重载方式 3 ,求双精度数的绝对值 return ((x >= 0) ? x : -x); } public long abs(long x) { //重载方式 4 ,求长整型数的绝对值 return ((x >= 0) ? x : -x); } public static void main(String[] args) { AbsoluteValue obj = new AbsoluteValue(); int a = -8, b; b = obj.abs(a); System.out.println(a + " 的绝对值是 " + b);

float c = -3.14f, d; d = obj.abs(c); System.out.println(c + " 的绝对值是 " + d); }}

方法重载的两种方式之一利用不同数据类型的参数重载;对于带有相同数量参数但参数类型不同的重载方法,编译器可以区分它们,如:

只要所有参数式样都是唯一的,就可以使用多个重载;很多编程语言已经重载了输出方法,以便您可以用相同的函数名称输出任何类型的数据。

int square(int x) {……}float square(float x) {……}double square(double x) {……}

方法重载的两种方式之二利用不同数量的参数重载,如:

将调用与参数最匹配的方法,如果没有方法得出最佳匹配,那么编译器将报错;– 编译器解决重载的方式与方法定义的顺序无关;– 重载不考虑方法的返回值类型。

/*函数定义 */int fun(int n1) {……}int fun(int n1, int n2, int n3) {……}/*函数调用 */fun(a);fun(x, y, z);

方法重载的作用域规则只有属于同一作用域范围内的方法才能构成重载。

class First{ …… public void show() { …… } ……}

class Second{ …… public void show(int x) { …… } ……}

不构成重载

课堂练习 1

在 Java 中,对于成员方法float add(float f1, float f2) {……}下面选项()是对该成员方法的重载。a)int add(int n1, int n2) {……}b)float add(float f) {……}c)void add(float f1, float f2) {……}d)void add(float f1, float f2, float x) {……}e)float add(float f2, float f1) {……}

课堂练习 2

在 Java 中,下列方法()不能与int fun(int x) {……}构成方法重载。a)int fun(int x, int y) {……}b)float fun(int x) {……}c)int fun(float x) {……}d)int fun(int x, int y, int z) {……}

关于方法重载的重要提示

请勿将功能完全不一样的方法进行重载!

重载构造方法

方法重载的一个重要应用就是重载构造方法;可以定义一组构造方法,使其构成重载关系,从而方便使用者能够灵活机动地以各种不同的方式来实例化对象。

重载构造方法示例class MyTime { //定义时间类 private int mHour, mMinute, mSecond; // 三个成员变量,时、分、秒 public MyTime() { // 构造方法重载 1 mHour = 0; mMinute = 0; mSecond = 0; } public MyTime(int mHour) { // 构造方法重载 2

mHour = mHour; mMinute = 0; mSecond = 0; } public MyTime(int hour, int minute) { // 构造方法重载 3 mHour = hour; mMinute = minute; mSecond = 0; } public MyTime(int hour, int minute, int second) { // 构造方法重载 4 mHour = hour; mMinute = minute; mSecond = second; } public void display() { //显示时间信息的方法 System.out.println(mHour + ":" + mMinute + ":" + mSecond); }}

重载构造方法示例(续)/*测试时间类 */public class Test{ /*main方法,程序入口 */ public static void main(String[] args) { // 分别调用不同的构造方法来初始化时间对象 MyTime t1 = new MyTime(); //调用重载方式 1 t1.display(); MyTime t2 = new MyTime(8); //调用重载方式 2 t2.display(); MyTime t3 = new MyTime(8, 30); //调用重载方式 3 t3.display(); MyTime t4 = new MyTime(8, 30, 30); //调用重载方式 4 t4.display(); }}/* 使用重载构造方法可以使初始化对象的方式灵活机动,大大方便类的使用者。 */

对象在内存中的存放形式

成员方法 1成员方法 2……成员方法 n

成员变量 1成员变量 2……成员变量 n

成员变量 1成员变量 2……成员变量 n

成员变量 1成员变量 2……成员变量 n

对象 1

对象3

对象 2

每个对象都有自己独立的数据成员,但是所有的对象共享成员方法。

this 关键字

既然所有的对象都共用相同的成员方法,那么在不同的对象都调用同一方法时,它是怎么确定要使用哪个对象的数据成员呢?每个成员方法都有一个隐含的 this引用,它总是指向调用它的对象;关键字 this给出用于调用成员方法的对象的地址;每当调用成员方法时,编译器会向 this 分配调用该方法的对象的地址;可以像使用任何引用那样使用 this 。

this 引用示例/*this示例,代码片段 */public class Student //定义学生类{ private String mName; //姓名 private int mAge; // 年龄 public Student(String name, int age) { //隐式调用,等同于 this.mName = name; mName = name; //显式调用,等同于 mAge = age; this.mAge = age; } ……}

回顾 C 语言中的静态变量

C 语言中的静态变量的特点:– 具有全局变量的生命周期(完成了某些全局变量才能完成的功能);– 具有有局部变量的作用域(比全局变量更安全)。在 C 语言中声明静态变量的语法:static int x;声明静态变量使用关键字 static 。

静态成员变量

在成员变量前加 static 关键字,可以将其声明为静态成员变量;如果类中成员变量被定义为静态,那么不论有多少个对象,静态成员变量只有一份内存拷贝,即所有对象共享该成员变量;静态成员变量的作用域只在类内部,但其生命周期却贯穿整个程序。

静态成员变量示例class Dog{ public static int count = 0; //静态成员变量 public Dog() // 构造方法 { count++; }}

public class Test{ public static void main(String[] args) { System.out.println("当前狗的数量是: " + Dog.count); Dog d1 = new Dog(); Dog d2 = new Dog(); System.out.println("当前狗的数量是: " + Dog.count); }}

静态成员变量的注意事项

和程序同生命周期;在没有实例化对象时,可以通过类名访问静态成员变量;也可以通过对象访问静态成员变量,但不论使用的是哪个对象,访问到的都是同一个变量;静态成员变量在声明时最好初始化,如果不进行初始化,系统会默认初始化为初始值。

静态成员方法

在成员方法前加 static 关键字,可以将其声明为静态成员方法;静态成员方法只能对类的静态成员变量进行操作;静态成员方法没有 this引用;在没有实例化对象时,可以通过类名访问静态成员方法。

静态成员方法示例class Dog { private static int count = 0; //静态成员变量 public Dog() { count++; } //显示数量的方法,静态成员方法 public static void displayCount() { System.out.println("当前狗的数量是: " + count); }}

public class Test { public static void main(String[] args) { // 没有实例化对象之前,直接通过类名调用静态成员方法 Dog.displayCount(); Dog d1 = new Dog(); Dog d2 = new Dog(); Dog.displayCount(); }}

静态成员小结

静态成员包括静态数据成员和静态成员方法;静态成员属于整个类而不是属于某个对象,它被该类的所有对象共享;访问静态成员时一般通过类名访问,也可以通过对象访问;静态成员也受访问权限的约束;静态数据成员在使用之前应尽量初始化;静态成员方法不能操作非静态成员;静态成员方法没有 this引用。

有时候,类和类的名称可能发生冲突;Java 提供了把类名空间划分为更容易管理的块的机制,这就是包;包允许将类组合成较小的单元,类似于文件夹;有助于避免命名冲突,分属不同包的类即便名称相同也不会引起误会;能在包与包之间对于类的访问权限提供更有力的约束。

使用 package 关键字打包可以使用 package 关键字将源文件中的类打入某个包中,语法是:package 包名 ;该语句必须是整个源文件的第一条语句;

包中还可以包含下一级子包,这与文件目录体系非常相似。

// 将本源文件中的所有类打到 mypkg 包中package mypkg;

class Student //定义学生类{ ……}

缺省包

如果省略了 package 语句,类将保存在一个缺省的没有名称的包中;尽管缺省包很方便,但对于大型的程序,它是不恰当的;请尽量为自己编写的类定义具体的包。

使用 import 关键字导入包

如要使用到某个包中的类,就需要使用 import 关键字将包或类显式地包括到程序中,有如下两种情况:

一个程序中允许有多条 import 语句,导入多个包或多个类。

/*导入 java 包下的 io子包中的所有类 */import java.io.*;

/*导入 mypkg 包下的 Student 类,但并不导入该包中的其它类*/

import mypkg.Student;……

总结

方法重载可以方便方法的调用者,但方法重载应遵循三大原则:– 方法名相同– 参数列表不同– 属于同一作用域可以适当地重载构造方法,使初始化对象的方式更加灵活;this引用总是指向调用方法的对象本身;静态成员属于类而不是属于某个对象;总是将自己定义的类打入到具体的包中是良好的编程习惯;可以使用 import 语句导入需要的包或类

作业

在 JAVA 中定义一个类,在该类中有一系列的求绝对值的方法,要求使用方法重载? 定义一个学生类,并将学生类的属性私有化同时为其提供公开的 setter 和 getter ,同时要求方法的参数名字要求要和属性名字一样!使用 this 关键字的编程!使用 package 对自己编写的类打包!使用 import 将其他的包中的类导入到程序中来

逻辑趣味思考题

小明和他四个兄弟住在一起,每个人都有一个房间,假设每个房间都配有两个钥匙,除了每人拥有自己房间的一枚钥匙以外,如何放置剩下钥匙,且能让任何一个人任何时间都能顺利地进入每个房间呢?

继承

方法重载应遵循三大原则;适当地重载构造方法,可以使初始化对象的方式更为灵活;this引用总是指向调用成员方法的对象本身;静态成员(包括静态成员属性和静态成员方法)属于类而不是属于某个对象,可以在实例化对象之前通过类名访问;使用 package 关键字打包;使用 import 关键字导入包或类。

本章相关词汇(蓝色为关键字)

单 词 说 明protected 受保护的extends 扩充,扩展super 上一级的,超级的access 访问inheritance 继承,遗传base class 基类 /父类derived class 子类 /派生类modifier 修饰符

本章目标

对象数组(补充)理解什么是继承在 Java 中实现继承, extends 关键字四种访问控制权限– public– protected– private– 缺省继承中的构造方法super 关键字

继承

在 Java 中实现继承, extends 关键字四种访问控制权限继承中的构造方法super 关键字

对象数组在 Java 中不但可以声明由原始数据组成的数组,还可以声明由对象组成的数组;声明对象数组的方式如:

但上述方式类似于 C 语言中的:struct Student **p = NULL;p = (struct Student**)malloc(5 * sizeof(struct Student*));//只是声明了一个指针数组,没有任何实际的结构体变量

//假设已经定义了一个 Student 类/*声明由 5 个 Student 对象组成的数组 */Student[] stdAry = new Student[5];

对象数组 == 引用数组错误的理解方式

学生对象的实例学生对象的实例学生对象的实例学生对象的实例学生对象的实例

01234

堆栈 stdAry

堆栈 stdAry

正确的理解方式

对象的引用 01234

对象的引用对象的引用对象的引用对象的引用

对象数组示例/* 对象数组示例,假设已经定义了 Student 类 */public static void main(String[] args){ /* 创建包含有 5 个 Student引用的数组 */ Student[] stdAry = new Student[5];

/*逐一为每个引用创建对象实例 */ stdAry[0] = new Student("张三 ", 18); stdAry[1] = new Student("李四 ", 20); stdAry[2] = new Student("王五 ", 24); stdAry[3] = new Student("郑六 ", 21); stdAry[4] = new Student("田七 ", 19);

for (int i = 0; i < stdAry.length; i++) { stdAry[i].display(); }}

对象数组的存放形式堆栈

stdAry

0123

stdAry[0]stdAry[1]stdAry[2]stdAry[3]stdAry[4]4

张三18

李四20

王五24郑六

21

田七19

面向对象的三大特征

面向对象的程序设计有三大特征:– 封装:解决了数据的安全性问题– 继承:解决了代码的重用问题– 多态:解决了程序的扩展问题前面的章节我们已经学习了有关于封装的各个概念,这一章我们来讨论第二大特征——继承。

继承的概念

在现实生活中的继承,可以理解为儿子继承了父亲的财产,即财产重用;面向对象程序设计中的继承,则是代码重用;继承是利用现有的类创建新类的过程,现有的类称作基类(或父类),创建的新类称作派生类(子类)。基类方法和属性

派生类基类方法+ 附加方法

大学系统人员分类树

学生 老师

大学生 研究生

继承的概念(续)

最高层是最普遍的、最一般的情况,往下每一层都比上一层更具体,并包含有高层的特征,通过这样的层次结构使下层的类能自动享用上层类的特点和性质;继承其实就是自动地共享基类中成员属性和成员方法的机制。

在 Java 中实现继承

在 Java 中实现继承需要使用到 extends 关键字;实现继承的一般语法是:[ 访问修饰符 ] class 派生类名 extends 基类名 {成员列表}如:class Student extends Person{……}

实现继承示例class Person { //定义人类 public String mName; //姓名 public int mAge; // 年龄 public void dining() { System.out.println("吃饱了 ..."); } //吃饭的方法}class Student extends Person { //学生类继承于人类 public float mGrade; // 成绩 public void examination() { System.out.println("考试及格了 ..."); } //考试的方法}class Teacher extends Person { //教师类继承于人类 public float mSalary; //薪水 public void prelection() { System.out.println(" 上课很累 ..."); } // 上课的方法}public class InheritanceDemo { //该类用于容纳main方法 public static void main(String[] args) { Student std = new Student(); // 实例化学生对象 std.mName = "张三 "; std.mAge = 18; // 为姓名和年龄赋值,访问的是父类中的成员 std.dining(); //调用吃饭的方法,访问的是父类中的成员 std.examination(); //调用考试方法,访问的是子类中的成员 Teacher tea = new Teacher(); // 实例化教师对象 tea.mName = "谭浩强 "; tea.mAge = 65; tea.dining(); tea.prelection(); }}

继承的作用

当今软件设计的特征:– 软件规模越来越大;– 软件设计者越来越多;– 软件设计分工越来越细。引入继承,实现了代码重用;引入继承,实现了递增式的程序设计。

继承的作用(续)

继承是能自动传播代码和重用代码的有力工具;继承能够在某些比较一般的类的基础上建造、建立和扩充新类;能减少代码和数据的重复冗余度,并通过增强一致性来减少模块间的接口和界面,从而增强了程序的可维护性;能清晰地体现出类与类之间的层次结构关系。

与继承有关的注意事项

继承是单方向的,即派生类可以继承和访问基类中的成员,但基类则无法访问派生类中的成员;在 Java 中只允许单一继承方式,即一个派生类只能继承于一个基类,而不能象 C++ 中派生类继承于多个基类的多重继承方式。

类成员的访问控制权限

信息隐藏是面向对象程序设计的重要特点之一,它可以:–防止类的使用者意外损坏数据;– 对任何实现细节所作的修改不会影响到使用该类的其它代码;– 使类更易于使用。

在 Java 中实现信息隐藏的是访问控制权限机制;访问控制权限包括 4 个访问修饰符:public 、 protected 、 private 和缺省;可以使用上述访问修饰符修饰类的成员。

访问修饰符

不受任何限制,本类或非本类均可随意访问。

public本类及其子类可以访问(父子友好),同一个包中的其它类也可访问(包内友好)。

protected

只有相同包中的类可以访问(包内友好)。

缺 省只有本类可以访问,其余都不可以。

private

访问控制权限(列表)

public protected 缺省 privat

e本类 可以 可以 可以 可以同包子类 可以 可以 可以 不可以同包非子类 可以 可以 可以 不可以不同包子类 可以 可以 不可以 不可以不同包且非子类 可以 不可以 不可以 不可以

访问修饰符位 置

课堂练习 1

源文件 BaseClass.javapackage com.chinasofti……

public class BaseClass{ public int pubA; protected int proB; int defC; private int priD;}

源文件 DerivedClass.javapackage com.chinasofti……

public class DerivedClass extends BaseClass{ public void fun() { pubA = 10; proB = 20; defC = 30; priD = 40; }}

√√√

×

课堂练习 2

源文件 Frist.javapublic class Frist{ public int pubA; protected int proB; int defC; private int priD;}

public class Second{ public void fun() { Frist obj; obj = new Frist(); obj.pubA = 10; obj.proB = 20; obj.defC = 30; obj.priD = 40; }}

源文件 Second.java

√×

课堂练习 3

源文件 SuperClass.javapackage com.chinasofti……

public class SuperClass{ public int pubA; protected int proB; int defC; private int priD;}

源文件 SubClass.javapackage com.chinasofti……

import aaa.SuperClass;

public class SubClass extends SuperClass{ public void fun() { pubA = 10; proB = 20; defC = 30; priD = 40; }}

√√××

类的访问权限

还可以在定义类时为类添加访问修饰符,对类进行访问权限控制;对类使用的访问修饰符只有 public 和缺省两种;被 public修饰的类可以从任何地方访问,不受限制;不加访问修饰符,缺省修饰的类只能从本包中访问,不同包则无法访问到;但要注意的是:在一个源文件中只能有一个被 public修饰的类,并且文件名必须与 public 的类同名;如果要定义多个 public 的类,则必须分别写在不同的源文件中,一个源文件只写一个类是良好的编程习惯。

继承中的构造方法

父类中的构造方法不能被子类继承,即便它是 public的;父类的构造方法负责初始化属于它的成员变量,而子类的构造方法则只需考虑属于自己的成员变量,不必去关注父类的情况。

继承中的构造方法示例class ParentClass { //定义父类 public ParentClass() { // 构造方法 System.out.println("这是父类的构造方法。 "); }}

class ChildClass extends ParentClass { //子类继承于父类 public ChildClass() { // 构造方法 System.out.println("这是子类的构造方法。 "); }}

public class ConstructorTest { //该类用于容纳main方法 public static void main(String[] args) { ChildClass cc = new ChildClass(); // 实例化子类对象 }}

构造方法的执行顺序

当实例化子类的对象时,必须先执行父类的构造方法,然后再执行子类的构造方法;如果父类还有更上级的父类,就会先调用最高父类的构造方法,再逐个依次地将所有继承关系的父类构造方法全部执行;如果父类的构造方法执行失败,那么子类的对象也将无法实例化。

案例class Point { //定义 "点 " 类 //x轴坐标和 y轴坐标,由于准备用于继承,故修饰为 protected protected float mX, mY; public Point(float x, float y) { // 构造方法 mX = x; mY = y; }}class Circle extends Point { //定义 "圆 " 类继承于 "点 " 类 protected float mRadius; //半径 public Circle(float r) { // 构造方法 mRadius = r; }}public class Demo { public static void main(String[] args) { Circle c = new Circle(2.5f); // 实例化 "圆 " 类对象 }}

本例将报出错误

案例分析

在实例化 Circle 类对象时,虚拟机一定会先调用其父类( Point 类)的构造方法;Point 类的构造方法需要两个参数来初始化其成员,但此时并没有获得这两个参数,造成 Point 类的构造方法无法执行;父类的构造方法执行失败从而导致子类( Circle 类)的对象也无法创建;问题的关键是:在实例化子类对象时,如何将参数传递给父类的构造方法?这将使用到 super 关键字。

super 关键字的第一种用途

在 Java 中, super 关键字有两个主要用途;第一种用途是:在子类的构造方法中, super 关键字可以显式地调用父类的构造方法,用于将参数传递给它;其一般语法是: super( 实际参数 );需要注意的是:该语句必须是子类构造方法的第一条语句。

super 关键字示例 1class Point //定义 "点 " 类{ protected float mX, mY; //x轴坐标和 y轴坐标 public Point(float x, float y) // 构造方法 { mX = x; mY = y; } ……}

class Circle extends Point //定义 "圆 " 类继承于 "点 " 类{ protected float mRadius; //半径 public Circle(float x, float y, float r) // 构造方法 { super(x, y); //显式调用父类构造方法,必须是第一条语句 mRadius = r; } ……}

super 关键字的第二种用途

如果父类和子类中有同名成员,在子类中默认访问是属于自己的那一个成员;super 关键字可以明确地指定要访问父类中的成员;其一般语法是:super. 成员名 ;前提条件是:父类中的该成员不是 private 的。

super 关键字示例 2

//定义父类class SuperClass{ protected int num; ……}

//定义子类,继承于父类class SubClass extends SuperClass{ protected int num; //子类中有与父类成员同名的成员 public void fun() { num = 10; //默认访问自己的成员 super.num = 20; // 指定访问父类的成员 } ……}

super 与 this

this 关键字有两种意义:–表示一个指向“ implicit parameter” 的引用–调用本类中的另一个构造器

super 关键字也有两种意义:–调用父类的方法–调用父类的构造器但是, super并不表示一个指向对象的引用,它只是一个特殊的关键字,用来告诉编译器,现在要调用的是父类的方法。

作业

编写一个程序,用于创建一个名为 Employee 的父类和两个名为 Manager 和 Director 的子类, Employee 类中包含三个属性和一个方法,属性为 name,basic 和 address,方法为 show 用于显示这些属性的值。 Manager 类有一个称为 department 的附加属性。 Director 类有一个称为transportAllowance 的附加属性。创建包含main方法的EmployeeTest 类,在 main方法创建 Manager 和Director 类的对象 ,并调用 show方法显示其详细信息。– 要求 :

• 所有类的属性通过构造方法初始化。• name 是名字、 basic 是基本底薪、 address 是地址、

transportAllowance 是津贴

作业题效果

逻辑趣味思考题

从空中放下两列火车,每列火车都带着降落伞,降落到一条没有尽头的笔直的铁道上。两列火车之间的距离不清楚。两列车都面向同一个方向。在落地后,降落伞掉在地上,与火车分离。两列火车都有一个微芯片,可以控制它们的运动。两个芯片是相同的。两列火车都不知道自己的位置。你需要在芯片中写入编码,让这两列火车相遇。每行编码都有一定的执行命令的时间。你能使用以下指令(而且只能用这些指令):– MF—让火车朝前开  – MB—让火车朝后开  – IF ( P )—如果火车旁边有降落伞,这个条件就得到了满足。– GOTO

多态( 1 )

继承是指从一个基类(父类)派生出派生类(子类)的过程;继承使用关键字 extends ;对于类成员,可以使用 public 、 protected 、缺省和private这 4 种访问权限修饰符;对于类,可以使用 public 和缺省这 2 个访问权限;创建子类实例时,必须先执行父类的构造方法,然后再执行子类的构造方法;super 关键字有两种主要用途,都与父类有关。

本章相关词汇(蓝色为关键字)

单 词 说 明polymorphism 多态,多态性casting 转型overridden method 方法覆盖

本章目标

方法覆盖引用转型了解多态,在 Java 中实现多态

多态( 1 )

方法覆盖引用转型了解多态,在 Java 中实现多态

父 /子类中的同名成员

上次课在讲述 super 关键字时,提到了父类和子类中存在同名成员的情况;如果是同名属性(即成员变量同名),那么可以通过 this和 super显式地区分开来;如果是同名方法(即成员方法同名),情况将会相对复杂。

父 /子类中成员方法仅仅同名class BaseClass //定义基类{ public void fun() { …… //具体实现代码略 }}

class DerivedClass extends BaseClass //派生类继承于基类{ public void fun(int x) //跟父类中有相同名称的方法 { …… //具体实现代码略 }}

/*如果仅仅只是名称相同,但参数列表不同的话,则构成方法重载*/

父 /子类中成员方法同原型class SuperClass //定义父类{ public void fun() { …… //具体实现代码略 }}

class SubClass extends SuperClass //子类继承于父类{ public void fun() //与父类中的方法完全同原型 { …… //具体实现代码略 }}

/*如果不但名称相同,而且连方法原型也完全相同的话,则构成方法覆盖*/

方法覆盖

在类的继承体系结构中,如果子类中出现了与父类中有同原型的方法,那么认为子类中的方法覆盖了父类中的方法(也称为方法重写);通过子类的实例调用被覆盖的方法时,将总是调用子类中的方法,而父类中的方法将被隐藏。

方法覆盖示例class ParentClass { //定义父类 public void fun() { System.out.println("这是父类中的方法。 "); }}

class ChildClass extends ParentClass {//子类继承于父类 public void fun() { //子类覆盖父类中的方法 System.out.println("这是子类中的方法。 "); }}

class OverriddenTest { // 用于容纳main方法 public static void main(String[] args) { ParentClass parObj = new ParentClass(); parObj.fun(); //父类的实例调用此方法 ChildClass chiObj = new ChildClass(); chiObj.fun(); //子类的实例调用此方法 }}

方法覆盖的注意事项

子类中重写的方法,其访问权限不能比父类中被重写方法的访问权限更低– 参见示例: AccessTest.java在子类中重写方法时要保持方法的签名与父类中方法的签名一致– 参见示例: AnnotationTest.java

区分方法覆盖和方法重载

方法覆盖(重写)和方法重载是两个极易混淆的概念,必须严格区分;方法覆盖出现的前提条件之一是必须有继承发生的情况下,而且要求父类和子类中的方法必须同原型;方法重载时,继承并不是必需的,它只要求方法名称相同,而参数列表则必须不同,换言之,各方法的原型其实是不同的。

引用转型

基类的引用可以指向派生类的对象,如:BaseClass obj = new DerivedClass();这样的语句是合法的;但是派生类的引用则不可以指向基类的对象,如:DerivedClass obj = new BaseClass();这样的语句将引发错误。

引用转型示例class Person { //定义人类 ……}

class Student extends Person { //学生类继承于人类 ……}

public class OverriddenDemo { public static void main(String[] args) {

// 正确,所有的学生一定是人 Person per = new Student();

//错误,并不是所有的人都是学生 Student std = new Person(); }}

问题

既然基类的引用可以指向派生类的实例,如果基类和派生类中存在方法覆盖的情况,那么通过基类的引用将会调用到哪个类中的方法呢?

class Shapes { // 基本形状类 public void draw() { //绘图的方法 System.out.println("绘制了一个基本形状。 "); }}class Circle extends Shapes { //圆形类继承于基本形状类 public void draw() { //覆盖父类的绘图方法 System.out.println("绘制了一个圆形。 "); }} class Square extends Shapes { // 正方形类继承与基本形状类 public void draw() { //覆盖父类的绘图方法 System.out.println("绘制了一个正方形。 "); }}public class polymorphismDemo { public static void main(String[] args) { Shapes obj = new Shapes(); //父类的引用指向父类的实例 obj.draw(); //调用绘图方法 obj = new Circle(); //父类的引用指向子类的实例 obj.draw(); //调用绘图方法 obj = new Square(); //父类的引用指向子类的实例 obj.draw(); //调用绘图方法 }}

示例

多态

从上例中可以看出,父类的引用指向哪个类的实例就调用哪个类中的方法;同样是使用父类的引用,调用同一个名称的方法,却可以得到不同的调用结果,这就是 Java 中的多态,即:同一函数,多种形态;实际上多态包括动态多态和静态多态。

静态多态

静态多态也称为编译时多态,即在编译时决定调用哪个方法;静态多态一般是指方法重载;只要构成了方法重载,就可以认为形成了静态多态的条件;静态多态与是否发生继承没有必然联系。

动态多态

动态多态也称为运行时多态,即在运行时才能确定调用哪个方法;形成动态多态必须具体以下条件:– 必须要有继承的情况存在;– 在继承中必须要有方法覆盖;– 必须由基类的引用指向派生类的实例,并且通过基类的引用调用被覆盖的方法;由上述条件可以看出,继承是实现动态多态的首要前提。

使用多态的优点

我们在前面的学习过程,已经学习到了 OOP 的封装和继承。其中封装我们是为了隐藏细节,使代码模块化,而继承是为了代码重用,那么多态呢?我们来看一段需求!

飞行系统代码//具体实现代码省略public class Plane { void fly(){…}; // 起飞 void land(){…}; // 着陆 }public class Copter extends Plane { void fly(){…}; // 直升机起飞 void land(){….}; //直升机着陆 }public class Jet extends Plane { void fly(){…}; // 喷气式飞机起飞 void land(){….}; //喷气式飞机着陆 }

//现在我们设计控制一个飞行的方法public void planeFly(Plane p) {

– p.fly();}

多态的优点

观察上面代码,我们会发现只要使用 planeFly方法,可以让所有传给它的飞机( Plane 的子类对象)正常起飞!不管是直升机还是喷气机,甚至是现在还不存在的,以后可能会增加的飞碟。可以看到 planeFly()方法接受的参数是 Plane 类对象引用,而实际传递给它的都是 Plane 的子类对象,现在回想一下开头所描述的“多态”,不难理解,我们使用多态便于灵活地扩展我们开发的程序。

总结

如果基类和派生类中有原型完全相同的方法,那么就形成了方法覆盖;引用转型是指父类的引用可以指向子类的实例,但反之不可以;在有方法覆盖的前提下,并且有引用转型的情况,就将形成动态多态。

作业

在 JAVA 中开发一个飞行体系!该体系可以对任何飞行器进行控制起飞和降落!

逻辑思考题

周 X 的前男友 D鹏是 D唯的堂弟; D唯是 W菲的前老公;周 X 的前男友宋 N 是高 Y 的表弟;高 Y 是 D唯的现任老婆; D唯是 W菲的前老公;周 X 的前男友李亚 P 是 W菲的现任老公;周 X 的前男友 P树的音乐制作人是张亚D ;张亚 D 是 W菲的前男友、 D唯的妹妹 D颖的前老公,也是 W菲的音乐制作人;张亚 D 是李亚 P 的前女友瞿Y 的现男友——则下列说法最有可能正确的是?a) W菲与周 X 是好友关系b) 瞿 Y与W菲是好友关系c) D颖与周 X 是好友关系d) 瞿 Y与周 X 是好友关系

多态( 2 )

如果基类和派生类中有原型完全相同的方法,那么就形成了方法覆盖;引用转型是指父类的引用可以指向子类的实例,但反之不可以;在有方法覆盖的前提下,并且有引用转型的情况,就将形成动态多态。

本章相关词汇(蓝色为关键字)

单 词 说 明abstract 抽象,抽象的interface 接口,界面implements 实现final 最终的,决定性的

本章目标

abstract 关键字抽象方法和抽象类接口和实现接口, interface 关键字和 implements 关键字final 关键字类与类之间的关系(补充)

多态( 2 )

abstract 关键字抽象方法和抽象类final 关键字

抽象方法

在某些情况下,基类无法(或者没有必要)提供被覆盖方法的具体实现,那么就可以将此方法声明成抽象方法;使用关键字 abstract声明抽象方法,一般语法:[ 访问权限 ] abstract 返回值类型 方法名称 (参数列表 );如:public abstract void draw();

抽象类

如果某个类中包含有抽象方法,那么该类就必须定义成抽象类;定义抽象类同样使用关键字 abstract ,一般语法:[ 访问权限 ] abstract class 类名 {成员列表}如:public abstract class Shapes {

public abstract void draw();}

抽象类的注意事项

抽象类不可以直接实例化,只可以用来继承;抽象类的派生子类应该提供对其所有抽象方法的具体实现;可以这么认为,抽象方法实际上就是由抽象基类强制要求其派生子类必须实现的方法原型;如果抽象类的派生子类没有实现其中的所有抽象方法,那么该派生子类仍然是抽象类,只能用于继承,而不能实例化;抽象类中也可以包含有非抽象的方法;构造方法和静态方法不可以修饰为 abstract 。

abstract class Shapes { // 基本形状类,抽象类 public abstract void draw(); //绘图方法,抽象方法}class Circle extends Shapes { //圆形类继承于基本形状类 public void draw() { // 实现抽象父类的抽象绘图方法 System.out.println("绘制了一个圆形。 "); }} class Square extends Shapes { // 正方形类继承与基本形状类 public void draw() { // 实现抽象父类的抽象绘图方法 System.out.println("绘制了一个正方形。 "); }}public class abstractDemo { //该类用于容纳 main方法 public static void main(String[] args) { Shapes obj; obj = new Circle(); //父类的引用指向子类的实例 obj.draw(); //调用绘图方法 obj = new Square(); //父类的引用指向子类的实例 obj.draw(); //调用绘图方法 }}

修改前面的案例

接口

如果某个类中的所有方法都是抽象方法,那么可以考虑将该类定义为接口;定义接口使用关键字 interface ,一般语法:[ 访问权限 ] interface 接口名 {成员列表}如:public interface IMyInterface {

……}

实现接口

与抽象类相似,接口同样不可以实例化,只能用于实现;如果某类要实现接口,则使用 implements 关键字,一般语法:[ 访问权限 ] class 类名 implements 接口名 {成员列表}如:public class MyClass implements IMyInterface {……}

源文件 IMyInterface.javapackage com.chinasofti……

//定义接口public interface IMyInterface

{

/*接口中的所有方法都默认为抽象方法 无需加 abstract 关键字 */

public int add(int x, int y);

public int sub(int x, int y);

}

package com.chinasofti……

import aaa.IMyInterface;

//MyClass 类实现于 IMyInterface接口public class MyClass implements IMyInterface { // 实现接口中的抽象方法 public int add(int x, int y) { return (x + y); }

public int sub(int x, int y) { return (x - y); }}

源文件 MyClass.java

接口示例

接口的注意事项

接口中不能定义非抽象方法,也就是说接口中不能包含有函数实体;接口中的所有方法都默认为抽象方法,无需在每个方法前加 abstract 关键字;接口的实现类应该提供对接口中所有抽象方法的具体实现,否则将成为抽象类;与抽象类和它的继承类相似,也可以使用接口的引用指向其实现类的对象,从而达到动态多态的效果。

/*InterfaceDemo.java 源文件 */// 用于容纳 main方法public class InterfaceDemo{ public static void main(String[] args) { // 使用接口的引用指向实现类的实例 IMyInterface obj = new MyClass(); System.out.println("两数的和是: " + obj.add(20,

30)); System.out.println("两数的差是: " + obj.sub(30,

20)); }}

接口示例 (续 )

关于接口的更多知识

Java只支持单继承,而不能象 C++那样可以多重继承,接口正是为了弥补这一点;某个类只能继承于一个父类,但可以实现多个接口,如:public class 实现类名 implements 接口 1, 接口 2,

……, 接口 n{ 成员列表}

关于接口的更多知识(续)

Java 中还允许一个接口继承于另一个接口,即由父接口派生出子接口,如:public interface 子接口名 extends 父接口名 {成员列表}这样的话,甚至可以使用父接口的引用指向子接口的实现类的对象。

动手实践

USB接口能实现供电和数据传输这两个典型的功能,下面我们来用程序模拟 USB接口和 USB 设备的工作情况–教学案例:接口课堂练习案例

final 关键字

在 Java 中, final 关键字有最终的,不可修改的含义;final 关键字有三种用途,可以分别应用于变量、成员方法和类。

final修饰变量

如果将某个变量修饰为 final ,那么该变量就成为常量,一般语法: [ 访问权限 ] final 数据类型 常量名 = 值 ;如:final double PI = 3.14159;PI 成为常量,其后任何试图对 PI进行赋值的语句都将报错;常量在声明时必须初始化。

final修饰方法

如果将某个成员方法修饰为 final ,则意味着该方法不能被子类覆盖,一般语法:[ 访问权限 ] final 返回值类型 方法名 (参数列表 ) {

……}如:public final void fun() {

……}如果在派生类中出现同原型的方法,将会报错。

final修饰类

如果将某个类修饰为 final ,则说明该类无法被继承,一般语法:[ 访问权限 ] final class 类名 {成员列表}如:public final class MyClass {

……}任何类想继承于 MyClass 类都将报错。

类与类之间的关系

类与类之间的关系一般来说有两种:“有”关系和“是”关系;所谓“是”关系就是指类的继承关系。如:从动物类派生出哺乳动物类,那么可以说哺乳动物是动物;而“有”关系是指在一个类中包含了另一个类的对象,即一个类中有另一个类(的对象),可以理解为类的嵌套。

总结

无法实现的方法可以定义成抽象方法,包含有抽象方法的类应该定义成抽象类;通过接口也能够实现多态,同时也可以弥补 Java只支持单一继承的不足;final 关键字有三种用途;类与类之间的关系:“是”关系和“有”关系。

作业

在此前作业的基础上,进行修改。在 Employee 类中添加一个抽象方法以计算薪资总额。定义一个方法基于休假天数 leave 计算要从基本薪资中扣除的部分。计算薪资扣除部分的公式为:– lessPay=(leave<=5)?(0.25*basic):(0.5*basic)

Manager 和 Director 类重写父类的抽象方法。计算 Manager 的薪资总额 totalAmount 的公式为:– totalAmount=basic+houseRentAllowance+dearnes

sAllowance+medicalAllowance;

作业(接上一页)

其中:– houseRentAllowance为basic的8%– dearnessAllowance为basic的30%– medicalAllowance为1500计算Director的薪资总额的公式为:– totalAmount=basic+houseRentAllowance+dearnessAllowance+medi

calAllowance+entertainmentAllowance + transportAllowance– 其中:– houseRentAllowance为basic的20%– dearnessAllowance为basic的50%– medicalAllowance为4500– entertainmentAllowance为5000改程序还应该计算应付薪资,其公式为:– NetPay=totalAmount-lessPay– 请在getDescription方法中显示name,address,basic,totalAmount和

netPay属性中储存的值。

作业题效果

逻辑趣味思考题

老板让工人为自己工作 7天,给工人的总回报是一根金条,且在这 7天当中,老板必须在每天结束工作时给工人金条中的 1段。如果只允许把金条弄断两次,他该如何切割金条,又该如何给工人支付报酬?

异常处理

使用 abstract 关键字声明抽象方法和抽象类;使用 interface 关键字声明接口,使用 implements 关键字实现接口;抽象类和接口的区别final 关键字有三种用途;类与类之间的关系:“是”关系和“有”关系。

本章相关词汇(蓝色为关键字)单 词 说 明

try 监视,考验,审问catch 捕捉,捕获finally 最后,终于,不可更改地throw 抛出,扔throws 抛出,扔exception 异常error 错误

本章目标

理解异常的概念运用 try块、 catch块和 finally块处理异常Java 中异常类的继承体系结构运用多重 catch块处理异常运用嵌套 try/catch块处理异常运用关键字 throw 和 throws 处理异常用户自定义异常

异常处理

运用 try块、 catch块和 finally块处理异常运用多重 catch块处理异常运用关键字 throw 和 throws 处理异常

什么是异常?

程序中出现的错误被称为异常;异常可分为两大类:编译时异常和运行时异常;编译时异常一般是指语法错误,可以通过编译器的提示加以修正,这里我们不予讨论;运行时异常包括:– 运行错误:如数组下标越界,除数为 0 等;– 逻辑错误:如年龄超过 200岁等。

产生异常的原因

产生异常的原因多种多样,大致有:– 系统资源不可用:如内存分配失败,文件打开失败,数据源连接失败等等;– 程序控制不当:如被零除,负数开方,数组下标越界等等。

产生异常后的反应

当异常发生时,程序一般会作出如下反应:– 发生异常的部分产生系统定义的错误信息;– 程序意外终止,并将控制权返回操作系统;– 程序中所有已分配资源的状态保持不变,这样将会导致资源泄漏。那么我们就必须对有可能产生的异常进行处理。

产生异常后的反应(示例)public class ExceptionDemo { public static void main(String[] args) { int a = 10, b = 0, c; //这里的除数为 0 ,将会产生异常 c = a / b; System.out.println(c); }}

try 块和 catch 块

try块:一般用来监视有可能产生异常的代码部分;catch块:当异常产生后, catch块捕捉异常,并在其中对异常进行处理。

try/catch 块的一般形式……try{ …… //监视有可能发生异常的代码段}catch (异常类型 ) //捕获发生的异常{ …… // 对异常进行处理}……

Java 中的异常类异 常 说 明

Exception 异常层次结构的根类RuntimeException 许多 java.lang异常的基类ArithmeticException 算术异常,如:除数为 0

IllegalArgumentException 方法接收到非法参数ArrayIndexOutOfBoundsException 数组下标越界NullPointerException 访问空引用ClassNotFoundException 不能加载所需的类NumberFormatException 字符串转换数字失败IOException I/O异常的根类FileNotFoundException 找不到文件EOFException 文件结束

try/catch 块示例public class ExceptionDemo{ public static void main(String[] args) { int a = 10, b = 0, c;

try //监视有可能出现异常的代码段 { c = a / b; System.out.println(c); } catch (ArithmeticException ae) // 如果出现异常,将被捕获 { System.out.println("除数为 0 。 "); }

System.out.println(" 程序结束。 "); }}

finally 块

无论 try/catch块中发生了什么, finally块都一定会执行;当异常发生时,程序可能会意外中断,有些被占用的资源就得不到清理。 finally块可以确保执行所有的清理工作;无论是否发生异常, finally块都将执行;finally块是可选的,可视具体情况决定是否添加; finally块必须和 try块一起使用,不能单独存在。

try/catch/finally 块示例public class ExceptionDemo { public static void main(String[] args) { int a = 10, b = 0, c;

try { c = a / b; System.out.println(c); } catch (ArithmeticException ae) { System.out.println("除数为 0 。 "); } // 不论是否发生异常, finally块中的语句都会执行 finally { System.out.println("finally块中的语句。 "); } System.out.println(" 程序结束。 "); }}

try/catch/finally执行流程

try 块

finally 块

catch 块 无异常

发生异常

try/catch/finally 应用模型try{ …… //连接到数据库的代码,有可能发生异常 …… // 对数据库进行操作的代码,有可能发生异常}catch (SQLException sqle) //捕获数据库异常{ …… // 对捕获的异常进行处理}finally{ …… // 在 finally块中执行关闭数据库的操作}

异常类体系结构图

Exception

ArithmeticExceptionNullPointerException

Object

Throwable

Error

SQLException RuntimeException

NumberFormatException……

AWTError

ThreadDeath

……

ClassNotFoundException

……

异常类体系结构说明

Throwable 有两个直接子类,它们是:– Error 类: Error 类的异常通常为内部错误,因此在正常情况下并不期望用户程序捕获它们;– Exception 类:绝大部分用户程序应当捕获的异常类的根类;一些常用的异常类都直接或间接派生自 Exception 类,因此我们可以认为绝大部分的异常都属于 Exception 。

异常类中的常用方法

方法原型 说 明String getMessage() 在 Exception 类中定义的方法,被继承到所有的异常类中,用

于获得与异常相关的描述信息。void printStackTrace() 在 Exception 类中定义的方法,用于在控制台上显示有关异常

的信息,不但有异常的原因,还涉及产生异常的代码行。

多重 catch 块

有时候,在 try块中的代码段将有可能产生多种不同类型的异常,而我们又需要针对不同的异常类型进行不同的处理方式,那么我们就可以使用多重 catch块,来分别捕获不同类型的异常。

多重 catch 块示例public class ExceptionDemo { public static void main(String[] args) { int a, b, c; try { // 从命令行参数获得用户输入的数字 a = Integer.parseInt(args[0]); b = Integer.parseInt(args[1]); c = a / b; System.out.println(c); } catch (ArrayIndexOutOfBoundsException aioobe) { //捕捉数组下标越界异常 System.out.println("您没有指定命令行参数。 "); } catch (NumberFormatException nfe) { //捕捉字符串到数字转换异常 System.out.println("您输入的不是数字。 "); } catch (ArithmeticException ae) { //捕捉算术(除数为 0 )异常 System.out.println("除数为 0 。 "); } catch (Exception e) { //捕捉其它不可预测的异常 System.out.println(e.getMessage()); } System.out.println(" 程序结束。 "); }}

多重 catch 块的注意事项

虽然多重 catch块可以同时监视多个不同类型的异常,但是 try块中一旦有某个异常产生,程序就会跳转到与之异常类型最匹配的 catch块中执行,然后执行 finally块(如果有 finally块的话)或之后的语句;也就是说,多重 catch块只会捕捉到最先产生的异常,而不是把所有的异常全部捕捉完;即:不论有多少个 catch块,最多只会执行其中的一个;请注意 catch块的书写顺序:类层次越低的越往上写,越高的越往下写。

多重 catch 块书写顺序示例public class ExceptionDemo { public static void main(String[] args) { int a, b, c; try { a = Integer.parseInt(args[0]); b = Integer.parseInt(args[1]); c = a / b; System.out.println(c); } // 由于 Exception 类的层次最高,以下的所有异常类型都是其子类,这样写将会报错 catch (Exception e) { //捕捉其它不可预测的异常 System.out.println(e.getMessage()); } catch (ArrayIndexOutOfBoundsException aioobe) { //捕捉数组下标越界异常 System.out.println("您没有指定命令行参数。 "); } catch (NumberFormatException nfe) { //捕捉字符串到数字转换异常 System.out.println("您输入的不是数字。 "); } catch (ArithmeticException ae) { //捕捉算术(除数为 0 )异常 System.out.println("除数为 0 。 "); } System.out.println(" 程序结束。 "); }}

嵌套 try/catch 块

有时候,整个语句块可以产生异常,而其中的某个部分又可能产生另外的异常,而我们需要分别进行处理;这样,就可以通过嵌套 try/catch块来完成;嵌套 try/catch块就是在一个 try/catch块中包含有另外的 try/catch块。

嵌套 try/catch 块示例public class ExceptionDemo{ public static void main(String[] args) { /*外层 try/catch块 */ try { System.out.println("传递的参数是: " + args[0]); /* 嵌套 try/catch块 */ try { int num = Integer.parseInt(args[0]); System.out.println(num + " 的平方是 " + (num * num)); } catch (NumberFormatException nfe) { System.out.println("您输入的不是数字。 "); } } catch (ArrayIndexOutOfBoundsException aioobe) { System.out.println("您没有输入命令行参数。 "); } }}

throw 关键字

throw 语句用于手工抛出异常;执行流程将在 throw 语句后立即停止,转而寻找与之类型相匹配的 catch块;throw 语句的语法是:throw (异常类型的实例 );

throw 语句示例public class ThrowDemo{ public static void main(String[] args) { try { int age = Integer.parseInt(args[0]); if (age < 0 || age > 100) { // 创建一个异常实例,并将其手工抛出 throw (new Exception("您输入的年龄无效。 ")); } System.out.println("您的年龄是: " + age + "岁。 "); } catch (Exception e) //捕捉异常 { //打印出异常信息 System.out.println(e.getMessage()); } }}

用户自定义异常

Exception 类和其子类都是系统内置的异常,这些异常不一定总能捕获程序中发生的所有异常;有时候,我们可能要创建用户自定义的异常类;用户自定义异常类应该是 Exception 类的子类;类似于:class MyException extends Exception{

……}

自定义异常示例class AgeException extends Exception { // 用户自定义年龄异常类 public AgeException() { // 构造方法 super(" 年龄无效。 "); }}public class Test { public static void main(String[] args) { try { int age = Integer.parseInt(args[0]); if (age < 0 || age > 100) { throw (new AgeException()); //抛出自定义异常类实例 } System.out.println("您的年龄是: " + age + "岁。 "); } catch (AgeException ae) { //捕捉自定义异常类型 System.out.println(ae.getMessage()); //打印异常信息 } }}

throws 关键字

如果某个函数中的代码有可能引发异常,可以使用 try/catch块进行处理,这种处理方式成为“内部处理”;如果不方便在函数内部进行处理,也可以将异常往函数外部传递,这就要使用到关键字 throws ;throws 用于将函数内部产生的异常抛给主调函数;一般语法:返回值类型 函数名 (参数列表 ) throws 异常类型{

…… }

throws 关键字示例public class Student //定义学生类{ private String name; //姓名 private int age; // 年龄 …… // 其它方法,代码略 /* 为姓名赋值的方法 */ public void setName(String name) { mName = name; }

/* 为年龄赋值的方法,该方法有可能抛出异常 */ public void setAge(int age) throws AgeException { if (age < 0 || age > 100) { throw (new AgeException()); } age = age; } …… // 其它方法,代码略}

调用带有 throws 的函数 1

当调用带有 throws 关键字的函数时,则必须放在try/catch块中进行监控,否则编译器将会报错;public class ThrowsTest { public static void main(String[] args) { Student std = new Student();

try { std.setName("zhangsan"); std.setAge(24); //该方法必须放在 try/catch块中 std.display(); } catch (AgeException ae) { System.out.println(ae.getMessage()); } }}

调用带有 throws 的函数 2

同样地,如果不便进行监控处理,也可以继续使用throws往外抛出异常,但不太推荐此种做法。public class ThrowsTest{

public static void main(String[] args) throws AgeException { Student std = new Student(); std.setName("zhangsan"); std.setAge(180); // 对该函数不进行监控,只是将异常继续往外抛 std.display(); }

}

总结

异常是运行时产生的错误;可以使用 try/catch/finally块,配合使用来处理异常;如有多种类型的异常要进行处理,可以使用多重 catch块;要手动发生异常,使用 throw 关键字;任何抛到函数外部的异常,都必须使用 throws 关键字指定其异常类型;请注意 throw 和 throws 的区别;自定义异常类一般继承于 Exception 类;Exception 类是绝大部分异常类的父类,在异常类型不明的情况下,可以都认为是 Exception 。

作业

用命令行参数接收用户输入的一个数字,然后对其开平方。要求用异常处理输入错误。用其它方法(不是异常)来处理开方中的不同情况(比如对负数开方)。用命令行参数接收用户输入的两个整数,然后做除法。要求用异常处理输入错误,和除数为 0 的错误。用命令行参数接收用户输入的两个浮点数,然后做除法。要求用异常处理输入错误,用其它方式处理除数为 0 的错误。

逻辑趣味思考题

如果 29只青蛙在 29 分钟里捕捉到了 29只苍蝇, 那么要在 87 分钟内抓到 87只苍蝇,得要多少 只青蛙才行?

前序课程

JAVA 面向对象编程

预备知识

Class.foName()服务定位读取文本工厂模式

案例目的

帮助学员理解面向对象的意义及面向对象中各术语、关键字的含义。深入理解抽象、继承及多态等面向对象特征在程序中的实际应用。

案例描述

本次复习课案例提供由基础到复杂的 4 个版本

案例描述

版本 1:原始版本,提供一个主类用以测试数据模型及操作的正确性

案例描述

版本 2在原始版本的基础上默认提供 3 种游戏规则:默认规则,允许两个元素通过一条直线、一次转折和两次转折连接;一条直线规则:只允许两个元素通过一条直线连接;一次转折规则:允许两个元素通过一条直线和一次转折连接。

案例描述

版本 3在版本二的基础上,将模型原型中的判断两个元素是否能够连接的方法改变为抽象方法,用以提示自定义游戏规则必须实现的方法,并利用对象数组改进模型原型的折点表示方式,使其能够表示自定义游戏规则中超过 2 个的折点信息。并提供一个支持元素一条线连接、一折连接、两折连接和三折连接的自定义游戏规则实现。

案例描述

版本 4在版本三的基础上提供模型工厂类,通过文本类型的配置器,返回相应的游戏模型,实现游戏规则的动态扩展。

课后作业

分别实现案例的 4 个版本

JDK API : java.lang 包

只要在运行时遇到错误,就会发生异常;Java 中的异常都是对象,用来描述某段代码中发生的异常情况;发生异常情况时,将在导致错误的的方法中创建和引发表示该异常的对象;可以使用 try 、 catch 、 finally 、 throw 和throws 来进行异常处理:

有可能导致异常的代码放在 try块中进行监视, catch块用于捕获和处理异常,无论异常发生与否,都必须执行的代码则放在 finally块中;要手工引发异常,可以使用关键字 throw ,而 throws则用于将异常抛往函数外部。

JDK API : java.lang 包

单 词 说 明language 语言integer 整数character 字符buffer 缓冲器math 数学parse 转换equals 相等compare 比较,相比replace 替换,取代

JDK API : java.lang 包

了解 java.lang 包掌握 包装类掌握 String 、 StringBuffer 和 StringBuilder 类运用 Math 类 中的方法运用 Object 类 中的方法运用 Class 类 中的方法

JDK API : java.lang 包

掌握包装类掌握 String 、 StringBuffer 和 StringBuilder 类运用 Object 类 中的方法运用 Class 类 中的方法

java.lang 包简介

java.lang 包是 java内置的一个基础包,其中包含了一系列程序中经常要用到的类;在默认情况下,每个 java 程序都会自动导入该包,因此无需在程序中显式地声明。

包装类

使用原始数据类型声明的变量,如:int num = 10;这里的 num只是一个变量,而不是对象;在某些必须操作对象的场合,这样的变量就不能使用了;Java 提供一系列包装类,以便将原始数据类型当作对象进行操作;在 java.lang 包中,对于每个原始数据类型都有一个相应的包装类。

原始数据类型和包装类对照表

原始数据类型 包 装 类boolean (布尔型) Boolean

byte (字节型) Byte

char (字符型) Character

short (短整型) Short

int (整型) Integer

long (长整型) Long

float (浮点型) Float

double (双精度浮点型) Double

包装类的构造方法

可以使用原始类型作为参数,实例化相应的包装类对象。public class LangDemo{ public static void main(String[] args) { Boolean objBool = new Boolean(true); Character objChar = new Character('X'); Integer objInt = new Integer(100); Long objLong = new Long(2568); Double objDou = new Double(3.1415);

System.out.println(objBool); System.out.println(objChar); System.out.println(objInt); System.out.println(objLong); System.out.println(objDou); }}

包装类的 valueOf 方法

每个包装类都有一个静态的 valueOf方法,用于将字符串转换成相应包装类的对象。public class LangDemo { public static void main(String[] args) { String str = "120"; // 如果转换失败,将会引发 NumberFormatException异常 Byte objByte = Byte.valueOf(str); Short objShort = Short.valueOf(str); Integer objInt = Integer.valueOf(str); Long objLong = Long.valueOf(str);

System.out.println(objByte); System.out.println(objShort); System.out.println(objInt); System.out.println(objLong); }}

包装类的 parseXxx 方法

除了 Boolean 类和 Character 类以外,其它的包装类都有静态的parseXxx方法( Xxx 指代具体的数据类型),用于将字符串转换成相对应的原始数据类型值。public class ParseTest { public static void main(String[] args) { String str = "116"; // 分别调用各个包装类的 paseXxx方法对字符串进行转换,如果转换失败,将报异常 int i = Integer.parseInt(str); short s = Short.parseShort(str); byte b = Byte.parseByte(str); long l = Long.parseLong(str); float f = Float.parseFloat(str); double d = Double.parseDouble(str); System.out.println(i); System.out.println(s); System.out.println(b); System.out.println(l); System.out.println(f); System.out.println(d); }}

Character 类中的常用方法

以上方法都是静态方法,可以直接通过类名调用,返回值均为 boolean 类型,如果是返回 true ,否则返回 false 。

方 法 原 型 说 明 boolean isLetter(char ch) 判断字符 ch 是否为英文字母 boolean isDigit(char ch) 判断字符 ch 是否为 0~9 之间的数字 boolean isUpperCase(char ch) 判断字符 ch 是否为大写形式 boolean isLowerCase(char ch) 判断字符 ch 是否为小写形式 boolean isWhitespace(char ch) 判断字符 ch 是否为空格或换行符

Character 类常用方法示例

public class CharacterDemo { public static void main(String[] args) { char[] charArray = {'*', '7', 'b', ' ', 'A'}; for (int i = 0; i < charArray.length; i++) { if (Character.isDigit(charArray[i])) { System.out.println(charArray[i] + " 是一个数字。 "); } if (Character.isLetter(charArray[i])) { System.out.println(charArray[i] + " 是一个字母。 "); } if (Character.isWhitespace(charArray[i])) { System.out.println(charArray[i] + " 是一个空格。 "); } if (Character.isLowerCase(charArray[i])) { System.out.println(charArray[i] + " 是小写形式。 "); } if (Character.isUpperCase(charArray[i])) { System.out.println(charArray[i] + " 是大写形式。 "); } } }}

JDK5.0 以后提供了自动封解箱的操作,并运用了比较特殊的池操作,猜测以下代码的运行结果:

包装类的特殊操作

public static void main(String[] args){Integer i1=1;Integer i2=1;System.out.println(i1==i2);

Integer i3=128;Integer i4=128;System.out.println(i3==i4);

}

String 类

Java 中,字符串是 String 类的对象;可以通过使用 String 类提供的方法来完成对字符串的操作;创建一个字符串对象之后,将不能更改构成字符串的字符;每当更改了字符串版本时,就创建了一个新的字符串对象,并在其内包含所做的修改,原始字符串保持不变。

String 类的构造方法

String 类的构造方法共有 13 种重载方式,以下是常用的几个:构造方法 说 明

String() 将创建一个空字符串String(String original) 将新建一个字符串作为指定字符串的副本String(char[] value) 将根据字符数组构造一个新字符串String(byte[] tytes) 将通过转换指定的字节数组新建一个字符串

String 类构造方法示例

public class StringDemo{ public static void main(String[] args) { char[] aryChar = {‘I', 'C', ‘S', ‘S'}; String str1 = “ETC"; //利用一个字符串常量值创建新的字符串 String str2 = new String(“ICSSETC"); //利用一个字符型数组创建新的字符串 String str3 = new String(aryChar); System.out.println(str1); System.out.println(str2); System.out.println(str3); }}

字符串长度

String 类中提供 length 成员方法 ,用来获得字符串的长度,方法原型: int length()该方法返回字符串中有效字符的个数。public class StringDemo{ public static void main(String[] args) { String str1 = "John Smith"; String str2 = new String("I Love Java");

System.out.println(str1.length()); System.out.println(str2.length()); }}

字符串比较

要判断两个字符串是否相等,可以使用“ ==” 运算符和equals()方法,但是得到的结果可能不完全相同( String重载了 equals方法);== 运算符用于比较两个引用是否指向同一个对象;而 equals()方法则是比较两个字符串中的内容是否相同,其原型:boolean equals(Object anObject)如果相等返回 true ,否则返回 false 。

字符串比较示例public class StringDemo { public static void main(String[] args) { String str1 = “ICSS", str2 = “ICSS"; String str3 = new String(“ETC"), str4 = new String(“ETC"); if (str1 == str2) { System.out.println("str1 和 str2 指向同一字符串 "); } else { System.out.println("str1 和 str2 分别指向不同字符串 "); }

if (str1.equals(str2)) { System.out.println("str1 和 str2 的内容完全相同 "); } else { System.out.println("str1 和 str2 的内容不相同 "); }

if (str3 == str4) { System.out.println("str3 和 str4 指向同一字符串 "); } else { System.out.println("str3 和 str4 分别指向不同字符串 "); }

if (str3.equals(str4)) { System.out.println("str3 和 str4 的内容完全相同 ");} else { System.out.println("str3 和 str4 的内容不相同 "); } }}

其它的比较方法

方 法 原 型 说 明booleanequalsIgnoreCase(String anotherString)

判断字符串 anotherString 是否与当前字符串相等,忽略大小写形式

int compareTo(String anotherString) 根据 ASCII 码比较字符串 anoterString 和当前字符串的大小,比较方式类似于 C 语言中的 strcmp 函数

boolean startsWith(String prefix) 判断当前字符串是否以字符串 prefix 为开头boolean endsWith(String suffix) 判断当前字符串是否以字符串 suffix 为后缀

字符串搜索

如果需要搜索某个字符(或某个子串)在字符串中是否出现过,这就要使用到 indexOf方法和 lastIndexOf方法。方 法 原 型 说 明int indexOf(int ch) 搜索字符 ch 在当前字符串中第一次出现的索引,没有出现则返回 -1

int indexOf(String str) 搜索字符串 str 在当前字符串中第一次出现的索引,没有出现则返回 -1

int lastIndexOf(int ch) 搜索字符 ch 在当前字符串中最后一次出现的索引,没有出现则返回 -1

int lastIndexOf(String str) 搜索字符串 str 在当前字符串中最后一次出现的索引,没有出现则返回 -1

字符串搜索示例

public class StringDemo{ public static void main(String[] args) { String strEmail = "java@sun.com"; int index; System.out.println("E-mail 地址: " + strEmail); index = strEmail.indexOf('@'); System.out.println("@ 字符出现的索引: " + index); index = strEmail.indexOf("sun"); System.out.println(" 字符串 \"sun\" 出现的索引: " + index); index = strEmail.lastIndexOf('a'); System.out.println("a 字符最后一次出现的索引: " + index); }}

提取字符串

方 法 原 型 说 明char charAt(int index) 用于从指定位置提取单个字符,该位置由 index指定,

索引值必须为非负String substring(int index) 用于提取从 index指定的位置开始的字符串部分String substring(int begin, int end) 用于提取 begin 和 end 位置之间的字符串部分String concat(String str) 用于连接两个字符串,并新建一个包含调用字符串

的字符串对象String replace(char oldChar, char newChar) 用于将调用字符串中出现 oldChar指定的字符全部

都替换为 newChar指定的字符replaceAll(String regex, String replacement) 用于将调用字符串中出现或者匹配 regex 的字符串全

部都替换为 replacement指定的字符String trim() 用于返回一个前后不含任何空格的调用字符串的副

提取字符串示例

public class StringDemo{ public static void main(String[] args) { String str1 = " Java is OOP"; String str2 = new String(“icss"); System.out.println(str1.charAt(2)); System.out.println(str1.substring(5)); System.out.println(str1.substring(2, 9)); System.out.println(str1.concat(str2)); System.out.println(str1 + str2); System.out.println(str1.replace('a', 'e')); System.out.println(str1.trim()); }}

更改字符串的大小写形式

有时候,我们需要将字符串中字符的大小写形式进行转换。方 法 原 型 说 明

String toUpperCase() 返回当前字符串的全大写形式String toLowerCase() 返回当前字符串的全小写形式

更改大小写形式示例

public class StringDemo{ public static void main(String[] args) { String str1 = "Java is OOP"; String str2; str2 = str1.toLowerCase(); System.out.println(str2); str2 = str1.toUpperCase(); System.out.println(str2); }}

数据格式转化

在某些特定的场合,我们可能需要将字符串转化成其它格式的数据进行操作;方 法 原 型 说 明

byte[] getBytes() 返回当前字符串转化成 byte 型数组的形式(即字符串在内存中保存的最原始的二进制形态)

char[] toCharArray() 返回当前字符串的字符数组形式,类似于 C 语言中字符串的保存形式

StringBuffer 类

StringBuffer 类用于表示可以修改的字符串;使用 + 运算符的字符串将自动创建字符串缓冲对象;以下是 StringBuffer 类的构造方法有 4 种重载方式,以下是常用的几个:构造方法 说 明

StringBuffer() 创建一个空的 StringBuffer 对象,默认保留 16 个字符的缓冲空间

StringBuffer(String str) 根据字符串 str 的内容创建 StringBuffer 对象,并默认保留 16 个字符的缓冲空间

StringBuffer(int capacity) 创建一个空的 StringBuffer 对象,缓冲空间大小由 capacity指定

StringBuffer 类的常用方法

方 法 原 型 说 明StringBuffer insert(int index, x x) 将 x插入到索引为 index 的位置, x 可以为任何类型的数

据int length() 获得当前 StringBuffer 对象的长度void setCharAt(int index, char ch) 使用 ch 指定的新值替换 index指定的位置上的字符String toString() 转换为字符串形式StringBuffer reverse() 将当前 StringBuffer 对象中的字符序列倒置StringBuffer delete(int start, int end) 删除当前对象中从 start 位置开始直到 end 指定的索引

位置的字符序列StringBuffer deleteCharAt(int index) 将删除 index 指定的索引处的字符StringBuffer replace(int start, int end, String str) 此方法使用一组字符替换另一组字符。将用替换字符串从

start指定的位置开始替换,直到 end 指定的位置结束

StringBuffer 示例

public class StringBufferDemo { public static void main(String[] args) { StringBuffer strBuf = new StringBuffer("Java"); strBuf.append(" Guide Ver1/"); //连接 System.out.println(strBuf); strBuf.append(3); System.out.println(strBuf);

strBuf.insert(5, "Student"); // 插入 System.out.println(strBuf); strBuf.setCharAt(20, '.'); //替换字符 System.out.println(strBuf); strBuf.reverse(); //倒序 System.out.println(strBuf); String str = strBuf.toString(); System.out.println(str); }}

不变性

在 Java 中一旦创建了字符串就不能直接更改,这就是字符串的不变性;而 StringBuffer 类正是针对此问题而提供的字符可变序列;StringBuffer与 String 是同等的类,唯一区别是可以进行更改。

StringBuilder 示例

JDK1.5 以后,提供了一个新的字符缓冲区:StringBuilfer 。

提供和 StringBuffer相似的 API非线程安全

String 特殊操作

为了提高性能, JAVA 在创建字符串时提供了有限制的池操作public static void main(String[] args){

String str1="123";String str2="123“;System.out.println(str1==str2);String str3="234";String str4=new String("234");System.out.println(str3==str4);

}

Math 类

Math 类中提供了一系列基本数学运算和几何运算的方法;该类的构造方法被修饰为 private ,因此不能实例化;该类中的所有方法都是静态的,可以通过类名直接调用;该类被修饰为 final ,因此没有子类。

Math 类的常用方法

方 法 原 型 说 明static int abs(int a) 求 a 的绝对值,有 4种重载,还有

float , double 和 long

static double pow(double a, double b) 求 a 的 b次方幂static double sqrt(double a) 求 a 的平方根static int round(float a) 求 a 的四舍五入结果static double ceil(double a) 返回不小于 a 的最小整数值static double floor(double a) 返回不大于 a 的最大整数值static double sin(double a) 返回 a 的正弦值static double cos(double a) 返回 a 的余弦值

Math 类中的常量

Math 类中还包括两个常用的常量:PI :圆周率 πE :自然常量以上常量在 Math 类中都被声明成静态,可以直接通过类名进行访问。

Object 类

Java 中的类体系遵循单根结构,即任何一个类往上追溯都到达同一个父类;Object 类就是这个单根体系的根,也就是说它是其它所有类的共同父类;如果用户定义的类没有扩展任何其它类,则默认扩展自Object 类;Object 类中定义的一些方法,会被继承到所有类中。

Object 类的常用方法

方 法 原 型 说 明boolean equals(Object obj) 判断当前对象是否与参数 obj (内容)相等,如果有必要,

应该在自定义的类中覆盖该方法String toString() 返回当前对象的字符串表示,如果有必要,应该在自定义的

类中覆盖该方法Class getClass() 返回当前对象的类描述对象,此方法被继承到所有类中protected void finalize()throws Throwable

当前对象被垃圾回收时调用此方法(类似于 C++ 的析构函数),但无法确定具体何时调用

public final void wait()throws InterruptedException 使当前线程进入等待状态

toString 方法示例

class Student { //定义 Student 类,缺省继承于 Object 类 private String mName; private int mAge; public Student(String name, int age) { // 构造方法 mName = name; mAge = age; } public String toString() { //覆盖 Object 类中的 toString方法 String str = "姓名: " + mName + ", 年龄: " + mAge + "岁 "; return (str); }}

public class ToStringDemo { //容纳main方法 public static void main(String[] args) { Student std = new Student("张三 ", 18); System.out.println(std); //默认调用 toString方法 }}

Class 类

Java 应用程序实际上都是由一个个对象组成,这些对象分别属于什么类,是从哪个类继承而来,这一系列的信息都是由 Class 类的实例来记录的;Class 类的实例用于记录对象的类描述信息;如果在程序运行时,需要检索某个对象的相关类信息,可以调用该对象的 getClass方法来获得记录其描述信息的 Class 类实例;Class 类没有公共的构造方法,无法通过 new 运算符实例化,只能通过对象的 getClass方法,或是通过Class 的静态方法 forName 来获得实例。

Class 类的常用方法

方 法 原 型 说 明static Class forName(String className)throws ClassNotFoundException

使用参数 className 来指定具体的类,来获得相关的类描述对象,该方法有可能抛出类加载异常( ClassNotFoundException ),必须捕捉

Class getSuperclass() 获得当前类描述对象的父类的描述对象String getName() 返回当前类描述对象的类名称

类描述对象示例

/*该示例可以检索任意类的继承关系 */public class ClassDemo { public static void main(String[] args) { try { /* 使用 forName方法获得任意一个类的类描述对象 这里以 StringBuffer 类为例 forName方法有可能抛异常,必须捕捉 */ Class cls = Class.forName("java.lang.StringBuffer"); //循环打印父类信息,直到没有父类 while (cls != null) { System.out.println(cls); cls = cls.getSuperclass(); } } catch (ClassNotFoundException cnfe) { cnfe.printStackTrace(); } }}

JDK API : java.lang 包

java.lang 包是 Java 中最基础的一个包,也是唯一一个无需显示声明就默认导入包;包装类共有 8 个,它们可以以对象的形式封装原始类型的数据;Java 中的字符串以 String 类对象的形式存放,该类中提供一系列对字符串进行操作的方法;StringBuffer 类是另一种形式的字符串,与 String类的区别是允许对自身的内容进行修改;Math 类提供一系列进行算术计算的方法,这些方法都是静态的;Object 类是其它所有类的父类;Class 类的实例记录了对象的类信息。

JDK API : java.lang 包

有一个方法申明为 fun(Integer i),调用的时候采用: int i=10;fun(i) 为什么会报错?int 是一种基本数据类型,而 Integer 是相应于 int 的类类型,称为对象包装。实现这种对象包装的目的主要是因为类能够提供必要的方法,用于实现基本数据类型的数值与可打印字符串之间的转换,以及一些其他的实用程序方法;另外,有些数据结构库类只能操作对象,而不支持基本数据类型的变量,包装类提供一种便利的方式,能够把基本数据类型转换成等价的对象,从而可以利用数据结构库类进行处理。虽然我们在示例里面用到了 Integer i=10 ;这样的赋值方法,但是这仅仅是 JDK5.0 以后提供的自动封解箱的机制,其本质实际上是 Integer i=new Integer(10);因此问题中的写法在 5.0 以后的 JDK 版本中是可以通过的,而 5.0 之前的 JDK 版本则不允许这么做。但是,因为自动封解箱的操作特殊约定比较多,因此建议即使在5.0 以后也不要过于依赖它。

JDK API : java.lang 包

在使用 Class.forName() 的时候经常遇到类找不到的情况,但实际上需要加载的类确实存在Class.forName()方法的参数要求描述类的全限定名(即需要传递完整的包路径)

JDK API : java.lang 包

String明明可以执行串联操作,为什么说是静态的不可改变的呢?String 的确可以执行串联操作, String s1=”abc”;String s2=”123”;s1=s1+s2; 代码运行后 s1 的确也变成了” abc123”,然而改变的却不是原始的字符串对象, JAVA会新建一个字符串,把结果放到新建的字符串对象中,并将新字符串的引用传递给 s1,因此,现在的 s1跟以前的” abc” 字符串已经不存在任何关系了。

JDK API : java.lang 包

不利用 Integer.parseInt()方法将一个给定的字符串转变为Int型数值 ,并检测自己实现的方法和 Integer.parseInt()的性能差距。用户从命令行输入一个字符串,要求判断是否为合法的 email地址,如果合法,请判断是否为 sina的邮箱地址。 (PS:记住异常处理 )– 合法邮箱规则 :

• 有且仅有一个 @ 和 .• @ 在 . 之前 且@ 不能是第一位 . 不能是最后一位• @与 . 不能相邻• 新浪邮箱应当以 @sina.com 结尾用户从命令行输入一个字符串,请判断是否为回文字符串。

思考上述题!用其他方法判断字符串是否为回文(不用数组)!编写一个 Student 类,包含 name 、 age 等属性,要求使用 System.out.println()打印 Student 类的对象引用时,输出的为 name 的值!编写自定义的字符串一致性匹配方法,只要两个字符串包含同样的字符,不管字符的顺序如何,都认为两个字符串一致,如:” aabbcc” 和” abcabc”被认为是一致的编写代码求两个字符串的最大子串

JDK API : java.lang 包

逻辑趣味思考题

在房里有三盏灯,房外有三个开关,在房外看不见房内的情况,你只能进门一次,你用什么方法来区分那个开关控制那一盏灯?

多线程

JAVA 提供了包装类用于使用对象的方式存储基本数据类型,并提供可相应的转换方法。JDK5.0 以后提供自动封解箱的操作,并在封箱操作中使用了比较特殊的池操作JAVA 中的字符串是不可变的,再创建字符串时也利用的池操作最新版本的 JDK 中有两个可变字符串缓冲:StringBuffer 和 StringBuilder

单 词 说 明thread 线,线程runnable 可追捕的,可猎取的current 当前的,最近的sleep 睡,睡眠yield 屈服,屈从interrupted 中断的,被阻止的daemon 后台程序wait 等待,等候notify 通报synchronized 同步的

多线程

多线程

多线程的概念在 Java 中实现多线程– Thread 类– Runnable接口线程的生命周期和线程状态后台线程线程同步与死锁

多线程

在 Java 中实现多线程– Thread 类– Runnable接口线程的生命周期和线程状态线程同步与死锁

多任务

当今的操作系统绝大部分都是基于多任务的操作系统;多任务操作系统的最大特点,是可以同时运行多个程序;由于操作系统支持时间片轮换算法,使得用户感觉多个程序在同时运行,似乎有多个 CPU 在起作用。

线程的概念

运行在操作系统之上的每个应用程序,都会占用一个独立的进程( process ),而进程内又允许运行多个线程( thread ),这意味着一个程序可以同时执行多个任务的功能;在基于线程的多任务而处理环境中,线程是执行特定任务的可执行代码的最小单位;多线程帮助你写出 CPU 最大利用率的高效程序,因为空闲时间保持最低,这对 Java 运行的交互式的网络互连环境是至关重要的,例如:网络的数据传输速率远低于计算机的处理能力,在传统的单线程环境中,你的计算机必须花费大量的空闲时间来等待,多线程能够使你充分利用这些空闲时间。

进程与线程的区别

进程是指系统中正在运行中的应用程序,它拥有自己独立的内存空间;线程是指进程中一个执行流程,一个进程中允许同时启动多个线程,他们分别执行不同的任务;线程与进程的主要区别在于:每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地址空间中,这些线程可以共享数据,因此线程间的通信比较简单,消耗的系统开销也相对较小。

多线程

Java 支持编写多线程的程序;多线程最大的好处在于可以同时并发执行多个任务,当程序的某个功能部分正在等待某些资源的时候,此时又不愿意因为等待而造成程序暂停,那么就可以创建另外的线程进行其它的工作;多线程可以最大限度地减低 CPU 的闲置时间,从而提高CPU 的利用率;

Java 对多线程的支持

在 Java 中实现线程有两种方式,分别是:扩展 java.lang.Thread 类实现 java.lang.Runnable接口

350

Thread 类中的常用静态方法

java.lang.Thread 类用于创建和操作线程,其中包括几个很重要的静态方法,用于控制当前线程:

351

方 法 原 型 说 明static Thread currentThread() 返回对当前正在执行的线程对象的引用static void sleep(long millis)throws InterruptedException

让当前正在执行的线程休眠(暂停执行),休眠时间由 millis (毫秒)指定

static void sleep(long millis, int nanos)throws InterruptedException

让当前正在执行的线程休眠,休眠时间由 millis (毫秒)和 nanos (纳秒)指定

static void yield() 暂停当前正在执行的线程,转而执行其它的线程static boolean interrupted() 判断当前线程是否已经中断

主线程

任何一个 Java 程序启动时,一个线程立刻运行,它执行main方法,这个线程称为程序的主线程;也就是说,任何 Java 程序都至少有一个线程,即主线程;主线程的特殊之处在于:

它是产生其它线程子线程的线程;通常它必须最后结束,因为它要执行其它子线程的关闭工作。

352

主线程示例

353

public class MainThreadDemo { public static void main(String[] args) { //获得当前运行的线程 Thread tMain = Thread.currentThread(); System.out.println("当前运行的线程是: " + tMain);

try { for (int i = 0; i < 5; i++) { System.out.println(i); Thread.sleep(2000); // 使当前线程休眠 2秒 } } catch (java.lang.InterruptedException ie) { ie.printStackTrace(); } }}

线程别名

线程优先级

线程学名

自定义线程

在 Java 中要实现线程,最简单的方式就是扩展 Thread类,重写其中的 run方法,方法原型如下:public void run()如:public class MyThread extends Thread {

public void run() {……

}}

Thread 类中的 run方法本身并不执行任何操作,如果我们重写了 run方法,当线程启动时,它将执行 run方法。 354

Thread 类的构造方法

Thread 类共提供 8 种构造方法重载,以下是常用的几种:

355

构 造 方 法 说 明Thread() 创建一个新的线程Thread(String name) 创建一个指定名称的线程Thread(Runnable target) 利用 Runnable 对象创建一个线程,启动时将执行该对象的

run 方法Thread(Runnable target, String name) 利用 Runnable 对象创建一个线程,并指定该线程的名称

Thread 类的常用方法

356

方 法 原 型 说 明void start() 启动线程final void setName(String name) 设置线程的名称final String getName() 返回线程的名称final void setPriority(int newPriority) 设置线程的优先级final int getPriority() 返回线程的优先级

案例:泡茶

357

class BoilThread extends Thread { //烧开水的线程 public void run() { try { System.out.println(" 开始烧水 ..."); Thread.sleep(10000); //假设烧水需要 10秒 System.out.println("水烧开了。 "); } catch (InterruptedException ie) { ie.printStackTrace(); } }}class WashThread extends Thread { //洗茶杯的线程 public void run() { try { for (int i = 1; i <= 5; i++) { //洗 5 个茶杯 System.out.print(" 开始洗第 " + i + " 个茶杯 ..."); Thread.sleep(1500); //假设每洗一个茶杯需要 1.5秒 System.out.println(" 第 " + i + " 个茶杯洗干净。 "); } } catch (InterruptedException ie) { ie.printStackTrace(); } }}public class MakeTea { public static void main(String[] args) { new BoilThread().start(); //启动烧水线程 new WashThread().start(); //启动洗茶杯线程 }}

Runnable接口

java.lang.Runnable接口中仅仅只有一个抽象方法:public void run();也可以通过实现 Runnable接口的方式来实现线程,只需要实现其中的 run方法即可;Runnable接口的存在主要是为了解决 Java 中不允许多继承的问题;使用 Runnable接口可以使语法的自由度更高。

358

案例:修改后的泡茶案例

359

class BoilThreadRunnable implements Runnable { //烧开水的线程 public void run() { try { System.out.println(" 开始烧水 ..."); Thread.sleep(10000); //假设烧水需要 10秒 System.out.println("水烧开了。 "); } catch (InterruptedException ie) { ie.printStackTrace(); } }}class WashThreadRunnable implements Runnable { //洗茶杯的线程 public void run() { try { for (int i = 1; i <= 5; i++) { //洗 5 个茶杯 System.out.print(" 开始洗第 " + i + " 个茶杯 ..."); Thread.sleep(1500); //假设每洗一个茶杯需要 1.5秒 System.out.println(" 第 " + i + " 个茶杯洗干净。 "); } } catch (InterruptedException ie) { ie.printStackTrace(); } }}public class MakeTea { public static void main(String[] args) { new Thread(new BoilThreadRunnable()).start(); //启动烧水线程 new Thread(WashThreadRunnable()).start(); //启动洗茶杯线程 }}

线程优先级

事实上,计算机只有一个 CPU ,各个线程轮流获得 CPU的使用权,才能执行任务;优先级较高的线程有更多获得 CPU 的机会,反之亦然;优先级用整数表示,取值范围是 1~10 ,一般情况下,线程的默认优先级都是 5 ,但是也可以通过 setPriority和 getPriority方法来设置或返回优先级;Thread 类有如下 3 个静态常量来表示优先级:MAX_PRIORITY :取值为 10 ,表示最高优先级MIN_PRIORITY :取值为 1 ,表示最底优先级NORM_PRIORITY :取值为 5 ,表示默认的优先级

360

线程优先级示例

361

class MyThread implements Runnable { //自定义线程,实现 Runnable接口 public void run() { String strName = Thread.currentThread().getName(); for (int i = 0; i < 50; i++) { System.out.println(strName + ":" + i); } }}public class ThreadPriority { public static void main(String[] args) { // 三个线程 Thread t1 = new Thread(new MyThread()); // 第一个自定义线程 Thread t2 = new Thread(new MyThread()); // 第二个自定义线程 Thread tm = Thread.currentThread(); //获得当前线程,即主线程 t1.setName(("t1"); t2.setName(("t2"); tm.setName("tm"); // 分别设置线程的名称 t1.setPriority(Thread.MAX_PRIORITY); // 设置线程的优先级 t2.setPriority(Thread.MIN_PRIORITY);

System.out.println(t1.getName() + " 的优先级: " + t1.getPriority()); System.out.println(t2.getName() + " 的优先级: " + t2.getPriority()); System.out.println(tm.getName() + " 的优先级: " + tm.getPriority()); t1.start(); t2.start(); String strName = tm.getName(); for (int i = 0; i < 50; i++) { System.out.println(strName + ":" + i); } }}

线程的生命周期

线程在它的生命周期中会处于不同的状态:

362

新线程(新建)

就 绪 运 行 死 亡

等 待 阻 塞

start()

系统调度 run()方法执行完毕

睡 眠挂 起sleep()或 join()或等待 IO

同步锁notify()或 interupt()

解锁

线程状态

新建状态( New ):使用 new 关键字创建线程对象,仅仅被分配了内存;就绪状态( Ready ):线程对象被创建后,等待它的 start方法被调用,以获得 CPU 的使用权;运行状态( Running ):执行 run方法,此时的线程的对象正占用CPU ;睡眠状态( Sleeping ):调用 sleep方法,线程被暂停,睡眠时间结束后,线程回到就绪状态,睡眠状态的线程不占用 CPU ;死亡状态( Dead ): run方法执行完毕后,线程进入死亡状态;阻塞状态( Blocked ):线程由于某些事件(如等待键盘输入)放弃 CPU ,暂停运行,直到线程重新进入就绪状态,才有机会转到运行状态;

363

与线程状态相关的一些方法

364

方 法 原 型 说 明final void join()throws InterruptedException 等待线程终止final boolean isAlive() 判断线程是否处于活动状态void interrupt() 中断线程void destroy() 用于销毁线程,已过时final void suspend() 用于将线程挂起,已过时final void resume() 用于恢复线程,已过时final void stop() 用于强迫线程停止,已过时

sleep 、 yield 和 join

sleep 和 yield都是 Thread 类的静态方法,都会使当前处于运行状态的线程放弃 CPU ,但两者的区别在于:sleep给其它线程运行的机会,但不考虑其它线程的优先级;但yield只会让位给相同或更高优先级的线程;当线程执行了 sleep方法后,将转到阻塞状态,而执行了yield方法之后,则转到就绪状态;sleep方法有可能抛出异常,而 yield则没有;在一般情况下,我们更建议使用 sleep方法。

join方法用于等待其它线程结束,当前运行的线程可以调用另一线程的 join方法,当前运行线程将转到阻塞状态,直至另一线程执行结束,它才会恢复运行。365

后台线程

后台线程是指为其它线程提供服务的线程,也称为守护线程或精灵线程;Java 虚拟机的垃圾回收线程是典型的后台线程,它负责回收其它线程不再使用的内存;后台线程的特点是:后台线程与前台线程相伴相随,只有所有的前台线程都结束生命周期,后台线程才会结束生命周期。

366

与后台线程相关的方法

要将某个线程设置为后台线程的话,必须在它启动之前调用 setDeamon方法;默认情况下,由前台线程创建的线程仍是前台线程,由后台线程创建的线程仍是后台线程。367

方 法 原 型 说 明final void setDaemon(boolean on) 将该线程设置为后台线程final boolean isDaemon() 判断该线程是否为后台线程

线程同步与通讯

信号量同步关键字Lock接口线程间管道流通讯

368

生产者与消费者模式

在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。

369

线程池

线程池原理很简单,类似于操作系统中的缓冲区的概念,它的流程如下:先启动若干数量的线程,并让这些线程都处于睡眠状态,当客户端有一个新请求时,就会唤醒线程池中的某一个睡眠线程,让它来处理客户端的这个请求,当处理完这个请求后,线程又处于睡眠状态。

370

多线程

对多线程的支持是 JAVA 的优势之一,在 JAVA 中有两种实现线程的方式:继承 Thread 、实现 Runnable信号量、同步关键字、 Lock 等工具可以帮助我们实现线程之间的同步,我们也能通过管道流在线程之间互相通讯生产者 - 消费者模式是在并发型应用中常见的实现方法可以利用线程池解决大量并发线程创建、销毁的 IO及CPU 消耗问题

371

多线程

sleep 和 wait 有什么区别?

Sleep 是线程类( Thread )的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep 不会释放对象锁。 wait 是 Object 类的方法,对此对象调用 wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出 notify方法(或 notifyAll )后本线程才进入对象锁定池准备获得对象锁进入运行状态。

多线程

同步和异步有什么区别?

如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。

多线程

为什么要反对使用 stop 等方法?反对使用 stop() ,是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。 suspend()方法容易发生死锁。调用 suspend() 的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被 "挂起 " 的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用 suspend() ,而应在自己的 Thread 类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用 wait() 命其进入等待状态。若标志指出线程应当恢复,则用一个 notify()重新启动线程。

多线程

创建两个线程,每个线程打印出线程名字后再睡眠,给其它线程以执行的机会,主线程也要打印出线程名字后再睡眠,每个线程前后共睡眠 5 次。要求分别采用从 Thread中继承和实现 Runnable接口两种方式来实现程序。(即写两个程序)

多线程

结合生产者与消费者模式及线程池编写如下程序: 生产者随机时间( 0.5-1.5秒)产生随机的字符串 消费者由线程池中的线程担任,假设每处理一个字符串需要 2-3秒时间(处理内容里必须要包含将处理线程编号和待处理的字符串打印出来)

java.util 包

多线程的概念在 Java 中实现多线程– Thread 类– Runnable接口线程的生命周期和线程状态后台线程线程同步与死锁

本章相关词汇

单 词 说 明utility 有用的,实用的date 日期calendar 日历,历法random 随意,任意,胡乱的list 列表link 链条,链状物hash 散乱的,一般指散列码map 地图vector 向量,矢量

本章目标

了解 java.util 包运用如下类进行 Java 编程:– Date 类– Calendar 类– Random 类使用 Collection接口及其实现类:– ArrayList 类– LinkedList 类– Vector 类掌握 HashMap 类

java.util 包

ArrayList 类掌握 HashMap 类

java.util 包简介

java.util 包是 Java内置的一个工具包,其中包含一系列常用的工具类;如处理日期和日历的类以及一些集合类;java.util 包不会默认导入,如果要使用到该包中的类,则必须在程序的开始部分显式地声明如下语句:import java.util.*;

Date 类

Date 类对象用来表示时间和日期;该类提供一系列操纵日期和时间各组成部分的方法;Date 类最多的用途是获取系统当前的日期和时间。

Date 类的构造方法Date 类的构造方法有 6 种重载方式,以下是比较常用的几种。 构 造 方 法 说 明

Date() 使用系统当前时间创建日期对象Date(long date) 使用自 1970年 1月 1日以后的指定毫秒数创建日期对象Date(int year, int month, int date) 创建指定年、月、日的日期对象Date(int year, int month, int date,int hrs, int min, int sec) 创建指定年、月、日、时、分、秒的日期对象

方 法 原 型 说 明boolean after(Date when) 如果当前日期对象在 when指定的日期对象之后,返回 true ,

否则返回 false

boolean before(Date when) 如果当前日期对象在 when指定的日期对象之前,返回 true ,否则返回 false

void setTime(long time) 设置日期对象,以表示自 1970年 1月 1日起的指定毫秒数boolean equals(Object obj) 如果两个日期对象完全相同,返回 true ,否则返回 false

String toString() 返回日期的格式化字符串,包括星期几

Date 类的常用方法

Date 类示例public class DateDemo{ public static void main(String[] args) { Date date = new Date(); //获得当前的系统日期和时间 System.out.println("今天的日期是: " + date);

long time = date.getTime(); //获得毫秒数 System.out.println("自 1970 年 1月 1日起以毫秒为单位的时间

(GMT):" + time);

//截取字符串中表示时间的部分 String strDate = date.toString(); String strTime = strDate.substring(11, (strDate.length()

- 4)); System.out.println(strTime); strTime = strTime.substring(0, 8); System.out.println(strTime); }}

Calendar 类

Calendar 类也是用来操作日期和时间的类,但它可以以整数形式检索类似于年、月、日之类的信息;Calendar 类是抽象类,无法实例化,要得到该类对象只能通过调用 getInstance方法来获得;Calendar 对象提供为特定语言或日历样式实现日期格式化所需的所有时间字段。

Calendar 类的常用方法

方 法 原 型 说 明Calendar getInstance() 返回默认地区和时区的 Calendar 对象int get(int fields) 返回调用对象中 fields指定部分的值void set(int fields, int value) 将 value 中指定的值设置到 fields指定的部分void add(int fields, int amount) 将 amount 值添加到 fields指定的时间或日期部分Date getTime() 返回与调用对象具有相同时间的 Date 对象Object clone() 返回调用对象的副本void clear() 清除当前对象中所有的时间组成部分boolean after(Object obj) 如果调用对象时间在 obj 之后,返回 true

boolean before(Object obj) 如果调用对象时间在 obj 之前,返回 true

boolean equals(Object obj) 判断调用对象与 obj 是否相等

Calendar 类示例public class CalendarDemo{ public static void main(String[] args) { // 创建包含有当前系统时间的 Calendar 对象 Calendar cal = Calendar.getInstance(); //打印 Calendar 对象的各个组成部分的值 System.out.print("当前系统时间: "); System.out.print(cal.get(Calendar.YEAR) + " 年 "); System.out.print((cal.get(Calendar.MONTH) + 1) + "月 "); System.out.print(cal.get(Calendar.DATE) + "日 "); System.out.print(cal.get(Calendar.HOUR) + ":"); System.out.print(cal.get(Calendar.MINUTE) + ":"); System.out.println(cal.get(Calendar.SECOND)); // 将当前时间添加 30 分钟,然后显示日期和时间 cal.add(Calendar.MINUTE, 30); Date date = cal.getTime(); System.out.println(" 将当前时间添加 30 分钟后的时间: " + date); }}

日期格式化

在显示日期的时候,可以使用很多格式,这时我们就可以借助 java.text 包中的 DateFormat 类及其子类进行调整– 利用格式字符串进行调整– 利用预设格式进行调整– 示例演示

国际化( I18N )

国际化是什么,问什么叫 I18N?国际化的用途如何实现日期时间的国际化– 使用 java.util.Locale 类– 示例演示

数字格式化

顺便来看看数字的格式化要格式化数字,可以使用 NumberFormat 类来实现– 示例演示

Random 类Random 类专门用来生成随机数;该类的构造方法有 2 种重载方式。构 造 方 法 说 明

Random() 直接创建一个 Random 类对象Random(long seed) 使用 seed 作为随机种子创建一个 Random 类对象

Random 类的常用方法

方 法 原 型 说 明int nextInt() 从随机数生成器返回下一个整型值long nextLong() 从随机数生成器返回下一个长整型值float nextFloat() 从随机数生成器返回 0.0 到 1.0 之间的下一个浮点值double nextDouble() 从随机数生成器返回 0.0 到 1.0 之间的下一个双精度值double nextGaussian() 从随机数生成器返回下一个高斯分布的双精度值。中间值为 0.0 ,

而标准差为 1.0

Random 类示例public class RandomDemo{ public static void main(String[] args) { // 创建一个 Random 类对象 Random rand = new Random(); // 随机生成 20 个随机整数,并将其显示出来 for (int i = 0; i < 20; i++) { int num = rand.nextInt(); System.out.println(" 第 " + (i + 1) + " 个随机数是: " + num); } }}

集合集合是将多个元素组成一个单元的对象;类似于数组,但数组最大的缺点是:长度受到限制(一经创建,就不可再改变),并且只能存放相同数据类型的元素;集合的长度没有限制,可以存放任意多的元素,而且元素的数据类型也可以不同;集合还提供一系列操纵数据的方法,如存储、检索等等。

集合框架的优点

提供有用的数据结构和算法,从而减少编程工作;提高了程序速度和质量 ,因为它提供了高性能的数据结构和算法;允许不同 API 之间的相互操作, API 之间可以来回传递集合;可以方便地扩展或改写集合。

java.util 包中的集合类

为了满足不同场合的需要, java.util 包中包含有一系列集合类;如: ArrayList 类、 LinkedList 类、 Vector类、 HashMap 类等等,接下来将逐一进行介绍;集合类中只能存放对象,而不能存放原始数据类型的元素,所以当有原始数据类型需要存放时,只能将其转换成相应的包装类对象。

ArrayList 类

ArrayList 是长度可变的对象引用数组,称为动态数组;随着元素的添加,元素数目的增大,数组容量也会随之自动扩展;访问和遍历数组元素时, ArrayList 的性能优越;ArrayList 类继承了 AbstractList 类并实现了 List接口。

ArrayList 类的构造方法ArrayList 类的构造方法有 3 种重载方式。构 造 方 法 说 明

ArrayList() 创建一个空的 ArrayList 对象ArrayList(Collection c) 根据指定的集合创建 ArrayList 对象ArrayList(int initialCapacity ) 使用给定的大小创建 ArrayList 对象

ArrayList 类的常用方法方 法 原 型 说 明

int size() 返回 ArrayList 对象的大小,即元素的数量boolean isEmpty() 判断 ArrayList 对象是否为空,为空返回 true ,否则返回

false

void clear() 清空 ArrayList 对象中的所有元素boolean add(Object element) 向 ArrayList 对象中添加一个元素,该元素可以是任何类

的对象Object remove(int index) 从 ArrayList 对象中删除指定索引位置的元素Object get(int index) 返回指定索引位置的元素Object set(int index, Object elem) 将元素 elem 存放到由 index指定的索引位置上int indexOf(Object element) 判断 element 在 ArrayList 对象中是否存在,存在返回对应

的索引,否则返回 -1

ArrayList 类示例 1public class ArrayListDemo1 { public static void main(String[] args) { ArrayList al = new ArrayList(); // 创建一个空 ArrayList 对象 for (int i = 0; i < 10; i++) { Integer num = new Integer(i); // 创建整型包装类对象 al.add(num); // 将该对象存放到 ArrayList 中 } System.out.println(" 数组中的元素: "); for (int i = 0; i < al.size(); i++) { Integer temp = (Integer)(al.get(i)); //获得 ArrayList 中索引为 i 的元素 System.out.println(temp); } System.out.println("*********************************"); al.clear(); //清空 System.out.println(" 数组被清空后的情况: "); System.out.println(" 数组长度为: " + al.size()); if (al.isEmpty()) { //判断是否为空 System.out.println(" 数组现在为空。 "); } else { System.out.println(" 数组现在不为空。 "); } }}

ArrayList 类示例 2

public class ArrayListDemo2 { public static void main(String[] args) { ArrayList al = new ArrayList(); // 创建一个空的 ArrayList 对象 //往动态数组中添加元素 al.add("苹果 "); al.add("梨子 "); al.add("香蕉 "); al.add(" 西瓜 "); al.add("榴莲 "); System.out.println("目前数组的长度: " + al.size()); for (int i = 0; i < al.size(); i++) { System.out.println((String)(al.get(i))); } String str = new String(" 西瓜 "); int index = al.indexOf(str); //判断某个元素是否存在 if (index < 0) { System.out.println(str + " 在数组中不存在。 "); } else { System.out.println(str + "存在,索引为: " + index); } al.remove(3); //删除某个索引位置的元素 System.out.println("删除索引为 3 的元素后的情况: "); for (int i = 0; i < al.size(); i++) { System.out.println((String)(al.get(i))); } }}

迭代器

有的时候我们不太确定集合中到底有多少个元素,这个时候要想遍历集合,就可以使用迭代器– 示例: IteratorDemo.java

LinkedList 类

LinkedList 类用于创建链表数据结构;链表中元素的数量不受任何限制,可以随意地添加和删除;与 ArrayList相比,如果需要频繁地添加和删除元素, LinkedList 的性能更加优越;LinkedList 类继承了 AbstractSequentialList 类,并实现了 List接口;

LinkedList 类的构造方法

LinkedList 类的构造方法有 2 种重载方式。构 造 方 法 说 明

LinkedList() 创建一个空链表LinkedList(Collection c) 根据指定的集合创建链表

LinkedList 类的常用方法方 法 原 型 说 明

int size() 返回链表的大小,即元素的数量boolean isEmpty() 判断链表是否为空,为空返回 true ,否则返回 false

void clear() 清空链表中的所有元素,使其成为空链表boolean add(Object element) 向链表中添加一个元素,该元素可以是任何类的对象Object remove(int index) 从链表中删除指定索引位置的元素Object get(int index) 返回指定索引位置的元素Object set(int index, Object elem) 将元素 elem 存放到由 index指定的索引位置上int indexOf(Object element) 判断 element 在链表中是否存在,存在返回对应的索引,

否则返回 -1

LinkedList 类的常用方法(续)

方 法 原 型 说 明void addFirst(Object element) 将指定元素添加到链表的开始处void addLast(Object element) 将指定元素添加到链表的结尾处Object removeFirst() 删除链表中的第一个元素Object removeLast() 删除链表中的最后一个元素Object getFirst() 返回链表中的第一个元素Object getLast() 返回链表中的最后一个元素

LinkedList 类示例public class LinkedListDemo { public static void main(String[] args) { LinkedList ll = new LinkedList(); // 创建空的链表 for (int i = 1; i <= 10; i++) { Double temp = new Double(Math.sqrt(i)); // 创建包装类对象 ll.add(temp); // 将包装类对象添加到链表中 } System.out.println("链表中的元素: "); //循环打印链表中的元素 for (int i = 0; i < ll.size(); i++) { System.out.println(ll.get(i)); } System.out.println("*********************************"); ll.removeFirst(); //删除第一个元素 ll.removeLast(); //删除最后一个元素 System.out.println("删除第一个元素和最后一个元素后的链表: "); for (int i = 0; i < ll.size(); i++) { System.out.println(ll.get(i)); } }}

Vector 类

Vector 类与 ArrayList 类和 LinkedList 类很相似,最大的区别在于 Vector 是线程同步的;如果在多线程的程序中要使用到集合框架,并且不希望线程与线程之间相互干扰,那么 Vector 是不错的选择;Vector 类继承于 AbstractList 类,并实现了 List接口。

Vector 类的构造方法Vector 类的构造方法有 4 种重载方式。构 造 方 法 说 明

Vector() 创建一个空的 Vector 对象。初始容量为 10 ,容量增量为 0

Vector(Collection c) 根据指定的集合创建 Vector 对象Vector(int initialCapacity) 创建一个 Vector 对象,初始容量由 initialCapacity指定,容量

增量为 0

Vector(int initialCapacity, int capacityIncrement)

创建一个 Vector 对象,初始容量由 initialCapacity指定,容量增量由 capacityIncrement指定

Vector 类的常用方法

方 法 原 型 说 明int size() 返回 Vector 对象的大小,即元素的数量boolean isEmpty() 判断 Vector 对象是否为空,为空返回 true ,否则返回

false

void clear() 清空 Vector 对象中的所有元素boolean add(Object element) 向 Vector 对象中添加一个元素,该元素可以是任何类的对

象Object remove(int index) 从 Vector 对象中删除指定索引位置的元素Object get(int index) 返回指定索引位置的元素Object set(int index, Object elem) 将元素 elem 存放到由 index指定的索引位置上int indexOf(Object element) 判断 element 在 Vector 对象中是否存在,存在返回对应的

索引,否则返回 -1

Vector 类的常用方法(续)方 法 原 型 说 明

int capacity() 返回 Vector 对象的容量,即可以存放元素的个数void addElement(Object element) 将指定元素插入到 Vector 对象的末尾处void insertElementAt(Object elem, int index) 将指定元素插入到指定索引位置void setElementAt(Object elem, int index) 将指定对象替换位于指定索引处的对象Object ElementAt(int index) 检索位于指定索引处的元素boolean contains(Object elem) 如果 Vector 对象包含指定元素,返回 true

Object firstElement() 返回 Vector 对象中的第一个元素Object lastElement() 返回 Vector 对象中的最后一个元素void removeAllElements() 删除 Vector 对象中的所有元素void copyInto(Object[] anArray) 将 Vector 对象中的元素复制到指定数组中void setSize(int newSize) 根据 newSize 的值设置 Vector 对象的容量

Vector 类示例public class VectorDemo{ public static void main(String[] args) { Vector vec = new Vector(); // 创建空的 Vector

//往 Vector 中添加元素 vec.addElement("Java"); vec.addElement("C#"); vec.addElement("Oracle"); vec.addElement("C++"); vec.addElement("HTML");

System.out.println(vec.toString()); //打印 Vector 中的元素 vec.removeElement("C++"); //删除其中的元素 System.out.println(vec.toString()); }}

HashMap 类

HashMap 以键值对的形式存储元素;对于 HashMap 来说,不存在索引,也就是说不可以通过索引来访问元素,只能通过键去访问值,如要快速检索元素的话, HashMap 性能优越;由于没有索引,所以 HashMap 中元素的存放是没有顺序的;HashMap 类继承了 AbstractMap 类,并实现了 Map接口。

HashMap 类的构造方法HashMap 类的构造方法有 4 种重载方式。

构 造 方 法 说 明HashMap() 创建一个空的 HashMap 对象HashMap(Map m) 根据指定的 Map集合创建 HashMap 对象HashMap(int initialCapacity) 创建一个指定容量和默认负载系数的 HashMap 对象HashMap(int initialCapacity, float loadFactor) 创建一个指定容量和指定负载系数的 HashMap 对象

HashMap 类的常用方法

方 法 原 型 说 明int size() 返回 HashMap 对象的大小,即元素的个数boolean isEmpty() 判断 HashMap 对象是否为空,为空返回 true ,否则

返回 false

void clear() 清空 HashMap 对象中的所有元素Object put(Object key, Object value) 向 HashMap 对象中添加一个元素,必须指定该元素

的键和值Object remove(Object key) 通过键对象删除相对应的值对象Object get(Object key) 通过键对象查找相对应的值对象boolean containsKey(Object key) 查找指定的键对象在 HashMap 对象中是否存在boolean containsValue(Object value) 查找指定的值对象在 HashMap 对象中是否存在

HashMap 示例public class HashMapDemo { public static void main(String[] args) { HashMap hm = new HashMap(); // 创建空的 HashMap //往 HashMap 中添加元素 hm.put("93-07", "张三 "); hm.put("84-12", "李四 "); hm.put("102-20", "王五 "); hm.put("91-04", "郑六 "); hm.put("111-17", "田七 ");

//打印出 HashMap 中的元素 System.out.println(hm.toString()); // 通过键对象查找值对象 System.out.println("学号 91-04 的学生是 " + hm.get("91-04")); System.out.println("学号 84-12 的学生是 " + hm.get("84-12")); hm.remove("93-07"); // 通过键对象删除元素 System.out.println(hm.toString()); }}

Set集合

Set 集合的主要特点是,其中不允许出现重复的元素Set 集合类都实现了 Set接口,其中典型的例子有HashSet 类Properties 类也是一个集合类,目前不算常用– 但以后会用到(比如读取属性文件)– 详见示例

补充 : Collection 与Collections

Collection 是集合类的上级接口,继承与他的接口主要有Set 和 List.Collections 是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作 .

补充 : 正则表达式

“ 正则表达式”最早是由数学家 Stephen Kleene 于1956 年提出的,其主要优势就是能方便、快捷地匹配字符串。java.util.regex 包Pattern 类(查询 API )–示例演示

总结

java.util 包中包含一系列常用的工具类和集合框架类;Date 类和 Calendar 类用来操作日期和时间;可以使用 Random 类的对象来生成随机数;集合框架提供了一系列复杂的数据结构和优越的算法;ArrayList 类可以创建动态数组;LinkedList 类可以创建链表结构;Vector 类可以创建线程同步的动态数组;HashMap 类可以创建键值对集合。

作业一

使用 Calendar 类的相关属性以及方法!打印出某年某个月的日历信息!(要求年月日由命令行输入)从命令行输入一个字符串!要求从中随机选择 6 个字符组成验证码!使用 Random 类完成小型猜拳游戏一个!

输入输出流

使用 Data 类和 Calendar 类获取当前系统时间及日期使用 Random 类对象生成随机数理解 Java 中的集合框架使用 ArrayList 类创建动态数组使用 HashMap 类创建键值对集合使用 LinkedList 类创建链表Vector 类

输入输出流

单 词 说 明input 输入output 输出file 文件,档案directory 目录stream 流write 写,书写read 读,阅读source 源头,来源destination 目标,目的地

输入输出流

了解 java.io 包运用 File 类 对文件或目录的 属性 进行操作理解 流,理解 输入 / 输出流 的概念使用字节流和字符流读取文件或从控制台 接受输入运用 DataInputStream 类 和 DataOutputStream 类读写数据文件对象序列化补充: Properties 类

输入输出流

运用 File 类 对文件或目录的 属性 进行操作理解 流,理解 输入 / 输出流 的概念运用 FileInputStream 类 和 FileOutputStream 类 读/ 写 字节文件运用 FileReader 类 和 FileWriter 类 配合BufferedReader 类 和 BufferedWriter 类 读 / 写 字符文件对象序列化

java.io 包简介

java.io 包也是 Java内置的包,其中包含一系列对文件和目录的属性进行操作,对文件进行读写操作的类;程序中如果要使用到该包中的类,对文件或流进行操作,则必须显式地声明如下语句:import java.io.*;

文件

什么是文件?文件可以认为是相关记录或存放在一起的数据的集合;文件一般是存放在磁盘上的,例如:硬盘、软盘和光盘等等。

File 类

File 类的对象不但可以表示文件,还可以表示目录,在程序中一个 File 类对象可以代表一个文件或目录;当创建一个文件对象后,就可以利用它来对文件或目录的属性进行操作,如:文件名、最后修改日期、文件大小等等;需要注意的是, File 对象并不能直接对文件进行读 / 写操作,只能查看文件的属性;

File 类的构造方法

File 类的构造方法有 4 种重载方式,常用的如下:

如:// 在当前目录下创建一个与 aaa.txt 文件名相关联的文件对象File f1 = new File("aaa.txt");// 指明详细的路径以及文件名,请注意双斜线File f2 = new File("D:\\Java\\Hello.java");

构 造 方 法 说 明File(String pathname) 指定文件(或目录)名和路径创建文件对象

File 类中的常用方法

方 法 原 型 说 明boolean exists() 判断文件是否存在,存在返回 true ,否则返回 false

boolean isFile() 判断是否为文件,是文件返回 true ,否则返回 false

boolean isDirectory() 判断是否为目录,是目录返回 true ,否则返回 false

String getName() 获得文件的名称String getAbsolutePath() 获得文件的绝对路径long length() 获得文件的长度(字节数)boolean createNewFile()throws IOException

创建新文件,创建成功返回 true ,否则返回 false ,有可能抛出IOException异常,必须捕捉

boolean delete() 删除文件,删除成功返回 true ,否则返回 false

File[] listFiles() 返回文件夹内的子文件与子文件夹的数组

File 类示例

public class FileDemo{ public static void main(String[] args) { // 创建一个文件对象,使之与一个文件关联 File file = new File("test.txt"); //显示与文件有关的属性信息 System.out.println(" 文件或目录是否存在: " + file.exists()); System.out.println(" 是文件吗: " + file.isFile()); System.out.println(" 是目录吗: " + file.isDirectory()); System.out.println(" 名称: " + file.getName()); System.out.println("绝对路径: " + file.getAbsolutePath()); System.out.println(" 文件大小: " + file.length()); }}

stream (流)

流是指一连串流动的数据信号,是以先进先出的方式发送和接收数据的通道。

‘A’‘B’‘C’‘D’‘E’‘F’InputStream

来自数据源的数据流

OutputStream

流向目的地的数据流

流(续)

流的类型

根据流动方向的不同,流分为输入流和输出流;对于输入和输出流,由于传输格式的不同,又分为字节流和字符流:字节流是指 8 位的通用字节流,以字节为基本单位,在 java.io包中,对于字节流进行操作的类大部分继承于 InputStream(输入字节流)类和 OutputStream (输出字节流)类;字符流是指 16 位的 Unicode 字符流,以字符(两个字节)为基本单位,非常适合处理字符串和文本,对于字符流进行操作的类大部分继承于 Reader (读取流)类和 Writer (写入流)类。

使用 FileInputStream 类读文件

FileInputStream 类称为文件输入流,继承于InputStream 类,是进行文件读操作的最基本类;它的作用是将文件中的数据输入到内存中,我们可以利用它来读文件;由于它属于字节流,因此在读取 Unicode 字符(如中文)的文件时可能会出现问题。

FileInputStream 类的构造方法

FileInputStream 类的构造方法有 3 种重载方式,以下是常用的几种。

构 造 方 法 说 明FileInputStream(File file)throws FileNotFoundException

使用 File 对象创建文件输入流对象,如果文件打开失败,将抛出异常

FileInputStream(String name)throws FileNotFoundException

使用文件名或路径创建文件输入流对象,如果文件打开失败,将抛出异常

FileInputStream 类的常用方法

方 法 原 型 说 明int read()throws IOException

读取文件中的数据,一次读取一个字节,读取的数据作为返回值返回,如果读到文件末尾则返回 -1 ,有可能抛异常,必须捕捉

int read(byte[] b)throws IOException

读取文件中的数据,将读到的数据存放到 byte 型数组中,并返回读取的字节的数量,未读到数据返回 -1 ,有可能抛异常,必须捕捉

void close()throws IOException 关闭流对象,有可能抛异常,必须捕捉

FileInputStream 对象读文件示例 1

public class FileInputStreamDemo1 { public static void main(String[] args) { try { File file = new File("test.txt"); // 创建文件对象 // 使用文件对象创建文件输入流对象,相当于打开文件 FileInputStream fis = new FileInputStream(file); for (int i = 0; i < file.length(); i++) { char ch = (char)(fis.read()); //循环读取字符 System.out.print(ch); } System.out.println(); fis.close(); // 关闭流 } catch (FileNotFoundException fnfe) { System.out.println(" 文件打开失败。 "); } catch (IOException ioe) { ioe.printStackTrace(); } }}

FileInputStream 对象读文件示例 2

public class FileInputStreamDemo2 { public static void main(String[] args) { try { File file = new File("test.txt"); // 创建文件对象 FileInputStream fis = new FileInputStream(file); //根据文件的字节长度创建字节数组 byte[] buf = new byte[(int)(file.length())]; fis.read(buf); //读取文件中的数据存放到字节数组中 String str = new String(buf); //利用字节数组创建字符串 System.out.println(str); //打印字符串 fis.close(); // 关闭流 } catch (FileNotFoundException fnfe) { System.out.println(" 文件打开失败。 "); } catch (IOException ioe) { ioe.printStackTrace(); } }}

使用 FileOutputStream 类写文件

FileOutputStream 类称为文件输出流,继承于OutputStream 类,是进行文件写操作的最基本类;它的作用是将内存中的数据输出到文件中,我们可以利用它来写文件。

FileOutputStream 类的构造方法

FileOutputStream 类的构造方法有 5 种重载方式,以下是常用的几种。构 造 方 法 说 明

FileOutputStream(File file)throws FileNotFoundException

使用 File 对象创建文件输出流对象,如果文件打开失败,将抛出异常

FileOutputStream(File file, boolean append)throws FileNotFoundException

使用 File 对象创建文件输出流对象,并由参数append指定是否追加文件内容, true 为追加, false 为不追加,异常情况同上

FileOutputStream(String name)throws FileNotFoundException

直接使用文件名或路径创建文件输出流对象,异常情况同上

FileOutputStream(String name, boolean append)throws FileNotFoundException

直接使用文件名或路径创建文件输出流对象,并由参数 append指定是否追加,异常情况同上

FileOutputStream 类的常用方法

方 法 原 型 说 明void write(int b)throws IOException 往文件中写数据,一次写一个字节,有可能抛异常,必须捕捉void write(byte[] b)throws IOException

往文件中写数据,将 byte 数组中的数据全部写入到文件中,有可能抛异常,必须捕捉

void close()throws IOException 关闭流对象,有可能抛异常,必须捕捉

FileOutputStream 对象写文件示例 1

public class FileOutputStreamDemo1{ // 在函数内部不进行异常处理,将异常抛出函数外部 public static void main(String[] args) throws IOException { String str = "Hello world!"; File file = new File("test.txt"); // 创建文件对象 // 通过文件对象创建文件输出流对象 // 附加第二个参数 true ,指定进行文件追加,默认为不追加 FileOutputStream fos = new FileOutputStream(file, true); //逐个将字符写入到文件中 for (int i = 0; i < str.length(); i++) { fos.write(str.charAt(i)); } fos.close(); // 关闭流 }}

FileOutputStream 对象写文件示例2

public class FileOutputStreamDemo2{ // 在函数内部不进行异常处理,将异常抛出函数外部 public static void main(String[] args) throws Exception { String str = "I Love Java"; // 通过文件名创建文件输出流对象 FileOutputStream fos = new FileOutputStream("test.txt"); // 将字符串转化为字节数组 byte[] buffer = str.getBytes(); // 将字节数组中包含的数据一次性写入到文件中 fos.write(buffer); // 关闭流 fos.close(); }}

FileInputStream/FileOutputStream小结

FileInputStream 类和 FileOutputStream 类是成对出现的,一个进行输入(读文件)操作,一个进行输出(写文件)操作;由于采用字节方式进行数据传输,不必考虑数据的格式问题,这两个类对文件操作的效率较高;可以使用这两个类完成复制文件的操作。

复制文件示例

public class CopyFileDemo { public static void main(String[] args) throws IOException { File srcFile = new File("src.dat"); // 源文件对象 File destFile = new File("dest.dat"); //目标文件对象 if (!(destFile.exists())) { //判断目标文件是否存在 destFile.createNewFile(); // 如果不存在则创建新文件 } // 使用源文件对象创建文件输入流对象 FileInputStream fis = new FileInputStream(srcFile); // 使用目标文件对象创建文件输出流对象 FileOutputStream fos = new FileOutputStream(destFile); byte[] buf = new byte[1024]; // 创建字节数组,作为临时缓冲 System.out.println(" 开始复制文件 ..."); while (fis.read(buf) != -1) { //循环从文件输入流中读取数据 fos.write(buf); // 写入到文件输出流中 } System.out.println(" 文件复制成功! "); fis.close(); // 关闭流 fos.close(); }}

字符流

FileInputStram 类和 FileOutputStream 类虽然可以高效率地读 / 写文件,但对于 Unicode 编码的文件,使用它们有可能出现乱码;考虑到 Java 是跨平台的语言,要经常操作Unicode 编码的文件,使用字符流操作文件是有必要的;使用字符流将涉及到以下 4 个类:

FileReader 类和 FileWriter 类;BufferedReader 类和 BufferedWriter 类。

FileReader 类

FileReader 类称为文件读取流,允许以字符流的形式对文件进行读操作,其构造方法有 3 种重载方式,以下是常用的几种:

该类将从文件中逐个地读取字符,效率比较低下,因此一般将该类对象包装到缓冲流中进行操作。

构 造 方 法 说 明FileReader(File file)throws FileNotFoundException

使用 File 对象创建文件读取流对象,如果文件打开失败,将抛出异常

FileReader(String name)throws FileNotFoundException

使用文件名或路径创建文件读取流对象,如果文件打开失败,将抛出异常

BufferedReader 类

BufferedReader 类主要为字符流提供缓冲,以提高效率,其构造方法有 2 种重载方式,以下是常用的几种:

以下是 BufferedReader 类的常用方法:构 造 方 法 说 明

BufferedReader(Reader in) 将字符读取流对象包装成缓冲读取流对象

方 法 原 型 说 明String readLine() throws IOException 从缓冲读取流中读取一行字符,以字符串的形式返回,

有可能抛异常,必须捕捉void close() throws IOException 关闭流对象,有可能抛异常,必须捕捉

FileReader 配合 BufferedReader读文件示例

public class ReaderDemo{ public static void main(String[] args) throws IOException { File file = new File("test.txt"); // 通过文件对象创建文件读取流对象 FileReader fr = new FileReader(file); // 将文件读取流包装成缓冲读取流 BufferedReader br = new BufferedReader(fr); String str; while ((str = br.readLine()) != null) //逐行读取数据 { System.out.println(str); } br.close(); // 关闭流 fr.close(); // 关闭流 }}

FileWriter 类

FileWriter 类称为文件写入流,以字符流的形式对文件进行写操作,其构造方法有 5 种重载,以下是常用的几种:

与 FileReader 类相似, FileWriter 类同样需要使用缓冲流进行包装。

构 造 方 法 说 明FileWriter(File file)throws IOException

使用 File 对象创建文件写入流对象,如果文件打开失败,将抛出异常,必须捕捉

FileWriter(File file, boolean append)throws IOException

使用 File 对象创建文件写入流对象,并由参数 append指定是否追加,异常情况同上

FileWriter(String name)throws IOException

直接使用文件名或路径创建文件写入流对象,异常情况同上

FileWriter(String name, boolean append)throws IOException

直接使用文件名或路径创建文件写入流对象,并由参数append指定是否追加,异常情况同上

BufferedWriter 类

BufferedWriter 类可以为 FileWriter 类提供缓冲,其构造方法有 2 种重载方式,以下是常用的几种:

以下是 BufferedWriter 类的常用方法:构 造 方 法 说 明

BufferedWriter(Writer out) 将字符写入流对象包装成缓冲写入流对象

方 法 原 型 说 明void write(String str)throws IOException 将一行字符串写入到缓冲写入流中,有可能抛异常,必须捕捉void newLine()throws IOException

将一个回车换行符写入到文件中,从而达到换行的效果,有可能抛异常,必须捕捉

FileWriter 配合 BufferedWriter 写文件示例

public class WriterDemo{ public static void main(String[] args) throws IOException { File file = new File("test.txt"); // 通过文件对象创建文件输出字符流对象 FileWriter fw = new FileWriter(file); // 将文件输出字符流包装成缓冲流 BufferedWriter bw = new BufferedWriter(fw); bw.write(" 大家好! "); bw.write("我正在学习 Java 。 "); bw.newLine(); //换个新行 bw.write("请多多指教。 "); bw.newLine(); //换新行 bw.write("Luckybug@21cn.com"); bw.close(); // 关闭流 fw.close(); // 关闭流 }}

从控制台接受输入

System.in 的 read方法可以从控制台接受输入;但由于 in 实际上是一个 InputStream 类的对象,它只能以字节形式接收数据,并且一次只能接受一个字节,这使输入操作很不便;必须将 System.in进行处理,才可以顺利地从控制台接受输入,这将使用到 :InputStreamReader 类BufferedReader 类

转换和包装 System.in

InputStreamReader 类用于将 InputStream 类型的字节输入流对象转换成 Reader 类型的字符读取流对象;其构造方法有 4 种重载方式,常用的如下:

可以使用它来转换 System.in ,如:InputStreamReader isr = new InputStreamReader(System.in);这样的字符流效率很低,再使用 BufferedReader 类来为其建立缓冲,如: BufferedReader br = new BufferedReader(isr);这样,就可以从控制台接受输入了。

构 造 方 法 说 明InputStreamReader(InputStream in) 将 InputStream 对象转换成 Reader 对象

从控制台接受输入示例

public class ConsoleInputDemo{ public static void main(String[] args) throws IOException { // 将 System.in转化成字符流对象 InputStreamReader isr = new InputStreamReader(System.in); // 用缓冲流进行包装 BufferedReader br = new BufferedReader(isr); System.out.println("请输入您的姓名: "); String name = br.readLine(); //接受输入 System.out.println("请输入您的年龄: "); int age = Integer.parseInt(br.readLine()); //接受输入 System.out.println("您的姓名: " + name); System.out.println("您的年龄: " + age); br.close(); // 关闭流 isr.close(); // 关闭流 }}

从控制台输入并写入到文件示例

/* 本例从控制台接受输入,然后写入到文件中,直到用户输入 "!!!" 为止 */File file = new File("input.txt"); // 创建文件对象if (!file.exists()) //判断该文件是否存在,如果不存在则创建新文件{ file.createNewFile();}FileWriter fr = new FileWriter(file); //针对文件对象创建文件写入流对象BufferedWriter bw = new BufferedWriter(fr); // 为文件写入流建立缓冲流// 将控制台输入对象转化成字符流,并建立缓冲流BufferedReader bin = new BufferedReader(new InputStreamReader(System.in));String str = bin.readLine(); //接受从控制台输入的一行字符串while (!(str.equals("!!!"))) // 如果输入 "!!!"则代表输入结束{ bw.write(str); // 将从控制台输入的字符串写入到文件中 bw.newLine(); //换新行 str = bin.readLine(); //再从控制台接受输入}// 关闭所有已经打开的流bw.close();fr.close();bin.close();

基本数据类型的读 / 写

FileInputStream 和 FileOutputStream 在读写文件时不考虑数据的类型;FileWriter 和 FileReader 在读写文件时,将所有的数据都看做字符;但有时候,我们需要将各种类型的数据写入文件或是从文件中读取, DataInputStream 类和DataOutputStream 类可以满足需要。

DataInputStream 类

DataInputStream 类可以输入任何类型的数据,但它不可以单独使用,需要要配合其它字节输入流一起使用;DataInputStream 类的构造方法只有一种方式:

如:// 将文件输入流包装成数据输入流,以便从文件中读取各种类型的数据FileInputStream fis = new FileInputStream("data.dat");DataInputStream dis = new DataInputStream(fis);

构 造 方 法 说 明DataInputStream(InputStream in) 利用其它的字节输入流创建数据输入流对象

DataInputStream 类的常用方法

方 法 原 型 说 明final boolean readBoolean() throws IOException 从数据输入流中读取一个 boolean 型数据final char readChar() throws IOException 从数据输入流中读取一个 char 型数据final int readInt() throws IOException 从数据输入流中读取一个 int 型数据final long readLong() throws IOException 从数据输入流中读取一个 long 型数据final float readFloat() throws IOException 从数据输入流中读取一个 float 型数据final double readDouble() throws IOException 从数据输入流中读取一个 double 型数据

DataOutputStream 类

DataOutputStream 类可以输出任何类型的数据,同样也需要配合其他字节输出流一起使用;DataOutputStream 类的构造方法如下:

如:// 将文件输出流包装成数据输出流,以便往文件中写入各种类型的数据FileOutputStream fos = new FileOutputStream("data.dat");DataOutputStream dos = new DataOutputStream(fos);

构 造 方 法 说 明DataOutputStream(OutputStream out) 利用其它的字节输出流创建数据输出流对象

DataOutputStream 类的常用方法

方 法 原 型 说 明final void writeBoolean(boolean v) throws IOException 往数据输出流中写入一个 boolean 型数据final void writeChar(char v) throws IOException 往数据输出流中写入一个 char 型数据final void writeInt(int v) throws IOException 往数据输出流中写入一个 int 型数据final void writeLong(long v) throws IOException 往数据输出流中写入一个 long 型数据final void writeFloat(float v) throws IOException 往数据输出流中写入一个 float 型数据final void writeDouble(double v) throws IOException 往数据输出流中写入一个 double 型数据

对象序列化

Java 的对象序列化( Object Serialization )将那些实现了 Serializable接口的对象转换成一个字节序列,并可以在以后将这个字节序列完全恢复为原来的对象。这一过程甚至可通过网络进行。这意味着序列化机制能自动弥补不同操作系统之间的差异。

对象序列化

只要对象实现了 Serializable接口(该接口仅是一个标记接口,不包括任何方法),对象的序列化处理就会非常简单。当序列化的概念被加入到语言中时,许多标准库类都发生了改变,以便能够使之序列化——其中包括所有原始数据类型的包装器、所有容器类以及许多其他的东西。甚至 Class 对象也可以被序列化。

对象序列化

为了序列化一个对象,首先要创建某些 OutputStream对象,然后将其封装在一个 ObjectOutputStream 对象内。这时,只需调用 writeObject() 即可将对象序列化,并将其发送给 OutputStream 。要将一个序列重组为一个对象,需要将一个 InputStream封装在 ObjectInputStream内,然后调用 readObject() 。和往常一样,我们最后获得的是指向一个向上转型为 Object 的句柄,所以必须向下转型,以便能够直接对其进行设置。

补充

Properties 类该类是位于 util 包里的一个工具类,与 IO 的结合能十分有效地读写文件,特别在准确快速读取方面效率十分高!该类结合了 util 包里的 HashMap 的存储方法,使用了键值对方式

构 造 方 法 说 明Properties() 创建一个 Properties 对象

补充 Properties 类的常用方法

方 法 原 型 说 明void load(Reader reader) 通过字符流加载一个文件void load(InputStream in) 通过字节流加载一个文件String getProperty(String key) 通过键获得文件中对应的值

Object setProperty(String key,String value) 存入一对键值对void store(OutputStream out, String comments) 以适合使用 load 方法加载到 Properties 表中的格式,将此

Properties 表中的属性列表(键和元素对)写入输出流。

输入输出流

java.io 包是 Java内置的包,其中包含一系列对输入 / 输出进行操作的类;File 类的对象可以访问文件(或目录)的属性,但不可以进行读 / 写操作;从方向上讲,流分为输入流和输出流,但从格式上区分的话,流分为字节流和字符流;使用 FileInputStream 类和 FileOutputStream 类以字节流方式对文件进行读 / 写操作,这是操作文件最基本的两个类;

输入输出流

FileReader 类和 FileWriter 类配合BufferedReader 类和 BufferedWriter 类可以以字符流方式对文件进行读 / 写操作;从控制台接受输入要使用到 InputStreamReader 类和BufferedReader 类;DataInputStream 类和 DataOutputStream 类允许读 / 写各种基本数据类型;绝大部分的 IO 操作都可能抛出 IOException 。

JDK API : java.lang 包

若通过 ObjectOutputStream 向一个文件中多次以追加方式写入object, 为什么用 ObjectInputStream读取这些 object 时会产生StreamCorruptedException?

使用缺省的 serializetion 的实现时 , 一个 ObjectOutputStream 的构造和一个 ObjectInputStream 的构造必须一一对应 .ObjectOutputStream 的构造函数会向输出流中写入一个标识头 , 而ObjectInputStream会首先读入这个标识头 .因此 ,多次以追加方式向一个文件中写入 object 时 ,该文件将会包含多个标识头 .所以用ObjectInputStream 来 deserialize这个 ObjectOutputStream 时 ,将产生 StreamCorruptedException. 一种解决方法是可以构造一个ObjectOutputStream 的子类 ,并覆盖 writeStreamHeader()方法 .被覆盖后的 writeStreamHeader()方法应判断是否为首次向文件中写入object, 是则调用 super.writeStreamHeader();若否 , 即以追加方式写入 object 时 ,则应调用 ObjectOutputStream.reset()方法 .

输入输出流

使用 IO技术,创建一个目录,然后复制一个文件到该目录!使用 IO技术,开发出一个控制台的资源管理器!–要求:

• 从命令行输入一个路径!如果存在将该目录下所有的文件和文件夹列举出来,• 如果不存在则输出不存在该路径。

输入输出流

从控制台输入一些字符串,并将该类信息保存到日志文件中去。从控制台进行输入用户名以及用户密码,判断是否登录成功!要求准确的用户名和密码存在配置文件中!

474

附录: IO 流的继承体系

InputStream

FileInputStream

StringBufferInputStream

FilterInputStream

DataInputStream

BufferedInputStream

PipedInputStream

SequenceInputStream

LineNumberInputStream

PushbackInputStream

字节输入流

475

附录: IO 流的继承体系

Reader

BufferedReader

字符输入流

CharArrayReader PipedReader

StringReader

LineNumberReader

FilterReader

InputStreamReader

FileReaderPushbackReader

476

附录: IO 流的继承体系

Writer

BufferedWriter

字符输出流

CharArrayWriter

StringWriterPipedWriter

PrintWriter OutputStreamWriter

FileWriter

477

附录: IO 流的继承体系

OutputStream

FileOutputStream

PipedOutputStream

字节输出流

FilterOutputStreamByteArrayOutputStream

DataOutputStream PrintStream

BufferedOutputStream

478

网络通讯

• Java 提供 java.io 包进行 io 操作• Java 提供将对象序列化的方法将对象持久化或者通过网络传输

479

本章相关词汇

单 词 说 明Internet Protocol ( IP ) 互联网协议net 网络,网状物socket 套接字,插座server 服务器,服务端client 客户,客户端port 端口accept 接收,认可

480

网络通讯

网络基础知识IP 地址与端口java.net 包

InetAddressSocketServerSocket

UDPURL扩展 IO 流的相关知识

481

网络通讯

java.net 包InetAddressSocketServerSocket

482

计算机网络与通信

通信是人类生活中最重要的需求之一;通信是指:对语言、文字、声音和图片以及其它任何类型的相关数据进行传输;计算机的出现以及网络的构成,使得数据通信更加快速有效。

483

网络原理

网络就是一组连接在一起的计算机。

使用网卡、电缆、集线器等连接设备连接计算机以组成网络。

484

客户端 /服务器模式

网络发展,促使客户端 /服务器模式应运而生;通过网络,向另一台计算机请求服务的计算机称为客户端,而处理请求的计算机称为服务器;如数据服务器,需要数据的客户端机器向数据服务器提出请求,而服务器则处理客户端的请求并向其发送所需的数据;客户端 /服务器的优势在于:服务器和客户端分摊任务,分别负责部分处理工作;服务器可以同时为多台客户端服务;多个客户端可以同时访问服务器上的相同数据。

485

IP地址

网络中有多台计算机,它们必须通过某种标识来区分每一台机器,这就是 IP 地址;IP 地址由 4 个字节共 32 位二进制数组成,类似于:192.168.0.8 ;在网络寻找某一台计算机都是依靠它的 IP 地址(用域名或计算机名定位主机其实也是通过某种服务转成 IP 地址后再找到该主机的);

网络 ID :网络 ID标识了计算机或网络设备所在的网段;主机 ID :主机 ID标识了特定的主机或网络设备

486

特殊 IP地址

许多网络地址被保留用于特殊用途;0.0.0.0 和 127.0.0.1就是两个此类地址,第一个称为缺省路由,后一个是环回地址;127.0.0.1被保留用于用户主机的本地 IP 话务,它被分配给一个特殊接口,即起到闭合电路作用的环回接口。

487

端口

在一台物理性的计算机中,往往运行着多个网络程序,一个 IP 地址并不足以完整标识一个服务器,因此,端口是机器内部独一无二的场所;一台计算机上可能同时运行多个网络程序, IP 地址只能确保把数据送到指定的计算机,但不能保证把这些数据传递给哪个网络程序;端口使用一个 16 位的数字来表示,它的范围是0~65535 , 1024 以下的端口号保留给预定义的服务,如: http 使用 80 端口;网络间通信其实是在网络应用程序端口之间进行的。

488

java.net 包

使用 java.net 包可以实现 Java 的网络功能,其中包含了一系列与网络通讯相关的类,比较重要的有:InetAddressSocketServerSocket如果要进行网络通讯编程的话,必须导入此包。

489

java.net.InetAddress

InetAddress 用来表示互联网协议( IP )地址,它的实例将 IP 地址和 DNS (主机名解析)进行了封装;该类无法直接实例化,只能通过下列静态工厂方法获得实例:方 法 原 型 说 明

static InetAddress getByName(String host)throws UnknownHostException 在给定主机名的情况下确定主机的 IP 地址static InetAddress getLocalHost()throws UnknownHostException 返回本地主机的 InetAddress 对象static InetAddress getByAddress(byte[] address)throws UnknownHostException 在给定原始 IP 地址的情况下,返回 InetAddress 对象static InetAddress[] getAllByName(String host)throws UnknownHostException

在给定主机名的情况下,根据系统上配置的名称服务返回其 IP 地址所组成的数组

490

InetAddress 示例

import java.net.*; //导入 java.net 包public class InetAddressDemo { public static void main(String[] args) { try { //获得当前本地主机的 IP 地址 InetAddress add1 = InetAddress.getLocalHost(); System.out.println("当前本地主机: " + add1); //根据域名,通过 DNS域名解析,获得相应服务的主机地址 InetAddress add2 = InetAddress.getByName("www.163.com"); System.out.println(" 网易服务器主机: " + add2); //根据字符串表现形式的 IP 地址获得相应的主机地址 (若果网络上有该 IP 地址的话 ) InetAddress add3 = InetAddress.getByName("192.168.0.22"); System.out.println("IP 地址为 192.168.0.22 的主机: " + add3); //根据机器名获得相应的主机地址(如果网络上有该机器名的话) InetAddress add4 = InetAddress.getByName("J104"); System.out.println(" 机器名为 J104 的主机: " + add4); } catch (UnknownHostException uhe) { uhe.printStackTrace(); } }}

491

InetAddress 的其它方法

方 法 原 型 说 明byte[] getAddress() 返回此 InetAddress 对象的原始 IP 地址String getHostAddress() 返回此 InetAddress 对象的 IP地址字符串String getHostName() 返回此 InetAddress 对象的主机名称boolean equals(Object other) 判断两个 IP地址是否相等String toString() 返回此 InetAddress 对象的字符串表现形式(主机名 /IP地

址)

492

套接字

网络上计算机通过 TCP/IP协议进行通信,而套接字( socket )则将这些通信协议进行了封装;TCP/IP套接字用于在主机和 Internet 之间建立可靠的、双向的、持续的、点对点的流式连接;socket 用于描述 IP 地址和端口,应用程序通过套接字向网络发出请求或者应答网络请求;也就是说,套接字起到通信端点的作用;客户端和服务器通过套接字建立连接和通信。

493

主机甲

套接字通信原理

A 程序

主机乙

B 程序TCP/IP协议

网 卡

网络管理软件socket socket

主机甲上的 A 程序将一段数据写入 socket 中 主机乙上的 B 程序从 socket 中读取这段数据

494

java.net.Socket

Socket 类用于创建套接字对象,其构造方法共有 9种重载,常用的有如下几种:

套接字会因为网络的连接中断而失效,所以对它的操作都有可能抛出 IOException

构 造 方 法 说 明Socket(InetAddress address, int port)throws IOException

用预先存在的 InetAddress 对象和端口创建一个与本地主机连接的套接字

Socket(String hostName, int port)throws UnknownHostException, IOException 创建一个本地主机与给定名称的主机和端口连接的套接字

495

Socket 的常用方法

一旦 Socket 对象被成功创建,就可以获得访问与之相关的输入 / 输出流的权力,通过流操作从套接字中发送(输出)和接收(输入)数据:方 法 原 型 说 明

InputStream getInputStream() throws IOException 获得当前套接字的输入流OutputStream getOutputStream() throws IOException 获得当前套接字的输出流void close() throws IOException 关闭当前套接字

496

Socket 的其它方法

使用下面的方法,可以在任何时候检查套接字的地址和与之相关的端口信息:方 法 原 型 说 明

InetAddress getInetAddress() 返回与当前套接字连接的远程主机的 InetAddress 对象,如果未连接,则返回 null

int getPort() 返回与当前套接字连接的远程主机端口InetAddress getLocalAddress() 返回与当前套接字绑定的本地主机的 InetAddress 对象int getLocalPort() 返回与当前套接字绑定的本地主机端口

497

java.net.ServerSocket

ServerSocket 类用来创建服务器套接字,它监听本地或远程客户程序通过公共端口的连接;ServerSocket 类有 4 个重载的构造方法,以下是常用的:

ServerSocket 的常用方法如下:构 造 方 法 说 明

ServerSocket(int port) throws IOException 创建服务套接字,它负责侦听由 port指定的端口

方 法 原 型 说 明Socket accept() throws IOException 等待并侦听来自客户端的请求,一旦接收到,返回一个

与客户进行通信的 Socket

void close() throws IOException 关闭当前服务器套接字

498

Socket 编程

编写服务器端程序:创建一个服务器套接字( ServerSocket ),绑定到指定端口;调用 accept方法,侦听来自客户端的请求,如果客户发出请求,则接受连接,返回通信套接字( Socket );调用 Socket 的 getInputStream 和getOutputStream方法,获得输入 / 输出流,开始网络数据的接收和发送;关闭通信套接字,关闭服务器套接字。

499

Socket 编程

编写客户端程序:创建一个套接字( Socket ) ,向服务器的侦听端口发出请求;与服务器正确连接后,调用 Socket 的 getInputStream 和getOutputStream方法,获得输入 / 输出流,开始网络数据的接收和发送;关闭通信套接字。

500

ClientServer

Socket 编程示意图

ServerSocket(int port)

InputStream getInputStream()

OutputStream getOutputStream()

close() close()

InputStream getInputStream()

OutputStream getOutputStream()

Socket(InetAddress address, int port)

Socket accept()

501

示例(服务端)public class Server { private static final int SERVER_PORT = 9001; // 指定侦听端口 public Server() { try { ServerSocket ss = new ServerSocket(SERVER_PORT); // 创建服务器套接字 System.out.println("服务端已启动,正在等待客户端 ..."); Socket s = ss.accept(); //侦听来自客户端的请求 InputStream in = s.getInputStream(); //获得输入流,用来接收数据 OutputStream out = s.getOutputStream(); //获得输出流,用来发送数据 byte[] buf = new byte[1024]; int len = in.read(buf); // 从输入流中读取数据 String strFromClient = new String(buf, 0, len); System.out.print(" 来自客户端的信息 >>"); System.out.println(strFromClient); String strToClient = "我也很好! "; out.write(strToClient.getBytes()); //往输出流中发送数据 in.close(); out.close(); // 关闭输入流和输出流 s.close(); ss.close(); // 关闭通信套接字和服务器套接字 System.out.println("服务端已关闭。 "); } catch (IOException ioe) { ioe.printStackTrace(); } }

public static void main(String[] args) { new Server(); }}

502

示例(客户端)public class Client { private static final int SERVER_PORT = 9001; //服务器的侦听端口 public Client() { try { // 由于服务端也是运行在本机,所以创建本机的 InetAddress 对象 InetAddress address = InetAddress.getByName("localhost"); Socket s = new Socket(address, SERVER_PORT); // 向服务器侦听端口发出请求 System.out.println(" 客户端已启动。 "); InputStream in = s.getInputStream(); //获得输入流,用来接收数据 OutputStream out = s.getOutputStream(); //获得输出流,用来发送数据 String strToServer = "你好! "; out.write(strToServer.getBytes()); //往输出流中发送数据 byte[] buf = new byte[1024]; int len = in.read(buf); // 从输入流中读取数据 String strFromServer = new String(buf, 0, len); System.out.print(" 来自服务端的回答 >>"); System.out.println(strFromServer); in.close();out.close(); // 关闭输入流和输出流 s.close(); // 关闭通信套接字 System.out.println(" 客户端已关闭。 "); } catch (UnknownHostException nhe) { System.out.println("未找到指定主机 ..."); } catch (IOException ioe) { ioe.printStackTrace();} } public static void main(String[] args) { new Client(); }}

UDP

用数据报方式编写 client/server 程序时,无论在客户方还是服务方,首先都要建立一个 DatagramSocket对象,用来接收或发送数据报,然后使用DatagramPacket 类对象作为传输数据的载体

UDP

接收端程序编写:调用 DatagramSocket(int port) 创建一个数据报套接字,并绑定到指定端口上;调用 DatagramPacket(byte[] buf, int length) ,建立一个字节数组以接收 UDP 包 。调用 DatagramSocket 类的 receive() ,接收 UDP 包。最后关闭数据报套接字。

UDP

发送端程序编写:调用 DatagramSocket() 创建一个数据报套接字;调用 DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) ,建立要发送的 UDP 包调用 DatagramSocket 类的 send() ,发送 UDP 包。最后关闭数据报套接字。

TCP 与 UDP 的区别

UDP 和 TCP协议的主要区别是两者在如何实现信息的可靠传递方面不同。 TCP协议中包含了专门的传递保证机制,当数据接收方收到发送方传来的信息时,会自动向发送方发出确认消息;发送方只有在接收到该确认消息之后才继续传送其它信息,否则将一直等待直到收到确认信息为止。与 TCP 不同, UDP协议并不提供数据传送的保证机制。如果在从发送方到接收方的传递过程中出现数据报的丢失,协议本身并不能做出任何检测或提示。因此,通常人们把UDP协议称为不可靠的传输协议。

TCP 与 UDP 的区别

相对于 TCP协议, UDP协议的另外一个不同之处在于如何接收突法性的多个数据报。不同于TCP , UDP并不能确保数据的发送和接收顺序。

URL

URL(Uniform Resource Locator)---- 统一资源定位器,表示 Internet 上某一资源的地址。URL 组成 : 协议名和资源名

protocol:resourceNameURL举例 :

http://www.dcreat.nethttp://www.tsinghua.edu.cnftp://files.tiandown.com

URL 类

常用构造方法public URL(String spec);例如: URL u1 = new URL(“http://home.netscape.com/home/”);public URL(URL context, String spec);例如: URL u2 = new URL(u1, “welcome.html”);public URL(String protocol, String host, String file);例如: URL u3 = new URL(“http”, “www.sun.com”, “developers/index.html” );public URL (String protocol, String host, int port, String file);例如: URL u4 = new URL(“http”, “www.sun.com”, 80, “developers/index.html” );

URL实例

public class URLConnectionReader { public static void main(String[] args) throws Exception { URL yahoo = new URL("http://www.yahoo.com/");

URLConnection yc = yahoo.openConnection(); BufferedReader in = new BufferedReader( new InputStreamReader( yc.getInputStream())); String inputLine;

while ((inputLine = in.readLine()) != null) System.out.println(inputLine); in.close(); }}

511

关于 IO 流

在两台主机之间通过 socket 建立握手连接之后,主要是通过输入 / 输出流来接收和发送数据,实际上,对于网络的操作,绝大部分是在操作 IO 流;实际上操作网络端口流与操作磁盘文件流的原理是相似的;出于效率的考虑, socket 使用的是字节流,但是在实际操作中,字节往往会带来不便,所以我们一般会把字节流改造成字符流后进行操作。

512

改写示例(代码片段)

public class Client { //改用字符流的方式收发网络数据 …… Socket s = new Socket(address, SERVER_PORT); InputStream is = s.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader in = new BufferedReader(isr);

OutputStream os = s.getOutputStream(); PrintWriter out = new PrintWriter(os, true); …… out.println(strToServer); …… String strFromServer = in.readLine(); …… out.close(); in.close(); ……}

513

网络通讯

网络的发展促使计算机之间的通信日趋重要;网络上的计算机通过 IP 地址来区分不同的机器,端口是数据的通道,使得多个应用程序在同一主机上进行网络通信;InetAddress 用来描述主机地址;Socket 用来创建两台主机之间的连接;ServerSocket 用来侦听来自客户端的请求;我们实际上是通过操作 IO 流来进行数据的收发工作,将字节流改造成字符流会使操作更加方便。

网络通讯中,端口有什么含义。端口的取值范围?端口是一个主机中不同应用程序的标识,它是一个编号, CPU 将根据这个编号决定把从网卡接收的的数据发送给哪一个应用程序进行处理。端口的本质是一个 16 位的无符号整数,因此它的取值范围为 0-65535 , 1024 之前的端口号称为保留端口,因为很多系统常见服务默认需要占用这些端口,因此我们自定义的网络服务要尽量避免使用这些端口号(自己实现的诸如HTTP 、 FTP 等替代系统服务的应用除外)

网络通讯

515

网络通讯

使用 Socket与 ServerSocket 实现客户端与服务器端的通信习题 2 : 使用 URL读取 www.sohu.com 的网页内容习题 3 : 使用 UDP 实现客户端与服务器端的通信

回顾 1

java.io 包中包含了一系列对输入 / 输出进行操作的类File 类用于访问文件系统,但只能访问文件(或目录)的属性,而不能进行读 / 写操作流是指一连串流动的数据信号,是以先进先出方式发送信息的通道,从流动方向上区分,流可以分为输入流和输出流,从格式上区分,可以分为字节流和字符流使用 FileInputStream 类和 FileOutputStream 类以字节流的方式读写文件

回顾 2

FileReader 类和 FileWriter 类配合BufferedReader 类和 BufferedWriter 类可以以字符流的方式对文件进行读 / 写操作要从控制台接受输入,需要将 System.in 对象进行包装,使用如下语句:InputStreamReader isr = new InputStreamReader(System.in);BufferedReader br = new BufferedReader(isr);

DataInputStream 类和 DataOutputStream 类在读 / 写数据时可以区分数据类型绝大部分的 IO 操作都有可能抛出 IOException

本章相关词汇

单 词 说 明Reflection 反射 , 映象 , 倒影driver 驱动,驱动程序connection 连接manager 管理器statement 语句prepared 预备的,预编译的result 结果create 创建,创造execute 执行query 查询

JDBC

Reflection简介Relection 如何工作了解 Relection 相关 API了解 JDBC 的概念和 必要性了解 JDBC驱动程序类型了解 java.sql 包使用 JDBC进行 数据库编程 PreparedStatement接口纯 Java驱动方式 连接 不同数据库

JDBC

Reflection简介Relection 如何工作了解 Relection 相关 API了解 JDBC 的概念和 必要性了解 JDBC驱动程序类型了解 java.sql 包使用 JDBC进行 数据库编程 PreparedStatement接口纯 Java驱动方式 连接 不同数据库

java 动态相关机制

尽管 Java 不属于动态语言,但是它却有一个非常突出的动态相关机制,那就是反射: Reflection 。Reflection 机制 允许程序在运行时 透过 Reflection APIs取得 任何一个 已知名称的 class 的 内部信息 ,并可于运行时 改变 fields内容或 唤起 methods 。

Reflection Java 反射机制

JAVA反射机制是在运行状态中,对于任意一个类,都能够 知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。Reflection 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性。例如,使用它能获得Java 类中各成员的名称并显示出来。

Reflection 如何工作?编写一段代码,实现下列功能:列出java.util.Stack 类的各方法名以及它们的限制符和返回类型。

try { Class c = Class.forName(args[0]); Method m[] = c.getDeclaredMethods(); for (int i = 0; i < m.length; i++) { System.out.println(m[i].toString());

} }

使用 Reflection 的 3 个步骤

参看下列代码:Class c = Class.forName("java.lang.String");Method m[] = c.getDeclaredMethods();System.out.println(m[0].toString());

使用 Reflection 的 3 个步骤总结

第一步 :获得你想操作的类的 java.lang.Class 对象。第二步 :调用诸如 getDeclaredMethods 的方法,以取得需要的信息第三步:使用 reflection API 来操作这些信息

Reflection API 的组成由 java.lang.Class 和 java.lang.reflect classes: (包括: Field, Method,Constructor等)组成。Class Description

Array   Provides static methods to dynamically create and access arrays. Class   Represents, or reflects, classes and interfaces.

Constructor   Provides information about, and access to, a constructor for a class. Allows you to instantiate a class dynamically.

Field   Provides information about, and dynamic access to, a field of a class or an interface.

Method   Provides information about, and access to, a single method on a class or interface. Allows you to invoke the method dynamically.

Modifier   Provides static methods and constants that allow you to get information about the access modifiers of a class and its members.

Object   Provides the getClass method.

数据库访问技术简介

当今企业级应用程序大部分采用了客户机 /服务器( C/S )模式;客户端机器需要与服务器进行通讯,要操作数据库中的数据,执行 SQL ( Structured Query Language 结构化查询语言)语句以及检索查询结果;在 Java 中实现这些活动的技术称作 JDBC 。

数据库编程示意图

客户机 /服务器应用程序

数据库编程

JDBC

数据库

执行 SQL 语句

检索查询结果

关于 DBMS

DBMS ( DataBase Management System )是指数据库管理系统;目前 DBMS 的生产商众多,产品也不尽相同,如:– Oracle 公司的 Oracle 系列;– Microsoft 公司的 Access 系列和 SQL Server 系列;– Microsoft 公司早期的 FoxPro ;– IBM 公司的 DB2 ;– Sybase 公司的 Sybase ;– 还有自由开源的 MySQL 等等。这就意味着编程语言要针对不同的 DBMS 开发不同版本的应用程序,这将是一项非常枯燥的工作。

ODBC

ODBC ( Open DataBase Connectivity )指开放式数据库连接,是由 Microsoft 公司提供的应用程序接口;它负责连接各种不同产商和类型的 DBMS ,然后为各种不同的编程语言提供查询、插入、修改和删除数据的功能;如同在各种不同的 DBMS 和各种不同的编程语言之间架设了一座通用的桥梁。

JDBC

JDBC ( Java DataBase Connectivity )是由 Sun Microsystem 公司提供的 API ( Application Programming Interface 应用程序编程接口);它为 Java 应用程序提供了一系列的类,使其能够快速高效地访问数据库;这些功能是由一系列的类和对象来完成的,我们只需使用相关的对象,即可完成对数据库的操作。

JDBC 工作方式示意图

Java 程序

JDBC 驱动程序

数据库SQL 命令 结果

JDBC驱动程序类型

使用 JDBC连接数据库可以通过不同的驱动方式来实现,有 4 种驱动类型:– JDBC-ODBC桥驱动– 纯 Java驱动– 本地 API 部分 Java驱动– JDBC 网络纯 Java驱动

不论采用哪种驱动方式,在程序中对数据库的操作方式基本相同,只是加载不同的驱动程序即可。

java.sql 包

java.sql 包也是 Java内置的包,其中包含了一系列用于与数据库进行通信的类和接口;如果要使用到这些类和接口的话,则必须显式地声明如下语句:import java.sql.*;

java.sql 包中的一些接口接口名称 说 明

Connection 连接对象,用于与数据库取得连接Driver 用于创建连接( Connection )对象Statement 语句对象,用于执行 SQL 语句,并将数据检索到结果集

( ResultSet )对象中PreparedStatement 预编译语句对象,用于执行预编译的 SQL 语句,执行效率比

Statement高CallableStatement 存储过程语句对象,用于调用执行存储过程ResultSet 结果集对象,包含执行 SQL 语句后返回的数据的集合

java.sql 包中的一些类

类 名 称 说 明SQLException

数据库异常类,是其它 JDBC异常类的根类,继承于java.lang.Exception ,绝大部分对数据库进行操作的方法都有可能抛出该异常

DriverManager 驱动程序管理类,用于加载和卸载各种驱动程序,并建立与数据库的连接

Date 该类中包含有将 SQL日期格式转换成 Java日期格式的方法TimeStamp 表示一个时间戳,能精确到纳秒

JDBC 程序访问数据库步骤开 始 导入 java.sql 包

加载并注册驱动程序

创建 Connection 对象 创建 Statement 对象

执行 SQL 语句关闭 ResultSet 对象

关闭 Statement 对象 关闭 Connection 对象

使用 ResultSet 对象

建立数据源( ODBC ) 附加相应产商提供的驱动

结 束

JDBC-ODBC桥方式 纯 Java驱动方式

步骤详解 1 :建立数据源

这里以 JDBC-ODBC桥驱动方式为例,逐步详细地讲解在Java 程序中如何操作数据库,而对于其它驱动方式,只需更换驱动程序即可,其余不变;首先建立 ODBC 数据源:【开始】→ 【设置】→ 【控制面板】→【管理工具】→【数据源( ODBC )】新建数据源,名称可以任意,这里假设已经建立了一个名为 myODBC 的数据源,连接到 SQL Server 2000 中的pubs 数据库。

步骤详解 2 :加载驱动程序使用 Class 类的 forName方法,将驱动程序类加载到JVM ( Java 虚拟机)中;

对于使用 JDBC-ODBC桥的驱动方式,应该加载:sun.jdbc.odbc.JdbcOdbcDriver 类如:Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

方 法 原 型 说 明static Class forName(String className)throws ClassNotFoundException

将由 className指定完整名称的类加载到 JVM中,如果加载失败,将抛出异常,必须捕捉

步骤详解 3 :获得连接对象成功加载驱动后,必须使用 DriverManager 类的静态方法getConnection 来获得连接对象;

对于使用 JDBC-ODBC桥的连接方式,连接字符串的一般形式是:“ jdbc:odbc: 数据源名称”,如:Connection con =DriverManager.getConnection("jdbc:odbc:myODBC", "sa", "");

方 法 原 型 说 明static Connection getConnection(String url, String user,String password)throws SQLException

参数 url 是连接字符串,参数 user 是数据库用户名,参数password 是登录口令,成功连接到数据库返回Connection 对象,连接失败则抛出 SQLException异常,必须捕捉

步骤详解释 4 :创建语句对象一旦成功连接到数据库,获得 Connection 对象后,必须通过 Connection 对象的 createStatement方法来创建语句对象,才可以执行 SQL 语句;

如:Statement sta = con.createStatement();

方 法 原 型 说 明Statement createStatement()throws SQLException

成功创建返回 Statement 对象,否则抛出SQLException异常,必须捕捉

步骤详解 5 :执行 SQL 语句使用语句对象来执行 SQL 语句,有两种情况:一种是执行 DELETE 、 UPDATE 和 INSERT 之类的数据库操作语句( DML ),这样的语句没有数据结果返回,使用 Statement 对象的 executeUpdate方法执行;

如:sta.executeUpdate("INSERT INTO Friends VALUES('田七 ', '重庆 ', 456712, '2003-2-25', 7500)");

方 法 原 型 说 明int executeUpdate(String sql)throws SQLException

参数 sql 是要执行的 SQL 语句,执行成功返回受影响的行数,执行失败则抛出 SQLException异常,必须捕捉

步骤详解 5 :执行 SQL 语句(续)另一种是执行 SELECT这样的数据查询语句( DQL ),这样的语句将从数据库中获得所需的数据,使用Statement 对象的 executeQuery 方法执行;

如:ResultSet rs =sta.executeQuery("SELECT * FROM Friend");

方 法 原 型 说 明ResultSet executeQuery(String sql)throws SQLException

参数 sql 是要执行的 SQL 语句,查询成功返回包含有结果数据的 ResultSet 对象,否则抛出SQLException异常,必须捕捉

步骤详解 6 :关闭资源当对数据库的操作结束后,应当将所有已经被打开的资源关闭,否则将会造成资源泄漏;Connection 对象、 Statement 对象和 ResultSet 对象都有执行关闭的 close方法;函数原型都是: void close() throws SQLException– 如:rs.close(); // 关闭 ResultSet 对象sta.close(); // 关闭 Statement 对象con.close(); // 关闭 Connection 对象有可能抛出 SQLException异常,必须捕捉;请注意关闭的顺序,最后打开的资源最先关闭,最先打开的资源最后关闭。

数据库操作示例import java.sql.*; //导入 java.sql 包public class JDBCDemo { public static void main(String[] args) { String strCon = "jdbc:odbc:dsn_javaBase"; //连接字符串 String strUser = "sa"; // 数据库用户名 String strPwd = “sa"; //口令 System.out.println(" 正在连接数据库 ..."); try { //监控异常 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); // 加载驱动程序 Connection con; //获得连接对象 con = DriverManager.getConnection(strCon, strUser, strPwd); System.out.println(" 成功连接到数据库。 "); Statement sta = con.createStatement(); // 创建语句对象 // 执行 SQL 语句 String strSql = "DELETE FROM Friends WHERE [Name] = '郑六 '"; int count = sta.executeUpdate(strSql); System.out.println(" 成功删除 " + count + " 行数据。 "); sta.close(); con.close(); // 关闭所有已经打开的资源 } catch (ClassNotFoundException cnfe) { cnfe.printStackTrace(); } catch (SQLException sqle) { sqle.printStackTrace(); } }}

操作结果集使用 Statement 对象的 executeQuery方法成功执行SELECT 语句后,将返回一个包含有结果数据的ResultSet 对象,要从该对象中获取数据,将使用到如下方法:

方 法 原 型 说 明boolean next()throws SQLException

将结果集游标往下移动一行,如果已经到达结果集最后,将会返回 false ,有可能抛异常,必须捕捉

X getX(String columnName)throws SQLException

获得某个字段的值, X 是指具体的数据类型,视数据库表中字段的具体情况而定,该方法有一组,并且每个都有两种重载方法,一种是以字段名称为参数,另一种是以字段索引为参数(字段索引从 1 开始),有可能抛异常,必须捕捉

X getX(int columnIndex)throws SQLException

操作结果集示例try { String strCon = "jdbc:odbc:dsn_javaBase"; System.out.println(" 正在连接数据库 ..."); Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); Connection con; con = DriverManager.getConnection(strCon, "sa", “sa"); System.out.println(" 成功连接到数据库。 "); Statement sta = con.createStatement(); ResultSet rs = sta.executeQuery("SELECT * FROM Friends"); System.out.println("查询到数据如下: "); while (rs.next()) { //循环将结果集游标往下移动,到达末尾返回 false //根据字段名称获得各个字段的值 System.out.print(rs.getString("Name") + "\t"); //获得字符串 System.out.print(rs.getString("Address") + "\t"); //获得字符串 System.out.print(rs.getInt("Telephone") + "\t"); //获得整数 System.out.print(rs.getDate("HireDate") + "\t"); //获得日期型数据 System.out.println(rs.getFloat("Salary")); //获得浮点型数据 } rs.close(); sta.close(); con.close();} catch (ClassNotFoundException cnfe) { cnfe.printStackTrace(); } catch (SQLException sqle) { sqle.printStackTrace(); }

PreparedStatement接口1

如果要多次执行相似的 SQL 语句,可以使用PreparedStatemend (预编译语句对象)对象来执行;通过 Connection 对象的 prepareStatement方法来创建预编译语句对象;

PreparedStatement 对象会将 SQL 语句预先编译,这样将会获得比 Statement 对象更高的执行效率。

方 法 原 型 说 明PreparedStatementprepareStatement(String sql)throws SQLException

参数 sql 是要执行的 SQL 语句,根据指定的 SQL 语句创建PrepareStatement 对象,有可能抛异常,必须捕捉

PreparedStatement接口2

包含在 PreparedStatement 对象中的 SQL 语句可以带有一个或多个参数,使用“ ?” 作为占位符,如 :PreparedStatement ps = con.prepareStatement("UPDATE Friends SET Address = ? WHERE Name = ?");在执行 SQL 语句之前,必须使用 PreparedStatement 对象中的 setX方法设置每个“ ?” 位置的参数值;

如:ps.setString(1, "长沙 ");ps.setString(2, "王五 ");

方 法 原 型 说 明void setX(int parameterIndex, X x)throws SQLException

将 parameterIndex指定的“ ?” 位置指定为 x 的值,这里 X 可以指代任意数据类型, “ ?” 的索引从 1 开始。

PreparedStatement接口3

设置好每个参数的值之后,就可以使用PreparedStatement 对象的 executeUpdate 和executeQuery方法来执行 SQL 语句,这一点和Statement 对象很相似:

方 法 原 型 说 明int executeUpdate()throws SQLException

用于执行 INSERT 、 DELETE 和 UPDATE 语句,执行成功返回受影响的行数,否则抛出 SQLException异常,必须捕捉

ResultSet executQuery()throws SQLException

用于执行 SELECT 语句,执行成功返回包含有结果数据的ResultSet 对象,否则抛出 SQLException异常,必须捕捉

PreparedStatement 对象示例String strCon = "jdbc:odbc:dsn_javaBase";System.out.println(" 正在连接数据库 ...");Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");Connection con;con = DriverManager.getConnection(strCon, "sa", “sa");System.out.println(" 成功连接到数据库。 ");

PreparedStatement ps;// 使用带参数的 SQL 语句创建 PreparedStatement 对象ps = con.prepareStatement("UPDATE Friends SET Address = ? WHERE Name = ?");// 设置 SQL 语句中的参数值ps.setString(1, "长沙 ");ps.setString(2, "王五 ");int count = ps.executeUpdate(); // 执行命令System.out.println(" 成功更新了 " + count + " 行数据。 "); ps.close(); // 关闭资源con.close();

纯 Java驱动方式连接数据库

使用 JDBC-ODBC桥方式连接数据库,其性能完全取决于数据源( ODBC )的性能,并且无法脱离 Microsoft 的平台,这样将带来不便;大部分 DBMS 产商都为自己的产品开发了纯 Java 的驱动程序,我们只需要加载相应的驱动,就可以直接连接到数据库,而无需通过 ODBC桥连接;鉴于 DBMS 产品太多,这里只针对当今比较流行的 SQL Server 2000 和 Oracle 9i进行介绍。

下载驱动程序包

要使用纯 Java驱动,首先必须获得相应数据库的驱动程序包;根据数据库的类型,登录对应产商的官方网站,一般都可以免费获得;下载后,复制到本地磁盘,并将完整路径设置到classpath 环境变量中,如用开发工具开发程序,还需在开发环境中设置路径。

纯 Java驱动连接 SQL Server2000

使用纯 Java驱动连接到 SQL Server 2000 数据库,加载驱动程序应改成如下语句:Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");连接字符串应如下格式:

"jdbc:microsoft:sqlserver://服务器名或IP:1433;databaseName= 数据库名 "如:Connection con = DriverManager.getConnection("jdbc:microsoft:sqlserver://127.0.0.1:1433;databaseName=pubs", "sa", "");

纯 Java驱动连接 SQL Server2005

使用纯 Java驱动连接到 SQL Server 2005 数据库,加载驱动程序应改成如下语句:Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");连接字符串应如下格式:"jdbc:sqlserver://服务器名或 IP:1433;databaseName= 数据库名 "如:Connection con = DriverManager.getConnection("jdbc:sqlserver://127.0.0.1:1433;databaseName=pubs", "sa", "sa");

纯 Java驱动连接 MySQL

使用纯 Java驱动连接到 MySQL 5.0 数据库,加载驱动程序应改成如下语句:Class.forName("com.mysql.jdbc.Driver");连接字符串应如下格式:"jdbc:mysql://服务器名或 IP:3306/ 数据库名 "如:Connection con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "root");

纯 Java驱动连接 Oracle

使用纯 Java驱动连接到 Oracle 9i 数据库,加载驱动程序应改成如下语句:Class.forName("oracle.jdbc.driver.OracleDriver");连接字符串应如下格式:"jdbc:oracle:thin:@服务名或 IP:1521: 数据库名 "如:Connection con = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:test", "scott", "tiger");

总结

JDBC 是使用 Java 程序操作数据库的技术;使用 Class 类的 forName 方法可以将驱动程序加载到 Java 解释器中;使用 DriverManager 类的 getConnection方法获得Connection 对象,从而建立与数据库的连接; 使用 Connection 对象的 createStatement方法创建语句对象,以便执行 SQL 语句;使用 Statement 对象的 executeQuery或 executeUpdate方法执行 SQL 语句,并使用 ResultSet 对象来操作结果数据;PreparedStatement接口允许创建预编译的 SQL 语句,并使得在后续阶段可以指定语句的参数。

前序课程

集合框架线程包装类

预备知识

回溯

案例目的

帮助学员练习集合类 , 包装类的使用 , 理解线程并行机制。

案例规则描述

数独顾名思义——每个数字只能出现一次。数独是一种源自 18 世纪末的瑞士,后在美国发展、并在日本得以发扬光大的数字谜题。数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入 1-9 的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次( 如下图中已经填写完整的那样,不能重复,独立存在 ) 。 这种游戏全面考验做题者观察能力和推理能力,虽然玩法简单,但数字排列方式却千变万化,所以不少教育者认为数独是训练头脑的绝佳方式。

案例规则描述

谜题

填充完成之后

案例分析

可选列表:由数独的规则可以看出,每个节点都有一个可选列表(集合 1-9排除掉同行、同列、同一九宫格中已经存在的数字,就为当前节点的可选列表)。

可选列表为:{ 1,6,7}

课后作业

完成数独解谜和生成工具的实现

GUI入门

JDBC连接数据库的过程注册驱动( Class.forName )创建连接创建连接对象执行 SQL 语句

Statement 对象的类型与作用

GUI入门

单 词 说 明layout 版面,布局flow 流动,流式border 边界,边境grid 网格,格子title 头衔,标题size 尺寸,大小visible 可见的,显著的east 东,东方west 西,西方south 南,南方north 北,北方center 中央,中心

GUI入门

了解 AWT 以及 java.awt 包了解 Swing 组件和 javax.swing 包手工编码实现 GUI 程序掌握常用基本 Swing 组件的使用方法掌握常用布局管理器流式布局( FlowLayout )边界布局( BorderLayout )网格布局( GridLayout )使用面板( JPanel )实现复杂布局

GUI入门

手工编码实现 GUI 程序掌握常用基本 Swing 组件的使用方法掌握常用布局管理器流式布局( FlowLayout )边界布局( BorderLayout )网格布局( GridLayout )

GUI 的概念

到目前为止,我们在 C 和 Java 中编写的都是基于控制台的程序;GUI ( Graphical User Interface )即图形用户界面,它能够使应用程序看上去更加友好;Java 语言之所以如此流行的一个主要原因,就是因为它支持 GUI ;

AWT 简介

上一章我们已经知道,实现 GUI 编程是由一系列图形化组件来完成的(即一系列定义好的类),这些组件也被称为控件;在 Java 的早期版本中, GUI 组件由名为AWT ( Abstract Window Toolkit ,抽象窗口工具包)的标准库来提供;除了 GUI 组件外, AWT还包括其它功能来支持图像绘画、处理剪切 /复制类型的数据传送,以及其它相关操作。

java.awt 包

java.awt 包是 Java内置的包,属于 Java 基础类库( JFC )的一部分,其中包括以下内容:便于用户输入的一组丰富的界面组件;将组件放置在适当位置的几种布局管理器;事件处理模型;图形和图像工具等等。要使用到该包中的类,则必须显式地声明如下语句:

import java.awt.*;

AWT 组件的类体系结构

Component

Button Checkbox Container Choice Canvas TextComponent Label

Panel

Applet Frame Dialog

Window TextArea TextField

AWT 编程示例

AWT 组件最大的缺陷是它依赖于操作系统,也就是说, AWT 程序运行在不同的操作上会有不同的外观和行为,这一点对于 Java 的平台无关性来讲,是无法容忍的。

Swing 简介以及 javax.swing包

Swing 组件是在 AWT 组件基础上发展而来的轻量级组件,与 AWT相比不但改进了用户界面,而且所需的系统资源更少;Swing 是纯 Java 组件,使得应用程序在不同的平台上运行时具有相同外观和相同的行为。javax.swing 包中包含了一系列 Swing 组件,如果要使用该包中的类,则必须显式地声明如下语句:import javax.swing.*;

Swing 组件的类体系结构

JFrame

Frame

JDialog

Dialog

Window JComponent

Container

Component

Object

JOptionPane

JMenuBar

JList

JLabel

JComboBox

JText

JPanel

JScrollBar

AbstractButton

JMenuItem JButtonJToggleButton

JMenuJRadioButtonJCheckBox

JTextField

JTextArea

SWT 简介

IBM 资助的 Eclipse 项目使用 SWT ( The Standard Widget Toolkit ,标准部件工具包) ,采用和 AWT 相反的做法,即使用最大公倍数的做法来使用本地类库( C++开发的 DLL )来封装操作系统的组件,如果不存在,则采用模拟的方式,这样 SWT 随着 Eclipse 的推广获得了流行。

常用的基本 Swing 组件

在 Swing 编程中,有一些经常要使用到的组件,其中包括:JFrame (窗体,框架)JButton (按钮)JLabel (标签)JTextField (文本框)

javax.swing.JFrame

JFrame 组件用于在 Swing 程序中创建窗体;JFrame 类的构造方法有 4 种重载方式,以下是常用的几种:构 造 方 法 说 明

JFrame() 创建新窗体,该窗体初始为不可见JFrame(String title) 创建新窗体,使用参数 title指定标题,该窗体初始为不可见

JFrame 的常用方法

方 法 原 型 说 明void setTitle(String title) 设置窗体标题,标题内容由参数 title指定void setSize(int width, int height) 设置窗体的大小,参数 width指定宽度,参数 height

指定高度,单位为像素void setResizable(boolean resizable) 设置窗体能否调整大小,由参数 resizable决定void setVisible(boolean b) 设置窗体是否为可见,由参数 b决定, true 为可见,

false 为不可见Container getContentPane() 获得当前窗体的内容面板void setDefaultCloseOperation(int operation) 设置窗体在关闭时默认执行的操作void dispose() 释放当前窗体及其所有子组件所占用的资源,即卸载

窗体void repaint() 重新绘制当前窗体

创建窗体

对于类似于窗体这样的容器组件,我们一般自定义一个类,继承于 JFrame 类,然后将窗体中的子组件作为类中成员进行声明,以方便操作,如:public class MyFrame extends JFrame{

……}容器组件是指可以容纳其它组件的组件。

创建窗体示例

import javax.swing.*; //导入必要的包/**自定义窗体类,继承于 JFrame 类 */public class MyFrame extends JFrame{ /** 构造方法 */ public MyFrame() { //super("这是我的第一个窗体 "); //利用父类的构造方法设置标题 this.setTitle("这是我的第一个窗体 "); // 设置窗体的标题 this.setSize(300, 200); // 设置窗体的大小 this.setVisible(true); // 设置窗体为可见,即显示窗体 } /**main方法,程序入口 */ public static void main(String[] args) { MyFrame mf = new MyFrame(); // 创建窗体实例 }}

窗体的内容面板

一个完整的窗体是由外部框架和内容面板两部分组成的;外部框架是指由标题栏和四边所组成空心边框,它主要用来控制窗体的大小和外观;我们实际操作的是内容面板,如设置窗体的背景色,设置窗体的布局,往窗体中添加其它组件等等;使用 getContentPane方法获得当前窗体的内容面板,该方法的返回值是 Container (容器)类对象,如:Container contentPane = getContentPane();Container 类在 java.awt 包中。

java.awt.Container

Container 类通常用于操作 JFrame 的内容面板,其常用的方法有:方 法 原 型 说 明

void setBackground(Color bg) 设置容器的背景色,由参数 bg指定颜色void setLayout(LayoutManager mgr) 设置容器的布局,参数是布局管理器Component add(Component comp) 往容器中添加一个组件Component add(Component comp, int index) 将指定组件添加到容器中的指定位置上void remove(Component comp) 从容器中移除指定的组件void removeAll() 从容器中移除所有组件void repaint() 重新绘制当前容器

内容面板示例

import java.awt.*; //Container 类和 Color 类在此包中import javax.swing.*;public class ContentPaneDemo extends JFrame { public ContentPaneDemo() { super("内容面板示例 "); //获得当前窗体的内容面板 Container contentPane = this.getContentPane(); // 设置内容面板的背景色为红色 contentPane.setBackground(Color.RED); // 设置窗体关闭时即退出程序 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(300, 200); setResizable(false); // 设置窗体不可调整大小 setVisible(true); } public static void main(String[] args) { ContentPaneDemo cpd = new ContentPaneDemo(); }}

java.awt.Color

Color 类用于创建颜色对象,其构造方法共有 7 种重载方式,以下是常用的几种:

Color 类中还提供一系列静态的颜色常量:构 造 方 法 说 明

Color(int r, int b, int g) 使用指定的红、蓝、绿的色值( 0~255 )创建 sRGB颜色对象

常 量 颜 色 常 量 颜 色Color.BLACK 黑色 Color.BLUE 蓝色Color.CYAN 青色 Color.DARK_GRAY 深灰色Color.GRAY 灰色 Color.GREEN 绿色Color.LIGHT_GRAY 浅灰色 Color.MAGENTA 洋红色Color.ORANGE 桔黄色 Color.PINK 粉红色Color.RED 红色 Color.WHITE 白色Color.YELLOW 黄色

javax.swing.JButton

在 Swing 程序中,按钮可能是使用量最大的组件之一,JButton则是用来创建按钮的;JButton 类的构造方法有 5 种重载方式,以下是常用的几种:构 造 方 法 说 明

JButton() 创建一个空按钮JButton(String text) 创建一个带文本的按钮JButton(Icon icon) 创建一个带图标的按钮JButton(String text, Icon icon) 创建一个带文本和图标的按钮

JButton 的常用方法

方 法 原 型 说 明void setText(String text) 设置按钮上的文本String getText() 获得按钮上的文本void setBackground(Color bg) 设置按钮的背景色Color getBackground() 获得按钮的背景色void setEnabled(boolean b) 设置启用(或禁用)按钮,由参数 b决定void setVisible(boolean b) 设置按钮是否为可见,由参数 b决定void setToolTipText(String text) 设置按钮的悬停提示信息void setMnemonic(int mnemonic) 设置按钮的快捷键

javax.swing.JLabel

JLabel 是最简单的 Swing 组件之一,用于在窗体上显示标签, JLabel 既可以显示文本,也可以显示图像;JLabel 类的构造方法有 6 种重载方式:

构 造 方 法 说 明JLabel() 创建一个空的标签JLabel(String text) 创建一个带文本的标签JLabel(String text, int ha) 创建一个带文本的标签,并指定其对齐方式,可以是

JLabel.LEFT 、 JLabel.CENTER 和 JLabel.RIGHT

JLabel(Icon image) 创建一个带图像的标签JLabel(Icon image, int ha) 创建一个带图像的标签,并指定其对齐方式JLabel(String text, Icon image, int ha) 创建一个带文本和图像的标签,并指定其对齐方式

JLabel 的常用方法

方 法 原 型 说 明void setText(String text) 设置标签上的文本String getText() 获得标签上的文本void setIcon(Icon icon) 设置标签中的图像Icon getIcon() 获得标签中的图像void setHorizontalAlignment(int alignment) 设置标签中文本的对齐方式void setVisible(boolean b) 设置标签是否为可见

javax.swing.JTextField

JTextField 是文本框组件,主要用来接受用户的输入;JTextField 类的构造方法有 5 种重载方式,以下是常用的几种:

构 造 方 法 说 明JTextField() 创建一个空的文本框JTextField(String text) 创建一个带文本的文本框JTextField(int columns) 创建一个指定列数的空文本框JTextField(String text, int columns) 创建一个带文本,并指定列数的文本框

JTextField 的常用方法

方 法 原 型 说 明void setText(String text) 设置文本框中的文本String getText() 获得文本框中的文本void setHorizontalAlignment(int alignment)

设置文本框中文本的对齐方式,可以是JTextField.LEFT 、 JTextField.CENTER 和JTextField.RIGHT

void setEditable(boolean b) 设置文本框是否可以编辑,由参数 b决定void setEnabled(boolean enabled) 设置启用(或禁用)文本框void setVisible(boolean b) 设置文本框是否为可见

布局管理器

用户界面上的组件可以按照不同的方式进行排列,例如:可以依序水平排列,或者按网格方式进行排列;每种方案都是指组件的一种布局,要管理这些布局,就需要使用布局管理器;布局管理器是一组实现了 java.awt.LayoutManager接口的类,由这些类自动定位组件;布局管理器类在 java.awt 包中。

几种常用布局

流式布局java.awt.FlowLayout 边界布局

java.awt.BorderLayout

网格布局java.awt.GridLayout

流式布局示例import java.awt.*; //布局管理器在此包中import javax.swing.*;

public class FlowLayoutDemo extends JFrame { private JTextField txt1, txt2, txt3, txt4; //声明 4 个文本框 public FlowLayoutDemo() { super(" 流式布局示例 "); // 使用父类的构造方法设置窗体标题 txt1 = new JTextField(" 文本框 1"); // 分别实例化 4 个文本框 txt2 = new JTextField(" 文本框 2"); txt3 = new JTextField(" 文本框 3"); txt4 = new JTextField(" 文本框 4"); Container me = getContentPane(); //获取当前窗体的内容面板 me.setLayout(new FlowLayout()); // 设置内容面板的布局为流式布局 me.add(txt1); // 分别将 4 个文本框添加到内容面板中 me.add(txt2); me.add(txt3); me.add(txt4); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(300, 200); setVisible(true); } public static void main(String[] args) { new FlowLayoutDemo(); // 实例化窗体,匿名对象 }}

边界布局示例public class BorderLayoutDemo extends JFrame { private JButton btn1, btn2, btn3, btn4, btn5; //声明 5 个按钮 public BorderLayoutDemo() { btn1 = new JButton("北边按钮 "); // 分别实例化 5 个按钮 btn2 = new JButton(" 南边按钮 "); btn3 = new JButton(" 西边按钮 "); btn4 = new JButton("东边按钮 "); btn5 = new JButton(" 中间按钮 "); Container me = getContentPane(); me.setLayout(new BorderLayout()); // 设置内容面板的布局为边界布局 me.add(btn1, BorderLayout.NORTH); // 分别将按钮添加到内容面板的各个方位 me.add(btn2, BorderLayout.SOUTH); me.add(btn3, BorderLayout.WEST); me.add(btn4, BorderLayout.EAST); me.add(btn5, BorderLayout.CENTER); setTitle("边界布局示例 "); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(300, 200); setVisible(true); } public static void main(String[] args) { new BorderLayoutDemo(); }}

网格布局示例import java.awt.*;import javax.swing.*;

public class GridLayoutDemo extends JFrame { private JButton[] btnAry; //声明按钮数组 public GridLayoutDemo() { btnAry = new JButton[9]; // 创建有 9 个按钮引用的数组 Container me = getContentPane(); me.setLayout(new GridLayout(3, 3)); // 将内容面板设置为 3 行 3列的网格布局 //循环实例化按钮,并逐一将按钮添加到内容面板上 for (int i = 0; i < btnAry.length; i++) { btnAry[i] = new JButton("按钮 " + (i + 1)); me.add(btnAry[i]); } setTitle(" 网格布局示例 "); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(300, 200); setVisible(true); } public static void main(String[] args) { new GridLayoutDemo(); }}

复杂布局

单独使用某种布局管理器很难实现相对复杂的布局效果,这往往需要多个布局管理器配合使用;如下面的“猜数字游戏”界面,就是边界布局配合网格布局实现的效果。 整个窗体的内容面板采用边界布局

边界布局的南面嵌套 1 行 3列的网格布局

javax.swing.JPanel

JPanel 提供面板组件,它是轻量级的容器组件;面板中可以添加其它组件,也可以设置布局,我们一般使用面板来实现布局嵌套;JPanel 类的构造方法有 4 种重载方式,以下是常用的几种: 构 造 方 法 说 明

JPanel() 创建一个空面板JPanel(LayoutManaer layout) 创建带有指定布局的面板

JPanel 的常用方法

JPanel (面板)的操作方式与 Container (内容面板)很相似,以下是一些常用方法:方 法 原 型 说 明

void setBackground(Color bg) 设置面板的背景色,由参数 bg指定颜色void setLayout(LayoutManager mgr) 设置面板的布局,参数是布局管理器Component add(Component comp) 往面板中添加一个组件Component add(Component comp, int index) 将指定组件添加到面板中的指定位置上void remove(Component comp) 从面板中移除指定的组件void removeAll() 从面板中移除所有组件void repaint() 重新绘制当前面板

Applet 的使用

Applet :小应用程序,是一种在 Web 环境下,运行于客户端的 Java 程序组件(因此需要客户端安装JRE )。 Applet 必须运行于某个特定的“容器”,这个容器可以是浏览器本身,也可以是通过各种插件,或者包括支持 Applet 的移动设备在内的其他各种程序来运行。与一般的 Java 应用程序不同, Applet 不是通过 main方法来运行的运行时会遵循严格的安全检查,阻止潜在的不安全因素

Applet 生命周期

Init () Start () Stop () Destroy()

Applet签名

由于 Applet 应用与沙箱模型之中,只运行通过 HTTP协议访问和程序本身同一个 URL 的相关资源,如果希望访问客户端本地资源或通过 Socket/DatagramSocket 访问网络资源,那么必须通过特定的签名来注明该应用是安全、合法的。

Applet签名指令

需要签名的 Applet 必须打包为 jar 文件创建签名文件:

查看签名文件信息:对 jar 文件进行签名:

C:\Users\Eric>keytool -genkey -dname "cn=ETC, ou=ICSS, o=ICSS, c=China" -alias etc -keypass icssetc -storepass icssetc -validity 365 -keystore .\etc 组织名称 证书别名

密码 有效期 证书文件名

keytool -list -keystore .\etc -storepass icssetc

jarsigner -verbose -keystore .\etc applet.jar etc

GUI入门

早期的 Java 程序使用 AWT 实现 GUI 编程,但 AWT 依赖于操作系统;Swing 组件是在 AWT 组件的基础上发展而来的,它具有更好的平台无关性;我们通常自定义一个继承于 JFrame 的类,从而实现窗体;常用的 Swing 组件包括: JButton 、 JLabel 和 JTextField 等;在手工编码中,一般使用布局管理器来定位组件,常用的布局管理器有: FlowLayout 、 BorderLayout 和 GridLayout 等;为了实现复杂的布局效果,可以将多个布局管理器配合使用,实现嵌套布局往往要使用到 JPanel 。使用 Applet进行敏感操作(本地文件、 Socket 等)需要对其进行签名

GUI入门

实现猜数字的用户界面实现连连看的用户界面

回顾

javax.swing 包中包含经过改进后的 GUI 组件,而java.awt 包中则包括其它一系列与图形用户界面相关的类;常用的基本 Swing 组件包括:JFrame 、 JButton 、 JLabel 和 JTextField 等;在手工编码中,一般使用布局管理器来定位组件,常用的布局管理器有: FlowLayout 、 BorderLayout 和GridLayout 等;我们通常利用 JPanel 来实现嵌套布局,从而达到设计复杂界面的目的。

本章相关词汇

单 词 说 明event 事件listener 监听器,收听者source 源,来源,源头action 动作,行为focus 焦点item 项目,选项,条款motion 运动,动作adapter 适配器,改编者performed 表演者drag 拖,拖拽move 移动command 命令

GUI事件监听器

解释 Java 中的事件授权处理模型了解 java.awt.event 包了解 Java 中常用的事件类型在 Swing 编程中实现事件处理

GUI事件监听器

解释 Java 中的事件授权处理模型了解 java.awt.event 包了解 Java 中常用的事件类型在 Swing 编程中实现事件处理

事件处理

对于采用了图形用户界面的程序来说,事件控制是非常重要的;到目前为止,我们编写的图形用户界面程序都仅仅只是完成了界面,而没有任何实际的功能,要实现相应的功能,必须进行事件处理;用户与 GUI 组件进行交互就会发生事件,如:按下一个按钮、用键盘输入一个字符、点击鼠标等等;当前我们要关注的并不是“事件是如何产生的”,而是讨论当发生事件后,我们应当“如何处理之”。

事件处理模型

Java 中,事件处理的基本思路如下:一个源(事件源)产生一个事件(事件对象)并把它送到监听器那里,监听器只是简单地等待,直到它收到一个事件,一旦事件被接受,监听器将处理这些事件;一个事件源必须注册监听器以便监听器可以接受关于一个特定事件的通知。

事件源与事件

当在一个图形用户界面点击鼠标或者按下键盘时,都是针对于具体组件而发生的动作,如:使用鼠标点击某个按钮;按下键盘向文本框输入内容等等;我们把动作所操纵的对象称为事件源,请注意:事件源一定是指某个组件;当针对于事件源发生动作时,就会产生一个事件。

监听器与监听器接口

针对每一类型的事件,都有与之相对应的监听器,用于监听事件的发生;在 Java 中,监听器由一系列接口来提供;实际上,事件监听器就是实现了事件监听接口的类,监听器不断监听事件源的动作,当事件源产生一个事件后,监听器接收到事件源的通知,就调用特定的方法,以执行指定的动作。特定的事件监听器只对特定的事件感兴趣。

java.awt.event 包

java.awt.event 包中包含了一系列与事件处理相关的类和接口,其中包括:监听器接口,事件对象和事件适配器等;一般来说,编写图形用户界面程序都必须显式地导入此包;每种事件监听器接口都是以类似于XxxListener的形式来命名的,如:ActionListener 、 MouseListener 、 ItemListener 等;

ActionListener接口

按钮被点击是图形编程中最普遍的事件,我们经常要处理;ActionListener被称为活动监听器,一般用来监听按钮的点击事件;该接口中包含有一个抽象方法,原型如下:public void actionPerformed(ActionEvent ae);实现该监听器接口必须重写上面的方法。

案例 1 :按钮点击

实现界面代码import java.awt.*;import java.awt.event.*;import javax.swing.*;

public class EventDemo extends JFrame { private JLabel lblMsg; private JButton btnClick;

public EventDemo() { lblMsg = new JLabel("请点击下面的按钮 ..."); btnClick = new JButton("请点击我 "); Container cpMe = getContentPane(); cpMe.setLayout(new BorderLayout()); cpMe.add(lblMsg, BorderLayout.CENTER); cpMe.add(btnClick, BorderLayout.SOUTH); setTitle("ActionListener Demo"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(300, 200); setResizable(false); // 设置不允许用户自行调整窗口大小 setVisible(true); }

public static void main(String[] args) { new EventDemo(); }}

事件处理步骤 1 :确定事件源及监听器类型

由于我们想要处理按钮的点击事件,因此,按钮便是事件源;监听器类型是 ActionListener 。

事件源

事件处理步骤 2 :实现监听器接口

编写类来实现监听器接口,并重写其中的抽象方法,如:class MyListener implements ActionListener { public void actionPerformed(ActionEvent ae) { …… }}事实上,我们重写的这个抽象方法就是事件处理函数。也就是说,当事件发生时,这个方法将自动调用,其中的代码将被执行;但是,为了方便成员间的相互访问,我们一般采用内部类的方式来实现监听器。

事件处理步骤 2 :实现监听器接口(代码)import java.awt.*;import java.awt.event.*; //要进行事件处理,必须导入此包import javax.swing.*;

public class EventDemo extends JFrame { private JLabel lblMsg; private JButton btnClick;

public EventDemo() { // 构造方法,代码略 …… …… }

/*采用内部类的方式实现监听器接口 */ private class MyListener implements ActionListener { /* 实现接口中的抽象方法,事件发生时,将自动调用此方法 */ public void actionPerformed(ActionEvent ae) { lblMsg.setText("我被点击了! "); // 设置标签中的文本 } }

public static void main(String[] args) { new EventDemo(); }}

附:内部类

内部类是 Java 独有的一种语法结构,即在一个类的内部定义另一个类,如:public class ExteriorClass { private class InteriorClass { }}此时,内部类 InteriorClass就成为外部类 ExteriorClass 中的成员,访问权限遵循类成员的访问权限机制,可以是public 、 protected 、缺省和 private ;内部类可以很方便地访问外部类中的其它成员;内部类主要为了解决类成员间相互访问的问题,类似于 C++ 中友元类的做法。

附:关于内部类的更多知识内部类实际上是外部类的成员,如果在外部类以外需要实例化内部类对象的话,则必须通过外部类的实例才可以;

注意:内部类可以直接访问外部类的成员,但外部类不可以直接访问内部类的成员。

class Outer { //外部类public class Inner { //内部类}

}

public class InnerClassDemo {public static void main(String[] args) {

Outer a = new Outer(); // 实例化外部类的对象Outer.Inner b = a.new Inner();// 通过外部类的实例创建其内部类对象

}}

事件处理步骤 3 :事件源注册监听器

最后,我们要将事件源注册到监听器,也就是说,必须委派监听器去监听事件源所发生的事件;每种类型的事件都有其自己的注册方法,一般形式为:void addXxxListener(XxxListener listener);这里的 Xxx 指代具体的事件类型,而 listener则是相应类型的监听器实例;一般会采用如下形式将事件源注册到监听器:事件源 .addXxxListener(监听器实例 );如:btnClick.addActionListener(new MyListener());

事件处理步骤 3 :事件源注册监听器 (代码)import java.awt.*;import java.awt.event.*;import javax.swing.*;

public class EventDemo extends JFrame { private JLabel lblMsg; private JButton btnClick;

public EventDemo() { // 构造方法,代码略 …… btnClick.addActionListener(new MyListener()); // 将事件源注册到监听器 …… }

/*内部类实现监听器接口 */ private class MyListener implements ActionListener { public void actionPerformed(ActionEvent ae) { // 实现事件处理函数 lblMsg.setText("我被点击了! "); } }

public static void main(String[] args) { new EventDemo(); }}

事件的类型

实际上,事件是区分类型的,如:操作鼠标时会产生鼠标事件,使用键盘输入字符时会产生键盘事件,窗体打开或关闭时会产生窗体事件等等;对于不同类型的事件会有不同类型的监听器与之对应;java.awt.event 包中包含了一系列监听器接口,分别用来处理不同类型的事件。

AWT事件类的继承体系EventObject

AWTObject

ComponentEventAdjustmentEvent ItemEvent TextEventActionEvent

InputEvent PaintEventFocusEventContainerEvent WindowEvent

MouseEventKeyEvent

AWT事件类型(列表)

事件类 说 明 事件源ActionEvent 通常按下按钮,双击列表项或选中一个菜单项时,就会生成

此事件 JButton, JList, JMenuItem, TextField

AdjustmentEvent 操纵滚动条时会生成此事件 JScrollbar

ComponentEvent 当一个组件移动、隐藏、调整大小或为可见时会生成此事件 Component

ContainerEvent 将组件添加至容器中或从中删除时会生成此事件 Container

FocusEvent 组件获得或失去焦点时会生成此事件 Component

ItemEvent 单击复选框或列表项时,或者当一个选择框或一个可选菜单项被选择或取消时生成此事件 JCheckbox, JChoice,

JList

KeyEvent 接收到键盘输入时会生成此事件 Component

MouseEvent 拖动、移动、单击、按下或释放鼠标或在鼠标进入或退出一个组件时,会生成此事件 Component

TextEvent 在文本区或文本域的文本改变时会生成此事件 JTextField, JTextArea

WindowEvent 当一个窗口激活、关闭、正在关闭、恢复、最小化、打开或退出时会生成此事件 Window

常用监听器 1 :ActionListener

ActionListener 可能是使用得最多的监听器,与之对应的事件类型是 ActionEvent ,一般在鼠标点击某个按钮时会产生该事件;该接口只包含有一个抽象方法,其原型如下:

其实现类必须重写 actionPerformed方法,当事件发生时将调用该方法。

public interface ActionListener{ public void actionPerformed(ActionEvent ae);}

常用监听器 2 :KeyListener

KeyListener专门用来处理键盘事件,其对应事件类型是 KeyEvent ;该接口中包含有三个抽象方法,分别在不同的时刻被调用,原型如下:public interface KeyListener{ /*按下键盘上的某键时调用 */ public void keyPressed(keyEvent ke); /* 释放键盘上的某键时调用 */ public void keyReleased(KeyEvent ke); /* 输入某个字符时调用 */ public void keyTyped(KeyEvent ke);}

常用监听器 3 :MouseListener

操作鼠标时会产生鼠标事件 MouseEvent ,而MouseListener 用来处理鼠标的动作,其原型:

public interface MouseListener { /*鼠标按钮在某个组件上按下时调用 */ public void mousePressed(MouseEvent me); /*鼠标按钮在某个组件上释放时调用 */ public void mouseReleased(MouseEvent me); /*鼠标按钮在某个组件上点击(按下并释放)时调用*/

public void mouseClicked(MouseEvent me); /*鼠标进入到某个组件的范围之内时调用 */ public void mouseEntered(MouseEvent me); /*鼠标离开某个组件的范围之外时调用 */ public void mouseExited(MouseEvent me);}

常用监听器 4 :MouseMotionListener

MouseMotionListener 是专门处理鼠标运动事件的,比如将鼠标进行移动和拖动的时候,该接口的原型如下:public interface MouseMotionListener{ /* 在某个组件上移动鼠标时调用 */ public void mouseMoved(MouseEvent me);

/* 在某个组件上拖动(按下键并移动)鼠标时调用*/

public void mouseDragged(MouseEvent me);

}

常用监听器 5 : ItemListener

对于象下拉列表、单选按钮、复选按钮这些有选项的组件而言,当它们的选项发生改变的时候,都会产生选项事件ItemEvent ,如果需要处理这样的事件,就用到了ItemListener ,其原型:

public interface ItemListener{ /*当选项的状态(选择或取消)发生改变时调用 */ public void itemStateChanged(ItemEvent ie);}

常用监听器 6 :WindowListener

操作窗口时会产生窗口事件 WindowEvent ,其对应监听器是 WindowListener ,原型如下:public interface WindowListener { /*窗口被激活时调用 */ public void windowActivated(WindowEvent we); /*窗口被禁止时调用 */ public void windowDeactivated(WindowEvent we); /*窗口被关闭时调用 */ public void windowClosed(WindowEvent we); /*窗口正在关闭时调用 */ public void windowClosing(WindowEvent we); /*窗口最小化时调用 */ public void windowIconified(WindowEvent we); /*窗口恢复时调用 */ public void windowDeiconified(WindowEvent we); /*窗口打开时调用 */ public void windowOpened(WindowEvent we);}

常用监听器 7 :FocusListener

某个组件得到 /丢失焦点时将产生焦点事件FocusEvent ,可以使用 FocusListener 来处理这样的事件,该接口原型:public interface FocusListener{ /*某个组件获得焦点时调用 */ public void focusGained(FocusEvent fe);

/*某个组件失去焦点时调用 */ public void focusLost(FocusEvent fe);}

案例 2 :鼠标运动

实现界面代码import java.awt.*;import java.awt.event.*;import javax.swing.*;

public class MouseMotionDemo extends JFrame { private JLabel lblN; //放置在北边的标签 private JLabel lblS; //放置在南边的标签 public MouseMotionDemo() { lblN = new JLabel("请移动鼠标 "); lblS = new JLabel("请拖动鼠标 "); Container cpMe = this.getContentPane(); cpMe.setLayout(new BorderLayout()); cpMe.add(lblN, BorderLayout.NORTH); cpMe.add(lblS, BorderLayout.SOUTH); this.setTitle("MouseMotionListener Demo"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setSize(600, 400); this.setVisible(true); }

public static void main(String[] args) { new MouseMotionDemo(); }}

步骤 1 :确定事件源及监听器类型

我们要处理的是鼠标在窗体上移动 /拖动时产生的事件,因此,窗体是事件源;监听器类型是 MouseMotionListener 。

事件源是整个窗体

步骤 2 :实现监听器接口public class MouseMotionDemo extends JFrame { private JLabel lblN; private JLabel lblS;

public MouseMotionDemo() { // 构造方法,代码略 …… }

/*内部类实现监听器接口 */ private class MyListener implements MouseMotionListener { /*接口中的抽象方法,必须实现,当鼠标移动时自动调用此方法 */ public void mouseMoved(MouseEvent me) { …… }

/*接口中的抽象方法,必须实现,当鼠标拖动时自动调用此方法 */ public void mouseDragged(MouseEvent me) { …… } }

public static void main(String[] args) { new MouseMotionDemo(); }}

事件对象

Java 中的所有事件都被封装在事件对象中,所有事件对象皆派生自 EventObject 类;对于不同的事件类型会有不同的事件对象,它们都以类似于 XxxEvent 的方式命名,如:ActionEvent 、 MouseEvent 等等;事件对象中包含有事件发生时的相关信息(即事件触发时产生的一些数据),会被事件底层机制传递到事件处理函数中;实际上事件对象就是事件处理函数中被传递进来的参数,如果在处理事件的过程中需要使用到相关的某些数据,可以从事件对象中获取。

java.awt.event.MouseEvent 的常用方法

方 法 原 型 说 明int getX() 返回事件发生时,鼠标相对于源组件的水平 x轴坐标int getY() 返回事件发生时,鼠标相对于源组件的垂直 y轴坐标

int getButton()

返回事件发生时,是哪个鼠标键被按下,可以是MouseEvent.BUTTON1 (鼠标左键)、 MouseEvent.BUTTON2 (鼠标中间键)、 MouseEvent.BUTTON3 (鼠标右键)或MouseEvent.NOBUTTON (无鼠标键)

int getClickCount() 返回事件发生时,鼠标按钮被点击的次数

事件处理代码/*内部类实现监听器接口 */private class MyListener implements MouseMotionListener { /*接口中的抽象方法,必须实现,当鼠标移动时自动调用此方法 */ public void mouseMoved(MouseEvent me) { int x = me.getX(); //得到鼠标位置的 x坐标 int y = me.getY(); //得到鼠标位置的 y坐标 // 将鼠标坐标信息显示到北边的标签中 String str = " 正在移动鼠标 X:" + x + ",Y:" + y; lblN.setText(str); }

/*接口中的抽象方法,必须实现,当鼠标拖动时自动调用此方法 */ public void mouseDragged(MouseEvent me) { int x = me.getX(); //得到鼠标位置的 x坐标 int y = e.getY(); //得到鼠标位置的 y坐标 // 将鼠标坐标信息显示到南边的标签中 String str = " 正在拖动鼠标 X:" + x + ",Y:" + y; lblS.setText(str); }}

事件对象

事件对象

步骤 3 :事件源注册监听器public class MouseMotionDemo extends JFrame { private JLabel lblN; private JLabel lblS;

public MouseMotionDemo() { // 构造方法 …… // 将窗体注册到鼠标运动监听器 this.addMouseMotionListener(new MyListener()); …… }

/*内部类实现监听器接口,代码略 */ private class MyListener implements MouseMotionListener { …… …… }

public static void main(String[] args) { new MouseMotionDemo(); }}

实现事件处理的其他方式

事实上, Java 语言的语法自由度很大;要进行事件处理,除了使用内部类的方式以外,还可以:– 直接实现的方式;– 匿名类的实现方式。

直接实现的方式进行事件处理/*继承于 JFrame 类,并实现 MouseMotionListener接口 不但具备窗体的功能,同时又是一个监听器 */public class MouseMotionDemo extends JFrame implements MouseMotionListener { …… //声明窗体中的组件,代码略 public MouseMotionDemo() { // 构造方法,代码略 …… this.addMouseMotionListener(this); // 将事件源注册到监听器 …… }

/* 实现 MouseMotionListener接口中的鼠标移动方法,代码略 */ public void mouseMoved(MouseEvent me) { …… }

/* 实现 MouseMotionListener接口中的鼠标拖动方法,代码略 */ public void mouseDragged(MouseEvent me) { …… }}

匿名类的方式进行事件处理public class MouseMotionDemo extends JFrame { …… //声明窗体中的组件,代码略 public MouseMotionDemo() { // 构造方法,代码略 …… /* 将事件源注册到监听器,监听器用匿名类实现 */ this.addMouseMotionListener(new MouseMotionListener() { // 实现 MouseMotionListener接口中的鼠标移动方法,代码略 public void mouseMoved(MouseEvent me) { …… } // 实现 MouseMotionListener接口中的鼠标拖动方法,代码略 public void mouseDragged(MouseEvent me) { …… } }); …… }

public static void main(String[] args) { …… }}

匿名类

匿名类其实就是一种比较特殊的内部类,只是这个类没有名字而已;匿名类与内部类相似,能够访问到外部类中的所有成员;很多情况下(特别是在事件处理中),匿名类一般被定义在外部类的某个方法中,所以也被称为局部内部类,对于局部内部类,它还可以访问到这个方法的参数;在适当的情况下,使用匿名类来实现事件处理,会使代码更简洁,更灵活。

总结

与事件处理相关的三个概念:事件源、监听器、事件对象;在程序中要进行事件处理的话,需要导入java.awt.event 包;事件处理的三个步骤:– 确定事件源和监听器类型– 实现监听器接口– 将事件源注册到监听器事件处理的三种语法实现形式:– 内部类的实现方式– 匿名类的实现方式– 直接实现的方式

回顾

与事件处理相关的三个概念:事件源、监听器、事件对象;与事件处理相关的接口和类都被包含在java.awt.event 包中,如果程序中要进行事件处理的话,需要导入此包;事件处理的三个步骤:– 确定事件源和监听器类型– 实现监听器接口– 将事件源注册到监听器事件处理的三种语法实现形式:– 内部类的实现方式– 匿名类的实现方式– 直接实现的方式

GUI事件适配器

事件源与监听器之间的关系:– 一个监听器监听多个事件源– 一个事件源注册多个监听器事件适配器

GUI事件适配器

事件源与监听器之间的关系:– 一个监听器监听多个事件源– 一个事件源注册多个监听器事件适配器

事件源与监听器的对应关系

事件源与监听器之间并不是一对一的对应关系,也就是说,一个监听器并不是只可以监听一个事件源,而一个事件源也并不是只能够注册到一个监听器;在某些情况下,为了使代码更灵活,程序控制更方便,我们会使用一个监听器监听多个事件源。

案例 1 :猜数字游戏

实现界面代码public class GuessFrame extends JFrame{ private JLabel lblMsg; //显示提示信息的标签 private JTextField txtInput; //接受输入的文本框 private JButton btnGuess, btnAnew, btnExit; // 三个按钮 public GuessFrame() { txtInput = new JTextField(); lblMsg = new JLabel("请输入一个 1000 以内的整数 "); Container cpMe = this.getContentPane(); cpMe.setLayout(new BorderLayout()); cpMe.add(txtInput, BorderLayout.NORTH); cpMe.add(lblMsg, BorderLayout.CENTER); cpMe.add(new SouthPanel(), BorderLayout.SOUTH);

this.setTitle("猜数字游戏 "); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setSize(300, 200); this.setResizable(false); this.setVisible(true); }

private class SouthPanel extends JPanel //内部类的方式实现南面面板 {……}}

/*内部类的方式实现南面面板 */private class SouthPanel extends JPanel{ public SouthPanel() // 构造方法,初始化南面面板 { btnGuess = new JButton("猜 "); btnAnew = new JButton("重新来过 "); btnExit = new JButton("退出游戏 ");

// 南面面板使用 1 行 3列的网格布局 this.setLayout(new GridLayout(1, 3)); this.add(btnGuess); this.add(btnAnew); this.add(btnExit); }}

java.awt.event.ActionEvent 的常用方法方 法 原 型 说 明

Object getSource()返回最初的事件源从 java.util.EventObject继承而来,所有类型的事件对象中都有此方法

String getActionCommand() 返回与此动作相关的命令字符串,该命令字符串可以用来标识和区分事件源

使用一个监听器监听三个事件源public class GuessFrame extends JFrame { ……

private class SouthPanel extends JPanel implements ActionListener { public SouthPanel() { …… // 分别为三个按钮注册到同一个监听器 btnGuess.addActionListener(this); btnAnew.addActionListener(this); btnExit.addActionListener(this); …… }

//事件处理方法,无论哪个按钮被点击,都会执行此方法 public void actionPerformed(ActionEvent ae) { JButton btnTemp = (JButton)(ae.getSource()); //获得真正的事件源 if (btnTemp == btnGuess) { …… //【猜】按钮被点击时应执行的动作 } else if (btnTemp == btnAnew) { …… //【重新开始】按钮被点击时应执行的动作 } else if (btnTemp == btnExit) { …… //【退出游戏】按钮被点击时应执行的动作 } } }}

关于 Action Command

对于可以注册 ActionListener 的组件来说,都有一个方法,用来设置它们的活动命令( Action Command ),方法原型如下:void setActionCommand(String actionCommand);活动命令其实就是一个字符串,可以将其理解成类似于一个标记,用来唯一标识某个事件源;可以利用 ActionEvent 的 getActionCommand方法来获得事件源的活动命令,达到区分它们的目的。

使用 Action Command区分事件源private class SouthPanel extends JPanel implements ActionListener { public SouthPanel() { …… // 分别为三个按钮设置不同的活动命令,用来区分它们 btnGuess.setActionCommand("guess"); btnAnew.setActionCommand("anew"); btnExit.setActionCommand("exit");

// 分别为三个按钮注册到同一个监听器 btnGuess.addActionListener(this); btnAnew.addActionListener(this); btnExit.addActionListener(this); …… }

public void actionPerformed(ActionEvent ae) { String strCmd = ae.getActionCommand(); //获得相应事件源的活动命令 if (strCmd.equals("guess")) { …… //【猜】按钮被点击时应执行的动作 } else if (strCmd.equals("anew")) { …… //【重新开始】按钮被点击时应执行的动作 } else if (strCmd.equals("exit")) { …… //【退出游戏】按钮被点击时应执行的动作 } }}

完整代码请参见工程源文件

案例 2 :鼠标离开窗口

当鼠标离开窗口时

实现代码public class MouseFrame extends JFrame{ private JLabel lblN; private JLabel lblS;

public MouseFrame() { …… // 将当前窗体注册到MouseMotionListener监听器 this.addMouseMotionListener(new MyMouseMotionListener()); // 将当前窗体注册到MouseListener监听器 this.addMouseListener(new MyMouseListener()); …… }

private class MyMouseMotionListener implements MouseMotionListener { …… //内部类实现 MouseMotionListener监听器接口,代码略 }

private class MyMouseListener implements MouseListener { …… //内部类实现 MouseMotionListener监听器接口,代码略 }}

一个事件源注册多个监听器

MouseListener监听器的实现代码public class MouseFrame extends JFrame{ ……

/*内部类方式实现 MouseListener监听器接口 */ private class MyMouseListener implements MouseListener { // 实现接口中的抽象方法,当鼠标离开时调用该方法 public void mouseExited(MouseEvent me) { lblN.setText("鼠标已经离开窗体 "); lblS.setText("鼠标已经离开窗体 "); }

//接口中的其他抽象方法 public void mouseEntered(MouseEvent me){} public void mouseReleased(MouseEvent me){} public void mousePressed(MouseEvent me){} public void mouseClicked(MouseEvent me){} }}

不得不将接口中的其它抽象方法都实现。

事件适配器

有时候,为了处理某个事件,要实现相应的事件监听接口,但是我们可能只对其中的某个方法感兴趣,但是又不得不将接口中的所有抽象方法进行重写;java.awt.event 包中提供了一系列适配器类,每个适配器类都将其对应的监听器接口中的所有抽象方法进行了空实现。

监听器接口和适配器类

事件监听接口 对应的适配器类ComponentListener ComponentAdapterContainerListener ContainerAdapterFocusListener FocusAdapterKeyListener KeyAdapterMouseListener MouseAdapterMouseMotionListener MouseMotionAdapterWindowListener WindowAdapter

例如:KeyListener.javapackage java.awt.event;

public interface KeyListener extends EventListener

{

public void keyPressed(KeyEvent ke);

public void keyReleased(KeyEvent ke);

public void keyTyped(KeyEvent ke);

}KeyAdapter.java

package java.awt.event;

public class KeyAdapter implements KeyListener

{

public void keyPressed(KeyEvent ke){}

public void keyReleased(KeyEvent ke){}

public void keyTyped(KeyEvent ke){}

}

事件适配器(续)

请勿将适配器理解成另一种事件处理机制;适配器的实质是为事件监听接口提供一种更加机动灵活的语法实现形式,方便我们有选择性、有针对性地实现感兴趣的方法,而不必将所有方法全部重写;

使用适配器改写前面的案例public class MouseFrame extends JFrame{ …… ……

/*内部类方式继承MouseAdapter 适配器类 */ private class MyMouseListener extends MouseAdapter { //只需要重写鼠标离开时的事件处理函数 public void mouseExited(MouseEvent me) { lblN.setText("鼠标已经离开窗体 "); lblS.setText("鼠标已经离开窗体 "); } }} 完整代码请参见工程源文件

关于适配器的注意事项

请注意:适配器是类而不是接口,只能用来继承( extends ),不能用来实现( implements );因此,从另一个角度来说,适配器反而限制了语法的自由度;对于只有一个抽象方法的监听器接口,没有提供与之相对应的适配器,如 ActionListener 等。

总结

监听器与事件源之间的对应关系并不是一对一的,既可以一个监听器监听多个事件源,也可以一个事件源注册多个监听器;适当地使用事件适配器可以避免一些不必要的代码,但需要注意的是:适配器只可以用来继承,不可以实现。

回顾

一个监听器监听多个事件源可以使代码的灵活度更高;在某些情况下,一个组件如果有多个类型的事件需要处理的话,可以将它注册到多个监听器;事件适配器其实只是将对应的监听器接口中的所有方法进行了空实现,以便我们可以有针对性地重写其中感兴趣的方法。

本章相关词汇

单 词 说 明dialog 会话,对话scroll 监听器,收听者password 卷轴,滚动area 区域,面积checkBox 复选框radio 收音机comboBox 组合框group 团体,组font 字体wrap 包装,缠绕

GUI其他常用控件

javax.swing包中的 常用组件:– 容器 组件

• JFrame• JDialog(补充:多窗体程序 以及 窗体间 传递数据)• JPanel• JScrollPane

– 文本 组件• JLabel、JTextField、 JPasswordField、 JTextArea

– 表单 组件• JButton• JCheckBox• JRadioButton和 ButtonGroup• JComboBox

GUI其他常用控件

javax.swing包中的 常用组件:– 容器 组件

• JFrame• JDialog(补充:多窗体程序 以及 窗体间 传递数据)• JPanel• JScrollPane

– 文本 组件• JLabel、JTextField、 JPasswordField、 JTextArea

– 表单 组件• JButton• JCheckBox• JRadioButton和 ButtonGroup• JComboBox

常用 Swing 组件

到目前为止,我们只接触到了几个基本的 Swing 组件:JFrame 、 JPanel 、 JLabel 、 JTextField 和JButton ;在实际的程序开发过程中,仅仅依靠以上几个基本组件是无法实现复杂功能的;我们将常用的 Swing 组件根据其性质不同,分类进行介绍,其中包括:–容器组件– 文本组件–表单组件

Swing 中常用的容器组件

容器组件是指可以容纳其它组件的组件,常用的 Swing容器包括:– JFrame (框架)– JDialog (对话框)– JPanel (面板)– JScrollPane (滚动面板)

关于 JFrame 和 JPanel 在前面章节已经介绍过,这里不再赘述。

javax.swing.JDialog

JDialog 用于在程序中创建对话框组件,对于多窗口的程序而言,对话框尤为重要;对话框其实就是轻量级的窗体,它比 JFrame 消耗更少的系统资源;它与 JFrame 的区别在于, JFrame 可以在程序中不依赖于其它窗体单独存在,而 JDialog则必须依赖于其它窗口,一般做辅助窗口呈现;但更重要的是, JDialog 支持模式显示。所谓模式显示,是指窗口以不丢失焦点的独占方式显示。

JDialog 的构造方法

JDialog 类的构造方法共有 11 种重载,以下是常用的几种:构 造 方 法 说 明JDialog() 创建一个没有标题并且没有指定所有者的无模式对话框JDialog(Frame owner) 创建一个没有标题但将指定的 owner 作为其所有者的无

模式对话框JDialog(Frame owner, boolean modal) 创建一个没有标题但有指定所有者的对话框,根据参数

modal 来决定它是否模式显示JDialog(Frame owner, String title) 创建一个具有指定标题和指定所有者的无模式对话框JDialog(Frame owner, String title, boolean modal)

创建一个有指定标题和指定所有者的对话框,参数modal决定它是否模式显示

JDialog(Dialog owner, boolean modal)

创建一个没有标题但有指定所有者的对话框,根据参数modal 来决定它是否模式显示

JDialog 的常用方法除了模式显示的功能外, JDialog 的使用跟 JFrame很相似,以下是一些常用的方法:

方 法 原 型 说 明void setTitle(String title) 设置对话框的标题,标题内容由参数 title指定void setSize(int width, int height)

设置对话框的大小,参数 width指定宽度,参数 height指定高度

void setResizable(boolean resizable) 设置对话框能否调整大小,由参数 resizable决定void setVisible(boolean b) 设置对话框是否为可见,由参数 b决定, true 为可

见, false 为不可见Container getContentPane() 获得当前对话框的内容面板void dispose() 释放当前对话框及其所有子组件所占用的资源,即卸载对话框

案例 1 :显示小窗口

JDialog

实现代码//主窗口类,继承于 JFramepublic class MainFrame extends JFrame { private JButton btnDisplay;

public MainFrame() { btnDisplay = new JButton("显示子窗口 "); Container cp = this.getContentPane(); cp.add(btnDisplay, BorderLayout.SOUTH); //匿名类方式为按钮注册监听器 btnDisplay.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { // 实例化子窗体,并把当前主窗体的引用传递给其构造方法 new SubFrame(MainFrame.this); } });

this.setTitle("主窗口 "); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setSize(600, 400); this.setVisible(true);

}}

//子窗口类,继承于 JDialogpublic class SubFrame extends JDialog { // 构造方法需要将所有者的引用作为参数传递进来 public SubFrame(MainFrame owner) { //调用父类的构造方法,相对于所有者模式显示 super(owner, true); this.setTitle("子窗口 "); this.setSize(300, 200); this.setResizable(false); this.setVisible(true); }}

案例 2 :跨窗口传递数据

完整代码请参见工程源文件

javax.swing.JScrollPane

JScrollPane 是滚动面板组件,当某些组件的可视区域不足以显示其全部内容时,可以将该组件添加到滚动面板中,为其增加滚动条。

JScrollPane 的构造方法

JScrollPane 的构造方法共有 4 种重载:构 造 方 法 说 明

JScrollPane() 创建一个空的 JScrollPane ,需要时水平和垂直滚动条都可显示

JScrollPane(Component view) 创建一个显示指定组件内容的 JScrollPane ,只要组件的内容超过视图大小就会显示水平和垂直滚动条

JScrollPane(Component view,int vsbPolicy, int hsbPolicy)

创建一个显示指定组件内容的 JScrollPane ,并指定滚动条的显示策略

JScrollPane(int vsbPolicy, int hsbPolicy) 创建一个空的 JScrollPane ,并指定滚动条的显示策略

JScrollPane 的常用方法方 法 原 型 说 明

JScrollBar getHorizontalScrollBar() 返回当前滚动面板的水平滚动条JScrollBar getVerticalScrollBar() 返回当前滚动面板的垂直滚动条JViewport getViewport() 返回当前滚动面板的 JViewport

Swing 中常用的文本组件

所谓文本组件是指专门用来存放文字的组件,包括:– JLabel (标签)– JTextField (文本框)– JPasswordField (密码框)– JTextArea (文本域)我们对 JLabel 和 JTextField都已非常熟悉,这里不再介绍。

javax.swing.JPasswordField

JPasswordField 用来提供密码框组件,它的构造方法共有 5 种重载,以下是常用的几种:构 造 方 法 说 明

JPasswordField() 创建一个空的密码框JPasswordField(String text) 用指定文本初始化密码框JPasswordField(int columns) 创建一个指定列数的空密码框JPasswordField(String text, int columns) 创建一个带文本,并指定列数的密码框

JPasswrodField 的常用方法方 法 原 型 说 明

void setText(String text) 设置密码框中的文本String getText() 获得密码框中的文本,出于安全考虑,此方法已过时,由

getPasswrod 方法替代char[] getPassword() 获得密码框中的文本,只不过是以字符数组的方式返回void setEchoChar(char c) 设置密码框的密文字符

javax.swing.JTextArea

当用户有大量文本需要输入的时候,就可以使用到文本域组件, JTextArea 的构造方法共有 6 种重载,以下是常用的几种:构 造 方 法 说 明

JTextArea() 创建一个空的文本域JTextArea(String text) 用指定文本初始化文本域JTextArea(int rows, int columns) 创建一个指定行数和列数的空文本域JTextArea(String text,int rows, int columns) 创建一个带文本,并指行数和列数的文本域

JTextArea 的常用方法

方 法 原 型 说 明void setText(String text) 设置文本域中的文本String getText() 获得文本域中的文本void setFont(Font font) 设置文本域中文本的字体void setLineWrap(boolean wrap) 设置文本域中文本的自动换行策略void setTabSize(int size) 设置制表符‘ \t’ 所占的字符宽度,默认为 8 个字符宽

java.awt.Font

Font 类用来表示字体,常用的构造方法如下:

常量 Font.BOLD表示粗体, Font.ITALIC表示斜体,粗体加斜体可以用 Font.BOLD + Font.ITALIC表示,Font.PLAIN表示普通样式;任何包含有文字的组件,都可以使用 setFont方法来设置字体。

构 造 方 法 说 明Font(String name, int style, int size)

构造一个 Font 对象,参数 name指定字体名称, style指定字体样式(可以是 Font.BOLD 、Font.ITALIC 和 Font.PLAIN ), size指定字体的大小

Swing 中常用的表单组件

这里套用了 HTML 中的“表单”一词,常用的表单组件包括:– JButton (按钮)– JCheckBox (复选框)– JRadioButton (单选按钮)– JComboBox (组合框,又名:下拉列表)JButton 在前面的课程中已大量使用过,这里不再介绍。

javax.swing.JCheckBox

JCheckBox 用来提供复选框组件,一般用来提供多个选项,并且可选项不限定的情况下,可以使用到复选框;其构造方法共有 8 种重载,常用的如下:构 造 方 法 说 明

JCheckBox() 创建一个没有文本、没有图标并且最初未被选定的复选框

JCheckBox(String text) 创建一个带指定文本的、最初未被选定的复选框JCheckBox(String text, boolean selected)

创建一个带指定文本的复选框,并可以指定其最初是否被选择

JCheckBox(String text, Icon icon) 创建带有指定文本和图标的、最初未选定的复选框JCheckBox(String text, Icon icon,boolean selected)

创建带有指定文本和图标的、最初未选定的复选框,并可以指定其最初是否被选择

JCheckBox 的常用方法

方 法 原 型 说 明void setSelected(boolean b) 设定复选框的选择状态, true 为被选择, false 为不被选择boolean getSelected() 返回复选框的选择状态void setText(String text) 设置复选框的文本String getText() 返回复选框的文本void setIcon(Icon icon) 设置复选框的图标

javax.swing.JRadioButton

JRadioButton 提供单选按钮组件,其构造方法共有 8 种重载,以下是常用的几种:构 造 方 法 说 明

JRadionButton() 创建一个没有文本、没有图标并且最初未被选定的单选按钮JRadionButton (String text) 创建一个带指定文本的、最初未被选定的单选按钮JRadionButton (String text,boolean selected)

创建一个带指定文本的单选按钮,并可以指定其最初是否被选择

JRadionButton (String text, Icon icon) 创建带有指定文本和图标的、最初未选定的单选按钮JRadionButton (String text, Icon icon,boolean selected)

创建带有指定文本和图标的、最初未选定的单选按钮,并可以指定其最初是否被选择

JRadioButton常用方法方 法 原 型 说 明

void setSelected(boolean b) 设定单选按钮的选择状态, true 为被选择, false 为不被选择

boolean getSelected() 返回单选按钮的选择状态void setText(String text) 设置单选按钮的文本String getText() 返回单选按钮的文本void setIcon(Icon icon) 设置单选按钮的图标

javax.swing.ButtonGroup

事实上,单选按钮本身并不具备单选效果,它必须依靠按钮组才能达到单选的目的;ButtonGroup 用来提供按钮组,将一系列按钮加入到同一个按钮组中,那么同一按钮组中的按钮只能有一个被选择;ButtonGroup 的构造方法如下:构 造 方 法 说 明

ButtonGroup() 创建一个新的按钮组

ButtonGroup 的常用方法

方 法 原 型 说 明void add(AbstractButton button) 将指定按钮添加到按钮组中int getButtonCount() 返回按钮组中按钮的数量void remove(AbstractButton button) 将指定按钮从按钮组中删除Enumeration getElements() 返回按钮组中所有的按钮

javax.swing.JComboBox

使用 JComboBox 可以创建组合框组件,也就是俗称的下拉列表,以下是它的 4 种构造方法重载:构 造 方 法 说 明

JComboBox() 创建一个空的组合框JComboBox(Object[] items) 使用数组中的元素作为选项的组合框JComboBox(Vector items) 使用集合中的元素作为选项的组合框JComboBox(ComboBoxModel model) 创建具有指定数据模型的组合框

JComboBox 的常用方法

方 法 原 型 说 明void addItem(Object item) 为列表添加选项void insertItemAt(Object item, int index) 在指定索引位置添加指定的选项int getSelectedIndex() 返回被选择的选项的索引Object getSelectedItem() 返回被选择的选项void setSelectedIndex(int anIndex) 设置指定索引位置的选项被选择void setSelectedItem(Object anItem) 设置指定的选项被选择void removeItem(Object anItem) 删除指定的选项void removeItemAt(int anIndex) 删除指定索引位置的选项int getItemCount() 返回列表中所有选项的数量

表单组件示例

完整代码请参见工程源文件

总结

可以使用 JDialog 来创建对话框,编写多窗口程序;使用 JScrollPane 来为某些大视图的组件提供滚动面板;文本组件是指专门用来操作文字的组件;适当的使用表单组件可以使用户界面更加人性化,最大限度地方便用户操作。

即时通项目简介

当前流行的即时通讯系统– 腾讯 QQ– 微软 MSN Messenger– 雅虎 Messenger– 百度 Hi– Google Talk– 网易 PoPo– 新浪 UC– AOL ICQ

即时通项目简介

即时通讯基本具有功能– 注册– 登录– 修改资料– 发送信息– 接收信息– 聊天记录查看– 个人状态维护– 加入好友– 建组和加入组

即时通项目简介

即时通讯高级功能– 传输表情图片– 传输文件– 声音文件播放– 语音通话– 视频聊天– 远程桌面

项目的任务

任务:即时通讯工具的基本功能系统结构: C/S个人形式完成学时: 60 学时

项目的主要技术

基础技术– 数据库访问操作– Socket 编程– Swing 编程扩展技术– 图片解析和显示– 文件传输– Robot– JMF

简单示例讲解

即时通讯软件一般包括服务器端软件和客户端软件即时通讯服务器开发基本思路– 启动 Socket服务端口侦听– 循环侦听客户 Socket连接请求– 建立 Socket请求处理程序– 服务器端得到请求,根据需求分发给各客户端处理程序客户端开发基本思路– 初始化客户端的界面– 与服务器建立Socket连接– 启动线程侦听Socket端口,获得信息进行处理与显示– 读取客户输入信息,向Socket发送信息

简单示例讲解

服务器端类说明– ClientHandler: 一个客户端处理句柄,读取客户端发送的信息,并将获得消息向各客户端发送– IChatServer: 即时通的服务器程序,主要启动服务器端口侦听,建立客户端句柄,当收到信息后,发送到已连接的客户端;

客户端类说明– IChatClient: 客户端主程序,主要创建界面,建立 socket连接,启动处理进程– IncomingReader:读取 Socket 数据并进行显示– SendButtonListener :对按钮事件进行处理,向 Socket 发送数据

简单示例演示

复杂示例演示

常见问题

在这个项目中服务器充当了什么角色?客户端又充当了什么角色?两个用户是不是一个充当服务器,一个充当客户端呢?当我想要添加 XX功能的时候,为什么觉得很麻烦呢?——感觉好像有很多地方需要修改。Swing 开发出来的程序似乎和普通的 Windows 程序外观不太一样,这是怎么回事?

总结Java 是面向对象的、跨平台的程序设计语言;Java 程序是运行在 Java 虚拟机之上的;要下载安装 JDK ,才可以开发和运行 Java 程序;JDK 提供一系列的工具,这些工具位于 JDK 安装路径的 bin目录下,常用的有:javac :编译java :运行javadoc :提取文档

可以使用任何文本编辑器编写 Java 源程序;

作业熟悉 JDK目录,以及 JDK 环境变量使用记事本编写 Hello World 程序使用记事本编写九九乘法表使用记事本编写空心菱形

Recommended