近日为了复习CoreJava故自己收集了很多书籍汇编成以下java最核心的内容:参考出处(Java从入门到精通,Java学习手册(app))备注:我不留对别人毫无用处的博文,所以如果对你有用处请评论。否则,一个月过后没有超过500我会自行删除。
Java的诞生与发展历史
Java的出生地:SUN Microsystems Inc.
SUN:Stanford University Network
Java之父:James Gosling(詹姆斯·高斯林)
1995年发布Java第一版
Java发展历史中的几个重要版本:
l Java 1.2 从该版本开始,把Java分成Java SE、Java ME、Java EE三部分。
l Java 5 2004年9月30日18:00PM,J2SE 1.5发布,成为Java语言发展史上的又一里程碑。为了表示该版本的重要性,J2SE 1.5更名为Java SE 5.0
l Java 6 Java的各种版本更名,以取消其中的数字"2":J2EE更名为Java EE,J2SE更名为Java SE,J2ME更名为Java ME。
2009年04月20日,甲骨文74亿美元收购Sun。取得java的版权。
1.2 Java技术体系
Java技术分为三个体系:
l Java SE(J2SE)(Platform Standard Edition,java平台标准版)开发桌面应用程序
l Java EE(J2EE)(Java 2 Platform,Enterprise Edition,java平台企业版)开发面向Internet的应用程序
l Java ME(J2ME)(Java 2 Platform Micro Edition,java平台微型版)开发运行于智能设备的程序。
1.3 Java语言的特点
面向对象(OOP)
跨平台
安全健壮
没有指针操作
垃圾自动回收机制
多线程
分布式
1.4 Java程序的运行机制
1.4.1 JVM与跨平台
Java程序不是直接在操作系统之上运行,而是运行于JVM(java虚拟机)之上。
针对不同的操作系统开发相应的JVM,Java程序运行于不同的JVM之上,因此Java程序可以在不同修改的情况下运行于不同的操作系统之上,从而实现了所谓的跨平台。
Java源代码(.java文件)经编译器编译成字节码(.class文件),JVM本质上就是一个负责解释执行Java字节码的程序。
JVM执行Java程序的过程:
l 加载.class文件
l 管理并分配内存
l 执行垃圾收集
1.4.2 JVM、JRE、JDK
JVM:Java虚拟机
JRE:Java运行时环境(JVM+类库) //后面编写代码时在强调类库
JDK:Java开发工具包(JRE+编译工具)
JDK的全称:Java Development kit
提示:
运行Java程序只需要安装JRE,开发Java程序则需要安装JDK。
1.5 java开发环境安装、配置
1.5.1 安装JDK
1.5.2 配置环境变量
1.5.2.1 PATH
目的:在任意路径下能够直接执行相关命令。
原理:在命令行中执行某个命令时,首先在当前路径下查找,如果找不到则到PATH配置的各个路径下查找。
配置细节:
JAVA_HOME:C:\Program Files\Java\jdk1.6.0
PATH:%JAVA_HOME%\bin; 或
C:\Program Files\Java\jdk1.6.0\bin; //bin = binary(二进制)
环境变量之间使用;分割
1.5.2.2 CLASSPATH
(1)目的:在任意路径下能够找到要运行的.class文件。
(2)CLASSPATH:
.;
C:\Program Files\Java\jdk1.6.0\lib\dt.jar; //lib = library(库)
C:\Program Files\Java\jdk1.6.0\lib\tools.jar;
从其他目录下找class文件
(3)注意:Java1.4之后不需要配置上述CLASSPATH环境变量,默认从上述路径下查找.class文件。
1.6 java开发流程
1.6.1 结构化编程与面向对象编程
结构化编程:函数
面向对象编程:类
java类的基本结构:变量 +方法
1.6.2 编写和运行Java程序的三个步骤
l 编写源代码,保存到源代码文件中,例如 HelloWorld.java
l 编译源代码,例如javac HelloWorld.java
l 执行字节码,例如java HelloWorld
1.6.3 源文件与class文件
在Java中源文件的名称必须是文件中主类的名称,扩展名必须为.java。源文件中可以包含多个类,但是最多只能有一个类使用public修饰,使用public修饰的类就是主类。在Java中,源文件还被作为编译单元,即一个源文件就是一个编译单元。
编译器会为源代码中的每个类生成一个.class文件,.class文件的名称是类的名称,扩展名为.class。
1.6.4 main()方法
方法名:只能是main,不能是Main等形式。
修饰符:public static void 三个缺一不可,多一个不行
参数:
1、参数类型为字符串数组
2、参数名称只要符合Java中标识符命名要求即可
3、参数声明的两种方式:String[] args,或 String args[]
案例1.2 打印输出
1、打印:个人信息
姓名:xxx
年龄:xx
性别:x
2、打印一首古诗
1.7 常用命令
1.7.1 Dos命令
命 令 | 功 能 |
盘符: | 转换到指定分区 |
cd 目录 | 进入指定的目录 |
dir | 查看当前路径下的目录和文件 |
cd.. | 进入上一级目录,..表示上一级目录 |
cls | 清屏 |
1.7.2 Java命令
javac //后跟文件名称,需要包含扩展名.java
javac -d //指明存放类文件的位置
java //后跟类名
javadoc //生成注释文档
第2单元 标识符、关键字、数据类型
2.1 注释
注释的三种形式:单行注释、多行注释、文档注释
文档注释(documentation comment)以“/**”开始,以“*/”结束。使用文档注释可以将关于程序的信息嵌入到程序自身中。
javadoc命令可以文档注释中的内容提取出来,将其放入到一个HTML文件中。文档注释方便了程序的文档化。
2.2 分隔符、代码块
每行功能代码以;作为结束符号
空格没有实际意义,可以利用空格无意义,将代码合理缩进,易读
{}包含的代码称之为代码块, 例如类if(){}、方法{}、类{}等等
2.3 标识符
2.3.1 标识符的概念
Java中类、方法和变量的名称,称之为标识符。
2.3.2 标识符的语法要求
(1)以字母、数字、_或$组成
(2)不能以数字开头
(3)不能使用java的关键字和保留字
注意:
1、标识符的长度没有限制
2、Java是大小写敏感的,所有标识符区分大小写
2.3.2 标识符的命名规范(驼峰)
Java中的标识符通常是由多个英文单词构造,每个单词除了首字母外其他字母小写。
2.3.2.1 大驼峰
第一个单词的首字母大写。以大写字母开头,用于类名、接口名
class Accoun {…} //类名
class HelloWorld{…} //类名
interface AccountBase {…} //接口名
2.3.2.2 小驼峰
第一个单词的首字母是小写,其他单词的首字母大写。以小写字母或单词开头,用于变量名、方法名
String studentName; //变量名
String getStudentName() {…} //方法名
2.3.2.3 常量命令规范
常量是使用final修饰的存储单元。(最终的)
全部为大写字母表示
final public int DAYS_WEEK = 7;
final public double PI = 3.1415926;
2.3.3 标识符案例
演示标识符的要求、规范、常量的定义
abc a+b my_city $money class _78
myName _come my&bb 2name public
name#1 age- name3 class5 _$ $a
2.4 Java的关键字
2.4.1 Java中的全部关键字
目前定义了50个关键字
abstract | continue | for | new | switch |
assert | default | goto | package | synchronized |
boolean | do | if | private | this |
break | double | implements | protected | throw |
byte | else | import | public | throws |
case | enum | instanceof | return | transient |
catch | extends | int | short | try |
char | final | interface | static | void |
class | finally | long | strictfp | volatile |
const | float | native | super | while |
Java保留了const和goto关键字,但是没有使用。Java还保留了下面这些关键字:true、false和null。这些关键字是Java定义的数值。
2.4.2 目前已经接触到的关键字
public static void class
2.5 基本数据类型
2.5.1 数据类型的分类
注意:基本数据类型也可以分成两大类:数值型、布尔型
2.5.2 整型
Java不支持无符号的、只是正值的整数。
2.5.2.1 类型、宽度、范围
名 称 | 宽 度 | 范 围 |
long | 64/8 | -9 223 372 036 854 775 808至9 223 372 036 854 775 807 |
int | 32/4 | -2 147 483 648至2 147 483 647 大约21亿 |
short | 16/2 | -32 768至32 767 |
byte | 8/1 | -128至127 |
2.5.2.2 字面值
(1)整数字面值默认是int类型
(2)将字面值赋给byte或short变量时,如果字面值位于目标类型的范围之内,就不产生错误。
(3)大写或小写的L明确地标识其类型为long
(3)在字面值可以包含下划线,例如1_000_000_000
(4)十进制、八进制(0)、十六进制(0X/0x)、二进制(0B/0b)
案例2.1 整型案例
2.5.3 浮点型
浮点数,也称为实数(real number),当计算需要小数精度的表达式时使用。
2.5.3.1 类型、宽度、范围
名称 | 宽度(位) | 大致范围 |
double(双精度) | 64/8 | 4.9e-324~1.8e+308 |
float (单精度) | 32/4 | 1.4e-045~3.4e+038 |
2.5.3.2 浮点数字面值
(1)默认为double类型,为了指定float字面值,需要使用后缀F或f
(2)科学计数法。例如6.022E23、314159E-5、2e+100
案例2.2 浮点型案例
2.5.4 字符型
2.5.4.1 char类型与字符编码
(1)char是16位,Java在内部使用16位的整数表示字符(Unicode编码),char类型的范围0~65536。//全世界基本的语言符号基本都包含了
(2)char也可以用作整数类型,可以将整型字面值赋给char类型的变量,可以在char类型上执行算术运算。
(3)26个小写字母、26个大写字母、以及10个数字0-9,其编码是连续的。
2.5.4.2 char类型字面值
(1)字符型字面值使用单引号中的字符表示,例如’a’。
(2)转义字符
转义序列 | 描 述 |
\ddd | 八进制字符(ddd) |
\uxxxx | 十六进制Unicode字符(xxxx) |
\’ | 单引号 |
\” | 双引号 |
\\ | 反斜杠 |
\r | 回车符 |
\n | 新行符(也称为换行符) |
\f | 换页符 |
\t | 制表符 |
\b | 回格符 |
字符串类型:
字符串类型是String,String是类,所以是引用类型。字符串字面值是使用双引号包围起来的内容。
案例2.3 字符型案例
2.5.5 布尔型
(1)boolean类型表示逻辑值,它只能是true或false。
(2)boolean类型的值与整数0和1没有任何关系
案例2.4 boolean类型
2.6 变量与常量
2.6.1 变量的声明与赋值
说明:变量表示存储单元,变量名就是存储单元的名称,变量初始化之后就可以通过变量名访问存储单元。
1、变量声明 int i; String str; //还没有分配存储空间
2、初始化(赋初值) i=10; str=”abc”; //初始化之后就分配了存储空间
3、声明并赋值 int i = 10; String str=”abc”; //声明的同时进行初始化
注意:变量在使用之前必须先初始化(赋初值)。
2.6.2 常量的声明与赋值
声明常量需要使用final关键字,如,final double PI = 3.1415926。
常量通常在声明时赋值,并且赋值之后其值不能改变
常量标识符通常全部为大写。
案例 2.5 常量案例
2.6.3 实例变量与局部变量
根据变量声明的位置,变量可以分为实例变量和局部变量。
2.6.3.1 实例变量及作用范围
在类的{}内直接定义的变量,称为实例变量或成员变量。
作用范围:整个类中都可以使用
实例变量在创建对象时会自动初始化,并有初始值(默认值)
byte/short/int:0
long:0L
float:0.0f
double:0.0
boolean:false
引用类型:null
2.6.3.2 局部变量及作用范围
在方法中或代码块{}中定义的变量,称之为局部变量。
作用范围:直接包含它的{}内有效
局部变量不会自动初始化,没有默认值,使用之前必须要初始化。
2.7 类型转换
当将一种类型的变量或字面值赋给另外一种类型的变量时,就会发生类型转换。
Java中类型转换分自动类型转换和强制类型转换。
总结:对于数值类型,如果目标类型的范围包含了原类型的范围,则可以自动转换,否则就需要强制转换。
2.7.1 自动类型转换
类型兼容、小类型转换为大类型
byte-->int short--->int int--->long
long--->float float--->double
String--->Object (子类--->父类)
2.7.2 强制类型转换
大类型转换为小类型
int-->byte int--->short long--->int
float--->long double--->float
Object--->String
2.8 面向对象初步(了解)
2.8.1 了解类和对象
2.8.2 创建对象
2.9 原始类型和引用类型(了解)
原始类型变量的赋值
int x = 10; int y = x; (将x的值10赋给y,x和y没有任何关联关系,改变值互不影响)
引用类型变量的赋值
Teacher teacher = new Teacher(“John Smith”, 30, 10000);
or
Teacher teacher1, teacher2;
teacher1= new Teacher(“John Smith”, 30, 10000);
teacher2 = teacher1;
teacher2=teacher1; 将teacher1的引用的地址值赋给teacher2,这样两个引用指向同一个堆内存地址,所以任何一个修改属性,另一个也变化
第3单元 运算符&表达式&选择结构
3.1 运算符
可以将大部分Java运算符划分为四组:算术运算符、位运算符、关系运算符以及逻辑运算符。
3.1.1 赋值运算符
赋值运算符有一个有趣的特性:它允许创建赋值链。例如,分析下面的代码段:
int x, y, z;
x = y = z = 100; // set x, y, and z to 100
3.1.2 算术运算符
算术运算符用于数学表达式,其使用方法与在代数中的使用方法相同。
运算符 | 结 果 |
+ | 加法(也是一元加号) |
- | 减法(也是一元负号) |
* | 乘法 |
/ | 除法 |
% | 求模 |
| |
+= | 加并赋值 |
-= | 减并赋值 |
*= | 乘并赋值 |
/= | 除并赋值 |
%= | 求模并赋值 |
|
|
++ | 自增 |
-- | 自减 |
需要注意的地方:
(1)当将除法运算符用于整数类型时,其结果不会包含小数部分。
(2)求模运算符%,返回除法操作的余数。它既可以用于浮点数也可以用于整数。可以用于判断一个整数是否是奇数、偶数、是否是某个数的倍数。
案例3.1 基本算数运算符演示
名称:BasicMathDemo
演示算术运算,特别注意是整除,演示除0效果
案例3.2 求模运算符案例1
项目名称:ModulusDemo
演示求模运算符的运算规则和应用:
判断一个数是奇数还是偶数
判断一个数是否是5的倍数…
案例3.3 求模运算符案例2
名称:GetDigital
输入一个三位数,分别获取个、十、百位上的数字。123
3.1.2.1 算术与赋值复合运算符
int a = 1;
a += 2; //相当于a = a+2;
(1)对于所有的二元算术运算,都有相应的复合赋值运算符。
(2)复合赋值运算符的效率更高。所以在专业的Java程序中,会经常看到复合赋值运算符。
案例3.4 复合运算符案例:
名称:OpEqualsDemo
演示复合运算符的规则。
3.1.2.2 自增与自减
(1)自增运算符将其操作数加1。自减运算符将其操作数减1。
(2)自增与自减分前缀形式与后缀形式。++、--
前缀形式:
int i = 10;
int a = ++ i; //等同于 i++; int a = i;所以a=11,i= 11
后缀形式:
int j = 10;
int b = j++; //等同于 int b = j; j++; 所以b=10,j = 11
案例3.5 自增与自减运算符案例
名称:IncDecDemo
重点演示前缀与后缀形式的区别。
3.1.2.3 表达式
(1)表达式的概念:由操作数与运算符组成 j++; a+b;
(2)表达式的求值:表达式是有值的,需要注意表达式值的类型
(3)表达式中的类型提升规则:
表达式的类型会自动提升为所有操作数的最大类型。
对于操作数全为非long类型整数的表达式,其类型会自动提升为int。
案例3.6 表达式求值案例
名称:ExpressionDemo
演示类型自动提升
3.1.3 关系运算符
关系运算符也称为比较运算符
运算符 | 结 果 |
== | 等于 |
!= | 不等于 |
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
(1)关系运算的结果为boolean型数值。
(2)关系运算符最常用于if语句和各种循环语句中的控制表达式。
案例3.7 关系运算符应用演示
CompareOperatorDemo
判断一个字符是否是小写字母、大写字母、数字
3.1.4 逻辑运算符
布尔逻辑运算符,只能操作boolean型操作数。
运算符 | 结 果 |
&& | 逻辑与(短路形式) |
|| | 逻辑或(短路形式) |
^ | 逻辑异或 |
! | 逻辑一元非 |
& | 逻辑与 |
| | 逻辑或 |
布尔逻辑运算规则:
操作数 | 逻辑运算及结果 | ||||
A | B | A || B | A && B | !A | A ^ B |
false | false | false | false | true | false |
true | false | true | false | false | true |
false | true | true | false | true | true |
true | true | true | true | false | false |
运算规则说明:
对于||,只要有一个操作数为true,则结果为true;
对于&&,只要有一个操作数为false,则结果为false。
对于^,两个操作数不同,则结果为true,否则结果为false。
注意:&&和||,
这两个运算符是所谓短路形式的逻辑运算符。假如单独根据左操作数就能确定表达式的结果,那么就不会计算右操作数的值。
案例3.8 逻辑运算案例
BoolLogicDemo
演示逻辑运算符的运算符规则
示例3.9 短路逻辑案例
演示短路逻辑表达式的求值过程
int a = 5;
int b = 6;
if(a < b || ++a == b)
//通过第一部分就能确定最终结果的话,第二部分就不会执行
{
System.out.println(a);
} //分别使用 | 和|| 测试效果(输出a的值)
3.1.5 位运算符(了解)
3.1.5.1 左移与右移
(1)左移<<:低位补0,左移动1位,相当于乘以2
(2)右移>>:高位补符号位,右移1位,相当于除以2
(3)无符号右移>>>:高位补0
3.1.5.2 位逻辑运算符
运算符 | 结 果 |
~ | 按位一元取反 |
& | 按位与 |
| | 按位或 |
^ | 按位异或 |
运算规则:
操作数 | 位运算及结果 | ||||
A | B | A | B | A & B | A ^ B | ~A |
0 | 0 | 0 | 0 | 0 | 1 |
1 | 0 | 1 | 0 | 1 | 0 |
0 | 1 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 | 0 |
注意:
&和|,如果操作数为boolean类型,则为逻辑运算符,如果操作数为整数则为位运算符。通常将&和|作为位运算符。
3.1.5.3 位运算符与赋值运算符的结合
例如:a >>= 4; 相当于a = a >> 4;
示例3.10 位运算案例
演示<<、>>以及>>>
演示位逻辑运算
3.1.6 ?运算符
运算规则
expression1 ? expression2 : expression3
expression1可以是任何结果为boolean型数值的表达式。如果expression1为true,则对expression2进行求值;否则对expression3进行求值。“?”运算的结果是对其进行求值的表达式。expression2和expression3都需要返回相同(或兼容)的类型,并且不能为void。
示例3.11 条件运算符示例
(1)使用?运算符解决除0问题
ratio = denom == 0 ? 0 : num / denom;
//优美地解决了除0问题
(2)使用?运算符获取绝对值
int i=-10;
int k = i < 0 ? -i : i; //k为i的绝对值
3.2 运算符的优先级
最高 |
|
|
|
|
|
|
++(后缀) | --(后缀) |
|
|
|
|
|
++(前缀) | --(前缀) | ~ | ! | +(一元) | -(一元) | (类型匹配) |
* | / | % |
|
|
|
|
+ | - |
|
|
|
|
|
>> | >>> | << |
|
|
|
|
> | >= | < | <= | instanceof |
|
|
== | != |
|
|
|
|
|
& |
|
|
|
|
|
|
^ |
|
|
|
|
|
|
| |
|
|
|
|
|
|
&& |
|
|
|
|
|
|
|| |
|
|
|
|
|
|
?: |
|
|
|
|
|
|
= | op= |
|
|
|
|
|
最低 |
|
|
|
|
|
|
大概顺序:算术运算符>移位运算符>关系运算符>逻辑运算符>赋值运算符
圆括号会提升其内部操作数的优先级。为了得到所期望的结果,这通常是必需的。圆括号(不管是否冗余)不会降低程序的性能。所以,为了减少模糊性而添加圆括号,不会对程序造成负面影响。
3.3 流程控制-选择结构
选择结构是通过分支语句实现的,分支语句有两种。
3.3.1 if-else
1、if语句的三种形式
注意:
else 不能单独使用,要和if配对使用
if else 都可以后面不跟{},但是只能控制下面的一行代码
if-else案例
(1)输入两个数,输出较大的数(if-else)
(2)使用Scanner获取输入成绩分值,输出对应的成绩等级(if-else if-else)
3.3.2 多分支语句switch
3.3.2.1 switch语句的结构
switch (expression) {
case value1:
// statement sequence
break;
case value2:
// statement sequence
break;
...
case valueN :
// statement sequence
break;
default:
// default statement sequence
}
3.3.2.2 switch语句需要注意的问题
(1)switch(expression)中expression的结果必须是byte,short,char,int中的一种。 新增:枚举类型、String
(2)在同一个switch语句中,两个case常量不允许具有相同的值
(3)每个case语句中需要加上break;语句。如果遗漏了break,则会继续进入到下一个case。
(4)可以省略default语句。
(5)default语句通常放在末尾,可以放在开始,中间或者末尾位置。
3.3.2.3 switch案例
输入1-7之间的数字表示一周的中的第几天,输出对应的星期几。
3.3.3 if-else与Switch的区别
(1)switch语句只能进行相等性测试,这一点与if语句不同,if语句可以对任何类型的布尔表达式进行求值。即,switch只查看表达式的值是否和某个case常量相匹配。
(2)相对于一系列嵌套的if语句,switch语句通常效率更高
第4单元 控制语句
选择语句(分支语句)、迭代语句(循环语句)、跳转语句
4.1 while循环结构
while(condition) {
// body of loop
}
一般在循环体内控制循环的结束条件:让循环条件为false或break
4.2 do while循环结构
do {
// body of loop
} while (condition);
至少执行一次循环。
示例:
分别使用while和do-while循环计算1-100的和
//改进第3单元中判断成绩等级的例子,如果输入的数值为负数,则退出程序
4.3 While和do while的区别
do while先执行 后判断 所以它至少执行一次
while先判断条件 再执行 有可能一次也不执行
所有的循环,都可能出现死循环
4.4 for循环结构
4.4.1 语法
for(initialization; condition; iteration) {
// body
}
四个部分:初始化部分A、循环条件B、循环体D、迭代部分C
A只执行一次。
执行B判断真假,如果真T,执行D,然后再执行C变量变化
演示案例:
使用for循环计算1-100的和
4.4.2 嵌套的for循环
循环是可以嵌套的,包括while和do-while循环
4.4.3 使用两个变量控制循环
案例:输入两个数,查找两个数的中间数
for(int i=0, j=10; i<j; i++,j--){
//输出i和j的值
第5单元 数组
5.1 数组的概念
一个具有固定大小,可以容纳相同类型数据的集合.
数组元素的类型:可以是基本类型,也可以是引用类型
数组可以认为是Java中最简单的复合类型。
数组的声明和使用,在语法上与C语言类似,但是在内部实现机制上有本质的区别。
5.2 数组声明
两种方式:强烈建议采用第一种方式
int[] nums; 或
int nums[];
5.3 数组创建与初始化
5.3.1 使用new创建数组
int[] nums; //声明数组,并没有创建数组,没有开辟堆内存。
nums = new int[5]; //创建数组,必须设置长度 开辟堆内存
new:用于分配内存的特殊运算符。通过new分配的数组,其元素会被自动初始化为0(对于数值类型)、false(对于boolean类型)或null(对于引用类型)。
说明:获得一个数据需要两个步骤,第一步是声明,第二步是创建数组对象。
一个数组就是一个对象。数组是动态创建的,所有对象都是动态创建的。
5.3.2 创建数组的三种方式
int[] nums= new int[5]; //初始化为默认值
int[] nums = {1,2,3,4,5}; //初始化为{}中指定的值,静态初始化
int[] nums = new int[] {1,2,3,4,5};//初始化为{}中指定的值,静态初始化
5.3.3 创建数组需要注意的问题
1、创建数组时必须知道数组的长度,否则new不知道要开辟多大的内存
2、第二种方式创建数组,必须在声明数组的同时创建数组
3、创建数组之后,数组的长度不能再改变。
说明:
数组的初始化分为静态初始化和动态初始化,静态初始化在初始化时由程序员显示指定每个数组元素的初始值,由系统决定数组长度。
5.4 数组操作
1、通过下标访问数组元素
为指定的数组元素赋值、使用数组元素。如果数组下标越界,会抛出异常。
2、通过循环处理数组(打印输出所有数组元素)
数组与循环是分不开的
3、可以使用length属性获取数组的长度,从而可以避免数组越界。
示例5.1
创建数组、静态初始化、遍历输出数组元素
示例5.2 随机数生成
使用Math类的random方法,动态初始化
5.5 数组排序
5.5.1 排序(冒泡)
5.5.2 Arrays.sort()
5.6 数组元素的类型
数组元素的类型不仅可以为基本类型,也可以为引用类型。
演示案例:
字符串数组
定义一个Student类,定义一个Student类的数组,使用初始化器创建数组对象,通过循环对数组进行处理。
5.7 多维数组
5.7.1 二维数组的声明、创建
在Java中,多维数组(multidimensional array)实际上是数组的数组。
下面声明了一个名为twoD的二维数组:
本质上,Java中只有一维数组。
int[][] twoD = new int[4][5];
int[] 表示int类型的数组,即数组元素为int类型
int[][] 表示int[]类型的数组,即数组元素为int[]类型
5.7.2 单独为第二维分配内存
当为多维数组分配内存时,可以只为第一(最左边的)维指定内存,之后再单独为余下的维分配内存。
int twoD[][] = new int[4][];
twoD[0] = new int[5];
twoD[1] = new int[5];
twoD[2] = new int[5];
twoD[3] = new int[5];
对于这种情况,第一维的长度必须指定。
int twoD[] = new int[][]; //错误
第二维的长度可以不同:
int twoD[][] = new int[4][];
twoD[0] = new int[1]; //twoD[0],本质是一维数组
twoD[1] = new int[2];
twoD[2] = new int[3];
twoD[3] = new int[4];
5.7.3 为二维数组使用初始化器
int[][] i= new int[3][];
int[] a1 ={1,2,3};
int[] a2 ={4,-5,6,9,0,8};
int[] a3 ={7,8};
下面这行代码 String[][] s=new String[10][];
A s 是一10行10列的数组。
B s 是一包含10个一维数组的二维数组。
C s中每个元素都为null。
D 该行代码非法。
特别注意:
二维数组,创建数组是,[][]第一个必须指明长度
声明的有效方式:
int[][ ] ia1 = new int[2][3];
int[][ ] ia2 = new int[2][];
ia2[0] = new int[2], ia2[1] = new int[3];
Teacher[][] ta;
ta = new Teacher[2][];
ta[0] = new Teacher[3];
ta[1] = new Teacher[4];
无效的声明方式
int[][ ] ia1 = new int[][3];
Teacher[][] ta = new Teacher[][5];
第6单元 面向对象-类和对象
6.1 类与对象的概念
6.1.1 面向对象的概念
面向对象编程:OOP(Object-Oriented Programming)
6.1.2 使用类和对象开发程序的基本步骤
对于面向对象编程,主要工作就是编写类。面向对象开发的步骤:
l 开发类,类 = 属性(成员变量)+方法
l 通过new关键字创建对象
l 使用类中的属性和方法:对象.属性名 对象.方法名()
6.1.3 类与对象
(1)类是一种逻辑结构,对具有公共属性特征和行为(功能)的一个群体进行描述。例如可以定义Student类描述学生的公共属性和行为,定义一个Teacher类,描述老师的公共属性和行为。
(2)定义了类之后,就可以根据类创建(new)出一个实例。比如学生张三,老师王老师。
通俗地说:
类定义了一种新的数据类型。对象就是根据类定义的变量。可以将类看做是复合类型。
类是对象的模板(template),对象是类的实例(instance)。因为对象是类的实例,所以经常会看到交换使用“对象”和“实例”这两个
6.2 定义类
程序 = 数据 + 算法
类 = 属性 + 方法
6.2.1 类的一般形式
class 类名 { //类名通常以大写字母开始
类型 变量1;
类型 变量2;
…
类型 方法名(参数列表) {
// 方法体
}
…
}
在类中定义的变量和方法都称为类的成员。所以变量又称为成员变量,方法又称为成员方法。
6.2.2 类的属性
类的成员变量又称为类的属性。
public class Student {
/**
* 属性 成员变量
* 类的{}内直接声明(定义)的变量 叫 成员变量/实例变量
*/
String name;
int age;
double score;
}
属性属于类的某个具体对象。类的每个实例(即,类的每个对象)都包含这些变量的副本,因此在类中定义的变量又被称为实例变量。
6.2.2 类的方法
方法是对象行为特征的抽象,类具有的共性的功能操作,称之为方法。方法是个“黑匣子”,完成某个特定的应用程序功能。
方法的基本语法:
修饰符 返回类型 方法名(形参列表){
//功能代码
}
形参可以为空,可以有多个,形参的类型可以是基本类型也可以是引用类型。
public class Student {
String name;
int age;
double score;
void study(){
//
}
void show(){
//
}
}
注意:
方法中定义变量称为局部变量。
如果没有返回值,则方法的返回类型必须为void
当方法有具体的返回类型时,则必须使用return语句返回一种值。
方法深入分析:
6.3 对象的声明与创建
Student stu1; //声明对象的引用
sut1 = new Student(); //创建对象
public static void main(String[] args) {
Student stu1 = new Student();
stu1.name = "张三"; //访问对象的属性
stu1.age = 20;
stu1.score=95.5;
stu1.show(); //方法调用
Student stu2 = new Student();
stu2.name = "李四"; //访问对象的属性
stu2.age = 22;
stu2.score=98;
stu2.show(); //方法调用
Student stu3 = stu2;
sut2.show();
}
提示:如何使用对象的成员变量和方法
注意:
属性属于类的具体对象,不同对象的属性值通常是不同的。
虽然方法也是通过对象调用的,但是各对象共享相同的方法
6.4 类练习
Teacher类
定义Teacher,属性:姓名,年龄,工资
方法:Teach(); Show();
Box类
属性:长、宽、高
方法:计算体积
Dog类:
属性:name color、age
方法:eat()
Cat类:
属性:name color、age
方法:eat()
Triangle类:底、高、计算面积
Reatangle类:长length、宽width,计算面积area
Circle类:半径,计算面积
测试类:Engineer类
6.5 为引用变量变量赋值
//Box b1 = new Box(); //创建对象,让b1指向(引用)所创建的对象
Box b2 = b1;
注意:b1和b2引用同一个对象。
对象引用与对象的关系:
(1)对象引用,有时也称为对象引用变量,或称为引用变量。
(2)对象引用与对象在物理上是两个不同的东西。
(3)对于上图,我们通常说b1引用(有时也称为指向)图中的那个Box对象。
(4)对象只能通过对象引用来操作。有时直接使用对象引用代指对象,例如对于上面的例子,有时会直接将b1引用的对象称为“对象b1”或“”b1对象。
(5)将一个对象引用赋值给另一个对象引用,则两个引用变量指向同一个对象。
(6)对引用变量进行相等性比较,例如b1==b2,是比较两个引用变量是否引用同一个对象,所以b1==b2的结果为true。对对象引用进行相等性比较,有时也直接称为对象的相等性比较。
注意:基本变量与引用变量的区别
基本类型的变量位于栈(线程)内存中,引用变量所所引用的变量位于堆(进程)内存中。
7.3 方法深入分析
方法可以看做是独立的功能模块,供调用模块调用,功能模块要有输入、输出,对于方法而言输入就是方法的参数,输出就是方法的返回值。调用者通过参数将需要输入的数据传递给方法,方法通过返回值将输出返回给调用者。
7.3.1 方法定义
1、方法定义包括:访问修饰符、返回类型、方法名、形参
2、方法必须有返回类型(构造方法除外),可以省略访问修饰符
3、可以有参数,也可以没有参数
7.3.1 方法调用
1、实参与形参的概念
2、方法调用的执行过程
7.3.2 参数传递
方法调用中发生的数据传送是单向的。即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。
参数传递:
值传递:Swap(int a, int b)方法
引用传递(对象作为参数,本质上是引用变量作为参数)
7.3.3 return
(1)return语句用于明确地从一个方法返回。即,return语句导致程序的执行控制转移回到方法的调用者。
(2)如果return之后还有代码也不会执行。
(3)如果方法的返回类型为void,可以使用return跳出函数,但是不能使用return返回数据。
(4)可以返回对象。
7.3.4 方法调用
因为封装,不能直接访问其他对象的成员变量,通常是调用其他对象的方法。方法调用有两种情况:
调用相同类中的方法:可以直接调用。(本质上是使用this关键字调用)
调用其他类中的方法:对象.方法名
6.6 构造器/构造方法
6.6.1 构造方法的语法
构造方法的作用:开辟内存空间、创建实例、初始化属性值。
构造方法的特点:
(1)方法名与类名相同
(2)不能声明返回类型
(3)不能使用return语句
(4)通常为public
注意:
(1)如果没有明确提供构造方法,则系统会提供一个默认的构造方法,默认构造方法(也称为缺省构造方法)没有参数。
(2)如果我们提供了一个构造方法,则系统不再提供无参数的默认构造方法。
(3)如果我们提供了一个有参数的构造方法,同时又需要无参构造方法的话,则必须同时提供一个无参数的构造方法。
6.6.2 new运算符深入分析
new运算符实际是就是调用构造方法。当进入到构造方法内部时,实际上对象已经创建完毕,可以在构造方法中为各成员变量赋值。
6.7 this关键字
在类的内部,可以在任何方法中使用this引用当前对象。
使用this关键字解决在实例变量和局部变量之间可能发生的任何名称冲突。
局部变量,包括方法的形参,可以和类的实例变量重名。当局部变量和实例变量具有相同的名称时,局部变量隐藏了实例变量。
第7单元 面向对象-封装
7.1 封装的概念
封装是面向对象的三大特征之一。
面向对象的三大特征是:封装、继承、多态。
类 = 属性 + 方法,类是对属性和方法的封装。类封装了类的成员。
结合Student类,介绍后续内容:
如果在类的外部可以随意访问类的成员,那将属性和方法放到类中就没有意义了。因此Java允许在类中通过访问修饰符控制类成员的访问权限。之前已经接触过public访问修饰符。
7.2 访问控制
在完整学习访问控制之前,先熟悉一下包的概念。
7.2.1 包与访问范围
(1)包的概念
类通常位于某个包中,包(packages)是多个类的容器。它们用于保持类的名称空间相互隔离。因此包是一种名称机制。
例如,Java类库中的类,分别包含在不同的包中:java.lang;java.util。例如String类位于java.util包中。
(2)定义包
package pack1
(3)层次化的包
package www.bawie.java1502c
(4)包与目录结构
位于包中的类,在文件系统中也必须有与包名层次相同的目录结构。
需要指出的是,包名与文件目录结构一致是针对.class文件而言的,对于源代码文件没有这一要求,但是通常也将源代码文件放到与包名一致的目录结构中。并且建议将源文件与类文件分开存放。
(5)导入包
类的全名为:包名.类名。例如在www.bawie包中定义的Student类,其全面为www.bawie.Student。
当在不同的包中使用某个类时,需要使用类的全名,如果包名很长的话,使用类的全名不是很方便,这时可以通过导入包来避免使用类的全名。
导入包的目的:减少输入
导入包的两种方式
import java.util.Scanner;
import java.util.*;
包既是一种命名机制,也是一种可见性控制机制。可以在包中定义该包外部的代码不能访问的类成员。
7.2.2 访问修饰符与访问范围
类是对类成员(属性和方法)的封装,可以通过不同的访问修饰符控制类成员的可见范围,即控制类的成员在多大的范围是可见的。
类成员的访问范围划分为四个:本类、本包、子类、全局(所有包)
为类的成员指定不同的修饰符,就是控制成员的在什么样的范围内是可见的,即在什么样的范围是能够访问的。
| private | 默认 | protected | public |
同一个类中 | 是 | 是 | 是 | 是 |
相同包中的其他类 | 否 | 是 | 是 | 是 |
子类(不同包、相同包) | 否 | 否 | 是 | 是 |
全局(所有包) | 否 | 否 | 否 | 是 |
访问范围: 本类 < 本包 < 子类 全局(所有包)
访问修饰符:private < 缺省(default)< protected < public
封装的通常做法:
(1)将类的属性的访问权限设置为private,提供访问权限为public的set和get方法。在有些情况下不提供set和get方法,有时只提供其中的一个。
(2)提供合理的构造方法,即开发相应参数的构造方法,方便实例化对象。
(3)类的全部或部分方法的访问权限为public,类的公有方法是类对外开放的接口,供外部代码使用类的功能。类的私有方法通常是辅助方法实现部分功能,供类的公有方法调用。
封装案例:
在p1包中定义Student类
在p1包中定义StudentTest类,使用Student类,访问该类中的成员变量和方法。
将StudentTest类的定义放入到p2包中,再次访问类中的成员变量和方法。
封装案例:堆栈类
7.4 方法重载
7.4.1 方法重载基础
直接切入主题:
在类中可以定义名称相同的方法:只要形参列表不同即可。
特点:
1、方法名相同
2、形参列表不同:形参的类型、形参的个数
注意的地方:
1、返回值在区分重载方法时不起作用。修饰符也不起作用
2、当调用重载方法时,Java使用参数的类型和/或数量确定实际调用哪个版本。
方法重载案例:
Calculator类,添加add()方法,计算两个数的和。int float double
7.4.2 重载构造方法
Box(double length, double width, double height)
Box(double dim)
Box(Box box)
Box()
特别说明:
在某个构造方法中可以使用this()调用重载的构造方法:
public Box(double dim){
this(dim, dim, dim)
}
7.5 static关键字
在正常情况下,只有通过组合类的对象才能访问该类的成员。有时可能希望定义能够独立于类的所有对象进行使用的成员。为了创建这种成员,需要在成员声明的前面使用关键字static。例如Math类中的方法,就是静态方法。
方法和变量都可以声明为静态的。main()方法是最常见的静态成员的例子。main()方法被声明为静态的,因为需要在创建所有对象之前调用该方法。
7.5.1 静态变量
案例:(演示静态变量的访问方式、不同实例共享相同的值)
Math中的成员变量PI就是静态变量。
public class StaticM{
//实例变量
private int i;
//静态变量
static int si;
public static showStatic(){};
}
特别注意:
(1)被声明为静态的变量本质上是全局变量,类的所有实例共享相同的静态变量。因此,通过一个对象修改静态变量的值后,通过该类的其他对象访问到的静态变量是修改后的值。
(2)访问静态变量的方式:
类名.变量名(推荐)
对象.变量名(不推荐)
(3)初始化时机
静态变量:当类被虚拟机加载,静态变量就初始化,既不需要创建类的对象就可以使用静态变量。
实例变量:创建类的对象时初始化
7.5.2 静态代码块
静态代码块,只执行一次,而且是类加载时就执行
作用:一般完成静态变量初始化赋值或完成整个系统只执行一次的任务
7.5.3 静态方法
最典型的静态方法是main()方法;
静态的方法有几个限制:
l 它们只能直接调用其他静态方法。
l 它们只能直接访问静态数据
l 它们不能以任何方式引用this或super关键字。(super是与继承相关的关键字,将在下一章介绍。)
案例:
改写前面的Calculator类,使用静态方法实现两个数量相加的功能。
main方法、math类中提供的许多方法都是静态方法
7.5.4 关于static的几点说明
1、static的本质作用是区分成员属于类还是属于实例。
2、通常把使用static修饰的变量和方法称为类变量和类方法,有时也称为静态变量和静态方法,把不使用static修饰的变量和方法称为实例变量和实例方法。
3、对于使用static修饰的成员,既可以通过类来调用也可以通过类的实例调用,但是建议使用类调用静态成员。对于实例变量和实例方法,则只能通过类的实例调用
第8单元 面向对象-继承
8.1 继承基础
继承是面向对象的基本特征之一。
8.1.1 继承的概念
使用继承可以为一系列相关对象定义共同特征的一般类,然后其他类(更特殊的类)可以继承这个一般类,每个进行继承的类都可以添加其特有的内容。
被继承的类称为超类(super class)/父类,继承的类称为派生类/子类(subclass)。
一旦创建了一个定义一系列对象共同特征的超类,就可以使用该超类创建任意数量的更特殊的子类。
8.1.2 继承的语法
继承使用关键字extends(扩展)实现。
public class A extends SuperA{
}
子类可以从父类继承属性和部分方法,自己再增加新的属性和方法。通过继承可以重用父类的方法和属性,减少代码重复编写,便于维护、代码扩展。
继承案例:
案例1
父类:Person:name age sex sleep()
子类:Student:grade score study()
子类:Teacher:college course teach()
案例2:
父类:Animal:name color age eat(); “动物吃东西!”
子类:Dog:layal watch(); “忠诚地看家护院”
子类:Cat:wakan CatchMouse(); “聪明地捉老鼠”
案例3:
父类:Box:length width height volume()
子类:WeightBox:weight
子类:ColorBox:color
8.1.3 对继承的说明
(1)子类不能从父类继承的资源:私有方法、构造方法、如果子类与父类在不同包中,子类不能继承父类中那些具有默认访问权限的方法。即不能继承那些不能访问的方法。在子类中不能访问到的那些方法,无法继承的。
理论上子类会继承父类的全部成员变量,但是子类不能访问父类的私有成员变量,如果子类与父类在不同包中,子类也不能访问父类中具有默认访问权限的成员变量。
(2)java类继承只允许单继承(只能有一个超类);java中接口允许多继承。
(3)子类中可以定义与父类中同名的成员变量,这时子类的成员变量会隐藏/覆盖父类中的同名成员变量。
(4)子类中也可以定义与父类中同名的成员方法,这时子类中的方法重写了父类中的同名方法。
8.1.4 子类的构造方法
(1)构造方法的调用顺序
在类继承层次中按照继承的顺序从超类到子类调用构造函数。
(2)在子类的构造方法中,一定会首先调用父类的构造方法。super();
(3)子类的每个构造方法都会隐式的调用父类的无参数构造方法,如果想调用父类的其他构造方法,必须使用super(参数列表)来显式调用。
说明:编写类时,通常需要提供无参数构造方法。
(4)如果父类没有无参的构造方法,或者想调用父类的有参构造方法,则在子类的构造方法中必须显式使用super(xxx)调用父类有参构造方法。这时super(xxx)必须是子类中的第一条语句。
(5)通常的做法:
在父类中定义有参数的构造方法,负责初始化父类的成员变量。
在子类的构造方法中,先调用父类的构造方法完成从父类继承来的那些成员变量,然后初始化子类中特有的成员变量。
注意:
如果父类中定义了一个有参数的构造方法,系统就不会再为父类提供默认的构造方法。这时,在子类的构造方法中,必须使用super(xxx)显示调用父类的有参构造方法。
8.1.5 创建多级继承层次
public GrandFather( ){
}
public Father( ) extends GrandFather{
}
public Son( ) extends Father{
}
案例:BawieStudent继承自Student类
8.1.6 方法重写介绍
当子类从父类中继承来的方法不能满足需要时,子类可以重写该方法,重写方法要求方法名与参数列表都相同。
案例:
重写Student、Teacher类中的sleep()、show()
重写Dog、Cat类中的eat()、show()
重写WeightBox、ColorBox类中的Show()
8.1.6 超类引用变量可以引用子类对象
SuperA sa; //声明超类的变量
A a = new A(); //创建子类对象
sa = a; //将子类对象赋给引用对象
sa = new A(); //创建一个新的子类对象,赋给超类引用变量
可以将子类的对象赋给父类的引用变量,但是这时使用父类的引用变量只能访问父类中定义的那些成员变量。换句话说,可以访问哪些成员是由引用变量的类型决定的,而不是由所引用的对象类型决定的。
动态方法调度介绍:
案例演示:
8.1.7 对象的转型
Animal a = new Dog(); //小转大 可以自动进行
a.layal; //语法错误,这时通过a只能使用父类中定义的成员变量。编译时就确定
a.eat(); //
Animal a = new Dog();
d = (Dog)a; //正确 大转小, 需要强制转换
Dog d = new Animal(); //错误
Dog d = (Dog)new Animal(); //语法没错,可以编译,但运行时会抛出异常
Cat c = new Cat();
d = (Dog)c; //不正确
子类对象 赋给父类引用 可以,自动转换
父类引用 赋给 子类引用 需要强转,前提:父类引用确实指向了正确的子类对象
8.2 super关键字
关键字super用于调用/访问从父类中继承来的实例变量和方法。
super有两种一般用法。第一种用于调用超类的构造方法。第二种用于访问超类中被子类的某个成员隐藏的成员。
8.2.1 使用super()调用父类的构造方法
l 在子类中使用super()调用父类的构造方法,必须是第一条语句
l 在本类中可以使用this()调用重载的构造方法,也必须是第一条语句
l 在子类的构造方法中this()和super()不能同时使用
8.2.2 使用super访问父类中被子类隐藏的成员变量
父类的属性被子类继承,如果子类又添加了名称相同的属性,则子类有两个相同名称的属性,如果父类型对象调用属性,就是父类的,如果是子类型对象调用就是子类的属性。
一个子类需要引用它的直接超类,都可以使用关键字super。
案例:
8.3 Object
8.3.1 Object类介绍
所有其他类都是Object的子类。也就是说,Object是所有其他类的超类。这意味着Object类型的引用变量可以引用任何其他类的对象。此外,因为数组也是作为类实现的,所以Object类型的变量也可以引用任何数组。
Object类定义了下面列出的方法,这意味着所有对象都可以使用这些方法。
方 法 | 用 途 |
Object clone() | 创建一个和将要复制的对象完全相同的新对象。 |
boolean equals(Object object) | 确定一个对象是否和另外一个对象相等 |
void finalize() | 在回收不再使用的对象前调用 |
Class<?> getClass() | 在运行时获取对象的类 |
int hashCode() | 返回与调用对象相关联的散列值 |
void notify() | 恢复执行在调用对象上等待的某个线程 |
void notifyAll() | 恢复执行在调用对象上等待的所有线程 |
String toString() | 返回一个描述对象的字符串 |
void wait() void wait(long milliseconds) void wait (ling milliseconds, int nanoseconds) | 等待另一个线程的执行 |
8.3.2 对象相等性比较
Object类中的equals()方法实现等价于“==”运算符,比较相等,如果实现对象的内容相等比较,自己的类必须重写equals方法。
例如:String类对equals()方法进行了重写,重写后的equals方法比较两个两个字符串的内容是否相同。
8.3.3 Object类的常用方法
l equals(Object obj)方法
比较对象相等 Object类的实现是 等价于==
相等的含义:两个引用是否指向同一个对象。
自己的类要比较对象相等,重写equals()方法
案例:重写Box类的equals()方法
l toString()方法
直接打印对象时,默认调用对象的toString()方法
Object类的toString方法输出格式:
getClass().getName() + '@' + Integer.toHexString(hashCode())
自己的类要重写toString()
案例:重写Box类的toString()方法。
l protected Object clone()
克隆对象的方法 被克隆的对象的类必须实现Cloneable接口
l finalize()方法 //终结方法
当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
l HashCode()方法
返回该对象的哈希码值
当我们重写equals()方法,判断两个对象相等时,最好也同时重写hascode()方法,让相同对象的哈希码值也相同
8.4 final
8.4.1 final修饰变量、方法、类
l 如果final修饰变量,变量就是常量,常量不可修改,定义时必须初始化
l 如果final修饰方法,方法就不能被子类重写
l 如果final修饰类,类就不能再被扩展,不能再有子类。Java类库中的String、Math就是final类。
8.4.2 引用类型的常量
如果常量是基本数据类型,不可以再修改。
如果常量是引用类型,不能再将其他对象赋给该引用,但可以使用该引用改变对象内部的属性。
例如
final Student s = new Student(“zhangsan”,20);
s = new Student(“李四”,20); //错误
s.setName(“李四”); //可以 正确
第9单元 面向对象—多态
9.1 多态的概念
同一个方法名称,执行不同的操作。方法重载就是一种多态的一种形式。
9.2 方法重写
当子类从父类中继承来的方法不能满足需要时,子类可以重写该方法,重写方法要求方法名与参数列表都相同。
因此如果子类中的方法与父类中的方法同名、并且参数类型也相同,那么子类中的方法就重写了父类中的同名方法。
为什么不直接定义另外一个方法?因为重写方法可以实现运行时多态的效果。
注意:重写只针对方法,属性没有重写概念
9.2.1 方法重写的规则
两同:方法名相同、参数列表相同
两小:返回值类型更小(子类)或相等、抛出的异常类更小或相等
一大:访问权限更大或相等
案例复习:
Student、Teacher重写Person中的sleep()、show()方法
Dog、Cat重写Animal中的eat()、show()方法
WeightBox、ColorBox重写Box中的show()方法
重写Shape类中的area()方法
重写fruit类中的show()方法
9.2.2 方法重写与方法重载的区别
只有当两个方法的名称和类型签名都相同时才会发生重写。如果不是都相同,那么这两个方法就只是简单的重载关系。
9.2 动态方法调度与运行时多态
9.2.1 动态方法调度
当通过父类引用调用重写方法时,在运行时会调用子类中的重写版本。
动态方法调用要以方法重写为前提。
Animal a;
a = new Dog();
a.eat(); //动态方法调度:在运行时根据超类引用指向的对象确定调用哪个方法
9.2.2 运行时多态
运行时多态的实现机理:动态方法调度
总结:方法重写是前提、动态调度是手段、多态是最终的目的
运行时多态的优点:灵活
Animal a;
a = new Dog();
a.eat();
a = new Cat();
a.eat(); //运行时多态:方法名相同,得到的结果不同
运行时多态的两个要素:
(1)在子类中重写超类中的方法
(2)使用超类引用调用重写方法。
在自己的类中定义的toString()方法就是重写方法。
注意不要混淆:
使用超类引用调用成员变量时,调用的是超类的成员变量。
9.2.3 多态的两种形式
运行时多态:动态方法调度实现
编译时多态:重载方法,编译时通过方法匹配实现的
9.3 多态实例
实例1:Animal、Dog、Cat类的eat()方法、show()方法
实例2:Person、Student、Teacher、Worker类的show()方法、eat、sleep
实例3:Fruit、Apple、Banana的Show()方法
实例4:Shape、Rectangle、Circle、Triangle类的area()、draw()方法
9.4 多态应用
案例1:Feeder类,喂养Dog、Cat
案例2:Manager类,管理Student、Teacher、Worker
案例3:Salesman类,卖水果
案例4:Engineer类,使用Shape类
9.4 抽象方法与抽象类
当编写一个类时,常常会为该类定义一些方法,这些方法用以描述该类的行为,那些这些方法都有具体的方法体。但在某些情况下,某个父类只知道其子类应该包含哪些方法,但无法准确地知道这些子类如何实现这些方法。例如Shape类的area()方法,因为Shape类的不同子类对面积的计算方法不同,即Shape类无法准确地知道其子类计算面积的方法,因此area()方法只能留给子类实现。
在某些情况下会希望定义这样一种超类,在该超类中定义了一些子类应该包含的方法,但是在超类中不能给出这些方法的有意义的实现。例如,Animal类的eat()方法、Shape类的area()方法,无法给出有实际意义的实现,对于这类方法可以声明为抽象方法。
问题:
既然父类中不能给出抽象方法的实现,为什么还要在父类中添加这些方法呢?
9.4.1 抽象方法
抽象方法是使用abstract修饰的方法。将一个方法声明为抽象方法,从而要求子类必须重新该方法。
注意:
l 抽象方法没有方法实现,即没有方法体{},只有定义。
l 类中如果有抽象方法,该类必须是抽象类,必须使用abstract
l 对于抽象方法,abstract不能与private、static同时使用。为父类添加抽象方法,然后让子类实现,一个主要目的就是实现多态的效果,而实现多态效果需要两个前提:一是子类重写父类中的方法,二是使用父类引用调用子类重写后的方法,根据父类实际指向的对象调用相应的重写方法。如果将抽象方法声明为private,则子类中就无法重写该抽象方法;如果方法为static方法,则该方法属于类,而不属于某个对象,从而也就无法根据实际的指向的对象调用想用的方法。
9.4.2 抽象类
类定义中使用abstract修饰的类为抽象类。
注意:
l 从语法上讲,抽象类中可以没有抽象方法,但是没有实际意义
l 有抽象方法的类必须是抽象类
l 不能创建抽象类的对象,即不能new对象
l 抽象类可以当做一种引用类型来使用,声明变量
l 继承自抽象类的类,必需重写抽象类中的所有抽象方法,否则自身也使用abstract修饰,即也是抽象类。
抽象类的子类,会继承抽象类中的所有抽象方法,子类要么重写所有的抽象方法。如果有一个抽象方法的没有重写的话,子类中也有抽象方法。
说明:
抽象类只定义被其所有子类共享的一般形式,而让每个子类填充其细节。这种类确定了子类必需实现的方法。
注意:
有抽象方法的一定是抽象类。
错误,因为接口中的也有抽象方法,而且接口中的所有方法都是接口方法。
第10单元 面向对象—接口
10.1 接口的概念与定义
接口可以理解为抽象到不能再抽象的类,但是不要将接口和类混为一谈。可以认为类是一套体系,接口是另外一套体系,只不过类可以实现接口。
接口中的方法全部都是抽象方法,不能存在实现的方法。
接口使用interface关键字定义,接口的定义和类很相似。下面是经过简化的接口的一般形式:
access interface name {
return-type method-name1(parameter-list); //可以省略各种修饰符
return-type method-name2(parameter-list);
...
return-type method-nameN(parameter-list);
type varname1 = value; //可以省略各种修饰符
type varname2 = value;
..
type varnameN = value;
}
10.2 接口中的属性和方法
(1)接口中所有方法默认是公有的抽象方法。
隐式地标识为public、abstract,并且接口中的方法也只允许使用这两个修饰符。
注意,在抽象类中必需使用abstract关键字明确指定方法为抽象方法。
(2)在接口中所有变量默认为公有的静态常量。
被隐式地标识为public、static、final。这意味着实现接口的类不能修改它们。同时还必须初始化它们。
public interface A1 {
//接口中的属性必须public、static、final常量
public static final int I=10;
//接口中的属性public、static、final都可以省略
int J = 100;
//接口中的方法默认是public、abstract,所以public abstract可以省略
public abstract void print();
public void print2();
void print3();
}
注意:
l 接口能new对象吗?不可以
l 接口能作为一种类型定义引用变量吗? 可以 A1 a;
10.3 接口的实现
一旦定义了一个接口,一个或多个类就可以实现该接口。为了实现接口,在类定义中需要包含implements子句,然后创建接口定义的方法。
class classname implements interfacename {
//
}
注意:
(1)实现接口的类,必须实现接口的所有抽象方法,如果只实现了部分抽象方法,该类必须声明为抽象类。
(2)一个类可以实现多个接口,实现的多个接口用“,”隔开
(3)实现接口的类可以同时继承一个超类。
public interface A1 {
//接口中的属性必须是 public static final常量
public static final int I=10;
//接口中的属性 public static final都可以省略
int J = 100;
//接口中的方法都是 public abstract 所以public abstract可以省略
public abstract void print();
public void print2();
void print3();
}
public interface A2 {
void show();
}
/**
* 类实现接口 使用implements
* 类可以实现多个接口,用 ,隔开
* 一个具体的类,实现接口,必须实现接口的所有抽象方法
* @author Administrator
*
*/
public class SubA implements A1,A2{
@Override
public void print() {
}
@Override
public void print2() {
}
@Override
public void print3() {
}
@Override
public void show() {
}
}
说明:
接口定义了一组抽象方法,实现该接口的类需要实现这些抽象方法,从而实现接口的类就具备了接口所规定的行为(功能)。
在Java中,接口可理解为对象间相互通信的协议,相当于模板。
10.4 接口继承
接口可以通过关键字extends继承另一个接口,其语法和类继承相同。如果类实现的接口继承自另外一个接口,则该类必需实现在接口继承链中定义的所有方法。
10.5 接口的实例
10.5.1 实例1
public interface Shape {
double area();
void show(); //打印输出类的成员信息
void draw(); //我是XXX(矩形、圆形、三角形)
}
定义Rectangle、Circle、Triangle类,添加适当的属性,并实现Shape接口
定义测试类测试Rectangle、Circle、Triangle类。在测试类中定义一个Shape[]数组,在该数组中存储不同的形状对象。通过循环,分别调用各个对象的area()、show()、draw()方法。
10.5.2 案例2
public interface IntStack {
void push(int item);
int pop();
}
10.6 抽象类和接口的区别
补充:
抽象类有构造方法,接口没有构造方法
类只能单继承,接口可以多重继承接口
抽象类中可以没有抽象方法,但是有抽象方法的类必须是抽象类。
第11单元 常用类
11.7 包装类
一方面出于性能方面的考虑,java为数值使用基本类型,而不是对象。基本类型不是对象层次的组成部分,它们不继承Object。
另一方面有时需要创建表示基本类型的对象,例如集合类只处理对象。为了在类中存储基本类型,需要将基本类型包装到一个类中,为此Java为8种基本数据类型分别提供了对应的包装类。本质上这些类将基本类型包装到一个类中,因此通常将它们称为类型包装器。包装器类位于Java.lang包中。
八个包装类
Byte Short Integer Long Float Double Character Boolean
11.7.1 Character包装器
Character是char类型的包装器。Character的构造函数为:
Character(char ch)
其中,ch指定了将由即将创建的Character对象包装的字符。
为了获取Character对象中的char数值,可以调用charValue(),如下所示:
char charValue( )
该方法返回封装的字符。
11.7.2 Boolean包装器
Boolean是包装boolean值的包装器。它定义了以下构造函数:
Boolean(boolean boolValue)
Boolean(String boolString)
在第一个版本中,boolValue必须是true或false。在第二个版本中,如果boolString包含字符串“true”(大写或小写形式都可以),则新的Boolean对象将为真,否则,将为假。
为了从Boolean对象获取boolean值,可以使用booleanValue(),如下所示:
boolean booleanValue( )
该方法返回与调用对象等价的boolean型值。
11.7.3 数值类型的包装器类
1、构造器
所有数值类型包装器都定义了用于从给定数值或数值的字符串表示形式构造对象的构造函数,例如,下面是为Integer定义的构造器:
Integer(int num)
Integer(String str)
如果str没有包含有效的数字值,则会抛出NumberFormatException异常。
2、从包装器对象中提取数值
最常用类型的包装器是那些表示数值的包装器。包括Byte、Short、Integer、Long、Float以及Double。所有这些数值类型包装器都继承自抽象类Number。Number声明了以不同数字格式从对象返回数值的方法,如下所示:
byte byteValue( )
double doubleValue( )
float floatValue( )
int intValue( )
long longValue( )
short shortValue( )
3、将包装器对象转换成字符串
类型包装器都重写了toString()方法,该方法可以将数值转换成字符串形式。
String str = Integer.toString(100);
案例:包装器类测试
11.7.1 自动装箱与自动拆箱
自动装箱是这样一个过程,只要需要基本类型的对象,就自动将基本类型自动封装(装箱)进与之等价的类型包装器中,而不需要明确地构造对象。自动拆箱是当需要时自动抽取(拆箱)已装箱对象数值的过程。不需要调用intValue()或doubleValue()这类方法。
自动装箱和自动拆箱特性极大地简化了一些算法的编码,移除了单调乏味的手动装箱和拆箱数值操作。它们还有助于防止错误。此外,它们对于泛型非常重要,因为泛型只能操作对象。最后,集合框架需要利用自动装箱特性进行工作。
案例:自动装箱与自动拆箱测试
11.7.2 数值与字符串形式之间的转换
最常见的编程杂务之一是将数值的字符串表示形式转换成数值。数值类型的包装器类为此提供了相应的方法。例如:
l Int类的parseInt()方法
l Long类的parseLong()方法
l Double类的parseDouble()方法
为了将数值转换成字符串形式,可以调用相应包装类的toString()方法。
示例:
说明:
各包装器类以静态方法的形式提供了许多很有用的辅助功能,请查阅帮助文档。
String.valueOf()
Int.toString();
Double.toString();
int I = 100;
String str = I + “”;
测试案例:
11.7.3 字符分类
Character类提供一些静态方法用于判断字符属于哪一类。
static boolean isDigit(char ch) | 如果ch是数字,则返回true。 |
static boolean isLetter(char ch) | 如果ch为字母,则返回true。 |
static boolean isLetterOrDigit(char eh) | 如果ch为字母或数字,则返回true。 |
static boolean isLowerCase(char ch) | 如果ch为小写字母,则返回true; |
static boolean isUpperCase(char ch) | 如果ch为大写字母,则返回true。 |
static boolean isSpaceChar(char ch) | 如果ch为空格字符,则返回true。 |
static boolean isWhitespace(char ch) | 如果ch为空白字符,则返回true。 |
11.7.4 包装器类中其他常用的常量和方法
Integer.MAX_VALUE
Integer.MIN_VALUE
Integer.SIZE //长度,多少bit
Integer.valueOf(100); //根据整数创建Integer对象
Integer. valueOf("100"); //根据字符串创建Integer对象
11.2 String
11.2.1 String类介绍
(1)创建的每个字符串实际上都是String类的对象。即使是字符串字面值实际上也是String对象。
(2)String类型的对象是不可变的;一旦创建了一个String对象,其内容就不能再改变。即,一旦创建了一个String对象,就不能改变该字符串包含的字符。
所谓Stirng类型对象中的字符串是不可改变的,是指创建了String实例后不能修改String实例的内容。但是可以修改String引用变量,使其指向其他String对象。
当每次需要已存在字符串的修改版本时,会创建包含修改后内容的新String对象。原始字符串仍然没有改变。使用这种方法的原因是,实现固定的、不能修改的字符串与实现能够修改的字符串相比效率更高。
(3)对于那些需要能够修改的字符串的情况,Java提供了两个选择:StringBuffer和StringBuilder。这两个类都包含在创建之后可以进行修改的字符串。
(4)String、StringBuffer和StringBuilder类都是在java.lang包中定义的。这三个类都实现了CharSequence接口。
注意:API文档的使用
11.2.2 String类的构造方法
l String(); //创建不包含内容的字符串对象
l String(char[ ] chars)
l String(char[ ] chars, int startIndex, int numChars)
l String(String strObj)
l 还可以直接使用字符串字面创建String对象:String str =“abc”;
示例:StringMakeDemo
注意:没有使用单位字符作为参数的构造器。
//String(char c);
说明:
因为会为字符串字面值创建String对象,所以在能够使用String对象的任何地方都可以使用字符串字面值。
System.out.println("abc".length());
11.2.3 字符串比较
11.2.3.1 字符串相等性比较
String类重写了equals()方法,重写后的方法比较两个字符串对象的内容是否相同。
运算符“==”比较两个String引用是否指向同一个String对象。
示例:StringCompareDemo
11.2.3.2 其他比较方法
l boolean equalsIgnoreCase(String str)
将此String与另一个String比较,不考虑大小写。
l boolean endsWith(String suffix)
测试此字符串是否以指定的后缀结束。
l boolean startsWith(String prefix)
测试此字符串是否以指定的前缀开始。
l int compareTo(String str)
按照字典顺序比较两个字符串。
其中,str是将要与调用String对象进行比较的String对象。返回的比较结果及其解释如下所示:
值 | 含 义 |
小于0 | 调用字符串小于str。 |
大于0 | 调用字符串大于str。 |
0 | 两个字符串相等。 |
l int compareToIgnoreCase(String str)
按字典顺序比较两个字符串,不考虑大小写。
11.2.4 字符串连接
String str1 = “abc”;
String str2 = “def”;
String str3 = “hij”;
String str1 = str1 + str2;
String类型的引用,指向的字符串对象是不能修改的。
String表示不可变的字符串,只要创建了字符串对象,那么这个对象的内容就不能再改变。
11.2.5 字符串查找
案例:StringSearchDemo
1.boolean contains(CharSequence s)
当且仅当此字符串包含指定的char值序列时,返回true。
CharSequence 表示字符串序列,是String的父类
2.int indexOf(int ch)和int indexOf(String str)
返回指定字符/子串第一次出现处的索引。
3.int lastIndexOf(int ch)和int lastIndexOf(String str)
返回指定字符/子串最后一次出现处的索引。
说明:
(1)当没有字符/子串没有出现时,返回值为-1。可以使用该方法判断字符/子串是否存在。
(2)可以使用下面这些重载形式指定查找的开始位置:
int indexOf(int ch, int startIndex)
int lastIndexOf(int ch, int startIndex)
int indexOf(String str, int startIndex)
int lastIndexOf(String str, int startIndex)
11.2.6 字符串修改
案例:StringModifyDemo
1.String concat(String str)
将指定字符串连接到此字符串的结尾,concat()与“+”执行相同的功能。
2.String replace(char oldChar, char newChar)
返回一个新的字符串,它是通过用newChar替换此字符串中出现的所有oldChar得到的。
3.String toLowerCase()
使用默认语言环境的规则将此String中的所有字符都转换为小写。
4.String toUpperCase()
使用默认语言环境的规则将此String中的所有字符都转换为大写。
5.String trim( )
返回字符串的副本,删除前导空白和尾部空白。
11.2.7 提取字符与子串
案例:GetCharsDemo
1.char charAt(int index)
返回指定索引处的char值。
2.char[ ] toCharArray()
将此字符串转换为一个新的字符数组。
3.String substring(int beginIndex)
返回一个新的字符串,它是此字符串的一个子字符串。该子字符串始于指定索引处的字符,一直到此字符串末尾。
4.String substring(int beginIndex, int endIndex)
返回一个新字符串,它是此字符串的一个子字符串。该子字符串从指定的beginIndex处开始,一直到索引endIndex-1处的字符。
11.2.8 其他字符串常用方法
案例:StringOtherMethDemo
1.int length()
返回此字符串的长度
注意:数组的长度为数组对象.length 属性
2.String[] split(String regex)
根据给定正则表达式的匹配拆分此字符串。最简单的使用方法是为参数regex
3.static String valueOf( int i)
将数据从内部格式转换成人类可读的形式。它是一个静态方法。
对于大部分数组,valueOf()会返回一个相当隐蔽的字符串,表明这是某种类型的数组。然而,对于字符数组,会创建包含字符数组中字符的String对象。
11.3 StringBuffer和StringBuilder
11.3.1 StringBuffer与StringBuilder类介绍
StringBuffer是String的对等类,提供了许多字符串功能。您可能知道,String表示长度固定、不可修改的字符序列。与之相对应,StringBuffer表示可增长、可写入的字符序列。StringBuffer允许在中间插入字符和子串,或在末尾追加字符和子串。StringBuffer能够自动增长,从而为这类添加操作准备空间,并且通常预先分配比实际所需更多的字符空间,以允许空间增长。
StringBuilder类是由JDK 5引入的,以增加Java的字符串处理能力,提供与StringBuffer相同的功能。
StringBuffer与StringBuilder的区别:
l StringBuffer类是线程安全的,而StringBuilder则不是,即不保证其对象的同步性,在多线程环境中是不安全的。
l StringBuilder在性能上要比StirngBuffer好一些。
11.3.2 StringBuffer类的构造方法
StringBuffer( ) //默认预留16个字符的空间
StringBuffer(int size) //size指定预留的字符空间
StringBuffer(String str) //额外预留16个字符的空间
StringBuffer(CharSequence chars) //额外预留16个字符的空间
提示:
再次分配内存空间是很耗时的操作。此外,频繁分配空间会产生内存碎片。
11.3.2 StringBuffer类的常用方法
1.append ()
append()方法将各种其他类型数据的字符串表示形式连接到调用StringBuffer对象的末尾。该方法有多个重载版本,下面是其中的几个:
StringBuffer append(String str)
StringBuffer append(int num)
StringBuffer append(Object obj)
2.insert ()
在指定位置插入参数提供的内容,返回修改后的该StringBuffer对象引用。该方法有多个重载版本,下面是其中的几个:
StringBuffer insert(int index, String str)
StringBuffer insert(int index, char ch)
StringBuffer insert(int index, Object obj)
3.StringBuffer delete (int start,int end)
删除从start开始到end-1为止的一段字符序列,返回修改后的该StringBuffer对象引用。
4.StringBuffer deleteCharAt(int index)
移除指定位置的字符,返回修改后的该StringBuffer对象引用。
5.StringBuffer reverse()
将字符序列逆序,返回修改后的该StringBuffer对象引用。
6.StringBuffer setCharAt( (int index,char ch)
将指定索引处的字符设置为 ch,返回修改后的该StringBuffer对象引用。
public static void main(String[] args){
StringBuffer sb = new StringBuffer();
sb += “I”;
sb.append(“am”);
sb.append(true);
System.out.println(sb);
}
几点说明
(1)StringBuffer对象不能使用+=赋值
(2)注意使用StringBuffer的append()方法连接字符串与使用“+”运算符直接连接String对象的区别。
案例:StringBufferDemo
11.3.3 长度与容量的概念
长度是指StringBuffer中实际保存的字符的个数,容量是指已经分配的空间大小。
1.int length()
获取StringBuffer对象的当前长度
2.void setLength(int len)
设置StringBuffer对象中字符串的长度。当增加字符串的大小时,会向末尾添加空字符。如果调用setLength()时使用的值小于length()返回的当前值,则超出新长度的字符将丢失。
3.int capacity( )
获取StringBuffer对象的当前容量
4.void ensureCapacity(int minCapacity)
设置缓存的大小。minCapacity指定了缓存的最小尺寸。(出于效率考虑,可能会分配比minCapacity更大的缓存。)
11.4 Math
11.4.1 Math介绍
Math类包含所有用于几何和三角运算的浮点函数,以及一些用于通用目的的方法,这些方法都是静态方法。Math类定义了两个静态常量:E和PI。
位于java.lang包下。
11.4.2 Math类的常量
1.自然对数的底 E(约等于2.72)
2.圆周率PI (约等于3.14)
11.4.3 Math类的常用方法
1.public static double abs(double a)
返回 double 值的绝对值
2.public static double random()
返回一个随机数,大于等于0.0 且小于 1.0
2.static double sqrt(double arg)
返回arg的平方根
4.static double pow(double y, double x)
返回y的x次方。例如,pow(2.0, 3.0)返回8.0
5.public static long round(double a)
将a四舍五入为整数,例如a=4.51,则返回5。
6.public static double ceil(double a)
返回大于或等于a的最小整数,例如a=4.3则返回5。
7.public static double floor(double a)
返回小于或等于a的最大整数,例如a=4.6,则返回4
注意:round()、ceil()、floor()三个方法之间的区别。
案例:MathDemo
11.5 Random
11.5.1 Random类介绍
Random类是伪随机数生成器。Random类位于java.util包中。Random类的构造方法如下:
Random( ) //使用相对唯一的种子
Random(long seed) //手动指定种子
常见的做法是使用当前时间(毫秒数)作为种子。
Random类与math.random()方法的区别:
Random类提供了多个法,可以生成多种形式的随机数,Math.random()方法只能随机生成[0.0, 1.0)之间的浮点数。
11.5.2 常用方法
1.public int nextInt()
返回下一个伪随机数
2.public int nextInt(int n) [0,100)
返回一个伪随机数,它是取自此随机数生成器序列的、在 0(包括)和指定值(不包括)之间均匀分布的int值
int I = random.nextInt(100);
11.6 Date与Calendar
11.7.1 Date类
Date类封装了当前日期和时间。位于java.util包。
(1)类构造方法
public Date() //使用当前日期和时间初始化对象
public Date(long date) //参数date为自从1970年1月1日子时经历的毫秒数
(2)常用方法
long getTime() | 返回从1970年1月1日午夜开始已经流逝毫秒数 |
void setTime(long time) | 将日期和时间设置为time所指定的值,time是是自从1970年1月1日午夜开始已经流逝的毫秒数 |
String toString() | 将调用Date对象转换成字符串并返回结果 |
(3)Date类示例
package com.bw;
import java.util.Date;
public class DateTest {
public static void main(String[] args) {
Date d = new Date();
System.out.println(d);
//System.out.println(d.toLocaleString());
long time1 = d.getTime();//1970-1-1 00:00:00 GMT 到现在的 总毫秒数
System.out.println(time1);
long time2 = System.currentTimeMillis();
System.out.println(time2);//1970-1-1 00:00:00 GMT 到现在的 总毫秒数
Date d2 = new Date(time2);
System.out.println(d2);
}
}
11.7.2 Calendar与GregorianCalendar类
Calendar是抽象类,提供了一套方法将毫秒数形式的时间转换成大量有用的组分,例如年、月、日、小时、分和秒。
GregorianCalendar是Calendar的具体实现,它实现了您熟悉的常规的格林尼治日历。Calendar类的getInstance()方法通常会返回一个使用默认地区和时区下的当前时间初始化的GregorianCalendar对象。
示例:CalendarDemo
第12单元 集合
12.1 集合概述
(1)集合是存储其他对象的特殊对象。可以将集合当做一个容器。
(2)集合的相关接口和类位于java.util包中
(3)集合中的接口和类是一个整体、一个体系。
12.2 集合接口
接口定义了一组抽象方法,实现该接口的类需要实现这些抽象方法,从而实现接口的类就具备了接口所规定的行为(功能)。
集合框架定义了一些接口,它们决定了集合类的本质特性。具体的集合类只是提供了标准接口的不同实现。
接 口 | 描 述 |
Collection | 允许操作一组对象,它位于集合层次结构的顶部 |
List | 扩展Collection,以处理序列(对象列表) |
Deque | 扩展Queue,以处理双端队列 |
Set | 扩展Collection,以处理组,组中的元素必须唯一 |
注意:
Set类型的集合(实现Set接口的类)称为集,有些资料称为组,其特点是组中的元素必须唯一。
12.2.1 Collection接口
Collection接口是构建集合框架的基础,Collection是泛型接口,其声明如下:
interface Collection<E>,
其中,E指定了集合将存储的对象类型。
提示:
l 集合中只能存储对象,不能存基本类型。
l 使用泛型的优点是在编译时可以检查元素的类型,从而更加安全。
Collection接口扩展了Iterable接口。这意味着所有集合都可以使用for-each风格的for循环进行遍历。
方 法 | 描 述 |
boolean add(E obj) | 将obj添加到调用集合。 |
boolean addAll(Collection<? extends E> c) | 将c中的所有元素添加到调用集合中。 |
boolean remove(Object obj) | 从调用集合中删除obj的一个实例。 |
boolean removeAll(Collection<?> c) | 从调用集合中删除c的所有元素。 |
void clear() | 删除调用集合中的所有元素 |
boolean contains(Object obj) | 如果obj是调用集合的元素,则返回true。 |
boolean isEmpty() | 如果调用集合为空,则返回true。 |
int size() | 返回调用集合中元素的数量 |
Iterator<E> iterator() | 返回调用集合的一个迭代器 |
containAll(Collection c) |
|
提示:
1、Collection接口中没有提供修改元素的方法。
2、Collection接口的父接口是Iterable接口,实现了Iterable接口的类是可以迭代的。
12.2.2 List接口
List接口扩展了Collection,并且声明了存储一连串元素的集合的行为。在列表中,可以使用从0开始的索引,通过它们的位置插入或访问元素。列表可以包含重复的元素。其声明如下:
interface List<E>
方 法 | 描 述 |
void add(int index, E obj) | 将obj插入到index所指定的位置。 |
boolean addAll (int index, Collection<?extends E> c) | 将c的所有元素插入到index所指定的位置。 |
E remove(int index) | 删除index位置的元素 |
E set(int index, E obj) | 将index所指定位置的元素设置为obj |
E get(int index) | 返回指定索引处存储的对象 |
int indexOf(Object obj) | 返回第一个obj实例的索引。 |
int lastIndexOf(Object obj) | 返回列表中最后一个obj实例的索引 |
ListIterator<E> listIterator() | 返回一个迭代器,该迭代器从列表的开头开始 |
List<E>subList(int start,int end) | 返回一个子列表。 |
注意:
List接口中操作元素的方法许多都提供了index参数,这是与Collection接口中所提供相关方法的主要区别。
12.2.3 Queue和Deque接口(了解)
Queue接口扩展了Collection接口,并声明了队列的行为,队列通常是先进先出的列表。Deque接口扩展了Queue接口,并且声明了双端队列的行为。双端队列既可以像标准队列那样先进先出,也可以像堆栈那样后进先出。声明如下:
interface Queue<E>
interface Deque<E>
12.2.4 Set接口
Set接口定义了组/集/集合(set)。它扩展了Collection接口,并声明了不允许重复元素的集合的行为。如果为集合添加重复的元素,add()方法会返回false。声明如下:
interface Set<E>
Set接口没有添加自己的方法。
SortedSet接口扩展了Set接口,并且声明了以升序进行排序的集合的行为。
interface SortedSet<E>
SortedSet定义了一些便于进行集合处理的方法。例如,为了获得集合中的第一个对象,可以调用first()方法。为了得到最后一个元素,可以使用last()方法。
NavigableSet接口扩展了SortedSet接口,并且该接口声明了支持基于最接近匹配原则检索元素的集合行为。
注意:
Set相关接口表示的集合没有索引的概念。
12.3 集合类
类 | 描 述 |
ArrayList | 动态数组 |
LinkedList | 链表 |
ArrayDeque | 双端队列 = 队列+堆栈 |
PriorityQueue | 支持基于优先级的队列 |
HashSet | 使用哈希表存储元素的组 |
LinkedHashSet | 扩展HashSet类,以允许按照插入的顺序进行迭代 |
TreeSet | 实现存储于树中的集合。 |
12.3.1 Arraylist类
ArrayList实现了List接口。本质上是元素为对象引用的长度可变的数组。
构造方法:
l ArrayList( ) //长度取默认值
l ArrayList(int capacity) //指定长度,容量
l ArrayList(Collection<? extends E> c)
12.3.2 LinkedList类
LinkedList类实现了List、Deque以及Queue接口。它提供了(双向)链表数据结构。
LinkedList具有两个构造方法:
LinkedList( )
LinkedList(Collection<? extends E> c)
ArrayList与LinkedList的区别:
1、ArrayList是基于数组结构的集合,有容量的概念;LinkedList是基于链表结构的集合,没有容量的概念
2、对于随机访问(get和set方法),ArrayList优于LinkedList,因为LinkedList要移动指针。
3、对于新增和删除操作(add和remove方法),LinkedList比较占优势,因为ArrayList要移动数据。但是如果只是在末尾追加元素,并且没有超出容量限制,则ArrayList的性能更好。
4、LinkedList还实现了Queue接口,该接口比List提供了更多的方法,包括offer(),peek(),poll()等。
12.3.3 HashSet
HashSet类实现了Set接口。该类在内部使用哈希表存储元素。
哈希表使用称之为散列法(hashing)的机制存储信息。哈希法的优点是add()、contains()、remove()以及size()方法的执行时间保持不变,即使是对于比较大的集合也是如此。
HashSet( ) //默认容量是16
HashSet(int capacity)
HashSet(int capacity, float fillRatio) //填充率:0.0-1.0之间,默认0.75
HashSet(Collection<? extends E> c)
HashSet中元素不是按有序的顺序存储的,遍历输出HashSet中的元素时精确的输出可能不同。
12.3.4 LinkedHashSet类
LinkedHashSet类扩展了HashSet类,它没有添加它自己的方法。
LinkedHashSet在内部使用一个链表维护元素添加到集合中的顺序,因此可以按照插入顺序迭代集合。
12.3.4 TreeSet类
TreeSet类实现了NavigableSet接口,该类在内部使用树结构存储元素。元素以升序存储,访问和检索相当快。TreeSet适合于存储大量的、必须能够快速查找到的有序信息。
Set与List的区别:
1、Set中的元素不能重复,List中的元素可以重复。
2、List有索引(下标)的概念,Set没有索引的概念。
3、对于Set表示的集合,通常是遍历操作,没有get()和set()方法。
注意:
TreeSet以升序保存对象,所以TreeSet中保存的对象比较能够比较大小,即TreeSet保存的对象类型必须实现Comparable接口。
注意:
HashSet是无序的,LinkedHashSet和TreeSet是有序的。
12.4 集合遍历
12.4.1 Iterable接口
实现了Iterable接口的类是可以遍历的。因为Iterable接口是Collection接口的父接口,而所有集合类都实现了Collection接口,从而也都实现了Iterable接口,所以所有集合类都是可以遍历的。
Iterable接口只定义了一个方法:
Iterator<T> iterator() //返回迭代器对象
既然所有集合都实现了Iterable接口,所以所有集合类都重写了iterator()方法以返回一个迭代器对象。
12.4.2 Iterator接口
Iterator接口描述了迭代器的行为,所有迭代器类都必须实现该接口,该接口定义了一下方法:
l boolean hasNext() 如果迭代还有更多的元素则返回true
l T next() 返回下一个元素
l void remove() 删除迭代器返回的元素
提示:
从Iterator接口定义的方法不难看出Iterator只能从前向后进行遍历。
12.4.3 ListIterator接口
Iterator接口有一个子接口ListIterator,ListIterator接口即可以从前向后遍历,也可以从后向前遍历集合。只有实现了List接口的集合类才提供了ListIterator迭代器。
List接口提供以下两个方法用于获取列表集合的列表迭代器:
ListIterator<E> listIterator() 该迭代器从列表的开头开始
ListIterator<E> listIterator()(int index) 该迭代器从index所指定的位置开始
ListIterator接口定义了以下常用方法。
l boolean hasNext()
l Boolean hasPrevious()
l E next()
l int nextIndex()
l E previous()
l int previousIndex()
12.4.1 使用迭代器
通常,为了使用迭代器遍历集合的内容,需要以下步骤:
1.通过调用集合的Iterator()方法,获取指向集合开头的迭代器。
2.建立一个hasNext()调用循环。只要hasNext()返回true,就循环迭代。
3.在循环中,通过调用next()获取每个元素。
对于实现了List接口的集合,还可以调用listIterator()获取迭代器。列表迭代器提供了向前和向后两个方向访问集合的能力,并且允许修改元素。
12.4.2 增强的for循环
如果不修改集合的内容,也不以反向获取元素,则使用for-each版的for循环遍历集合通常比使用迭代器更方便。
迭代器与增强的for循环之间的区别:
使用迭代器遍历集合时,可以调用Iterator.remove()方法删除集合中元素,使用增强的for循环遍历集合时,不能删除集合中的元素。
可以使用增强的for循环遍历数据,但是数组不支持迭代器。
使用增强的for循环遍历基本类型的数组时,只能使用数组元素,而不能修改数组元素。
12.5 Comparable和Comparator接口
经常需要对集合中的对象进行排序,为了进行排序需要知道两个对象那个“大”那个“小”。这两个接口就是定义了比较两个对象大小的行为。
12.5.1 Comparable接口
该接口位于Comparable位于java.lang包下。实现了该接口的类可以比较大小。该接口只定义了一个方法:
int compareTo(T o)
实现了该接口的类必须实现该方法,定义该类对象之间大小比较的规则。该方法比较调用对象与指定对象的顺序。如果调用对象大于指定的对象o,则返回整数,调用对象小于指定对象o则返回负数,相等则返回0。
提示:
l 目前为止,用到的能够比较大小的引用类型有String、基本类型的包装器类,这些类都实现了Comparable接口。
l 此接口强行对实现它的每个类的对象进行整体排序。此排序被称为该类的自然排序,类的compareTo 方法被称为它的自然比较方法。
l 实现此接口的对象列表(和数组)可以通过Collections.sort (和 Arrays.sort )进行自动排序。
l 实现此接口的对象可以用作有序映射表中的键或有序集合中的元素
12.5.2 Comparator接口
该接口表示比较器,该接口定义了用于比较两个对象大小的方法:
int compare(T o1, T o2)
实现该接口的类必须实现该方法,指明两个对象大小比较的规则。
提示:
该接口为那些没有实现Comparable接口的类提供了“补救”措施。
12.6 Collections类与集合算法
集合框架定义了一些可以应用于集合和映射的算法。这些方法被定义为Collections类中的静态方法。
Collection:所有集合接口的父接口
Collections:类,提供了很多静态方法,对各种集合进行操作
例如:查找最大值、最小值、排序
Collection和Collections区别:
java.util.Collection 是一个集合接口,是所有其他集合接口的父接口,提供了对集合对象进行基本操作的通用接口方法。
java.util.Collections 是一个工具类,包含有各种有关集合操作的静态方法,所以此类不需要实例化。
12.7 遗留的集合类和接口
在集合之前,Java提供了特定的类,存储和管理对象组,例如Dictionary、Vector、Stack和Properties。
早期版本的java.util包没有包含集合框架。反而,定义了几个类和接口,提供存储对象的专门方法。当添加集合时(由J2SE 1.2添加),对几个原始类进行了重新设计,以支持集合接口。因此,从技术上讲它们现在是集合框架的组成部分。
前面介绍的所有现代集合类都不是同步的,但是所有遗留类都是同步的。
12.7.1 Vector类
现在Vector类实现了动态数组,与ArrayList类似,也实现List接口。但有如下区别:
l Vector实现同步,线程安全。ArrayList没有实现线程安全。
l Vector性能比ArrayList低。
l Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%.
12.7.2 Hashtable类
Hashtable类也实现了Map接口,与HashMap类似。
主要区别:
1、Hashtable是同步的、线程安全的,而HashMap不是同步的,没有实现线程安全。
2、HashMap允许将null作为一个条目的key或者value,而Hashtable不允许。当然只能有一个条目的键为null。
3、HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因为contains方法容易让人引起误解。
第13单元 映射
映射(map)是存储键和值间关联(即,键值对)的对象。给定一个键,可以找到其值。键和值都是对象。键必须唯一,但是值可以重复。
13.1 支持映射的接口
接 口 | 描 述 |
Map | 将唯一键映射到值 |
NavigableMap | 扩展SortedMap接口,以处理基于最接近匹配搜索原则的键/值对检索 |
SortedMap | 扩展Map接口,从而以升序保存键 |
Map.Entry | 描述映射中的元素(键/值对) |
interface Map<K, V>
映射围绕两个基本操作:get()和put()。为了将值放入到映射中,使用put(),指定键和值。为了获取值,调用get(),传递键作为变元,值会被返回。
13.2 映射类
常用的映射类:HashMap、LinkedHashMap、TreeMap
13.2.1 HashMap
HashMap实现了Map接口。它使用哈希表存储映射,即使对于比较大的集合,get()和put()的执行时间也保持不变。其声明如下:
class HashMap<K, V>
HashMap类定义了以下构造方法:
HashMap( )
HashMap(int capacity) //指定容量,默认是16
HashMap(int capacity, float fillRatio) //充填比率,0.0-1.0之间,默认0.75
HashMap(Map<? extends K, ? extends V> m)
13.2.2 LinkedHashMap
LinkedHashMap扩展了Hashmap类,该类在内部使用一个链表维护条目添加到映射中的顺序,从而可以按照插入顺序迭代整个映射。
LinkedHashMap定义了以下构造方法:
LinkedHashMap( )
LinkedHashMap(int capacity)
LinkedHashMap(int capacity, float fillRatio)
LinkedHashMap(Map<? extends K, ? extends V> m)
13.2.3 TreeMap
TreeMap类扩展了AbstractMap类,并实现了NavigableMap接口。该类使用树接口存储条目。TreeMap提供了有序存储键/值对的高效手段,并支持快速检索。应当注意,TreeMap确保其元素以键的升序存储。
TreeMap类定义了以下构造方法:
l TreeMap( )
l TreeMap(Comparator<? super K> comp) //比较器对象作为参数
l TreeMap(Map<? extends K, ? extends V> m)
l TreeMap(SortedMap<K, ? extends V> sm)
13.3 映射的遍历
映射不是集合,没有实现Collection接口。因此不能直接遍历映射。
Entry接口是在Map接口的内部定义的,是内部接口。
在Map中存储的元素是键-值对,一个键-值对作为一个整体使用Map.Entry接口表示。Map.Entry定义的方法:
K getKey() | 返回该映射条目的键 |
V getValue() | 返回该映射条目的值 |
V setValue(V v) | 将这个映射条目的值设置为v |
为了遍历映射需要采用变通的方法:获得映射的集合视图。
l 使用entrySet()方法,返回包含映射中所有元素(键-值对)的Set对象
l 使用keySet(),返回包含映射中所有键的Set对象。
l 使用values(),返回包含映射中所有值的Collection对象
对于这三个集合视图都是基于映射的,修改其中的一个集合会影响其他集合。
第14单元 异常Exception
14.1 概念
异常也就是非正常情况,比如使用空的引用、数组下标越界、内存溢出错误等,这些都是意外的情况,背离我们程序本身的意图。
Java提供了异常对象描述这类异常情况。
Java提供了异常机制来进行处理,通过异常机制来处理程序运行期间出现的错误。通过异常机制,可以更好地提升程序的健壮性。
可以将异常通俗的理解为“陷阱”,程序运行出现异常,就是掉到陷阱了。
注意:
Java异常是描述在一块代码中发生的异常情况(也就是错误)的对象。
14.2 异常处理基本流程
第一种情况:
try{
}
catch(…){
}
大部分设计良好的catch子句,应当能够分辨出异常情况,然后继续执行,就好像错误根本没有发生一样。
异常处理过程:
(1)当执行过程中遇到异常时,系统会抛出异常(对象)
(2)catch()块捕获异常并处理。
(3)没有被捕获的异常最终都将由默认处理程序进行处理。默认处理程序会显示一个描述异常的字符串,输出异常发生点的跟踪栈,并终止程序。
提示:
l 由try保护的语句必须使用花括号括起来(即,它们必须位于一个块中)。不能为单条语句使用try。catch块也不能省略花括号。
l try块中发生异常之后直接进入catch块,执行完catch块后也不会再返回到try块。因此,try块中发生异常之后的语句都不会再执行。
l 一个catch块可以捕获多个异常,为了使用多捕获,在catch子句中使用或运算符(|)分割每个异常。每个多捕获参数都被隐式地声明为final。(Java 7)
第二种情况:多条catch子句
try{
}
catch(…){
}
catch(…){
}
提示:
l try及其catch语句构成了一个单元。catch子句的作用域被限制在由之前try语句指定的那些语句。
l 执行了一条catch语句之后,会忽略其他catch语句,并继续执行try/catch块后面的代码。
l 当使用多条catch语句时,要重点记住异常子类必需位于它的所有超类之前。
第三种情况:finally
try(){
}
/*可以有多个catch块*/
finally{
}
提示:
l 不管是否有异常抛出,都会执行finally块。
l 最多只能有一个finally块。
l 对于关闭文件句柄、以及释放资源,finally子句都是很有用的。
第四种情况:
try(){
}
catch(…){
}
catch(…){
}…
finally{
}
提示:
每个try语句至少需要有一个catch子句或一个finally子句。
14.3 嵌套的try语句
一条try语句可以位于另外一条try语句中。
如果内层的try语句没有为特定的异常提供catch处理程序,执行流程就会跳出该try语句,检查外层try语句的catch处理程序,查看异常是否匹配。这个过程会一直继续下去,直到找到了一条匹配的catch语句,或直到检查完所有的try语句。如果没有找到匹配的catch语句,则Java运行时系统会处理该异常。
提示:
当涉及方法调用时,可以能会出现不那么明显的try语句嵌套。
14.4 异常类型
14.4.1 异常继承体系
Error一般指与JVM相关的错误,如系统崩溃、虚拟机错误、动态链接失败等。
Exception有一个重要子类RuntimeException。所有RuntimeException类及其子类的实例被称为运行时(Runtime)异常。对于您编写的程序而言,这种类型的异常是自动定义的,包括除零和无效数组索引这类情况。
运行时异常之外,称为非运行时异常/编译异常。
Throwable重写了(由Object定义的)toString()方法,从而可以返回一个包含异常描述的字符串。可以使用println()语句显示这个描述。
14.4.2 异常分类
Java的异常分为两大类:checked异常和unchecked异常。
l unchecked异常(非检查异常),也称运行时异常(RuntimeException),比如常见的NullPointerException、IndexOutOfBoundsException。对于运行时异常,java编译器不要求必须进行异常捕获处理或者抛出声明,由程序员自行决定。运行异常
l checked异常(检查异常),也称非运行时异常(运行时异常以外的异常就是非运行时异常)。Java认为Checked异常都是可以被处理(修复)的异常,所以Java程序必须显式处理Checked异常,否则无法通过编译。编译异常
含义:checked异常是指程序员比较进行检查,必须进行处理。
对于Checked异常的处理方式有如下两种:
l 当前方法明确知道如何处理该异常,则应该使用try…catch块来捕获该异常,然后在对应的块中修复该异常。
l 当前方法不知道如何处理这种异常,则应该在定义该方法时声明抛出该异常。
14.4.3 常用异常
unchecked异常 运行时异常
要求:看到异常类名,要知道表示哪种错误,知道属于哪类异常
java.lang包中定义的unchecked异常
异 常 | 含 义 |
ArithmeticException | 算术错误,例如除零 |
ArrayIndexOutOfBoundsException | 数组索引越界 |
ArrayStoreException | 使用不兼容的类型为数组元素赋值 |
ClassCastException | 无效的转换 |
EnumConstantNotPresentException | 试图使用未定义的枚举值 |
IllegalArgumentException | 使用非法实参调用方法 |
IllegalMonitorStateException | 非法的监视操作,例如等待未锁定的线程 |
IllegalStateException | 环境或应用程序处于不正确的状态 |
IllegalThreadStateException | 请求的操作与当前线程状态不兼容 |
IndexOutOfBoundsException | 某些类型的索引越界 |
NegativeArraySizeException | 使用负数长度创建数组 |
NullPointerException | 非法使用空引用 |
NumberFromatException | 字符串到数字格式的无效转换 |
SecurityException | 试图违反安全性 |
StringIndexOutOfBounds | 试图在字符串边界之外进行索引 |
TypeNotPresentExcepton | 未找到类型 |
UnsupportedOpetationException | 遇到一个不支持的操作 |
常用的checked异常 编译
IOExeption //输入、输出异常
FileNotFoundException //文件不存在异常
SQLException //SQL异常
java.lang包中定义的Checked异常(了解)
异 常 | 含 义 |
ClassNotFoundException | 未找到类 |
CloneNotSupportedException | 试图复制没有实现Cloneable接口的对象 |
IllegalAccessException | 对类的访问被拒绝 |
InstantiationException | 试图为抽象类或接口创建对象 |
InterruptedException | 一个线程被另一个线程中断 |
NoSuchFieldException | 请求的字段不存在 |
NoSuchMethodException | 请求的方法不存在 |
ReflectiveOperationException | 与反射相关的异常的子类(该异常是由JDK 7新增的) |
14.5 throw
也可以使用throw语句显式地抛出一个异常。
在throw语句之后的执行流会立即停止,所有后续语句都不会执行。然后检查最近的try块,查看是否存在和异常类型相匹配的catch语句。
14.6 throws
如果方法可能引发一个Checked异常,则必须在方法声明中提供throws子句列出了方法可能抛出的异常类型,从而方法的调用者能够保卫它们自己以防备该异常。
throw和throws区别:
l throw抛异常对象,应用在代码块内
l throws声明可能抛出的异常类型,在方法定义后面。
l 如果方法内使用throw抛出Checked异常对象,又没有进行try catch处理,则该方法定义同时需要使用throws指明抛出异常类型
14.7 异常的传播
public class TestException {
static boolean a() throws Exception {
b();
}
static boolean b() throws Exception {
c();
}
static boolean c() throws Exception {
try{
int i = 5/0;
}catch(Exception e){
throw e;
}
return false;
}
public static void main(String [] args){
try{
a();
}catch(Exception e){
System.out.println(e.getMessage());
}
}
}
14.8 自定义异常
自己写类继承自Exception或RuntimeException
自定义异常必须先throw自定义异常的对象,然后才能捕获catch(自定义异常对象)。
Exception定义了四个构造函数。其中的两个支持链式异常,链式异常将在下一节描述。另外两个如下所示:
Exception( )
Exception(String msg)
第15单元 File-IO流
15.1 I/O的概念和java.io包
输入:外部源——>程序
输出:程序——>输出目标(文件)
外部源和输出目标:磁盘文件、网络连接、内存缓存
Java程序通过流执行I/O。流(stream)是一种抽象,它要么产生信息,要么使用信息。流通过Java的I/O系统链接到物理设备。
所有流的行为方式是相同的,尽管与它们链接的设备是不同的。因此,可以为任意类型的设备应用相同的I/O类和方法。这意味着可以将许多不同类型的输入:磁盘文件、键盘或网络socket,抽象为一个输入流。反之,一个输出流可以引用控制台、磁盘文件或网络连接。流是一种处理输入/输出的清晰方式,例如,代码中的所有部分都不需要理解键盘和网络之间的区别。
流是Java在由java.io包定义的类层次中实现的。
System.in 标准输入流对象:接收键盘输入
System.out标准输出流对象:直接输出到控制台
15.2 File类
File对象既可以表示一个文件,也可以表示一个路径/目录。
15.2.1 创建File对象
File对象描述了文件/目录本身的属性。File对象用于获取和操作与磁盘文件/目录关联的信息,例如权限、时间、日期以及目录路径,并且还可以浏览子目录层次。
File(String directoryPath)
File(String directoryPath, String filename)
File(File dirObj, String filename)
File(URI uriObj)
15.2.2 File类的常用方法
l boolean exists() //File对象所表示的文件或目录是否存
l String getName() //获取文件名或路径名
l String getPath() //
l String getAbsolutePath()
l boolean isFile()
l boolean isDirectory()
l boolean createNewFile() 创建新文件,只能创建文件,如果目录不存在,则异常
l boolean mkdir() 只能创建一层目录 make dirctory
l boolean mkdirs() 可以创建多层目录
l boolean delete() 删除文件或文件夹(要求文件夹为空)
l File[ ] listFiles() 返回值类型为
l String[] list();
l boolean canWrite() 判断文件对象表示的文件/目录是否可以写入
l boolean canRead() 判断文件对象表示的文件/目录是否可以读取
l long length() 返回文件的长度
File类中重写了equals方法,比较的文件路径。
15.3 流
Java中的流是个抽象的概念,当程序需要从某个数据源读入数据的时候,就会开启一个数据流,数据源可以是文件、内存或网络等等。相反地,需要写出数据到某个数据源目的地的时候,也会开启一个数据流,这个数据源目的地也可以是文件、内存或网络等等。
15.3.1 流的分类
Java中的流可以从不同的角度进行分类:
l 按照流的方向不同:分为输入流和输出流。
l 按照处理数据单位的不同:分为字节流(8位)和字符流(16位)。
l 按照功能不同:分为节点流和处理流。
节点流:是可以从一个特定的数据源(节点)读写数据的流(例如文件,内存)。就像是一条单一的管子接到水龙头上开始放水。
处理流:是“连接”在已经存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。
15.3.2 IO流结构图
15.3.2 字节流
1、InputStream和OutputStream类
InputStream和OutputStream是所有字节输入流和字节输出流的父类,抽象类。
InputStream抽象类定义的常用方法:
l int read() 读一次 返回一个字节对应的整数
l public int read(byte[] b)throws IOException从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中
l close() //关闭流,使用完流对象后必须关系
OutputStream抽象类定义的常用方法:
l write(int b)
l write(byte[] b)
l close()
2、FileInputStream和FileOutputStream
FileInputStream类是InputStream的子类,用于创建一个可以用于从文件读取内容的InputStream对象。
两个常用的构造方法:
FileInputStream(String filePath)
FileInputStream(File fileObj)
都会抛出FileNotFoundException异常
FileOutputStream类是OutputStream的子类,用于创建能够用于向文件写入字节的OutputStream对象。它的四个构造方法如下所示:
FileOutputStream(String filePath)
FileOutputStream(File fileObj)
FileOutputStream(String filePath, boolean append)
FileOutputStream(File fileObj, boolean append)
它们都可能抛出FileNotFoundException异常。如果append为true,则以追加方式打开文件。
FileOutputStream对象的创建不依赖于已经存在的文件。当创建对象时,FileOutputStream会在打开文件之前创建文件。
3、DataOutputStream和DataInputStream
通过DataOutputStream和DataInputStream类,可以向流中写入基本类型数据或从流中读取基本类型数据。它们分别实现了DataOutput和DataInput接口。
DataOutputStream(OutputStream outputStream) //注意构造方法的参数
DataInputStream(InputStream inputStream)
假如,希望将一个基本类型的数据写入到文件中。
FileOutputStream fs = new FileOutputStream(“D:\\123.txt”);
DataOutputStream ds = new DataOutputStream(fs );
15.3.3 字符流
1、Reader和Writer
Reader和Writer是所有字符输入流和字符输出流的父类,抽象类
Reader类定义的常用方法:
l int read() 读取单个字符。
l int read(char[] cbuf) 将字符读入数组。
l abstract int read(char[] cbuf, int off, int len) 将字符读入数组的某一部分。
Writer类定义的常用方法:
l void write(char[] cbuf) 写入字符数组。
l abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分。
l void write(int c) 写入单个字符。
l void write(String str) 写入字符串。
l void write(String str, int off, int len) 写入字符串的某一部分。
2、FileReader和FileWriter
FileReader类是Reader的子类,用于创建可以用于读取文件内容的Reader对象。
最常用的两个构造方法:
FileReader(String filePath)
FileReader(File fileObj)
每个构造方法都会抛出FileNotFoundException异常
FileWriter类是Writer的子类,用于创建能够用于写入文件的Writer对象。
最常用的四个构造方法:
FileWriter(String filePath)
FileWriter(File fileObj)
FileWriter(String filePath, boolean append)
FileWriter(File fileObj, boolean append)
它们都会抛出IOException异常。如果append为true,则输出被追加到文件的末尾。
FileWriter对象的创建不依赖于已经存在的文件。当创建对象时,FileWriter会在打开文件之前为输出创建文件。
15.4 缓存流
15.4.1 缓存的字节流
对于面向字节的流,缓存流将一个内存缓存附加到I/O系统。这种流允许Java在一定字节上执行多次I/O操作,从而提升性能。缓存的字节流类是BufferedInputStream和BufferedOutputStream。
1、BufferedInputStream类
缓存I/O是很常见的性能优化。Java的BufferedInputStream类允许将任何InputStream对象包装进一个缓存流中以提高性能。
BufferedInputStream类有两个构造方法:
BufferedInputStream(InputStream inputStream) //使用默认缓存大小
BufferedInputStream(InputStream inputStream, int bufSize) //指定缓存大小
2、BufferedOutputStream类
除了增加了flush()方法之外,BufferedOutputStream与所有OutputStream类似,该方法用于确保将数据缓存写入被缓存的流中。
BufferedOutputStream是通过减少系统实际写数据的次数来提高性能的,因此可能需要调用flush()要求立即写入缓存中的所有数据。
Java中用于输出的缓存只是为了提高性能。下面是两个构造函数:
BufferedOutputStream(OutputStream outputStream)
BufferedOutputStream(OutputStream outputStream, int bufSize)
15.4.2 缓存的字符流
1、BufferedReader
BufferedReader通过缓存输入提高性能。它具有两个构造方法:
BufferedReader(Reader inputStream)
BufferedReader(Reader inputStream, int bufSize)
关闭BufferedReader对象也会导致inputStream所指定的底层流被关闭。
提示:
BufferedReader类提供了ReadLine()方法,用于读取一行。
//BufferedReader类,继承类read()方法
2、BufferedWriter
BufferedWriter是缓存输出的Writer。使用BufferedWriter可以通过减少实际向输出设备物理地写入数据的次数来提高性能。
BufferedWriter具有以下这两个构造方法:
BufferedWriter(Writer outputStream)
BufferedWriter(Writer outputStream, int bufSize)
BufferedWriter类也增加了flush()方法。
15.5 序列化与反序列化
序列化(串行化)是将对象的状态写入到字节流的过程。反序列化是从流中回复这些对象的话。
如果希望将程序的状态保存到永久性存储区域(例如文件)这是很有用的。
15.5.1 Serializable接口
只有实现了Serializable接口的类能够通过串行化功能进行保存和恢复。Serializable接口没有定义成员。它简单地用于指示类可以被串行化。如果类是可串行化的,它的所有子类也是可串行化的。
不能保存static变量。
15.5.2 ObjectOutput和ObjectInput接口
ObjectOutput接口定义了writeObject()方法,它用于串行化对象。
ObjectInput接口定义readObject()方法,用于反串行化对象。
15.5.3 ObjectInputStream和ObjectOutputStream类
ObjectOutputStream类扩展了OutputStream类,并实现了ObjectOutput接口。它负责将对象写入到流中。该类的一个构造方法如下所示:
ObjectOutputStream(OutputStream outStream) throws IOException
ObjectInputStream类扩展了InputStream类,并实现了ObjectInput接口。ObjectInputStream负责从流中读取对象。该类的一个构造方法如下所示:
ObjectInputStream(InputStream inStream) throws IOException
形参inStream是从中读取串行化对象的输入流。
关闭ObjectInputStream对象会自动关闭inStream所指定的底层流。
第16单元 反射&线程&网络编程
16.1 线程(Thread)
16.1.1 线程的概念
l 进程:执行的程序。进程是重量级的任务,多任务一种形式。
l 线程:是进程中某个单一顺序的执行流。线程是轻量级的多任务
l 多进程:在操作系统中同时运行多个任务(程序)
l 多线程:在同一应用程序(进程)中有多个执行流同时执行
l 线程的生命周期:一个线程从创建到执行完的整个过程
多线程的作用:通过使用多线程可以提高程序的性能。
Java中,一旦进入main(),就会自动创建一个主线程。为了实现多线程效果,其他的线程需要由程序员创建。
线程的优先级:优先级高的线程会执行,优先级相同的线程会交替执行。
线程的名称:
案例:MainThreadDemo
16.1.2 创建与启动线程
创建线程的两种方式:
l 继承Thread类,重写父类run()方法
l 实现Runnable接口,实现run()方法
两种方式的比较:
l 第一种:继承的方式简单,但是不利于扩展,java只允许单继承
l 第二种:开发比较麻烦,但是容易扩展,接口可以多个实现
注意线程的启动方式:
不是直接调用run()方法,而是通过Thread.Start()方法启动线程,Start()方法会在内部调用相应的run()方法。
run()是执行方法,start()是启动方法
start()启动不一定执行run()方法
案例:NewThread1
案例:NewThread2
16.1.3 线程同步与线程安全
当多个线程并发访问同一个资源对象时,需要考虑线程安全问题。
所谓同步是指多个线程不能同时访问共享对象。
实现线程安全:synchronized
(1)方法加锁
public synchronized void a(){
//在该方法中可以访问共享的对象
}
(2)代码块加锁
public void b(){
synchronized(共享对象){
i++;
}
}
16.1.4 线程的状态
线程可以处于许多不同的状态。可以调用Thread类定义的getState()方法获取线程的当前状态。该方法返回Thread.State类型的值。
值 | 状 态 |
|
|
NEW(被创建) | 线程还没有开始运行 |
RUNNABLE(运行) | 线程要么当前正在执行,要么当它获得CPU的访问权时将会执行 |
BLOCKED(阻塞) | 线程因为正在等待所需要的锁而挂起执行 |
TIMED_WAITING(等待一段时间) | 线程挂起执行一段指定的时间,例如当调用sleep()时就会处于这种状态。当调用wait()或join()的暂停版时也会进入这种状态 |
WAITING(等待) | 线程因为发行了某些动作而挂起执行。例如,因为调用非暂停版的wait()或join()方法而等待时会处于这种状态 |
TERMINATED(终止) | 线程已经完成执行 |
16.2 反射
16.2.1 反射的概念
反射是在运行时获取类或对象的信息的能力。具体的讲:可以通过类名或对象获取该类的相关信息,例如类的属性和方法、类实现接口或继承父类。甚至可以通过反射在运行过程中实例化对象,动态调用方法。
反射是通过Class、Constructor、Field以及Method类实现的。这些类位java.lang.reflect包下。
16.2.2 类加载与Class对象
在程序中使用某个类时,JVM首先需要将该类的class文件加载到内存,并为之创建一个java.lang.Class类型的对象。这个对象就是所谓的Class对象,通过该对象就可以获取该类的相关信息。
可以通过以下三种方式获取类的Class对象:
l 类名.class //大部分时候使用这种方式
l 对象.getClass()
l Class.forName(“类的全名”) //参数必须是类的全名
一旦获得了类的Class对象,就可以通过该Class对象获取类的相关信息了。
16.2.3 获取类的相关信息
通过类的Class对象,可以获取类的属性、方法、构造方法等信息,这些信息分别用Field、Method、Constructor类型的对象表示。
获取Student类中所有的属性、方法、构造方法:
Field[] fields = Student.class.getDeclaredFields(); //getFields() 公有的
Method[] methods = c1.getDeclaredMethods(); //getMethods()
Constructor[] cs = c1.getDeclaredConstructors(); //getConstructor()
获取Student类中公有的属性、方法、构造方法:
Field[] fields = Student.class.getFields();
Method[] methods = c1.getMethods();
Constructor[] cs = c1.getConstructors();
此外还可以获取指定的属性、方法、构造器。
16.1.4 使用反射生成并操作对象
获得了类的Class对象后,还可以通过Class对象调用newInstance()方法创建该类的对象。
Class<Student> c1 = Class.forName(“www.bawie.Student”);
Student s = c1.newInstance(); //这种情况要求Student类提供了无参构造方法
创建对象的另外一种方法:通过Constructor对象。
16.3 网络编程
java语言在java.net包中提供了许多类和接口,用于支持网络编程。
16.3.1 IP地址与端口号
IP地址:唯一标识网络中的一台计算机
端口号:唯一标识计算机中的一个进程(运行的程序)。
InetAddress类:用于封装数字化的IP地址和该地址的域名。
16.3.2 套接字(Socket)
TCP/IP套接字用于在Internet上的主机之间实现可靠的、双向的、连续的、点对点的、基于流的连接。
在Java中有两种类型的TCP套接字。
套接字对象就表示网络通信的端点。
Socket:用于客户端。
SeverSocket:用于服务端,被设计成“监听者”,它等待客户端进行连接。
Socket的两个构造方法:
Socket(String hostName, int port) throws UnknownHostException, IOException | 创建连接到命名的主机和端口的套接字 |
Socket(InetAddress ipAddress, int port) throws IOException | 使用已存在的InetAddress对象和端口创建套接字 |