1 前言

1.1 三大特征

  • ①封装

  • ②继承

  • ③多态

1.2 特性

  • ①面向对象

  • ②跨平台(即能在任何操作系统运行)

  • ③开源

  • ④简单易用

  • ⑤多线程

  • ⑥安全性

1.3 多态

概述:

  • 多态是同一个行为具有多个不同表现形式或形态的能力。
  • 多态就是同一个接口,使用不同的实例而执行不同操作
  • 多态性是对象多种表现形式的体现。

多态的优点:

  • 消除类型之间的耦合关系
  1. 可替换性
  2. 可扩充性
  3. 接口性
  4. 灵活性
  5. 简化性

多态存在的三个必要条件

  • 继承
  • 重写
  • 父类引用指向子类对象:Parent p = new Child();

注意:当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。

多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。

1.4 技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
1.用shift右键,点击powershell窗口,用java -jar来启动jar包	如sql.jar	java -jar .\sql.jar

2.System.currentTimeMillis()记录时间

3.在使用私人成员变量时,建立构造方法和get或者set方法时用Alt+Fn+insert快捷键,列表第一个时构造方法

4.在安装java时,尽量把开发相关都放在一起,如放在D盘的develop文件

5.ASCLL码:
A-Z 65-90
a-z 97-122
可以进行字符和数字的加减(隐式转换)

6.系统环境变量的Path:就是把文件路径记录进去,这样你想打开某个程序,直接在任意地方输入程序名即可
在添加java的环境变量时,我们建议先在系统变量添加变量名为JAVA_HOME,值为jdk所在的目录,然后再在Path里面添加%JAVA_HOME%\bin。因为bin里面有我们要用的java和javac。
更改jdk版本时,只需要在环境变量将原本指向8的,改成指向17的就可以
在idea中更改jdk,要去项目结构中和设置中关于编译器设置进行更改。同时maven项目要去pom文件也修改。


7.制表符\t:在打印的时候,把前面字符串的长度补齐到8,或者8的整数倍。最少补1个空格,最多补8个空格。

8.8bit为一个字节。

9.一个循环尽量只做一件事情。

10.关于 == ,基本数据类型比较的是数据值,引用数据类型比较的是地址值。

11.在idea中,fori是正遍历,forr是倒着遍历。

12.main方法中的String[] args是以前版本用于接收参数的,运行前通过edit run进行添加参数,中间用空格隔开,这样就可以带着参数运行。

13.在导入模块时,可以直接导入模块对应的iml配置文件,而不是导入文件夹。

14.一个类如果没有父类,默认继承自Object类

15.144到154,164,165,175-181,184

16.导入第三方库时,一般在项目下创建一个lib的文件夹,然后添加第三方库,再add as library即可。

17,Bom头是txt文件中的,它保存一些文件编码的等一些信息,在使用字符流读取时可能会读取Bom头数据。

18.UUID类的方法可以帮助生成一个唯一id

1.5 CMD

概述:在windows中,利用命令行的方式操作计算机。

常用CMD命令

  • ①盘符名词+冒号:切换到某盘

  • ②dir:查看当前路径下的内容

  • ③cd目录:进入该目录,或者cd 目录1\目录2.…

  • ④cd ..:回退到上一级目录

  • ⑤cls:清屏

  • ⑥exit:退出

  • ⑦cd \:回退到盘符目录

1.6 JDK的安装目录介绍

目录名称 说明
bin 该路径下存放了JDK的各种工具命令。javac和java就放在这个目录。
conf 该路径下存放了JDK的相关配置文件。
include 该路径下存放了一些平台特定的头文件。
jmods 该路径下存放了JDK的各种模块。
legal 该路径下存放了JDK各模块的授权文档。
lib 该路径下存放了JDK工具的一些补充JAR包。

1.7 Java程序开发步骤

开发Java程序,需要三个步骤:编写程序,编译程序,运行程序。

编译过程也就是将.java的代码文件转化为机器认识的过程。
编译文件:编译后会产生一个class文件。
java文件:程序员自己编写的代码。
class文件:也叫字节码文件,交给计算机执行的文件。

运行的是编译之后的class文件。

用到两个命令:
javac + 文件名 + 后缀名 (就是编译java文件)
java + 文件名(运行编译之后的class文件)

1.8 高级语言的编译运行方式

编译型:整体的去翻译,特点经过翻译产生一个新的文件,而解释型不会。如C

解释型:按行的去翻译,如python

混合型:如Java,首先把代码整体编译成一个class字节码文件,然后再按行交给设备翻译,且在虚拟机上运行。(跨平台就是通过虚拟机来实现的)

1.9 Java三大使用平台

Java SE:Java语言的(标准版) ,用于桌面应用的开发,是其他两个版本的基础。(但不是Java的长处,c语言在这方面更好)

Java ME:Java语言的(小型版),用于嵌入式电子设备或者小型移动设备。(凉了)

Java EE:Java语言的(企业版),用于Web方向的网站开发。在这个领域,是当之无愧的No1。

1.10 JDKJRE

JDK (Java Development kit) : Java开发工具包。由JVM、核心类库(事先定义好的类)和开发工具组成。

JVM (Java Virtual Machine) : Java虚拟机,真正运行Java程序的地方

JRE (Java Runtime Environment):Java的运行环境。由JVM、核心类库和运行工具组成。

2 Java基础概念

2.1 注释

单行注释:格式如 // 注释信息

多行注释:格式如 /* 注释信息 */

文档注释:格式如 /** 注释信息 */

2.2 字面量

整数类型:不带小数点的数字。

小数类型:带小数点的数字。

字符串类型:如”abc”,”a”,””。都是字符串。

字符类型:如’a’,’b’。单引号内只能有一个。

布尔类型:即true和false

空类型:即null。

2.3 变量的注意事项

  • ①只能存一个值

  • ②变量名不允许重复定义

  • ③一条语句可以定义多个变量。如:int d = 100,e = 200;

  • ④变量在使用之前一定要赋值。建议在定义时直接赋值。

  • ⑤变量的作用域范围

2.4 进制

二进制:由0和1组成,代码中以0b开头。

八进制:由0到7组成,代码中以0开头。

十进制:由0到9组成,默认不用开头。

十六进制:由0到9和a到f组成,代码中以0x开头。

任意进制转十进制

公式:系数 * 基数的权次幂 依次相加

系数:就是每一位上的数

基数:当前进制数

权:从右往左,依次为0,1,2,3等。

十进制转其他进制

除基取余法:不断的除以基数(几进制,基数就是几)得到余数,直到商为0,再将余数倒着拼起来即可。

2.5 数据类型

基本数据类型:分为4类8种,整数(byte,short,int,long),浮点数(float,double),字符(char),布尔(boolean)

基本数据类型又分为数值型和非数值型,数值型有整数(byte,short,int,long),浮点数(float,double),字符(char)

非数值有布尔值(boolean)

如图:

整理提示:上方图片使用本地绝对路径 D:\Users\作业\知识点\java\图片\基本数据类型.png,如果在其他设备预览失败,需要把图片复制到笔记目录并改成相对路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
①byte:
byte数据类型是8位、有符号的,以二进制补码表示的整数;(256个数字),占1字节
最小值是-128(-2^7);
最大值是127(2^7-1);
默认值是0;
byte类型用在大型数组中节约空间,主要代替整数,因为byte变量占用的空间只有int类型的四分之一;
②short:
short数据类型是16位、有符号的以二进制补码表示的整数,占2字节
最小值是-32768(-2^15);
最大值是32767(2^15 - 1);
Short数据类型也可以像byte那样节省空间。一个short变量是int型变量所占空间的二分之一;
默认值是0;
③int:
int数据类型是32位、有符号的以二进制补码表示的整数;占4字节
最小值是-2,147,483,648(-2^31);
最大值是2,147,485,647(2^31 - 1);
一般地整型变量默认为int类型;
默认值是0;
④long:
long数据类型是64位、有符号的以二进制补码表示的整数;占8字节
最小值是-9,223,372,036,854,775,808(-2^63);
最大值是9,223,372,036,854,775,807(2^63 -1);
这种类型主要使用在需要比较大整数的系统上;
默认值是0L;
⑤float:
float数据类型是单精度、32位、符合IEEE 754标准的浮点数;占4字节 -3.4*E38- 3.4*E38。。。浮点数是有舍入误差的
float在储存大型浮点数组的时候可节省内存空间;
默认值是0.0f;
浮点数不能用来表示精确的值,如货币;
⑥double:
double数据类型是双精度、64位、符合IEEE 754标准的浮点数;
浮点数的默认类型为double类型;
double类型同样不能表示精确的值,如货币;
默认值是0.0d;
⑦boolean:
boolean数据类型表示一位的信息;
只有两个取值:true和false;
这种类型只作为一种标志来记录true/false情况;
默认值是false;
⑧char:
char类型是一个单一的16位Unicode字符;用 ‘’表示一个字符。java内部使用Unicode字符集。他有一些转义字符,占2字节
最小值是'\u0000'(即为0);
最大值是'\uffff'(即为65,535);可以当整数来用,它的每一个字符都对应一个数字

各个类型占的字节大小:
byte 1字节
char 2字节
short 2字节
int 4字节
float 4字节
long 8字节
double 8字节
boolean 至少1字节

注意事项

  • ①定义long类型的数据时要在赋值的后面加上L,例:long a=10000000000L;

  • ②定义float类型时也是,后面要加上F。

  • ③默认的浮点型是double,默认的整数是int;

  • ④整数和小数取值范围大小关系:double > float > long > int > short> byte

引用数据类型

引用里面有类(class)。接口(interface),数组([]),对象。所有引用类型的默认值都是null。

基本数据类型和引用数据类型的区别

基本类型 : 在JVM的栈中分配空间存“值”。

引用类型 : 在堆里面分配空间存“值”。

2.6 标识符

定义:就是给类,方法,变量等起的名字。

注意事项:由数字、字母、下划线和美元符组成。不能以数字开头,不能是关键字,区分大小写(关键字全是小写)

小驼峰命名法:方法、变量

大驼峰命名法:类名

2.7 键盘录入

Scanner的使用分三步

  • ①导包:import java.util.Scanner;(导包的动作必须出现在类定义的上面)

  • ②创建对象:Scanner sc =new Scanner(Syestem.in);(只有sc是变量名,可以变,其他都不可以)

  • ③接受数据:int i=sc.nextInt();(上面只有i是变量名可以变,其他都不能变)

用法:String i = sc.next();

方法:

next():读取字符串,一定要读取到有效字符后才可以结束输入,对输入有效字符之前遇到的空格键、Tab键或Enter键等结束符,next()方法会自动将其去掉,只有在输入有效字符之后,next()方法才将其后输入的空格键、Tab键或Enter键等视为分隔符或结束符。

nextLine():读取字符串,会读取回车符,并结束输入。

nextInt():只会读取数值,剩下”\n”还没有读取,并将cursor放在本行中。也就是说如果后面跟了nextLine,那么nextint的回车符会被nextLine接收到,并且回车也代表nextLine结束了输入

2.8 输出

println输出的会换行,print的不会换行。

printf有两部分参数,第一部分是占位符%s,第二部分是占位符要代表的数据。如printf(“你好啊%s”,”张三”);

有几个占位符,后面就要填充几个。

2.9 运算符

算数运算符

1
2
3
4
5
扩展:数值拆分
取个位: 数值 % 10
取十位: 数值 / 10 % 10
取百位: 数值 / 100 % 10
取千位: 数值 / 1000 % 10

隐式转换:在进行算术的时候,低级的数据类型会先被转换为高级的(即取值范围小的升为取值范围大的)。

并且char,short,byte在计算前都先会转换为int,然后再进行运算。

强制转换数据类型:从大到小的强转。用法:目标数据类型 变量名=(目标数据类型)值或变量,如int k=(int)88.88。结果可能会出错。

注意事项

1.整数参与计算,结果只能得到整数

2.小数参与计算,结果有可能是不精确的,如果我们需要精确计算,那么需要用到后面的知识点。

3.+号在字符串是连接符,如果出现字符串与数字相加,如果数字在后面会被直接当成字符串连接起来,如果数字在前面会先相加在连接。

4.当出现 字符+字符 或者 字符+数字时,会把字符换算成对应的ASCLL码表上的数字在进行计算。

自增自减运算符

注意事项

  • ①自增减在单独使用的时候,写前面或者后面都是一样的。但是参与计算后,写后面代表先用后加,写前面代表先加后用。

赋值运算符

+=即加后赋值其他同理。

注意事项

  • ①s+=20等同于s=(int) (s+20),他包含了强制转换数据类型。

关系运算符

逻辑运算符

  • ①&(逻辑与):即并且。两边都为真,才为真,其他都为假。

  • ②|(逻辑或):即或者。

  • ③^(逻辑异或):即当两者不相同时为true,相同时为false。

  • ④!(逻辑非):即取反

短路逻辑运算符

  • ①&&(短路与)

  • ②||(短路或)

短路:当左边的表达式能够确定最终的结果时,那么右边就不会参与运行了。

常用:&&,||,!。

三元运算符

格式:关系表达式?表达式1:表达式2;

输出:如果关系表达式是true则输出表达式1的值,否则输出表达式2的值。

位运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
概述:
Java 定义的位运算(bitwise operators)直接对整数类型的位进行操作,这些整数类型包括 long,int,short,char 和 byte。
位运算符主要用来对操作数二进制的位进行运算。按位运算表示按每个二进制位(bit)进行计算,其操作数和运算结果都是整型值。

位逻辑运算符
位逻辑运算符包含 4 个:&(与)、|(或)、~(非)和 ^(异或)。除了 ~(即位取反)为单目运算符外,其余都为双目运算符。

①位与运算符为&
其运算规则是:参与运算的数字,低位对齐,高位不足的补零,如果对应的二进制位同时为 1,那么计算结果才为 1,否则为 0。因此,任何数与 0 进行按位与运算,其结果都为 0。
简单说:就是两个十进制数将他们换算成二进制后(注意是16位,不足的补0),在前面的放在上面,后面的数放下面,然后进行二进制比较,如果他们同样位置的数都为1则对应位置的二进制数为1,其他都为0,最后把结果换算成十进制输出出去。

②位或运算符为|
其运算规则是:参与运算的数字,低位对齐,高位不足的补零。如果对应的二进制位只要有一个为 1,那么结果就为 1;如果对应的二进制位都为 0,结果才为 0。
简单说:就是上下两个数对应的二进制数的位置的数只要有1就把对应的位置输出1

③位异或运算符为^
其运算规则是:参与运算的数字,低位对齐,高位不足的补零,如果对应的二进制位相同(同时为 0 或同时为 1)时,结果为 0;如果对应的二进制位不相同,结果则为 1。
简单说:同上,只要对应位置相同输出0,不相同都为1
提示:在有的高级语言中,将运算符^作为求幂运算符,要注意区分。

④位取反运算符为~
其运算规则是:只对一个操作数进行运算,将操作数二进制中的 1 改为 0,0 改为 1。
简单说:同上都是二进制对比,把二进制数的0反转为1
注意:位运算符的操作数只能是整型或者字符型数据以及它们的变体,不用于 float、double 或者 long 等复杂的数据类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
位移运算符
概述:
位移运算符用来将操作数向某个方向(向左或者右)移动指定的二进制位数。有>>和<<,它们都属于双目运算符。
①左移位运算符为<<
其运算规则是:按二进制形式把所有的数字向左移动对应的位数,高位移出(舍弃),低位的空位补零。
简单说:就是十进制换算成二进制以后,把所有值往左移,超出16位的删掉,最右边没有的补上。
即向左移动,低位补0.
如:a << 2代表a左移2位。每左移1位代表乘2,每右移1位代表除2.

②右移位运算符为>>
其运算规则是:按二进制形式把所有的数字向右移动对应的位数,低位移出(舍弃),高位的空位补零。
简单说:同上不过是向右移了
即向右移动,高位补0.最高位当为正数补0,为负数补1.
③无符号右移>>>
向右移动,高位补0
1
2
3
4
5
6
7
复合位赋值运算符
&= 按位与赋值 num1 &= num2 等价于 num 1=num 1 & num2
|= 按位或赋值 num1 |= num2 等价于 num 1=num 1 | num2
^= 按位异或赋值 num1 ^= num2 等价于 num 1=num 1 ^ num2
-= 按位取反赋值 num1 -= num2 等价于 num 1=num 1 - num2
«= 按位左移赋值 num1 «= num2 等价于 num 1=num 1 « num2
»= 按位右移赋值 num1 »= num2 等价于 num 1=num 1 » num2
1
2
3
4
5
位运算符使用技巧
①判断奇偶数
我们可以利用&运算符的特性,来判断二进制数第一位是0还是1。用if ((a & 1) == 0) 代替 if (a % 2 == 0)来判断a是不是偶数。
②求相反数: ~a + 1
③求绝对值: a >> 31 == 0 ? a : (~a + 1)

运算符优先级

> 整理提示:上方图片使用本地绝对路径 `D:\Users\作业\知识点\java\图片\运算符优先级.png`,如果在其他设备预览失败,需要把图片复制到笔记目录并改成相对路径。

2.10 原码、补码、反码

原码:十进制数据的二进制表现形式,最左边的那一位是符号位,0为正,1为负。利用原码对正数计算是不会出错的。

反码:正数的补码反码是其本身,负数的反码是符号位保持不变,其余位取反(取反就是0变1,1变0)。

补码:正数的补码是其本身,负数的补码是在其反码的基础上+1。负数要利用其补码进行计算。

一个字节是8个bit,他的取值范围是-128到+127。(在一个字节下,-128只有补码,没有正和反码)

计算机中,数字的存储和计算都是通过补码进行计算。

2.11 判断和循环

if

注意事项:

  • ①大括号的开头可以另起一-行书写,但是建议写在第一-行的末尾

  • ②在语句体中,如果只有一句代码,大括号可以省略不写

  • ③如果对一个布尔类型的变量进行判断,不要用==号

  • ④如果是一种情况判断直接用if,两种用if和else,多种用if 和 else if。

switch

格式:switch(表达式){

case 常量表达式1:

语句体1;

break;

case 常量表达式2:

语句体2;

break;

….

default:

语句体n+1;

break;

}

注意事项:

  • ①default是前面所有结果不匹配后,就会进行这个,他可省略,并且可在任何位置。

  • ②case后面只能是字面量,不能是变量,且不能重复。

  • ③表达式取值为byte、short、 int、 char。JDK5以后可以是枚举(即1,2,3),jDK7以后可以是String

1
2
3
4
扩展:
在jdk12后,case的结构体可以把:改为->。即case 1 -> {},这样可以省略break。且当只有一句时可以省略大括号。
if的第三种场景适用于对范围的判断。
switch适用于把有限个数据一一列举出来,选择其中一个。

for

while

格式:

初始化语句;

while(条件判断语句){

循环体语句;

条件控制语句;

}

for和while区别:for是已知循环次数或范围,while是只需要知道循环结束的条件。

do while

2.12 跳转控制语句

continue:基于条件控制,跳过某次循环体内容的执行,继续下一次的执行。

break:基于条件控制,终止循环体内容的执行,也就是结束当前的整个循环。

注意:这里的break一般只是跳出当前循环,如果想要跳出指定的循环,可以给循环前面加上 (名字:) 为他起名,然后使用 (break 名字;) 即可。

2.13 随机数

Random

作用:产生一个随机数,使用分三步

  • ①导包import java.Random;(必须出现在类定义的上面)

  • ②创建对线:Random r = new Random();(只允许变变量名)

  • ③获取随机数:int number = r.nextInt(10);(获取数据的范围[0,10),这里面只允许变变量名和范围)

2.14 数组

定义:是一种容器,可以用来存储同种数据类型的多个值。(这里的同种数据类型要结合隐式转换来考虑)

定义格式:数据类型[] 数组名 或者 数据类型 数组名[]

java中的数组必须要先初始化,初始化意思就是:为数组中的数组元素分配内存空间,并为每个数组元素赋值

动态初始化:初始化时只指定数组长度,由系统为数组分配初始值。格式:数据类型[] 便量名=new 数据类型[数组长度]

静态初始化:初始化时指定每个数组元素的初始值,由系统决定数组长度。 格式:数据类型[] 变量名=new 数据类型[]{数据1,数据2..}

静态的可以简写为数据类型[] 变量名={数据1,数据2….}

获取数组的长度

方法:数组名.length

数组遍历的通用格式:int[] arr={…..};

for(int x=0;x<arr.length;x++){

arr[x]

}

