java核心

Java核心知识点总结

【入门程序、常量、变量】

1. Java语言用途

1
开发网站的后台业务逻辑,例如:传统行业和电商

2. 十进制和二进制的转换规则

1
2
3
十进制转二进制:辗转相除法,一直到商为0为止,取余数,将余数倒着连接就是二进制

二进制转十进制:位权乘以二进制数,再相加

3.计算机存储单元

1
2
3
最小的存储单元是字节,一个字节 = 8 bit,100Mbps ≈ 12MB/s

进制单位是1024

4. Java的跨平台性?

1
因为Java运行在虚拟机上,不同的系统对应不同版本的虚拟机(JVM)

5. JDK、JRE和JVM

1
2
3
4
5
6
7
JDK:是开发工具包,能进行开发,运行环境

JRE:是java的运行环境

JVM:是java的核心,虚拟机

包含关系: jdk --> jre --> jvm

6. 环境变量配置的意义

1
配置环境变量的意义是让操作系统知道去哪里找java命令,使计算机能够在任意目录下运行java和javac这两个命令

7. Java程序编写的三个步骤

1
2
3
4
5
编写:程序员编写代码

编译:将我们编写的代码编程计算机能识别的代码

运行:运行字节码文件,.class文件,显示效果

8. 常用命令提示符

功能 输入
切换盘符 盘符名称:
进入文件夹 cd 文件夹1\文件夹2\文件夹3
返回上一级 cd ..
直接回根路径 cd \
查看当前文件夹内容 dir
清屏 cls
退出 exit

9. 注释及其格式

1
注释是对代码进行解释说明的文字,提高代码的可读性

java中的注释分三种:

  • 单行注释: // 文字
  • 多行注释: /* 文字 */
  • 文档注释: /** 文字 */

10. 关键字

1
2
3
关键字:被java语言赋予了特殊的含义的单词,Java一共定义了50个关键字。

特点:(1)全部小写;(2)在不同的编辑器下关键字的颜色是不一样的

目前已学的关键字可分为4类:(更新至Day05课程)

定义数据类型:class byte short int long float double char boolean void

定义数据值类型:true false null

定义流程控制:if else switch case default while do for break continue return

其他类型:public static new package import

11. 标识符及其定义规范

1
标识符:自己定义的单词

硬性要求:

  1. 标识符包含英文26字母(大小写)、数字0-9、$、_
  2. 不能以数字开头
  3. 不能是关键字

软性建议:

  1. 类名:大驼峰(单词的首字母全部大写)
  2. 变量名:小驼峰(如果有多个单词,那么第一个单词首字母小写,后面单词首字母大写)
  3. 方法名:小驼峰

12. 数据类型

1
2
3
java是强类型语言, 对于每一种数据类型都规定了明确的取值范围。

数据类型分为两种:基本数据类型(四类八种)、引用数据类型(目前有String和数组)
四类 八种 字节数 表示范围
整型 byte 1 -128~127
整型 short 2 正负3万多
整型 *int * 4 正负21亿多
整型 long 8 正负19位数
布尔型 boolean 1 true, false
字符型 char 2 一个字符’A’,’a’
浮点型 float 4 正负38位数
浮点型 double 8 正负308位数

13. 常量及其分类

1
常量:在程序的执行过程中, 其值不可以发生改变的量
  1. 整数常量:所有整数
  2. 浮点数常量:所有小数
  3. 字符常量:被单引号括起来的内容, 里面只能装单个字
  4. 字符串常量:被双引号括起来的内容
  5. 布尔常量:true、false
  6. 空常量:null

14. 变量及其定义格式

1
变量:在程序的执行过程中, 其值在某个范围内可以发生改变的量。变量就像一个容器,可以不断修改其中的内容。

三种定义格式:

  1. 一步到位 : int a = 10;
  2. 分开定义,赋值: int a; a = 10;
  3. 简便格式: int a = 10, b = 20, c = 30;

变量需注意的细节:

  1. 变量不赋值,不能使用
  2. 变量名不能重复定义
  3. 变量赋值的范围,不能超过数据类型的最大表示范围
  4. 变量有作用域,作用域是一对大括号
  5. long类型数据需加上标识L,float类型数据需加上标识F

【数据类型转换、运算符、方法入门】

1. 数据类型转换

1
2
3
自动类型转换:是程序自动完成的,将小的数据类型转换成大的数据类型

强制类型转换:需要手动进行转换,将大的数据类型转换成小的数据类型 格式:int a = (int) 10.5;

2. 精度损失和数据溢出

1
2
3
精度损失:当一个浮点数转为整数的时候,会发生精度损失,精度损失是直接舍弃小数部分

数据溢出:当一个表示范围大的数据类型强转为范围小的数据类型的时候,会发生数据溢出,造成部分数据丢失

3. 运算时数据类型的转换规则

1
2
3
范围小的类型向范围大的类型提升,byteshortchar 运算时直接提升为int

byteshortchar-->int-->long-->float-->double

4. 编译器的两点优化

1
2
3
4
优化一:对于byte/short/char三种类型来说,如果右侧赋值的数值没有超过范围,那么javac编译器将会自动隐含地为
我们补上一个(byte)(short)(char)。

byte num1 = /*(byte)*/ 30; // 右侧没有超过左侧的范围
1
2
3
4
5
优化二:在给变量进行赋值的时候,如果右侧的表达式当中全都是常量,没有任何变量,那么编译器javac将会直接将若干个常量表达式计算得到结果。但是注意:一旦表达式当中有变量参与,那么就不能进行这种优化了。

short a = 5;
short result = 5 + 8; //编译通过
short result2 = a + 8; //编译报错

5. 加号的三种用法

1
2
3
4
5
1.数值运算

2.和char类型参数运算的时候,会将char类型按照ASCII码表,查表找到对应的ASCII值,进行计算

3.用于字符串拼接,所有的数据类型和字符串进行拼接,都会变成字符串,也要看运算顺序

6. ASCII码表

美国标准信息交换码(American Standard Code for Information Interchange )

1
2
3
人类定义的一个字符和计算机中二进制存储的对照关系表,是所有编码表的核心。

编码表本身是字符和一个十进制数进行对应起来组成一张表格,需要记住:'0'-48,'A'-65,'a'-97

7. 运算符

