第一章
1.1
- 1990年美国Sun公司成立Green项目组
- 设计一种新的语言Oak(橡树)
- 后改名为Java
- 1996年发布Java1.0
1.2
- Java语言特点
- 简单、
面向对象、
分布式、
结构中立、
可移植、
解释执行、
健壮、
安全、
高性能、
多线程、
动态
- JDK: Java Development Kits Java开发工具包
- 在Java运行环境中,始终存在着一个系统级的线程,
专门跟踪内存的使用情况,定期检测出不再使用的内存,
并进行自动回收
1.3
- Java SE (Java Standard Edition)
- Java SE中主要包含了:
JRE(Java SE Runtime Environment,Java SE运行环境)、
JDK(JavaDevelopment Kit,Java开发工具包)
和Java核心类库。
- 如果只是运行Java程序,不考虑开发Java程序,
那么只安装JRE就可以了。
在JRE中包含了Java程序运行所需要的Java虚拟机
(JVM,Java VirtualMachine)
- JDK中包含了JRE和一些开发工具,这些工具包括:
编译器、文档生成器和文件打包等工具。
- Java EE是Java Enterprise Edition,主要目的是为
简化企业级系统的开发、部署和管理
- Java EE是以Java SE为基础的,并提供了一套服务、API接口和协议,
能够开发企业级分布式系统、Web应用程序和业务组件等,
其中的包括:JSP、Servlet、EJB、JNI和Java Mail等。
- Java ME是Java Micro Edition,主要是面向消费类电子产品
1.4
- Java应用程序能够跨平台运行,主要是通过Java虚拟机实现的
- 不同软硬件平台Java虚拟机是不同的,
Java虚拟机往下是不同的操作系统和CPU,
使用或开发时需要下载不同的JRE或JDK版本
- Java虚拟机中包含了Java解释器
- Java程序运行过程
- 首先由编译器将Java源程序文件(.java文件)
编译成为字节码文件(.class文件),
然后再由Java虚拟机中的解释器将字节码解释成为机器码去执行。
第二章
- Java开发IDE工具有很多,其中主要有:
Eclipse、IntelliJ IDEA和NetBeans等。
2.1
- JDK下载和安装
- 下载地址是http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
- 下载完成后就可以安装了,双击jdk-8u131-windows-x64.exe文件就可以安装了
- 安装过程中会弹出内容选择对话框,
其中“开发工具”是JDK内容;
“源代码”是安装Java SE源代码文件,如果安装源代码,安装完成后会见src.zip文件就是源代码文件;
公共JRE就是Java运行环境了
- 这里可以不安装,因为JDK文件夹中也会有一个JRE
- 设置环境变量
- 主要包括:
- JAVA_HOME环境变量,指向JDK目录,很多Java工具运行都需要的JAVA_HOME环境变量
- 将JDK\bin目录添加到Path环境变量中,这样在任何路径下都可以执行JDK提供的工具指令
2.2
- Eclipse是著名的跨平台IDE工具,最初Eclipse是IBM支持开发的免费Java开发工具
- 2001年11月贡献给开源社区
- Eclipse界面
- 包资源管理器视图,代码编辑视图,显示大纲等辅助视图,显示问题、控制台等辅助视图
第三章
3.1
- 在Eclipse中通过项目(Project)管理Java类,
因此需要先创建一个Java项目,然后在项目中创建一个Java类。
- Eclipse创建项目步骤是:打开Eclipse,
选择菜单“文件”→“新建”→“Java项目”,打开新建Java项目对话框
- 项目创建完成后,需要创建一个类执行控制台输出操作
- 选择刚刚创建的项目,然后选择菜单“文件”→“新建”→“类”,打开新建类对话框
- 创建方法存根:就是在代码创建这些方法,
本例中需要选中第一个方法(main方法),这个main方法是程序的入口。
- HelloWorld.java
package com.a51work6;
public class HelloWorld{
public static void main(String[] args){
System.out.print("Hello, World");
}
}
- public static void main(String[] args)方法是一个应用程序的入口,
也表明了HelloWorld是一个Java应用程序(Java Application),可以独立运行
- System.out.print(“HelloWorld.”)语句是输出Hello World.字符串到控制台。
3.2
- 注意 一个源程序文件包含多个类时,需要注意如下问题:
- 只能有一个类声明为公有(public)的。
- 文件命名必须与公有类名完全一致,包括字母大小写。
- public static void main(String[] args)只能定义在公有类中。
- 编译程序需要在命令行中使用JDK的javac指令编写
- 编译指令javac中的-d参数是指定类文件生成位置,
-d后面跟的是一个目录的路径,本例中使用“.”点表示当前目录
- java和javac指令都可以带有-classpath(缩写-cp),
它用来指定类路径,即搜索类的路径,类似于操作系统中的path,
路径之间用分号分隔,其中点(.)表示当前路径
3.3
// 包定义
package com.a51work6;
// 类定义
public class HelloWorld{
// 定义静态main方法
public static void main(String[] args){
System.out.print("Hello, World");
}
}
- 包是一个命名空间,可以防止命名冲突问题
- public修饰符是声明类是公有的,class是定义类关键字
- HelloWorld是自定义的类名了,后面跟有“{…}”是类体,
类体中会有成员变量和方法,也会有一些静态变量和方法
- 作为一个Java应用程序,类中必须包含静态main方法
- main方法中除参数名args可以自定义外,其他必须严格遵守如下来两种格式
- public static void main(String args[])
- public static void main(String[] args)
- args参数是程序运行时,通过控制台向应用程序传递字符串参数。
- print(String s) :打印字符串不换行,有多个重载方法,可以打印任何类型数据。
- println(String x) :打印字符串换行,有多个重载方法,可以打印任何类型数据。
- printf(String format, Object… args) :使用指定输出格式,打印任何长度的数据,但不换行
- 修改HelloWorld.java示例代码如下:
public class HelloWorld{
public static void main(String[] args){
System.out.print(args[0]);
System.out.println(args[1]);
System.out.printf("%s", args[2]);
System.out.println();
int i = 123;
System.out.printf("%d\n", i);
double d= 123.456;
System.out.printf("%f\n", d);
System.out.printf("%5.2f", d);
}
}
第四章
- Java的一些基本语法,其中包括
标识符、
关键字、
保留字、
常量、
变量、
表达式
4.1
- 标识符就是变量、常量、方法、枚举、类、接口等由程序员指定的名字
- Java语言中标识符的命名规则如下:
- 区分大小写,
首字符,可以是下划线()或美元符或字母,但不能是数字。
除首字符外其他字符,可以是下划线()、美元符、字母和数字
关键字不能作为标识符。
- Java语言中有50个关键字
- Java中的关键字全部是小写字母。
- abstract assert boolean break byte case catch
char class const continue default do double else
enum extends final finally float for goto if implements
import instanceof int interface long native new package
private protected public return strictfp short static super
switch synchronized this throw throws transient try void
volatile while
- Java中有一些字符序列既不能当作标识符使用,
也不是关键字,也不能在程序中使用,
这些字符序列称为保留字。
Java语言中的保留字只有两个goto和const:
- const:在其他语言中是声明常量关键字
在Java语言中声明常量使用public static final 方式声明。
- 在Java源代码中,有一些字符被用作分隔,称为分隔符。
- 分隔符主要有:分号(;)、左右大括号({})和空白。
- 在Java语言中,以左右大括号({})括起来语句集合称为语句块(block)或复合语句,
语句块中可以有0~n条语句。
- 变量和常量是构成表达式的重要部分,变量所代表的内部是可以被修改的
- 变量包括变量名和变量值,变量的声明格式为:
- 数据类型 变量名 [=初始值];
- 变量作用域是变量的使用范围,在此范围内变量可以使用,
超过作用域,变量内容则被释放,根据作用域不同分为:
成员变量和局部变量
- 示例:
public class HelloWorld{
// 声明int型成员变量
int y;
public static void main(String[] args){
// 声明int型局部变量
int x;
// 声明float型变量并赋值
float f = 4.5f;
// x = 10
System.out.println("x=" + x); // 编译错误,局部变量x未初始化
System.out.println("f=" + f);
if (f<10){
// 声明型局部变量
int m = 5;
}
System.out.println(m); // 编译错误
}
}
- 成员变量是在类体中,而在方法之外,作用域是整个类,
如果没有初始赋值,系统会为它分配一个默认值,
每一种数据类型都有默认值,int类型默认值是0
- 局部变量在使用之前必须显示地初始化
- 常量事实上是那些内容不能被修改的变量,常量与变量类似也需要初始化,
即在声明常量的同时要赋予一个初始值
- 常量一旦初始化就不可以被修改。它的声明格式为:
- final 数据类型变量名 = 初始值;
- final关键字表示最终的,它可以修改很多元素,修饰变量就变成了常量
- 示例:
public class HelloWorld{
// 静态常量,替代保留字const
public static final double PI = 3.14;
// 声明成员常量
final int y = 10;
public static void main(String[] args){
// 声明局部常量
final double x = 3.3;
}
}
- 常量有三种类型:静态常量、成员常量和局部常量
- public static修饰的常量作用域是全局的,
不需要创建对象就可以访问它,在类外部访问形式:HelloWorld. PI
- 成员常量,作用域类似于成员变量,但不能修改。
- 局部常量,作用域类似于局部变量,但不能修改。
第五章
5.1
- 驼峰命名(Camel-Case),又称“骆驼命名法”,是指混合使用大小写字母来命名。
- 驼峰命名又分为小驼峰法和大驼峰法。
小驼峰法就是第一个单词是全部小写,后面的单词首字母大写
如myRoomCount;
大驼峰法是第一个单词的首字母也大写,如ClassRoom。
- 包名:包名是全小写字母,中间可以由点分隔开。
- 作为命名空间,包名应该具有唯一性,推荐采用公司或组织域名的倒置,如com.apple.quicktime.v2。
- 类和接口名:采用大驼峰法,如SplitViewController。
- 文件名:采用大驼峰法,如BlockOperation.java。
- 变量:采用小驼峰法,如studentNumber。
- 常量名:全大写,如果是由多个单词构成,可以用下划线隔开
- 方法名:采用小驼峰法
5.2
- Java中注释的语法有三种:单行注释(//)、多行注释(/…/)和
文档注释(/**…*/)
- 文档注释就是指这种注释内容能够生成API帮助文档,
JDK中javadoc命令能够提取这些注释信息并生成HTML文件。
- 文档注释标签:
- @author:说明类或接口的作者
@deprecated 说明类,接口,成员已经废弃
@param 说明方法参数
@return 说明返回值
@see 参考另一个主题的链接
@exception 说明方法抛出的异常类
@throws 同上
@version 类或接口的版本
- 如果你想生成API帮助文档,可以使用javadoc指令
- javadoc -d apidoc Data.java指令,
-d参数指明要生成文档的目录,apidoc是当前目录下面的apidoc目录
如果不存在javadoc会创建一个apidoc目录
Data.java是当前目录下的Java源文件
- 地标注释
- Eclipse工具支持如下三种地标注释:
- TODO:说明此处有待处理的任务,或代码没有编写完成。
- FIXME:说明此处代码是错误的,需要修正。
- XXX:说明此处代码虽然实现了功能,但是实现的方法有待商榷,希望将来能改进
- 一行代码的长度应尽量不要超过80个字符,如果超过则需断行
第六章
- Java语言的数据类型分为:基本类型和引用类型。
6.1
- 基本类型分为4大类,共8种数据类型
- 整数类型:byte、short、int和long
- 浮点类型:float和double
- 字符类型:char
- 布尔类型:boolean
- 其中整数类型、浮点类型和字符类型都属于数值类型,它们之间可以互相转换。
6.2
- Java中整数类型包括:byte、short、int和long ,它们之间的区别仅仅是宽度和范围的不同
- Java中整数都是有符号,与C不同没有无符号的整数类型。
- 无论你计算机是32位的还是64位的,byte类型整数都是一个字节(8位)
- Java语言的整型类型默认是int类型
- long类型需要在数值后面加l(小写英文字母)或L(大写英文字母)
6.3
- 浮点类型主要用来储存小数数值,也可以用来储存范围较大的整数。
- Java语言的浮点类型默认是double类型
- 如果想要表示float类型,则需要在数值后面加f或F
6.4
- 整数类型和浮点类型都表示数字类型,那么在给这些类型的变量或常量赋值时,
应该如何表示这些数字的值呢?
- 如果为一个整数变量赋值,使用二进制数、八进制数和十六进制数表示
- 二进制数:以 0b 或0B为前缀,注意0是阿拉伯数字,不要误认为是英文字母o
- 八进制数:以0为前缀,注意0是阿拉伯数字。
- 十六进制数:以 0x 或0X为前缀,注意0是阿拉伯数字。
- 进行数学计算时往往会用到指数表示的数值。
- 如果采用十进制表示指数,需要使用大写或小写的e表示幂,e2表示10^2。
6.5
- 字符类型表示单个字符,Java中char声明字符类型,
Java中的字符常量必须用单引号括起来的单个字符
- Java字符采用双字节Unicode编码,占两个字节(16位),
因而可用十六进制(无符号的)编码形式表示,它们的表现形式是\un,
其中n为16位十六进制数,所以’A’字符也可以用Unicode编码’\u0041’表示,
- 字符类型在计算机中保存的是Unicode编码,
双字节Unicode的存储范围在\u0000~\uFFFF,
所以char类型取值范围0~2^16-1。
6.6
- 在Java语言中声明布尔类型的关键字是boolean,它只有两个值:true和false。
- 在C语言中布尔类型是数值类型,它有两个取值:1和0。
而在Java中的布尔类型取值不能用1和0替代,也不属于数值类型,
不能与int等数值类型之间进行数学计算或类型转化。
- 基本数据类型中数值类型之间可以互相转换,布尔类型不能与它们之间进行转换。
6.7
- 数值类型包括了byte、short、char、int、long、float和double
- 数值类型之间的转换有两个方向:自动类型转换和强制类型转换。
- 自动类型转换就是需要类型之间转换是自动的,不需要采取其他手段,
总的原则是小范围数据类型可以自动转换为大范围数据类型
- char类型比较特殊,char自动转换为int、long、float和double,
但byte和short不能自动转换为char,
而且char也不能自动转换为byte或short。
- 在运算中往往是先将数据类型转换为同一类型,然后再进行计算。
- 强制类型转换是在变量或常量之前加上“(目标类型)”实现
- 强制类型转换主要用于大宽度类型转换为小宽度类型情况,如把int转换为byte。
- 当大宽度数值转换为小宽度数值时,大宽度数值的高位被截掉,这样就会导致数据精度丢失。
6.8
- 在Java中除了8种基本数据类型外,其他数据类型全部都是引用(reference)数据类型
- 引用数据类型用了表示复杂数据类型
- 包含:类、接口和数组声明的数据类型。
- Java中的引用数据类型,相当于C等语言中指针(pointer)类型,
引用事实上就是指针,是指向一个对象的内存地址。
- 引用数据类型变量中保持的是指向对象的内存地址
第七章
- 算术运算符、关系运算符、逻辑运算符、位运算符和其他运算符。
7.1
- Java中的算术运算符主要用来组织数值类型数据的算术运算
- 按照参加运算的操作数的不同可以分为一元运算符和二元运算符。
- 算术一元运算符一共有3个,分别是-、++和–。
-
- 二元运算符:+,-,*, ,%
7.2
- 关系运算符:==, !=, >, <, >=, <=
7.3
- 逻辑运算符:!, &, |, &&(短路), ||(短路)
7.4
- 位运算符:>> 有符号右移 >>> 无符号右移
7.5
- 三元运算符:?:
- instanceof: 判断某个对象是否为属于某个类
- new: 对象内存分配运算符
- -> java8 新增,用来声明Lambda表达式
- :: java8 新增,用于Lambda表达式中方法的引用
7.6
- 运算符优先级:算术运算符>位运算符>关系运算符>逻辑运算符>赋值运算符
第八章
8.1
- 程序设计中的控制语句有三种,即顺序、分支和循环语句
- 分支语句:if和switch
- 循环语句:while、do-while和for
- 跳转语句:break、continue、return和throw
- switch语句中“表达式”计算结果只能是int、byte、short和char类型,不能是long更不能其他的类型。
8.2
- 循环语句能够使程序代码重复执行
- for和while循环是在执行循环体之前测试循环条件,
而do-while是在执行循环体之后测试循环条件
- for和while循环可能连一次循环体都未执行,而do-while将至少执行一次循环体。
- 另外Java 5之后推出for-each循环语句,
for-each循环是for循环的变形,
它是专门为集合遍历而设计的,
注意for-each并不是一个关键字。
- 初始化、循环条件以及迭代部分都可以为空语句(但分号不能省略),三者均为空的时候,
相当于一个无限循环
- 在初始化部分和迭代部分,可以使用逗号语句来进行多个操作。
逗号语句是用逗号分隔的语句序列
- 使用for-each循环不必按照for的标准套路编写代码,只需要提供一个集合就可以遍历。
int[] numbers = {42,32,75,53,54,7,10};
System.out.println("--------for------------");
for(int i=0; i
8.3
- break语句
- 作用是强行退出循环体,不再执行循环体中剩余的语句。
- 在循环体中使用break语句有两种方式:带有标签和不带标签
- 不带标签的break语句使程序跳出所在层的循环体,
而带标签的break语句使程序跳出标签指示的循环体。
- 默认情况下,break只会跳出最近的内循环(代码第②行for循环)。
如果要跳出代码第①行的外循环,可以为外循环添加一个标签label1,
注意在定义标签的时候后面跟一个冒号
label1: for(int x=0; x<5; x++){
for(int y=5; y>0; y--){
if(y==x){
break label1;
}
System.out.println("(x, y) = (%d, %d)", x, y);
System.out.println();
}
}
- continue语句
- continue语句用来结束本次循环,跳过循环体中尚未执行的语句,
接着进行终止条件的判断,以决定是否继续循环。
- 在循环体中使用continue语句有两种方式可以带有标签,也可以不带标签。
- 默认情况下,continue只会跳出最近的内循环(代码第②行for循环),
如果要跳出代码第①行的外循环,可以为外循环添加一个标签label1,
然后在第③行的continue语句后面指定这个标签label1,
这样当条件满足执行continue语句时,程序就会跳转出外循环。
第九章
9.1
- 数组
- 大部分计算机语言中数组具有如下三个基本特性:
- 一致性:数组只能保存相同数据类型元素,元素的数据类型可以是任何相同的数据类型
- 有序性:数组中的元素是有序的,通过下标访问
- 不可变性:数组一旦初始化,则长度(数组中元素的个数)不可变。
- 当数组中每个元素都只带有一个下标时,这种数组就是“一维数组”。
- 数组是引用数据类型,引用数据类型在使用之前一定要做两件事情:声明和初始化。
- 数组的声明就宣告这个数组中元素类型,数组的变量名。
- 数组声明完成后,数组的长度还不能确定,
JVM(Java虚拟机)还没有给元素分配内存空间。
- 数组声明语法如下:
- 元素数据类型[] 数组变量名;
元素数据类型 数组变量名[];
- Java更推荐采用第一种声明方式,因为它把“元素数据类型[]”看成是一个整体类型,即数组类型。
- 第二种是C语言数组声明方式。
- 数组初始化
- 声明完成就要对数组进行初始化,
数组初始化的过程就是为数组每一个元素分配内存空间,
并为每一个元素提供初始值。
- 初始化之后数组的长度就确定下来不能再变化了
- 有些计算机语言虽然提供了可变类型数组,
它的长度是可变的,
这种数组本质上是创建了一个新的数组对象,
并非是原始数组的长度发生了变化。
- 数组初始化可以分为静态初始化和动态初始化。
- 静态初始化
- 静态初始化就是将数组的元素放到大括号中,元素之间用逗号(,)分隔
int[] intArray;
intArray = {21,32,43,45};
String[] strArray;
strArray = {"张三","李四"};
//声明同时初始化数组
int intArray[] = {21,23,24,25};
- 动态初始化
- 动态初始化使用new运算符分配指定长度的内存空间
- new 元素数据类型[数组长度] ;
int intArray[];
intArray = new int[4];
intArray[0] = 21;
intArray[1] = 22;
intArray[2] = 25;
intArray[3] = 24;
String[] strArray = new String[2];
strArray[0] = "张三";
strArray[1] = "李四";
- new分配数组内存空间后,数组中的元素内容是什么呢?
答案是数组类型的默认值,不同类型默认值是不同的
- 数据类型默认值
- byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0d
char ‘\u0000’
boolean false
引用 null
- 数组长度是不可变,要想合并两个不同的数组,
不能通过在一个数组的基础上追加另一个数组实现。
需要创建一个新的数组,
新数组长度是两个数组长度之和。
然后再将两个数组的内容导入到新数组中。
9.2
- 当数组中每个元素又可以带有多个下标时,这种数组就是“多维数组”
- Java中声明二维数组需要有两个中括号,具体有三种语法如下:
- 元素数据类型[][] 数组变量名;
- 元素数据类型 数组变量名[][];
- 元素数据类型[] 数组变量名[];
- 二维数组的初始化也可以分为静态初始化和动态初始化。
- 静态初始化
int intArray[][] = {{1,2,3},{11,12,13}};
- 动态初始化
- 动态初始化二维数组语法如下:
- new 元素数据类型[高维数组长度][低维数组长度];
- int[][] intArray = new int[4][3]
- 动态初始化不规则数组比较麻烦,不能使用new int[4][3]语句,
而是先初始化高维数组,然后再分别逐个初始化低维数组。
第十章
- 由字符组成的一串字符序列,称为“字符串”
10.1
- Java中的字符串是由双引号括起来的多个字符
- 单个字符如果用双引号括起来,那它表示的是字符串,而不是字符了
- ""表示空字符串,双引号中没有任何内容,空字符串不是null,
空字符串是分配内存空间,而null是没有分配内存空间。
- Java SE提供了三个字符串类:String、StringBuffer和StringBuilder。
String是不可变字符串,
StringBuffer和StringBuilder是可变字符串。
10.3
- 很多计算机语言都提供了两种字符串,即不可变字符串和可变字符串,
它们区别在于当字符串进行拼接等修改操作时,
不可变字符串会创建新的字符串对象,
而可变字符串不会创建新对象。
- Java中不可变字符串类是String,属于java.lang包,它也是Java非常重要的类。
- 在使用java.lang包中的类时不需要引入(import)该包,因为它是由解释器自动引入的。
- 当然引入java.lang包程序也不会有编译错误。
- 创建String对象可以通过构造方法实现,常用的构造方法:
- String():使用空字符串创建并初始化一个新的String对象。
- String(String original):使用另外一个字符串创建并初始化一个新的 String 对象。
- String(StringBuffer buffer):使用可变字符串对象(StringBuffer)创建并初始化一个新的 String对象。
- String(StringBuilder builder):使用可变字符串对象(StringBuilder)创建并初始化一个新的String 对象。
- String(byte[] bytes):使用平台的默认字符集解码指定的byte数组,通过byte数组创建并初始化一个新的 String 对象。
- String(char[] value):通过字符数组创建并初始化一个新的 String 对象。
- String(char[] value, int offset, int count):通过字符数组的子数组创建并初始化一个新的 String 对象;
offset参数是子数组第一个字符的索引,count参数指定子数组的长度。
- Java中对象是使用new关键字创建,字符串对象也可以使用new关键字创建
- String s9 = “Hello”;
- String s7 = new String(“Hello”);
- 使用new关键字与字符串常量都能获得字符串对象,但它们之间有一些区别。
- ==运算符比较的是两个引用是否指向相同的对象
- Java中的不可变字符串String常量,采用字符串池(String Pool)管理技术,字符串池是一种字符串驻留技术
- 用字符串常量赋值时,会在字符串池中查找"Hello"字符串常量,
如果已经存在把引用赋值给s9,
否则创建"Hello"字符串对象,并放到池中。
- 但此原理并不适用于new所创建的字符串对象,代码运行到第①行后,会创建"Hello"字符串对象,
而它并没有放到字符串池中。
代码第②行又创建了一个新的"Hello"字符串对象,s7和s8是不同的引用,指向不同的对象。
- String字符串虽然是不可变字符串,但也可以进行拼接只是会产生一个新的对象。
- String字符串拼接可以使用+运算符或String的concat(String str)方法
- +运算符优势是可以连接任何类型数据拼接成为字符串,
而concat方法只能拼接String类型字符串。
- Java中所有对象都有一个toString()方法,该方法可以将对象转换为字符串,
拼接过程会调用该对象的toString()方法,将该对象转换为字符串后再进行拼接
10.4
- 在给定的字符串中查找字符或字符串是比较常见的操作
- 在String类中提供了indexOf和lastIndexOf方法用于查找字符或字符串,
返回值是查找的字符或字符串所在的位置,-1表示没有找到
- 这两个方法有多个重载版本:
- int indexOf(int ch):从前往后搜索字符ch,返回第一次找到字符ch所在处的索引。
- int indexOf(int ch, int fromIndex):从指定的索引开始从前往后搜索字符ch,返回第一次找到字符ch所在处的索引。
- int indexOf(String str):从前往后搜索字符串str,返回第一次找到字符串所在处的索引。
- int indexOf(String str, int fromIndex):从指定的索引开始从前往后搜索字符串str,返回第一次找到字符串所在处的索引。
- int lastIndexOf(int ch):从后往前搜索字符ch,返回第一次找到字符ch所在处的索引。
int lastIndexOf(int ch, int fromIndex):从指定的索引开始从后往前搜索字符ch,返回第一次找到字符ch所在处的索引。
int lastIndexOf(String str):从后往前搜索字符串str,返回第一次找到字符串所在处的索引。
int lastIndexOf(String str, int fromIndex):从指定的索引开始从后往前搜索字符串str,返回第一次找到字符串所在处的索引。
- 字符串本质上是字符数组,因此它也有索引,索引从零开始。
- String的charAt(int index)方法,可以返回索引index所在位置的字符。
10.5
- 字符串比较是常见的操作,包括比较相等、比较大小、比较前缀和后缀等。
- 比较相等
- String提供的比较字符串相等的方法:
- boolean equals(Object anObject):比较两个字符串中内容是否相等。
- boolean equalsIgnoreCase(String anotherString):类似equals方法,只是忽略大小写。
- 比较大小
- 有时不仅需要知道是否相等,还要知道大小,String提供的比较大小的方法:
- int compareTo(String anotherString):按字典顺序比较两个字符串。
如果参数字符串等于此字符串,则返回值 0;
如果此字符串小于字符串参数,则返回一个小于 0 的值;
如果此字符串大于字符串参数,则返回一个大于 0 的值。
- int compareToIgnoreCase(String str):类似compareTo,只是忽略大小写。
- 比较前缀和后缀
- boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束。
- boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始。
- trim()方法可以去除字符串前后空白
- toLowerCase()方法可以将此字符串全部转化为小写字符串,
类似的方法还有toLowerCase()方法,可将字符串全部转化为小写字符串。
10.6
- Java中字符串String截取方法主要的方法如下:
- String substring(int beginIndex):从指定索引beginIndex开始截取一直到字符串结束的子字符串。
- String substring(int beginIndex, int endIndex):
从指定索引beginIndex开始截取直到索引endIndex -1处的字符,
注意包括索引为beginIndex处的字符,但不包括索引为endIndex处的字符。
- split(" ")方法,参数是分割字符串,返回值String[]。
10.7
- 可变字符串在追加、删除、修改、插入和拼接等操作不会产生新的对象。
- Java提供了两个可变字符串类StringBuffer和StringBuilder,
中文翻译为“字符串缓冲区”。
- StringBuffer是线程安全的,它的方法是支持线程同步,线程同步会操作串行顺序执行,在单线程环境下会影响效率。
- StringBuilder是StringBuffer单线程版本,Java 5之后发布的,
它不是线程安全的,但它的执行效率很高。
- 线程同步是一个多线程概念,就是当多个线程访问一个方法时,只能由一个优先级别高的线程先访问,
在访问期间会锁定该方法,
其他线程只能等到它访问完成释放锁,才能访问。
- StringBuffer和StringBuilder具有完全相同的API,即构造方法和普通方法等内容一样
- StringBuilder的中构造方法有4个:
- StringBuilder():创建字符串内容是空的StringBuilder对象,初始容量默认为16个字符。
- StringBuilder(CharSequence seq):指定CharSequence字符串创建StringBuilder对象。
CharSequence接口类型,它的实现类有:String、StringBuffer和StringBuilder等,
所以参数seq可以是String、StringBuffer和StringBuilder等类型。
- StringBuilder(int capacity):创建字符串内容是空的StringBuilder对象,初始容量由参数capacity指定的。
- StringBuilder(String str):指定String字符串创建StringBuilder对象。
- 字符串长度和字符串缓冲区容量区别。
- 字符串长度是指在字符串缓冲区中目前所包含字符串长度,通过length()获得
- 符串缓冲区容量是缓冲区中所能容纳的最大字符数,通过capacity()获得。
- 当所容纳的字符超过这个长度时,字符串缓冲区自动扩充容量,但这是以牺牲性能为代价的扩容。
10.8
- 字符串追加
- StringBuilder在提供了很多修改字符串缓冲区的方法,追加、插入、删除和替换等
- 字符串追加方法是append,append有很多重载方法,可以追加任何类型数据,它的返回值还是StringBuilder。
StringBuilder的追加法与StringBuffer完全一样
- StringBuilder insert(int offset, String str):
在字符串缓冲区中索引为offset的字符位置之前插入str,insert有很多重载方法,可以插入任何类型数据。
- StringBuffer delete(int start, int end):在字符串缓冲区中删除子字符串,
要删除的子字符串从指定索引start开始直到索引end - 1处的字符。
start和end两个参数与substring(int beginIndex, intendIndex)方法中的两个参数含义一样。
- StringBuffer replace(int start, int end, String str)字符串缓冲区中用str替换子字符串,
子字符串从指定索引start开始直到索引end - 1处的字符。
start和end同delete(int start, int end)方法。
第十一章
- Java是彻底的、纯粹的面向对象语言,在Java中“一切都是对象”
11.1
- 面向对象的编程思想:按照真实世界客观事物的自然规律进行分析,客观世界中存在什么样的实体,构建的软件系统就存在什么样的实体。
- 作为面向对象的计算机语言——Java,具有定义类和创建对象等面向对象能力。
11.2
- 面向对象思想有三个基本特性:封装性、继承性和多态性。
- 封装性:封装能够使外部访问者不能随意存取对象的内部数据,隐藏了对象的内部细节,只保留有限的对外接口。
- 继承性:在Java语言中一般类称为“父类”,特殊类称为“子类”。
- Java语言是单继承的,即只能有一个父类,但Java可以实现多个接口,可以防止多继承所引起的冲突问题。
- 多态性:多态性是指在父类中成员变量和成员方法被子类继承之后,可以具有不同的状态或表现行为。
11.3
- 类是Java中的一种重要的引用数据类型,是组成Java程序的基本要素。
它封装了一类对象的数据和操作。
- Java语言中一个类的实现包括:类声明和类体。
- 类声明语法格式如下:
[public][abstract|final] class className [extends superclassName] [implements interfaceNameList] {
//类体
}
- class是声明类的关键字,className是自定义的类名;
- class前面的修饰符public、abstract、final用来声明类,它们可以省略
- superclassName为父类名,可以省略,如果省略则该类继承Object类,Object类所有类的根类,所有类都直接或间接继承Object
- interfaceNameList是该类实现的接口列表,可以省略,接口列表中的多个接口之间用逗号分隔。
11.4
- 成员变量
- 声明类体中成员变量语法格式如下:
class className{
[public|protected|private] [static] [final] type variableName; //成员变量
}
- type是成员变量数据类型,variableName是成员变量名
- public、protected和private修饰符用于封装成员变量
- static修饰符用于声明静态变量,所以静态变量也称为“类变量”
- final修饰符用于声明变量,该变量不能被修改。
11.5
- 成员方法
- 声明类体中成员方法语法格式如下:
class className{
[public | protected | private] [static] [final | abstract] [native] [synchronized]
type methodName([paramList]) [throws exceptionList] {
//方法体
}
}
- type是方法返回值数据类型,methodName是方法名
- public、protected和private修饰符用于封装方法
- static修饰符用于声明静态方法,所以静态方法也称为“类方法”。
- final | abstract不能同时修饰方法,final修饰的方法不能在子类中被覆盖;
abstract用来修饰抽象方法,抽象方法必须在子类中被实现。
- native修饰的方法,称为“本地方法”,本地方法调用平台本地代码(如:C或C++编写的代码),不能实现跨平台。
- synchronized修饰的方法是同步的,当多线程方式同步方法时,只能串行地执行,保证是线程安全的。
- throws exceptionList是声明抛出异常列表。
11.6
- 包
- 在程序代码中给类起一个名字是非常重要的,但是有时候会出现非常尴尬的事情,名字会发生冲突
- 在Java中为了防止类、接口、枚举和注释等命名冲突引用了包(package)概念,
本质上命名空间(namespace)。
在包中可以定义一组相关的类型(类、接口、枚举和注释),并为它们提供访问保护和命名空间管理。
- Java中使用package语句定义包,package语句应该放在源文件的第一行,
在每个源文件中只能有一个包定义语句,并且package语句适用于所有类型(类、接口、枚举和注释)的文件
package pkg1[.pkg2[.pkg3...]];
- pkg1~ pkg3都是组成包名一部分,之间用点(.)连接,它们命名应该是合法的标识符,
其次应该遵守Java包命名规范,即全部小写字母。
- 为了能够使用一个包中类型(类、接口、枚举和注释),需要在Java程序中明确引入该包。
使用import语句实现引入包,import语句应位于package语句之后,
所有类的定义之前,可以有0~n条import语句,其语法格式为:
import package1[.package2...].(类型名|*)
- “包名.*”采用通配符,表示引入这个包下所有的类型
- “包名.类型名”形式可以提高程序的可读性。
- 如果在一个源文件中引入两个相同包名+类型名,会发生编译错误。
为避免这个编译错误,可以在没有引入包的类型名前加上包名
- 当前源文件与要使用的类型(类、接口、枚举和注释)在同一个包中,可以不用引入包
11.7
- Java SE提供一些常用包,其中包含了Java开发中常用的基础类。
这些包有:
java.lang、
java.io、
java.net、
java.util、
java.text、
java.awt、
javax.swing。
- java.lang包含中包含了Java语言的核心类,
如Object、Class、String、包装类和Math等,
还有包装类Boolean、Character、Integer、Long、Float和Double。
- 使用java.lang包中的类型,不需要显示使用import语句引入,它是由解释器自动引入。
- java.io包含中提供多种输入/输出流类,
如InputStream、OutputStream、Reader和Writer。
还有文件管理相关类和接口,如File和FileDescriptor类以及FileFilter接口。
- java.net包含进行网络相关的操作的类,如URL、Socket和ServerSocket等。
- java.util包含一些实用工具类和接口,如集合、日期和日历相关类和接口。
- java.text包中提供文本处理、日期式化和数字格式化等相关类和接口。
- java.awt和javax.swing包提供了Java图形用户界面开发所需要的各种类和接口。
- java.awt提供是一些基础类和接口,javax.swing提供了一些高级组件。
11.8
- 方法重载(Overload)
- 出于使用方便等原因,在设计一个类时将具有相似功能的方法起相同的名字
- 这些相同名字的方法之所以能够在一个类中同时存在,是因为它们的方法参数列表,调用时根据参数列表调用相应重载方法。
- 方法重载中参数列表不同的含义是:
参数的个数不同或者是参数类型不同。
另外,返回类型不能用来区分方法重载。
11.9
- 封装性与访问控制
- Java面向对象的封装性是通过对成员变量和方法进行访问控制实现的,
访问控制分为4个等级:私有、默认、保护和公有,
具体规则所示:
- 私有级别的关键字是private,
私有级别的成员变量和方法只能在其所在类的内部自由使用,在其他的类中则不允许直接访问。
私有级别限制性最高。
- 默认级别没有关键字,也就是没有访问修饰符,默认级别的成员变量和方法,
可以在其所在类内部和同一个包的其他类中被直接访问,
但在不同包的类中则不允许直接访问。
- 公有级别的关键字是public,公有级别的成员变量和方法可以在任何场合被直接访问,是最宽松的一种访问控制等级。
- 保护级别的关键字是protected,保护级别在同一包中完全与默认访问级别一样,
但是不同包中子类能够继承父类中的protected变量和方法,
这就是所谓的保护级别,
“保护”就是保护某个类的子类都能继承该类的变量和方法。
- 访问级别顺序是:私有级别→默认级别→保护级别→公有级别。
11.10
- 静态变量和静态方法
- static修饰的成员变量是静态变量
- staitc修饰的方法是静态方法
- 没有static修饰的成员变量是实例变量
- 没有staitc修饰的方法是实例方法
- 调用静态变量或静态方法时,可以通过类名或实例名调用
11.11
67.静态代码块
68. 如果初始化静态变量不是简单常量,需要进行计算才能初始化,
可以使用静态(static)代码块,静态代码块在类第一次加载时执行,并只执行一次
package com.lingu;
public class Account{
double amount = 0.0;
String owner;
static double interestRate;
public static double interestBy(double amt){
return interestRate * amt;
}
//静态代码块
static{
System.out.println("静态代码块被调用");
//初始化静态变量
interestRate = 0.0668;
}
}
- 在静态代码块中可以初始化静态变量
- Account静态代码块是在第一次加载Account类时调用
第十二章
- 类实例化可生成对象,实例方法就是对象方法,实例变量就是对象属性
- 一个对象的生命周期包括三个阶段:创建、使用和销毁
12.1
- 创建对象包括两个步骤:声明和实例化。
- 声明对象与声明普通变量没有区别,语法格式如下:
- type objectName;
- 其中type是引用类型,即类、接口和数组。示例代码如下:
- String name;
- 实例化过程分为两个阶段:为对象分配内存空间和初始化对象,
首先使用new运算符为对象分配内存空间,
然后再调用构造方法初始化对象。
- 一个引用变量没有通过new分配内存空间,这个对象就是空对象,
Java使用关键字null表示空对象。
- 示例代码如下:
String name = null;
name = "Hello World";
- 引用变量默认值是null。当试图调用一个空对象的实例变量或实例方法时,
会抛出空指针异常NullPointerException
- 程序员应该避免调用空对象的成员变量和方法
if(name != null){
int len = name.length();
}
- 产生空对象有两种可能性:第一是程序员自己忘记了实例化,第二是空对象是别人传递过来的。
程序员必须防止第一种情况的发生,应该仔细检查自己的代码,为自己创建的所有对象进行实例化并初始化。
第二种情况需要通过判断对象非null进行避免。
- 构造方法
- 构造方法是类中特殊方法,用来初始化类的实例变量,这个就是构造方法,
它在创建对象(new运算符)之后自动调用。
- Java构造方法的特点:
- 构造方法名必须与类名相同。
构造方法没有任何返回值,包括void。
构造方法只能与new运算符结合使用。
- 默认构造方法
- 有时在类中根本看不到任何的构造方法。
- Java虚拟机
为没有构造方法的类,提供一个无参数的默认构造方法,默认构造方法其方法体内无任何语句
- 默认构造方法的方法体内无任何语句,也就不能够初始化成员变量了,
那么这些成员变量就会使用默认值,成员变量默认值是与数据类型有关
- 构造方法重载
- 在一个类中可以有多个构造方法,它们具体有相同的名字(与类名相同),
参数列表不同,所以它们之间一定是重载关系。
- 构造方法封装
- 构造方法也可以进行封装,访问级别与普通方法一样
- 不允许在外边访问,私有构造方法可以应用于单例设计模式等设计。
- 单例模式是一种常用的软件设计模式,单例模式可以保证系统中一个类只有一个实例。
12.4
- this关键字
- this指向对象本身,一个类可以通过this来获得一个代表它自身的对象变量。
this使用在如下三种情况中:
- 调用实例变量。
调用实例方法。
调用其他构造方法。
//Person.java 文件
package com.a51work6;
import java.util.Date;
public class Person{
//名字
private String name;
private int age;
private Date birthDate;
//三个参数构造方法
public Person(String name, int age, Date d){
this.name = name;
this.age = age;
birthDate = d;
System.out.println(this.toString());
}
public Person(String name, int age){
this(name, age, null);
}
public Person(String name, Date d){
this(name, 30, d);
}
public Person(String name){
this(name, null);
}
@Override
public String toString(){
return "Person [name=" + name + ",age=" + age + ",birthDate=" + birthDate + "]";
}
}
- 使用this调用其他构造方法时,this语句一定是该构造方法的第一条语句。
## 12.5
32. 对象销毁
33. 对象不再使用时应该销毁。C++语言对象是通过delete语句手动释放,
Java语言对象是由垃圾回收器(Garbage Collection)收集然后释放,程序员不用关心释放的细节。
34. 垃圾回收器(Garbage Collection)的工作原理是:
当一个对象的引用不存在时,认为该对象不再需要,
垃圾回收器自动扫描对象的动态内存区,把没有引用的对象作为垃圾收集起来并释放。
第十三章
- 类的继承性是面向对象语言的基本特性,多态性的前提是继承性
- Java支持继承性和多态性
13.1
- Java中的继承
package com.lingu;
import java.util.Date;
public class Student extends Person{
private String school;
}
- Student类继承了Person类中的所有成员变量和方法,
从上述代码可以见继承使用的关键字是extends,extends后面的Person是父类。
- 如果在类的声明中没有使用extends关键字指明其父类,
则默认父类为Object类,
java.lang.Object类是Java的根类,所有Java类包括数组都直接或间接继承了Object类,
在Object类中定义了一些有关面向对象机制的基本方法,
如equals()、toString()和finalize()等方法。
- 。在Java中,类的继承只能是单继承,而多重继承可以通过实现多个接口实现。
也就是说,在Java中,一个类只能继承一个父类,但是可以实现多个接口。
- 面向对象分析与设计(OOAD)时,会用到UML图,其中类图非常重要,用来描述系统静态结构。
- 类用矩形表示,一般分为上、中、下三个部分,上部分是类名,中部分是成员变量,下部分是成员方法。
- 实线+空心箭头表示继承关系,箭头指向父类,箭头末端是子类
- 虚线+空心箭头表示实现关系,箭头指向接口,箭头末端是实现类。
- UML是Unified Modeling Language的缩写,即统一标准建模语言。–图示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GMicCKsl-1665971153348)(image/01.png)]
13.2
- 调用父类构造方法
- 当子类实例化时,不仅需要初始化子类成员变量,也需要初始化父类成员变量,
初始化父类成员变量需要调用父类构造方法,
子类使用super关键字调用父类构造方法。
- super语句必须位于子类构造方法的第一行。
public class Student extends Person{
private String school;
public Student(String name, int age, Date d, String school){
super(name, age, d);
this.school = school;
}
}
- 构造方法由于没有super语句,编译器会试图调用父类默认构造方法(无参数构造方法),
但是父类Person并没有默认构造方法,因此会发生编译错误。
解决这个编译错误有三种办法:
- 在父类Person中添加默认构造方法,子类Student会隐式调用父类的默认构造方法。
在子类Studen构造方法添加super语句,显式调用父类构造方法,super语句必须是第一条语句。
在子类Studen构造方法添加this语句,显式调用当前对象其他构造方法,this语句必须是第一条语句。
13.3
- 成员变量隐藏和方法覆盖
- 子类继承父类后,在子类中有可能声明了与父类一样的成员变量或方法,那么会出现什么情况呢?
- 子类成员变量与父类一样,会屏蔽父类中的成员变量,称为“成员变量隐藏”
package com.lingu;
class PersonClass{
int x = 10;
}
class SubClass extends PersonClass{
int x = 20;
public void print(){
System.out.println("x = " + x);
System.out.println("super = " + super.x);
}
}
--------------------------------------------------
package.com.lin;
public class HelloWorld{
public static void main(String[] args){
SubClass pObj = new SubClass();
pObj.print();
}
}
- 方法的覆盖(Override)
- 如果子类方法完全与父类方法相同,
即:相同的方法名、相同的参数列表和相同的返回值,只是方法体不同,
这称为子类覆盖(Override)父类方法。
- 在声明方法时添加@Override注解,@Override注解不是方法覆盖必须的,
它只是锦上添花,但添加@Override注解有两个好处:
- 提高程序的可读性。
编译器检查@Override注解的方法在父类中是否存在,如果不存在则报错。
- 方法覆盖时应遵循的原则:
- 覆盖后的方法不能比原方法有更严格的访问控制(可以相同)。
例如将代码第②行访问控制public修改private,那么会发生编译错误,因为父类原方法是protected。
- 覆盖后的方法不能比原方法产生更多的异常。
13.4
- 发生多态要有三个前提条件:
- 继承。多态发生一定要子类和父类之间。
覆盖。子类覆盖了父类的方法。
声明的变量类型是父类类型,但实例则指向子类实例。
- 变量是父类类型,指向子类实例,发生多态
- 多态发生时,Java虚拟机运行时根据引用变量指向的实例调用它的方法,而不是根据引用变量的类型调用。
- 引用类型检查
- 有时候需要在运行时判断一个对象是否属于某个引用类型,这时可以使用instanceof运算符
- instanceof运算符语法格式如下:
- obj instanceof type
- 其中obj是一个对象,type是引用类型,如果obj对象是type引用类型实例则返回true,否则false。
- 引用类型转换
- 引用类型可以进行转换,但并不是所有的引用类型都能互相转换,
只有属于同一棵继承层次树中的引用类型才可以转换。
- 类型转换有两个方向:
将父类引用类型变量转换为子类类型,这种转换称为向下转型(downcast);
将子类引用类型变量转换为父类类型,这种转换称为向上转型(upcast)。
向下转型需要强制转换,而向上转型是自动的。
13.5
- final关键字能修饰变量、方法和类。
- final修饰的变量即成为常量,只能赋值一次,但是final所修饰局部变量和成员变量有所不同。
- final修饰的局部变量必须使用之前被赋值一次才能使用。
- final修饰的成员变量在声明时没有赋值的叫“空白final变量”。
空白final变量必须在构造方法或静态代码块中初始化。会
package com.ling;
class FinalDemo{
void doSomething(){
final int e;
e = 100;
System.out.print(e);
final int f = 200;
}
// 实例常量
final int a = 5;
final int b; //空白final常量
//静态常量
final static int c = 12;
final static int d; // 空白final常量
// 静态代码块
static{
// 初始化静态变量
d = 32;
}
// 构造方法
FinalDemo(){
b = 3; // 初始化实例变量
// b = 4; 第二次赋值,会发生编译错误
}
}
- final修饰的类不能被继承。
- final修饰的方法不能被子类覆盖。
第十四章
- 抽象类与接口
14.1
- Java语言提供了两种类:一种是具体类;另一种是抽象子类
- 在Java中具有抽象方法的类称为“抽象类”
- 在UML类图抽象类和抽象方法字体是斜体的
- 在Java中抽象类和抽象方法的修饰符是abstract,声明抽象类Figure示例代码如下:
package com.lin;
public abstract class Figure{
public abstract void onDraw();
}
- 注意抽象方法中只有方法的声明,没有方法的实现,即没有大括号({})部分。
- 如果一个方法被声明为抽象的,那么这个类也必须声明为抽象的。
而一个抽象类中,可以有0n个抽象方法,以及0n具体方法。
- 抽象类不能被实例化,只有具体类才能被实例化。
- 比抽象类更加抽象的是接口,在接口中所有的方法都是抽象的。
- Java 8之后接口中新增加了默认方法,因此“接口中所有的方法都是抽象的”这个提法在Java8之后是有待商榷。
14.2
- 接口可以有成员变量
- 在UML类图中接口的图标是“I”。类的图标是“C”
- 在Java中接口的声明使用的关键字是interface
package com.lin;
public interface Figure{
//接口中静态成员变量
String name = "几何图形"; //省略public static final
void onDraw(); // 省略public
}
- 声明接口使用interface关键字,interface前面的修饰符是public或省略。
public是公有访问级别,可以在任何地方访问。
省略是默认访问级别,只能在当前包中访问。
- 某个类实现接口时,要在声明时使用implements关键字,
当实现多个接口之间用逗号(,)分隔。
实现接口时要实现接口中声明的所有方法。
- 接口与抽象类一样都不能被实例化。
- 在Java中只允许继承一个类,但可实现多个接口。
通过实现多个接口方式满足多继承的设计需求。
- Java语言中允许接口和接口之间继承。由于接口中的方法都是抽象方法,
所以继承之后也不需要做什么,因此接口之间的继承要比类之间的继承简单的多。
- 在Java 8之前,尽管Java语言中接口已经非常优秀了,但相比其他面向对象的语言而言Java接口存在如下不足之处:
- 不能可选实现方法,接口的方法全部是抽象的,
实现接口时必须全部实现接口中方法,哪怕是有些方法并不需要,也必须实现。
- 没有静态方法。
- 针对这些问题,Java 8在接口中提供了声明默认方法和静态方法的能力。
package com.lin;
public interface InterfaceA{
void methodA();
String methodB();
default int methodC(){
return 0;
}
default String methodD(){
return "这是默认方法...";
}
static double methodF(){
return 0.0;
}
}
- 静态方法不需要实现,实现类中不能拥有接口中的静态方法。
14.3
-
抽象类与接口区别
-
归纳抽象类与接口区别如下:
-
接口支持多继承,而抽象类(包括具体类)只能继承一个父类。
接口中不能有实例成员变量,接口所声明的成员变量全部是静态常量,即便是变量不加public static final修饰符也是静态常量。
抽象类与普通类一样各种形式的成员变量都可以声明。
接口中没有包含构造方法,由于没有实例成员变量,也就不需要构造方法了。
抽象类中可以有实例成员变量,也需要构造方法。
抽象类中可以声明抽象方法和具体方法。
Java 8之前接口中只有抽象方法,而Java 8之后接口中也可以声明具体方法,具体方法通过声明默认方法实现。
-
在多数情况下接口不能替代抽象类,例如当需要维护一个对象的信息和状态时只能使用抽象类,
而接口不行,因为维护一个对象的信息和状态需要存储在实例成员变量中,而接口中不能声明实例成员变量。
第十五章
- Java 5之前没有提供枚举类型,尽管可以通过声明静态常量(final static变量)替代枚举,
但是仍然很多Java程序员期待能有类似其他语言中的枚举类型。
- Java 5之后提供了枚举类型,Java枚举类型本质上是一种继承java.lang.Enum类,
是引用数据类型,因此也称为“枚举类”。
15.1
- 在C和Objective-C等其他语言中,枚举用来管理一组相关常量的集合,
使用枚举可以提高程序的可读性,使代码更清晰且更易于维护。
- 在Java 5之前没有提供枚举类型,可以通过声明静态常量(final static变量)替代枚举常量,
例如想声明一组常量表示一周中的5个工作日,那么Java 5之前实现代码如下:
package com.lin;
public interface WeekDays{
int MONDAY = 0;
int TUESDAY = 1;
int WEDNESDAY = 2;
int THURSDAY = 3;
int FRIDAY = 4;
}
- Java中枚举类型的作用已经不仅仅是定义一组常量提高程序的可读性了,还具有如下特性:
- Java枚举类型是一种类,是引用类型,具有了面向对象特性,可以添加方法和成员变量等。
- Java枚举类型父类是java.lang.Enum,不需要显式声明。
- Java枚举类型可以实现接口,与类实现接口类似。
- Java枚举类型不能被继承,不存在子类。
15.2
- Java中是使用enum关键词声明枚举类,具体定义放在一对大括号内,枚举的语法格式如下:
[public] enum 枚举名{
枚举常量列表
}
- enum前面的修饰符是[public]表示public或省略。public是公有访问级别,可以在任何地方访问。
省略是默认访问级别,只能在当前包中访问。
- switch表达式类型和case常量类型只能是int、byte、short和char类型,而Java 5之后还可以是枚举类型。
- 枚举类可以像类一样包含成员变量和成员方法,成员变量可以是实例变量也可以是静态变量,
成员方法可以是实例方法,也可以是静态方法,但不能是抽象方法。
- 添加的其他成员的枚举类需要注意,“枚举常量列表”语句必须是枚举类中的第一行代码。
- 枚举类构造方法
- 在枚举类中也是通过构造方法初始化成员变量的。
package com.lin;
public enum WeekDays{
MONDAY("星期一", 0), THUSDAY("星期二", 1);
private String name;
private int index;
private static int staticVar = 100;
private WeekDays(String name, int index){
this.name = name;
this.index = index;
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append(name);
sb.append("-");
sb.append(index);
return sb.toString();
}
private String getInfo(){
return super.toString();
}
public static int getStaticVar(){
return staticVar;
}
}
- 枚举类的中的构造方法只能是私有访问级别,构造方法可以省略private关键字,但它仍然是私有的构造方法。
- 私有构造方法经常用于单例设计模式和工厂设计模式,
使得不允许在类的外边直接调用构造方法创建对象。枚举类实现类似于工厂设计模式。
15.3
- 所有枚举类都继承java.lang.Enum类,Enum中定义了一些枚举中常用的方法:
- int ordinal():返回枚举常量的顺序。这个顺序根据枚举常量声明的顺序而定,顺序从零开始。
- 枚举类型[] values():静态方法,返回一个包含全部枚举常量的数组。
- 枚举类型 valueOf(String str):静态方法,str是枚举常量对应的字符串,返回一个包含枚举类型实例。
- 在Java类引用类型进行比较时,有两种比较方法==和equals,
==比较的是两个引用是否指向同一个对象,
equals是比较对象内容是否相同。
第十六章
- 在Java SE中提供了众多丰富类和接口,其中很多类前面已经使用过了,如String、StringBuiler和StringBuffer等。
16.1
- Java根类:Object
- 第一个应该介绍的常用类就是java.lang.Object类,它是Java所有类的根类,
Java所有类都直接或间接继承自Object类,它是所有类的“祖先”。
- Object类属于java.lang包中的类型,不需要显示使用import语句引入,它是由解释器自动引入。
- Object类有很多方法,常用的几个方法:
- String toString():返回该对象的字符串表示。
- boolean equals(Object obj):指示其他某个对象是否与此对象“相等”。
- 为了日志输出等处理方便,所有的对象都可以以文本方式表示,
需要在该对象所在类中覆盖toString()方法。
如果没有覆盖toString()方法,默认的字符串是“类名@对象的十六进制哈希码”。
- 曾经介绍过有两种比较方法:==运算符和equals()方法,
==运算符是比较两个引用变量是否指向同一个实例,
equals()方法是比较两个对象的内容是否相等,
通常字符串的比较,只是关心的内容是否相等。
10.事实上equals()方法是继承自Object的,所有对象都可以通过equals()方法比较,问题是比较的规则是什么,
例如两个人(Person对象)相等是指什么?是名字?是年龄?问题的关键是需要指定相等的规则,
就是要指定比较的是哪些属性相等,所以为了比较两个Person对象相等,
则需要覆盖equals()方法,在该方法中指定比较规则。
16.2
-
在Java中8种基本数据类型不属于类,不具备“对象”的特征,没有成员变量和方法,不方便进行面向对象的操作。
-
Java提供包装类(Wrapper Class)来将基本数据类型包装成类,
每个Java基本数据类型在java.lang包中都有一个相应的包装类,每个包装类对象封装一个基本数据类型数值。
-
基本数据类型与包装类对应关系
-
基本数据类型 包装类
boolean Boolean
byte Byte
char Character
short Short
int Integer
long Long
float Float
double Double
-
包装类都是final的,不能被继承。
包装类都是不可变类,类似于String类,一旦创建了对象,其内容就不可以修改。
-
装类还可以分成三种不同类别:数值包装类(Byte、Short、Integer、Long、Float和Double)、Character和Boolean
-
数值包装类(Byte、Short、Integer、Long、Float和Double)都有一些相同特点。
-
构造方法类似
Integer(int value):通过指定一个数值构造Integer对象。
Integer(String s):通过指定一个字符串s构造对象,s是十进制字符串表示的数值。
-
共同的父类, 这6个数值包装类有一个共同的父类——Number,Number是一个抽象类
-
AtomicInteger、AtomicLong、BigDecimal和BigInteger,其中BigDecimal和BigInteger后面还会详细介绍
-
要求它的子类必须实现如下6个方法:
-
byte byteValue():将当前包装的对象转换为byte类型的数值。
-
double doubleValue():将当前包装的对象转换为double类型的数值。
-
float floatValue():将当前包装的对象转换为float类型的数值。
-
int intValue():将当前包装的对象转换为int类型的数值
-
long longValue():将当前包装的对象转换为long类型的数值。
-
short shortValue():将当前包装的对象转换为short类型的数值。
-
每一个数值包装类都有int compareTo(数值包装类对象)方法,可以进行包装对象的比较。
方法返回值是int,如果返回值是0,则相等;
如果返回值小于0,则此对象小于参数对象;
如果返回值大于0,则此对象大于参数对象。
-
每一个数值包装类都提供一些静态parseXXX()方法将字符串转换为对应的基本数据类型
-
static int parseInt(String s):将字符串s转换有符号的十进制整数。
-
static int parseInt(String s, int radix):将字符串s转换有符号的整数,
radix是指定基数,基数用来指定进制。
注意这种指定基数的方法在浮点数包装类(Double和Float)中没有的。
-
每一个数值包装类都提供一些静态toString()方法实现将基本数据类型数值转换为字符串
-
static String toString(int i):将该整数i转换为有符号的十进制表示的字符串。
-
static String toString(int i, int radix):将该整数i转换为有符号的特定进制表示的字符串,radix是基数可以指定进制。
注意这种指定基数的方法在浮点数包装类(Double和Float)中没有的。
-
Character类是char类型的包装类。
-
Character(char value):构造方法,通过char值创建一个新的Character对象。
-
char charValue():返回此Character对象的值。
-
int compareTo(Character anotherCharacter):
方法返回值是int,如果返回值是0,则相等;
如果返回值小于0,则此对象小于参数对象;
如果返回值大于0,则此对象大于参数对象。
-
Boolean类
-
Boolean类有两个构造方法,构造方法定义如下:
-
Boolean(boolean value):通过一个boolean值创建Boolean对象。
-
Boolean(String s):通过字符串创建Boolean对象。
s不能为null,s如果是忽略大小写"true"则转换为true对象,其他字符串都转换为false对象。
-
Boolean类有int compareTo(Boolean包装类对象)方法,可以进行包装对象的比较。
方法返回值是int,如果返回值是0,则相等;
如果返回值小于0,则此对象小于参数对象;
如果返回值大于0,则此对象大于参数对象。
-
Boolean包装类都提供静态parseBoolean()方法实现将字符串转换为对应的boolean类型,
-
自动装箱/拆箱
-
Java 5还提供了相反功能,自动装箱( autoboxing ),
装箱能够自动地将基本数据类型的数值自动转换为包装类对象,而不需要使用构造方法。
16.3
- Math类
- Java语言是彻底地面向对象语言,哪怕是进行数学运算也封装到一个类中的,
这个类是java.lang.Math,Math类是final的不能被继承。
- Math类中包含用于进行基本数学运算的方法,如指数、对数、平方根和三角函数等
- 舍入方法
- static double ceil(double a):返回大于或等于a最小整数。
static double floor(double a):返回小于或等于a最大整数。
static int round(float a):四舍五入方法。
- 最大值和最小值
- static int min(int a, int b):取两个int整数中较小的一个整数。
static int min(long a, long b):取两个long整数中较小的一个整数。
static int min(float a, float b):取两个float浮点数中较小的一个浮点数。
static int min(double a, double b):取两个double浮点数中较小的一个浮点数。
- 绝对值
- static int abs(int a):取int整数a的绝对值。
static long abs(long a):取long整数a的绝对值。
static float abs(float a):取float浮点数a的绝对值。
static double abs(double a):取double浮点数a的绝对值。
- 三角函数
- static double sin(double a):返回角的三角正弦。
static double cos(double a):返回角的三角余弦。
static double tan(double a):返回角的三角正切。
static double asin(double a):返回一个值的反正弦。
static double acos(double a):返回一个值的反余弦。
static double atan(double a):返回一个值的反正切。
static double toDegrees(double angrad):将弧度转换为角度。
static double toRadians(double angdeg):将角度转换为弧度。
- 对数运算
- static double log(double a),返回a的自然对数
- 平方根:
static double sqrt(double a),返回a的正平方根。
- 幂运算:
static double pow(double a, double b),返回第一个参数的第二个参数次幂的值。
- 计算随机值:
static double random(),返回大于等于 0.0 且小于 1.0随机数
- 常量
圆周率PI
自然对数的底数E。
16.4
- 大数值
- 对货币等大值数据进行计算时,int、long、float和double等基本数据类型已经在精度方面不能满足需求了。
- 为此Java提高了两个大数值类:BigInteger和BigDecimal,这里两个类都继承自Number抽象类。
- java.math.BigInteger是不可变的任意精度的大整数。
BigInteger构造方法有很多,其中字符串参数的构造方法有两个:
- BigInteger(String val):将十进制字符串val转换为BigInteger对象。
- BigInteger(String val, int radix):按照指定基数radix将字符串val转换为BigInteger对象。
- BigInteger提供多种方法,下面列举几个常用的方法:
- int compareTo(BigInteger val):将当前对象与参数val进行比较,
方法返回值是int,如果返回值是0,则相等;如果返回值小于0,则此对象小于参数对象;如果返回值大于0,则此对象大于参数对象。
BigInteger add(BigInteger val):加运算,当前对象数值加参数val。
BigInteger subtract(BigInteger val):减运算,当前对象数值减参数val。
BigInteger multiply(BigInteger val):乘运算,当前对象数值乘参数val。
BigInteger divide(BigInteger val):除运算,当前对象数值除以参数val。
- java.math.BigDecimal是不可变的任意精度的有符号十进制数
- BigDecimal构造方法有很多:
- BigDecimal(BigInteger val):将BigInteger对象val转换为BigDecimal对象。
BigDecimal(double val):将double转换为BigDecimal对象,参数val是double类型的二进制浮点值准确的十进制表示形式。
BigDecimal(int val):将int转换为BigDecimal对象。
BigDecimal(long val):将long转换为BigDecimal对象。
BigDecimal(String val):将字符串表示数值形式转换为BigDecimal对象。
16.5
-
日期时间相关类
-
Java 8之前日期类是java.util.Date,Date类比较古老
-
Java 8之前与日期时间相关类还有DateFormat、Calendar和TimeZone,
DateFormat用于日期格式化,Calendar日历类,TimeZone是时区类。
-
在Java SE核心类中有两个Date,
分别是java.util.Date和java.sql.Date。
java.util.Date就是本节要介绍的日期时间类,
而java.sql.Date是JDBC中日期字段类型。
-
Date类构造方法:
-
Date():用当前时间创建Date对象,精确到毫秒。
Date(long date):指定标准基准时间以来的毫秒数创建Date对象。标准基准时间是格林威治时间1970年1月1日00:00:00。
-
Date类的普通方法:
-
boolean after(Date when):测试此日期是否在when日期之后。
boolean before(Date when):测试此日期是否在when日期之前。
int compareTo(Date anotherDate):比较两个日期的顺序。如果参数日期等于此日期,则返回值0;
如果此日期在参数日期之前,则返回小于0的值;
如果此日期在参数日期之后,则返回大于0的值。
long getTime():返回自1970年1月1日00:00:00以来此Date对象表示的毫秒数。
void setTime(long time):用毫秒数time设置日期对象,time是自1970年1月1日00:00:00以来此Date对象表示的毫秒数。
-
日期格式化和解析
-
日期格式化类是java.text.DateFormat,
DateFormat是抽象类,它的常用具体类是java.text.SimpleDateFormat。
-
DateFormat中提供日期格式化和日期解析方法,具体方法说明如下:
-
String format(Date date):将一个Date格式化为日期/时间字符串。
Date parse(String source):从给定字符串的开始解析文本,以生成一个日期对象。如果解析失败则抛出ParseException。
-
具体类是SimpleDateFormat构造方法如下:
SimpleDateFormat():用默认的模式和默认语言环境的日期格式符号构造SimpleDateFormat
SimpleDateFormat(String pattern):用给定的模式和默认语言环境的日期格式符号构造
pattern参数是日期和时间格式模式,
-
y 年
M 年中的月份
D 年终的天数
d 月份中的天数
H 一天中的小时数(0-23)
h AM/PM中的小时数
a AM/PM标记
m 小时中的分钟数
s 分钟中的秒数
S 毫秒数
Z 时区
-
Calendar类
-
java.util.Calendar类
-
Calendar是一个抽象类,不能实例化,但是通过静态工厂方法getInstance()获得Calendar实例
-
Calendar类的主要方法
-
static Calendar getInstance():使用默认时区和语言环境获得一个日历。
void set(int field, int value):将给定的日历字段设置为给定值。
void set(int year,int month,int date):设置日历字段YEAR、MONTH和DAY_OF_MONTH的值
Date getTime():返回一个表示此Calendar时间值(从1970年1月1日00:00:00至现在的毫秒数)的Date对象。
boolean after(Object when):判断此Calendar表示的时间是否在指定时间之后,返回判断结果
boolean before(Object when):判断此Calendar表示的时间是否在指定时间之前,返回判断结果
int compareTo(Calendar anotherCalendar):比较两个Calendar对象表示的时间值
-
calendar.clear()语句是重新初始化日历对象
-
注意在设置“月”时,应该是“月份-1”,因为日历中的月份中第一个月是0,第二个月是1,
依次类推那么本例中设置8月份,则实际参数应该为7
16.6
- Java 8新日期时间相关类
- Java 8之后提供了新的日期时间相关类、接口和枚举,这些类型内容非常多,令人生畏
- Java 8之后提供了新的日期时间类有三个:
LocalDate、LocalTime和LocalDateTime,
它们都位于java.time包中,
LocalDate表示一个不可变的日期对象;
LocalTime表示一个不可变的时间对象;
LocalDateTime表示一个不可变的日期和时间
- 这三个类有类似的方法,首先先看看创建日期时间对象相关方法,
这三个类并没有提供公有的构造方法,
创建它们对象可以使用静态工厂方法,
主要有now()和of()方法。
- static LocalDate now():LocalDate静态工厂方法,该方法使用默认时区获得当前日期,返回LocalDate对象。
static LocalTime now():LocalTime静态工厂方法,该方法使用默认时区获得当前时间,返回LocalTime对象。
static LocalDateTime now():LocalDateTime静态工厂方法,该方法使用默认时区获得当前日期时间,返回LocalDateTime对象。
- static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second):
按照指定的年、月、日、小时、分钟和秒获得LocalDateTime实例,将纳秒设置为零。
static LocalTime of(int hour, int minute, int second):按照指定的小时、分钟和秒获取一个LocalTime实例。
static LocalDate of(int year, int month, int dayOfMonth):按照指定的年、月和日获得一个LocalDate实例,
日期中年、月和日必须有效,否则将抛出异常。
- Java 8提供的日期格式化类是java.time.format.DateTimeFormatter
- 日期格式化方法是format,这三个类每一个都有String format(DateTimeFormatter formatter),
参数formatter是DateTimeFormatter类型。
- 日期解析方法是parse,这三个类每一个都有两个版本的parse方法
- static LocalDateTime parse(CharSequence text):使用默认格式,从一个文本字符串获取一个LocalDateTime实例,
如2007-12-03T10:15:30。
static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter):使用指定格式化,
从文本字符串获取LocalDateTime实例。
static LocalDate parse(CharSequence text):使用默认格式,从一个文本字符串获取一个LocalDate实例,如2007-12-03。
static LocalDate parse(CharSequence text, DateTimeFormatter formatter):使用指定格式化,
从文本字符串获取LocalDate实例。
static LocalTime parse(CharSequence text):使用默认格式,从一个文本字符串获取一个LocalTime实例。
static LocalTime parse(CharSequence text, DateTimeFormatter formatter):使用指定的格式化,
从文本字符串获取LocalTime实例。
第17章
- Java中还有一种内部类技术,简单说就是在一个类的内部定义一个类。
- 事实上Java应用程序开发过程中内部类使用的地方不是很多,一般在图形用户界面开发中用于事件处理。
- 内部类技术虽然使程序结构变得紧凑,但是却在一定程度上破坏了Java面向对象思想。
17.1
- 内部类的作用如下:
- 封装。将不想公开的实现细节封装到一个内部类中,内部类可以声明为私有的,只能在所在外部类中访问。
提供命名空间。静态内部类和外部类能够提供有别于包的命名空间
便于访问外部类成员。内部类能够很方便访问所在外部类的成员,包括私有成员也能访问。
- 内部类的分类
- 按照内部类在定义的时候是否给它一个类名,可以分为:
- 有名内部类和匿名内部类
- 有名内部类又按照作用域不同可以分为:
局部内部类和成员内部类
- 成员内部类又分为:
实例内部类和静态内部类。
- 成员内部类类似于外部类的成员变量,在外边类的内部,且方法体和代码块之外定义的内部类。
- 实例内部类与实例变量类似,可以声明为公有级别、私有级别、默认级别或保护级别,即4种访问级别都可以,
而外部类只能声明为公有或默认级别。
- 内部类编译成功后生成的字节码文件是“外部类$内部类.class”。
- 静态内部类与静态变量类似,在声明的时候使用关键字static修饰,
静态内部类只能访问外部类静态成员,所以静态内部类使用的场景不多。
- 局部内部类就是在方法体或代码块中定义的内部类,局部内部类的作用域仅限于方法体或代码块中。
- 局部内部类访问级别只能是默认的,不能是公有的、私有的和保护的访问级别,
即不能使用public、private和protected修饰。
- 局部内部类也不能是静态,即不能使用static修饰。
局部内部类可以访问外部类所有成员。
- new Inner().display()是实例化Inner对象后马上调用它的方法,没有为Inner对象分配一个引用变量名,
这种写法称为“匿名对象”。匿名对象适合只运行一次情况下。
- 匿名内部类是没有名字的内部类,本质上是没有名的局部内部类,具有局部内部类所有特征。
- 如果匿名内部类在方法中定义,它所访问的参数需要声明为final的。
- 匿名内部类通常用来实现接口或抽象类的,很少覆盖具体类
第十八章
- Java 8之后推出的Lambda表达式开启了Java语言支持函数式编程
- Lambda表达式,也称为闭包(Closure),现在很多语言都支持Lambda表达式
- 函数式编程与面向对象编程有很大的差别,函数式编程将程序代码看作数学中的函数,
函数本身作为另一个函数的参数或返回值,即高阶函数
18.1
- Lambda表达式是一个匿名函数(方法)代码块,可以作为表达式、方法参数和方法返回值。
- (参数列表) -> { //Lambda表达式体 }
- Lambda表达式参数列表与接口中方法参数列表形式一样,Lambda表达式体实现接口方法。
- Lambda表达式实现的接口不是普通的接口,称为是函数式接口,这种接口只能有一个方法。
- 如果接口中声明多个抽象方法,那么Lambda表达式会发生编译错误
- 为了防止在函数式接口中声明多个抽象方法,Java 8提供了一个声明函数式接口注解@FunctionalInterface
- 在接口之前使用@FunctionalInterface注解修饰,那么试图增加一个抽象方法时会发生编译错误。但可以添加默认方法和静态方法。
- Lambda表达式是一个匿名方法代码,Java中的方法必须声明在类或接口中,那么Lambda表达式所实现的匿名方法是在函数式接口中声明的。
- Lambda表达式中参数只有一个时,可以省略参数小括号。
- 如果Lambda表达式体中只有一条语句,那么可以省略return和大括号
- Lambda表达式一种常见的用途是作为参数传递给方法。这需要声明参数的类型声明为函数式接口类型。
18.4
- Lambda表达式可以访问所在外层作用域内定义的变量,包括:成员变量和局部变量。
- 成员变量包括:实例成员变量和静态成员变量。在Lambda表达式中可以访问这些成员变量,
此时的Lambda表达式与普通方法一样,可以读取成员变量,也可以修改成员变量。
- 静态方法中不能访问实例成员变量
- 当访问实例成员变量或实例方法时可以使用this,如果不与局部变量发生冲突情况下可以省略this。
- 静态方法中不能访问实例成员变量
- 对于成员变量的访问Lambda表达式与普通方法没有区别,但是对于访问外层局部变量时,会发生“捕获变量”情况
- Lambda表达式中捕获变量时,会将变量当成final的,在Lambda表达式中不能修改那些捕获的变量。
18.5
- Java 8之后增加了双冒号“::”运算符,该运算符用于“方法引用”,注意不是调用方法。
“方法引用”虽然没有直接使用Lambda表达式,但也与Lambda表达式有关,与函数式接口有关。
- 方法引用分为:静态方法的方法引用和实例方法的方法引用
- 它们的语法形式如下:
- 类型名::静态方法 // 静态方法的方法引用
实例名::实例方法 // 实例方法的方法引用
- 被引用方法的参数列表和返回值类型,必须与函数式接口方法参数列表和方法返回值类型一致。
第十九章
- 异常处理
- Java异常处理机制
19.1
- 程序运行过程中难免会发生异常,发生异常并不可怕,
程序员应该考虑到有可能发生这些异常,编程时应该捕获并进行处理异常,不能让程序发生终止,这就是健壮的程序。
19.2
-
异常封装成为类Exception,此外,还有Throwable和Error类,异常类继承层次如图所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V9KLOQPf-1665971153349)(image/异常类继承.png)]
-
Throwable类:所有的异常类都直接或间接地继承于java.lang.Throwable类,在Throwable类有几个非常重要的方法
-
String getMessage():获得发生异常的详细消息。
void printStackTrace():打印异常堆栈跟踪信息。
String toString():获得异常对象的描述。
-
堆栈跟踪是方法调用过程的轨迹,它包含了程序执行过程中方法调用的顺序和所在源代码行号。
-
Throwable有两个直接子类:Error和Exception
-
Error是程序无法恢复的严重错误,程序员根本无能为力,只能让程序终止。
例如:JVM内部错误、内存溢出和资源耗尽等严重情况。
-
Exception是程序可以恢复的异常,它是程序员所能掌控的。
例如:除零异常、空指针访问、网络连接中断和读取不存在的文件等。
-
Exception类可以分为:受检查异常和运行时异常
-
受检查异常是除RuntimeException以外的异常类。
-
它们的共同特点是:编译器会检查这类异常是否进行了处理,
即要么捕获(try-catch语句),要么不抛出(通过在方法后声明throws),否则会发生编译错误。
-
运行时异常是继承RuntimeException类的直接或间接子类。
-
它们的共同特点是:编译器不检查这类异常是否进行了处理,也就是对于这类异常不捕获也不抛出, 程序也可以编译通过。
由于没有进行异常处理,一旦运行时异常发生就会导致程序的终止,这是用户不希望看到的。
19.3
- 捕获异常是通过try-catch语句实现的,最基本try-catch语句语法如下:
- try{ //可能会发生异常的语句}
catch(Throwable e){//处理异常e}
- 静态方法、实例方法和构造方法都可以声明抛出异常,凡是抛出异常的方法都可以通过try-catch进行捕获,
当然运行时异常可以不捕获。一个方法声明抛出什么样的异常需要查询API文档。
- 每个try代码块可以伴随一个或多个catch代码块,用于处理try代码块中所可能发生的多种异常。
catch(Throwable e)语句中的e是捕获异常对象,e必须是Throwable的子类,
异常对象e的作用域在该catch代码块中。
- Java提供的try-catch语句嵌套是可以任意嵌套
- Java 7推出了多重捕获(multi-catch)技术,可以帮助解决此类问题,上述代码修改如下:
- try{//可能会发生异常的语句}
catch (IOException | ParseException e)
{//调用方法methodA处理}
19.4
- try-catch语句后面还可以跟有一个finally代码块
- 无论try正常结束还是catch异常结束都会执行finally代码块
- 使用finally代码块释放资源会导致程序代码大量增加,一个finally代码块往往比正常执行的程序还要多。
在Java 7之后提供自动资源管理(Automatic Resource Management)技术,
可以替代finally代码块,优化代码结构,提高程序可读性。
- 在try语句后面添加一对小括号“()”,其中是声明或初始化资源语句,可以有多条语句语句之间用分号“;”分隔。
- 声明或初始化三个输入流,三条语句放到在try语句后面小括号中,语句之间用分号“;”分隔,
这就是自动资源管理技术了,采用了自动资源管理后不再需要finally代码块,
不需要自己close这些资源,释放过程交给了JVM。
- 所有可以自动管理的资源需要实现AutoCloseable接口
19.5
- 在一个方法中如果能够处理异常,则需要捕获并处理。
但是本方法没有能力处理该异常,捕获它没有任何意义,
则需要在方法后面声明抛出该异常,通知上层调用者该方法有可以发生异常。
- 方法后面声明抛出使用throws关键字
- 方法中可能抛出的异常(除了Error和RuntimeException及其子类外)都必须通过throws语句列出,多个异常之间采用逗号(,)分隔。
- 如果声明抛出的多个异常类之间有父子关系,可以只声明抛出父类。
但如果没有父子关系情况下,最好明确声明抛出每一个异常,因为上层调用者会根据这些异常信息进行相应的处理。
19.6
- 自定义异常类
- 实现自定义异常类需要继承Exception类或其子类,如果自定义运行时异常类需继承RuntimeException类或其子类。
- 自定义异常类一般需要提供两个构造方法,
一个是代码第②行的无参数的默认构造方法,异常描述信息是空的;
另一个是代码第③行的字符串参数的构造方法,message是异常描述信息,getMessage()方法可以获得这些信息。
自定义异常就这样简单,主要是提供两个构造方法就可以了。
19.7
- Java异常相关的关键字中有两个非常相似,它们是throws和throw,
其中throws关键字前面19.5节已经介绍了,throws用于方法后声明抛出异常,
而throw关键字用来人工引发异常
- 也可以通过throw语句显式抛出异常,语法格式如下:
- throw Throwable或其子类的实例
- throw显式抛出的异常与系统生成并抛出的异常,在处理方式上没有区别,就是两种方法:要么捕获自己处理,要么抛出给上层调用者。
第二十章
- 对象容器—集合
- 集合本质是基于某种数据结构数据容器
- 常见的数据结构:
数组(Array)、集(Set)、队列(Queue)、链表(Linkedlist)、
树(Tree)、堆(Heap)、栈(Stack)和映射(Map)等结构。
21.1
-
Java中提供了丰富的集合接口和类,它们来自于java.util包。
-
如图所示是Java主要的集合接口和类,
从图中可见Java集合类型分为:Collection和Map,
Collection子接口有:Set、Queue和List等接口。
每一种集合接口描述了一种数据结构。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BohzW9Bc-1665971153349)(image/java集合.png)]
-
在Java SE中List名称的类型有两个,一个是java.util.List,另外一个是java.awt.List。
-
java.util.List是一个接口,这本章介绍的List集合。
-
java.awt.List是一个类,用于图形用户界面开发,它是一个图形界面中的组件。
-
学习Java中的集合,首先从两大接口入手,
重点掌握List、Set和Map三个接口,熟悉这些接口中提供的方法。
然后再熟悉这些接口的实现类,并了解不同实现类之间的区别。
20.2
- List集合中的元素是有序的,可以重复出现
- List集合关心的元素是否有序,而不关心是否重复,请大家记住这个原则
- List接口的实现类有:ArrayList 和 LinkedList
- ArrayList是基于动态数组数据结构的实现,
LinkedList是基于链表数据结构的实现。
- ArrayList访问元素速度优于LinkedList,
LinkedList占用的内存空间比较大,但LinkedList在批量插入或删除数据时优于ArrayList。
- List接口继承自Collection接口,List接口中的很多方法都继承自Collection接口的。
- List接口中常用方法如下:
get(int index):返回List集合中指定位置的元素。
set(int index, Object element):用指定元素替换List集合中指定位置的元素。
add(Object element):在List集合的尾部添加指定的元素。该方法是从Collection集合继承过来的。
add(int index, Object element):在List集合的指定位置插入指定元素。
remove(int index):移除List集合中指定位置的元素。
remove(Object element):如果List集合中存在指定元素,则从List集合中移除第一次出现的指定元素。该方法是从Collection集合继承过来的。
clear():从List集合中移除所有元素。该方法是从Collection集合继承过来的。
isEmpty():判断List集合中是否有元素,没有返回true,有返回false。该方法是从Collection集合继承过来的。
contains(Object element):判断List集合中是否包含指定元素,包含返回true,不包含返回false。该方法是从Collection集合继承过来的。
indexOf(Object o):从前往后查找List集合元素,返回第一次出现指定元素的索引,如果此列表不包含该元素,则返回-1。
lastIndexOf(Object o):从后往前查找List集合元素,返回第一次出现指定元素的索引,如果此列表不包含该元素,则返回-1。
iterator():返回迭代器(Iterator)对象,迭代器对象用于遍历集合。该方法是从Collection集合继承过来的。
size():返回List集合中的元素数,返回值是int类型。该方法是从Collection集合继承过来的。
subList(int fromIndex, int toIndex):返回List集合中指定的 fromIndex(包括)和toIndex(不包括)之间的元素集合,返回值为List集合。
- 在Java中任何集合中存放的都是对象,即引用数据类型,基本数据类型不能放到集合中。
- 集合最常用的操作之一是遍历,遍历就是将集合中的每一个元素取出来,进行操作或计算。
List集合遍历有三种方法:
使用for循环遍历:List集合可以使用for循环进行遍历,for循环中有循环变量,通过循环变量可以访问List集合中的元素。
使用for-each循环遍历:for-each循环是针对遍历各种类型集合而推出的,笔者推荐使用这种遍历方法。
使用迭代器遍历:Java提供了多种迭代器,List集合可以使用Iterator和ListIterator迭代器。
20.3
- Set集合
- Set集合是由一串无序的,不能重复的相同类型元素构成的集合。
- List集合中的元素是有序的、可重复的,而Set集合中的元素是无序的、不能重复的
- 当不考虑顺序,且没有重复元素时,Set集合和List集合可以互相替换的。
- Set接口直接实现类主要是HashSet,HashSet是基于散列表数据结构的实现。
- Set接口也继承自Collection接口,Set接口中大部分都是继承自Collection接口,
这些方法如下:
add(Object element):在Set集合的尾部添加指定的元素。该方法是从Collection集合继承过来的。
remove(Object element):如果Set集合中存在指定元素,则从Set集合中移除该元素。该方法是从Collection集合继承过来的。
clear():从Set集合中移除所有元素。该方法是从Collection集合继承过来的。
isEmpty():判断Set集合中是否有元素,没有返回true,有返回false。该方法是从Collection集合继承过来的。
contains(Object element):判断Set集合中是否包含指定元素,包含返回true,不包含返回false。该方法是从Collection集合继承过来的。
iterator():返回迭代器(Iterator)对象,迭代器对象用于遍历集合。该方法是从Collection集合继承过来的。
size():返回Set集合中的元素数,返回值是int类型。该方法是从Collection集合继承过来的。
- Set集合中的元素由于没有序号,所以不能使用for循环进行遍历,但可以使用for-each循环和迭代器进行遍历。
20.4
- Map集合
- Map(映射)集合表示一种非常复杂的集合,允许按照某个键来访问元素。
- Map集合是由两个集合构成的,一个是键(key)集合,一个是值(value)集合。
- 键集合是Set类型,因此不能有重复的元素。
- 而值集合是Collection类型,可以有重复的元素。Map集合中的键和值是成对出现的。
- Map接口直接实现类主要是HashMap,HashMap是基于散列表数据结构的实现。
- Map集合中包含两个集合(键和值),所以操作起来比较麻烦,Map接口提供很多方法用来管理和操作集合。
- 主要的方法如下:
get(Object key):返回指定键所对应的值;如果Map集合中不包含该键值对,则返回null。
put(Object key, Object value):指定键值对添加到集合中。
remove(Object key):移除键值对。
clear():移除Map集合中所有键值对。
isEmpty():判断Map集合中是否有键值对,没有返回true,有返回false。
containsKey(Object key):判断键集合中是否包含指定元素,包含返回true,不包含返回false。
containsValue(Object value):判断值集合中是否包含指定元素,包含返回true,不包含返回false。
keySet():返回Map中的所有键集合,返回值是Set类型。
values():返回Map中的所有值集合,返回值是Collection类型。
size():返回Map集合中键值对数。
- Map集合遍历与List和Set集合不同,Map有两个集合,因此遍历时可以只遍历值的集合,
也可以只遍历键的集合,也可以同时遍历。
- 这些遍历过程都可以使用for-each循环和迭代器进行遍历。
第21章 泛型
- Java 5之后提供泛型(Generics)支持,使用泛型可以最大限度地重用代码、保护类型的安全以及提高性能。
- 泛型特性对Java影响最大是集合框架的使用
21.1
- 对于Java 5之前程序员而言,使用集合经常会面临一个很尴尬的问题:
放入一个种特定类型,但是取出时候全部是Object类型,于是在具体使用时候需要将元素转换为特定类型。
- 强制类型转换是有风险的,如果不进行判断就臆断进行类型转换会发生ClassCastException异常
- 在Java 5之前没有好的解决办法,在类型转换之前要通过instanceof运算符判断一下,该对象是否是目标类型。
- 而泛型的引入可以将这些运行时异常提前到编译期暴露出来,这增强了类型安全检查
- List和ArrayList 后面添加了,这就是List和ArrayList的泛型表示方式,
尖括号中可以任何的引用类型,它限定了集合中是能存放该种类型的对象,
所以代码试图添加非String类型元素时,会发生编译错误。
- 可见原本在运行时发生的异常,提早暴露到编译期,使程序员早发现问题,避免程序发布上线之后发生系统崩溃。
21.2 使用泛型
- 泛型对于Java影响最大就是集合了,
Java 5之后所有的集合类型都可以有泛型类型,可以限定存放到集合中的类型。
- Collection、List、ArrayList、Set和Map,这说明这些类型是支持泛型的。
尖括号中的E、K和V等是类型参数名称,它们是实际类型的占位符。
## 21.3
11. 根据自己的需要也可以自定义泛型类、泛型接口和带有泛型参数的方法。
12. 虽然Java SE已经提供了支持泛型的队列java.util.Queue类型,
但是为了学习泛型的目的,本节中还是要介绍一个自己实现的支持泛型的队列集合。
21.4
- 自定义泛型接口与自定义泛型类类似,定义的方式完全一样
- 需要注意的实现泛型接口的具体类也应该支持泛型,
所以Queue中类型参数名要与IQueue接口中的类型参数名一致,占位符所用字母相同。
21.5
- 在方法中也可以使用泛型,即方法的参数类型或返回值类型,可以用类型参数表示
public static boolean isEquals(T a, T b) {
return a.equals(b);
}
- 另外,泛型的类型参数也可以限定一个边界,例如比较方法isEquals()只想用于数值对象大小的比较,
实现代码如下:
public static boolean isEquals(T a, T b){
return a.equals(b);
}
- 上述代码定义泛型使用语句,该语句是限定类型参数只能是Number类型。
第22章 文件管理与I/O流
22.1
- Java语言使用File类对文件和目录进行操作,查找文件时需要实现FilenameFilter或FileFilter接口
- 另外,读写文件内容可以通过
FileInputStream、FileOutputStream、FileReader和FileWriter类实现,它们属于I/O流
- File类表示一个与平台无关的文件或目录。
- File类中常用的方法如下:
构造方法:
File(String path):如果path是实际存在的路径,则该File对象表示的是目录;如果path是文件名,则该File对象表示的是文件。
File(String path, String name):path是路径名,name是文件名。
File(File dir, String name):dir是路径对象,name是文件名。
获得文件名
String getName( ):获得文件的名称,不包括路径
String getPath( ):获得文件的路径。
String getAbsolutePath( ):获得文件的绝对路径。
String getParent( ):获得文件的上一级目录名
文件属性测试
boolean exists( ):测试当前File对象所表示的文件是否存在。
boolean canWrite( ):测试当前文件是否可写
boolean canRead( ):测试当前文件是否可读
boolean isFile( ):测试当前文件是否是文件
boolean isDirectory( ):测试当前文件是否是目录
文件操作
long lastModified( ):获得文件最近一次修改的时间。
long length( ):获得文件的长度,以字节为单位
boolean delete( ):删除当前文件。成功返回 true,否则返回false
boolean renameTo(File dest):将重新命名当前File对象所表示的文件。成功返回 true,否则返回false。
目录操作
boolean mkdir( ):创建当前File对象指定的目录。
String[] list():返回当前目录下的文件和目录,返回值是字符串数组。
String[] list(FilenameFilter filter):返回当前目录下满足指定过滤器的文件和目录,参数是实现FilenameFilter接口对象,返回值是字符串数组。
File[] listFiles():返回当前目录下的文件和目录,返回值是File数组
File[] listFiles(FilenameFilter filter):返回当前目录下满足指定过滤器的文件和目录,参数是实现FilenameFilter接口对象,返回值是File数组。
File[] listFiles(FileFilter filter):返回当前目录下满足指定过滤器的文件和目录,参数是实现FileFilter接口对象,返回值是File数组。
- 对目录操作有两个过滤器接口:FilenameFilter和FileFilter
- 它们都只有一个抽象方法accept,FilenameFilter接口中的accept方法如下:
- boolean accept(File dir, String name):测试指定dir目录中是否包含文件名为name的文件
- FileFilter接口中的accept方法如下:
- boolean accept(File pathname):测试指定路径名是否应该包含在某个路径名列表中
- UNIX、Linux和macOS中使用正斜杠“/”,而Windows下使用反斜杠“\”
22.2
- Java将数据的输入输出(I/O)操作当作“流”来处理,“流”是一组有序的数据序列
- “流”分为两种形式:输入流和输出流,从数据源中读取数据是输入流,将数据写入到目的地是输出流。
- 所有的输入形式都抽象为输入流,所有的输出形式都抽象为输出流,它们与设备无关
- 以字节为单位的流称为字节流,以字符为单位的流称为字符流。
Java SE提供4个顶级抽象类,
两个字节流抽象类:InputStream和OutputStream;
两个字符流抽象类:Reader和Writer。
- 字节输入流根类是InputStream,它有很多子类,这些类的说明如下所示。
- FileInputStream: 文件输入流
ByteArrayInputStream: 面向字节数组的输入流
PipedInputStream: 管道输入流,用于两个线程之间的数据传递
FilterInputStream: 过滤输入流,它是一个装饰器扩展其他输入流
BufferedInputStream: 缓冲区输入流,它是FilterInputStream的子类
DataInputStream: 面向基本数据类型的输入流
- 字节输出流根类是OutputStream,它有很多子类,这些类的说明如下所示。
- FileOutputStream: 文件输出流
ByteArrayOutputStream: 面向字节数组的输出流
PipedOutputStream: 管道输出流,用于两个线程之间的数据传递
FilterOutputStream: 过滤输出流,它是一个装饰器扩展其他输入流
BufferedOutputStream: 缓冲区输出流,它是FilterOutputStream的子类
DataOutputStream: 面向基本数据类型的输出流
- 字符输入流根类是Reader,这类流以16位的Unicode编码表示的字符为基本处理单位。
它有很多子类,这些类的说明如下所示
- FileReader: 文件输入流
CharArrayReader: 面向字符数组的输入流
PipedReader: 管道输入流,用于两个线程之间的数据传递
FilterReader: 过滤输入流,它是一个装饰器扩展其他输入流
BufferedReader: 缓冲区输入流,它也是装饰器,它不是FilterReader的子类
InputStreamReader: 把字节流转换为字符流,它也是一个装饰器,是FileReader的父类
- 字符输出流根类是Writer,这类流以16位的Unicode编码表示的字符为基本处理单位.
它有很多子类,这些类的说明如下所示
- FileWriter: 文件输出流
CharArrayWriter: 面向字符数组的输出流
PipedWriter: 管道输出流,用于两个线程之间的数据传递
FilterWriter: 过滤输出流,它是一个装饰器扩展其他输出流
BufferedWriter: 缓冲区输出流,它是装饰器,它不是FilterWriter的子类
OutputStreamWriter: 把字节流转换为字符流,它是一个装饰器,是FileWriter的父类
22.3
-
掌握字节流的API先要熟悉它的两个抽象类:InputStream 和OutputStream,了解它们有哪些主要的方法。
-
InputStream是字节输入流的根类,它定义了很多方法,影响着字节输入流的行为。
-
InputStream主要方法如下:
int read():读取一个字节,返回0到255范围内的int字节值。如果已经到达流末尾,而且没有可用的字节,则返回值-1。
int read(byte b[] ):读取多个字节,数据放到字节数组b中,返回值为实际读取的字节的数量,
如果已经到达流末尾,而且没有可用的字节,则返回值-1。
int read(byte b[ ], int off, int len):最多读取len个字节,数据放到以下标off开始字节数组b中,
将读取的第一个字节存储在元素b[off]中,下一个存储在b[off+1]中,依次类推。
返回值为实际读取的字节的数量,如果已经到达流末尾,而且没有可用的字节,则返回值-1。
void close():流操作完毕后必须关闭
-
上述所有方法都可能会抛出IOException,因此使用时要注意处理异常
-
OutputStream是字节输出流的根类,它定义了很多方法,影响着字节输出流的行为。
-
OutputStream主要方法如下:
void write(int b):将b写入到输出流,b是int类型占有32位,写入过程是写入b 的8个低位,b的24个高位将被忽略。
void write(byte b[ ]):将b.length个字节从指定字节数组b写入到输出流。
void write(byte b[ ], int off, int len):把字节数组b中从下标off开始,长度为len的字节写入到输出流。
void flush():刷空输出流,并输出所有被缓存的字节。由于某些流支持缓存功能,该方法将把缓存中所有内容强制输出到流中。
void close( ):流操作完毕后必须关闭。
-
上述所有方法都声明抛出IOException,因此使用时要注意处理异常。
-
流(包括输入流和输出流)所占用的资源,不能通过JVM的垃圾收集器回收,需要程序员自己释放。
一种方法是可以在finally代码块调用close()方法关闭流,释放流所占用的资源。
另一种方法通过自动资源管理技术管理这些流,流(包括输入流和输出流)都实现了AutoCloseable接口,可以使用自动资源管理技术
-
案例:文件复制
-
FileInputStream(String name):创建FileInputStream对象,name是文件名。
如果文件不存在则抛出FileNotFoundException异常。
FileInputStream(File file):通过File对象创建FileInputStream对象。
如果文件不存在则抛出FileNotFoundException异常。
-
FileOutputStream构造方法主要有:
FileOutputStream(String name):通过指定name文件名创建FileOutputStream对象。
如果name文件存在,但如果是一个目录或文件无法打开则抛出FileNotFoundException异常。
FileOutputStream(String name, boolean append):通过指定name文件名创建FileOutputStream对象,append参数如果为 true,
则将字节写入文件末尾处,而不是写入文件开始处。如果name文件存在, 但如果是一个目录或文件无法打开则抛出FileNotFoundException异常。
FileOutputStream(File file):通过File对象创建FileOutputStream对象。
如果file文件存在,但如果是一个目录或文件无法打开则抛出FileNotFoundException异常。
FileOutputStream(File file, boolean append):通过File对象创建FileOutputStream对象,append参数如果为 true,
则将字节写入文件末尾处,而不是写入文件开始处。如果file文件存在,但如果是一个目录或文件无法打开则抛出FileNotFoundException异常。
-
过滤流实现了装饰器(Decorator)设计模式,这种设计模式能够在运行时扩充一个类的功能。
而继承在编译时扩充一个类的功能。
-
BufferedInputStream(InputStream in):通过一个底层输入流in对象创建缓冲流对象,
缓冲区大小是默认的,默认值8192。
BufferedInputStream(InputStream in, int size):通过一个底层输入流in对象创建缓冲流对象,size指定的缓冲区大小,
缓冲区大小应该是2的n次幂,这样可提供缓冲区的利用率。
BufferedOutputStream构造方法主要有:
BufferedOutputStream(OutputStream out):通过一个底层输出流out 对象创建缓冲流对象,缓冲区大小是默认的,默认值8192。
BufferedOutputStream(OutputStream out, int size):通过一个底层输出流out对象创建缓冲流对象,size指定的缓冲区大小,
缓冲区大小应该是2的n次幂,这样可提高缓冲区的利用率。
-
使用缓冲流的FileCopyWithBuffer明显要比不使用缓冲流的FileCopy速度快。
-
字符流
-
掌握字符流的API先要熟悉它的两个抽象类:Reader和Writer,了解它们有哪些主要的方法。
-
Reader是字符输入流的根类,它定义了很多方法,影响着字符输入流的行为
-
Reader主要方法如下:
int read():读取一个字符,返回值范围在065535(0x000xffff)之间。如果因为已经到达流末尾,则返回值-1。
int read(char[] cbuf):将字符读入到数组cbuf中,返回值为实际读取的字符的数量,如果因为已经到达流末尾,则返回值-1。
int read(char[] cbuf, int off, int len):最多读取len个字符,数据放到以下标off开始字符数组cbuf中,
将读取的第一个字符存储在元素cbuf[off]中,下一个存储在cbuf[off+1]中,依次类推。
返回值为实际读取的字符的数量,如果因为已经到达流末尾,则返回值-1。
void close():流操作完毕后必须关闭。
-
上述所有方法都声明了抛出IOException,因此使用时要注意处理异常。
-
Writer是字符输出流的根类,它定义了很多方法,影响着字符输出流的行为
-
Writer主要方法如下:
void write(int c):将整数值为c的字符写入到输出流,c是int类型占有32位,写入过程是写入c的16个低位,c的16个高位将被忽略。
void write(char[] cbuf):将字符数组cbuf写入到输出流。
void write(char[] cbuf, int off, int len):把字符数组cbuf中从下标off开始,长度为len的字符写入到输出流。
void write(String str):将字符串str中的字符写入输出流。
void write(String str,int off,int len):将字符串str 中从索引off开始处的len个字符写入输出流。
void flush():刷空输出流,并输出所有被缓存的字符。由于某些流支持缓存功能,该方法将把缓存中所有内容强制输出到流中。
void close( ):流操作完毕后必须关闭。
-
案例:文件复制
多线程编程
- 无论PC(个人计算机)还是智能手机现在都支持多任务,都能够编写并发访问程序
- 在Windows操作系统出现之前,PC上的操作系统都是单任务系统,
只有在大型计算机上才具有多任务和分时设计。
随着Windows、Linux等操作系统出现,把原本只在大型计算机才具有的优点,带到了PC系统中。
23.1
- 一个进程就是一个执行中的程序,而每一个进程都有自己独立的一块内存空间、一组系统资源
- 在进程的概念中,每一个进程的内部数据和状态都是完全独立的
- 在Windows操作系统中一个进程就是一个exe或者dll程序,它们相互独立,互相也可以通信,
在Android操作系统中进程间的通信应用也是很多的。
- 线程与进程相似,是一段完成某个特定功能的代码,是程序中单个顺序控制的流程,
但与进程不同的是,同类的多个线程是共享一块内存空间和一组系统资源。
- 线程被称为轻量级进程。一个进程中可以包含多个线程。
- Java程序至少会有一个线程,这就是主线程,程序启动后是由JVM创建主线程,程序结束时由JVM停止主线程。
- 主线程它负责管理子线程,即子线程的启动、挂起、停止等等操作
- 获取主线程示例:
public class HelloWorld{
public static void main(String[] args){
Thread mainThread = Thread.currentThread();
System.out.println("主线程名:" + mainThread.getName());
}
}
- 创建子线程
- Java中创建一个子线程涉及到:java.lang.Thread类和java.lang.Runnable接口
- Thread是线程类,创建一个Thread对象就会产生一个新的线程。
而线程执行的程序代码是在实现Runnable接口对象的run()方法中编写的,实现Runnable接口对象是线程执行对象。
- 线程执行对象实现Runnable接口的run()方法,run()方法是线程执行的入口,该线程要执行程序代码都在此编写的,run()方法称为线程体。
- 创建线程Thread对象时,可以将线程执行对象传递给它,这需要是使用Thread类如下两个构造方法:
- Thread(Runnable target, String name):target是线程执行对象,实现Runnable接口。name为线程指定一个名字。
- Thread(Runnable target):target是线程执行对象,实现Runnable接口。线程名字是由JVM分配的。
public class static void main(String[] args){
@Override
public void run(){
for (int i=0; i<10; i++){
System.out.printf("第 %d 次执行 - %s\n", i, Thread.currentThread().getName());
try{
long sleepTime = (long) (1000 * Math.random());
Thread.sleep(sleepTime);
} catch (InterruptedException e){
}
}
}
System.out.pirintln("执行完成!" + Thread.currentThread().getName());
}
- Thread.sleep(sleepTime)是休眠当前线程, sleep是静态方法它有两个版本:
- static void sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠。
- static void sleep(long millis, int nanos) 在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠。
- 所以用活sleep()方法是多线程编程的关键。
- 事实上Thread类也实现了Runnable接口,所以Thread类也可以作为线程执行对象,这需要继承Thread类,覆盖run()方法。
- Java只支持单重继承,继承Thread类的方式不能再继承其他父类。
- 当开发一些图形界面的应用时,需要一个类既是一个窗口(继承JFrame)又是一个线程体,
那么只能采用实现Runnable接口方式。
- 如果线程体使用的地方不是很多,可以不用单独定义一个类。
- 可以使用匿名内部类或Lambda表达式直接实现Runnable接口。
Runnable中只有一个方法是函数式接口,可以使用Lambda表达式。
- 匿名内部类和Lambda表达式不需要定义一个线程类文件,使用起来很方便
23.3
- 在线程的生命周期中,线程会有几种状态,如图23-5所示,线程有5种状态。下面分别介绍一下。
- 新建状态(New)是通过new等方式创建线程对象,它仅仅是一个空的线程对象。
- 当主线程调用新建线程的start()方法后,它就进入就绪状态(Runnable)。
此时的线程尚未真正开始执行run()方法,它必须等待CPU的调度。
- CPU的调度就绪状态的线程,线程进入运行状态(Running),处于运行状态的线程独占CPU,执行run()方法。
- 因为某种原因运行状态的线程会进入不可运行状态,即阻塞状态(Blocked),
处于阻塞状态的线程JVM系统不能执行该线程,即使CPU空闲,也不能执行该线程。
- 如下几个原因会导致线程进入阻塞状态:
- 当前线程调用sleep()方法,进入休眠状态。
- 被其他线程调用了join()方法,等待其他线程结束。
- 发出I/O请求,等待I/O操作完成期间。
- 当前线程调用wait()方法。
- 处于阻塞状态可以重新回到就绪状态,如:休眠结束、其他线程加入、I/O操作完成和调用notify或notifyAll唤醒wait线程。
- 线程退出run()方法后,就会进入死亡状态(Dead),
线程进入死亡状态有可以是正常实现完成run()方法进入,也可能是由于发生异常而进入的。
- 线程管理是比较头痛的事情,这是学习线程的难点。下面分别介绍一下。
23.4 线程管理
- 线程的调度程序根据线程决定每次线程应当何时运行,Java提供了10种优先级,分别用1~10整数表示,
最高优先级是10用常量MAX_PRIORITY表示;最低优先级是1用常量MIN_PRIORITY;默认优先级是5用常量NORM_PRIORITY表示。
- Thread类提供了setPriority(int newPriority)方法可以设置线程优先级,通过getPriority()方法获得线程优先级。
- 影响线程获得CPU时间的因素,除了受到的线程优先级外,还与操作系统有关。
- 在介绍现在状态时提到过join()方法,当前线程调用t1线程的join()方法,则阻塞当前线程,等待t1线程结束,
如果t1线程结束或等待超时,则当前线程回到就绪状态。
- Thread类提供了多个版本的join(),它们定义如下:
- void join():等待该线程结束。
- void join(long millis):等待该线程结束的时间最长为millis毫秒。如果超时为0意味着要一直等下去。
- void join(long millis, int nanos):等待该线程结束的时间最长为millis毫秒加nanos纳秒。
23.4 线程让步
- 线程类Thread还提供一个静态方法yield(),调用yield()方法能够使当前线程给其他线程让步。
- 它类似于sleep()方法,能够使运行状态的线程放弃CPU使用权,暂停片刻,然后重新回到就绪状态
- 与sleep()方法不同的是,sleep()方法是线程进行休眠,能够给其他线程运行的机会,无论线程优先级高低都有机会运行。
而yield()方法只给相同优先级或更高优先级线程机会。
- yield()方法在实际开发中很少使用,大多都使用sleep()方法,sleep()方法可以控制时间,而yield()方法不能
- 线程体中的run()方法结束,线程进入死亡状态,线程就停止了。
- 控制线程的停止有人会想到使用Thread提供的stop()方法,这个方法已经不推荐使用了,这个方法有时会引发严重的系统故障,
类似还是有suspend()和resume()挂起方法。Java现在推荐的做法就是采用本例的结束变量方式。
## 23.5 线程安全
55. 在多线程环境下,访问相同的资源,有可以会引发线程不安全问题。
56. 多一个线程同时运行,有时线程之间需要共享数据,一个线程需要其他线程的数据,否则就不能保证程序运行结果的正确性。
57. 线程同步保证线程安全的重要手段,但是线程同步客观上会导致性能下降。
58. 可以通过两种方式实现线程同步,两种方式都涉及到使用synchronized关键字,一种是synchronized方法,
使用synchronized关键字修饰方法,对方法进行同步;
另一种是synchronized语句,使用synchronized关键字放在对象前面限制一段代码的执行。
59. synchronized关键字修饰方法实现线程同步,方法所在的对象被锁定
60. ynchronized语句方式主要用于第三方类,不方便修改它的代码情况。
23.6 线程通信
- 为了实现线程间通信,需要使用Object类中声明的5个方法:
- void wait():使当前线程释放对象锁,然后当前线程处于对象等待队列中阻塞状态
- void wait(long timeout, int nanos):同wait()方法,等待timeout毫秒加nanos纳秒时间
- void notify():当前线程唤醒此对象等待队列中的一个线程
- void notifyAll():当前线程唤醒此对象等待队列中的所有线程
第24章 网络编程
- 现代的应用程序都离不开网络,网络编程是非常重要的技术。
- Java SE提供java.net包,其中包含了网络编程所需要的最基础一些类和接口。
- 这些类和接口面向两个不同的层次:
- 基于Socket的低层次网络编程和基于URL的高层次网络编程
- 所谓高低层次就是通信协议的高低层次,
Socket采用TCP、UDP等协议,这些协议属于低层次的通信协议
URL采用HTTP和HTTPS这些属于高层次的通信协议
- Socket编程与基于URL的高层次网络编程比较,能够提供更强大的功能和更灵活的控制,但是要更复杂一些。
24.1
- 网络结构是网络的构建方式,目前流行的有:
客户端服务器结构网络
对等结构网络
- 客户端服务器(Client Server,缩写C/S)结构网络,是一种主从结构网络。
- 生活中很多网络服务都采用这种结构。例如:Web服务、文件传输服务和邮件服务等。
- 对等结构网络
- 对等结构网络也叫点对点网络(Peer to Peer,缩写P2P),每个节点之间是对等的。
- 对等结构网络分布范围比较小。
通常在一间办公室或一个家庭内,因此它非常适合于移动设备间的网络通讯,
网络链路层是由蓝牙和WiFi实现。
- 网络通信会用到协议,其中TCP/IP协议是非常重要的。
- TCP/IP协议是由IP和TCP两个协议构成的,
IP(Internet Protocol)协议是一种低级的路由协议,它将数据拆分成许多小的数据包中,
并通过网络将它们发送到某一特定地址,但无法保证都所有包都抵达目的地,也不能保证包的顺序。
- 由于IP协议传输数据的不安全性,网络通信时还需要TCP协议,
传输控制协议(Transmission ControlProtocol,TCP)是一种高层次的协议,
面向连接的可靠数据传输协议,如果有些数据包没有收到会重发,并对数据包内容准确性检查并保证数据包顺序,
所以该协议保证数据包能够安全地按照发送时顺序送达目的地。
- 为实现网络中不同计算机之间的通信,每台计算机都必须有一个与众不同的标识,这就是IP地址,
TCP/IP使用IP地址来标识源地址和目的地址。
- 最初所有的IP地址都是32位数字构成,由4个8位的二进制数组成,每8位之间用圆点隔开,
如:192.168.1.1,这种类型的地址通过IPv4指定。
- 而现在有一种新的地址模式称为IPv6,IPv6使用128位数字表示一个地址,分为8个16位块。
- Java语言同时指出IPv4和IPv6。
- 在IPv4地址模式中IP地址分为A、B、C、D和E等5类
- A类地址用于大型网络,地址范围:1.0.0.1~126.155.255.254
B类地址用于中型网络,地址范围:128.0.0.1~191.255.255.254
C类地址用于小规模网络,192.0.0.1~223.255.255.254。
D类地址用于多目的地信息的传输和作为备用。
E类地址保留仅作实验和开发用
- 一个特殊的IP地址127.0.0.1,127.0.0.1称为回送地址,指本机。
- 主要用于网络软件测试以及本地机进程间通信,使用回送地址发送数据,不进行任何网络传输,只在本机进程间通信。
- 一个IP地址标识这一台计算机,每一台计算机又有很多网络通信程序在运行,
提供网络服务或进行通信,这就需要不同的端口进行通信。
- 如果把IP地址比作电话号码,那么端口就是分机号码,进行网络通信时不仅要指定IP地址,还要指定端口号。
- TCP/IP系统中的端口号是一个16位的数字,它的范围是0~65535。
- 小于1024的端口号保留给预定义的服务,如HTTP是80,FTP是21,Telnet是23,Email是25等,
除非要和那些服务进行通信,否则不应该使用小于1024的端口。
24.2
- TCP/IP协议的传输层有两种传输协议:TCP(传输控制协议)和 UDP(用户数据报协议)
- TCP为了保证数据的正确性,会重发一切没有收到的数据,还会对进行数据内容进行验证,并保证数据传输的正确顺序
- 因此TCP协议对系统资源的要求较多。
- 基于TCP Socket编程很有代表性,先介绍TCP Socket编程。
- Socket是网络上的两个程序,通过一个双向的通信连接,实现数据的交换。
- 这个双向链路的一端称为一个Socket。
- Socket通常用来实现客户端和服务端的连接。
- Socket是TCP/IP协议的一个十分流行的编程接口,
一个Socket由一个IP地址和一个端口号唯一确定,
一旦建立连接Socket还会包含本机和远程主机的IP地址和远端口号
- Socket是成对出现的。
- 使用Socket进行C/S结构编程
- 服务器端监听某个端口是否有连接请求,
服务器端程序处于阻塞状态,
直到客户端向服务器端发出连接请求,服务器端接收客户端请求,
服务器会响应请求,处理请求,
然后将结果应答给客户端,这样就会建立连接。
- 一旦连接建立起来,通过Socket可以获得输入输出流对象。
借助于输入输出流对象就可以实现服务器与客户端的通信,
最后不要忘记关闭Socket和释放一些资源(包括:关闭输入输出流)。
- java.net包为TCP Socket编程提供了两个核心类:
Socket和ServerSocket,
分别用来表示双向连接的客户端和服务器端。
- 先介绍一下Socket类,Socket常用的构造方法有:
- Socket(InetAddress address, int port) :
创建Socket对象,并指定远程主机IP地址和端口号。
Socket(InetAddress address, int port, InetAddress localAddr, int localPort):
创建Socket对象,并指定远程主机IP地址和端口号,以及本机的IP地址(localAddr)和端口号(localPort)。
Socket(String host, int port):
创建Socket对象,并指定远程主机名和端口号,IP地址为null,null表示回送地址,即127.0.0.1。
Socket(String host, int port, InetAddress localAddr, int localPort):
创建Socket对象,并指定远程主机和端口号,以及本机的IP地址(localAddr)和端口号(localPort)。host主机名,IP地址为null,null表示回送地址,即127.0.0.1。
- Socket其他的常用方法有:
InputStream getInputStream():通过此Socket返回输入流对象。
OutputStream getOutputStream():通过此Socket返回输出流对象。
int getPort():返回Socket连接到的远程端口。
int getLocalPort():返回Socket绑定到的本地端口。
InetAddress getInetAddress():返回Socket连接的地址。
InetAddress getLocalAddress():返回Socket绑定的本地地址。
boolean isClosed():返回Socket是否处于关闭状态。
boolean isConnected():返回Socket是否处于连接状态。
void close():关闭Socket
- Socket与流类似所占用的资源,不能通过JVM的垃圾收集器回收,需要程序员释放。
一种方法是可以在finally代码块调用close()方法关闭Socket,释放流所占用的资源。
另一种方法通过自动资源管理技术释放资源,Socket和ServerSocket都实现了AutoCloseable接口。
- ServerSocket类
- ServerSocket类常用的构造方法有:
ServerSocket(int port, int maxQueue):
创建绑定到特定端口的服务器Socket。maxQueue设置连接的请求最大队列长度,如果队列满时,则拒绝该连接。默认值是50。
ServerSocket(int port):
创建绑定到特定端口的服务器Socket。最大队列长度是50。
- ServerSocket其他的常用方法有:
InputStream getInputStream():通过此Socket返回输入流对象。
OutputStream getOutputStream():通过此Socket返回输出流对象。
boolean isClosed():返回Socket是否处于关闭状态。
Socket accept():侦听并接收到Socket的连接。此方法在建立连接之前一直阻塞。
void close():关闭Socket。
- ServerSocket类本身不能直接获得I/O流对象,而是通过accept()方法返回Socket对象,
通过Socket对象取得I/O流对象,进行网络通信。
- 此外,ServerSocket也实现了AutoCloseable接口,通过自动资源管理技术关闭ServerSocket。
- 基于TCP Socket编程比较复杂,先从一个简单的文件上传工具案例介绍TCP Socket编程基本流程。
- 上传过程是一个单向Socket通信过程,如图24-5所示,
客户端通过文件输入流读取文件,然后从Socket获得输出流写入数据,
写入数据完成上传成功,客户端任务完成。
- 服务器端从Socket获得输入流,然后写入文件输出流,写入数据完成上传成功,服务器端任务完成。
- 需要注意当前运行的路径是Eclipse工程根目录,需要指定类路径,命令的-cp .;./bin就是指定类路径,
包括两个当前路径:
其中点(.)表示当前路径,
./bin表示bin目录,也可以写成.\bin。
为什么要指定bin目录呢?是因为编译之后的字节码文件放在此目录中。
24.3
- UDP Socket低层次网络编程
- UDP(用户数据报协议)就像日常生活中的邮件投递,是不能保证可靠地寄到目的地。
- UDP是无连接的,对系统资源的要求较少,UDP可能丢包不保证数据顺序。
但是对于网络游戏和在线视频等要求传输快、实时性高、质量可稍差一点的数据传输,UDP还是非常不错的。
- UDP Socket网络编程比TCP Socket编程简单多,UDP是无连接协议,不需要像TCP一样监听端口,建立连接,然后才能进行通信。
- java.net包中提供了两个类:DatagramSocket和DatagramPacket用来支持UDP通信
- DatagramSocket也实现了AutoCloseable接口,通过自动资源管理技术关闭DatagramSocket。
- DatagramPacket用来表示数据报包,是数据传输的载体。
24.4
-
数据交换格式就像两个人在聊天一样,采用彼此都能听得懂的语言,你来我往,其中的语言就相当于通信中的数据交换格式。
-
数据交换格式主要分为纯文本格式、XML格式和JSON格式,其中纯文本格式是一种简单的、无格式的数据交换方式。
-
而XML格式和JSON格式可以带有描述信息,它们叫做“自描述的”结构化文档。
-
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。所谓轻量级,是与XML文档结构相比而言的,
描述项目的字符少,所以描述相同数据所需的字符个数要少,那么传输速度就会提高,而流量却会减少。
-
目前Java官方没有提供JSON编码和解码所需要的类库,所以需要使用第三方JSON库,
笔者推荐JSON-java库,JSON-java库提供源代码,最重要的是不依赖于其他第三方库
-
可以在https://github.com/stleary/JSON-java下载源代码
-
将JSON-java库源代码文件添加到工程中,需要两个步骤:
-
创建org.json包
选择Eclipse项目的src源代码文件夹,右击菜单中选择“新建”→“包”,弹出新建包对话框,
如图24-16所示在名称的中输入org.json,然后单击完成,就成功创建org.json包。
复制源代码文件
org.json包创建好后,需要将JSON-java库文件夹中的源代码文件复制到Eclipse工程的org.json包中。
-
如果按照规范的JSON文档要求,每个JSON数据项目的“名称”必须使用双引号括起来,不能使用单引号或没有引号。
24.5
- Java的java.net包中还提供了高层次网络编程类——URL,通过URL类访问互联网资源。
- 互联网资源是通过URL指定的,URL是Uniform Resource Locator简称,翻译过来是“一致资源定位器”,但人们都习惯URL简称。
- Java 的java.net.URL类用于请求互联网上的资源,采用HTTP/HTTPS协议,请求方法是GET方法,一般是请求静态的、少量的服务器端数据。
- 由于URL类只能发送HTTP/HTTPS的GET方法请求,如果要想发送其他的情况或者对网络请求有更深入的控制时,可以使用HttpURLConnection类型。
- 使用HttpURLConnection发送POST请求
第25章 Swing图形用户界面编程
- 图形用户界面(Graphical User Interface,简称 GUI)编程对于某种语言来说非常重要。
- Java的应用主要方向是基于Web浏览器的应用,用户界面主要是HTML、CSS和JavaScript等基于Web的技术,
这些介绍要到Java EE阶段才能学习到。
- Java图形用户界面技术主要有:AWT、Applet、Swing和JavaFX。
- Swing的基础是AWT,Swing的事件处理和布局管理都是依赖于AWT,但是AWT提供的组件在使用开发中很少使用,
因此重点学习Swing提供的组件。
第26章 反射
- 反射(Reflection)是程序的自我分析能力,通过反射可以确定类有哪些方法、有哪些构造方法以及有哪些成员变量。
- Java语言提供了反射机制,通过反射机制能够动态读取一个类的信息;能够在运行时动态加载类,而不是在编译期。
- 反射可以应用于框架开发,它能够从配置文件中读取配置信息动态加载类、创建对象,以及调用方法和成员变量。
- Java反射机制在一般的Java应用开发中很少使用,即便是Java EE阶段也很少使用。
26.1
- Java反射机制API主要是 java.lang.Class类和java.lang.reflect包。
第27章 注解
- Java 5之后可以在源代码中嵌入一些补充信息,这种补充信息称为注解(Annotation)
- 注解并不能改变程序运行的结果,不会影响程序运行的性能。
- 使用注解对于代码实现功能没有任何的影响。程序员即便是不知道注解,也完全可以编写Java程序代码。
第28章 数据库编程
- 数据必须以某种方式来存储才可以有用,数据库实际上是一组相关数据的集合。
- 数据库编程相关的技术很多,涉及具体的数据库安装、配置和管理,还要掌握SQL语句,最后才能编写程序访问数据库。
28.3
-
Java中数据库编程是通过JDBC(Java Database Connectivity)实现的。
使用JDBC技术涉及到三种不同的角色:Java官方、开发人员和数据库厂商。
-
Java官方提供JDBC接口,如Connection、Statement和ResultSet等
数据库厂商为了支持Java语言使用自己的数据库,他们根据这些接口提供了具体的实现类,这些具体实现类称为JDBC Driver(JDBC驱动程序)
-
JDBC API为Java开发者使用数据库提供了统一的编程接口,它由一组Java类和接口组成。
这种类和接口来自于java.sql和javax.sql两个包。
-
在编程实现数据库连接时,JVM必须先加载特定厂商提供的数据库驱动程序。使用Class.forName()方法实现驱动程序加载过程
-
无法找到MySQL驱动程序com.mysql.jdbc.Driver类,这需要配置当前项目的类路径(Classpath),在类路径中包含MySQL驱动程序。
MySQL驱动程序是在MySQL安装目录中Connector.J5.1中的mysql-connector-java-xxx-bin.jar文件。笔者默认安装驱动程序文件路径如下:
-
“C:\Program Files (x86)\MySQL\Connector.J 5.1\mysql-connector-java-5.1.41-bin.jar”
-
一般在发布java文件时,会把字节码文件(class文件)打包成.jar文件,.jar文件是一种基于.zip结构的压缩文件。
28.3
- 下面重点介绍一下JDBC API中最重要的三个接口:Connection、Statement和ResultSet。
- java.sql.Connection接口的实现对象代表与数据库的连接,也就是在Java程序和数据库之间建立连接。
- java.sql.Statement称为语句对象,它提供用于向数据库发出SQL语句,并且访问结果。
- 有三种Statement接口:
java.sql.Statement、java.sql.PreparedStatement和java.sql.CallableStatement,
其中PreparedStatement继承Statement接口,CallableStatement继承PreparedStatement接口。
- 在Statement执行SQL语句时,如果是SELET语句会返回结果集,结果集通过接口java.sql.ResultSet描述的,
它提供了逐行访问结果集的方法,通过该方法能够访问结果集中不同字段的内容。