注意事项

  • ①:直接输出数组名会输出数组的首地址值,形如[D@776ec8df。其中[表示是一个数组,D表示为double类型,@是一个固定间隔符,后面的776ec8df(十六进制)才是真正的地址值。

2.15 Java 内存分配

在jdk8以前jvm把占用的内存空间分为了本地方法栈、寄存器、栈、方法区、堆。(方法区和堆连在一起)

在jdk8及以后取消方法区,新增元空间。把原来方法区的多种功能进行拆分,有的功能放到了堆中,有的功能放到了元空间中。

:方法运行时使用的内存,比如main方法运行,进入方法栈中执行。

:存储对象或者数组,new来创建的,都存储在堆内存。

方法区(元空间):存储可以运行的class文件。

本地方法栈JVM在使用操作系统功能的时候使用,和我们开发无关。

寄存器:给CPU使用,和我们开发无关。

栈内存和堆内存

一般栈内存:存储局部变量(使用完毕会立即消失)

堆内存:存储对象(使用完,空余时会被系统清除),new创建出来的对象都在堆中。例如int[] arr = new int[3] 左边存在栈内存中,右边在堆内存中。

扩展:每一个线程都有自己的栈空间,堆空间是唯一的

2.16 方法

定义:public static void 方法名(){}(public static是修饰符)

方法的返回值,这个返回值要与定义方法的数据类型一致,并且一般情况下都会有一个新的变量来接受返回的值。(void无返回值)

注意事项

  • ①方法与方法之间是平级关系,不能互相嵌套。

  • ②viod表示无返回值,可以省略return,也可以单独写一个return。

  • ③如果不是void类型的,一般创建一个变量来接受返回值

  • ④定义方法前要知道 我要干什么和我干这件事需要什么参数才能完成。

2.16.1 方法重载

重载(Overload):在同一个类中,定义了多个同名的方法,这些同名的方法具有同种的功能。每个方法具有不同的参数类型或参数个数,这些同名的方法,就构成了重载关系。实质表现就是允许一个类中存在多个具有不同参数个数或者类型的同名方法,是一个类中多态性的一种表现。

简单来说:在同一个类中,方法名相同,参数不同,与返回值类型无关(java会通过参数的不同来区分方法)

重写(Override):发生在父子类中,是父类与子类之间的多态性,实质是对父类的函数进行重新定义。(记得加注解@Override)

重载与重写的区别

  • ①重载发生在本类,重写发生在父类与子类之间。

  • ②重载的方法名必须相同,重写的方法名相同且返回值类型必须相同。

  • ③重载的参数列表不同,重写的参数列表必须相同。

方法重写的注意事项:

  • ①私有方法是不能被重写(也不会被继承)

  • ②子类重写父类的方法时,子类方法访问权限不能更低(public>protected>默认>private)

  • ③重写的本质就是子类的方法把虚方法表父类的方法进行了覆盖(也就是说进不去虚方法表的不能进行重写,即静态和私有不能重写)。

  • ④重写的方法名称、形参列表必须与父类一样。

方法参数的传递

基本类型的传递,形参的改变是不会影响实际参数的值。

引用类型的参数(数组,接口,对象),形参的改变会影响实际参数的值。

2.17 类和对象

类(设计图) :是对象共同特征的描述。

对象:是真实存在的具体东西。

对象赋值:如果把一个对象赋值给另一个对象,即地址赋给了另一个对象,这样就可以使用其中一个对象来修改其中的属性值。

public class 类名{

1、成员变量(代表属性,一般是名词)

2、成员方法(代表行为,一般是动词)

3、构造器(后面学习)

4、代码块(后面学习)

5、内部类(后面学习)

}

注意事项

  • ①在Java中,必须先设计类,才能获得对象。

  • ②类名首字母建议大写,需要见名知意,驼峰模式。

  • ③一个Java文件中可以定义多个class类,且只能一个类是public修饰, 而且public修饰的类名必须成为代码文件名。(但建议一个java文件只定义一个类)

2.17.1 JavaBean 类

定义:是一种符合Java语言规范的普通Java类,用来描述一类事物的类,它里面是没有main方法的。

设计:设计JavaBean时,要通过他都有哪些属性,有什么的行为去创建。

特征:

提供一个默认的无参构造函数。

需要被序列化并且实现了 Serializable 接口。

可能有一系列可读写属性,并且一般是 private 的。

可能有一系列的 getter 或 setter 方法。

条件

  • ①类必须是具体的和公共的

  • ②提供至少两个构造方法,一个无参,一个全部参数。

  • ③成员变量使用private修饰,并且提供set/get (如果boolean 则get可以替换成is)

  • ④类名需要见名知意

优点:包括可重用性高、易于管理、易于维护和易于编写测试。 它也是Java企业级开发中最流行的组件编程风格。

注意:在以前,编写main方法的类,叫做测试类。我们可以在测试类中创建javabean类的对象并进行赋值调用。

可以通过ptg插件或者Alt + insert快速生成构造和get/set方法。

JavaBean分两种:

  • ①封装数据的JavaBean:这种JavaBean也被叫做实体类,一般来说对应的是数据库中的一张表。

  • ②封装逻辑的JavaBean:这种JavaBean用于实现业务逻辑。目的是为了提高代码的复用和解耦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
第二种:封装逻辑的JavaBean
这种JavaBean用于实现业务逻辑。目的是为了提高代码的复用和解耦,如下:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.management.RuntimeErrorException;
import org.mymvc.entity.Login;

public class LoginDao {
//基本四项之前三
private String DBurl = "jdbc:mysql://localhost:3306/student?useUnicode=true&characterEncoding=utf8";
private String user = "root";
private String password = "123456";

public int login(UserDemo user) throws Exception{
Connection conn = jdbcUtil.getConnection();
Statement stmt = conn.createStatement();
String sql = "SELECT UPASSWORD FROM myusers WHERE UNAME ='"+user.getUname()+"' AND UPASSWORD='"+user.getUpwd()+"'";

ResultSet rs = stmt.executeQuery(sql);

int result;
if(rs.next()) {
result = 1;
}else {
result = 0;
}

jdbcUtil.close(conn, stmt, rs);
return result;
}
}
比如上面提到的UserDemo和LoginDao配合使用,调用LoginDao的login()方法会得到返回值,1表示成功,0表示失败。而LoginDao的代码可以在很其他代码中用到,而且只要实例化就可以用,十分方便

2.17.2 private

他是一个权限修饰符,主要用于封装,可以修饰成员变量和方法,作用是保护成员不被别的类使用,被他修饰的成员只能在自己的类中被访问。

针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作:

  • ①提供”get变量名()”方法,用于获取成员变量的值,方法用public修饰

  • ②提供”set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰(这两个方法定义在自己的类中,让别的类去调用)

2.17.3 成员变量和局部变量

成员变量:即类中方法外的变量

局部变量:方法中的变量

两个的区别

  • ①成员变量在堆内存的对象中,局部变量在栈内存的方法中。

  • ②成员变量随对象的存在而存在,局部变量随方法的调用而存在,随方法的调用完毕而消失

  • ③成员因为在堆内存,有默认的初始化值,而局部变量则没有

  • ④成员变量的作用域是整个类,局部变量的作用域是在当前方法中有效。

2.17.4 this

作用:区分成员变量和局部变量,一般情况下this指向的是成员变量。

this:代表所在类的对象引用。即方法被哪个对象调用,this就代表哪个对象。

this的本质:代表方法调用者的地址值。

注意:

正常的变量都会遵守就近原则,被this修饰的变量指向成员变量。

非静态方法,默认都会给this赋值为方法调用者的地址值。而静态方法是没有的

使用总结:

  • ①把this理解为一个变量,表示当前方法调用者的地址值。

  • ②this(name,age)代表访问本类中的其他构造方法。

2.17.5 构造方法

作用:在创建对象的时候给成员变量进行初始化的。

功能:主要是完成对象数据的初始化(在创建对象时可以直接调用构造方法)

格式:修饰符 类名(参数或不写参数){方法体;}

注意事项

  • ①如果没有给出构造方法,系统会给出一个默认的无参无内容的构造方法,如果定义了一个构造方法,系统将不会在给出一个默认的。

  • ②尽量都要手写一个无参的构造方法

  • ③方法名与类名相同,大小写也要一致,且没有void和return。

  • ④不管用不用都要把无参和全部参数的构造方法写上。

2.18 String

java.lang.String类代表字符串,Java程序中的所有字符串文字(例如”abc” )都为此类的对象。

创建

  • ①方法一:根据构造方法,先创建一个char a[]字符(或者byte数组)数组,然后用String s=new String(a)去接收

  • ②方法二:直接进行赋值String s=”abc” //建议使用这个,这样创建的内容会在常量池中

String对象的特点:

  • ①通过new创建的字符串对象,每一次new都会申请一个空间,即使内容相同,但是地址也会不同。

  • ②如果用直接赋值的方法(直接赋值会先检查字符串的内容是否存在),假使赋值了一个一样内容的字符串,则他俩的地址值是相等的(系统不会在给另一个创造空间)。

注意事项

  • ①字符串的内容是不会发生改变的,它的对象在创建后不能被更改。

  • ②因为字符串是对象,所以他如果要比较内容的话需要一个方法来实现,方法叫equals()

  • ③关于 ==号,基本数据类型比较的是数据值,引用数据类型比较的是地址值。

  • ④使用键盘录入的next()方法输入字符串,他是用new创建出来的。

  • ⑤通过变量和字符串的拼接实质是new StringBuilder() . append(s1) . append(“b”).toString(); 非常浪费性能,当出现多个字符串拼接时,推荐不使用这种拼接方法。

  • ⑥如果要修改String的内容,通常是两种办法,一种是使用subString()方法,另一种是先把字符串换成字符数组,经过修改,再变回去。

遍历字符串

通用格式:

for(int i=0;i<s.length(); i++){

s.charAt(i); //就是指定索引处的字符值,字符串的索引也是从0开始和数组一样

}

关于遍历字符串的三种方法:

  • ①直接for遍历,使用str.charAt(i)获取
  • ②直接遍历,使用str.substring(i,i+1)获取
  • ③先用str.tocharry()转化为字符数组,然后直接取值即可

方法:

  • ①substring(a,b):截取字符串,从a到b,不包括b
  • trim():去除字符串两端空格
  • ③Stirng[] split(a,b):将字符串按照指定字符串进行分割,然后返回一个新的字符串
    a是必须的决定个从a这分割
    b不是必须的,可选。该参数可指定返回的数组的最大长度
  • ④lastIndexOf(String str):返回指定字符出现的最后一次的下标
  • ⑤indexOf(String str):获取str在字符串对象中第一次出现的索引
  • ⑥public boolean equals(Object anObject):将此字符串与指定对象进行比较,例如:s1.equals(s2)
  • ⑦isEmpty():判断指定字符串是否为空
  • toCharArray():把字符串转换为字符数组
  • String repeat(int n):该字符串可以重复N次,并且我们可以生成一个具有重复的新字符串。
  • String join(a,b):把数组中的所有元素放入一个字符串
    a是每个元素后需要添加的元素(不包括最后一个)
    b是操作哪个字符串
  • String replace(a,b):b把a替换掉,并返回一个新的字符串
  • ⑩Boolean contains(a):如str.contains(a),str是否包含a
    注意:关于split,可以使用正则表达式,但是要用转义字符如(“\s+”):按照空格字符分开,在这里用+号可以将返回后多余的空格去掉
    正则表达式: \d 数字:[0-9]
    \D 非数字: [^0-9]
    \s 空白字符:[ \t\n\x0B\f\r]
    \S 非空白字符:[^\s]
    \w 单词字符:[a-zA-Z_0-9]
    \W 非单词字符:[^\w]

关于类型转换:
String转换为StringBuilder:public StringBuilder(String s):通过构造方法就可以实现把String转换为他。例如StringBuilder sb = new StringBuilder(s)
StringBuilder转换为String:public String toString():通过toString()就可以实现啦。

String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法。在Java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。

关于String类方法的补充:

  • 一、由基本数据型态转换成String
    String 类别中已经提供了将基本数据型态转换成 String 的 static 方法 ,也就是 String.valueOf() 这个参数多载的方法
    有以下几种:
    (1)String.valueOf(boolean b) : 将 boolean 变量 b 转换成字符串
    (2)String.valueOf(char c) : 将 char 变量 c 转换成字符串
    (3)String.valueOf(char[] data) : 将 char 数组 data 转换成字符串
    (4)String.valueOf(char[] data, int offset, int count) : 将 char 数组 data 中 由 data[offset] 开始取 count 个元素 转换成字符串
    (5)String.valueOf(double d) : 将 double 变量 d 转换成字符串
    (6)String.valueOf(float f) : 将 float 变量 f 转换成字符串
    (7)String.valueOf(int i) : 将 int 变量 i 转换成字符串

  • 二、由String转换成数字的基本数据型态
    要将String转换成基本数据型态转,大多需要使用基本数据型态的包装类别
    比如说String转换成byte,可以使用 Byte.parseByte(String s) ,这一类的方法如果无法将s分析则会丢出NumberFormatException
    (1)byte : Byte.parseByte(String s) : 将 s 转换成 byte
    (2)Byte.parseByte(String s, int radix) : 以 radix 为基底 将 s 转换为 byte ,比如说 Byte.parseByte(“11”, 16) 会得到 17
    (3)double : Double.parseDouble(String s) : 将 s 转换成 double
    (4)float : Double.parseFloat(String s) : 将 s 转换成 float
    (5)int : Integer.parseInt(String s) : 将 s 转换成 int
    (6)long : Long.parseLong(String s)

2.19 StringBuilder

概述:程序开发过程中,我们常常碰到字符串连接的情况,方便和直接的方式是通过”+”符号来实现,但是这种方式达到目的的效率比较低,且每执行一次都会创建一个String对象,即耗时,又浪费空间。使用StringBuilder类就可以避免这种问题的发生。

创建对象:StringBuilder strB = new StringBuilder();

构造方法:

  • ①public StringBuilder():创建一个空白可变字符串对象,不含有任何内容
  • ②public StringBuilder(String str):根据字符串的内容,来创建可变字符串对象

方法:

  • ①append(String str)/append(Char c):字符串连接,能够不断的连接,返回容器本身。
  • ②public String toString():返回一个与构建起或缓冲器内容相同的字符串
  • ③appendcodePoint(int cp):追加一个代码点,并将其转换为一个或两个代码单元并返回this
  • ④setCharAt(int i, char c):将第 i 个代码单元设置为 c(可以理解为替换)
  • ⑤insert(int offset, String str)/insert(int offset, Char c):在指定位置之前插入字符(串)
  • ⑥delete(int startIndex,int endIndex):删除起始位置(含)到结尾位置(不含)之间的字符串
  • ⑦public StringBuilder reverse():进行字符串倒转
  • ⑧deleteCharAt(int i, char c):用于移除序列中指定位置的字符
    注意事项:
  • ①和StringBuffer类的区别主要是,StringBuilder不是线程安全类的,他的速度最快
  • ②打印StringBuilder的对象输出的是属性值而不是地址值。
1
2
扩展
StringBuilder创建的时候,底层默认创建一个字节的数组,容量为16,当出现超出16时,默认的扩容机制是,16*2+2=34.但是当超出默认的扩充长度时,则扩阔的长度就是实际存储的长度。值最大只能为Integer.MAX_VALUE。

2.20 StringJoiner

StringJoinerStringBuilder一样,也可以看成是一个容器, 创建之后里面的内容是可变的。

作用:提高字符串的操作效率,而且代码编写特别简洁,但是目前市场.上很少有人用。他是JDK8出现的。

构造方法:

  • ①public StringJoiner (间隔符号):创建一个StringJoiner对象, 指定拼接时的间隔符号。

  • ②public StringJoiner (间隔符号,开始符号,结束符号):创建一个StringJoiner对象,指定拼接时的间隔符号、开始符号、结束符号。

方法:

  • ①public StringJqiner add (添加的内容):添加数据,并返回对象本身。

  • ②public int length():返回长度(字符出现的个数)

  • ③public String toString():返回一一个字符串(该字符串就是拼接之后的结果)

2.21 StringBuffer

构造方法:

  • StringBuffer() 构造一个空的字符串缓冲区,并且初始化为 16 个字符的容量。

  • StringBuffer(int length) 创建一个空的字符串缓冲区,并且初始化为指定长度 length 的容量。

  • StringBuffer(String str) 创建一个字符串缓冲区,并将其内容初始化为指定的字符串内容str,字符串缓冲区的初始容量为 16 加上字符串 str 的长度。

用法和StringBuilder用法一样。

StringStringBufferStringBuilder区别
1、长度是否可变

  • String 是被 final修饰的,他的长度是不可变的,就算调用 String 的concat 方法,那也是把字符串拼接起来并重新创建一个对象,把拼接后的 String 的值赋给新创建的对象
  • StringBufferStringBuilder类的对象能够被多次的修改,并且不产生新的未使用对象,StringBufferStringBuilder中的方法和功能完全是等价的。调用StringBuffer的append方法,来改变 StringBuffer 的长度,并且,相比较于 StringBufferString 一旦发生长度变化,是非常耗费内存的!

2、执行效率
三者在执行速度方面的比较:StringBuilder > StringBuffer > String

3、应用场景
如果要操作少量的数据用 = String
单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
StringBufferStringBuilder区别

1、是否线程安全
StringBuilder类在Java5中被提出,它和StringBuffer之间的最大不同在于StringBuilder的方法不是线程安全的(不能同步访问),StringBuffer是线程安全的。只是StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是线程不安全的。

2、应用场景
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。
然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。 append方法与直接使用+串联相比,减少常量池的浪费。

2.22 char 类型

JAVA中,char占2字节,16位。可在存放汉字,char类型应ASCII表中,对应的字符。

char赋值是用单引号,它可以进行运算,因为它对应ASCII码,其中a表示97

注意:char类型转int类型,将字符减一个‘0’即可。

2.23 包装类

一共有8个数据类型,其中int的包装类为Integer,char的包装类为character,其他都是首字母大写

他将基本数据类型封装成对象,方便定义方法进行操作。

2.24 Integer 类(原笔记:Integar类)

构造方法:Integer 对象名字=Integer.valueOf(int i或者String s但必须是数字)

将int转换为String类:

  • ①第一种隐式转换,直接做字符串和int类型的相加

  • ②第二种用String.valueOf(int i)方法转换

String转换为int

第一种先用Integer类创建一个对象将字符串对象填进去,然后用public int intValue()方法直接转换

第二种用public static int parseInt(String s)方法就行,static说明可以直接用类调用,如Integer.parseint(String s),直接用一个int类型接收就行。

扩充:

将整数转换为二进制:Integer.toBinaryString(int n)

八进制:toOctalString(int n)

十六进制:toHexString(int n)

其他进制转换为十进制:Integer.parseInt(String s,int radix) 这里radix是String的进制,String是对应的进制数

封装

对象代表什么,就得封装对应的数据,并提供数据对应的行为。

自动装箱和拆箱

装箱:把基本数据类型转换为对应的包装类类型

拆箱:把包装类类型转换为对应的基本数据类型

装箱的例子:如基本用方法装的,Integer i = Integer.valueOf(填整形变量)这是基本实现,自动的化直接用Integer 名字 = 整型变量

拆箱的例子:ii.intValue()这个方法是拆箱的方法,可以直接用 ii=ii+200就完成自动的拆箱了(前提是ii这个包装类型不能是null值,如果是引用类型最好先做判断)

character类

属性:

  • ①static int MIN_RADIX :返回最小基数。

  • ②static int MAX_RADIX :返回最大基数。

  • ③static char MAX_VALUE :字符类型的最大值。

  • ④static char MIN_VALUE :字符类型的最小值。

  • ⑤static Class TYPE :返回当前类型。

构造方法:

  • Character(char value):以char参数构造一个Character对象。

方法:

  • ①public static boolean isUpperCase(char ch):判断给定的字符是否是大写字符

  • ②public static boolean isLowerCase(char ch):判断给定的字符是否是小写字符

  • ③public static boolean isDigit(char ch):判断给定的字符是否是数字字符

  • ④public static char toUpperCase(char ch):把给定的字符转换为大写字符

  • ⑤public static char toLowerCase(char ch):把给定的字符转换为小写字符

  • ⑥Character.isLetter(ch);用于直接判断字符是否为字母。

2.25 工具类

帮助我们做一些事情的,但是不描述任何事物的类。

特点:

  • ①类名见名知意。如ArrUtil。

  • ②私有化构造方法。

  • ③方法都定义为静态

3 集合

3.1 集合体系结构

3.1.1 单列集合

Collection(即一次只能添加一个元素),下图为单列集合体系结构。

> 整理提示:上方图片使用本地绝对路径 `D:\Users\作业\知识点\java\图片\集合体系结构1.png`,如果在其他设备预览失败,需要把图片复制到笔记目录并改成相对路径。

其中:

  • List系列的集合:添加的元素是有序(有序指的是存和取的顺序是一样的),可重复(元素可重复),有索引。

  • Set系列的集合:添加的元素是无序(存和取的顺序不一定一样),不重复,无索引。

双列集合Map(即一次添加一对数据)

3.1.1.1 Collection 集合接口

Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。

集合里面存储的只能是引用型类型

创建对象需要用他的实现类去创建。创建Collection集合的对象:

  • ①多态的方式 Collection<String填类名> c = new ArrayList<String>();

  • ②具体的实现类ArrayList

方法:

1
2
3
4
5
6
7
public boolean add(E e):把给定的对象添加到当前集合中
public void clear():清空集合中所有的元素
public boolean remove(E e):把给定的对象在当前集合中删除
public boolean contains(object obj):判断当前集合中是否包含给定的对象
public boolean isEmpty():判断当前集合是否为空
public int size():返回集合中元素的个数/集合的长度

遍历方式:

  • ①迭代器遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
迭代器在Java中的类是Iterator,迭代器是集合专用的遍历方式。
Collection集合获取迭代器的方法为 Iterator<E> iterator():返回迭代器对象, 默认指向当前集合的0索引。
Iterator常用方法:
boolean hasNext():判断当前位置是否有元素,有元素返回true ,没有元素返回false
E next():获取当前位置的元素,并将迭代器对象移向下一个位置。

遍历如下:
Iterator<String> it = list. iterator();
while(it. hasNext()){
String str = it.next( );
System.out.println(str);
}
细节注意点:
1,报错NoSuchElementException(空元素异常)
2,迭代器遍历完毕,指针不会复位(即想要第二次遍历,需要创建一个新的Iterator对象)
3,循环中只能用一次next方法
4,迭代器遍历时,不能用集合的方法进行增加或者删除(会报并发修改异常的错),如果必须删除,需要采用Iterator中的remove方法。
  • ②增强for遍历
1
2
3
4
5
增强for的底层就是迭代器,为了简化迭代器的代码书写的。
它是JDK5之后出现的,其内部原理就是-个Iterator迭 代器
所有的单列集合和数组才能用增强for进行遍历。
格式:
for (元素的数据类型变量名:数组或者集合){}
  • Lambda表达式遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
default void forEach(Consumer<? super T> action):   :结合lambda遍历集合
如:
//底层原理:
//其实也会自己遍历集合,依次得到每一-个元素
//把得到的每一个元素,传递给下面的accept方法
//s依次表示集合中的每一个数据
co11.forEach(new Consumer<String>() {
@Override
public void accept(String s){
System.out.println(s);
}
});
lambda简化后
coll.forEach( (String s) -> {
System.out.println(s);
}
);

coll.forEach( s -> System.out.println(s));
3.1.1.2 List 集合

特点:

  • ①有序:存和取的元素顺序一致

  • ②有索引:可以通过索引操作元素

  • ③可重复:存储的元素可以重复

特有的方法:

  • ①void add(int index, E element):再此集合中的指定位置插入指定的元素

  • ②E remove(int index):删除指定索引的元素,返回被删除的元素

  • ③E set(int index,E element):修改指定索引的元素,返回被修改的元素

  • ④E get(int dex):返回指定索引的元素,可以用它遍历

遍历方法:

  • ①迭代器遍历,在遍历的过程中需要删除元素,请使用迭代器。

  • ②列表迭代器遍历,在遍历的过程中需要添加元素,请使用列表迭代器。

  • ③增强for遍历,仅仅想遍历,那么使用增强for或Lambda表达式。

  • Lambda表达式遍历

  • ⑤普通for循环( 因为List集合存在索引),如果遍历的时候想操作索引,可以用普通for。

注意

并发异常修改:ConcurrentModificationException

产生原因:迭代器遍历的过程中,通过集合对象修改了集合中元素的长度,造成了迭代器获取元素中判断预期修改值和实际修改值不一致

避让方法:在使用增强for或者迭代器进行遍历集合的过程中,不要使用集合的方法进行添加或删除元素。

修改方法:用for循环,然后用集合对象做对应的操作即可

Listlterator(列表迭代器)

创建对象的方法:通过List集合的listlterator()方法得到,所以说它是List集合特有的迭代器。

作用:用于允许程序员沿任一方向遍历列表的列表迭代器,再迭代期间修改列表,并获取列表中迭代器的当前位置。

常用的方法:

  • ①E next():返回迭代中的下一个元素

  • ②bollean haNext():如果迭代具有更多元素,则返回true

  • ③E previous():返回列表中的上一个元素

  • ④boolean hasPrevious():如果此列表迭代器再相反方向遍历列表时具有更多元素,则返回true

  • ⑤void add(E e):将指定元素插入列表 他如果做前面的并发异常那里的操作,则不会报错

3.1.1.3 ArrayList 集合

特点:只能存储引用数据类型。底层数据结构是数组,查询快,增删慢

构造方法:public ArrayList():创建一个空的集合对象。

例如:ArrayList<String> array = new ArrayList<String>() //他的使用要导包

成员方法:

方法名 说明
public boolean add(要添加的元素) 将指定的元素追加到此集合的末尾
public boolean remove(要删除的元素) 删除指定元素,返回值表示是否删除成功
public E remove(int index) 删除指定索引处的元素,返回被删除的元素
public E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
public E get(int index) 返回指定索引处的元素
public int size() 返回集合中的元素的个数
public int indexOf(“值”) 获取该元素的索引

注意事项:

  • ①直接输出ArrayList会输出[里面存储的值],这是因为这是java已经写好的类,已经经过了处理。
1
2
3
4
5
6
7
扩展
ArrayList集合底层原理
①利用空参创建的集合,在底层创建一个默认长度为0的数组(elementData是数组名)
②添加第一个元素时,底层会创建一个新的长度为10的数组
③存满时,会扩容1.5倍
④如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准

3.1.1.4 LinkedList 集合

底层数据结构是双链表,查询慢,增删快。但操作的是首尾元素也是查询很快。

LinkedList集合特有的功能:

  • ①public void addFirst(E e):在该列表开头插入指定元素

  • ②public E getFirst():返回此列表的第一个元素

  • ③public E getLast():返回此列表的最后一个元素

  • ④public E removeFirst():从此列表中删除并返回第一个元素

  • List subList(int fromIndex, int toIndex):返回截取的一部分list集合

3.1.1.5 Set 集合

集合特点:

  • ①不包含重复元素的集合

  • ②不带索引的方法,因此不能使用普通的for遍历

  • ③无序:存、取顺序不一

实现类:

  • HashSet:无序、不重复、无索引

  • LinkedHashSet:有序、不重复、无索引

  • TreeSet:可排序、不重复、无索引

常用方法和Collection的一样。

遍历方式:

  • ①迭代器

  • ②增强for

  • ③lambda表达式,foreach方法

3.1.1.6 HashSet 集合

哈希值:是JDK根据对象的地址或者字符串或者数字(hashCode方法)算出来的int类型的数值

Object类有一个方法可以获得对象的哈希值:public int hashCode()返回对象的哈希值。(默认使用地址值进行计算,一般会重写方法使用对象的内部属性计算哈希值)

对象的哈希值特点:

  • ①如果没有重写hashCode方法,不同对象计算出的哈希值是不同的

  • ②如果已经重写hashcode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的

  • ③在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞 )

HashSet特点:

  • ①底层数据结构是哈希表(jdk8后底层由数组,链表,红黑树组成),哈希表是一种增删改查数据性能都较好的结构。

  • ②对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致

  • ③没有带索引的方法,所以不能使用普通的for循环

  • ④由于是Set集合,所以也是不包含重复元素的集合

HashSet底层原理:

  • ①创建一个默认长度16,默认加载因为0.75的数组,数组名table

  • ②根据元素的哈希值 跟数组的长度计算出应存入的位置

  • ③判断当前位置是否为null,如果是null直接存入

  • ④如果位置不为null, 表示有元素,则调用equals方法比较属性值

  • ⑤一样:不存。不一样:存入数组,形成链表。JDK8以前:新元素存入数组,老元素挂在新元素下面。JDK8以后:新元素直接挂在老元素下面

注意事项:

  • ①JDK8以后,当链表长度超过8,而且数组长度大于等于64时,自动转换为红黑树

  • ②如果集合中存储的是自定义对象,必须要重写hashCode和equals方法

  • ③要保证元素唯一性,需要重写hashCode()equals()

  • ④hashSet通过hashCode和equals方法进行去重的

3.1.1.7 LinkedHashSet 集合

HashSet的子类。

原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。

集合特点:

  • ①哈希表和链表实现的Set接口,具有可预测的迭代次序

  • ②由链表保证元素有序,也就是说元素的存储和取出顺序是一致的

  • ③由哈希表保证元素唯一,也就说没有重复元素

3.1.1.8 TreeSet 集合

集合特点:

  • ①元素可排序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序取决于构造方法,默认对于数值类型是从小到大排序,对于字符串类型是按照ASCLL码的数值升序排序。

  • ②也有着Set集合的特点,所以也不包含重复的,无索引的。

  • TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。

构造方法:

  • TreeSet():根据元素的自然排序进行排序。

  • TreeSet(Comparator comparator这个也叫做比较器):根据指定的比较器进行排序。

TreeSet创建对象时不带参是自然排序,带参是比较器。

排序比较的方式:

  • ①默认排序/自然排序:Javabean类实现Comparable接口指定比较规则,然后重写compareTo方法指定排序规则。

  • ②比较器排序:创建TreeSet对象时候,传递比较器Comparator指定规则。(优先级更高)

在用匿名内部类实现Comparator接口时,需要重写compare(o1 , o2)方法,

参数o1:表示当前要添加的元素(即无序)

参数o2:表示已经在红黑树存在的元素(有序)

返回值规则同Comparable接口的compareTo方法。

Comparable接口

interface Comparable该接口实现对它每个类的对象加一个比较排序,这个就叫做自然排序

实现自定义排序:

先对自定义的类实现该接口,并且重写方法compareTo(T t)

this:表示当前要添加的元索(无序)

t:表示已经在红黑树存在的元素(有序)

若返回值为0,表示当前要添加的元素已经存在,舍弃

如果是正数,表示当前要添加的元素是大的,存右边

如果是负数,表示当前要添加的元素是小的,存左边

注意:字符串本来本身就实现了自然排序接口(默认是从第一个字符依次比较)

1
2
3
4
5
6
7
8
9
10
11
12
使用场景:
1.如果想要集合中的元素可重复
●用ArrayList集合, 基于数组的。( 用的最多)
2.如果想要集合中的元素可重复,而且当前的增删操作明显多于查询
●用LinkedList集合, 基于链表的。
3.如果想对集合中的元素去重
●用HashSet集合,基于哈希表的。 (用的最多)
4.如果想对集合中的元素去重,而且保证存取顺序
●用LinkedHashSet集合, 基于哈希表和双链表,效率低于HashSet。
5.如果想对集合中的元素进行排序
●用TreeSet集合, 基于红黑树。后续也可以用List集合实现排序。

3.1.2 双列集合

即每次可以添加一对元素,左键(唯一)右值(不唯一),键与值一一对应。键+值的整体,我们就叫键值对对象或Entry。

双列集合的体系结构如下:

> 整理提示:上方图片使用本地绝对路径 `D:\Users\作业\知识点\java\图片\双列集合体系结构.png`,如果在其他设备预览失败,需要把图片复制到笔记目录并改成相对路径。 默认: `HashMap` ( 效率最高)

如果要保证存取有序: LinkedHashMap

如果要进行排序: TreeMap

3.1.2.1 Map 集合

Interface Map<K,V> K:键的类型 V:值的类型

一个键可以对应多个值,但是键必须唯一存在。

创建Map集合的对象:

  • ①多态的实现

  • ②具体的实现类HashMap。如Map<Integer, Integer> counts = new HashMap<>();

方法:

  • ①V put(K key, V value):将指定的值与该映射的值键相关联即添加方法(如果后来有个键重复了,后面这个键值会覆盖掉前面的)

  • ②V remove(Object key):根据键删除对应的键值对元素

  • ③void clear():移除所有的键值对元素

  • ④boolean containsKey(Object key):判断集合是否包含指定的键

  • ⑤boolean containsValue(Object value):判断集合是否包含指定值

  • ⑥boolean isEmpty():判断集合是否为空

  • ⑦int size():集合的长度,也就说键值对的个数

  • ⑧map.getOrDefault(key,默认值):在Map中存在key,则返回key所对应的的value,不存在key,则返回默认值。

获取功能:

  • ①V get(Object key) :根据键获取值,如果没有返回null

  • Set keySet():获取所有键的集合

  • Collection values():获取所有值的集合,返回的是集合可以用Set集合来接收

遍历:

  • ①通过键找值:用map的Set keySet()方法获取所有键,然后使用迭代器或增强for或Lambda的forEach,使用map.get(key)方法根据键获取对应的值

  • ②通过键值对对象:用Set<Map.Entry<String,String>> entrySet()方法:获取所有键值对对象的集合,同时使用增强for循环在Map.Entry<String,String>里面有getKey和getValue获取值或键。

如:for (Map.Entry<Character, Integer> entry : map.entrySet())

  • ③通过Lambda表达式:利用map.forEach(),forEach方法底层其实就是利用上面的键值对对象的方法。

如下所示,简化前:

map.forEach(new BiConsumer<String, String>() {

@Override

public void accept(String key, String value) {

System.out.println(key + “=” + value);

}

);

简化后:

map.forEach( (key, value) -> System.out.println(key + “=” + val1ue) );

3.1.2.2 HashMap

特点:

  • HashMapMap里面的一一个实现类。

  • ②没有额外需要学习的特有方法,直接使用Map里面的方法就可以了。

  • ③特点都是由键决定的:无序、不重复、无索引

  • HashMapHashSet底层原理是一模一样的,都是哈希表结构

注意:

  • ①hashMap集合依赖equals方法和hashcode方法来保证键的唯一性。

  • ②如果键存储的是自定义对象,需要重写hashCode和equals方法

  • ③如果值存储自定义对象,不需要重写hashCode和equals方法

练习:存储自定义对象,利用Map集合进行统计

3.1.2.3 LinkedHashMap

他的父类是HashMap

特点:由键决定的:有序(指存储和取出的顺序是一致的),不重复,无索引

有序的原理:底层数据结构是依然哈希表,只是每个键值对元素又额外的多了-一个双链表的机制记录存储的顺序。

场景:大多数情况下,只要不涉及线程安全问题,Map基本都可以使用HashMap,不过HashMap有一个问题,就是迭代HashMap的顺序并不是HashMap放置的顺序,也就是无序。HashMap的这一缺点往往会带来困扰,因为有些场景,我们期待一个有序的Map

优点:它虽然增加了时间和空间上的开销,但是通过维护一个运行于所有条目的双向链表,LinkedHashMap保证了元素迭代的顺序。该迭代顺序可以是插入顺序或者是访问顺序。

4个关注点:

LinkedHashMap是否允许空:Key和Value都允许空

LinkedHashMap是否允许重复数据:Key重复会覆盖、Value允许重复

LinkedHashMap是否有序:有序

LinkedHashMap是否线程安全:非线程安全

3.1.2.4 TreeMap

TreeMapTreeSet底层原理一样, 都是红黑树结构的。

由键决定特性:不重复、无索引、可排序

可排序:对键进行排序。

注意:默认存储后按照键的从小到大进行排序,也可以创建对象时自己规定键的排序规则。

概述:TreeMap是一个能比较元素大小的Map集合,会对传入的key进行了大小排序。其中,可以使用元素的自然顺序,也可以使用集合中自定义的比较器来进行排序;

代码书写两种排序规则

●在对应自定义类实现Comparable接口, 指定比较规则。

●创建集合时传递Comparator比较器对象, 指定比较规则(优先级更高)。

要点:

TreeMap继承于AbstractMap,实现了Map, Cloneable, NavigableMap, Serializable接口。

(1)TreeMap 继承于AbstractMap,而AbstractMap实现了Map接口,并实现了Map接口中定义的方法,减少了其子类继承的复杂度;

(2)TreeMap 实现了Map接口,成为Map框架中的一员,可以包含着key–value形式的元素;

(3)TreeMap 实现了NavigableMap接口,意味着拥有了更强的元素搜索能力;

(4)TreeMap 实现了Cloneable接口,实现了clone()方法,可以被克隆;

(5)TreeMap 实现了Java.io.Serializable接口,支持序列化操作,可通过Hessian协议进行传输;

练习:计数

3.1.2.5 不可变集合

不允许添加,修改,删除等,不然会报错。

应用场景:

  • ①如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。

  • ②当集合对象被不可信的库调用时,不可变形式是安全的。

Map(创建时键不能重复,以及一次最多只能创10对,但传入为entry对象时就可以改变上限),ListSet(创建时不能有重复)接口中,都存在静态的of方法,可以获取一个不可变的集合。

创建方式:List<类型> lists = List.of(填数据)

利用HashMap创建一个不可变集合:Map<Object, object> map = Map.ofEntries( hm.entrySet().toArray(new Map.Entry[0]) ); 或者在jdk10后,可以用Map.copeOf(hm)方法生成不可变集合。

4 进阶1

4.1 继承

格式:public class 子类名 extends 父类名()

优势:

  • ①可以把多个子类中重复的代码抽取到父类中了,提高代码的复用性。

  • ②子类可以在父类的基础上,增加其他的功能,使子类更强大。

什么时候用继承:当类与类之间,存在相同(共性)的内容,并满足子类是父类中的一种,就可以考虑使用继承,来优化代码。

注意事项

  • ①当调用子类的构造方法时会先访问父类的无参构造方法(如果没有父类的无参构造方法会报错)。

  • ②java只支持单继承,可以多层继承,但一个类只能继承一个父类。

  • ③子类只能调用父类中的非私有的成员

  • ④一个类如果没有父类,默认继承自Object

子类到底能继承父类中的哪些内容

  • ①构造方法不管是私有还是非私有都不能继承。

  • ②成员变量不管是私有还是非私有都能继承,但私有的不能直接调用,私有的只能通过get和set方法进行调用。

  • ③成员方法非私有的可以继承,但私有的不能。实质是只有父类中的虚方法才能被子类继承,虚方法必须是非私有,非静态,非final的方法。

在继承中,

成员变量的特点:

  • ①就近原则;

成员方法的特点:

  • ①直接调用是就近原则;

构造方法的特点:

  • ①父类中的构造不会被子类继承。

  • ②子类中所有的构造方法默认会先访问父类中的无参构造,再执行自己。

  • ③子类构造方法的第一行语句默认都是 :super(),该语句是完成父类数据空间的初始化,不写也存在,且必须在第一行。

  • ④如果想调用父类的有参构造,必须用super。如super(name, age)即调用父类的带参构造。

4.2 super

用法:用于访问父类的成员变量或方法,而this用于访问本类的成员变量或方法

访问变量格式super.成员变量 访问成员方法super.成员方法()

使用总结:

  • ①代表父类存储空间

4.3 修饰符

  • ①权限修饰符:private<默认<protected<public

概述:是用来控制一个成员能够被访问的范围的。可以修饰变量,方法,构造方法,内部类。

> 整理提示:上方图片使用本地绝对路径 `D:\Users\作业\知识点\java\图片\权限修饰符.png`,如果在其他设备预览失败,需要把图片复制到笔记目录并改成相对路径。

特点:

(1)在自己java程序里面4个权限都可以访问。

(2)在同一个包下面其他java程序只能访问其他三个,private不能访问。

(3)在其他包下面并且和那个java程序有关系只能访问后面两个权限的。

(4)如果在其他包下且没有关系只能访问最后一个。

使用总结:

  • ①一般只用private和public。

  • ②成员变量私有,方法公开,如果方法中的代码是抽取其他方法中共性代码,这个方法一般也私有。

  • ②状态修饰符

(1)final(最终态)

  • ①被final修饰的方法是不可以被重写的。

  • ②被final修饰的成员变量就会变成常量不能再次被改变。

常量:单词全部大写,多个单词中间用下划线隔开。

  • ③被final修饰的类不能被继承啦。

  • ④只要被final修饰的东西就不能再被改变,如修饰引用类型则他的地址不能在被改变。

  • ⑤若final修饰的变量是基本类型,则变量存储的数据值不能再改变。若是引用类型,那么变量存储的地址值不能改变,但对象内部可以改变。

(2)static(静态)

静态变量

  • ①他被类的所有对象共享

  • ②可以通过类名调用(推荐),也可以通过对象调用。 例如:Student.name=”he”;

  • ③静态变量是随着类加载而加载,优先于对象出现的。

  • ④静态变量属于类,而不是属于对象。

静态方法

  • ①被静态修饰的成员方法只能访问静态的,非静态的都可以。

  • ②多在工具类和测试类中使用,Javabean很少用。

  • ③可以通过类名调用(推荐),也可以通过对象调用。

注意事项:

  • ①静态方法只能访问静态变量和静态方法。

  • ②非静态方法可以访问静态变量或者静态方法,也可以访问非静态的成员变量和非静态的成员方法。

  • ③静态方法中是没有this关键字。

4.4 多态

概述:同种类型的对象,表现出的不同形态。

多态中成员访问的特点

成员变量:编译看左边(即先看左边的类中有没有这个变量,若没有会编译失败),运行也看左边(即获取的是左边的)

成员方法:编译看左边(即要看左边有没有这个方法) ,运行看右边(即如果右边的类中重写了方法则会执行他的方法)

多态的好处:

  • ①提高了程序的扩展性。在多态形式下,右边的对象可以实现解耦合,便于扩展和维护。

  • ②定义方法的时候,使用父类型作为参数,可以接收所有子类对象,体现多态的扩展性与便利。

多态的弊端:不能使用子类特有的功能。

向下转型可以解决这个弊端:子类类名 子类对象名 = (子类类名)父类对象名;

但是注意不能随便转,只能转多态实现的那个子类类名。这里可以使用 if(对象名 instanceof 类名) 进行判断对象是否属于这个类。并且jdk14在这类型转换加了省略如 if(a instanceof Dog d) ,若a属于Dog类型则直接强转为Dog类型,且对象名为d。

向上转型(常用)即子类对象赋值给父类:父类类名 父类对象名 = new 子类类名();

多态的前提

  • ①有继承/实现关系。

  • ②有父类引用指向子类对象。如fu f = new Zi();

  • ③有方法重写。

多态的应用场景

  • ①管理系统中有多个身份的注册,如教师,管理员,学生,但需要一个共同的父类去接收所有类型的参数。

4.5 包

概述:其实就是文件夹;

作用:对类进行分类管理。

包名规则:公司域名反写 + 包的作用。 全部小写,见名知意。如com.itheima.domain

注意事项:

  • ①使用同一个包中的类时,不需要导包。

  • ②使用java.lang包中的类时,不需要导包。其他情况都需要导包。

  • ③如果同时使用两个包中的同名类,需要用全类名。

导包:

用不同包下的java程序里面的成员变量或方法时需要加上包的名字,例如:com.itheima.Teacher(类名) t = new com.itheima.Teacher();

也可以和类似导包scanner一样进行导包,例如:import com.itheima.Teacher(类名); //这样在使用的话前面就不用加包的名字啦

4.6 代码块

根据位置不同分为:

  • ①局部代码块(凉了)

概述:指的是写在方法里面的{ 代码块内容 }

作用:设计初衷是节约内存,如定义在代码块中的变量,只在代码块内有效。

  • ②构造代码块(凉了)

概述:指的是写在成员位置的代码块。

作用:是为了让构造方法中重复的内容抽取出来。

执行时机:在创建本类对象时,会先执行构造代码块后执行构造方法。

改进:因为他不够灵活,现在如果有重复的内容,会使用this(参数…)去替代或者用一个方法把重复内容写进去,然后调用即可。

  • ③静态代码块(重点)

格式:static{}

特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次。

使用场景:在类加载的时候,做一些数据初始化时使用的。

4.7 抽象类

抽象(abstract)方法:将共性的行为(方法)抽取到父类之后。由于每一个子类执行的内容是不一样,所以,在父类中不能确定具体的方法体。该方法就可以定义为抽象方法。

意义:相当于一个规范,因为子类必须强制重写,可以理解为一个轮廓。

注意事项

  • ①抽象方法没有方法体,如果一个类中有抽象方法,那么他必须是抽象类,在public后面加个abstract。

  • ②抽象类不能实例化(创建对象),他需要子类使用上转型来创建对象,并且子类必须重写父类的方法,如果是抽象子类则不用重写。

  • ③抽象类可以有构造方法。且可以没有抽象方法。

  • ④抽象类的子类,要么重写抽象父类的所有抽象方法,要么它本身也是一个抽象类。

抽象类中的成员特点:

  • ①成员变量:可以是变量,也可以是常量

  • ②构造方法:有构造方法,但是不能实例化

  • ③成员方法:可以有抽象方法:限定子类必须完成某些动作(即重写方法),也可以有非抽象方法:提高代码复用性。

4.8 接口

意义:(interface)可以理解为一种规则,比如拥有游泳功能的接口,一般共有的功能去弄成接口,是对行为的抽象。

注意事项:

  • ①类和接口之间用implements实现,可以单实现,也可以多实现。

  • ②接口的实体化和抽象一样也需要借用子类,不可以直接创建对象。

  • ③接口的实现类要么重写方法,要么定义为抽象类。

  • ④接口中的成员变量默认是public static final(即默认是常量,可以通过接口类之间访问)

  • ⑤接口中是没有构造方法,并且成员方法也是没有方法体

版本变化:

  • ①jdk7以前,接口只能定义抽象方法。

  • ②jdk8以后新增的方法:

第一种,允许在接口中定义默认方法,需要使用关键字default修饰

作用:解决接口升级的问题

接口中默认方法的定义格式:

格式:public default 返回值类型 方法名(参数列表){ }

范例:public default void show(){ }

接口中默认方法的注意事项**:**

  • ①默认方法不是抽象方法,所以不强制被重写。但是如果被重写,重写的时候去掉default关键字。

  • ②public可以省略, default不 能省略。

  • ③如果实现了多 个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写。

第二种,允许在接口中定义静态方法,需要使用关键字static修饰。格式同上

接口中静态方法的注意事项**:**

  • ①静态方法只能通过接口名调用,不能通过实现类名或者对象名调用。

  • ②public可以省 略, static不能省略。

  • ③JDK9的新特性:接口中可以定义私有方法。

作用:去给默认的方法去服务的,因为有一些重复的代码不需要其他类去访问。

格式1: private 返回值类型 方法名(参数列表){ }

范例1: private void show(){ }

格式2: private static 返回值类型 方法名(参数列表) { }

范例2: private static void method() { }

接口的应用

  • ①接口代表规则,是行为的抽象。想要让哪个类拥有一个行为,就让这个类实现对应的接口就可以了。

  • ②当一个方法的参数是接口时,可以传递接口所有实现类的对象,这种方式称之为接口多态。

适配器设计模式

作用解决接口与接口实现类之间的矛盾问题,简单来说就是我只想用接口中的某个方法,但是因为规范问题,如果是实现类必须全部重写,因此可以创建一个adapter中间的抽象类(不让外界创造他的对象),用中间类实现,中间类的方法全部是空实现(即没有方法体),再用原本的实现类去继承中间类,去重写需要的方法,而不用再实现接口了。

类和接口的关系

类和类的关系:继承关系,只能单继承,但可以多层继承。

类和接口的关系:实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口。

接口和接口的关系:继承关系,可以单继承,也可以多继承。

抽象类主要是对类的抽象,包括行为和属性,接口主要是对行为的抽象

4.9 形参和返回值

(1)类名作为形参和返回值

方法的形参是类名,其实需要的是该类的对象。

方法的返回值是类名,其实返回的是该类的对象。

(2)抽象类名作为形参和返回值

方法的形参是抽象名,其实需要的是该抽象类的子类对象。

方法的返回值是抽象类名,其实返回的是该抽象类的子类对象。

(3)接口类作为形参和返回值

方法的形参是接口名,其实需要的是该接口的实现类对象。

方法的返回值是接口名,其实返回的是该接口类的实现类对象。

4.10 内部类

概述:即在一个类里面声明另一个类。内部类表示的事物是外部类的一部分。

内部类的访问特点:

  • ①内部类可以直接访问外部类的成员,包括私有。

  • ②外部类要访问内部类的成员,必须创建对象。

按照内部类在类中定义位置不同,可以分为:成员内部类和局部内部类。

(1)成员内部类(即在类的成员位置,属于外部类的成员)

在成员内部类里面,JDK16之前不能定义静态变量, JDK 16开始才可以定义静态变量。

成员内部类可以被一些修饰符所修饰,比如: private, 默认, protected, public, static等。

若内部类中想要获取外部类的成员变量可以使用 外部类.this.成员变量

格式一:外部类编写方法,对外提供内部类对象。(一般当成员内部类被修饰为私有时用)

格式二:外部类名.内部类名 对象名=外部类对象.内部类对象

如:Animal.Cat cat = new Animal().new Cat(); 这里可以把new Animal()看成一个对象,后面即成员变量。

1.2 静态内部类

即成员内部类用static修饰就叫静态内部类。

静态内部类只能访问外部类中的静态变量和静态方法,如果想要访问非静态的需要创建对象。

创建静态内部类对象的格式:外部类名.内部类名 对象名= new 外部类名.内部类名();

调用非静态方法的格式:先创建对象,用对象调用方法。

调用静态方法的格式:外部类名.内部类名.方法名();

(2)局部内部类(即内部类在方法体中声明,类似于方法里面的局部变量)

外界无法直接使用,需要方法内部创建内部类对象,然后用对象调用方法即可。

该类可以直接访问外部类的成员,也可以访问方法内的局部变量。

(3)匿名内部类(是局部内部类的一种特殊形式,必须掌握)

他的本质就是隐藏了名字的内部类。可以写在成员位置,也可也写在局部位置。

格式: new 类名或者接口名(){重写方法}; 例如:new inter(){pulic void show(){}}; //他整体是一个对象,这里的show是重写了接口或者父类里面的方法。

本质:是一个继承了该类或者实现了该接口的子类匿名对象。

应用场景:实际上,如果我们希望定义一个只要使用一次的类,就可考虑使用匿名内部类。匿名内部类的本质作用是为了简化代码。

匿名内部类的特点**:**

  • ①定义一个没有名字的内部类

  • ②这个类实现了父类,或者父类接口

  • ③匿名内部类会创建这个没有名字的类的对象

可以通过多态的形式去使用,即他可以当作是接口或者类的一个实现类对象 如:Inter i = new Inter(){重写方法};

5 常用 API

API (Application Programming Interface):应用程序编程接口。

简单理解:API就是别人已经写好的东西,我们不需要自己编写,直接使用即可。

API帮助文档:一般是去索引里面搜索相关的类。

5.1 Math

Math类所在包为java.lang包,因此在使用的时候不需要进行导包。并且Math类被final修饰了,因此该类是不能被继承的。

Math类包含执行基本数字运算的方法,我们可以使用Math类完成基本的数学运算。

使用方法:Math.方法名();

常见方法:

1
2
3
4
5
6
7
8
public static int abs(int a)					// 返回参数的绝对值
public static double ceil(double a) // 返回大于或等于参数的最小整数
public static double floor(double a) // 返回小于或等于参数的最大整数
public static int round(float a) // 按照四舍五入返回最接近参数的int类型的值
public static int max(int a,int b) // 获取两个int值中的较大值
public static int min(int a,int b) // 获取两个int值中的较小值
public static double pow (double a,double b) // 计算a的b次幂的值
public static double random() // 返回一个[0.0,1.0)的随机值

5.2 System

System类所在包为java.lang包,因此在使用的时候不需要进行导包。并且System类被final修饰了,因此该类是不能被继承的。

System包含了系统操作的一些常用的方法。比如获取当前时间所对应的毫秒值,再比如终止当前JVM等等。

它里面有几个有用的类字段和方法,他不能被实例化,不能被实例化即代表不能创建对象。

常用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static long currentTimeMillis()
// 获取当前时间所对应的毫秒值(当前时间为0时区所对应的时间即就是英国格林尼治天文台旧址所在位置)
表示时间戳即从1970年到现在的毫秒差值,可以两个之间之差用来进行计算一个程序运行所耗时间
public static void exit(int status)
// 终止当前正在运行的Java虚拟机,0表示正常退出,非零表示异常退出
System.exit(0)即到这里执行时会使java停止下面的运行
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
// 进行数值元素copy
数组拷贝注意事项:
1.如果数据源数组和目的地数组都是基本数据类型,那么两者的类型必须保持一致,否则会报错
2.在拷贝的时候需要考虑数组的长度,如果超出范围也会报错
3.如果数据源数组和目的地数组都是引用数据类型,那么子类类型可以赋值给父类类型

5.3 Runtime

概述:Runtime表示Java中运行时对象,可以获取到程序运行时设计到的一些信息。

常用方法:

1
2
3
4
5
6
7
8
9
10
public static Runtime getRuntime()		//当前系统的运行环境对象
public void exit(int status) //停止虚拟机
public int availableProcessors() //获得CPU的线程数
public long maxMemory() //JVM能从系统中获取总内存大小(单位byte)
public long totalMemory() //JVM已经从系统中获取总内存大小(单位byte)
public long freeMemory() //JVM剩余内存大小(单位byte)
public Process exec(String command) //运行cmd命令

获取Runtime的对象:Runtime r1 =Runtime.getRuntime();
获得CPU的线程数:System.out.println(Runtime.getRuntime().availableProcessors());

5.4 Object

Object类所在包是java.lang包。Object 是类层次结构的根,每个类都可以将 Object 作为超类。所有类都直接或者间接的继承自该类;换句话说,该类所具备的方法,其他所有类都继承了。

Object类中提供了一个无参构造方法,Object()。但是一般情况下我们很少去主动的创建Object类的对象,调用其对应的方法。更多的是创建Object类的某个子类对象,然后通过子类对象调用Object类中的方法。

每一个类都可以将他作为父类,所有类都直接或间接的继承自该类

常用方法:

1
2
3
4
5
6
7
8
9
10
11
12
public String toString()				//返回该对象的字符串表示形式(可以看做是对象的内存地址值)
public boolean equals(Object obj) //比较两个对象地址值是否相等;true表示相同,false表示不相同
protected Object clone() //对象克隆

克隆的细节:
①方法会在底层帮助我们创建一个对象,并把原对象的数据拷贝过去
②要重写Object中的clone方法,且JavaBean要实现Cloneable接口,然后创建原对象并调用clone就可以。
③浅克隆在克隆引用数据类型时,会把地址复制过来,这样就相当于两个对象操控一个数据
④基本数据类型拷贝过来,字符串复用,引用数据类型会重新创建新的
⑤Object是浅克隆

但一般克隆会使用gson第三方工具。
  • ①里面包含有toString()方法,建议每一个子类都重写这个方法(因为一般是想要看对象的各个属性值),这个方法默认返回哈希值,如果直接输出对象则相当于调toString方法。

  • equals()方法,如果直接用equals方法则比较的是两个对象的地址,需要重写以后,就可以比较内容。

5.5 Objects

Objects类所在包是在java.util包下,因此在使用的时候需要进行导包。并且Objects类是被final修饰的,因此该类不能被继承。

Objects类提供了一些对象常见操作的方法。比如判断对象是否相等,判断对象是否为null等等。

Objects类中无无参构造方法,因此我们不能使用new关键字去创建Objects的对象。同时我们可以发现Objects类中所提供的方法都是静态的。因此我们可以通过类名直接去调用这些方法。

常用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static String toString(Object o) 					// 获取对象的字符串表现形式
public static boolean equals(Object a, Object b) // 比较两个对象是否相等
public static boolean isNull(Object obj) // 判断对象是否为null
public static boolean nonNull(Object obj) // 判断对象是否不为null


public static <T> T requireNonNull(T obj)
// 检查对象是否不为null,如果为null直接抛出异常;如果不是null返回该对象;
public static <T> T requireNonNullElse(T obj, T defaultObj)
// 检查对象是否不为null,如果不为null,返回该对象;如果为null返回defaultObj值
public static <T> T requireNonNullElseGet(T obj, Supplier<? extends T> supplier)
// 检查对象是否不为null,如果不为null,返回该对象;如果 // 为null,返回由Supplier所提供的值
上述方法中的T可以理解为是Object类型。

5.6 BigInteger

BigInteger所在包是在java.math包下,因此在使用的时候就需要进行导包。我们可以使用BigInteger类进行大整数的计算。

常见方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public BigInteger(int num, Random rnd) 		//获取随机大整数,范围:[0 ~ 2的num次方-1]
public BigInteger(String val) //获取指定的大整数
public BigInteger(String val, int radix) //获取指定进制的大整数

下面这个不是构造,而是一个静态方法获取BigInteger对象
public static BigInteger valueOf(long val) //静态方法获取BigInteger的对象,内部有优化


BigInteger(String value):构造方法,
BigInteger add(BigInteger value):加法,
BigInteger subtract(BigInteger value):减法,
BigInteger multiply(BigInteger value):乘法,
BigInteger divide(BigInteger divisor):除法,
BigInteger modInverse(BigInteger m):求模,
BigInteger pow(int exponent):乘方,
BigInteger max(BigInteger value):最大数,
BigInteger min(BigInteger value):最小数,
BigInteger abs():绝对值,
BigInteger negate():相反数,
int intValue():转化int,将BigInteger类型数据转为int。
BigInteger valueOf(long val):转为BigInteger,将long类型转为BigIntege类型

构造方法小结:

  • 如果BigInteger表示的数字没有超出long的范围,可以用静态方法获取。
  • 如果BigInteger表示的超出long的范围,可以用构造方法获取。
  • 对象一旦创建,BigInteger内部记录的值不能发生改变。
  • 只要进行计算都会产生一个新的BigInteger对象。

5.7 BigDecimal

背景:在使用float或者double类型的数据在进行数学运算的时候,很有可能会产生精度丢失问题。我们都知道计算机底层在进行运算的时候,使用的都是二进制数据; 当我们在程序中写了一个十进制数据 ,在进行运算的时候,计算机会将这个十进制数据转换成二进制数据,然后再进行运算,计算完毕以后计算机会把运算的结果再转换成十进制数据给我们展示; 如果我们使用的是整数类型的数据进行计算,那么在把十进制数据转换成二进制数据的时候不会存在精度问题; 如果我们的数据是一个浮点类型的数据,有的时候计算机并不会将这个数据完全转换成一个二进制数据,而是将这个将其转换成一个无限的趋近于这个十进数的二进制数据; 这样使用一个不太准确的数据进行运算的时候, 最终就会造成精度丢失;为了提高精度,Java就给我们提供了BigDecimal供我们进行数据运算。

BigDecimal所在包是在java.math包下,因此在使用的时候就需要进行导包。我们可以使用BigDecimal类进行更加精准的数据计算。

构造方法:

1
2
3
BigDecimal(int val):将int转换为BigDecimal
BigDecimal(long val):将long转换为BigDecimal
BigDecimal(String val):将BigDecimal的字符串表示形式为 BigDecimal

常用方法:

1
2
3
4
public BigDecimal add(BigDecimal value)				// 加法运算
public BigDecimal subtract(BigDecimal value) // 减法运算
public BigDecimal multiply(BigDecimal value) // 乘法运算
public BigDecimal divide(BigDecimal value) // 触发运算

5.8 正则表达式

在java中pattern类是正则表达式,Matcher类是文本匹配器(按照正则表达式的规则读取字符串,从头开始读取)。

作用:检验字符串是否满足规则,或在一段文本中查找满足要求的内容。

字符类

1
2
3
4
5
6
7
8
9
10
11
12
13
1. [abc]:代表a或者b,或者c字符中的一个。
2. [^abc]:代表除a,b,c以外的任何字符。
3. [a-z]:代表a-z的所有小写字符中的一个。
4. [A-Z]:代表A-Z的所有大写字符中的一个。
5. [0-9]:代表0-9之间的某一个数字字符。
6. [a-zA-Z0-9]:代表a-z或者A-Z或者0-9之间的任意一个字符。
7. [a-dm-p]:a 到 d 或 m 到 p之间的任意一个字符。

练习
8. [a-d[m-p]]:a到d,或m到p
9. [a-z&&[def]]:a-z和def的交集。为:d,e,f
10. [a-z&&[^bc]]:a-z和非bc的交集。(等同于[ad-z])
11. [a-z&&[^m-p]]:a到z和除了m到p的交集。(等同于[a-1q-z])

使用方法:public boolean matches(String regex):判断是否与正则表达式匹配,匹配返回true

注意事项:

  • ①[]表示一个字符的范围。

  • String中的split和replaceAll方法也能用正则表达式。

逻辑运算符

  1. &&:并且
  2. | :或者
  3. \ :转义字符,改变后面那个字符原本的含义

注意:两个\的理解方式:前面的\是一个转义字符,改变了后面\原本的含义,把他变成一个普普通通的\而已。

预定义字符

  1. “.” : 匹配任何字符。
  2. “\d”:任何数字[0-9]的简写;
  3. “\D”:任何非数字[^0-9]的简写;
  4. “\s”: 空白字符:[ \t\n\x0B\f\r] 的简写
  5. “\S”: 非空白字符:[^\s] 的简写
  6. “\w”:单词字符:[a-zA-Z_0-9]的简写
  7. “\W”:非单词字符:[^\w]

数量词

  1. X? : 0次或1次
  2. X* : 0次到多次
  3. X+ : 1次或多次
  4. X{n} : 恰好n次
  5. X{n,} : 至少n次
  6. X{n,m}: n到m次(n和m都是包含的)

分组括号( )

只看左括号,不看有括号,按照左括号的顺序,从左往右,依次为第一组,第二组,第三组等等

1
2
3
4
5
6
7
8
9
10
11
捕获分组就是把这一组的数据捕获出来,再用一次。
\\组号:表示把第X组的内容再出来用一次

(?i) :表示忽略后面数据的大小写
(.)表示把重复内容的第一个字符看做一组
+ 至少一次
$1 表示把正则表达式中第一组的内容,再拿出来用

非捕获分组:分组之后不需要再用本组数据,仅仅是把数据括起来。(不占用组号)
(?:)就是非捕获分组
(?:) (?=) (?!)都是非捕获分组//更多的使用第一个

编写正则的小心得:
第一步:按照正确的数据进行拆分
第二步:找每一部分的规律,并编写正则表达式
第三步:把每一部分的正则拼接在一起,就是最终的结果
书写的时候:从左到右去书写。

小结:

> 整理提示:上方图片使用本地绝对路径 `D:\Users\作业\知识点\java\图片\正则表达式1.png`,如果在其他设备预览失败,需要把图片复制到笔记目录并改成相对路径。 > 整理提示:上方图片使用本地绝对路径 `D:\Users\作业\知识点\java\图片\正则表达式2.png`,如果在其他设备预览失败,需要把图片复制到笔记目录并改成相对路径。

5.9 Date时间类

格林尼治时间/格林威治时间( Greenwich Mean Time )简称GMT。

目前世界标准时间(UTC)已经替换为:原子钟

Date类是一个JDK写好的Javabean类,用来描述时间,精确到毫秒。

利用空参构造创建的对象,默认表示系统当前时间。

利用有参构造创建的对象,表示指定的时间。

直接创建一个Date的对象直接输出,会输出当前的时间(因为重写toString方法啦)

常用方法:

  • ①public long getTime()获取的是从1970年1月1号到现在的毫秒值

  • ②public long setTime(long time)方法用来设置时间给的是毫秒值

5.10 SimpleDateFormat类

作用如下

格式化:把时间变成我们喜欢的格式。

解析:把字符串表示的时间变成Date对象。

重点学习这个类的日期格式化和解析,这里面日期和时间格式由模式字符串指定其中:y年 M月 d日 H时 m分 s秒

构造方法:

  • ①public SimpleDateFormat():构造一个SimpleDateFormat,使用默认模式和日期格式

  • ②public SimpleDateFormat(String pattern):构造一个SimpleDateFormat,使用指定的模式和日期

格式化和解析日期

格式化:从Date到String public final String format(Date date):将日期格式和成日期/时间字符串

解析:从String到Date public Date parse(String source):从给定字符串的开始解析文本生成日期

5.11 Calendar类

Calendar代表了系统当前时间的日历对象,可以单独修改、获取时间中的年,月,日

细节: Calendar是一个抽象类,不能直接创建对象。

这个类是和日历相关,这个类里面的月份是从0开始

获取该对象的方法:Canlendar 名字 = Calendar.getInstance()

直接输出对象里面的东西太复杂可以进行选择:用public int get(int field)进行选择

名字.get(Calendar.YEAR)获取年份 get(Calendar.MONTH)获取月份-1 get(Calendar.DATE)获取日期

常用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public final Date getTime():获取日期对象
public final setTime(Date date):给日历设置日期对象
public long getTimeInMillis():拿到时间毫秒值
public void setTimeInMillis(1ong millis):给日历设置时间毫秒值
public int get(int field):取日历中的某个字段信息,这个field可以去Calendar里面查看那些静态常量。
public void set(int field,int value):修改日历的某个字段信息
public void add(int field, int amount):为某个字段增加/减少指定的值
例如:c.add(Calendar.YEAR, -3)意思为对c的日历进行减3年

jdk8以后新增的时间类:
Date类
ZoneId:时区
static Set<String> getQvailableZoneIds():获取Java中支持的所有时区
static ZoneId systemDefault():获取系统默认时区
static ZoneId of(String zoneId):获取一个指定时区

Instant:时间戳
static Instant now( ):获取当前时间的Instant对象( 标准时间)
static Instant ofXxxx(long epochMilli):根据(秒/毫秒/纳秒)获取Instant对象
ZonedDateTime atZone(ZoneId zone):指定时区
boolean isXxx( Instant otherInstant):判断系列的方法
Instant minusXxx(long millisToSubtract):减少时间系列的方法
Instant p1usXxx(long millisToSubtract ):增加时间系列的方法

ZoneDateTime:带时区的时间
static ZonedDateTimeL now( ):获取当前时间的ZonedDateTime对象
static ZonedDateTime ofXxxx(...):获取指定时间的ZonedDateTime对象
ZonedDateTime withXxx( 时间):修改时间系列的方法
ZonedDateTime minusXxx(时间):减少时间系列的方法
ZonedDateTime p1usXxx( 时间):增加时间系列的方法


日期格式化类
DateTimeFormatter用于时间的格式化和解析
static DateTimeFormatter ofPattern(格式):获取格式对象
String format( 时间对象):按照指定方式格式化


日历类
LocalDate:年、月、日
LocalTime:时、分、秒
LocalDateTime:年、月、日、时、分、秒
static XXX now( ):获取当前时间的对象
static XXX of(。。。):获取指定时间的对象
get开头的方法:获取曰历中的年、月、日、时、分、秒等信息
isBefore, isAfter:比较两个LocalDate
with开头的:修改时间系列的方法
minus开头的:减少时间系列的方法
plus开头的:增加时间系列的方法

public LocalDate toLocalDate():LocalDateTime转换成一个LocalDate对象
public LocalTime toLocalTime():LocalDateTime转换成一个LocalTime对象


工具类
Duration:时间间隔(秒,纳秒)
Period:时间间隔(年,月,日)
ChronoUnit:时间间隔(所有单位)

5.12 Arrays

概述:他是util包下的一个工具类,他里面包含有用于操作数组的各种方法。

工具类的设计思想:构造方法用private修饰,成员用public static修饰

方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public static String toString(数组):把数组拼接成一个字符串
public stati int binarySearch(数组,查找的元素):二分查找法查找元素
二分查找的前提是数组必须有序,如果查找的元素存在则返回真实的索引,若查找的元素不存在则返回插入点-1。
public static int[] copyOf(原数组,新数组长度):拷贝数组
public static int[] copyOfRange(原数组,起始索引,结束索引):拷贝数组( 指定范围)
public static void fill(数组,元素):填充数组
public static void sort(数组):按照默认方式进行数组排序
public static void sort(数组,排序规则):按照指定的规则排序,这里的数组必须是包装类
第二个参数是一个接口,所以我们在调用方法的时候,需要传递这个接口的实现类对象,作为排序的规则。
但是这个实现类,我只要使用一次, 所以就没有必要单独的去写一个类,直接采取匿名内部类的方式就可以了。

sort的底层是 插入排序+二分查找进行排序的。
//他默认把0索引的数据当做是有序的序列,1索引到最后认为是无序的序列。
//遍历无序的序列得到里面的每一个元素,假设当前遍历得到的元素是A元素
//把A往有序序列中进行插入,在插入的时候,是利用二分查找确定A元素的插入点。
//拿着A元素,跟插入点的元素进行比较,比较的规则就是compare方法的方法体
//如果方法的返回值是负数,拿着A继续跟前面的数据进行比较
//如果方法的返回值是正数,拿着A继续跟后面的数据进行比较
//如果方法的返回值是0,也拿着A跟后面的数据进行比较
//直到能确定A的最终位置为止。
参数o1:表示在无序序列中,遍历得到的每一一个元素
参数o2:有序序列中的元素
//返回值:
//负数:表示当前要插入的元素是小的,放在前面
//正数:表示当前要插入的元素是大的,放在后面
//0:表示当前要插入的元素跟现在的元素比是一样的们也会放在后面。

//简单理解:
//o1 - o2 :升序排列
//o2 - o1 :降序排序
如Arrays.sort(arr, new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2){
return o1 - o2;
}
});