类型 符号
算数运算符 + - * / % ++ --
赋值运算符 = += -= *= /= %=
比较运算符 > < == >= <= !=
逻辑运算符 & `
三元运算符 数据类型 变量名 = 条件判断?表达式A:表达式B

运算符注意事项:

  1. 除法,不会出现小数,会将小数部分舍弃
  2. + 符号在遇到字符串的时候,表示连接、拼接的含义
  3. 比较运算符返回的一定是布尔值true、false
  4. 逻辑运算符符号两边一定是布尔值
  5. 短路效果:如果已经得到结果,那么不会进行后面的操作(双写& | 得到短路效果)
  6. 三元运算符的结果必须被使用,必须同时保证表达式A和表达式B都符合左侧数据类型的要求。

8. ++、–的使用场景

1
2
3
单独操作: 就是自身完成+1或者是-1的动作;

参数运算:++在前, 先自增, 再赋值;++在后, 先赋值, 再自增。

9. a += 1和a = a + 1的区别

1
a += 1 等价于 a = (a的数据类型)(a+1),当中存在隐含了一个强制类型转换的过程

10. 定义方法的好处

1
2
3
1. 将代码按照功能进行划分,提高代码的可读性

2. 提高代码的复用性

11. 方法定义和调用的注意事项

1
2
3
4
5
6
7
1. 不能嵌套定义,方法中不能定义方法

2. 方法的执行顺序和定义顺序无关,和调用顺序有关

3. 方法不调用就不执行,main方法不能人为调用

4. 在定义方法形式参数的时候,参数需要用逗号隔开

【流程控制语句】

1. if语句的三种格式

1
2
3
4
5
1.if--一种情况的判断

2.if...else两种情况的判断

3.if...else if...else多种情况判断

2. 程序的健壮性

1
健壮性:我们需要尽可能多的考虑程序出现的情况,并给出解决方案

3. switch语句的格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) {
int num = 1;
switch (num) {
case 1:
System.out.println("春天");
break;
case 2:
System.out.println("夏天");
break;
case 3:
System.out.println("秋天");
break;
case 4:
System.out.println("冬天");
break;
default:
System.out.println("输入数据超出范围");
break;
}
}

switch语句的注意事项:

  1. switch可以接受的4+2种数据类型:4种基本:byte/short/char/int ; 2种引用:String/enum
  2. case后面的表达式不能重复
  3. case语句的穿透效果:没有break会往下穿透
  4. switch语句很灵活,前后顺序可颠倒

4. 三种循环语句的格式

(1)for循环
1
2
3
4
5
6
7
格式: for(初始化语句;条件判断语句;步进语句){
循环体;
}

示例: for(int i = 1; i <= 10; i++){
sop(i);
}
(2)while循环
1
2
3
4
5
6
7
8
9
10
11
格式:   初始化语句;
while(条件判断语句){
循环体;
步进语句;
}

示例: int i = 0;
while(i <= 10){
sop(i);
i++;
}
(3)do..while循环
1
2
3
4
5
6
7
8
9
10
11
格式:	  初始化语句;
do{
循环体;
步进语句;
}while(条件判断语句);

示例: int i = 0;
do{
sop(i);
i++;
}while(i <= 10);

5. 三种循环语句的区别

1
2
3
4
5
1. for循环的初始化表达式出了循环不能使用,while循环可以使用

2. for循环和while循环条件不满足,一次不会执行;do...while循环无论如何执行一次

3. 当已知循环次数的情况下,建议使用for循环,不知道次数建议使用while循环

6. break和continue

1
2
3
break:在switch语句中,表示遇到break,switch语句结束;在循环语句中,表示跳出循环,继续往下执行代码

continue:结束本次循环,进入下一次循环

7. 死循环的格式

1
2
3
while(true);

for(;;);

【Idea、方法】

1. 什么是方法

1
2
3
4
5
方法就是经常使用的一部分代码抽取成的代码块

方法可以理解为工厂,给方法一些参数,方法换给我们一个结果

方法可以理解为模板,方法是写的通用的逻辑,可以重复调用多次,根据传入的参数不同,返回的结果也不同

2. 方法的定义格式

1
2
3
4
5
6
7
修饰符  返回值类型  方法名(参数列表){

方法体;

return (返回值);

}

3. return的作用

1
2
3
1. 将返回值返回给方法的调用处

2. 结束方法

4.方法的调用的方式

1
2
3
4
5
1. 单独调用:适用于有返回值的和无返回值的方法,有返回值的方法单独调用没有意义

2. 打印调用:只适用于有返回值的方法,打印调用返回值结果只能使用一次

3. 赋值调用:只适用于有返回值的方法,返回值使用变量接收,可以使用多次

5. 方法的执行流程

1
2
3
4
5
6
7
1. 以main方法为起点,虚拟机调用main方法,main方法中调用其他方法

2. 调用方法,传递参数

3. 将传递的实参给方法中定义的形参进行赋值 ,执行方法体

4. 使用return语句将返回值,返回给方法的调用处

6. 方法的三要素

1
2
3
4
5
1. 返回值类型:看需求,说是想让你给出结果,还是直接在方法中打印

2. 参数列表:看该方法完成任务,需不需要外部提供的参数

3. 方法名:小驼峰命名规则,见名知意

7. 方法的重载

1
方法的重载:是指方法名称相同,但参数列表不同的一组方法。用户希望相似的功能,只需要记一个方法名称。

方法重载的三个相关

  1. 参数列表的个数
  2. 参数列表的数据类型
  3. 参数列表的参数顺序

方法重载的三个无关:

  1. 和参数的变量名称无关
  2. 和方法的返回值类型无关
  3. 和方法的修饰符无关

8. IDEA 常用快捷键

功能 快捷键
调出结果窗口 Alt + 4
自动导包+修正代码 Alt + Enter
删除光标所在行 Ctrl + Y
往下复制一行 Ctrl + D
格式化代码 Ctrl + Alt + L
自动生成常用方法 Alt + Insert
移动当前代码行 Alt + Shift + 上下箭头
选中一个变量的所有使用处 Shift + F6
快速生成for循环 XXX.fori

【数组】

1. 什么是数组

1
数组是Java中的一种容器,用于存储数据
  1. 数组中存储的数据类型一致
  2. 数组是引用数据类型
  3. 数组的长度一旦确定,不可改变

2. 数组的初始化

1
2
3
4
5
6
7
8
9
10
动态初始化:int[] array = new int[3];
静态初始化:int[] array = new int[]{1,2,3};
静态初始化的省略格式:int[] array = {1,2,3};
动态初始化的拆分:
int[] array;
array = new int[3];
静态初始化拆分:
int[] array;
array = new int[]{1,2,3};
(拆分后不能使用省略格式进行初始化:array = {1,2,3})

3. 索引

1
索引就是数组元素的编号,从0开始,到length-1为止

赋值:数组名[索引值] = 值;

访问:数据类型 变量名 = 数组名[索引值];

4. Java内存的5个组成部分

1
2
3
4
5
1.栈(Stack):方法运行时使用的内存,比如main方法运行,进入方法栈中执行,存放方法中的局部变量。
2.堆(Heap):存储对象或者数组,new来创建的,都存储在堆内存
3.方法区(Method Area):存储可以运行的class文件
4.本地方法栈(Native Method Stack):JVM在使用操作系统功能的时候使用,和开发无关
5.寄存器(pc Register):与CPU相关,性能极高,和开发无关

5. 数组越界索引异常

1
2
系统访问了数组中不存在的索引,将抛出数组越界索引异常。
(ArrayIndexOutOfBoundsException)
1
2
3
4
public static void main(String[] args) {
int[] arr = {1,2,3};
System.out.println(arr[3]);
}

6. 数组空指针异常

1
2
给数组赋值null之后,数组将不会保存数组的内存地址,也不允许再操作数组了,运行时会抛出空指针异常。
(NullPointerException)
1
2
3
4
5
 public static void main(String[] args) {
int[] arr = {1,2,3};
arr = null;
System.out.println(arr[0]);

7. 方法的参数类型区别

1
2
3
方法的参数为基本类型时,传递的是数据值,值不会受到影响;方法的参数为引用类型时,传递的是地址值,所以在方法中

将引用数据类型中的值进行修改,那么会永久性的修改

8. 数组的直接打印

1
2
3
直接打印数组名称,得到的是数组对应的内存地址哈希值
[I@7575412c2f
其中[代表数组,I代表int

9. 数组的内存执行流程

  1. 编写代码,编译代码,生成.class字节码文件
  2. 字节码文件,将信息加载到方法区中,方法区中有类的方法信息
  3. JVM虚拟机去找程序的入口——main方法
  4. main方法进栈执行
  5. main方法中定义的变量,会在栈内存中生成
  6. 在堆内存中,开辟了一块空间,将空间中的数值赋默认值,JVM将数组的内存地址赋值给引用类型变量
  7. 打印数组名称,找到堆内存中的地址值.
  8. 先通过数组名找到堆内存中的地址值,然后通过索引值找到对应的数据值,在数据值进行修改
  9. main方法会出栈/弹栈
  10. 堆内存中的两块区域,没有变量去引用了,堆内存中的内容会被JVM垃圾回收机制回收

10. 数组的反转

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
for (int min = 0, max = arr.length ‐ 1; min <= max; min++, max‐‐) {
//利用第三方变量完成数组中的元素交换
int temp = arr[min];
arr[min] = arr[max];
arr[max] = temp;
}
// 反转后,遍历数组
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}

【类与对象、封装、构造方法】

1. 面向对象

1
2
3
就是当我们需要做一件事情的时候,不是去自己亲力亲为的考虑每一个细节,而是找到能做这件事情的人,帮我们做事

在代码中,就是找到能完成这个功能的类,并调用这个类中的方法。

2. 类与对象

1
2
3
类:是一个模板,描述了现实事物的信息

对象:是根据类这个模板生成的一个实体,会在堆内存中开辟空间

类是对象的模板,对象是类的实体

3. 类描述的信息

1
2
3
属性:事物具备什么特征,在代码中叫做成员变量

行为:事物能做什么事情,在代码中叫做成员方法

4. 对象的使用方式

1
2
3
4
5
导包:通常不用考虑,IDEA会进行自动导包

创建对象:根据类生成一个对象实例

使用:给对象中的成员变量进行赋值,调用方法

5. 成员变量和局部变量区别

成员变量 局部变量
类中位置 类中,方法外 方法内
作用范围 类中 方法中
初始化值 有默认值 无默认值
内存中位置 堆内存 栈内存
生命周期 随着对象存在 随着方法存在

6. 封装

1
2
3
4
5
就是将细节隐藏起来,对外只暴露实现方式

(1)方法是一种封装

(2)private关键字也是一种封装

7. private关键字

1
将成员变量私有,只允许本类访问,不允许外界访问,对外提供Getter/Setter方法

8. this关键字

1
2
3
this代表所在类的当前对象的引用(地址值),即对象自己的引用。

方法被哪个对象调用,方法中的this就代表那个对象。即谁在调用,this就代表谁。

9. 成员变量的默认值

数据类型 默认值
基本类型 整数 0
浮点数 0.0
字符 ‘/u0000’
布尔 false
引用类型 数组,类,接口 null

10. 构造方法注意事项

  1. 构造方法的名称必须和所在的类名称完全一样,就连大小写也要一样
  2. 构造方法不要写返回值类型,连void都不写
  3. 构造方法不能return一个具体的返回值
  4. 如果你不提供构造方法,系统会给出无参数构造方法。
  5. 如果你提供了构造方法,系统将不再提供无参数构造方法。
  6. 构造方法是可以重载的,既可以定义参数,也可以不定义参数。

11. 标准代码——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
public class Student {
//成员变量
private String name;
private int age;
//构造方法
public Student() {}
public Student(String name,int age) {
this.name = name;
this.age = age;
}
//成员方法
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}

12. 对象的内存图流程

  1. 方法区加载类的方法信息
  2. JVM去找main方法,main方法进栈执行
  3. 在栈内存中生成一个变量Phone one,在堆内存中开辟一块空间,内含成员变量和成员方法
  4. 对成员变量进行赋值
  5. 调用成员方法,方法进栈执行,执行完毕之后,方法出栈
  6. 最后main方法出栈
  7. 垃圾回收机制回收堆内存中的两块区域

【Scanner类、Random类、ArrayList类】

1. API的使用方式

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

1
2
3
4
5
(1)看包路径 -->导包

(2)构造方法-->创建

(3)成员方法-->使用

2. Scanner类使用步骤

1
2
3
4
5
6
7
Scanner sc = new Scanner(System.in);

sc.nextInt();录入整数

sc.next();录入字符串,不含空格,Tab。有效字符之前的空格、Tab被视为无效;之后的空格、Tab视为结束符号

sc.nextLine();可以包含空格和Tab,只以回车为结束符号

3. Random类使用步骤

1
2
3
4
5
Random ran = new Random();

ran.nextInt();生成一个int范围的随机数

生成(a~b)范围的随机数: ran.nextInt(b-a+1)+a

4. 匿名对象

1
2
3
4
5
匿名对象就是没有变量名接受的对象,new  类名();

优点:省略变量名,减少代码的编写

缺点:对象只能使用一次

5. 数组与集合的不同

数组 集合
运行期间长度 不可变 可变
存储数据类型 基本+引用数据类型 引用数据类型
直接打印 地址值 集合的内容
可操作方法 查、改 增、删、改、查
获取长度 数组名.length 集合名称.size()

6. ArrayList类

1
ArrayList集合是Java中的一种容器,底层是数组,默认初始长度是10

特点:

  1. 长度是可变的
  2. 只能存储引用数据类型,如果想存储基本数据类型,要使用其包装类
  3. 直接打印显示的是集合的内容

7. ArrayList中的常用方法

1
2
3
4
5
public boolean add(E e):向集合当中添加元素,参数的类型和泛型一致,返回值是boolean(Always true)
public E remove(int index):从集合当中删除元素,参数是索引编号,返回值就是被删除掉的元素
public E set(int index, E element): 用指定的元素替代此列表中指定位置上的元素,返回被替代的元素
public E get(int index):从集合当中获取元素,参数是索引编号,返回值就是对应位置的元素
public int size(): 获取集合的尺寸长度,返回值就是集合中包含的元素个数

8. 包装类

基本类型 基本类型包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

自动装箱:基本类型 –>包装类型
自动拆箱:包装类型 –>基本类型

【String类、static关键字、Arrays类、Math类】

1. String类的三个特点

1
2
3
4
5
1. 字符串不变:字符串的值在创建后不能被更改。

2. 因为String对象是不可变的,所以它们可以被共享。

3. "abc" 等效于 char[] data={'a','b','c'},底层原理是byte[]字节数组。

2. 创建字符串的1+3种方法

1
2
3
4
5
6
一种直接创建:直接写上双引号,就是字符串对象

三种构造方法:
1. public String():初始化新创建的 String对象,以使其表示空字符序列
2. public String(char[] array):根据字符数组的内容,来创建对应的字符串
3. public String(byte[] array):根据字节数组的内容,来创建对应的字符串

字符串常量池:直接写“ ”的字符串,在常量池中,如果常量池中有相同的字符串,那么不会创建新的,而是使用以前的。双引号直接写的字符串在常量池当中,new出来的不在池当中。

1
2
3
4
5
6
7
public static void main(String[] args) {
String s1 = "Java";
String s2 = "Java";
char[] chars = {'J', 'a', 'v', 'a'};
String s3 = new String(chars);
System.out.println("s1和s2是否直接相等" + (s1 == s2)); //true
System.out.println("s1和s3是否直接相等" + (s1 == s3)); //false

3.String常用方法

判断
1
2
public boolean equals(Object anObject):将此字符串与指定对象进行比较。
public boolean equalsIgnoreCase(String anotherString):将此字符串与指定对象进行比较,忽略大小写。
获取
1
2
3
4
5
6
public int length () :返回此字符串的长度。
public String concat (String str) :将指定的字符串连接到该字符串的末尾。
public char charAt (int index) :返回指定索引处的 char值。
public int indexOf (String str) :返回指定子字符串第一次出现在该字符串内的索引。
public String substring (int begin) :返回一个子字符串,从begin开始截取字符串到字符串结尾。
public String substring (int begin, int end) :返回一个子字符串,从[begin,end)截取字符串。
转换
1
2
3
public char[] toCharArray () :将此字符串转换为新的字符数组。
public byte[] getBytes () :使用平台的默认字符集将该 String编码转换为新的字节数组。
public String replace (CharSequence target, CharSequence replacement) :将与target匹配的字符串使用replacement字符串替换。
分割
1
public String[] split(String regex) :将此字符串按照给定的regex(规则)拆分为字符串数组。

String可以比,接,截,割,替,从内容获取索引,从索引获取内容

equal, concat, substring, split, replace, indexof, charAt

4. 正则表达式

正则表达式也是一个字符串,是专门解决字符串规则匹配的工具,用来定义匹配规则

表达式 含义
x 字符 x
\\ 反斜线字符
[abc] a、b 或 c(简单类)
[^abc] 任何字符,除了 a、b 或 c(否定)
[a-zA-Z] a 到 z 或 A 到 Z,两头的字母包括在内(范围)
\w 单词字符:[a-zA-Z_0-9]
X? X,一次或一次也没有
X* X,零次或多次
X+ X,一次或多次
X{n} X,恰好 n 次
X{n,} X,至少 n 次
X{n,m} X,至少 n 次,但是不超过 m 次

5. static关键字

1
2
3
static关键字可以用来修饰的成员变量和成员方法,被修饰的成员是属于类的,而不是单单是属于某个对象的。

也就是说,既然属于类,就可以不靠创建对象来调用了。

静态存储在方法区中,既不在堆中,也不在栈中(方法中有一块独立的静态区)

类变量
1
2
3
当 static 修饰成员变量时,该变量称为类变量。该类的每个对象都共享同一个类变量的值。

任何对象都可以更改该类变量的值,也可以在不创建该类的对象的情况下对类变量进行操作。
静态方法
1
当static修饰成员方法时,该方法称为类方法,习惯称为静态方法。建议使用类名来调用,而不需要创建类的对象。

静态方法调用的注意事项:

  1. 静态方法可以直接访问类变量和静态方法。
  2. 静态方法不能直接访问普通成员变量或成员方法。反之,成员方法可以直接访问类变量或静态方法。
  3. 静态方法中,不能使用this关键字。
静态代码块
1
类中方法外,使用static修饰的代码块{ }。

当第一次用到本类时,静态代码块执行唯一的一次,优先于main方法和构造方法的执行。

作用:给类变量进行初始化赋值。

6. Arrays类

1
2
public static String toString(int[] a) :返回指定数组内容的字符串表示形式。
public static void sort(int[] a) :对指定的 int 型数组按数字升序进行排序。
1
2
3
4
5
6
public static void main(String[] args) {
int[] arr = {2,34,35,4,657,8,69,9};
System.out.println(arr); // [I@2ac1fdc4
System.out.println(Arrays.toString(arr)); // [2, 34, 35, 4, 657, 8, 69, 9]
Arrays.sort(arr);
System.out.println(Arrays.toString(arr)); //[2, 4, 8, 9, 34, 35, 69, 657]

备注:如果是数值,sort方法默认按照升序从小到大
如果是字符串,sort方法默认按照字母升序
如果是自定义的类型,那么这个自定义的类需要有Comparable或者Comparator接口的支持

1
2
3
4
5
6
7
// 冒泡排序法代码
public static void mySort(int[] arr){
for (int i = 1; i < arr.length; i++) {
for (int j = i; j > 0 && arr[j] < arr[j-1] ; j--) {
int temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;} } }

7. Math类

1
2
3
4
public static double abs(double a):获取绝对值
public static double ceil(double a):向上取整
public static double floor(double a):向下取整
public static long round(double a):四舍五入

【继承、super、this、抽象类】

1. 继承

1
2
3
就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。

子类可以直接访问父类中的非私有的属性和行为。

语法格式:子类 extends 父类

继承的作用:提高代码复用性,类与类之间产生了关系,是多态的前提。

继承的特点:

  1. 一个子类只能有一个直接父类,不能有多个直接父类
  2. 继承可以支持多级继承,一个子类只能有一个直接父类,但可以有多个间接父类
  3. 一个父类可以有多个子类

2. 抽象类

1
对子类共性的内容进行抽取,有可能包含抽象方法的类

语法格式:abstract关键字

抽象类的作用:

  1. 为子类提供便利:抽象类中可以定义一些方法,子类继承之后可以直接使用
  2. 对子类进行约束:抽象类中的抽象方法,子类继承之后,必须重写,否则子类也是一个抽象类

注意事项:

  1. 抽象类不能创建对象,如果要创建,需要创建的是子类(抽象类的对象调用方法无方法体,无意义)
  2. 抽象类中可以包含构造方法,用于初始化父类成员
  3. 抽象类中可以没有抽象方法,但是只要类中有抽象方法,那这个类一定是一个抽象类
  4. 子类继承抽象类必须重写全部抽象方法,否则该子类也是一个抽象类

3. 继承后的特点

成员变量重名
1
使用super关键字区分
成员方法重名——重写(Override)
1
子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。

方法重写的注意事项:

  1. 必须保证父子类之间方法的名称相同,参数列表也相同。
  2. @Override:写在方法前面,用来检测是不是有效的正确覆盖重写,也可以不写。
  3. 子类方法的返回值必须【小于等于】父类方法返回值的范围。
  4. 子类方法的权限必须【大于等于】父类方法的权限修饰符。

备注:public > protected >(default) > private,(default)不是关键字,是什么都不写,留空。

构造方法

构造方法注意事项:

  1. 子类构造方法当中有一个默认隐含的“super()”调用,所以一定是先调用的父类构造,后执行的子类构造。
  2. 子类构造可以通过super关键字来调用父类重载构造。
  3. super的父类构造调用,必须是子类构造方法的第一个语句。不能一个子类构造调用多次super构造。

4. super关键字的三种用法

1
2
3
4
5
(1)在子类的成员方法中,访问父类的成员变量

(2)在子类的成员方法中,访问父类的成员方法

(3)在子类的构造方法中,访问父类的构造方法

5. this关键字的三种用法

1
2
3
4
5
(1)在本类的成员方法中,访问本类的成员变量

(2)在本类的成员方法中,访问本类的成员方法

(3)在本类的构造方法中,访问本类的另一种重载的构造方法

注意:super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。

【接口、多态】

1. 接口的定义

1
接口,是Java语言中一种引用类型,是方法的集合,使用interface关键字,是一种公共的规范标准。

作用:(1)提供功能的拓展(2)提出约束

定义格式:public interface 接口名称{接口内容}

注意:接口也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。

2. 接口包含的内容

常量(Java 7)
1
2
3
接口当中也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰。

一旦赋值,不可以修改,从效果上看,这其实就是接口的【常量】。

注意事项:

  1. 三个修饰符可以省略,但是不写也默认有
  2. 接口当中的常量,必须进行赋值;不能不赋值。
  3. 接口中常量的名称,使用完全大写的字母,用下划线进行分割:

public static final int NUM_OF_PEOPLE = 10;

抽象方法(Java 7)
1
public abstract  返回值  方法名称(参数列表);

public abstract 可以省略不写

默认方法(Java 8)
1
public default 返回值类型 方法名称(参数列表){方法体}

接口中的默认方法用于接口的升级和修改,可以保证所有的实现类不必被强制要求重写抽象方法。

如果重写使用重写的方法,如果不重写使用默认方法。

静态方法(Java 8)
1
public static 返回值类型 方法名称(参数列表){方法体}

注意:不能通过接口实现类的对象来调用接口当中的静态方法!因为一个实现类可能实现多个接口,通过对象调用接口的静态方法有可能方法名重复导致冲突。正确用法:通过接口名称,直接调用其中的静态方法。

私有方法(Java 9)
1
2
3
1)普通私有方法:private  返回值  方法名称(参数列表){}

2)静态私有方法:private static 返回值 方法名称(参数列表){}

解决多个默认方法和静态方法代码重复度过高问题,同时避免该方法被接口的实现类使用。

从设计的角度讲,私有的方法是对默认方法和静态方法的辅助。

3. 接口的多实现

1
一个类可以实现多个接口

注意事项:

  1. 有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。
  2. 有多个默认方法时,实现类都可继承使用。如果默认方法有重名的,必须重写一次。
  3. 存在同名的静态方法并不会冲突,原因是只能通过各自接口名访问静态方法。
  4. 当一个类既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的默认方法重名,子类就近

选择执行父类的成员方法。

4. 接口的特点

1
2
3
4
5
6
7
8
9
1. 一个接口能继承另一个或者多个接口,使用 extends 关键字,子接口继承父接口的方法。

2. 如果父接口中的默认方法有重名的,那么子接口需要重写一次。

3. 如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类。

4. 接口中,没有构造方法,不能创建对象。

5. 接口中,没有静态代码块。

5. 多态

1
父类引用指向子类对象,一个对象的多种形态

多态使用的前提条件:必须以继承或者实现为前提条件(用父类或者接口去接收对象都行)

多态的表现形式:父类类型 变量名 = new 子类(); 接口类型 变量名 = new 实现类();

6. 多态成员的访问

1
2
3
4
5
6
7
8
9
多态成员变量的访问方式:编译看左边,运行也看左边

(1)直接访问:看等号左边是谁就优先用谁,没有向上找

(2)间接访问:方法属于谁,就优先用谁,没有向上找

多态成员方法的访问方式:编译看左边,运行看右边

看等号右边对象创建的是谁,就优先用谁,没有向上找

7. 多态的好处和弊端

1
2
3
好处:提高代码复用性

弊端:不能使用子类独有的方法

8. 引用、对象、对象名称的区分

1
2
3
4
5
6
7
Animal a = new Cat();

引用:等号左侧的数据类型叫引用(Animal)

对象:等号右边new的就是对象(new Cat())

对象名称:变量名称就叫对象名称(a)

9. 向上转型和向下转型

1
2
3
向上转型:向上转型一定是安全的,因为左父右子,从小范围转向了大范围 

向下转型:子类类型 变量名 = (子类类型)父类对象;不安全

对象的向下转型,其实是一个【还原】的动作。由哪个子类转成的父类类型,再转回去要注意,不能转为其

他子类类型,否则会报错。(ClassCastException)

10. instanceof 关键字

1
2
3
格式:变量名 instanceof 数据类型

如果变量属于该数据类型,返回true;如果变量不属于该数据类型,返回false

instanceof 可用于判断对象属于哪一个实例,一般都在方法中使用。

对传入的父类类型的对象进行实例判断,强转回子类,目的是调用子类特有的方法

1
2
3
4
5
6
7
8
9
10
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog){
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse

【final、权限、内部类、引用类型】

1. final 关键字

1
被final关键字修饰的类、方法和变量不可改变。有以下四种主要用法:
修饰类
1
2
final class 类名 {
}
修饰方法
1
2
3
修饰符 final 返回值类型 方法名(参数列表){
//方法体
}
修饰局部变量
1
因为局部变量无初始化默认值,可以先定义后赋值。{final int a; a = 10;}
修饰成员变量
1
2
3
4
5
1. 由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值了。{final int a = 10;}

2. 对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值,二者选其一。

3. 必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值。

2. 权限修饰符

public protected (default) private
同一类中
同一包中
不同包的子类
不同包的无关类

3. 成员内部类

1
2
3
4
5
6
定义在类中方法外的类。
class 外部类 {
class 内部类{

}
}

访问特点

  1. 内部类可以直接访问外部类的成员,包括私有成员。
  2. 外部类要访问内部类的成员,必须要建立内部类的对象。
  3. 创建内部类对象格式:外部类名.内部类名 对象名 = new 外部类型().new 内部类型();

注意:

  1. 内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号 。比如,Outer$Heart.class
  2. 如果内部类和外部类的变量出现了重名现象,那么在内部类调用外部类变量的格式是:

外部类名称.this.对象名

1
2
3
4
5
6
7
8
9
10
11
12
public class Outer {
int num = 10; // 外部类的成员变量
public class Inner {
int num = 20; // 内部类的成员变量
public void methodInner() {
int num = 30; // 内部类方法的局部变量
System.out.println(num); // 30 局部变量,就近原则
System.out.println(this.num); // 20 内部类的成员变量
System.out.println(Outer.this.num); // 10 外部类的成员变量
}
}
}

4. 类的权限修饰符

1
2
3
4
5
6
7
定义一个类的时候,权限修饰符规则:

1. 外部类:public/(default)

2. 成员内部类:四个都行

3. 局部内部类:什么都不能写

5. 局部内部类的final问题

1
2
3
4
5
6
7
8
9
//局部内部类,如果希望访问所在方法的局部变量,那么这个局部变量必须是【有效final的】。
public void methodOuter() {
int num = 10; // 所在方法的局部变量,从Java 8+开始,只要局部变量事实不变,那么final关键字可以省略
class MyInner {
public void methodInner() {
System.out.println(num);
}
}
}

原因:

  1. new出来的对象在堆内存当中。
  2. 局部变量是跟着方法走的,在栈内存当中。
  3. 方法运行结束之后,立刻出栈,局部变量就会立刻消失。
  4. 但是new出来的对象会在堆当中持续存在,直到垃圾回收消失。

6. 匿名内部类

1
是内部类的简化写法。它的本质是一个带具体实现的【父类或者父接口的】匿名的子类对象。
1
2
3
4
5
6
7
8
//格式
new 父类名或者接口名(){
// 方法重写
@Override
public void method() {
// 执行语句
}
};

匿名内部类特点:

匿名内部类的好处:不用编写实现类,就能创建实现类对象。

匿名内部类的弊端:创建的这个实现类对象,模板只能使用一次。

匿名内部类和匿名对象不是一回事,但是可以【匿名内部类】+【匿名对象】组合使用

【Object类、常用API】

1. Object类

1
java.lang.Object类是Java语言中的根类,即所有类的父类。它中描述的所有方法子类都可以使用。如果一个类没有特别指定父类, 那么默认则继承自Object类。

2. toString方法

1
2
toString方法返回该对象的字符串表示,其实该字符串内容就是对象的类型+@+内存地址值。
直接打印对象的名字,其实就是调用对象的toString方法。toString方法可以通过快捷键快速覆盖重写。

3. equals方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
调用成员方法equals并指定参数为另一个对象,则可以判断这两个对象是否是相同的。
默认进行的比较是 == 运算符的对象地址比较,只要不是同一个对象,结果必然为false
希望进行对象内容的比较时,可以通过重写进行对象内容的比较:

@Override
public boolean equals(Object o) {
// 如果对象地址一样,则认为相同
if (this == o) return true;
// 如果参数为空,或者类型信息不一样,则认为不同
if (o == null || getClass() != o.getClass()) return false;
//向下转型
Student student = (Student) o;
// 要求基本类型相等,并且将引用类型交给java.util.Objects类的equals静态方法取用结果
return age == student.age && Objects.equals(name, student.name);}

4. Objects类

1
在JDK7添加了一个Objects工具类,它由一些静态的实用方法组成,这些方法是null-save(空指针安全的)或null-tolerant(容忍空指针的),在比较两个对象的时候,Object的equals方法容易抛出空指针异常,而Objects类中的equals方法就优化了这个问题。

public static boolean equals(Object a, Object b) :判断两个对象是否相等

1
2
3
4
5
//源码
public static boolean equals(Object a, Object b) {
//短路与,若a为null,则避免了a.equals(b)的执行
return (a == b) || (a != null && a.equals(b));
}

5. Date类

1
表示特定的瞬间,精确到毫秒。

注意:

  1. 时间原点:1970年1月1日 00:00:00(英国格林威治时间)
  2. 中国属于东八区,会把时间增加8个小时

两个构造方法:

  1. Date():返回当前时间的日期对象
  2. Date(long date):返回一个从1970年1月1日 0点0分0秒 + 毫秒值 所对应的日期对象

一个成员方法:

  1. getTime():将一个日期对象,转为对象的毫秒值表示

6. DateFormat类

1
DateFormat用于将日期对象格式化成指定的字符串表示,或者将一个字符串解析成Date对象。

构造方法:public SimpleDateFormat(String pattern)

格式化:按照指定的格式,从Date对象转换为String对象: public String format(Date date)

解析:按照指定的格式,从String对象转换为Date对象: public Date parse(String source)

格式规则

字母 日期或时间元素
y
M 年中的月份
D 年中的天数
d 月份中的天数
E 星期中的天数
H 一天中的小时数(0-23)
m 小时中的分钟数
s 分钟中的秒数
S 毫秒数

7. Calendar类

1
Calendar 类是一个抽象类,它为特定瞬间与一组诸如 YEAR、MONTH、DAY_OF_MONTH、HOUR等日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。

常用方法:

  1. public static Calendar getInstance(): 通过静态方法创建对象
  2. public int get(int field):返回给定日历字段的值
  3. public abstract void add(int field, int amount) :根据日历的规则,为给定的日历字段添加或减去指定的时间量。
  4. public Date getTime():返回一个表示此Calendar时间值(从历元到现在的毫秒偏移量)的Date对象。
  5. public void set(int field, int value):将给定的日历字段设置为给定值。 (也可以同时设置年月日:set(int year, int month, int day)

Calendar类中提供很多成员常量,代表给定的日历字段:

字段值 含义
YEAR
MONTH 月(月份特殊,西方是0-11表示12个月,可以+1使用)
DAY_OF_MONTH 月中的天(几号)
HOUR 时(12小时制)
HOUR_OF_DAY 时(24小时制)
MINUTE
SECOND
DAY_OF_WEEK 周中的天(西方是从星期日开始每周的第一天,可以-1使用)

8. 日期、字符串、毫秒值和日历的相互转换

1
2
3
4
5
6
7
8
9
             SimpleDateFormat.parse(s)                 Date.getTime(d)    
字符串 ------------------------> 日期 -----------------------> 毫秒值
(String) <------------------------ (Date) <----------------------- (long)
SimpleDateFormat.format(d) /|\ | new Date(l)
| |
getTime() | | setTime(Date date)
| |
| \|/
日历(Calendar)

Date类是另外三种时间格式连接的桥梁,相互之间转换时都需要通过Date类。

9. System类

1
2
public static long currentTimeMillis() :返回以毫秒为单位的当前时间,经常用来测试程序性能。
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) :将源数组中指定的数据拷贝到目标数组中。数组的拷贝动作是系统级的,性能很高。
参数名称 参数类型 参数含义
src Object 源数组
srcPos int 源数组索引起始位置
dest Object 目标数组
destPos int 目标数组索引起始位置
length int 复制元素个数

10. StringBuilder类

1
StringBuilder又称为可变字符序列,它是一个类似于String的字符串缓冲区,支持可变的字符串,可以提高字符串的操作效率。底层也是一个数组,但是没有被final修饰,可以改变长度在数组中加入新内容。

构造方法:

  1. public StringBuilder() :构造一个空的StringBuilder容器。
  2. public StringBuilder(String str):构造一个StringBuilder容器,并将字符串添加进去。

常用方法:

  1. public StringBuilder append(…) :添加任意类型数据的字符串形式,并返回当前对象自身。
  2. public String toString() :将当前StringBuilder对象转换为String对象。

11. 基本类型与字符串之间的转换

基本类型转换为String

  1. 基本类型的值 + “” (推荐方法)
  2. static String toString(基本数据类型):String s = Integer.toString(int i)
  3. static String valueOf(基本数据类型):String s = String.valueOf(int i)

String转换为基本类型

除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型。

例如:Integer类: static int parseInt(String s)

【Collection、泛型】

1. Collection

1
单列集合类的根接口,有两个重要的子接口,分别是java.util.List和java.util.Set

List

特点:元素有序、元素可重复

主要实现类:java.util.ArrayListjava.util.LinkedList

Set

特点:元素无序,而且不可重复

主要实现类:java.util.HashSetjava.util.LinkedHashSet

2. 集合和数组的区别

长度不同

  • 集合:集合的长度是可变的,因为集合的底层就是数组,当增删元素的时候,会进行数组的扩容
  • 数组:数组的长度在运行期间不可变,一旦创建,就固定

存储的数据类型不同

  • 集合:只能存储引用数据类型,如果想存储基本数据类型,需要存储对应的包装类
  • 数组:基本数据类型和引用数据类型都可以存储

3. Collection 常用功能

1
Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合:
  • public boolean add(E e): 把给定的对象添加到当前集合中 。
  • public void clear() :清空集合中所有的元素。
  • public boolean remove(E e): 把给定的对象在当前集合中删除。
  • public boolean contains(E e): 判断当前集合中是否包含给定的对象。
  • public boolean isEmpty(): 判断当前集合是否为空。
  • public int size(): 返回集合中元素的个数。
  • public Object[] toArray(): 把集合中的元素,存储到数组中。

4. Iterator接口

1
【迭代器】Iterator是一种通用的Collection集合元素的获取方式,通过判断集合中是否有元素,有则取出,继续判断,直到把集合中的元素全部取出为止。

获取迭代器的方法:

  • collection.iterator():由于接口不能直接实例化,使用集合的Iterator方法获取

常用的两个方法:

  • public E next():返回迭代的下一个元素。
  • public boolean hasNext():如果仍有元素可以迭代,则返回 true。

并发修改异常:迭代器的底层实现原理,运用到指针。在使用迭代器的过程中,修改了集合的长度,就会抛出该异常。使用Iterator接口的一个子接口ListIterator接口可以解决这个问题。

1
2
3
4
5
6
7
//代码展示:
public static void main(String[] args) {
Collection<String> coll = new ArrayList<String>();
Iterator<String> it = coll.iterator(); //通过集合获取迭代器
while(it.hasNext()){ //判断是否有迭代元素
String s = it.next();//获取迭代出的元素
System.out.println(s);}}

5. ListIterator接口

1
Iterator接口有一个子接口ListIterator接口,其中定义了add方法和remove方法,可以对集合添加\删除元素。由迭代器自己添加\删除的不会抛出异常。

使用步骤:

  1. 创建的集合首先不能使用Collection,因为Collection获取不了ListIterator接口,需要使用List接口获取ListIterator接口实现类
  2. 调用listIterator()方法,获取listIterator接口实现类
  3. 使用hasNext和next方法进行迭代
  4. 使用接口实现类的add方法进行添加,remove方法进行删除元素。注意:不要使用集合进行添加删除
1
2
3
4
5
6
7
8
9
10
//代码展示
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("黑马");
ListIterator<String> lit = list.listIterator();
while (lit.hasNext()) {
String s = lit.next();
System.out.println(s);
if ("黑马".equals(s)) {
lit.add("金马");}}

6. 增强for

1
2
3
4
增强for循环是基于迭代器设计的一种高级for循环,专门用于遍历数组和集合。它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。格式:
for(元素的数据类型 变量 : Collection集合or数组){
//写操作代码
}

7. 泛型

1
在定义类或方法时,对于未知类型的数据进行占位,用于后期接收数据类型,以便预支使用的一种未知的数据类型。

泛型的好处:

  1. 避免了类型转换的麻烦,使用API时更加直观简洁。
  2. 把运行期异常,提升到了编译期
含有泛型的类

定义格式:修饰符 class 类名<代表泛型的变量> {}

确定泛型:在创建对象的时候

含有泛型的方法

定义格式:修饰符 <代表泛型的变量> 返回值类型 方法名(参数){}

确定泛型:调用方法时

含有泛型的接口

定义格式:修饰符 interface 接口名<代表泛型的变量> {}

确定泛型:1、定义类时确定泛型的类型; 2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型

8. 泛型通配符

1
2
不知道使用什么类型来接收的时候,此时泛型可以使用“?”表示,“?”表示未知通配符。
但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。

受限泛型:泛型没有继承概念,JAVA的泛型中可以指定一个泛型的上限和下限。

泛型的上限

  • 格式: 类型名称 <? extends 类 > 对象名称
  • 意义: 只能接收该类型及其子类

泛型的下限

  • 格式: 类型名称 <? super 类 > 对象名称
  • 意义: 只能接收该类型及其父类型

【List、Set、数据结构、Collections】

1. 数据结构

1
stack,又称堆栈,它是运算受限的线性表,其限制是仅允许在标的一端进行插入和删除操作

特点:先进后出

  • 压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
  • 弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。
队列
1
也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,而在表的另一端进行删除。

特点:先进先出

数组
1
数组是有序的元素序列,在内存中开辟一段连续的空间,并在此空间存放元素

特点:有索引值,查询快,增删慢

链表
1
链表中的每一个元素也称之为一个节点,一个节点包含了一个数据源,两个指针域(存储地址):一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

特点:查询慢,增删快

  • 单项链表:链表中只有一条链,不能保证元素的顺序(存储元素和取出元素的顺序可能不一致)
  • 双向链表:链表中有两条链子,有一条链子是专门记录元素的顺序,是一个有序的集合
红黑树
1
是一种比较平衡的二叉树

特点:速度特别快,趋近平衡树,查询叶子节点最大次数和最小次数不能超过2倍

约束:

  1. 节点可以是红色的或者黑色的
  2. 根节点是黑色的
  3. 叶子节点(特指空节点)是黑色的
  4. 每个红色节点的子节点都是黑色的
  5. 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同

2. List 接口

特点:

  1. 有序的集合,存储元素和取出元素的顺序是一致的(存储123 取出123)
  2. 有索引,包含了一些带索引的方法
  3. 允许存储重复的元素
1
2
3
4
5
常用方法:
- public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
- public E get(int index):返回集合中指定位置的元素。
- public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。
- public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素。

3. LinkedList 集合

1
LinkedList是一个双向链表,查询慢,增删快,包含了大量操作首尾元素的方法。
1
2
3
4
5
6
7
8
9
- public void addFirst(E e):将指定元素插入此列表的开头。
- public void addLast(E e):将指定元素添加到此列表的结尾。
- public E getFirst():返回此列表的第一个元素。
- public E getLast():返回此列表的最后一个元素。
- public E removeFirst():移除并返回此列表的第一个元素。
- public E removeLast():移除并返回此列表的最后一个元素。
- public E pop():从此列表所表示的堆栈处弹出一个元素。
- public void push(E e):将元素推入此列表所表示的堆栈。
- public boolean isEmpty():如果列表不包含元素,则返回true

4. HashSet 集合

1
是Set接口的一个实现类,存储的元素不可重复,并且元素都是无序的(即存取顺序不一致),没有索引,不能使用普通的for循环遍历。HashSet根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。

HashSet集合存储数据的结构(哈希表)

  • jdk1.8版本之前:哈希表 = 数组 + 链表
  • jdk1.8版本之后:哈希表 = 数组 +链表/红黑树;

(当链表长度超过阈值(8)时,将链表转换为红黑树提高查询的速度)

5. LinkedHashSet 集合

1
HashSet是无序的,LinkedHashSet是有序的

特点:底层是一个哈希表(数组+链表/红黑树)+ 链表:多了一条链表(记录元素的存储顺序),保证元素有序

6. 重写HashCode()方法

为什么需要重写HashCode()方法?

如果两个对象需要判断是否内容相同,可以调用equals方法进行比较,但如果一个对象的字段过多,那就会偏频繁的进行字段的比较,非常的耗费性能。我们可以对Object类继承过来的hashCode方法进行覆盖重写,不让他生成地址值,而是根据我们对象的内容,生成hash值进行比较。因为比较hash值比equals方法容易得多。如果hash值相同,再调用equals方法进行内容比较。

哈希值:是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,是模拟出来得到地址,不是数据实际存储的物理地址)

重写HashCode()进行比较的步骤:

  1. 重写Object类中继承过来的HashCode()方法,自定义,根据对象的内容生成的哈希值
  2. 我们在进行集合元素存储的时候,比如向HashSet集合添加元素的时候,会先调用HashCode()方法,生成哈希值,不同对象生成的哈希值可能相同(虽然概率比较低)
  3. 哈希值不同,对象的内容肯定不同;哈希值相同,对象的内容有可能相同,有可能不同
  4. 比较哈希值,如果不同,直接存;如果相同,再调用equals方法进行内容比较

7. 可变参数

1
2
当方法的参数列表数据类型已经确定,但是参数的个数不确定,就可以使用可变参数
格式:修饰符 返回值类型 方法名(参数类型... 形参名){ }

原理:可变参数底层就是一个数组,根据传递参数个数不同,会创建不同长度的数组,来存储这些参数,传递的参数个数,可以是0个(不传递),1,2,…多个

注意事项:

  1. 一个方法的参数列表,只能有一个可变参数
  2. 如果方法的参数有多个,那么可变参数必须写在参数列表的末尾
  3. 可变参数的终极写法:Object…obj

8. Collections 工具类

1
2
3
4
5
6
7
public static <T> boolean addAll(Collection<? super T> c, T... elements)
往集合中添加一些元素
public static void shuffle(List<?> list):打乱集合顺序
public static void sort(List<T> list)(<T extends Comparable<? super T>>):
根据元素的自然顺序对指定列表按升序进行排序
public static void sort(List<T> list, Comparator<? super T> c)
根据指定比较器产生的顺序对指定列表进行排序

9. Comparable/Comparator 接口

Comparable和Comparator的区别:

  • Comparable:自己(this)和别人(参数)比较,在源代码类中需要实现Comparable接口,重写比较的规则compareTo方法,耦合度太高
  • Comparator:在需要做排序的时候去选择的Comparator,相当于找一个第三方的裁判
  • 排序规则:this - 参数:升序;参数 - this:降序
1
2
3
4
5
6
7
8
//Comparable使用示例,在源码类中实现Comparable接口
public class People implements Comparable<People> {
String name;
int age;
@Override
public int compareTo(People o) {
return this.age - o.age;
}}
1
2
3
4
5
6
7
//Comparator使用示例,在Collections.sort方法中实现Comparator接口的匿名内部类
Collections.sort(list, new Comparator<People>() {
@Override
public int compare(People o1, People o2) {
return o1.age - o2.age;
}
});
1
2
3
4
5
6
7
8
9
10
11
//Comparator如果想实现更多规则,示例如下:
Collections.sort(list, new Comparator<People>() {
@Override
public int compare(People o1, People o2) {
int result = o1.age - o2.age;
if (o1.age - o2.age == 0) {
result = o1.name.charAt(0) - o2.name.charAt(0);
}
return result;
}
});

【Map、Debug】

1. Map接口

1
Collection 中的集合称为单列集合, Map 中的集合称为双列集合。

特点:

  1. 是一个双列集合,一个元素包含两个值(一个key,一个value)
  2. Map集合中的元素,key和value的数据类型可以相同,也可以不同
  3. Map集合中的元素,key是不允许重复的,value是可以重复的
  4. Map集合中的元素,key和value是一一对应的

2. Map的常用子类

HashMap:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需
要重写键的hashCode()方法、equals()方法。

LinkedHashMap:存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。

HashTable:底层也是一个哈希表,是一个线程安全的集合,单线程集合,速度慢。不能存储空值,空键。Hashtable和Vector集合一样,在jdk1.2版本之后被更先进的集合取代了。

3. Map的常用方法

1
2
3
4
5
6
7
public V put(K key, V value) : 把指定的键与指定的值添加到Map集合中。
//返回值V的说明:存储键值对的时候,key不重复,返回值V是null;key重复,会使用新的value替换map中重复的value,返回被替换的value值
public V remove(Object key) : 把指定的键所对应的键值对元素在Map集合中删除,返回被删除元素的值。如果不存在指定的键,则返回空。
public V get(Object key) 根据指定的键,在Map集合中获取对应的值。如果不存在指定的键,则返回空。
public boolean containsKey(Object key): 集合中是否包含指定的键
public Set<K> keySet() : 获取Map集合中所有的键,存储到Set集合中。
public Set<Map.Entry<K,V>> entrySet() : 获取到Map集合中所有的键值对对象的集合(Set集合)

4. Entry键值对对象

1
一对key(键)+value(值)称做Map中的一个Entry(项),Entry将键值对的对应关系封装成了对象,即键值对对象。
1
2
3
4
5
//Entry相关方法
public K getKey() :获取Entry对象中的键。
public V getValue() :获取Entry对象中的值。
在Map集合中也提供了获取所有Entry对象的方法:
public Set<Map.Entry<K,V>> entrySet() : 获取到Map集合中所有的键值对对象的集合(Set集合)

5. Map的两种遍历方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "thr");
//keySet遍历方式
for (Integer key : map.keySet()) {
System.out.println(key + " = " + map.get(key));
}
//entrySet遍历方式(更快)
Set<Map.Entry<Integer, String>> entries = map.entrySet();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println(entry.getKey + "=" + entry.getValue());
}

6. JDK9对集合添加的优化

1
Java 9给List接口,Set接口和Map接口增加了一个静态的方法of,可以给集合一次性添加多个元素。
1
2
3
4
5
6
7
8
9
10
public class HelloJDK9 {
public static void main(String[] args) {
Set<String> str1=Set.of("a","b","c");
//str1.add("c");这里编译的时候不会错,但是执行的时候会报错,因为是不可变的集合
System.out.println(str1);
Map<String,Integer> str2=Map.of("a",1,"b",2);
System.out.println(str2);
List<String> str3=List.of("a","b");
System.out.println(str3);
}}

注意:

  1. of()方法只是Map,List,Set这三个接口的静态方法,其父类接口和子类实现并没有这类方法,比如
    HashSet,ArrayList等;
  2. of方法的返回值时一个不能改变的集合,集合不能再使用add,put方法添加元素,会抛出异常;
  3. Set接口和Map接口在调用of方法的时候,不能有重复的元素,否则会抛出异常

7. Debug追踪

1
使用IDEA的断点调试功能,可以让代码逐行执行,查看代码执行的过程,调试程序中出现的bug。

使用方式:

  1. 在行号的右边,鼠标左键单击,添加断点(哪里有bug添加到哪里)
  2. 右键选择Debug执行程序
  3. 程序就会停留在添加的第一个断点处

执行程序:

  • F7:逐句执行程序(进入到方法中)
  • F8:逐行执行程序(不进入方法中)
  • Shift + F8:跳出方法
  • F9:跳到下一个断点,如果没有下一个断点,那么就结束程序
  • Ctrl + F2:退出Debug模式,停止程序

【异常、线程】

1. 异常

1
2
在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java处
理异常的方式是中断处理。

异常指的并不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行。

2. Throwable体系

  • Error:严重错误,无法通过处理的错误,只能事先避免,好比绝症。(内存不够用或和系统相关)
  • Exception:异常,产生后程序员可以通过代码的方式纠正,使程序继续运行,好比感冒。
1
2
3
4
//Throwable常用方法:
String getMessage():返回此throwable的简短描述,一般用于提示给用户
String toString():返回此throwable的详细消息字符串
void printStackTrace():JVM打印异常对象,默认此方法,信息是最全面的,用于开发和调试阶段

3. 异常的分类

  • 编译时期异常:必须要处理。在编译时期就会检查,如果没有处理异常,则编译失败。
  • 运行时期异常:可以不处理。在运行时期检查异常,在编译时期,运行异常不会被编译器报错。

4. 异常产生过程解析

1
2
3
4
5
6
7
8
9
10
// 定义一个对给定的数组通过给定的索引获取元素的方法
public static int getElement(int[] arr, int index) {
int element = arr[index];
return element;
}
// 在主方法中调用getElement方法
public static void main(String[] args) {
int[] arr = {1,2,3};
intnum = ArrayTools.getElement(arr, 4)
}

过程解析:getElement方法由于没有找到4索引,导致运行发生了异常,接下来JVM会:

  1. Jvm会根据异常产生的原因创建一个异常对象,这个对象包含了异常产生的(内容,原因,位置) new ArrayIndexOutOfBoundsException(“4”);
  2. 在getElement方法中,没有异常的处理逻辑(try…catch),那么JVM就会把异常对象抛出给方法的调用者main方法来处理这个异常
  3. main方法接受后也没有处理异常的逻辑,继续把对象抛给main方法的调用者JVM处理
  4. JVM收到这个异常对象,做了两件事:
    • 把异常对象(内容、原因、位置)以红色的字体打印在控制台
    • JVM会终止当前正在执行的Java程序—>中断处理

5. throw关键字

1
2
3
throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行。例如:
throw new NullPointerException("要访问的arr数组不存在");
throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围");

throw将异常抛出,也就是将问题返回给该方法的调用者。对于调用者来说,该怎么处理呢?一种是进行捕获处理,另一种就是继续将问题声明出去,使用throws声明处理。

注意:

  1. throw关键字必须写在方法的内部
  2. throw关键字后边new的对象必须是Exception或者Exception的子类对象
  3. throw关键字抛出RuntimeException或者是其子类对象,我们可以不处理,默认交给JVM处理
  4. throw关键字后边创建的是编译异常,我们就必须处理这个异常(try…catch)

6. Objects非空判断

1
2
3
4
5
public static <T> T requireNonNull(T obj) :查看指定引用对象不是null
//源码:
if (obj == null)
throw new NullPointerException()  ;
return obj;

7. throws声明异常

1
2
声明异常:如果方法内通过throw抛出编译时异常,而没有捕获处理,那么必须通过throws进行声明,将问题标识出来。
抛出异常:关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常。
1
2
3
4
5
6
7
8
9
10
11
12
//代码演示:
public static void main(String[] args) throws IOException {
read("a.txt");
}
public static void read(String path)throws FileNotFoundException, IOException {
if (!path.equals("a.txt")) {
throw new FileNotFoundException("文件不存在");
}
if (!path.equals("b.txt")) {
throw new IOException();
}
}

子父类的异常:子类抛出的异常在数量和继承关系上不能超出父类

8. try…catch捕获异常

1
2
3
4
5
6
7
捕获异常:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。
try{
编写可能会出现异常的代码
}catch(异常类型 e){
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//代码演示
public static void main(String[] args) {
try {
read("b.txt");
} catch (FileNotFoundException e) {
System.out.println(e);
}
System.out.println("over");
}
public static void read(String path) throws FileNotFoundException {
if (!path.equals("a.txt")) {
throw new FileNotFoundException("文件不存在");
}
}

9. finally代码块

1
因为异常会引发程序跳转,导致有些语句执行不到。但有一些特定的代码必须执行,将这些代码放在finally代码块中是一定会被执行的。
1
2
3
4
5
6
7
8
9
10
11
//代码演示
public static void main(String[] args) {
try {
read("a.txt");
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} finally {
System.out.println("不管程序怎样,这里都将会被执行。");
}
System.out.println("over");
}

注意:

  • finally不能单独使用。
  • 如果finally有return语句,永远返回finally中的结果,避免该情况。
  • 当只有在try或者catch中调用退出JVM的相关方法,此时finally才不会执行,否则finally永远会执行。

10. 多个异常使用捕获

1
2
3
4
5
6
7
8
9
10
//一般多个异常一次捕获,多次处理。
try{
编写可能会出现异常的代码
}catch(异常类型A e){ 当try中出现A类型异常,就用该catch来捕获.
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}catch(异常类型B e){ 当try中出现B类型异常,就用该catch来捕获.
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}

注意:这种异常处理方式,要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异
常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。

11. 自定义异常

1
2
3
4
5
6
7
8
9
10
//格式:
public class XXXException extends Exception/RuntimeException{
//空参数的构造方法
public XXXException() {
}
//添加一个带异常信息的构造方法
public XXXException(String message) {
super(message);
}
}

注意:

  1. 自定义异常类一般都是以Exception结尾,说明该类是一个异常类
  2. 所有的异常类都会有一个带异常信息的构造方法,方法内部会调用父类带异常信息的构造方法
  3. 自定义异常类,必须继承Exception/RuntimeException
    • 继承RuntimeException:那么就是一个运行期异常,无需处理,交给虚拟机
    • 继承Exception:那么就是一个编译期异常,如果方法内部抛出了编译期异常,就必须处理这个异常,要么throws,要么try…catch

12. 并发与并行

1
2
并发:指两个或多个事件在同一个时间段内发生(交替执行)。
并行:指两个或多个事件在同一时刻发生(同时执行)。

13. 线程与进程

1
2
3
进程:一个应用程序可以同时运行多个进程,每个进程都有一个独立的内存空间,是系统运行程序的基本单位。
线程:是CPU和任务之间的执行通道,是任务的执行单元,一个进程中可以有多个线程。
一个应用程序可以有多个进程,一个进程可以有多个线程。

线程调度:

  • 分时调度:
    所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
  • 抢占式调度:
    优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

【线程、同步】

1. Thread类

1
2
3
4
5
//构造方法:
public Thread() :分配一个新的线程对象。
public Thread(String name) :分配一个指定名字的新的线程对象。
public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
1
2
3
4
5
6
//常用方法:
public String getName() :获取当前线程名称。
public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
public void run() :此线程要执行的任务在此处定义代码。
public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
public static Thread currentThread() :返回对当前正在执行的线程对象的引用。

2. 创建线程-继承Thread类

1
创建多线程的第一种方式:创建Thread类的子类

实现步骤:

  1. 创建一个Thread类的子类,并重写Thread类中的run方法,设置线程任务
  2. 创建Thread类的子类对象
  3. 调用线程对象的start()方法,开启新的线程,执行run方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//代码演示
//创建Thread类的子类
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在执行!"+i);
}
}
//测试类
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}

3. 创建线程-Runnable接口

1
创建多线程的第二种方式:Thread构造器实现Runnable接口

实现步骤:

  1. 创建一个Runnable接口的实现类
  2. 在实现类中重写Runnable接口的run方法,设置线程任务
  3. 创建一个Runnable接口的实现类对象
  4. 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
  5. 调用Thread类中的start方法,开启新的线程执行run方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//代码演示
//创建Runnable接口的实现类
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
//测试类
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
t.start();
}//也可以使用匿名内部类方式实现线程的创建

4. Runnable接口具有的优势

  1. 适合多个相同的程序代码的线程去共享同一个资源。
  2. 可以避免java中的单继承的局限性。
  3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
  4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。

5. 线程安全

1
当多个线程对象访问同一个资源,并且多个线程中对资源有写的操作,就容易产生线程安全问题。

6. 同步代码块

1
synchronized 关键字可以用于方法中的某个区块中,表示对这个区块的资源实行互斥访问。
1
2
3
4
5
6
7
8
9
10
11
12
//代码演示
public class Ticket implements Runnable{
private int ticket = 100;
Object lock = new Object();
@Override
public void run() {
while(true){
synchronized (lock) { //对卖票的操作进行锁
if(ticket>0){
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket‐‐);
}

同步锁:对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁

  1. 锁对象可以是任意类型。
  2. 多个线程对象要使用同一把锁。
  3. 在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着。

7. 同步方法

1
使用synchronized修饰的方法,就叫做同步方法,保证一个线程执行该方法的时候,其他线程只能在方法外等着。
1
2
3
4
5
6
7
8
9
10
11
12
13
//代码演示
public class Ticket implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true){
sellTicket();
}
}
public synchronized void sellTicket(){
if(ticket>0){
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket‐‐);

同步锁是谁?

对于非static方法,同步锁就是this

对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。

8. Lock锁

1
java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外Lock更强大,更体现面向对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
//代码演示
public class Ticket implements Runnable{
private int ticket = 100;
Lock lock = new ReentrantLock(); //1.创建锁对象
@Override
public void run() {
while(true){
lock.lock(); //2.加同步锁
if(ticket>0){
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket‐‐);
}
lock.unlock(); //3.释放同步锁

9. 六种线程状态

线程状态 说明
新建状态(New) 线程刚被创建,但是还没调用start方法启动。
运行状态(Runnable) 线程可以在java虚拟机中运行的状态。
阻塞状态(Blockd) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态。
计时等待状态(Timed waiting) 一个线程在等待另一个线程调用notify或者notifyAll方法(唤醒)动作时,该线程进入Waiting状态,进入这个状态后是不能自动唤醒的。
无限等待状态(Waiting) 同waiting状态,这一状态将一直保持到超时期满或者接收到唤醒通知。
死亡状态(Terminated) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

10. 等待与唤醒方法

1
2
3
4
5
6
7
8
进入到TimeWaiting(计时等待)有两种方式:
1. sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态。sleep方法使用后线程失
去CPU执行权,但仍然拥有锁对象。
2. wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到
Runnable/Blocked状态。wait方法同时释放锁对象和cpu执行权。
唤醒的两种方式:
1. void notify() 唤醒在此对象监视器上等待的单个线程。
2. void notifyAll() 唤醒在此对象监视器上等待的所有线程。

wait和notify方法是来源于Object类,不是Thread类,等待和唤醒都是Object的方法

【线程池、Lambda表达式】

1. 等待唤醒机制

1
一个线程进行了规定操作后,通过wait()方法进入等待状态,等待其他线程执行完他们的指定代码过后再通过notify()方法将其唤醒,是线程间的一种协作机制。

注意:

  1. wait方法与notify方法必须要由同一个锁对象调用。
  2. wait方法与notify方法必须要在同步代码块或者是同步函数中使用,因为要通过锁对象调用这2个方法。
  3. 被唤醒的线程不一定能立即恢复执行,需要再次获取锁后才能在从 wait() 方法之后的地方恢复执行。

2. 线程池

1
容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

线程池的好处:

  1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

线程池使用步骤:

  1. 用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
  2. 创建一个类,实现Runnable接口,重写run方法,设置线程任务
  3. 调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
  4. 调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
1
2
3
4
5
6
//代码展示
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(3);
es.submit(() -> System.out.println(Thread.currentThread().getName()));
es.shutdown();
}

3. Lambda表达式

1
Lambda表达式的标准格式为:(参数类型 参数名称) ‐> { 代码语句 }

使用前提:

  1. 必须有函数式接口——只包含一个抽象方法的接口
  2. 必须有上下文引用(必须有接口作为数据类型接收)

省略格式(可推导,可省略):

  1. 小括号内参数的类型可以省略;
  2. 如果小括号内有且仅有一个参,则小括号可以省略;
  3. 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
1
2
3
4
5
6
7
//代码演示
public static void main(String[] args) {
//重写Runnable接口
new Thread(() -> System.out.println("创建并执行线程")).start();
//重写Comparator接口,降序排序
Collections.sort(list,(o1, o2) -> o2 - o1);
}

【File类、递归】

1. File类

1
2
File类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。
File类的实例是不可变的,一旦创建,File对象表示的抽象路径名将永不改变
构造方法
1
2
3
public File(String pathname) :通过将给定的路径名字符串创建新的 File实例。
public File(String parent, String child) :父路径名字符串+子路径名字符串创建新的File实例。
public File(File parent, String child) :从父File实例路径名+子路径名字符串创建新的File实例。
获取的方法
1
2
3
4
5
public String getAbsolutePath() :返回此File的绝对路径名字符串。
//File对象的toString方法调用的就是getPath()方法
public String getPath() :将此File转换为路径名字符串。
public String getName() :返回由此File表示的文件或目录的名称。
public long length() :返回由此File表示的文件的长度。
判断的方法
1
2
3
public boolean exists() :此File表示的文件或目录是否实际存在。
public boolean isDirectory() :此File表示的是否为目录。
public boolean isFile() :此File表示的是否为文件。
创建删除的方法
1
2
3
4
public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。
public boolean delete() :删除由此File表示的文件或目录,目录项下有内容的话无法删除。
public boolean mkdir() :创建由此File表示的目录。
public boolean mkdirs() :创建由此File表示的目录,包括任何必需但不存在的父目录。
遍历的方法
1
2
public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。
public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。

调用listFiles方法的File对象,表示的必须是实际存在的目录,否则返回null,无法进行遍历。

2. 绝对路径和相对路径

1
2
绝对路径:从盘符开始的路径,这是一个完整的路径。
相对路径:相对于项目目录的路径,这是一个便捷的路径,开发中经常使用。

File.pathSeparator:获取当前系统路径分隔符;(Windows用” ; “ Linux用” : “)
File.separator:获取当前系统文件名称分隔符;(Windows用”反斜杠\“ Linux用”正斜杠/“)

3. 递归

1
递归:指在当前方法内调用自己的这种现象。

注意事项:

  1. 递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。(StackOverflowError)
  2. 在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。(StackOverflowError)
  3. 构造方法,禁止递归。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//代码演示 #暴力删除#
public static void deleteAll(File f) {
if (f.exists()) {
if (f.isDirectory()) {
for (File file : f.listFiles()) {
if (file.isFile()) {
file.delete();
} else {
deleteAll(file);
}}}
f.delete();
}else{
System.out.println("该路径不是有效路径");
}}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//代码演示 #阶乘#
public static double factorial(int n) {
if (n == 1) {
return 1;
}
return n * factorial(n - 1);
}
//代码演示 #内存容量测试#
public static void main(String[] args) {
b(1);
}
public static void b(int i){
System.out.println(i);
b(++i);
}

4. 文件过滤器

1
2
3
4
5
6
7
public File[] listFiles():返回指定目录中的子目录和文件。
public File[] listFiles(FileFilter filter):返回指定目录中符合自定义规则的子目录和文件。
FileFilter接口中抽象方法:
boolean accept(File pathname) 测试指定格式路径名是否包含在某个路径名列表中。
public File[] listFiles(FilenameFilter filter):返回指定目录中指定文件。
FilenameFilter接口中抽象方法:
boolean accept(File dir, String name) 测试指定文件是否应该包含在某一文件列表中。
1
2
3
4
5
6
7
8
9
10
//定义一个方法,输出文件夹里的所有的java文件
public static void showJava(File f) {
File[] files = f.listFiles(pathname->pathname.isDirectory()||
pathname.getName().toLowerCase().endsWith(".java"));
for (File file : files) {
if (file.isDirectory()) {
showJava(file);
}else{
System.out.println(file.getAbsolutePath());
}}}

【字节流、字符流】

1. IO流

1
2
输入流 :把数据从其他设备上读取到【内存中】的流。
输出流 :把数据从【内存中】写出到其他设备上的流。
顶级父类们 输入流 输出流
字节流 字节输入流【InputStream】 字节输出流【OutputStream】
字符流 字符输入流【Reader】 字符输出流【Writer】

2. 字节输出流【OutputStream】

1
java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。
1
2
3
4
5
//OutputStream 基本方法:
public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流。
public void write(byte[] b, int off, int len) :从指定的字节数组写入len字节,从偏移量off开始输出到此输出流。
FileOutputStream
1
2
3
//构造方法:true 表示追加数据, false 表示清空原有数据
public FileOutputStream(String name, boolean append) : 创建文件输出流以指定的名称写入文件。
public FileOutputStream(File file, boolean append) :创建文件输出流以写入由指定的File对象表示的文件。

3. 字节输入流【InputStream】

1
java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。
1
2
3
4
//InputStream 基本方法:
public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
public abstract int read() : 从输入流读取数据的下一个字节。
public int read(byte[] b) : 从输入流中读取一些字节数,并将它们存储到字节数组b中,返回读取的字节数。
FileInputStream
1
2
3
//构造方法
FileInputStream (File file):通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的File对象file命名。
FileInputStream (String name):通过打开与实际文件的连接来创建一个 FileInputStream,该文件由文件系统中的路径名name命名。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//文件复制代码演示
public static void main(String[] args) throws IOException {
//创建输入输出流对象
FileInputStream fis = new FileInputStream("src.jpg");
FileOutputStream fos = new FileOutputStream("des.jpg");
//创建容器
byte[] bys = new byte[1024];
int len;
//读取源文件
while ((len = fis.read(bys)) != -1) {
//输出文件
fos.write(bys, 0, len);
}
//释放资源
fos.close();
fis.close();

4. 字符输入流【Reader】

1
java.io.Reader抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。
1
2
3
4
//Reader 基本方法:
public void close() :关闭此流并释放与此流相关联的任何系统资源。
public int read() : 从输入流读取一个字符。
public int read(char[] cbuf) : 从输入流中读取一些字符,并将它们存储到字符数组cbuf中 。
FileReader
1
2
3
//构造方法
FileReader(File file):创建一个新的 FileReader,给定要读取的File对象。
FileReader(String fileName):创建一个新的 FileReader,给定要读取的文件的名称。
1
2
3
4
5
6
7
8
9
//代码演示
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("a.txt");
char[] chars = new char[1024];
int len;
while ((len = fr.read(chars)) != -1) {
System.out.println(new String(chars,0,len));
}
}

5. 字符输出流【Writer】

1
java.io.Writer 抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。
1
2
3
4
5
6
7
8
//Writer 基本方法
void flush() 刷新该流的缓冲。
void close() 关闭此流,但会自动先刷新它。
void write(int c) 写入单个字符。
void write(char[] cbuf) 写入字符数组。
void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len字符个数。
void write(String str) 写入字符串。
void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len字符个数。
FileWriter
1
2
3
//构造方法
FileWriter(File file):创建一个新的 FileWriter,给定要读取的File对象。
FileWriter(String fileName):创建一个新的 FileWriter,给定要读取的文件的名称。

注意:

使用FileWriter中的方法write,是把数据写入到内存缓冲区中,需要刷新缓冲区,才能将数据保存到文件中。

  • flush :刷新缓冲区,流对象可以继续使用。
  • close :先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。

6. IO异常的处理

JDK7前处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//代码演示
public static void main(String[] args) {
FileWriter fw = null;
try {
fw = new FileWriter("fw.txt");
fw.write("黑马程序员");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();}}}
JDK7的处理
1
2
3
4
5
6
//JDK7优化后用()包裹流对象语句,确保每个流对象在结束使用后关闭
public static void main(String[] args) {
try ( FileWriter fw = new FileWriter("fw.txt"); ) {
fw.write("黑马程序员");
} catch (IOException e) {
e.printStackTrace();}}
JDK9的改进
1
2
3
4
5
6
7
//JDK9中使用引入对象的方式,同样可以确保每个流对象在结束使用后自动关闭
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("fw.txt");
try (fw) {
fw.write("黑马程序员");
} catch (IOException e) {
e.printStackTrace();}}

7. Properties类

1
2
java.util.Properties继承于Hashtable,使用键值结构存储数据,每个键及其对应值都是一个字符串。
可以方便地在双列集合和数据文件之间转换。
1
2
3
4
5
6
7
8
9
//构造方法
public Properties() :创建一个空的属性列表。
//存储方法
public Object setProperty(String key, String value):添加一个键值对。
public String getProperty(String key):通过键获取对应的值。
public Set<String> stringPropertyNames():获取所有键名称的集合。
//流相关方法
public void load(InputStream in):从字节输入流中读取键值对,生成Properties集合。
public void store(OutputStream out,String comments):从Properties集合中获取键值对,输出到字节输出流的文件中。
1
2
3
4
5
6
7
8
9
10
//代码演示
public static void main(String[] args) throws FileNotFoundException {
Properties pro = new Properties(); //创建Properties对象
pro.load(new FileInputStream("read.txt")); //从read.txt中读取数据
pro.setProperty("one", "111"); //往对象中添加数据
Set<String> strings = pro.stringPropertyNames(); //获取对象所有键名称的集合
for (String key : strings ) { //遍历集合并打印
System.out.println(key+" ‐‐ "+pro.getProperty(key));
}
pro.store(new FileOutputStream("write.txt"),"Saving"); //将对象的信息输出到文件中

【缓冲流、转换流、序列化流】

1. 缓冲流

字节缓冲流
1
2
public BufferedInputStream(InputStream in) :创建一个 新的缓冲输入流。
public BufferedOutputStream(OutputStream out) : 创建一个新的缓冲输出流。
字符缓冲流
1
2
3
4
5
public BufferedReader(Reader in) :创建一个 新的缓冲输入流。
public BufferedWriter(Writer out) : 创建一个新的缓冲输出流。
//特有方法
BufferedReader: public String readLine() : 读一行文字。
BufferedWriter: public void newLine() : 写一行行分隔符,由系统属性定义符号。
1
2
3
4
5
6
7
8
9
10
11
12
13
//通过缓冲流复制文件,代码演示:
public static void main(String[] args) throws IOException{
BufferedOutputStream bos =
new BufferedOutputStream(new FileOutputStream("des.jpg"));
BufferedInputStream bis =
new BufferedInputStream(new FileInputStream("src.jpg"));
byte[] bys = new byte[1024];
int len;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
bis.close(); }

2. 字符集

1
也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。
  • ASCII字符集 :基本的ASCII字符集,使用7位(bits)表示一个字符,共128字符。ASCII的扩展字符集使用8位(bits)表示一个字符,共256字符,方便支持欧洲常用字符。
  • GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了
    21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。
  • Unicode字符集 :为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国
    码,其中最为常用的是UTF-8编码:
    1. 128个US-ASCII字符,只需一个字节编码。
    2. 拉丁文等字符,需要二个字节编码。
    3. 大部分常用字(含中文),使用三个字节编码。
    4. 其他极少使用的Unicode辅助字符,使用四字节编码。

3. 转换流

InputStreamReader类
1
2
3
4
5
转换流java.io.InputStreamReader ,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定
的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
//构造方法
InputStreamReader(InputStream in) : 创建一个使用默认字符集的字符流。
InputStreamReader(InputStream in, String charsetName) : 创建一个指定字符集的字符流。
OutputStreamWriter类
1
2
3
4
5
转换流java.io.OutputStreamWriter ,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符
编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
//构造方法
OutputStreamWriter(OutputStream in) : 创建一个使用默认字符集的字符流。
OutputStreamWriter(OutputStream in, String charsetName) : 创建一个指定字符集的字符流。
1
2
3
4
5
6
7
8
9
10
11
12
13
//将GBK编码的文件转换为UTF-8编码,代码演示:
public static void main(String[] args) throws IOException{
OutputStreamWriter osw =
new OutputStreamWriter(new FileOutputStream("U8code.txt"));
InputStreamReader isr =
new InputStreamReader(new FileInputStream("GBKcode.txt"), "GBK");
int len;
char[] chars = new char[1024];
while ((len = isr.read(chars)) != -1) {
osw.write(chars,0,len);
}
osw.close();
isr.close();}

4. 序列化

1
Java提供了一种对象序列化的机制,使【内存中对象的数据】与【硬盘中文件里的数据】可以相互转化。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。
ObjectOutputStream类
1
2
3
4
5
java.io.ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。
//构造方法
public ObjectOutputStream(OutputStream out):创建一个指定OutputStream的序列化流对象。
//写出方法
public final void writeObject (Object obj) : 将指定的对象写出。

序列化操作注意事项:

  1. 要实现序列化的类必须实现java.io.Serializable接口,否则会抛出NotSerializableException异常。
  2. 被瞬态transient或静态static修饰的属性不会被序列化。
ObjectInputStream类
1
2
3
4
5
ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。
//构造方法
public ObjectInputStream(InputStream in) : 创建一个指定InputStream的ObjectInputStream。
//读取方法
public final Object readObject () : 读取一个对象。

serialVersionUID序列版本号:Serializable 接口给需要序列化的类,提供了一个序列版本号,目的在于验证序列化的对象和对应类是否版本匹配。如果对类进行和修改,那么其序列标本号也会发生修改。可通过在类中写死序列版本号,使修改后的类依然能够匹配序列化的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//序列化代码演示
//定义学生类
public class Student implements Serializable{
private String name;
private int age;
private static final long serialVersionUID = 8206478034432139083L;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//测试类
public static void main(String[] args) throws IOException, ClassNotFoundException {
//序列化
ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("Student.txt"));
oos.writeObject(new Student("Eric", 20));
oos.close();
//反序列化
ObjectInputStream ois =
new ObjectInputStream(new FileInputStream("Student.txt"));
Student stu = (Student)ois.readObject();
System.out.println(stu); //Student{name='Eric', age=20}
ois.close();}

5. 打印流

1
java.io.PrintStream 类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。
PrintStream类
1
2
3
4
5
6
7
//构造方法
public PrintStream(String fileName) : 使用指定的文件名创建一个新的打印流。
//改变打印流向
public static void main(String[] args) throws IOException {
PrintStream ps = new PrintStream("ps.txt");
System.setOut(ps);
System.out.println("Hello,World");} //在ps.txt中输出Hello,World

【网络编程】

1. 软件结构

  • C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、迅雷等软件。
  • B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有谷歌、火狐等。

2. 网络通信协议

1
传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol),是Internet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包含一系列的用于处理数据通信的协议,并采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。
分层 协议
应用层 HTTP/FTP/TFTP/SMTP/SNMP/DNS
传输层 TCP/UDP
网络层 ICMP/IGMP/IP/ARP/RARP
数据链路层+物理层 由底层网络定义的协议

3. TCP协议

1
2
TCP:传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,
在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。

三次握手建立连接:在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。

  1. 客户端向服务器端发出连接请求,等待服务器确认。
  2. 服务器端向客户端回送一个响应,通知客户端收到了连接请求。
  3. 客户端再次向服务器端发送确认信息,确认连接。

四次挥手断开连接:客户端与服务器之间的四次交互后断开,保证了数据的完整性。

  1. 客户端先向服务器发送断开请求,问询服务器是否可以断开(说明客户端没有数据要传输了)
  2. 服务器向客户端发送数据,需要客户端确认(说明服务器没有数据要传输了)
  3. 客户端再次问询服务器是否可以断开连接
  4. 断开连接

4. UDP协议

1
UDP:用户数据报协议(User Datagram Protocol)。UDP协议是一个面向无连接的协议。传输数据时,不需要建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。每个数据包的大小限制在64k以内。它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。日常应用中,例如视频会议、QQ聊天等。

5. IP地址

1
2
IP地址:指互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给一个网络中的计算机设
备做唯一的编号。假如我们把“个人电脑”比作“一台电话”的话,那么“IP地址”就相当于“电话号码”。

IP地址分类:

  • IPv4:是一个32位的二进制数,通常被分为4个字节,表示成a.b.c.d 的形式,例如192.168.65.100,最多可以表示42亿个。有资料显示,全球IPv4地址在2011年2月分配完毕。
  • IPv6:采用128位地址长度,每16个字节一组,分成8组十六进制数,解决了网络地址资源数量不够的问题。表示成:ABCD:EF01:2345:6789:ABCD:EF01:2345:6789
  • 查看本机IP地址,在控制台输入:ipconfig
  • 检查网络是否连通,在控制台输入:ping 空格 IP地址
  • 本机IP地址:127.0.0.1

6. 端口号

1
网络的通信,本质上是两个进程(应用程序)的通信,端口号可以标识设备中的不同进程(应用程序)。

用两个字节表示的整数,它的取值范围是0~65535。

0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。

常用端口号:

  1. 网络端口 https:443 http:80
  2. 数据库 mysql:3306 oracle:1521
  3. Tomcat服务器:8080

【协议+ IP地址+ 端口号】 三元组合可以标识网络中的进程,进程间的通信可以利用这个标识与其它进程交互。

7. Socket类

1
java.net.Socket类表示客户端。创建Socket对象向服务器发出连接请求,服务器响应请求,两者建立连接开始通信。
1
2
3
4
5
6
7
//构造方法
public Socket(String host, int port) :创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null,则相当于指定地址为回送地址。
//成员方法
public InputStream getInputStream():返回此套接字的输入流。
public OutputStream getOutputStream():返回此套接字的输出流。
public void close():关闭此套接字,关闭socket也将关闭相关的InputStream和OutputStream 。
public void shutdownOutput():禁用此套接字的输出流,任何先前写出的数据将被发送,随后终止输出流。

8. ServerSocket类

1
java.net.ServerSocket类表示服务端。创建ServerSocket 对象,相当于开启一个服务,并等待客户端的连接。
1
2
3
4
//构造方法
public ServerSocket(int port):使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。
//成员方法
public Socket accept():侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。
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
//客户端代码演示
public class TCPClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8888);
OutputStream os = socket.getOutputStream();
os.write("你好服务器".getBytes());
InputStream is = socket.getInputStream();
byte[] bys = new byte[1024];
int len = is.read(bys);
System.out.println(new String(bys,0,len));
System.out.println(socket);
socket.close();}}
//服务端代码演示
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8888);
Socket socket = server.accept();
InputStream is = socket.getInputStream();
byte[] bys = new byte[1024];
int len = is.read(bys);
System.out.println(new String(bys, 0, len));
OutputStream os = socket.getOutputStream();
os.write("收到谢谢".getBytes());
System.out.println(server);
socket.close();
server.close();}}

9. 文件上传优化分析

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
//文件上传案例服务端代码演示
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8888);
Socket socket = server.accept(); ②
FileOutputStream fos = new FileOutputStream("copy.jpg"); ①
InputStream is = socket.getInputStream();
int len;
byte[] bys = new byte[1024];
while ((len = is.read(bys)) != -1) {
fos.write(bys, 0, len);
}
socket.getOutputStream().write("上传成功".getBytes());
fos.close();
socket.close();
server.close();
//文件上传案例客服端代码演示
public static void main(String[] args)throws IOException {
FileInputStream fis = new FileInputStream("src.jpg");
Socket socket = new Socket("127.0.0.1", 8888);
OutputStream os = socket.getOutputStream();
int len;
byte[] bys = new byte[1024];
while ((len = fis.read(bys)) != -1) {
os.write(bys, 0, len);
}
socket.shutdownOutput(); //避免阻塞问题
InputStream is = socket.getInputStream();
while ((len = is.read(bys)) != -1) {
System.out.println(new String(bys, 0, len));
}
socket.close();
fis.close();
① 文件名称写死的问题
1
2
服务端,保存文件的名称如果写死,那么最终导致服务器硬盘,只会保留一个文件,建议使用系统时间优化,保证文件名称唯一,代码如下:
FileOutputStream fis = new FileOutputStream(System.currentTimeMillis()+".jpg") // 文件名称
② 循环接收的问题
1
2
3
4
5
服务端,指保存一个文件就关闭了,之后的用户无法再上传,这是不符合实际的,使用循环改进,可以不断的接收不同用户的文件,代码如下:
whiletrue){
Socket socket = server.accept();
......
}
③ 效率问题
1
2
3
4
5
6
7
8
9
10
服务端,在接收大文件时,可能耗费几秒钟的时间,此时不能接收其他用户上传,所以,使用多线程技术优化,代码如下:
whiletrue){
Socket socket = serverSocket.accept();
// socket 交给子线程处理.
new Thread(() ‐> {
......
InputStream is = socket.getInputStream();
......
}).start();
}

【函数式接口】

1. 函数式接口

1
有且仅有一个抽象方法的接口。

@FunctionalInterface注解:使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。

2. Lambda的延迟执行

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
//无论level是多少,都会先把字符串拼接并传入方法内。如果level不符合要求,拼接操作就白做了
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1, msgA + msgB + msgC);}

private static void log(int level, String msg) {
if (level == 1) {
System.out.println(msg);}}

//使用Lambda的延迟执行进行优化,只有满足要求的时候才会进行三个字符串的拼接;否则三个字符串将不会进行拼接。
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1, () ‐> msgA + msgB + msgC );}

private static void log(int level, MessageBuilder builder) {
if (level == 1) {
System.out.println(builder.buildMessage());}}

@FunctionalInterface
public interface MessageBuilder {
String buildMessage();}

3. Supplier接口

1
2
java.util.function.Supplier<T>接口用来获取一个泛型参数指定类型的对象数据。
仅包含一个无参的抽象方法:T get()
1
2
3
4
5
6
7
//代码演示
public static void main(String[] args) {
String s = getString(() -> "Eric" + "loset");
System.out.println(s);}

public static String getString(Supplier<String> sup) {
return sup.get();}

4. Consumer接口

1
2
3
4
5
6
java.util.function.Consumer<T> 接口与Supplier接口相反,是消费一个数据,其数据类型由泛型决定。
包含一个有参的抽象方法void accept(T t),一个默认方法andThen(),可以组合多个步骤。
//andThen方法源码:
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) ‐> { accept(t); after.accept(t); }
1
2
3
4
5
6
7
8
9
//代码演示
public static void main(String[] args) {
method("方法",
(t) -> System.out.print(t + "1"),
(t) -> System.out.print(t + "2"),
(t) -> System.out.print(t + "3"));} //方法1方法2方法3

public static void method(String name, Consumer<String> con1, Consumer<String> con2, Consumer<String> con3){
con1.andThen(con2).andThen(con3).accept(name);}

5. Predicate接口

1
2
3
java.util.function.Predicate<T>接口返回一个boolean值结果,用于对某种类型的数据进行判断。
接口中包含一个抽象方法: boolean test(T t),用于条件判断的场景。
接口中包含三个默认方法: and(),or(),negate()
1
2
3
4
5
6
7
8
9
10
11
//代码演示
public static void main(String[] args) {
String s = "abcdefg";
boolean b = checkString(s,
(str) -> str.length() > 20,
(str) -> str.contains("bc"));
System.out.println(b);} //true

public static boolean checkString(String s, Predicate<String> pre1,
Predicate<String> pre2) {
return pre1.or(pre2).test(s);}

6. Function接口

1
2
java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据。
包含一个有参的抽象方法 R apply(T t),一个默认方法andThen(),可以组合多个步骤。
1
2
3
4
5
6
7
8
//代码演示
private static void method(String str, Function<String, Integer> function) {
int num = function.apply(str);
System.out.println(num + 20);
}
public static void main(String[] args) {
method("100",s->Integer.parseInt(s));
}

【Stream流、方法引用】

1. 流式思想

1
2
3
Stream(流)是一个来自数据源(集合、数组等)的元素队列,元素是特定类型的对象,形成一个队列。
Java中的Stream并不会存储元素,而是按需计算。中间操作都会返回流对象本身,这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。
这样做可以对操作进行优化,比如延迟执行(laziness)和短路( short-circuiting)。

Stream流支持并行,效率高

2. 获取流

1
2
3
4
5
6
7
8
9
10
11
//单列集合中,Collecion接口中加入了stream方法用于获取流
Collection<String> c = new ArrayList();
Stream<String> collectionStream = c.stream();
//双列集合中,获取流需要先获得key集合或value集合或者entry集合
Map<String, String> map = new HashMap<>();
Stream<String> keyStream = map.keySet().stream();
Stream<String> valueStream = map.values().stream();
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
//要获取数组的流对象,可以通过Stream接口中提供的静态方法of
String[] arr = {"one", "two", "three"};
Stream<String> arrStream = Stream.of(arr);

备注: of 方法的参数其实是一个可变参数,所以支持数组。

3. 常用方法

  • 延迟方法:返回值类型仍然是Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)
  • 终结方法:返回值类型不再是Stream 接口自身类型的方法,因此不再支持类似StringBuilder 那样的链式调用。终结方法包括count 和forEach 方法。
forEach:逐一处理
1
void forEach(Consumer<? super T> action);
filter:过滤
1
Stream<T> filter(Predicate<? super T> predicate);
map:映射
1
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
count:统计个数
1
long count();
limit:取用前几个
1
Stream<T> limit(long maxSize);

参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。

skip:跳过前几个
1
Stream<T> skip(long n);

如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。

concat:组合
1
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
collect:将流元素生成集合
1
2
List<T> list = stream.collect(Collectors.toList());
Set<T> set = stream.collect(Collectors.toSet());
toArray:将流元素生成数组
1
Object[] array = stream.toArray();
1
2
3
4
5
6
7
//代码演示
// 第一个队伍只要名字为3个字的成员姓名;// 第一个队伍筛选之后只要前3个人;
Stream<String> streamOne = one.stream().filter(s ‐> s.length() == 3).limit(3);
// 第二个队伍只要姓张的成员姓名;// 第二个队伍筛选之后不要前2个人;
Stream<String> streamTwo = two.stream().filter(s ‐> s.startsWith("张")).skip(2);
// 将两个队伍合并为一个队伍;// 根据姓名创建Person对象;// 打印整个队伍的Person对象信息。
Stream.concat(streamOne, streamTwo).map(Person::new).forEach(System.out::println);

注意:Stream分为三类方法:

  1. 初始操作,将数据源转换为Stream流
  2. 中间操作,调用之后返回值也是Stream对象
  3. 终止操作,调用之后返回值不是Streasm对象

流中的数据只能被消费一次,流在操作过程中,如果执行的是初始操作、中间操作,那么实际上不会对流元素进行处理,只有在进行Stream的终止操作的时候才执行

4. 方法引用

1
双冒号::为引用运算符,它所在的表达式被称为方法引用,是简化Lambda的书写。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。前提是括号内的所有内容都是依赖于一个对象的某个方法实现的,并不是所有的Lambda表达式都能被简化为方法引用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//代码演示
public static int method(int i, Function<Integer, Integer> f) {
return f.apply(i);
}
public static void main(String[] args) {
System.out.println(method(-5, Math::abs));
}
//数组的构造器引用
public static void main(String[] args) {
int[] ints = createArray(5, int[]::new);
System.out.println(Arrays.toString(ints));
}
public static int[] createArray(int i, Function<Integer, int[]> f) {
return f.apply(i);
}
//类的构造器引用
public static void main(String[] args) {
personBuilder("Eric", (Person::new));
}
public static void personBuilder(String s, Function<String, Person> f) {
Person p = f.apply(s);
System.out.println(p);
}

【单元测试、反射、注解】

1. 单元测试

1
2
3
测试分类:
1. 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值,不用懂程序的知识也可以进行测试
2. 白盒测试:需要写代码的,关注程序具体的执行流程。

白盒测试步骤:

  1. 定义一个测试类,类名为被测试类名+Test(CalculatorTest)
  2. 定义一个测试方法,可以独立运行,方法名为test+被测方法名(testAdd() )

方法的返回值:void;参数列表:空参

  1. 给方法加@Test
  2. 导入junit依赖环境

注意事项:

  1. 使用断言操作Assert.assertEquals(期望的结果,运算的结果)来处理结果
  2. 红色结果为失败,绿色结果为成功
  3. @Before:修饰的方法会在测试方法之前被自动执行
  4. @After:修饰的方法会在测试方法执行之后自动被执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//代码演示
public class CalculatorTest {
@Before
public void before() {
System.out.println("初始化...");}
@Test
public void testAdd(){
Calculator c = new Calculator();
int add = c.add(1, 2);
Assert.assertEquals(3, add);}
@Test
public void testSub(){
Calculator c = new Calculator();
int sub = c.sub(1, 2);
Assert.assertEquals(-1, sub);}
@After
public void after(){
System.out.println("释放资源");}}

2. 反射

1
反射:将类的各个组成部分封装为其他对象,这就是反射机制。通过反射,可以在程序运行过程中操作这些对象,是框架的基础。运用反射可以解耦,提高程序的可拓展性。
获取Class对象
1
2
3
4
5
6
1. Class.forName("全类名"):用于配置文件
Class personClass = Class.forName("Reflect.Person");
2. 类名.class:用于参数的传递
Class personClass = Person.class;
3. 对象.getClass():用于对象的获取字节码的方式
Person p = new Person(); Class personClass = p.getClass();
操作成员变量对象
1
2
3
4
5
6
7
8
9
//获取成员变量对象
Field getField(String name):获取指定名称的public修饰的成员变量
Field[] getFields():获取所有public修饰的成员变量
Field getDeclaredField(String name):获取指定名称的成员变量,不考虑修饰符
Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符
//设置成员变量方法
void set(Object obj, Object value):设置值
get(Object obj):获取值
setAccessible(true):忽略访问权限修饰符的安全检查(暴力反射)
操作构造方法对象
1
2
3
4
5
6
7
//获取构造方法对象
Constructor<T> getConstructor(parameterTypes.class):获取指定参数列表的public修饰的成员变量
Constructor<?>[] getConstructors():获取所有public修饰的构造方法
Constructor<T> getDeclaredConstructor(parameterTypes.class):获取指定参数列表的成员变量
Constructor<?>[] getDeclaredConstructors():获取所有的构造方法,不考虑修饰符
//创建对象的方法
T newInstance(Object... initargs)
操作成员方法对象
1
2
3
4
5
6
7
//获取成员方法对象
Method getMethod(String name,parameterTypes.class):获取指定名称和参数列表的public修饰的方法
Method[] getMethods():获取所有public修饰的成员方法
Method getDeclaredMethod(String name,parameterTypes.class):获取指定名称和参数列表的成员方法
Method[] getDeclaredMethods():获取所有的成员方法,不考虑修饰符(含继承的方法)
//执行方法
method invoke(Object obj)

3. “框架”案例

1
2
3
4
5
6
7
8
9
10
11
12
13
//	配置文件ClassMessage.properties:
// className = Reflect.Person
// methodName = eat
public static void main(String[] args) throws Exception {
Properties pro = new Properties();
pro.load(new FileInputStream("ClassMessage.properties"));
String className = pro.getProperty("className"); //Reflect.Person
String methodName = pro.getProperty("methodName"); //eat
Class cls = Class.forName(className);
Object o = cls.getConstructor().newInstance();
Method method = cls.getMethod(methodName);
method.invoke(o); //调用了Person类中的eat方法
}

4. 注解

1
2
注解(Annotation),也叫元数据。一种代码级别的说明,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。JDK1.5及以后版本引入的特性。
格式:@注解名称

作用分类:

  1. 编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
  2. 代码分析:通过代码里标识的注解对代码进行分析【使用反射】
  3. 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】

JDK中预定义的一些注解:

  • @override:方法重写注解
  • @Deprecated:过期注解
  • @SuppressWarnings:压制警告,将当前所有的类警告都去除

一般传递参数all @SuppressWarnings(“all”)

5. 自定义注解

1
2
3
4
5
//格式:
元注解
public @interface 注解名称{
属性列表;
}

注解本质上就是一个接口,该接口默认继承Annotation接口public interface MyAnno extends java.lang.annotation.Annotation {}

注解接口中的要求:

  • 接口中的抽象方法称为属性,返回值类型为:

①基本数据类型;②String;③枚举;④注解;⑤以上类型的数组

  • 定义了属性,在使用时需要给属性赋值,若定义时已经给了默认值,可以不用赋值,如果只有一个属性需要赋值,并且属性的名称是value,则赋值的时候,value的字样可以省略
  • 数组赋值时,需要使用{}包裹,如果数组中只有一个值,则{}省略

6. 元注解

1
用于描述注解的注解。

@Target(ElementType):描述注解能够作用的位置

ElementType取值:

  1. ElementType.TYPE:可以作用于类上
  2. ElementType.METHOD:可以作用于方法上
  3. ElementType.FIELD:可以作用于成员变量上

@Retention(RetentionPolicy):描述注解被保留的阶段

RetentionPolicy取值:

  1. RetentionPolicy.RUNTIME:当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
  2. RetentionPolicy.CLASS:当前被描述的注解,会保留到class字节码文件中,不会被JVM读取
  3. RetentionPolicy.SOURCE:当前被描述的注解,不会被class字节码文件保留

@Documented:描述注解是否被抽取到api文档中

@Inherited:描述注解是否被子类继承

7. ”测试框架“案例

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
public static void main(String[] args) throws IOException {
Calculator c = new Calculator();
Class cls = c.getClass();
//获取类中的所有方法
Method[] methods = cls.getMethods();
//创建输出流和记录异常次数的变量count
BufferedWriter bw = new BufferedWriter(new FileWriter("error.txt"));
int count = 0;
for (Method method : methods) { //遍历所有方法,筛选出被@Check标记的方法
if (method.isAnnotationPresent(Check.class)) {
try {
method.invoke(c);
} catch (Exception e) {
count++;
//将异常打印到日志文件中
bw.write(method.getName() + "方法出异常了");
bw.newLine();
bw.write("异常的名称" + e.getCause().getClass().getSimpleName());
bw.newLine();
bw.write("异常的原因" + e.getCause());
bw.newLine();
bw.write("-----------------");
bw.newLine();}}}
bw.write("本次测试共出现了" + count + "次异常");
bw.close();}