5.13 Collections

概述:是一个针对集合操作的工具类

方法:

(他们都是根据Comparable自然排序)

public static void sort(List list):将列表进行升序排序

public static void sort(List list,Comparator<? super T> ):将集合中元素按照指定规则排序。

void reverse(List list):将列表元素进行反转排序

public static void shuffle(List list):将列表随机排序

public static boolean addAll(Collection q , T… elements ):批量添加元素

sort也可以和比较器Comparator相结合使用。如sort(arraylist,new Comparator(){重写compare方法})

6 进阶2

6.1 异常类

异常体系结构

> 整理提示:上方图片使用本地绝对路径 `D:\Users\作业\知识点\java\图片\异常类体系结构.png`,如果在其他设备预览失败,需要把图片复制到笔记目录并改成相对路径。

其中Error:出现严重问题,开发人员不需要处理,由sun公司处理。

Exception:称为异常类,他表示程序本身可以处理的问题

RuntimeException:运行时异常(即从字节码文件到运行结果的时期),在编译器是不检查的,出现问题后,需要我们回来修改代码。

非RuntimeException:在编译期(即从java文件变为字节码文件时的时期)就必须处理,否则程序不能通过编译,更不能运行了。

Throwable类的成员方法

  • ①public String getMessage():返回throwable的详细消息字符串

  • ②public String toString():返回此可抛出的简短描述,这个方法包含了第一个输出的信息

  • ③public void printStackTrace():把异常的错误信息输出在控制台

异常的处理方式

  • JVM默认的处理方式

把异常的名称,异常原因及异常出现的位置等信息输出在了控制台。程序停止执行,下面的代码不会再执行了。

  • ②自己处理(让程序继续执行)

try-catch

格式:try{可能出现异常的代码}catch(要捕获的异常类名 变量名){异常后所做的处理}

特点:

  • ①这种try-catch处理完以后程序会继续运行。

  • ②并且catch只会在try里面的代码出现异常才会执行。

  • ③在try中若出现多个异常且catch只捕获了一个,那程序依旧会执行,但在try中出现异常之后的代码就会跳过去。一般我们会捕获多个异常,且辈分大的异常要放在下面。

  • ④在jdk7以后,可以捕获多个异常,参数中间的异常类用|隔开。

  • ⑤如果try中遇到了问题,那么try下面的其他代码还会执行吗?下面的代码就不会执行了,直接跳转到对应的catch当中,执行catch里 面的语句体。但是如果没有对应catch与之匹配,那么还是会交给虚拟机进行处理。

建议使用:catch(Exception e){e.printStackTrace();}

  • ③抛出异常(告诉调用者出错)

throws

概述:写在方法定义处,表示声明一个异常。告诉调用者,使用本方法可能会有哪些异常。

格式:throws 异常类名1 , 异常类名2等;

特点:他是抛出异常,和默认一样如果出错了则后面还是不允许执行。运行时异常可省略不写,编译时异常必须写。

throw

概述:写在方法内,结束方法。手动创建异常,抛出异常对象,交给方法的调用者。方法中下面的代码不再执行了

自定义编译时异常

目的:就是为了让控制台的报错信息更加的见名之意,如NameFormatExcetpion表示名字格式化异常。

使用步骤:

  • ①首先要先创建一个类,让他继承自Exception,

  • ②然后重写两个构造方法,一个无参,一个参数为(String message),并且内容为super(message)。

  • ③然后在其他测试类出现异常的地方用throw new 自定义异常类(参数可以写自定义报错的信息),并且编译时异常必须用throws抛出。

作用:提醒强烈,一定要处理

自定义运行时异常

目的:就是为了让控制台的报错信息更加的见名之意。

使用步骤:

  • ①首先要先创建一个类,让他继承自RuntimeException,

  • ②然后写两个构造方法,一个无参,一个参数为(String message),并且内容为super(message)

  • ③然后在其他测试类出现异常的地方用throw new 自定义异常类名(参数可以写自定义报错的信息)

作用:提醒不强烈,运行时才去报错

throws和throw的区别

  • ①throws用在方法声明后面,跟的是异常类名。throw用在方法体内,跟的是异常对象名。

  • ②throws表示抛出异常,由该方法的调用者来处理。throw表示抛出异常,由方法体内的语句处理。

  • ③throws表示出现异常的一直可能性,并不一定会发生。throw执行throw一定抛出了某种异常。

  • ④throw是在方法内部直接创建了一个异常对象,并从此点抛出。

6.2 泛型

概述:本质是参数化类型

好处:统一了数据类型。

细节:

  • ①泛型只支持引用数据类型,且java中的泛型是伪泛型(即只在编译时进行限定)。

  • ②指定泛型的具体类型后,传递数据时,可以传入该类类型或者其子类类型,

  • ③如果不写泛型,类型默认是0bject

泛型类定义格式:

public class 类名<类型>:指定一种类型的格式,一般类型写为T,K,E,V等

使用场景:当一个类中,某个变量的数据类型不确定时,就可以定义带有泛型的类。

泛型方法格式:

修饰符<类型> 返回值类型 方法名(类型 变量名){}

使用场景:方法中形参不确定时

方案①:使用类名后面定义的泛型

方案②:在方法申明上定义自己的泛型

泛型接口格式:

修饰符 interface 接口名<泛型>{},他需要有个实现类,实现类也需要用泛型类

用法一:实现类给出具体类型

用法二:实现类延续泛型,创建对象时再确定

6.3 泛型的继承和通配符

泛型不具备继承性,但是数据具备继承性。

解决方法:泛型通配符 ? 。泛型通配符可以限定泛型的范围。

用法一: ? extends E表示可以传递E或E所有的子类类型

用法二: ? super E表示可以传递E或E所有的父类类型

6.4 可变参数

概述:就是可以有多个参数,使用时会将他们都封装到数组里面,并且可变参数只能放在后面。

格式:方法名(数据类型…变量名)

可变参数的使用:

Arrays工具类有一个静态方法(不可以增删,可以修改)

public static List aslist(T… a):返回由指定数组支持的固定大小的列表

List接口中有一个静态方法(不可以增删改)

public static List of(E… elements):返回包含任意数量元素的不可变列表

Set接口中有一个静态方法(再给元素时,不能给重复的元素)

public static Set of(E… elements):返回一个包含任意数量元素的不可变集合

6.5 Lambda表达式

函数式编程(是一种思想)的一种表现。

作用:简化匿名内部类的代码写法

简化格式:(匿名内部类被重写方法的形参列表) -> {

被重写方法的方法体代码

}

注意:-> 只是一个语法形式

Lambda表达式只能简化函数式接口的匿名内部类的写法形式。

例子:Arrays. sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer 01, Integer o2) {
return o1 - o2;
}
});
简化后:
Arrays. sort(arr, (Integer 01,Integer 02) -> {
return 01 - 02;
}
);

扩展
什么式函数式接口:
首先必须是接口,其次接口中有且仅有一个抽象方法的形式,接口上方可以加@FunctionalInterface注解

简化规则:

  • ①参数类型可以省略不屑
  • ②如果只有一个参数,参数类型可以省略,同时()也可也省略
  • ③如果Lambda表达式的方法体代码只有一行代码,可以省略大括号不写,同时要省略分号!
  • ④如果Lambda表达式的方法体代码只有一行代码,可以省略大括号不写。此时,如果这行代码是return语句,必须省略return不写,同时也必须省略分号

6.6 Stream

目的:结合了Lambda表达式,用于简化集合和数组操作的API。

使用步骤:

  • ①先获取stream流

  • ②使用中间方法对流的数据进行操作

  • ③使用终结方法对流的数据进行操作

获取流:

  • ①单列集合

default Stream stream():Collection中的默认方法

  • ②双列集合

无:无法直接使用stream流,需要先用keySet或entrySet方法先转成单列集合再使用。

  • ③数组

public static Stream stream(T[ array):Arrays工具类中的静态方法

  • ④一堆零散数据

public static Stream of(T… values):Stream接口中的静态方法,这里的of如果装入引用数据类型,会把它当成整体存进去。

数组:Arrays.stream(数组) / Stream.of(数组)

常用的中间方法(即用完还能再次使用):

Stream filter(Predicate<? super T> predicate):过滤元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
例如:把张开头留下,其余不要
//而test方法中的s,就依次表示流中的每一个数据.
//我们只要在test方法中对s进行判断就可以了.
list.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
//如果判断的结果为true,则当前的数据留下
//如果判断的结果为false,则当前数据就不要.
boolean result = s.startsWith("张");
return result;
}
}
).forEach(s-> System.out.println(s));
简化后:list.stream().filter(s -> s.startsWith('张'))

Stream skip(long n):跳过前几个元素

Stream limit(long maxSize):获取前几个元素

Stream distinct():去除流中的重复元素。依赖hashCode和equals方法

static Stream concat(Stream a, Stream b):合并a和b两个流为一个流(尽量两个数据类型一样)

boxed:作用就是将int类型的stream转成了Integer类型的Stream

Stream map(Function<T, R> mapper):转换流中的数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
例如:给出"张无忌-15",只要其中15
//Function中的第一个类型:流中原本的数据类型
//第二个类型: 要转成之后的类型
//当map方法执行完毕之后,流上的数据就变成了整数
list.stream().map(new Function<String,Integer>(){
//apply的形参s:依次表示流里面的每一个数据
@Override
public Integer app1y(String s) {
String[] arr = s.sp1it("-");
String ageString = arr[1];
int age = Integer.parseInt(ageString);
//返回值:表示转换之后的数据
return age;
//所以在下面forEach当中,s依次表示流里面的每一个数据,这个数据现在就是整数了
}
}).forEach(s -> System.out.println(s));
简化后:list.stream().map(s -> Integer.parseInt(s.split("-")[1]));

注意:

  • ①上面的方法调用完成后的stream流可以继续使用,注意原本流只能用一次,推荐链式编程。

  • ②修改流中的数据是不会影响原本的数据的。

终结方法(用完不能在使用流了)

long count():返回此流中的元素数

void forEach(Consumer action匿名内部类,可用lambda表达式简化):对此流的每个元素执行操作

toArray():收集流中的数据,放到数组中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
例如:
//IntFunction的泛型:具体类型的数组
//toArray方法的参数的作用:负贵创建一个指定类型的数组
//toArray方法的底层,公依次得到流里面的每一一个 数据。并把数据放到数组当中
//toArray方法的返回值:是一个装着流里面所有数据的数组
String[] arr = list.stream( ).toArray(new IntFunction<String[]>() {
//app1y的形参:流中数据的个数,要跟数组的长度保持一致
//app1y的返回值:具体类型的数组
//方法体:就是创建数组
@Override
public String[] apply(int value) {
return new String[value];
}
});
System.out.printIn(Arrays.toString(arr));
简化后:list.stream( ).toArray(value -> new String[value]);

collect(Collector collector):收集流中的数据,放到集合中(ListSetMap)。

形参可以是Collectors.toList(),Collectors.toSet(),Collectors.toMap(键的规则new Function,值的规则new Function)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
例如:如张无忌-男-15,把类似的数据中的男性都收集起来。键-姓名,值-年龄
注意toMap这个方法,键一定不能重复。

键的规则参数一:
Function泛型一:表示流中每一个数据的类型
泛型二:表示Map集合中键的数据类型
方法apply形参:依次表示流里面的每一个数据
方法体:生成键的代码
返回值:已经生成的键
值的规则参数二:
Function泛型一:表示流中每一个数据的类型
泛型二:表示Map集合中值的数据类型
方法apply形参:依次表示流里面的每一个数据
方法体:生成值的代码
返回值:已经生成的值
Map<String, Integer> map = list.stream()
.filter(s -> "男".equals(s.sp1it("-")[1]))
.col1ect(Co11ectors.toMap(new Function<String, String>() {
@Override
public String app1y(String s) {
//张无忌-男-15
return s.split( regex: "-")[0];
}
},new Function<String, Integer>() {
@Override
public Integer app1y(String s) {
return Integer . parseInt(s.split( regex: "-")[2]);
}
));
简化后:Map<String, Integer> map2 = list.stream()
.filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors. toMap(s -> s.split("-")[0],s -> Integer.parseInt(s.split( regex: "-")[2])));

6.7 方法引用

概述:把已经有的方法拿过来用,当做函数式接口中抽象方法的方法体。

条件:

  • ①引用处必须是函数式接口

  • ②被引用的方法必须已经存在

  • ③被引用方法的形参和返回值,需要跟抽象方法保持一致

  • ④被引用方法的功能要满足当前需求

1
2
3
4
5
6
7
8
9
10
11
例子:
//表示引用FunctionDemo1类里面的subtraction方法
//把这个方法当做抽象方法的方法体,::是方法引用符
Arrays.sort(arr, FunctionDemo1::subtraction);

//可以是Java已经写好的,也可以是一些第 三方的工具类
public static int subtraction(int num1, int num2) {
return num2 - num1;
}

//方法引用往往搭配stream流一起引用

方法引用的分类

引用静态方法

格式:类名::静态方法

1
2
例子:把list集合都变成int型
list.stream().map(Integer::parseInt).forEach(s-> System.out.println(s));

引用成员方法

格式:对象::成员方法

其他类格式:其他类对象::方法

本类格式:this::方法名,若是静态方法(没有this),则创建本类对象,在使用上述格式。

父类格式:super::方法名

引用构造方法

目的:创建这个类的对象

格式:类名::new

使用类名引用成员方法

格式:类名::成员方法

与上述的条件不同:

  • ①需要有函数式接口(同上述条件)

  • ②被引用的方法必须已经存在(同上述条件)

  • ③被引用方法的形参,需要跟抽象方法的第二个形参到最后一个形参保持一致, 返回值需要保持一致。

  • ④被引用方法的功能需要满足当前的需求(同上述条件)

抽象方法形参的详解:

第一个参数:表示被引用方法的调用者,决定了可以引用哪些类中的方法。在Stream流当中,第一个参数一般都表示流里面的每一个数据。假设流里面的数据是字符串,那么使用这种方式进行方法引用,只能引用String这个类中的方法。

第二个参数到最后一个参数:跟被引用方法的形参保持一致, 如果没有第二个参数,说明被引用的方法需要是无参的成员方法。

该方法的局限性:

  • ①不能引用所有类中的成员方法。

  • ②是跟抽象方法的第一一个参数有关,这个参数是什么类型的,那么就只能引用这个类中的方法。

引用数组的构造方法

格式:数据类型[]::new

目的:创建一个指定类型的数组,注意数组类型要和流中数据的类型保持一致。

6.8 日志

作用:可以把程序在运行过程中的详细信息都打印在控制台上,还可以把这些详细信息保存到文件和数据库中。

使用步骤:

  • ①把第三方的代码导入到当前的项目当中

  • ②新建lib文件夹,把jar粘贴到lib文件夹当中,全选后右键点击选择add as a ….

  • ③检测导入成功:导入成功后jar包可以展开。在项目重构界面可以看到导入的内容

  • ④把配置文件粘贴到src文件夹下

  • ⑤在代码中获取日志对象

  • ⑥调用方法打印日志

日志级别:TRACE, DEBUG, INFO, WARN, ERROR。以及ALL:输出所有日志。OFF:关闭所有日志

大小:TRACE < DEBUG < INFO < WARN < ERROR

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
配置文件案例1:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
CONSOLE :表示当前的日志信息是可以输出到控制台的。
-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--输出流对象 默认 System.out 改为 System.err-->
<target>System.out</target>
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c [%thread] : %msg%n</pattern>
</encoder>
</appender>

<!-- File是输出的方向通向文件的 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!--日志输出路径-->
<file>C:/code/itheima-data.log</file>
<!--指定日志文件拆分和压缩规则-->
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--通过指定压缩文件名称,来确定分割文件方式-->
<fileNamePattern>C:/code/itheima-data2-%d{yyyy-MMdd}.log%i.gz</fileNamePattern>
<!--文件拆分大小-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
</appender>

<!--

level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
, 默认debug
<root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
-->
<root level="info">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE" />
</root>
</configuration>

日志常见两个接口规范:

  • ①Commons Logging(不好用)

  • ②Simple Logging Facade for java(slfj4)

他的实现类有Logback(重点学习)

Logback

Logback主要分为三个技术模块:

logback-core:他为其他两个模块奠定了基础,必须有

logback-classic:它是log4j的一个改良版本,同时他完整实现了slf4j API。

logback-access:模块与Tomcat和jetty等Servlet容器集成,以提供HTTP访问日志功能

logback快速入门

使用步骤:

  • ①在新的项目里创建lib文件,然后把他们复制进去,然后右键点击add library,将他们填到这项目依赖里面

  • ②将logback的核心配置文件xml直接复制到src下

  • ③在代码中获取日志的对象

public static final Logger LoGGER = LoggerFactory.getLogger(“类名.class“);

  • ④调用方法进行日志记录就行

6.9 JUnit 单元测试框架

使用:导入jar包或者maven导入。Assert实现类里面有许多方法

步骤:

  • ①新建测试类

  • ②新建测试方法(要测试的方法名 + Test)

  • ③在这个方法中直接调用要测试的方法

  • ④在测试方法的上面写@Test

注意:

  • ①方法必须是公开的,无参数,无返回值的非静态方法

  • ②测试方法必须用Test注解标记

  • ③想要运行谁,就右键点击哪个方法。如果想要运行一个类里面所有的测试方法,选择类名,有点点击即可。

  • ④测试不污染原数据。(修改,篡改)

  • ⑤在单元测试中,相对路径是相对当前模块而言的。

6.10 反射

概述:反射允许对封装类的字段,方法和构造函数的信息进行编程访问。反射是指对于任何一个CLass类,在运行的时候都可以直接得到这个类全部成分。

  • ①获取Class类的对象三种方式:

1.Class.forName(“全类名”):获取该class,全类名即包名+类名

2.类名.class:获取该class

3.对象名.getClass():获取该Class

  • ②根据Class类对象获取构造器对象

1.Constructor<?>[] getConstructors():返回所有构造器对象的数组(只拿public)

2.Constructor<?>[] getDeclareConstructors():返回所有构造器对象的数组,存在就能拿

3.Constructor[] getConstructor(`Class`… parameterTypes):返回单个public构造器对象,常用

4.Constructor[] getDeclareConstructor(`Class`… parameterTypes):返回单个构造器对象,存在就能拿到,常用

获取构造器对象以后调用对应方法获取值

1.newInstance(“输入参数或无”):创建一个新的对象,可能需要强转

2.setAccessible(true):权限被打开,可以使用私有构造器,仅限一次

  • ③根据Class对象获取成员变量对象

1.Field[] getDeclaredFields():获取全部成员变量

2.Field[] getFields(): 返回所有公共成员变量对象的数组

3.Field getDeclaredField(String name):获取某个成员变量

4.Field getField(String name):返回单个公共成员变量对象

获取成员变量后可以进行一系列操作:

1.set(对象,值):赋值

2.setAccessible(true):暴力打开权限

2.get(对象):取这个对象的值,可能需要强转

  • ④根据Class对象获取成员方法对象

1.Method[] getMethods():返回所有public成员方法对象的数组

2.Method[] getDeclaredMethods():同上返回所有的

3.Method getMethod():返回单个public的成员方法对象

4.Method getDeclaredMethod(name,类型.class):返回单个成员方法对象,存在就能拿到

获取对象以后使用该对象进行执行此方法

1.Object invoke(Object obj,Object… args):运行方法。

参数一:用boj对象调用该方法

参数二:调用方法的传递的参数(如果没有就不写)

返回值:方法的返回值(如果没有就不写)

注意:方法如果没有结果回来,那么返回的是null

反射的作用:

  • ①绕过编译阶段为集合添加数据(跳过泛型的编译约束)

先获得集合的class,然后获得add方法的对象,然后执行方法即可。

  • ②通用框架的底层原理

6.11 动态代理

概述:代表类是java.lang.reflect.Proxy

特点:无侵入式的给代码增加额外的功能。

java通过接口来实现代理,接口中就是被代理的所有方法。后面的对象和代理需要实现同一个接口

方法:

  • ①static Object newProxyInstance(Classloder loder,Class<?> interfaces,invocationHandler h):用于为对象产生一个代理对象返回。

参数一:用于指定用哪个类加载器,去加载生成的代理类

参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法。代理类要实现的接口列表

参数三:用来指定生成的代理对象要干什么事情。将方法调用分派到的处理程序(代理对象的核心处理程序)

使用步骤:

  • ①先自定义代理类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
书写核心方法
public static Star createProxy(BigStar bigStar){
Star star = (Star)Proxy.newProxyInstance( //这里的Star是一个接口
ProxyUti1. class. getClassLoader(),
new Class[]{Star.class},
//指定代理要干哪些事情
new InvocationHandler() {
@Override
public object invoke(0bject proxy, Method method, object[] args) throws Throwable {
//参数一:代理的对象
//参数二:要运行被代理的方法
//参数三:调用方法时,传递的实参

return nu11;
}
}
);
return star;
}

执行流程:

  • ①先走向代理

  • ②代理可以为方法额外做一些辅助工作。

  • ③开发真正触发对象的方法的执行。

  • ④回到代理中,由代理负责返回结果给方法的调用者。

注意:

  • ①必须存在接口

  • ②被代理对象需要实现接口

  • ③使用Proxy类提供的方法,的对象的代理对象

6.12 注解

概述:注解,是给虚拟机看的。当虚拟机看到注解之后,就知道要做什么事情了。

Java中自带注解:

1
2
3
@Override:表示方法的重写
@Deprecated:表示修饰的方法已过时
@SuppressWarnings("all"):压制警告

Junit中的注解:

1
2
3
@Test 表示运行测试方法
@Before 表示在Test之前运行,进行数据的初始化
@After 表示在Test之后运行,进行数据的还原

自定义注解

作用:一般会跟反射结合起来使用,会用反射去解析注解。

格式:public @interface 注解名称{

public 属性类型 属性名() default 默认值;

}

使用:需要先定义一个注解接口,然后在里面定义属性值

然后在类或者方法上面可以进行使用自定义注解

注意:有默认值的时候使用的时候前面的属性名可以省略,并且若只有一个属性值也可也直接省略

元注解

概述:就是注解注解的注解(就是放在自定义注解接口上面的)

分类:

  • ①@Target:约束自定义注解智能在哪些地方使用

  • ②@Retention:申明注解的生命周期

使用格式:

@Target中可使用的值定义在ElementType枚举类中,常用值如下

  • ①TYPE,类,接口

  • ②FIELD,成员变量

  • ③METHOD,成员方法

  • ④PARAMETER,方法参数

  • ⑤CONSTRUCTOR,构造器

  • ⑥LOCAL_VARIABLE,局部变量

@Retention中可使用的值定义在RetentionPolicy枚举类中,常用值如下

  • ①SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在

  • ②CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值.

  • ③RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用)

注解解析

概述:注解的操作中经常需要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容。

注解解析相关的接口

  • ①Annotation:注解的顶级接口,注解都是Annotation类型的对象

  • ②AnnotatedElement:该接口定义了与注解解析相关的解析方法

方法

  • ①Annotation[] getDeclaredAnnotations():获得当前对象上使用的所有注解,返回注解数组。
  • ②T getDeclaredAnnotation(Class annotationClass):根据注解类型获得对应注解对象
  • ③boolean isAnnotationPresent(Class annotationClass):判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false
    注意:所有的类成分Class, Method , Field , Constructor,都实现了AnnotatedElement接口他们都拥有解析注解的能力

使用:

  • ①先获取相应的对象

  • ②然后调用上面的方法

  • ③获取该注解的对象

解析注解的技巧

  • ①注解在哪个成分上,我们就先拿哪个成分对象。

  • ②比如注解作用成员方法,则要获得该成员方法对应的Method对象,再来拿上面的注解

  • ③比如注解作用在类上,则要该类的Class对象,再来拿上面的注解

  • ④比如注解作用在成员变量上,则要获得该成员变量对应的Field对象,再来拿上面的注解

7 文件IO流学习

7.1 File

概述:File对象就表示一个路径,可以是文件的路径、也可以是文件夹的路径。它支持绝对路径,也支持相对路径。

File类在包java.io.File下,代表操作系统的文件对象。

相对路径是相对在工程的src路径下,./表示src的上一路径。

构造方法:

  • ①public File(String pathname ):根据文件路径创建文件对象

  • ②public File(String parent, String child):根据父路径名字符串和子路径名字符串创建文件对象

1
2
3
4
5
6
如D:\Blog\1.txt
子级路径:1.txt
父级路径D:\Blog
注意在Java中\符合表示转义字符,因此在创建file对象输入参数时,要输入\\。
在Linux系统中路径分隔符是/
在Linux系统中路径分隔符是\
  • ③public File(File parent, string child):根据父路径对应文件对象和子路径名字符串创建文件对象

File成员方法

  • ①public boolean isDirectory():判断此路径名表示的File是否为文件夹。

  • ②public boolean isFile():判断此路径名表示的File是否为文件。

  • ③public boolean exists():判断此路径名表示的File是否存在。

  • ④public long length():返回文件的大小( 字节数量)。

  • ⑤public String getAbsolutePath():返回文件的绝对路径。

  • ⑥public String getPath():返回定义文件时使用的路径。

  • ⑦public String getName( ):返回文件的名称,带后缀。

  • ⑧public long lastModified():返回文件的最后修改时间(时间毫秒值)。

返回的时间需要格式化一下如:new SimpleDateFormat(“yyyy/MM/dd HH:mm:ss”).format(time))

创建删除功能:

  • ①public boolean createNewFile( ):创建一个新的空的文件。

  • ②boolean mkdir():创建一个一级目录。

  • ③boolean mkdirs():创建一个多级目录,用的最多。

  • ④boolean delete():删除文件或者空文件夹。

File类的遍历功能:

  • String[] list():获取当前目录下所有的一级文件名称到一个数组中。

  • File[] listFiles():获取当前目录下所有的以及文件对象到一个数组中。

注意:

  • ①当调用的对象不存在的时候返回null(无权限也是null)。

  • ②当调用的对象是一个文件时,返回null。

  • ③当调用的对象是一个空文件夹时,返回一个长度为0的数组。

7.2 IO 流

概述:他是相对于程序和文件之间的IO。I表示input,读数据到内存,叫数据。O表示output,写数据到磁盘,叫输出

IO流分类如下:

> 整理提示:上方图片使用本地绝对路径 `D:\Users\作业\知识点\java\图片\IO流分类1.png`,如果在其他设备预览失败,需要把图片复制到笔记目录并改成相对路径。

体系结构如下:

> 整理提示:上方图片使用本地绝对路径 `D:\Users\作业\知识点\java\图片\IO流体系结构.png`,如果在其他设备预览失败,需要把图片复制到笔记目录并改成相对路径。

IO流分为:字节流和字符流

字节流有InputStreamOutputStream

字符流有Reader字符输入流和Writer字符输出流

注意:这四大流都是抽象类,不能直接用。纯文本一般是txt,md,xml,lrc文件等。

字节流体系结构如下:

> 整理提示:上方图片使用本地绝对路径 `D:\Users\作业\知识点\java\图片\字节流体系结构.png`,如果在其他设备预览失败,需要把图片复制到笔记目录并改成相对路径。

注意:IO流什么时候用什么时候创建,什么时候不用什么时候关闭。

7.3 文件字节输出流

概述:FileOutputStreamOutputStream的实现类,操作本地文件的字节输出流,可以把程序中的数据写到本地文件中。

使用步骤:

  • ①创建字节输出流对象

  • ②写数据

  • ③释放资源

构造方法:

  • FileOutputStream(File file,true) #这里面的true代表是续写,这样原本里面的就不会清空

  • FileOutputStream(“路径”)

注意:

若文件不存在,且父级路径存在就会自动创建一个新的文件。若文件已经存在,则会清空文件。

成员方法:

  • ①void write(int a):写一个字节进去

  • ②void write(byte[] buffer,int pos,int len):写一个字节数组进去,或者一部分进去

  • ③void write(byte[] b, int off, int len):一次写一个字节数组的部分数据,off是起始索引,len是写多少个。

1
2
3
os.write(97) //写出来是a
换行是:os.write("\r\n".getBytes()) //这里面\r是以前的回车符,表示将光标放到最前面。\n是换行符,表示换到下一行。
若要写到当前工程项目下,直接用 工程名\\1.txt

注意:写数据会自动创建文件,write以后要flush()刷新数据,最后要close()关闭,write默认是覆盖写,可以加参数true代表追加写

7.4 文件字节输入流

概述:FileInputStreamInputStream的实现类。

使用步骤:

  • ①创建字节输入流对象(文件不存在报错)

  • ②读取数据

  • ③释放资源

构造方法:

FileInputStream(File file)

FileInputStream(“路径”)

成员方法:

  • ①int read():每次读取一个字节返回其ASCII码,可以用char转,读取完毕返回-1
1
2
3
4
5
6
7
8
9
读取全部的例子:
int b;
while( (b = fis.read()) != -1){
sout((char)b)
}
注意:
①当出现读到为空的情况就会返回-1,空格也是数据,没法读中文
②一次读一个字节,读出来是对应的ASCII码的数字
③read是读取一个数据就移动一下指针。
  • ②int read(byte[] buffer):每次读取一个字节数组进行返回,如果没有字节可读返回-1
1
2
3
4
5
6
例子:byte[] buffer = new byte[3];
int len;
while((len = is.read(buffer)) != -1){
sout(new String(buffer,0,len)); //这里用0到len是为了避免出现下面情况①
}
注意:①他每次读取会尽可能的填满数组,因此当出现最后填不满的情况,空的地方没有被覆盖掉,会重复用上一次所存的。
  • ③byte[] readAllBytes():读取全部字节

也可以直接把桶buffer数组的大小设置为文件的大小,利用file获取用int强转

7.5 文件的字节流拷贝

目的:拷贝任何文件

步骤:

  • ①创建一个字节输入流管道与原文件接通

FileInputStream fis = new FileInputStream( “D: game\\movie . mp4” ) ;

  • ②创建一个字节输出流管道与目标文件接通

FileOutputStream fos = new FileOutputStream(“工程名\\copy.mp4”);

  • ③定义一个字节数组转移数据(拷贝,边读边写)
1
2
3
4
5
6
7
8
9
10
11
12
13
适用小文件的例子:
int b;
while( (b = fis.read()) != -1){
fos.write(b);
}
//3.释放资源
//规则:先开的最后关闭
fos.close();
fis.close();
#因为一次读取一个字节,非常慢,所以只适合小文件。

#如果要读大数据,要改变每次读取的字节,一般用一个byte数组,且大小要是1024的整数倍,如1024*1024*5 5MB

7.6 try-catch-finally

finally:在异常处理时提供finally块来执行所有清楚操作,比如说IO流中的释放资源

特点:finally语句里面的一定会执行,除非JVM退出,该方法,释放资源会比较繁琐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
例子:常在文件IO流将close放到finally里面
//1.创建对象
FileInputStream fis = null;
File0utputStream fos = nul1;
try {
fis = new FileInputStream( "D:\\itheimal\movie.mp4");
fos = new FileOutputStream( "myio\\copy.mp4");
//2.拷贝
int len;
byte[] bytes = new byte[1024 * 1024 * 5];
while( (len = fis.read(bytes)) != -1){
fos.write(bytes, off: 0,1en);
} catch (IOException e) {
e.printStackTrace();
} finally {
//3.释放资源
if(fos != nu11){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
#但是代码太繁琐,在jdk7和jdk9分别出现解决情况,但实际开发都是抛出异常,因此只需了解

7.7 字符集

GBK中一个中文字符占2个字节,且二进制高位字节的第一位是1。

英文和数字不管咋样都是1个字节。

UTF-8编码(是unicode中不同的编码规则)中中文一般占3个字节,且二进制第一个字节首位是1,第一个字节转成十进制是负数,英文还是1字节。

GBK和Unicode都兼容ASCII码。

ASCII编码规则:前面补0,补齐8位。

ASCII解码规则:直接解码位十进制数字

> 整理提示:上方图片使用本地绝对路径 `D:\Users\作业\知识点\java\图片\各个字符集.png`,如果在其他设备预览失败,需要把图片复制到笔记目录并改成相对路径。

乱码出现的原因:

  • ①读取数据时未读完整个汉字。

  • ②编码和解码时的方式不统一。

避免出现乱码的操作:

  • ①不要用字节流读取文本文件。

  • ②编码解码时使用同一个码表,同一个编码方式。

7.8 String编码

概述:将数据转换为能存储在硬盘上的字节数据

方法:

  • ①public byte[] getBytes():使用默认方式进行编码,默认是UTF-8编码,负的二进制数值一般是中文。

  • ②public byte[] getBytes(String charsetName):使用指定方式进行编码。

length():输出字节长度

Arrays.toString(byte[]):输出数组内容

7.9 String解码

概述:字节转换为数据

方法:

  • String(byte[] bytes):使用默认方式进行解码。
  • String(byte[] bytes, String charsetName ):使用指定方式进行解码。

7.10 字符流

概述:字符流底层其实就是字节流,他是字节流 + 字符集。

> 整理提示:上方图片使用本地绝对路径 `D:\Users\作业\知识点\java\图片\字节流体系.png`,如果在其他设备预览失败,需要把图片复制到笔记目录并改成相对路径。

特点:

  • ①输入流:一次读一一个字节,遇到中文时,一次读多个字节

  • ②输出流:底层会把数据按照指定的编码方式进行编码,变成字节再写到文件中。

场景:对于纯文本的文件进行操作。

底层:自带缓冲,8192大小的数组,93集

FileReader

构造方法:

  • ①public FileReader(File file):创建字符输入流关联本地文件

  • ②public FileReader(String pathname ):创建字符输入流关联本地文件

注意:若读取文件不存在,直接报错。

成员方法:

  • ①int read():读取一个字节(中文是一次读取多个字节),并在将其转换为十进制返回(可以将用char强转),读到末尾没数据时返回-1。

  • ②int read(char[] buffer):读取一个字符数组,返回读取的字符数,如果没有可读的返回-1。该方法是把读取数据,解码,强转在底层一起做了,把强转后的字符放到了数组中。

1
2
3
4
5
6
7
读取全部例子:
char[] buffer = new char[1024];
int len;
while( (len=fr.read(buffer)) != -1){
String rs = new String(buffer,0,len);
sout(rs);
}

FileWriter

构造方法:

  • ①public FileWriter(File file):创建字符输出流关联本地文件

  • ②public Filewriter(String pathname):创建字符输出流关联本地文件

  • ③public FileWriter(File file, boolean append):创建字符输出流关联本地文件,续写

  • ④public Filewriter(String pathname, boolean append):创建字符输出流关联本地文件,续写

成员方法:

  • ①void write(int c) :写出一个字符

  • ②void write(String str):写出一个字符串

  • ③void write(String str, int off, int len):写出一个字符串的一部分

  • ④void write(char[] cbuf):写出一个字符数组

  • ⑤void write(char[] cbuf, int off, int len):写出字符数组的一部分

注意:

  • ①写数据会自动创建文件,write以后要flush()刷新数据,最后要close()关闭。

  • ②write默认是覆盖写,因此文件若存在会先清空。可以在构造方法处加参数true代表追加写。

  • ③把管道的创建写在try括号里面会自动释放资源,jdk7的解决方法。

  • ④如果write方法的参数是整数,但是实际上写到本地文件中的是整数在字符集上对应的字符,因为write方法会先有一个编码的过程。

练习:文件加密,可以对数据使用异或运算符^对一个数值进行加密,之后同样对同一个数值进行异或运算就能还原。

7.11 缓冲流

> 整理提示:上方图片使用本地绝对路径 `D:\Users\作业\知识点\java\图片\缓冲流体系结构.png`,如果在其他设备预览失败,需要把图片复制到笔记目录并改成相对路径。

作用:缓冲流自带缓冲区,可以提高原始字节流,字符流读写数据的性能

字节缓冲流

概述:底层自带了长度为8192的缓冲区,并且实际操作的还是基本流,他只是用来包装。

BufferedInputStream

构造方法:

public BufferedInputStream(InputStream is)把基本流包装成高级流,提高读取数据的性能。可以设置第二个参数,手动设置缓冲区大小。

注意:关闭时只需要关闭缓冲流即可,他底层自带关闭基本流。

BufferedOutputStream

构造方法:public BufferedoutputStream(OutputStream os)把基本流包装成高级流, 提高写出数据的性能。

性能分析:缓冲流字符数组复制最完美。

字符缓冲流

概述:底层自带了长度为8192的缓冲区,并且实际操作的还是基本流,他只是用来包装。且他的基本流本身自带缓冲区。

BufferedReader

构造方法:public BufferedReader( Reader r):把基本流变成高级流

特有方法:

public String readLine( ):读取一行数据并返回,如果没有数据可读了,会返回null。方法在读取时,一次读一行,遇到回车换行结束,但是不会把回车换行都进去。

1
2
3
4
例子:String line;
while((line = br.readline()) != null){
sout(line)
}

BufferedWriter

构造方法:public BufferedWriter(Writer r):把基本流变成高级流

特有方法:

public void newLine( ):跨平台的换行

注意:想要续写需要在基本流加true参数。

7.12 转换流

概述:是字符流和字节流之间的桥梁。

作用:

  • ①字节流想要使用字符流中的方法

  • ②利用转换符按照指定字符编码读取(jdk11后淘汰啦)

**InputStreamReader**字符输入转换流

构造方法:

InputStreamReader(FileInputStream fis,String charset):把原始的字节流按照指定编码格式转换为字符输入流。

1
2
3
4
5
6
替代方案:FileReader fr = new FileReader( fileName: "工程名\\gbkfile.txt" ,Charset.forName("GBK"));
练习:利用字节流读取文件数据,每次读取一行,且不出现乱码。
字节流在读取中文的时候,是会出现乱码的。但是字符流可以搞定。
字节流里面是没有读一整行的方法的,只有字符缓冲流才能搞定。
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream( name: "工程名\\a.txt")));

OutputStreamWrite字符输出转换流

构造方法:

OutputStreamReader(FileOutputStream fos,String charset)

7.13 序列化流

也叫对象操作输出流

作用:可以把Java中的对象写到本地文件中。以内存为基准,把内存中的对象存储到磁盘文件中去,称为对象序列化。

使用到的流是对象字节输出流:ObjectOutputStream

构造方法:ObjectOutputStream(OutputStream os):把基本流包装成高级流

属性:public final void write0bject(object obj):把对象序列化(写出)到文件中去

注意:

  • ①对象如果要序列化,对应的类必须要实现Serializanle序列号接口

  • ②javabean实现序列化时,会根据类中的变量以及方法等算出一个版本号,并保存在序列号文件中。如果在反序列化前改变了javabean,那么版本号也会随之改变,从而导致反序列化报错。

  • ③可以固定版本号,如private static final long serialVersionUID = 1L;

  • ④如果要序列化多个对象,创建一个ArrayList集合将对象存进去,然后序列化集合。

7.14 反序列化流

作用:以内存为基准,把存储到磁盘文件中去的对象数据恢复成内存中的对象,称为对象反序列化

使用到的流是对象字节输出流:ObjectInputStream

构造方法:ObjectInputStream(InputStream is):把基本流包装成高级流

属性:Object readObject(Object obj):把序列化到本地文件中的对象,读取到程序中来。

注意:

  • ①如果某些密码啥子不想序列化,可以在定义常量前加上transient(瞬态关键字)修饰的成员变量不参加序列化。

7.15 打印流

作用:可以实现方便,高效的打印数据到文件中去。可以实现打印什么就是什么,默认覆盖。想要实现追加,还是在低级管道加true。

PrintStream

概述:字节打印流,继承OutputStream

特点:

  • ①打印流只操作文件目的地,不操作数据源

  • ②特有的写出方法可以实现,数据原样写出

  • ③特有的写出方法,可以实现自动刷新,自动换行

构造方法:

  • ①public PrintStream(OutputStream/ File/String):关联字节输出流/文件/文件路径。

  • ②public PrintStream(String fileName, Charset charset):指定字符编码。

  • ③public PrintStream(OutputStream out, boolean autoF]ush):自动刷新,在字节打印流没有缓冲区,自动刷新没有意义。

  • ④public PrintStream( OutputStream out, boolean autoFlush, String encoding ):指定字符编码且自动刷新

成员方法:

  • ①public void write(int b):常规方法:规则跟之前一样,将指定的字节写出

  • ②public void println(Xxx xx):特有方法:打印任意数据,自动刷新,自动换行

  • ③public void print(Xxx xx):特有方法:打印任意数据,不换行

  • ④public void printf(String format, 0bject… args):特有方法:带有占位符的打印语句,不换行

PrintWriter

概述:字符打印流,继承Writer,底层有缓冲区,自动刷新需要手动开启。

构造方法:

  • ①public PrintWriter(Write/File/String):关联字节输出流/文件/文件路径

  • ②public PrintWriter(String fileName, Charset charset):指定字符编码

  • ③public PrintWriter(Write W, boolean autoFlush):自动刷新

  • ④public PrintWriter(OutputStream out, boolean autoFlush, Charset charset):指定字符编码且自动刷新

成员方法:

  • ①public void write(int b):常规方法:规则跟之前一样,将指定的字节写出

  • ②public void println(Xxx xx):特有方法:打印任意数据,自动刷新,自动换行

  • ③public void print(Xxx xx):特有方法:打印任意数据,不换行

  • ④public void printf(String format, 0bject… args):特有方法:带有占位符的打印语句,不换行

重定向

System.setOut(printStream):改变系统打印流改成我们自己的打印流

注意:

  • ①System.out也是打印流,不过默认指向控制台,由虚拟机创建。

7.16 压缩流

压缩本质:把每一个(文件/文件夹)看成ZipEntry对象放到压缩包中,java只能实现对zip格式的压缩。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
例子:压缩一个文件
//1.创建File对象表示要压缩的文件
File src = new File("D:\\a.txt");
//2.创建File对象表示压缩包的位置
File dest = new File("D:\\");
//3.调用方法用来压缩
toZip(src,dest);

作用:压缩
参数一:表示要压缩的文件
参数二:表示压缩包的位置
public static void toZip(File src,File dest) throws IOException {
//1.创建压缩流关联压缩包
Zip0utputStream zos = new Zip0utputStream( new FileOutputStream(new File(dest, child: "a.zip")));
//2.创建ZipEntry对象,表示压缩包里面的每一个文件和文件夹.这里的a.txt若换成aaa\\a.txt,压缩时多个aaa文件夹。
ZipEntry entry = new ZipEntry("a.txt");
//3.把ZipEntry对象放到压缩包当中
zos.putNextEntry(entry);
//4.把src文件中的数据写到压缩包当中
FileInputStream fis = new FileInputStream(src);
int b;
while((b = fis.read()) != -1){
zos.write(b);
}
zos.closeEntry();
zos.close();
}

例子2:压缩一个文件夹
//1.创建File对象表示要压缩的文件夹
File src = new File("D:\\aaa");
//2.创建File对象表示压缩包放在哪里(压缩包的父级路径)
File destParent = src. getParentFile();//D:\\
//3.创建File对象表示压缩包的路径
File dest = new File(destParent, child: src.getName() + " .zip");
//4.创建压缩流关联压缩包
Zip0utputStream zos = new Zip0utputStream(new FileOutputStream(dest));
//5.获取src里面的每一个文件,变成ZipEntry对象,放入到压缩包当中
toZip(src,zos, src.getName());
//6.释放资源
zos.close();

/*
作用:获取src里面的每一一个文件, 变成ZipEntry对象, 放入到压缩包当中
参数一:数据源
参数二:压缩流
*参数三:压缩包内部的路径
public static void toZip(File src, ZipOutputStream zos , String name) throws I0Exception {
//1.进入src文件夹
Fi1e[] files = src.listFiles();
//2.遍历数组
for (File file : files) {
if(file.isFile()){
//3.判断文件,变成ZipEntry对象,放入到压缩包当中
ZipEntry entry = new ZipEntry( name + "\\" + file. getName());# aaa\\哈哈.txt,核心就在这个路径
zos. putNextEntry( entry);
//读取文件中的数据,写到压缩包
FileInputStream fis = new FileInputStream(file);
int b;
while((b = fis.read()) != -1){
zos.write(b);

}
fis.close();
zos.closeEntry();
}else{
//4.判断-文件夹,递归
toZip(file,zos, name + "\\" + file. getName();
}
}
}

7.17 解压缩流

解压本质:把每一个ZipEntry(zip文件里面的)按照层级拷贝到本地另一个文件夹中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
例子:解压一个文件夹
//1.创建一个File表示要解压的压缩包
File src = new File("D:\\aaa.zip");
//2.创建一个File表示解压的目的地
File dest = new File("D:\\");

//定义一个方法用来解压
public static void unzip(File src,File dest) throws IOException {
//解压的本质:把压缩包里面的每一一个 文件或者文件夹读取出来,按照层级拷贝到目的地当中
//创建一个解压缩流用来读取压缩包中的数据
ZipInputStream zip = new ZipInputStream(new FileInputStream(src));
//要先获取到压缩包里面的每一个zipentry对象
//表示当前在压缩包中获取到的文件或者文件夹
ZipEntry entry;
while((entry = zip.getNextEntry()) != nu11){
System.out.print1n(entry);
if(entry.isDirectory()){
// 文件夹: 需要在目的地dest处创建一个同样的文件夹
File file = new File(dest, entry.toString());
file.mkdirs();
}else{
//文件:需要读取到压缩包中的文件,并把他存放到目的地dest文件夹中(按照层级目录进行存放)
File0utputStream fos = new FileOutputStream(new Fi1e( dest,entry.toString()));
int b;
while((b = zip.read()) != -1){
//写到日的地
fos.write(b);
fos.close();
}
//表示在压缩包中的一一个 文件处理完毕了。
zip.closeEntry();
}
}
zip.close();

7.18 Properties属性集对象

概述:他其实是一个Map集合,但是不一般不当集合用,HashMap更好用

作用:其实他代表一个属性文件,可以把自己对象中的键值对信息存入到一个属性文件去

属性文件:后缀是.properties结尾的文件,里面的内容都是key=value,后续做配置信息

使用:直接用无参构造new一个

方法:

  • ①setPropety(“key”,”value”):存入键值对,和put一样

  • ②store(OutputStream os,String comments):保存管道,把他存到管道里,comments意思是保存的心得,相当于注释

  • ③store(Writer wt,String comments):同上

  • ④load(FileReader fr):加载属性文件到这个properties对象中

  • String getProperty(“key”):取key的值

7.19 commons-io框架

概述:是一个有关IO操作的开源工具包,提高IO流开发效率。

使用步骤:

  • ①在项目中创建一个文件夹: lib。即所有第三方jar包存放的地方。

  • ②将jar包复制粘贴到lib文件夹

  • ③右键点击jar包,选择Add as Library ->点击OK

FileUtils

方法:

  • ①static void copyFile(File srcFile, File destFile):复制文件

  • ②static void copyDirectory(File srcDir, File destDir):复制文件夹,直接拷贝文件夹里面的内容

  • ③static void copyDirectoryToDirectory(File srcDir, File destDir):复制文件夹,直接把文件夹整个复制过来,

  • ④static void deleteDirectory(File directory):删除文件夹

  • ⑤static void cleanDirectory(File directory) :清空文件夹

  • ⑥static String readFileToString(File file, Charset encoding):读取文件中的数据变成成字符串

  • ⑦static void write(File file, CharSequence data, String encoding):写出数据

IOUtils

方法:

  • ①public static int copy(InputStream input, OutputStream output ):复制文件

  • ②public static int copyLarge(Reader input, Writer output ):复制大文件

  • ③public static String readLines(Reader input):读取数据

  • ④public static void write(String data, OutputStream output ):写出数据

7.20 Hutool工具包

官网:https://hutool.cn/

API文档:https://apidoc.gitee.com/dromara/hutool/

中文使用文档:https://hutool.cn/docs/#/

IoUtil:流操作工具类

FileUtil:文件读写和操作的工具类

FileTypeUtil:文件类型判断工具类

WatchMonitor:目录、文件监听

ClassPathResource:针对ClassPath中资源的访问封装

FileReader:封装文件读取

FileWriter:封装文件写入

7.21 IO综合练习

7.21.1 爬虫

爬虫获得假数据的步骤:

  • ①定义变量记录网址
1
2
3
//1.定义变量记录网址
String boyNameNet = "http://www.haoming8.cn/baobao/10881.html";
String girlNameNet = "http://www.haoming8.cn/baobao/7641.html";
  • ②爬取数据,把爬取的网页源码拼接为一个字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 调取方法,爬取数据
String familyNameStr = webCrawler( familyNameNet);
String boyNameStr = webCrawLer( boyNameNet ) ;
String gir1NameStr = webCrawler(girlNameNet);

/*
* 作用:
* 从网络中爬取数据,把数据拼接成字符串返回
* 形参:
* 网址.
* 返回值:
* 爬取到的所有数据(即网页源码)
* */
public static String webCrawler(String net) throws IOException {
//1.定义StringBuilder拼接爬取到的数据
StringBuilder sb = new StringBuilder();
//2.创建一个URL对象
URL ur1 = new URL(net);
//3.链接上这个网址
//细节:保证网络是畅通的,而且这个网址是可以链接上的。
URLConnection conn = url.openConnection();
//4.读取数据,细节读取中文所以要将字节流转换为字符流
InputStreamReader isr = new InputStreamReader(conn.getInputStream());
int ch;
while((ch = isr.read()) != -1){
sb.append((char)ch);
//5.释放资源.
isr.close();
//6.把读取到的数据返回
return sb.toString();
}
  • ③通过正则表达式,把其中符合要求的数据获取出来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//3.通过正则表达式,把其中符合要求的数据获取出来
ArrayList<String> familyNameTempList = getData(fami1yNameStr,"正则表达式");

/*
* 作用:根据正则表达式获取字符串中的数据
* 参数一:完整的字符串
* 参数二:正则表达式
* 参数三:根据需求添加
* 返回值:真正想要的数据
private static ArrayList<String> getData(String familyNameStr, String regex) {
//1.创建集合存放数据
ArrayList<String> list = new ArrayList<>();
//2.按照正则表达式的规则,去获取数据
Pattern pattern = Pattern.compile(regex);
//按照pattern的规则,到str当中获取数据
Matcher matcher = pattern.matcher(str);
while(matcher.find()){
list.add(matcher.group());
return list;
}
  • ④处理数据

  • ⑤生成数据

注意:或者直接使用hutool包里面的爬虫(https://doc.hutool.cn/pages/http/example/)

1
2
3
4
使用hutool包
//6.写出数据
//细节:糊涂包的相对路径,不是相对于当前项目而言的,而是相对class文件而言的
FileUti1.writeLines(list,"names.txt","UTF-8");

8 配置文件

8.1 Properties

概述:properties是一个双列集合集合,拥有Map集合所有的特点。

重点:有一些特有的方法,可以把集合中的数据,按照键值对的形式写到配置文件当中。也可以把配置文件中的数据,读取到集合中来。

创建:Properties prop = new Properties();

特点:

  • ①后缀名为properties

  • ②数据以键值对形式进行存储

特有的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//1.创建集合
Properties prop = new Properties();
//2.读取本地Properties文件里面的数据
FileInputStream fis = new FileInputStream("myiotest\a.properties" );
prop.load(fis);
fis.close( );


//1.创建集合
Properties prop = new Properties();
//2.读取本地Properties文件里面的数据
FileInputStream fis = new FileInputStream("myiotestlla.properties");
prop.1oad(fis);
fis.close();

注意:

  • ①虽然我们可以往Prpperties当中添加任意的数据类型,但是一般只会往里面添加字符串类型的数据

8.2 XML

概述:XML是可扩展标记语言的缩写,是一种数据表示格式,常用于传输和存储数据以及软件的配置文件。

作为配置文件的优点:

  • ①可读性好

  • ②可维护性高

规范:

1
2
3
4
①文档的声明必须在第一行
②根标签有且只能有一个
③&lt; 小于 &gt; 大于 &amp; 和号 &apos; 单引号 &quot; 引号
④<![CDATA[ 内容 ]]> 在里面可以随便写

文档约束

分类:DTD(约束编写格式)和schema(可以在其基础上约束数据类型,后缀必须是.xsd)

解析XML

最常用的是dom4j框架

使用:

  • ①创建一个Dom4j的解析器对象,代表了整个dom4j框架

SAXReader saxReader = new SAXReader();

  • ②把XML文件加载到内存中成为一个Document文档对象

注意: getResourceAsStream中的/是直接去src下寻找的文件

InputStream is = 类名.class.getResourceAsStream(“/Contacts.xml”);

Document document = saxReader.read(is);

  • ③获取根元素对象

Element root = document.getRootElement();

  • ④然后调用相应方法获取想要的
1
2
3
4
5
6
7
8
方法:
①List<Element> elements():得到当前元素下所有子元素
②List<Element> elements(String name):得到当前元素下指定名字的子元素返回集合
③Element element(String name):得到当前元素下指定名字的子元素,如果有很多名字相同的返回第一个
④String getName():得到元素名字
⑤String attributeValue(String name):通过属性名直接得到属性值
⑥String elementText(子元素名):得到指定名称的子元素的文本
⑦String getText():得到文本

xpath查找XML数据

使用:

1
2
3
①导jar包jaxen
②List<Node> nodes = document.selectNodes("写xpath语法")
document.selectSingleNode("语法"):返回单个值

8.3 YAML

9 线程学习

9.1 线程

概念:线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

简单理解:应用软件中互相独立,可以同时运行的功能。

并发:在同一时刻,有多个指令在单个CPU上交替执行

并行:在同一时刻,有多个指令在多个CPU上同时执行

多线程的创建

方法一:继承Thread

实现步骤:

  • ①创建一个类继承Thread

  • ②然后在上述类中重写run方法,定义线程以后要干啥

  • ③之后在主类中创建子类的对象,在主类里面new一个自定义线程对象(就是刚刚定义那个继承Thread类)

  • ④然后,启动线程,通用start方法启动线程(执行的还是run方法)

注意:main方法中不要把主线程的任务设置在子线程的前面

缺点:没法继承其他,不利于扩展

1
2
在主类创建完自定义的线程类之后,可以使用Thread类的setName方法进行定义名字,用Thread类的getName获取线程名字。
若要创建多个线程,只需将步骤3中多new几个自定义线程类的对象即可

方法二:实现Runnable接口

实现步骤:

  • ①定义一个线程类实现Runnable接口

  • ②重写上述类中的run方法

  • ③在主类中创建自定义线程类的对象。(Runnable = new 自定义类名)

MyRun t = new MyRun();

  • ④创建一个Thread类的对象,并使用Thread类的对象start方法开启线程。

Thread t1 = new Thread(t);

注意:

  • ①如果要创建多个线程对象,多new几个Thread对象并把自定义线程类对象传进去即可。

  • ②如果要获取线程名字,需要在自定义线程类中先使用Thread的currentThread()方法获取当前线程类对象,再用该对象的getName方法获取名字。

匿名内部类的形式实现创建线程:

  • Runnable target = new Runnable(){

public void run(){}

};

  • Thread t = new Thread(target);

  • ③t.start()

缺点:编程多一层对象包装,如果线程有返回结果是不可以直接返回的

方法三:利用Callable接口和Fature接口方式

应用场景:前面两种不能返回结果。

使用步骤:

  • ①创建一个自定义线程类实现Callable接口,并重写call (是有返回值的,表示多线程运行的结果)
1
2
3
4
5
6
class MyCallable implements Callable<线程执行完返回的数值类型>{
public 线程执行完返回的数值类型 call() throws Exception(){
执行的任务代码
return ???;
}
}
  • ②创建MyCallable的对象( 表示多线程要执行的任务)
1
MyCallable call = new MyCallable();
  • ③创建Future接口实现类FutureTask的对象(作用管理多线程运行的结果)
1
2
3
//用FutureTask把Callable对象封装成线程任务对象
FutureTask<String> f1 = new FutureTask<>(call)
注意:FutureTask对象的作用:是Runnable的实现类,可以交给Thread,可以线程结束之后用他的get方法获取结果(get这里会等待线程执行完才会提取结果)
  • ④创建Thread头的对象,并调用Thread的start方法启动线程(表示线程)

多线程3种实现方式对比

优点 缺点
继承Thread 编程比较简单,可以直接使用Thread类中的方法 扩展性较差,不能再继承其他的类
实现Runnable接口 扩展性强,实现该接口的同时还可以继承其他的类 编程相对复杂,不能直接使用Thread类中的方法
实现Callable接口 扩展性强,实现该接口的同时还可以继承其他的类 编程相对复杂,不能直接使用Thread类中的方法

Thread常用方法

  • String getName():返回此线程的名称

  • ②void setName(String name ):设置线程的名字( 构造方法也可以设置名字)

  • ③static Thread currentThread( ):获取当前线程的对象

  • ④static void sleep(long time ):让线程休眠指定的时间,单位为毫秒

  • ⑤setPriority( int newPriority):设置线程的优先级,最小1,最大10,默认5,优先级越高抢到CPU的概率越大。

  • ⑥final int getPriority():获取线程的优先级

  • ⑦final void setDaemon( boolean on):设置为守护线程

  • ⑧public static void yield():出让线程/礼让线程,即出让当前cpu执行权,然后线程继续尝试抢夺cpu。

  • ⑨public static void join():插入线程/插队线程

注意:

  • ①线程默认名字为Thread-X(X为序号,从0开始)

  • ②线程的优先级出现是因为java中的任务调度是抢占式调度,具有随机性

  • ③守护线程的特点:当其他的非守护线程执行完毕之后,守护线程就会随之结束(这个结束有一定的过程)

  • ④因线程的执行具有随机性,故出让线程只能尽可能的平均利用线程。

  • ⑤main线程一般都会先执行,插入线程方法表示当该线程插入当前线程之前。

9.2 线程的生命周期

六大状态,其中的运行状态在java虚拟机中是并没用定义的,这里只是方便理解。没有运行状态的原因是因为当抢到CPU的执行权时,java虚拟机就会把执行权交给操作系统。

9.3 线程同步

作用:为了解决线程安全问题

核心思想:加锁,即把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来

加锁方式一:同步代码块

作用:把操作共享数据的代码上锁

格式:synchronized(同步锁对象){操作共享资源的代码}

特点:

  • ①锁默认打开,有一个线程进去了,锁自动关闭

  • ②里面的代码全部执行完毕,线程出来,锁自动打开

注意:

  • ①锁对象可以用任意的类型。

  • ②锁对象必须是唯一的,可以在前面加上static保证唯一。但一般不直接创建锁对象,一般用字节码文件对象。因为字节码对象一定是唯一的。

  • ③实例方法中的锁对象最好用this(用共享资源作为对象),静态方法最好用字节码(类名.class)对象作为锁对象。

  • ④一定要注意加锁的位置。

1
2
3
4
5
run方法如何写的技巧
//1.循环
//2.同步代码块(同步方法)
//3.判断共享数据是否到了末尾,如果到了末尾
//4.判断共享数据是否到了末尾,如果没有到末尾

方法二:同步方法

概述:直接把synchronized加在方法上

格式:修饰符 synchronized 返回值类型 方法名称(形参列表){}

特点:

  • ①同步方法是锁住方法里面所有的代码

  • ②锁对象不能自己指定。若当前方法为非静态为this,静态为当前类的字节码我文件对象。

注意:

  • ①直接在核心代码的方法进行加锁就行,它实例默认是this对象,代码必须高度面向对象,他用的比较多。

  • ②可以先写同步代码块,再把同步代码块中的所有代码复制到方法中(idea中选中代码ctrl+alt+m可以将选中代码抽取为方法)

方法三:Lock

场景:上述两种方法无法修改什么时候上锁和放锁,lock为此出现了。

他是一个接口,采用他的实现类ReentrantLock来构建锁对象

创建:private final Lock lock = new ReentrantLock(); //也可以用static保证唯一

方法:

  • ①void lock():上锁

  • ②void unlock():解锁,他一般放在异常的finally里面,try一般放在加锁的下面

注意:核心就是上锁和解锁的位置。

死锁:即出现了锁里嵌套了锁的情况

9.4 等待唤醒机制

Object类唤醒和等待方法

  • ①void wait():让当前线程等待并释放所占锁,直到另一个线程调用唤醒方法。

  • ②void notify():唤醒正在等待的单个线程。

  • ③void notifyAll():唤醒正在等待的所有线程。

注意:上述方法应该使用当前同步锁对象进行调用。

阻塞队列实现等待唤醒机制

创建:ArrayBlockingQueue<string> queue = new ArrayBlockingQueue<>( capacity: 1);

细节:

  • ①生产者和消费者必须使用一个阻塞队列

  • ②阻塞队列创建在测试类当中,可以通过在各自的线程类中在构造方法中传入阻塞队列的对象。如下

1
2
3
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
  • ③阻塞队列的底层已经加好锁和释放锁了

阻塞队列的继承结构

> 整理提示:上方图片使用本地绝对路径 `D:\Users\作业\知识点\java\图片\阻塞队列的继承结构.png`,如果在其他设备预览失败,需要把图片复制到笔记目录并改成相对路径。

9.5 线程池(重点)

场景:以前创建的多线程是用到线程的时候就创建,并且用完之后线程就消失了。

概述:线程池就是一个可以复用线程的技术。ExecutorService接口代表线程池。

核心原理:

  • ①创建一一个池子,池子中是空的

  • ②提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可

  • ③但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待

获取线程池对象

方式一:使用ExecutorService的实现类ThreadPoolExecutor自定义一个线程池对象

创建:ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor()

ThreadPoolExecutor()的七个参数:(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂, 任务的拒绝策略)

参数一:核心线程数量,不能小于0

参数二:最大线程数,不能小于等于0,最大数量>=核心线程数量

参数三:空闲线程最大存活时间,不能小于0

参数四:时间单位,用TimeUnit指定。如TimeUnit.SECONDS

参数五:任务队列,不能为null。如new ArrayBlockingQueue<>(3)

参数六:创建线程工厂,不能为null。如Executors.defaultThreadFactory( )

参数七:任务的拒绝策略,不能为null。如new ThreadPoolExecutor.AbortPolicy()

任务拒绝策略:

  • ①ThreadPoolExecutor.AbortPolicy:默认策略,丢弃任务并抛出RejectedExecutionException异常

  • ②ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常这是不推荐的做法

  • ③ThreadPoolExecutor.Discard0ldestPolicy:抛弃队列中等待最久的任务然后把当前任务加入队列中

  • ④ThreadPoolExecutor.CallerRunsPolicy:调用任务的run()方法绕过线程池直接执行

注意:

  • ①当核心线程都被使用,且又重新排满了,才会使用临时线程。如3个核心线程,当第7个任务来的时候,临时线程才会使用。

  • ②任务先提交不一定先执行。

  • ③当任务超出(核心线程*2+临时线程)的数量时,就会触发任务拒绝策略

方式二:使用Executors线程池的工具类返回不同特点的线程池对象

1
2
public static ExecutorService newCachedThreadPool():创建一个没有上限的线程池
public static ExecutorService newF ixedThreadPool(int nThreads ):创建有上限的线程池

创建:ExecutorService pool = Executors.newCachedThreadPool();

提交任务:pool.submit(线程类的对象)

关闭线程池:pool.shutdown()

线程池多大合适

项目若为CPU密集型运算:最大并行数(即处理器的线程数)+1

项目若为I/O密集型运算:

10 定时器

实现方式一:Timer定时器(内部时一个线程)

构造方法:Timer():创造Timer定时器对象

方法:

  • ①void schedule(TimerTask task,long delay延时,long period周期):开启一个定时器,按照计划处理TImerTask任务

特点和存在问题:

  • ①他是单线程,处理多个任务按照顺序执行,存在延时与与设置定时器的时间有出入

  • ②可能因为其中的某个任务的异常使线程死掉,从而影响后续任务执行

实现方式二:ScheduledExecutorService(内部是一个线程池)

Executors的方法

  • ①static scheduledExecutorService newSceduledThreadPool(int corePoolSize):得到线程池对象,指定几个线程

ScheduledExecutorService的方法:

  • ①ScheduleFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit):周期调度方法

优点:各个线程互不影响

11 网络编程

概述:在网络通信协议下,不同计算机上运行的程序,进行的数据传输。在java中是java.net包。

常见的软件架构:B/S浏览器和客户端,C/S客户端和服务器

三要素:IP地址,端口号,协议

IP地址:设备在网络中的地址,是唯一的标识。

常见的分类为:IPV4(32位4字节,分为4组,点分十进制)和IPV6(128位,分为8组,冒分十六进制,16字节)

DNS服务器:域名解析服务器

IP地址形式:公网地址,私有地址(局域网)

192.168.开头就是常见的私有地址,后面两组的范围位为0.0到255.255

常用命令:

ipconfig:查看本机IP地址

ping ip地址:检测网络是否连通

本机IP:127.0.0.1或者localhost,也称为本地回环地址,只会找本机

IP地址操作类:InetAddress

方法:

  • ①static InetAddress getLocalHost():返回本主机的地址对象

  • ②static InetAddress getByName(String host):得到指定主机的IP地址对象,参数是域名或者IP地址

  • String getHsotName():获取IP地址的主机名

  • String getHostAddress():返回IP地址字符串

  • ⑤boolean isReachable(int timeout):在指定毫秒内连通该IP地址对应的主机,连通返回true

端口:应用程序在设备中的唯一标识

端口号是一个16位的二进制,范围是0到65535

端口类型:

周知端口:0到1023,被某些知名应用占用(Http占用80,ftp占用21)

注册端口:1024到49161,分配给某些程序(如Tomcat用8080,MySQL用3306)

动态端口:49152到65535,是因为他不固定,是动态分配

注意:我们自己开发的端口选用注册端口

协议:数据在网络中传输的规则,常见的有UDP协议和TCP协议(传输层),http,https,ftp协议(应用层)等

连接和通信数据的规则被称为网络通信协议。

UDP协议

概述:用户数据报协议(User Datagram Protocol),面向无连接的通信协议

特点:

  • ①UDP是一种无连接、不可靠传输的协议。

  • ②将数据源P、目的地IP和端口封装成数据包,不需要建立连接每个数据包的大小限制在64KB内

  • ③发送不管对方是否准备好,接收方收到也不确认,故是不可靠的可以广播发送,发送数据结束时无需释放资源,开销小,速度快。

发送数据步骤:

  • ①创建发送端的DatagramSocket对象

DatagramSocket ds = new DatagramSocket(可指定端口号);

  • ②数据打包(DatagramPacket)
1
2
3
4
5
6
//2.打包数据
String str =“你好威啊! ! ! ";
byte[] bytes = str.getBytes();
InetAddress address = InetAddress.getByName( "127.0.0.1");
int port = 10086;
DatagramPacket dp = new DatagramPacket (bytes, bytes.length, address , port);
  • ③发送数据

ds.send(dp)

  • ④释放资源

ds.close()

接受数据步骤:

  • ①创建接收端的DatagramSocket对象(绑定的端口要和发送的端口一样)

  • ②接收打包好的数据(DatagramPacket)

1
2
3
4
5
//2.接收数据包
byte[] bytes = new byte[1024];
DatagramPacket dp = new Datagr amPacket(bytes , bytes.length) ;
//这个方法是阻塞的,要等待发消息
ds.receive(dp);
  • ③解析数据包
1
2
3
4
5
//3.解析数据包
byte[] data = dp.getData();
int len = dp.getLength();
InetAddress address = dp.getAddress();
int port = dp.getPort();
  • ④释放资源

UDP的三种通信方式

单播(1对1):代码实现即上述部分的DatagramSocket就是单播对象

组播(1对1组):使用MulticastSocket对象,发送端除了发送地址要指定组播地址外,其他同上。接收端要使用joinGroup(InetAddress.getByName( “地址”))将当前本机,添加到组播地址这一组当中,其他同上

广播(1对所有):和单播一样,只需把发送地址改为255.255.255.255

TCP协议

1
2
3
TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象
通信之前要保证连接已经建立
通过Socket产生I0流来进行网络通信

概述:传输控制协议TCP(Transmission Control Protocol),是面向连接的通信协议

特点:

  • ①使用TCP协议,必须双方先建立连接,它是一种面向连接的可靠通信协议。传输前,采用“三次握手”方式建立连接,所以是可靠的。

  • ②在连接中可进行大数据量的传输。

  • ③连接、发送数据都需要确认,且传输完毕后,还需释放已建立的连接,通信效率较低。

注意:在Java中使用java.net.Socket类实现通信,底层即是使用了TCP协议

客户端socket发送数据步骤:

  • ①创建客户端的Socket对象(Socket)与指定服务端连接

Socket(String host, int port) //创建对象时会连接服务端,三次握手协议保证连接建立

  • ②获取输出流,写数据

OutputStream getoutputStream( )

socket.shutdownOutput(); //往服务器写出结束标记

  • ③释放资源

服务端接受数据步骤:

  • ①创建服务器端的Socket对象(ServerSocket)

ServerSocket(int port)

  • ②监听客户端连接,返回一个Socket对象

Socket accept()

  • ③获取输入流,读数据,并把数据显示在控制台

InputStream getInputStream( ) //中文乱码可以把他转变为字符流去读取即可

  • ④释放资源

void close() //四次挥手协议保证正常结束

Socket扩展

构造方法:

  • ①public Socket(String host, int port):创建发送端的Socket对象与服务端连接,参数为服务端程序的ip和端口

成员方法:

  • OutputStream getOutputStream:获得字节输出流对象

  • InputStream getInputStream:获得字节输入流对象

使用步骤:

  • ①创建客户端的Socket对象,请求与服务器连接

Socket socket = new Socket("127.0.0.1","7777")

  • ②从Socket通信管道中获得字节输出流,负责发送数据

OutputStream os = socket.getOutputStream();

PrintStream ps = new PrintStream(os); //尽量使用打印流包装,效率更高

  • ③发送消息

ps.println():这里不能用 print,因为服务端是按行读取。

ps.flush()

  • ④不要关闭资源

ServerSocket(服务端)

构造方法:public ServerSocket(int port):注册服务端端口

成员方法:Socket accept():等待接收客户端的socket通信连接,连接成功返回Socket对象与客户端建立

使用步骤:

  • ①注册端口

ServerSocket serversocket = new ServerSocket(7777)

  • ②调用accept方法,建立socket通信管道

Socket socket = serversocket.accept()

  • ③从socket通信管道中得到一个字节输入流

InputStream is = socket.getInputStream()

BufferedReader br = new BufferedReader(new InputStreamReader(is))

  • ④按照行读取消息

String msg

if((msg = br.readLine()) != null){

sout(socket.getRemoteSocketAddress())

}

注意:如果要实现多个客户端通信需要实现多线程,这个时候可以采用线程池来优化性能,使用线程池适合通信时常较短的场景,以上都是CS(服务器和客户端)结构

即时通信是什么含义,要实现怎样的设计

  • ①即时通信,是指一个客户端的消息发出去,其他客户端可以接收到即时通信需要进行端转发的设计思想。

  • ②服务端需要把在线的Socket管道存储起来一旦收到一个消息要推送给其他管道

12 GUI

概述:Graphics User Interface,中文名称为图形用户界面,指的是采用图形化的方式显示操作界面。

在java中,关于GUI的内容分别在AWT包(最早的)和Swing包(新的)

组件:

JFrame:最外层的窗体

JMenuBar:最上层的菜单

JLabel:管理文字和图片的容器

13 Java 锁与并发控制

本章节为新增补充。原笔记已经记录了线程、线程同步、等待唤醒机制和线程池,这里继续补齐 Java 并发中最常考、最容易混淆的“锁”相关内容。

13.1 为什么需要锁

在多线程环境中,多个线程可能同时访问同一份共享数据。只要存在“共享数据 + 多线程读写 + 至少一个线程写入”,就可能出现线程安全问题。

并发编程中最核心的三个问题:

问题 含义 典型例子
原子性 一个操作要么全部完成,要么全部不完成,中间不能被打断 count++ 不是原子操作
可见性 一个线程修改共享变量后,其他线程能及时看到 一个线程修改 flag = true,另一个线程迟迟看不到
有序性 程序执行顺序可能被编译器或 CPU 优化重排 双重检查锁单例需要 volatile

记忆:synchronized 可以同时解决原子性、可见性、有序性;volatile 主要解决可见性和有序性,不保证复合操作的原子性。

13.2 synchronized 关键字

synchronized 是 Java 内置的同步机制,也叫内置锁、监视器锁、对象锁。它可以保证同一时刻只有一个线程进入被保护的临界区。

13.2.1 常见写法

同步代码块:

1
2
3
synchronized (lockObject) {
// 需要保证线程安全的代码
}

同步实例方法:

1
2
3
public synchronized void method() {
// 锁对象是 this
}

同步静态方法:

1
2
3
public static synchronized void method() {
// 锁对象是 当前类的 Class 对象,例如 Demo.class
}

13.2.2 锁对象是谁

写法 锁对象
synchronized (obj) 括号中的 obj
public synchronized void test() 当前对象 this
public static synchronized void test() 当前类的 Class 对象

注意:多个线程必须竞争同一把锁,锁才会生效。如果每个线程都 new Object() 当锁对象,那么不会形成互斥。

13.2.3 可重入性

synchronized 是可重入锁。同一个线程已经获得某把锁后,可以再次进入这把锁保护的其他代码,不会把自己锁死。

1
2
3
4
5
6
7
public synchronized void a() {
b();
}

public synchronized void b() {
System.out.println("同一个线程可以重复进入同一把锁");
}

13.2.4 synchronized 的优点和缺点

方面 说明
优点 语法简单,JVM 自动加锁和释放锁,不容易忘记释放
缺点 灵活性不如 Lock,不能主动尝试获取锁,不能设置公平锁,等待锁时不方便中断

13.3 volatile 关键字

volatile 用于修饰成员变量,主要作用是保证变量在多线程之间的可见性,并在一定程度上禁止指令重排序。

1
2
3
4
5
private volatile boolean running = true;

public void stop() {
running = false;
}

适合场景:

  • 状态标记,例如 runningflag
  • 单例模式的双重检查锁中防止指令重排。
  • 一个线程写,多个线程读,并且写操作不是复合操作。

不适合场景:

1
2
volatile int count = 0;
count++; // 不是线程安全的

原因:count++ 实际包含读取、加一、写回三个步骤,volatile 不能保证这三个步骤整体不可分割。

13.4 LockReentrantLock

Lockjava.util.concurrent.locks 包下的锁接口,ReentrantLock 是最常见的实现类。

13.4.1 基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
private int count = 0;
private final Lock lock = new ReentrantLock();

public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}

重点:unlock() 必须放在 finally 中,避免业务代码抛异常后锁无法释放。

13.4.2 ReentrantLock 相比 synchronized 的增强能力

能力 说明
可重入 同一个线程可以重复获得同一把锁
可中断 使用 lockInterruptibly() 可以响应中断
可尝试获取锁 使用 tryLock(),拿不到锁可以立即返回
可设置公平锁 构造方法 new ReentrantLock(true) 开启公平锁
可绑定多个条件队列 配合 Condition 实现更精细的等待唤醒

13.4.3 公平锁和非公平锁

类型 特点 优缺点
公平锁 按照线程等待顺序获取锁 更公平,但吞吐量可能下降
非公平锁 新来的线程可以直接竞争锁 性能通常更好,但可能导致线程饥饿

ReentrantLock 默认是非公平锁。

1
2
Lock fairLock = new ReentrantLock(true);  // 公平锁
Lock unfairLock = new ReentrantLock(); // 非公平锁

13.5 Condition 条件变量

Condition 类似于 wait() / notify(),但它必须配合 Lock 使用,并且一个锁可以创建多个 Condition,更适合复杂的生产者消费者模型。

1
2
3
Lock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition();
Condition notFull = lock.newCondition();

常用方法:

方法 含义
await() 当前线程等待,并释放锁
signal() 唤醒一个等待线程
signalAll() 唤醒所有等待线程

使用规则:

1
2
3
4
5
6
7
8
9
10
lock.lock();
try {
while (条件不满足) {
condition.await();
}
// 执行业务逻辑
condition.signalAll();
} finally {
lock.unlock();
}

注意:等待条件通常用 while,不要用 if,因为线程可能出现虚假唤醒,醒来后需要重新判断条件。

13.6 wait() / notify() / notifyAll()

这三个方法来自 Object 类,必须在 synchronized 代码块或同步方法中使用。

1
2
3
4
5
6
synchronized (lock) {
while (!condition) {
lock.wait();
}
lock.notifyAll();
}
方法 说明
wait() 当前线程进入等待状态,并释放锁
notify() 随机唤醒一个等待线程
notifyAll() 唤醒所有等待线程

wait()sleep() 的区别:

对比项 wait() sleep()
所属类 Object Thread
是否释放锁 释放锁 不释放锁
使用条件 必须在同步代码中 不要求在同步代码中
唤醒方式 notify() / notifyAll() 或超时 时间到或被中断

13.7 读写锁 ReadWriteLock

ReadWriteLock 适合“读多写少”的场景。它把锁拆成读锁和写锁:

  • 读锁和读锁可以并发。
  • 读锁和写锁互斥。
  • 写锁和写锁互斥。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Cache {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private String data;

public String read() {
rwLock.readLock().lock();
try {
return data;
} finally {
rwLock.readLock().unlock();
}
}

public void write(String newData) {
rwLock.writeLock().lock();
try {
this.data = newData;
} finally {
rwLock.writeLock().unlock();
}
}
}

13.8 CAS 与原子类

CAS 全称是 Compare And Swap,中文常叫“比较并交换”。它是一种乐观锁思想:

  1. 先读取旧值。
  2. 更新前比较当前值是否还是旧值。
  3. 如果没被其他线程修改,就更新成功;否则失败重试。

常见原子类:

作用
AtomicInteger 原子更新 int
AtomicLong 原子更新 long
AtomicBoolean 原子更新 boolean
AtomicReference<T> 原子更新对象引用

示例:

1
2
3
4
import java.util.concurrent.atomic.AtomicInteger;

AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();

CAS 的问题:

问题 说明
ABA 问题 值从 A 变成 B,又变回 A,CAS 会误以为没有变化
自旋开销 长时间失败重试会消耗 CPU
只能保证单个变量原子性 多变量一致性仍然复杂

13.9 悲观锁和乐观锁

类型 思想 Java 中的例子
悲观锁 总是假设会发生并发冲突,先加锁再操作 synchronizedReentrantLock
乐观锁 假设冲突较少,先操作,提交时再检查 CAS、版本号机制

数据库中的版本号更新就是典型乐观锁:

1
2
3
UPDATE product
SET stock = stock - 1, version = version + 1
WHERE id = 1 AND version = old_version;

13.10 死锁、活锁和饥饿

13.10.1 死锁

死锁指多个线程互相等待对方持有的资源,导致所有线程都无法继续执行。

死锁的四个必要条件:

条件 含义
互斥条件 资源一次只能被一个线程占用
请求并保持 线程持有资源的同时继续请求其他资源
不可剥夺 资源不能被强制抢走
循环等待 多个线程形成环形等待关系

避免死锁的方法:

  • 固定加锁顺序。
  • 避免在持有锁时再申请其他锁。
  • 使用 tryLock() 设置超时。
  • 减小锁粒度,缩短持锁时间。

13.10.2 活锁

活锁指线程没有阻塞,也一直在运行,但因为互相谦让或重复重试,导致整体没有进展。

13.10.3 饥饿

饥饿指某个线程长期得不到需要的资源,始终无法执行。例如非公平锁下,某些线程可能一直抢不到锁。

13.11 锁升级与锁优化

JVM 会对 synchronized 做大量优化,常见概念如下:

概念 说明
偏向锁 偏向第一个获得锁的线程,减少无竞争情况下的开销
轻量级锁 多个线程交替竞争时,通过 CAS 尝试获取锁
重量级锁 竞争激烈时,线程进入阻塞,依赖操作系统互斥量
锁消除 JIT 发现锁对象不会逃逸时,直接去掉锁
锁粗化 连续多次加锁解锁时,扩大锁范围减少开销

注意:不同 JDK 版本中锁实现细节可能有调整,复习时重点理解思想,不需要死记内部实现细枝末节。

13.12 面试高频总结

  • synchronized 是 Java 内置锁,自动释放;ReentrantLock 需要手动释放,但更灵活。
  • volatile 保证可见性和一定的有序性,但不保证 count++ 这类复合操作的原子性。
  • wait() 会释放锁,sleep() 不会释放锁。
  • notify() 随机唤醒一个线程,notifyAll() 唤醒所有等待线程。
  • ReentrantLock 默认非公平,可以通过 new ReentrantLock(true) 创建公平锁。
  • Condition 可以实现多个等待队列,比 wait() / notify() 更适合复杂场景。
  • CAS 是乐观锁思想,常见问题是 ABA、自旋开销、只能保证单变量原子性。

14 设计模式

本章节在原有“代理模式”的基础上扩展。设计模式不用一次性背全,但 Java 开发和面试中最常见的模式需要知道:解决什么问题、有哪些角色、适合什么场景、Java 代码如何落地。

14.1 设计模式概述

设计模式是软件开发中针对常见设计问题总结出来的可复用方案。它不是某个具体类库,而是一种代码组织思想。

常见分类:

分类 关注点 常见模式
创建型 对象如何创建 单例、工厂方法、抽象工厂、建造者、原型
结构型 类和对象如何组合 代理、适配器、装饰器、外观、桥接、组合
行为型 对象之间如何协作 策略、模板方法、观察者、责任链、命令、迭代器

学习设计模式时不要死背定义,应该按下面三个问题理解:

  1. 这个模式解决什么变化点?
  2. 它通过什么结构隔离变化?
  3. 使用它会带来什么成本?

14.2 单例模式 Singleton

14.2.1 作用

保证一个类在整个 JVM 中只有一个实例,并提供一个全局访问点。

适合场景:

  • 配置管理器。
  • 线程池管理器。
  • 日志对象。
  • 缓存对象。

14.2.2 饿汉式

1
2
3
4
5
6
7
8
9
10
public class Singleton {
private static final Singleton INSTANCE = new Singleton();

private Singleton() {
}

public static Singleton getInstance() {
return INSTANCE;
}
}

特点:类加载时创建对象,线程安全,写法简单,但可能造成对象过早创建。

14.2.3 懒汉式:双重检查锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {
private static volatile Singleton instance;

private Singleton() {
}

public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

注意:这里 volatile 很关键,用于防止指令重排导致其他线程拿到未初始化完成的对象。

14.2.4 静态内部类写法

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
private Singleton() {
}

private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}

public static Singleton getInstance() {
return Holder.INSTANCE;
}
}

特点:懒加载、线程安全、写法优雅,是实际开发中比较推荐的写法。

14.3 工厂模式 Factory

14.3.1 简单工厂

简单工厂不属于 GoF 23 种设计模式之一,但非常常用。它把对象创建逻辑集中到一个工厂类中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
interface Shape {
void draw();
}

class Circle implements Shape {
public void draw() {
System.out.println("画圆形");
}
}

class Rectangle implements Shape {
public void draw() {
System.out.println("画矩形");
}
}

class ShapeFactory {
public static Shape create(String type) {
if ("circle".equals(type)) {
return new Circle();
}
if ("rectangle".equals(type)) {
return new Rectangle();
}
throw new IllegalArgumentException("未知类型:" + type);
}
}

优点:调用方不需要直接 new 具体类。
缺点:新增类型时通常要修改工厂类,不完全符合开闭原则。

14.3.2 工厂方法模式

工厂方法把创建对象的职责延迟到子类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface Product {
void use();
}

interface Factory {
Product createProduct();
}

class ConcreteFactory implements Factory {
public Product createProduct() {
return new ConcreteProduct();
}
}

class ConcreteProduct implements Product {
public void use() {
System.out.println("使用产品");
}
}

适合场景:创建逻辑复杂,并且希望新增产品时不修改已有工厂代码。

14.4 抽象工厂模式 Abstract Factory

抽象工厂用于创建一组相关对象,强调“产品族”。

例子:不同操作系统风格的 UI 组件。

1
2
3
4
5
6
7
8
9
10
11
12
interface Button {
void render();
}

interface CheckBox {
void render();
}

interface UiFactory {
Button createButton();
CheckBox createCheckBox();
}

适合场景:

  • 系统中有多个产品族。
  • 同一个产品族中的对象需要一起使用。
  • 希望切换产品族时,业务代码不需要大改。

14.5 建造者模式 Builder

建造者模式适合创建参数很多、构造过程复杂的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class User {
private final String name;
private final int age;
private final String email;

private User(Builder builder) {
this.name = builder.name;
this.age = builder.age;
this.email = builder.email;
}

public static class Builder {
private String name;
private int age;
private String email;

public Builder name(String name) {
this.name = name;
return this;
}

public Builder age(int age) {
this.age = age;
return this;
}

public Builder email(String email) {
this.email = email;
return this;
}

public User build() {
return new User(this);
}
}
}

调用方式:

1
2
3
4
5
User user = new User.Builder()
.name("张三")
.age(18)
.email("[email protected]")
.build();

优点:可读性好,避免构造方法参数过长。

14.6 原型模式 Prototype

原型模式通过复制已有对象来创建新对象。

1
2
3
4
5
6
7
8
class User implements Cloneable {
private String name;

@Override
protected User clone() throws CloneNotSupportedException {
return (User) super.clone();
}
}

注意:

  • 浅拷贝只复制基本类型和引用地址。
  • 深拷贝会连同引用对象一起复制。
  • 实际开发中要谨慎使用 clone(),复杂对象更推荐手写拷贝方法或使用序列化/工具类。

14.7 代理模式 Proxy

14.7.1 概述

代理模式是一种结构型设计模式。它允许提供一个代理对象来控制对真实对象的访问。代理模式可以用来添加额外行为,例如延迟加载、权限验证、日志记录、事务控制、远程调用等,而不需要修改原始对象代码。

14.7.2 主要角色

角色 说明
Subject 抽象主题,即业务接口类,声明真实对象和代理对象都要实现的方法
RealSubject 真实主题,即真正执行业务逻辑的对象
Proxy 代理对象,持有真实对象引用,并在调用前后增强逻辑

14.7.3 静态代理

静态代理是在程序运行前就已经写好或生成代理类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface UserService {
void save();
}

class UserServiceImpl implements UserService {
public void save() {
System.out.println("保存用户");
}
}

class UserServiceProxy implements UserService {
private final UserService target;

public UserServiceProxy(UserService target) {
this.target = target;
}

public void save() {
System.out.println("开启事务");
target.save();
System.out.println("提交事务");
}
}

优点:简单直观。
缺点:每个接口都要写代理类,类数量容易膨胀。

14.7.4 JDK 动态代理

JDK 动态代理基于接口,通过反射在运行时生成代理对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.lang.reflect.Proxy;

UserService target = new UserServiceImpl();

UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxyObj, method, args) -> {
System.out.println("开启事务");
Object result = method.invoke(target, args);
System.out.println("提交事务");
return result;
}
);

proxy.save();

JDK 动态代理要求目标类实现接口。

14.7.5 CGLIB 代理

CGLIB 通过生成目标类的子类实现代理,不要求目标类实现接口。但如果类或方法被 final 修饰,就无法被 CGLIB 正常代理。

Spring AOP 中常见规则:

  • 目标对象实现了接口,默认优先使用 JDK 动态代理。
  • 目标对象没有实现接口,通常使用 CGLIB。

14.7.6 适用场景

  • 访问对象前后需要增强逻辑,例如日志、事务、权限控制。
  • 对象创建成本较高,希望延迟加载。
  • 需要控制真实对象的访问权限。
  • RPC、AOP、MyBatis Mapper、Spring 事务等框架底层经常使用代理思想。

14.7.7 类结构图说明

原笔记中的类结构图:

UML 说明:

  • 矩形框如果分为三部分,一般表示类:类名、属性、方法。
  • + 表示 public- 表示 private# 表示 protected
  • 带空心三角箭头的线表示继承或实现,箭头指向父类或接口。
  • 标注 Interface 的矩形通常表示接口。

14.8 适配器模式 Adapter

适配器模式用于把一个类的接口转换成客户端期望的另一个接口。

常见例子:

  • 手机充电器把 220V 电压适配成手机需要的电压。
  • Java 中 InputStreamReader 把字节流适配成字符流。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Target {
void request();
}

class Adaptee {
public void specificRequest() {
System.out.println("已有功能");
}
}

class Adapter implements Target {
private final Adaptee adaptee;

public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}

public void request() {
adaptee.specificRequest();
}
}

适合场景:已有类功能可用,但接口不符合当前系统要求。

14.9 装饰器模式 Decorator

装饰器模式用于在不改变原对象结构的情况下,动态增强对象功能。

Java IO 是典型例子:

1
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt"));

FileInputStream 提供基本读取能力,BufferedInputStream 在外层增强缓冲能力。

代理模式和装饰器模式区别:

对比 代理模式 装饰器模式
重点 控制访问 增强功能
客户端是否关注真实对象 通常不关注 通常主动组合增强对象
常见应用 AOP、权限、事务 IO 包装、功能叠加

14.10 外观模式 Facade

外观模式为复杂子系统提供一个统一入口,让调用方更简单。

1
2
3
4
5
6
7
8
9
10
11
class OrderFacade {
private final InventoryService inventoryService = new InventoryService();
private final PayService payService = new PayService();
private final DeliveryService deliveryService = new DeliveryService();

public void createOrder() {
inventoryService.lockStock();
payService.pay();
deliveryService.delivery();
}
}

适合场景:多个子系统调用流程复杂,希望对外暴露一个简单接口。

14.11 策略模式 Strategy

策略模式把不同算法封装成不同策略类,使它们可以互相替换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
interface PayStrategy {
void pay(int amount);
}

class AliPayStrategy implements PayStrategy {
public void pay(int amount) {
System.out.println("支付宝支付:" + amount);
}
}

class WeChatPayStrategy implements PayStrategy {
public void pay(int amount) {
System.out.println("微信支付:" + amount);
}
}

class PayContext {
private final PayStrategy strategy;

public PayContext(PayStrategy strategy) {
this.strategy = strategy;
}

public void pay(int amount) {
strategy.pay(amount);
}
}

适合场景:

  • 多种算法可以互换。
  • 想消除大量 if-else
  • 支付方式、排序规则、优惠策略等场景。

14.12 模板方法模式 Template Method

模板方法模式把固定流程放在父类,把可变步骤交给子类实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
abstract class CookTemplate {
public final void cook() {
prepare();
heat();
addFood();
finish();
}

private void prepare() {
System.out.println("准备食材");
}

private void heat() {
System.out.println("加热");
}

protected abstract void addFood();

private void finish() {
System.out.println("出锅");
}
}

适合场景:整体流程固定,但部分步骤允许子类定制。

14.13 观察者模式 Observer

观察者模式用于一对多通知。当一个对象状态变化时,自动通知依赖它的其他对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Observer {
void update(String message);
}

class Subject {
private final List<Observer> observers = new ArrayList<>();

public void addObserver(Observer observer) {
observers.add(observer);
}

public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}

适合场景:

  • 事件监听。
  • 消息订阅发布。
  • GUI 事件模型。
  • Spring 事件机制。

14.14 责任链模式 Chain of Responsibility

责任链模式把多个处理器串成一条链,请求沿着链传递,直到被某个处理器处理。

1
2
3
4
5
6
7
8
9
abstract class Handler {
protected Handler next;

public void setNext(Handler next) {
this.next = next;
}

public abstract void handle(String request);
}

适合场景:

  • Web 过滤器链。
  • Spring Security 过滤器链。
  • 审批流程。
  • 参数校验流程。

14.15 设计模式复习表

模式 一句话理解 高频场景
单例 全局只有一个实例 配置、缓存、线程池
工厂 把对象创建逻辑封装起来 根据类型创建不同对象
抽象工厂 创建一组相关对象 产品族切换
建造者 分步骤构建复杂对象 参数很多的对象
原型 复制已有对象 创建成本高、需要拷贝
代理 控制对真实对象的访问 AOP、事务、权限
适配器 转换接口 兼容旧接口
装饰器 动态增强功能 Java IO
外观 简化复杂子系统调用 统一门面接口
策略 替换不同算法 支付、优惠、排序
模板方法 固定流程,可变步骤 框架骨架方法
观察者 状态变化后通知依赖者 监听器、事件机制
责任链 多个处理器依次处理 过滤器、审批流

14.16 设计模式学习建议

  • 先学会识别变化点,不要为了用模式而用模式。
  • 小项目中可以优先掌握单例、工厂、策略、模板方法、代理、装饰器、观察者。
  • 设计模式经常和面向对象原则一起出现,例如开闭原则、依赖倒置、组合优于继承。
  • 面试回答时不要只背定义,要结合项目场景,例如“我在支付方式选择中使用策略模式消除了大量 if-else”。

15 补充:Java 高频知识点查漏补缺

本章节为新增补充,用来补齐原笔记中相对薄弱但面试和项目中常见的内容。每一节保持“够用、常见、可复习”的粒度,不展开到源码级别。

15.1 面向对象设计原则

原则 含义 简单理解
单一职责原则 SRP 一个类只负责一类职责 一个类不要又管业务又管日志又管持久化
开闭原则 OCP 对扩展开放,对修改关闭 新增功能尽量新增类,而不是频繁改旧代码
里氏替换原则 LSP 子类应该能替换父类 子类不要破坏父类原有行为
接口隔离原则 ISP 接口不要过于臃肿 不要强迫实现类实现用不到的方法
依赖倒置原则 DIP 面向抽象编程 依赖接口,不直接依赖具体实现
迪米特法则 LoD 尽量少知道其他对象细节 降低对象之间耦合

记忆:设计模式是“解决方案”,设计原则是“指导思想”。

15.2 JVM 基础

15.2.1 JVM 运行流程

  1. .java 源文件通过 javac 编译成 .class 字节码。
  2. 类加载器把 .class 文件加载进 JVM
  3. JVM 对字节码进行验证、准备、解析、初始化。
  4. 执行引擎解释执行或通过 JIT 编译成本地机器码执行。

15.2.2 类加载过程

阶段 说明
加载 读取 .class 文件,生成 Class 对象
验证 校验字节码是否合法、安全
准备 为静态变量分配内存并设置默认值
解析 把符号引用转换为直接引用
初始化 执行静态变量赋值和静态代码块

15.2.3 双亲委派模型

类加载器加载类时,会先把加载请求交给父加载器。父加载器无法加载时,子加载器才会尝试加载。

优点:

  • 避免类重复加载。
  • 保护 Java 核心类库,例如防止用户自定义一个假的 java.lang.String

15.2.4 运行时数据区

区域 线程共享 作用
程序计数器 记录当前线程执行到哪条字节码指令
Java 虚拟机栈 存储方法调用的栈帧、局部变量表等
本地方法栈 为 Native 方法服务
存放对象实例,是垃圾回收重点区域
方法区 / 元空间 存放类信息、常量、静态变量、即时编译代码等

15.2.5 垃圾回收基础

判断对象是否可以回收,常见方法:

  • 引用计数法:对象被引用一次,计数加一;计数为零则可回收。缺点是无法解决循环引用。
  • 可达性分析:从 GC Roots 出发,无法到达的对象可以被回收。Java 主流采用可达性分析。

常见 GC Roots:

  • 虚拟机栈中引用的对象。
  • 方法区中类静态属性引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中 JNI 引用的对象。

常见垃圾回收算法:

算法 特点
标记-清除 简单,但会产生内存碎片
标记-复制 适合新生代,效率高,但浪费一部分空间
标记-整理 适合老年代,避免碎片,但移动对象成本较高
分代收集 根据对象生命周期不同选择不同算法

15.3 HashMap 高频要点

15.3.1 底层结构

JDK 8 之后,HashMap 底层主要是:数组 + 链表 + 红黑树。

当链表过长,并且数组容量达到一定条件时,链表会转换成红黑树,以提升查询效率。

15.3.2 put() 大致流程

  1. 根据 key 计算哈希值。
  2. 根据哈希值计算数组下标。
  3. 如果当前位置为空,直接放入。
  4. 如果当前位置不为空,比较 key 是否相同。
  5. 如果 key 相同,覆盖旧值。
  6. 如果 key 不同,以链表或红黑树形式继续存储。
  7. 元素数量超过阈值时触发扩容。

15.3.3 为什么重写 equals() 一般也要重写 hashCode()

因为哈希集合或哈希映射会先根据 hashCode() 定位桶,再通过 equals() 判断对象是否相等。

如果两个对象 equals() 相等,但 hashCode() 不同,可能会被放进不同桶中,导致去重或查找失败。

15.4 ArrayListLinkedList 高频对比

对比项 ArrayList LinkedList
底层结构 动态数组 双向链表
随机访问 快,时间复杂度 O(1) 慢,时间复杂度 O(n)
尾部添加 通常快,扩容时较慢
中间插入删除 需要移动元素 找到节点后操作快,但查找节点慢
内存占用 较少 较多,需要额外存前后指针
常用程度 更常用 特定场景使用

实际开发中,如果没有明确的频繁头尾操作需求,通常优先使用 ArrayList

15.5 泛型进阶补充

15.5.1 泛型擦除

Java 泛型主要在编译期保证类型安全,编译后会发生类型擦除。

1
2
3
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass()); // true

原因:运行时它们本质上都是 ArrayList

15.5.2 通配符

写法 含义 使用场景
<?> 任意类型 只关心读取,不关心具体类型
<? extends T> T 或 T 的子类 主要用于读取,不能安全写入具体元素
<? super T> T 或 T 的父类 主要用于写入 T 或 T 的子类对象

记忆:PECS 原则:Producer Extends,Consumer Super。生产者用 extends,消费者用 super

15.6 异常处理最佳实践

  • 不要空 catch,否则问题会被吞掉。
  • 不要滥用 Exception 捕获所有异常,应该尽量捕获具体异常。
  • 资源释放优先使用 try-with-resources
  • 自定义异常类名建议以 Exception 结尾。
  • 业务异常和系统异常要区分。
  • 不要用异常控制正常业务流程。

try-with-resources 示例:

1
2
3
4
5
6
try (BufferedReader br = new BufferedReader(new FileReader("a.txt"))) {
String line = br.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
}

15.7 反射、注解和动态代理的关系

技术 作用 常见场景
反射 运行时获取类、字段、方法、构造器信息并操作对象 框架底层、对象创建、方法调用
注解 给代码添加元数据 @Override@Test、Spring 注解
动态代理 运行时生成代理对象,增强方法调用 AOP、事务、日志、权限

三者在框架中的配合:

  1. 通过扫描包找到类。
  2. 通过反射读取类上的注解。
  3. 根据注解创建对象并放入容器。
  4. 如果需要增强方法,就生成代理对象。

15.8 Optional 补充

Optional 用来表达“一个值可能存在,也可能不存在”,减少显式的空指针判断。

1
2
Optional<String> name = Optional.ofNullable(getName());
String result = name.orElse("默认值");

常用方法:

方法 说明
ofNullable(value) 允许传入 null
of(value) 不允许传入 null
orElse(defaultValue) 没有值时返回默认值
orElseGet(Supplier) 没有值时延迟计算默认值
ifPresent(Consumer) 有值时执行操作
map(Function) 对内部值做转换

注意:Optional 更适合作为返回值,不建议作为实体类字段。

15.9 Stream 使用注意事项

Stream 适合对集合进行链式数据处理。

1
2
3
4
List<String> result = list.stream()
.filter(s -> s.length() > 3)
.map(String::toUpperCase)
.collect(Collectors.toList());

常见操作:

操作 类型 说明
filter() 中间操作 过滤元素
map() 中间操作 转换元素
sorted() 中间操作 排序
distinct() 中间操作 去重
collect() 终止操作 收集结果
forEach() 终止操作 遍历消费
reduce() 终止操作 归约计算

注意事项:

  • Stream 不存储数据,它只是数据处理管道。
  • 中间操作是惰性的,只有遇到终止操作才会执行。
  • 一个 Stream 只能被消费一次。
  • 并行流 parallelStream() 不一定更快,数据量小或任务有共享状态时可能更慢。

15.10 IO、BIO、NIO、AIO 简要对比

类型 含义 特点
BIO Blocking IO 同步阻塞,传统 InputStream / OutputStream
NIO Non-blocking IO 同步非阻塞,基于 ChannelBufferSelector
AIO Asynchronous IO 异步非阻塞,完成后回调

NIO 三大核心:

核心 作用
Buffer 缓冲区,存放读写数据
Channel 通道,负责数据传输
Selector 多路复用器,一个线程管理多个通道

后端开发中,Netty 就是基于 NIO 思想实现的高性能网络通信框架。

15.11 Java 版本常见新特性

版本 常见特性
Java 8 LambdaStreamOptional、新的日期时间 API、接口默认方法
Java 9 模块化系统 JPMS、集合工厂方法
Java 10 局部变量类型推断 var
Java 11 标准 HTTP Client、String 新方法、长期支持版本
Java 14 switch 表达式增强
Java 16 record 正式引入
Java 17 密封类 sealed class、长期支持版本
Java 21 虚拟线程、模式匹配增强、长期支持版本

注意:学习基础阶段重点掌握 Java 8 特性;准备新项目或面试时,可以了解 Java 17 / Java 21 的常见变化。

15.12 面试复习优先级建议

如果时间有限,可以按下面顺序复习:

  1. 集合:ArrayListHashMapHashSetConcurrentHashMap
  2. 多线程:线程创建、线程池、synchronizedvolatileReentrantLockCAS
  3. JVM:运行时数据区、类加载、垃圾回收、双亲委派。
  4. 面向对象:封装、继承、多态、接口、抽象类、设计原则。
  5. 常用 API:StringStringBuilder、包装类、日期时间、IO。
  6. 设计模式:单例、工厂、策略、模板方法、代理、装饰器、观察者。
  7. 工程基础:异常处理、日志、单元测试、Maven、Spring 基础。

附录:新增补充与复习清单

Java 学习主线

  1. 先理解运行机制.java 源文件经过 javac 编译为 .class 字节码,再由 JVM 执行。
  2. 再掌握基础语法:变量、数据类型、运算符、流程控制、数组和方法是所有 Java 程序的基础。
  3. 重点突破面向对象:封装、继承、多态、抽象类、接口是 Java 代码组织能力的核心。
  4. 熟悉常用 API 和集合String、包装类、ListSetMap 是刷题、项目和面试的高频内容。
  5. 进入工程化能力:异常、泛型、Stream、IO、多线程、反射、注解、设计模式会直接影响项目代码质量。

高频易错点清单

  • ==:基本数据类型比较值,引用数据类型比较地址;字符串内容比较应使用 equals()
  • String:不可变对象,频繁拼接应优先使用 StringBuilder
  • ArrayList:底层是数组,查询快,增删慢;扩容不是每次加元素都扩。
  • LinkedList:底层是双向链表,首尾操作方便,但随机访问慢。
  • HashSet:自定义对象去重必须重写 equals()hashCode()
  • TreeSet:元素排序依赖自然排序 Comparable 或比较器 Comparator
  • HashMap:键唯一,值可重复;自定义对象作为键时也要重写 equals()hashCode()
  • Scanner.nextInt() 后接 nextLine() 时,容易把回车读走,需要额外处理。
  • try-catch-finallyfinally 常用于释放资源,但现代写法更推荐 try-with-resources
  • 多线程共享变量时要考虑线程安全,不能只看代码逻辑正确,还要考虑可见性、原子性和有序性。

建议后续继续补充的方向

  • Java 8/11/17 新特性对比,例如 LambdaStreamOptional、模块化、Record、文本块等。
  • JVM 基础,例如类加载、运行时数据区、垃圾回收、JMM 内存模型。
  • 常见集合源码重点,例如 ArrayList 扩容、HashMap put 流程、红黑树转换条件。
  • 后端开发常用内容,例如 Maven、Spring、Spring Boot、MyBatis、数据库事务、Redis、消息队列。
  • 面试专项内容,例如 Java 并发、JVM、集合、异常、IO/NIO、设计模式、网络协议。