一、java是什么

Java 是由 Sun Microsystems(后被 Oracle 收购)于 1995 年推出的跨平台面向对象编程语言,核心设计理念是「Write Once, Run Anywhere(一次编写,到处运行)」,凭借简洁性、安全性、可移植性成为全球最主流的编程语言之一,广泛应用于企业级开发、移动开发、大数据、云计算等领域。

核心特性

  1. 跨平台性:通过 JVM(Java 虚拟机)实现 ——Java 代码编译为与平台无关的字节码(.class 文件),JVM 负责将字节码解释 / 编译为对应操作系统的机器码,无需针对不同系统修改代码。
  2. 面向对象:完全遵循封装、继承、多态三大特性,万物皆对象(除基本数据类型),代码模块化、易维护。
  3. 安全性:内置安全机制(如字节码验证、沙箱机制),避免内存溢出、非法访问,适合网络 / 分布式应用。
  4. 健壮性:自动垃圾回收(GC)管理内存,减少内存泄漏;强类型检查、异常处理机制降低程序崩溃风险。
  5. 多线程:内置多线程 API,支持并发编程,适合高并发场景(如电商、服务器开发)。
  6. 开源生态:拥有丰富的标准库(IO、网络、集合等)和第三方框架(Spring、MyBatis、Hadoop 等),开发效率极高。

二、java概述

JDK 基本介绍

JDK 的全称(JavaDevelopment Kit Java 开发工具包)
JDK =JRE+java 的开发工具 [java,javac,javadoc,javap 等]
JDK是提供给Java开发人员使用的,其中包含了java的开发工具,也包括了JRE。所以安装了JDK,就不用在单独 安装JRE了。

JRE 基本介绍

JRE(Java Runtime Environment )
JRE =JVM+Java 的核心类库[类] Java 运行环境) 包括Java虚拟机(JVMJavaVirtual Machine)和 Java 程序所需的核心类库等,如果想要运行一个开发好的Java程序, 计算机中只需要安装JRE即可。
JDK、JRE 和 JVM的包含关系 1) JDK=JRE+ 开发工具集(例如Javac,java编译工具等)
JRE=JVM+JavaSE标准类库(java核心类库)

三、环境变量配置

流程:

在我的电脑右击属性,选择高级系统设置,选择环境变量,再系统变量选择新建,

名称为 %JAVA_HOME% 值为下载的 jdk路径

再path编辑输入 %JAVA_HOME%\bin即可

四、变量

分类 别名 定义 修饰 生命周期 调用方式 示例
局部变量 方法变量 定义在方法 / 代码块内 无权限修饰、不能加static 方法调用时创建,执行完销毁 仅方法内直接使用 int a = 10;
成员变量 (实例变量) 对象变量 定义在类中、方法外 可加private/public/protected 对象创建时存在,对象回收销毁 对象。变量名 User.name;
静态变量 (类变量) 全局变量 类中方法外,加static 必带static 类加载时初始化,全局唯一 类名。变量名 Math.PI
1
2
3
4
5
6
7
8
9
10
11
package com.Variable_1;

public class variable {
private int age; //方法变量
private static int age2; //静态变量


public void test() {
int name; //局部变量
}
}

基本数据类型(8类):

数据类型 占用字节 默认值 取值 / 说明 书写注意
byte 1 0 -128 ~ 127 字节类型
short 2 0 -32768 ~ 32767 短整型
int 4 0 日常整型默认 最常用
long 8 0L 长整型 数值后加 L
float 4 0.0f 单精度浮点数 数值后加 F/f
double 8 0.0 双精度浮点数 小数默认类型
char 2 \u0000 单个字符 单引号 'a'
boolean 1 false 只有 true /false 不参与运

引用数据类型:

引用类型分类 代表类型 默认值 特点 示例
字符串类型 String null 不可变字符序列,引用类型 String s = "abc";
数组 基本类型数组 / 对象数组 null 固定长度,存储批量数据 int[] arr = new int[5];
自定义类 自己写的实体类 null new 实例化,封装属性方法 User user = new User();
集合框架 List、Set、Map null 长度可变,增删灵活 List<String> list = new ArrayList<>();
系统类 Object、Date、Scanner 等 null JDK 内置工具 / 实体类 Date date = new Date();

数据强转:

1.隐式转换

规则:小范围 → 大范围,自动转,无损

优先级(从小到大)
byteshortintlongfloatdouble
charint
1
2
int a = 10;
double b = a; // 自动转换

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
25
26
27
28
public class CastDemo {
public static void main(String[] args) {
// 1. 浮点型 强转 整型:舍弃小数
double d = 5.99;
int num = (int) d;
System.out.println(num); // 5

// 2. 大范围整数 强转 小范围整数:可能溢出
int a = 130;
byte b = (byte) a;
System.out.println(b); // -126(溢出错乱)

// 3. int 强转 char(ASCII码)
int code = 97;
char ch = (char) code;
System.out.println(ch); // a

// 4. long 强转 int
long lo = 9999L;
int in = (int) lo;
System.out.println(in);

// 5. float 强转 byte
float f = 10.8f;
byte by = (byte) f;
System.out.println(by); // 10
}
}

五、转义符和注释

1.转义符:

转义符 作用 说明
\n 换行 常用,Windows/Linux 通用
\r 回车 回到当前行开头
\r\n 回车 + 换行 Windows 系统默认换行
\t 制表符(空格缩进) 相当于按 Tab 键
\\ 反斜杠本身 打印 \
\" 双引号 打印 “
\' 单引号 打印 ‘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class NewLineDemo {
public static void main(String[] args) {
// \n 换行
System.out.print("第一行\n第二行");

System.out.println("--------");

// \r\n windows标准换行
System.out.print("A\r\nB");

System.out.println("--------");

// \t 制表缩进
System.out.println("姓名\t年龄");
System.out.println("张三\t18");
}
}

2.注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//   单行注释

/*
多行注释
**/

/**
帮助文档
**/

IDE相关特殊标记
// TODO 待完成 代码没写完,后续要补
// FIXME 必须修复 有 Bug、报错、严重问题
// XXX 存在隐患 代码能运行,但不规范 / 有风险
// NOTE 重点备注 关键说明、注意事项
// HACK 暴力临时写法 临时凑合的代码,后期要重构
// BUG 明确 Bug 已知缺陷
// OPTIMIZE 待优化 性能差、代码冗余需要优化
// REVIEW 待审查 需要别人检查这段代码

六、运算符

1.算数运算符

符号 名称 作用 示例
+ 求和、字符串拼接 5+3
- 求差 5-3
* 乘积 5*3
/ 整数相除取整 5/2 = 2
% 取模 / 取余 求余数 5%2 = 1
++ 自增 自身 + 1 a++
-- 自减 自身 - 1 a--
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Operator {
public static void main(String[] args) {
int a = 10;
int b = 3;

System.out.println(a + b); // 加 13
System.out.println(a - b); // 减 7
System.out.println(a * b); // 乘 30
System.out.println(a / b); // 除 3(整数舍去小数)
System.out.println(a % b); // 取余 1

// 自增 自减
int x = 5;
System.out.println(x++); // 后置:先输出5,再加1
System.out.println(++x); // 前置:先加1,输出7
}
}

2.赋值运算符

符号 作用 等价写法
= 普通赋值 a = 10
+= 加后赋值 a += 5a = a + 5
-= 减后赋值 a -= 5a = a - 5
*= 乘后赋值 a *= 5a = a * 5
/= 除后赋值 a /= 5a = a / 5
%= 取余赋值 a %= 5a = a % 5
1
2
3
4
5
6
7
8
int num = 10;
num += 5; // num = num + 5 // 15
num -= 3; // num = num - 3 // 12
num *= 2; // num = num * 2 // 24
num /= 4; // num = num / 4 // 6
num %= 4; // num = num % 4 // 2

System.out.println(num);

3.关系运算符

符号 含义
> 大于
< 小于
>= 大于等于
<= 小于等于
== 等于(比较值)
!= 不等于
1
2
3
4
5
6
7
8
9
int m = 20;
int n = 15;

System.out.println(m > n); // true
System.out.println(m < n); // false
System.out.println(m >= 20); // true
System.out.println(m <= 15); // false
System.out.println(m == n); // false 判断相等
System.out.println(m != n); // true 判断不等

4.逻辑运算符

符号 名称 特点
&& 短路与 左边 false,右边不执行
|| 短路或 左边 true,右边不执行
! 逻辑非 取反
1
2
3
4
5
6
7
8
9
10
int score = 80;

// && 短路与:两边都满足
System.out.println(score >= 60 && score <= 100); // true

// || 短路或:一边满足即可
System.out.println(score < 60 || score > 50); // true

// ! 非 取反
System.out.println(!(score > 50)); // false

5.位运算符(了解)

符号 名称 规则(核心口诀)
& 按位与 同为 1 才是 1,有 0 则 0
` ` 按位或 同为 0 才是 0,有 1 则 1
^ 按位异或 相同为 0,不同为 1
~ 按位取反 0 变 1,1 变 0
<< 左移 整体左移,低位补 0 → 等价 ×2
>> 右移(符号右移) 整体右移,高位补符号位 → 等价 ÷2
>>> 无符号右移 整体右移,高位统一补 0

6.三元运算符

1
条件 ? 表达式1 : 表达式2;
1
2
3
4
int age = 17;
// 条件 ? 成立 : 不成立
String res = age >= 18 ? "成年" : "未成年";
System.out.println(res); // 未成年

七、标识符规则

标识符就是:变量名、类名、方法名、常量名

合法字符:

字母 (A-Za-z)、数字 (0-9)、下划线_、美元符号 $ 组成

不能以数字开头

可以用中文(但绝对不推荐

强制禁止:

不能是 Java 关键字(while、for、if、class、public 等)

不能有 空格、特殊符号(# @ ! * - 都不行)

区分大小写:nameName 是两个不同标识符

八、Scanner和Random API

1.Scanner键盘输入

方法 作用 说明
nextInt() 读取整数 只能输入 int 类型,遇到空格 / 回车结束
nextDouble() 读取小数 读取浮点型数字
next() 读取单个单词 遇到空格、回车自动截断,不能读带空格的句子
nextLine() 读取一整行 可以读取带空格的完整一句话,按回车结束
nextBoolean() 读取布尔值 只能输入 true /false
nextByte() 读取 byte 类型 字节整数
nextShort() 读取 short 类型 短整型
nextLong() 读取 long 类型 长整型
nextFloat() 读取 float 类型 单精度小数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.API;

import java.util.Scanner;

public class scanner {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); //1.创建Scanner System.in表示为输入
System.out.println("输入数字:");
int i = scanner.nextInt(); //2.必须为数字
System.out.println("输出:" + i);

scanner.close(); //3.关闭防止内存泄露
}
}

2.Random

r.nextInt(n) 随机 int 整数 正负全部整数
r.nextInt(n,m) 随机整数 0 ~ n-1
r.nextDouble(n) 随机小数 0.0 ~ 1.0
r.nextBoolean(n) 随机布尔 true / false
r.nextLong(n) 随机长整数 大范围正负长整型
1
2
3
4
5
6
7
8
9
10
11
package com.API;

import java.util.Random;

public class random {
public static void main(String[] args) {
Random random = new Random();
int i = random.nextInt(10);
System.out.println(i);
}
}

九、流程控制语句

1. if语句

1
2
3
4
5
6
7
8
9
if(条件1){

}else if(条件2){

}
...
else{
都不满足执行
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.ProcessControl_3;

import java.util.Scanner;

public class If {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入数字:");
int count = scanner.nextInt();

if(count == 1){
System.out.println(1);
}else if(count == 2){
System.out.println(2);
}else if(count == 3){
System.out.println(3);
}else{
System.out.println(count);
}
}
}

2. switch语句

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
package com.ProcessControl_3;

import java.util.Scanner;

public class Switch {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入数字:");
int count = scanner.nextInt();

switch(count){
case 1,4: //可以多值
System.out.println("星期1");
break; //必须要break防止穿透
case 2:
case 5:
System.out.println("星期2");
break;
case 3:
System.out.println("星期3");
break;
default:
System.out.println("其他");
}


/// java14+ 新版箭头
switch(count){
case 1 -> System.out.println("星期1");
case 2 -> System.out.println("星期2");
default -> System.out.println("其他");
}
}
}

3. for语句

1
2
3
for(初始化表达式; 判断条件; 步进表达式){
循环体;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.ProcessControl_3;

public class For {
public static void main(String[] args) {
for(int i = 0; i < 12; i++){
System.out.println(i);
}

for(int i = 0,j = 0; i < 12; i++,j++){
System.out.println(i + j);
}
}
}

4. while语句

1
2
3
while(布尔条件){
循环体;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.ProcessControl_3;

public class While {
public static void main(String[] args) {
int count = 0;
while(true){
System.out.println(111);
count++;
if(count == 10){
break; ///停止循环 break;
}
}
}
}

5. do-while语句(了解)

1
2
3
do{	//必定执行一次
循环体;
}while(条件); //这里不写了

6.跳转控制语句

1. break

退出整个循环,一般用于 while或for循环

2.continue

退出当前循环

3.return

退出整个函数

十、数组

数组是 同一类型、固定长度、连续存放的一组容器

1.定义方式

1
2
3
4
5
6
7
// 动态数组定义   格式:数据类型[] 数组名 = new 数据类型[长度];
int[] arr = new int[5];

//静态数组定义 // 完整格式
int[] arr = new int[]{10,20,30};
// 简写格式(常用)
int[] arr = {10,20,30}; //C++语言写法 int arr[] = {1,2,3}

2.数组遍历

1
2
3
4
5
6
7
8
9
10
11
int[] arr = {11,22,33,44};

// 普通for 使用for循环 length表示获取数组元素长度
for(int i = 0; i < arr.length; i++){
System.out.println(arr[i]);
}

//增强for
for(int i : arr){
System.out.println(arr[i]);
}

3.传递机制

基本数据类型传递机制是值拷贝,引用数据类型是传递的地址,为地址拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int[] arr = {11,33,44};
int[] arr1 = arr; //此时传递的是地址,两个数组共用一个地址
//更改arr1的内容
arr1[0] = 77;
for(int i = 0;i < arr.length; i++){
System.out.println(arr[i]);//此时第一个数组的内容会被更改!!!!
}

//如果我不想地址拷贝,单纯值拷贝怎么办?
int[] arr2 = {55,33,22,44};
int[] arr3 = new int[arr2.length];//在开辟一个新地址
arr3[0] = 66;
for(int i = 0;i < arr2.length;i++){
System.out.println(arr2[i]);
}

4.二维数组

定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 动态定义: 3行2列
int[][] arr = new int[3][2];


//不确认定义 // 后面再单独给每一行创建数组
int[][] arr = new int[3][];

arr[0] = new int[2];
arr[1] = new int[4];

//静态初始化
int[][] arr = {
{1,2,3},
{4,5},
{6,7,8,9}
};

//取值
arr[0][1];

遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int[][] arr = {
{1,2,3},
{4,5,6},
{7,8,9}
};

// 外层循环:遍历行
for (int i = 0; i < arr.length; i++) {
// 内层循环:遍历当前行的每一列
for (int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j] + " ");
}
System.out.println();
}

//增强for
for (int[] row : arr) {
for (int n : row) {
System.out.print(n + " ");
}
System.out.println();
}

5.Arrays API方法

方法 作用
Arrays.toString(数组) 一维数组转成字符串打印
Arrays.sort(数组) 数组升序排序
Arrays.binarySearch(数组, key) 二分查找元素下标(必须先排序
Arrays.copyOf(原数组, 新长度) 拷贝数组,可扩容 / 缩容
Arrays.copyOfRange(arr, 起始, 结束) 范围拷贝,左闭右开
Arrays.fill(数组, 值) 给数组全部填充同一个值
Arrays.equals(数组1, 数组2) 判断两个数组内容是否完全一样
Arrays.deepToString(二维数组) 打印二维数组
Arrays.deepEquals(二维数组1, 二维数组2) 判断二维数组内容是否相等

十一、类与对象

1. 什么是类?

类:是模板、图纸、抽象概念比如:人类、学生类、手机类

  • 定义有什么属性
  • 定义有什么行为

2. 什么是对象?

对象:是模板造出来的具体实例、真实个体根据「人类」这个模板,造出:张三、李四 就是对象。

3. 关系

类 → 模板****对象 → 模板 new 出来的真实个体一句话:类是抽象,对象是具体

1.定义类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 1. 定义类   Student学生类
public class Student {
// 2. 成员变量(属性)
String name;
int age;
String gender;

// 3. 成员方法(行为)
public void study(){ //无参无返回值
System.out.println(name + " 在认真学习");
}

public void eat(name){ //有参无返回值
System.out.println(name + " 在吃饭");
}

public int count(){ //无参有返回值
return 10;
}

//基本数据类型的值在方法体里值的改变不会影响main方法里的值;形参不会改变实参:值拷贝
//引用数据类型的值在方法体里值的改变会影响main方法里的值;形参会影响实参:地址传递
}

2.创建、使用对象

1
2
3
4
5
6
7
8
9
10
11
// 1. 创建学生对象
Student s = new Student();

// 2. 给成员变量赋值
s.name = "小明";
s.age = 18;
s.gender = "男";

// 3. 调用成员方法
s.study();
s.eat();

3.内存细节

1
2
3
4
5
new Student()

成员变量存在堆内存里
引用变量 s 存在栈内存,存的是堆内存地址值
多个对象互不干扰,各自有自己的成员变量

4.成员变量和局部变量

区别 成员变量 局部变量
定义位置 类中、方法外 方法里、代码块里
作用范围 整个类都能用 只能在当前方法 / 代码块
默认初始值 有默认值(int0,boolean false 等) 没有默认值,必须手动赋值
内存位置 堆内存 栈内存
生命周期 对象消失才消失 方法执行完立刻消失
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*成员变量与局部变量区别:
成员变量:类中方法外,作用与整个类 如果未赋值,系统默认初始值为空域;
局部变量:方法中 离他最近的大括号内 未赋值不可用 如果成员变量和局部变量 属性名相同,
则局部变量优先级更高; 局部变量不可以加修饰符如:private
*/

public class chenyuan{
String name; 代表成员变量

public void show(){
int a; 代表局部变量
System.out.println(name); 输出为 null
System.out.println(a); 此时会报错 因为局部变量一定要赋值!!!!!!!!!!!
}

public void show(int a){ 参数也属于局部变量 如括号里的 int a

}
}

5.构造方法

什么是构造方法?? 创建对象时,自动调用的方法

无参构造方法

1
2
3
4
5
6
class love{
//必须和类名相同!!!!
public love(){ //无参构造方法,如果不写默认自带(所有类自动继承Object顶级父类,所以自带默认无参构造器)

}
}

有参构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class love{
private String name;
private int age;

public love(String name,int age){ //全参构造器
this.name = name;
this.age = age;
}
public love(String name){ //部分参构造器
this.name = name;
}
}


/*
细节:1.一个类可以定义多个不同的构造器,即构造器重载; 2.构造器要和类名相同;
3.构造器没有返回值; 4.构造器是完成对象的初始化,并不是创建对象; 4.系统会自动调用该类的构造方法
5.如果类没有构造器,系统会默认生成无参构造方法,可以使用javap指令 反编译查看

*/

6. this关键字

Java虚拟机给每个对象分配this,代表当前对象。

this 属性 如果参数和类的属性相同,就要使用this关键字(一般与构造器相对)

用法1:

局部变量和成员变量同名时,用 this. 指代类的成员变量

1
2
3
4
5
6
7
8
9
10
class Person {
String name;

// 形参和成员变量重名
public void setName(String name){
this.name = name;
// this.name 是成员变量
// 右边 name 是局部形参
}
}

用法2:

this. 方法 () 调用本类方法

1
2
3
4
5
6
7
8
9
10
class love{
public void a(){
System.out.println("a方法");
}

public void b(){
this.a(); // 调用本类a方法
}
}

用法3:

this () 调用本类构造方法

1
2
3
4
5
6
7
8
9
10
11
class Person {
// 无参构造
public Person(){
this("李四", 20); // 调用有参构造,必须第一行
}

// 有参构造
public Person(String name, int age){
System.out.println("有参构造");
}
}

7.方法重载

同一个类中,方法名相同,但参数列表不同的多个方法,就是方法重载。

方法名必须完全一样

参数列表必须不同(满足任意一种即可)

  • 参数个数不同
  • 参数类型不同
  • 参数顺序不同(类型不同时)

返回值、访问修饰符(public/private)不影响重载

只看方法名 + 参数列表!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Calculator {

// 1. 两个int相加
public int add(int a, int b) {
return a + b;
}

// 2. 三个int相加(参数个数不同 → 重载)
public int add(int a, int b, int c) {
return a + b + c;
}

// 3. 两个double相加(参数类型不同 → 重载)
public double add(double a, double b) {
return a + b;
}

// 4. int + double(参数顺序不同 → 重载)
public double add(int a, double b) {
return a + b;
}
}

8.可变参数

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
25
26
27
28
29
public class Demo {
// 可变参数求和
public static int getSum(int... nums){
int sum = 0;
for (int n : nums) {
sum += n;
}
return sum;
}

public static void main(String[] args) {
// 多种调用方式
System.out.println(getSum());
System.out.println(getSum(1));
System.out.println(getSum(1,2,3));
System.out.println(getSum(10,20,30,40));

// 直接传数组也可以
int[] arr = {5,6,7};
System.out.println(getSum(arr));
}
}

/**
细节:1.可变参数可以为0-多个; 2.可变参数可以为数组; 3.可变参数的实参可以为数组
4.可变参数的本质就是数组; 5.一个形参列表只能有一个可变参数!!!
6.可变参数可以和普通参数一起放,但可变参数必须放最后面!!!!

**/

9.访问修饰符

修饰符 本类内部 同包无关类 不同包子类 不同包无关类
private 私有 ✅ 可以 ❌ 不行 ❌ 不行 ❌ 不行
default 默认 (包访问) ✅ 可以 ✅ 可以 ❌ 不行 ❌ 不行
protected 受保护 ✅ 可以 ✅ 可以 ✅ 可以 ❌ 不行
public 公共 ✅ 可以 ✅ 可以 ✅ 可以 ✅ 可以
1
注:如果用来修饰类,只允许public和默认修饰符!!!

十二、包

包就是文件夹用来分类管理类、防止类名冲突

1
package 包名;		//一般放到第一行

1.作用:

区分同名类:不同包里可以有一模一样的类名

分类管理:按功能分层(实体类、工具类、测试类)

控制访问权限:同包有权限可直接访问

2.导入包:

1
2
3
4
5
// 导入指定类
import java.util.Scanner;

// 导入包下所有类
import java.util.*;

3.命名规范

全部小写

公司域名倒着写

例:com.baidu.项目名.模块名

十三、面向对象三大特性

面向对象三大特征:封装、继承、多态!!!

1.封装

属性(成员变量)行为(成员方法) 捆绑在一个类中,隐藏内部实现细节,只对外暴露安全的访问接口。

三大作用:

隐藏细节:内部数据和实现逻辑对外不可见

安全控制:禁止外部随意篡改属性,可做校验、限制

代码复用 + 易维护:内部修改不影响外部调用

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
public class Person {
// 1. 私有属性设置
private String name;
private int age;

// 2. get/set 方法
public String getName() {
return name; //get获取值
}

public void setName(String name) {
this.name = name; //set设置值,可以在方法做一些判断
}


public int getAge() {
return age;
}

// 封装好处:赋值时做校验
public void setAge(int age) {
if (age > 0 && age < 150) {
this.age = age;
} else {
System.out.println("年龄不合法");
}
}
}

2.继承

继承:一个类复用另一个类的属性和方法,子类拥有父类所有非私有成员。

  • 父类(超类 / 基类):被继承的类
  • 子类(派生类):继承别人的类

核心作用:

代码复用:不用重复写重复代码

便于扩展:在父类基础上新增自己的功能

为多态打基础

语法:

1
class 子类 extends 父类 {}
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
// 父类
class Animal {
public void eat() {
System.out.println("动物会吃东西");
}
}

// 子类 Dog 继承 Animal
class Dog extends Animal {
// 自己独有方法
public void bark() {
System.out.println("狗会汪汪叫");
}
}

public class Test {
public static void main(String[] args) {
Dog d = new Dog();
d.eat(); // 继承父类的方法
d.bark(); // 自己独有的方法
}
}


/*
one.父类私有属性不能在子类之间访问,也要通过公共的方法;
two.子类必须调用父类构造器,来完成父类的初始化
three.当创建子类对象时,不管子类的那个构造器,默认会先调用父类构造器,如果父类没有无参构造器,
则子类的构造器中用的super()去指定父类的哪个构造器完成对父类的初始化工作,否则编译会报错!!!!

four.如果希望指定去调用父类的某个构造器,可以显式的调用一下
five.使用构造器super()时,必须将它放到第一行!!
six. super() 和 this() 都只能放在构造器第一行,因此这两个方法不可以存在一个构造器
this() 代表本类有两个以上的构造器,此时规定先调用指向的那个带参构造方法,在调用写了this()的构造方法!

seven. java所有类都是Object的子类,Object是所有类的父类
eight.父类构造器不限于直接父类!将一直往上追溯直到Objct类!!!!
nine. 子类最多继承一个父类,java是单继承机制

*/

super关键字

1. 访问父类的成员变量

格式:super.变量名区分子类和父类重名属性

2. 访问父类的成员方法

格式:super.方法名()子类重写方法后,想调用父类原方法

3. 调用父类的构造方法

格式:super(参数)必须放在子类构造方法第一行

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
class Father {
String name = "父亲";

public void show() {
System.out.println("父类方法");
}
}

//调用父类方法和字段
class Son extends Father {
String name = "儿子";

public void test() {
System.out.println(name); // 子类自己的 name
System.out.println(super.name);// 父类的 name

show(); // 子类自己(如果重写就走子类)
super.show(); // 强制调用父类方法
}
}

//调用父类构造函数
class Father {
public Father(String name) {
System.out.println("父类有参构造");
}
}

class Son extends Father {
// 子类构造必须先调用父类构造
public Son() {
super("张三"); // 必须第一行
System.out.println("子类构造");
}
}

/*
one.如果父类的属性和方法与子类的重名,则需要使用super才能调用到父类的,反之和this一样
two. super不限于父类,爷爷类也行,但如果父类有,就近原则!!!
*/

方法重写

又叫方法覆盖,子类根据父类继承的方法进行重新编写 重写时可以用super.方法来保留父类的方法

父类的构造方法无法重写!!!!!!

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
class Animal {
public void eat() {
System.out.println("动物吃东西");
}
}

class Dog extends Animal {
// 方法重写 @Override表示这个是重写了父类方法,可写可不写
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}


public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(); // 执行子类重写后的方法
}
}

/*
one.父类方法的返回值类型是Object时,子类返回值int、String等都是方法重写;但子类返回值是
Object,父类的返回值是int、String时会报错!!!!!
two.子类方法不能缩小父类方法的访问权限
public > protected > 默认 > private

*/

3.多态

父类引用指向子类对象,调用方法时执行子类重写后的逻辑

多态是方法的多态,属性没有多态,要看编译类型!

1.多态实现的三个前提(缺一不可)

  1. 必须有继承

  2. 必须有方法重写

  3. 父类引用指向子类对象

2.格式:

1
父类类型  对象名  =  new 子类();
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
class Animal {
public void eat() {
System.out.println("动物吃东西");
}
}

class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}

class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}

//测试
public class Test {
public static void main(String[] args) {
// 父类引用 指向 子类对象
Animal a1 = new Dog();
Animal a2 = new Cat();

a1.eat(); // 狗吃骨头
a2.eat(); // 猫吃鱼
}
}

/*
one.一个对象的编译类型和运行类型可以不一致
two.编译类型在定义对象的时候,就确定了,不可以改变
three.运行类型可以变化的
four.编译类型看定义时 =号 的左边,运行类型看 =号 的右边
instanceof 判断某个变量是否为某个类的类型或者其子类型
*/

3.向下转型

子类 对象名 = (子类) 向上转型对象名; 要先创建向上转型

此时可以调用子类特有的方法,父类的方法也可以调用!!!

注:向上转型对象名必须指向向下转型的子类!!!!!!

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
class Animal {
public void eat() {
System.out.println("动物吃东西");
}
}

class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}

class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}

//测试
public class Test {
public static void main(String[] args) {
// 父类引用 指向 子类对象
Animal a1 = new Dog();
Animal a2 = new Cat();
Dog d = (Dog) a2; //向下转型,强制

a1.eat(); // 狗吃骨头
a2.eat(); // 猫吃鱼
}
}

4.java动态绑定机制(超重点)

程序运行时,根据对象的运行实际类型,决定调用哪个重写后的方法,而不是根据引用的编译类型。

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
//父类
public class Father {
/// 动态绑定机制
public int i = 10;

public int getI(){
return i;
}

public int sum(){
return getI() + 10;
}

public int sun1(){
return i;
}
}


//子类
public class Son extends Father{
public int i = 20;

public int getI(){
return i;
}

//public int sum(){
// return i + 20;
//}

public int sun1(){
return i + 10;
}
}

//测试
public class Test {
public static void main(String[] args) {
Father f = new Son();
//System.out.println(f.sum()); //40
//System.out.println(f.sun1()); //30

//此时将 Son的sum方法注销,sum输出什么??
System.out.println(f.sum()); //30
/**
流程分析:
1.会先检查sum()子类是否有,如果没有调用父类
2.父类sum()方法里面有getI(),此时也会检查子类是否有getI(),如果有调用子类的getI()
3.此时父类的 sum() 调用子类的 getI() -> i = 20 此时输出 20 + 10 = 30
**/
}
}

//注意事项: 方法有对象动态绑定机制,属性没有

5.Object类

java.lang.Object 是 Java 中所有类的根父类

任何类,默认直接 / 间接继承 Object

所有对象、数组,都可以用 Object 引用接收

自带方法:

方法名 修饰符 作用功能 重点考点
toString() public 对象转为字符串输出 默认:类名 @哈希值,必须重写
equals(Object obj) public 判断对象是否相等 默认和 == 一样比地址,比内容要重写
hashCode() public 返回对象哈希码 int 值 重写 equals必须同步重写hashCode
getClass() public final 获取对象运行时真实类型 不能重写,反射基础,区分编译 / 运行类型
clone() protected 克隆对象(浅拷贝) 需实现Cloneable接口,默认浅克隆
finalize() protected 垃圾回收前回调 JDK9 + 废弃,现在不用
wait() public final 线程等待,释放锁 必须在synchronized中调用
wait(long timeout) public final 限时等待 线程通信
wait(long, int) public final 精确纳秒级限时等待 极少用
notify() public final 随机唤醒一个等待线程 同步锁内使用
notifyAll() public final 唤醒所有等待线程 同步锁内使用

注:只要创建的对象都可以直接使用这些方法!!!!!

十四、面向对象高级内容

1.静态变量

static 修饰的成员变量,整个类只有一份,所有对象共享这一个值。

特点:

属于,不属于某个对象

内存中只存一份,所有对象共用

可以通过 类名.变量名 直接访问

常用于:常量、计数器、共享配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Student {
// 静态变量:所有学生共享学校名
static String school = "第一中学";

String name; // 普通变量:每个学生自己的名字
}

public class Test {
public static void main(String[] args) {
// 不用new对象,直接用类名访问静态变量
System.out.println(Student.school);

Student s1 = new Student();
Student s2 = new Student();

// 修改静态变量,所有对象都会变
Student.school = "第二中学";
System.out.println(s1.school); // 第二中学
System.out.println(s2.school); // 第二中学
}
}

//类名.变量名 来访问,不需要new一个对象才能使用,因为类还没加载的时候静态变量就加载好了!!!

2.静态方法

static 修饰的方法,属于类,不属于对象

特点:

可以通过 类名.方法名 () 直接调用

不能直接访问普通成员变量 / 普通方法(因为没有对象)

只能访问静态变量静态方法

常用于:工具方法(Math、Arrays)、主方法 main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Calc {
// 静态方法:工具方法,不需要对象
public static int add(int a, int b) {
return a + b;
}
}

public class Test {
public static void main(String[] args) {
// 直接用类名调用,不用new
int sum = Calc.add(3,5);
System.out.println(sum); // 8
}
}

3. main方法

1
2
3
4
main方法是java虚拟机调用的,所以必须是public访问权限。
main方法调用不会创建对象,所以方法必须是static(静态方法)
该方法接收String类型的数组参数,该数组保存执行java命令时传递给运行时的类的参数。
所以格式:public static void main(String[] args){}

4.代码块

代码块又称初始化块,属于类中的一员,类似于方法,将逻辑语句封装在方法体里面,通过{}包裹起来

但和方法不同,没有方法名、返回、参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,

或创建类时隐式调用!!!

普通代码块

1
2
3
4
5
6
7
8
9
10
public class Demo {
public static void main(String[] args) {
// 普通代码块
{
int a = 10;
System.out.println(a);
}
// System.out.println(a); // 报错,a 出了块就失效
}
}

构造代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person {
// 构造代码块
{
System.out.println("构造代码块执行");
}

// 构造方法
public Person() {
System.out.println("构造方法执行");
}

public Person(int age){
System.out.println("构造方法执行");
}

//每次 new 对象,先执行构造代码块 → 再执行构造方法 所有构造函数都会
}

静态代码块

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
class Person {
// 静态代码块
static {
System.out.println("静态代码块执行");
}

{
System.out.println("构造代码块执行");
}

public Person() {
System.out.println("构造方法执行");
}

public Person(int age){
System.out.println("构造方法执行");
}

//类加载时只执行一次,只跑一遍
}


/*
one.子类继承父类,都有各自的静态代码块,双方的代码块都会被加载,而且会先输出双方代码块!!
two.同一个类无论创建多少个对象,静态代码块只会执行一次,但普通代码块不会!!
three.构造器其实前面隐藏了super()和调用普通代码块,而静态代码块是在类加载之前就加载好了的
所以优先于构造器和普通代码块!!!
four.执行顺序:父静态 -> 子静态 -> 父普通 -> 父构造 -> 子普通 -> 子构造 */

5. final关键字

可以修饰类、属性、方法和局部变量 使得不可变

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 void test() {
final int a = 10;
// a = 20; // 报错,不能二次赋值
}

/*
one.当类不希望被继承时,使用final关键字;
two.当不希望父类的某个方法被自己覆盖/重写时,使用final关键字;
three.当不希望类的某个属性的值被修改,可以使用final关键字;
four.当不希望某个局部变量被修改,使用final关键字;

细节:
one. final修饰的属性又叫 常量,一般用 XX_XX_XX 命名 如:FIND_INDEX
two. find修饰的属性必须赋值,否则报错 规则:定义中赋值; 在构造器赋值; 在代码块赋值;
three.如果final是给静态变量赋值, 规则:在定义中赋值; 在静态代码块赋值; 不可以在构造器赋值!!!
four. final类不可以继承,但可以实例化!!!
five. final方法 虽然无法重写,但是子类还是可以继承这个方法!!!
six. 如果这个类被final关键字修饰了,就没有必要在创建final方法了,因为无法被继承!!!
seven. final关键字无法修饰构造器!!
eight. final和static 往往搭配使用,效率更高;不会导致被类加载
nine. 包装类(Integer,Double,Float,Boolean等都是final),String也是final类!!!!


*/

6.抽象类

抽象:指从具体事物抽出、概括它们共同的方面、本质特征

抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
abstract class 类名 {

}

/*
抽象类不能 new 对象
抽象类里可以有普通方法 + 抽象方法
有抽象方法的类,必须是抽象类
子类继承抽象类,必须重写所有抽象方法
抽象类可以有构造方法(给子类初始化用)
抽象类可以有成员变量、静态方法

one.抽象类可以没有抽象方法!!
two.抽象类不可以实例化!!!
three. abstract 只能修饰方法,不可以修饰属性和其他的!!
four.抽象类也是类,可以有静态变量、非抽象方法、构造器等!!
five.一个类继承抽象类,它必须实现抽象方法,否则也是一个抽象类!!!
six.抽象方法不能使用private、final、static来修饰,因为这些都是和抽象重写相违背的!!!!!!!
*/

抽象方法

1
2
// 抽象方法:没有 {} 方法体
public abstract void eat();
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
// 抽象类
abstract class Animal {
// 抽象方法:没有方法体
public abstract void eat();

// 普通方法
public void sleep() {
System.out.println("动物睡觉");
}
}

class Dog extends Animal {
// 必须重写父类所有抽象方法
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}

public class Test {
public static void main(String[] args) {
// Animal a = new Animal(); // 报错,抽象类不能实例化

Animal a = new Dog();
a.eat();
a.sleep();
}
}

7.接口

接口是多个类的公开规范,接口是引用数据类型,最重要的内容就是其中的抽象方法

接口时比抽象类更抽象的抽象类,规范了子类的约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface 接口名 {

}

/*
接口不能 new 对象
接口里都是抽象方法(JDK7)
类用 implements 实现接口
实现类必须重写所有抽象方法
一个类可以多实现(弥补 Java 单继承缺点)
接口可以多继承
*/

//实现
public class 类名 implements 接口名{}
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
interface Animal {
// 常量:默认 public static final
String TYPE = "生物";

// 抽象方法:默认 public abstract
void eat();

// JDK8 默认方法
default void sleep() {
System.out.println("动物睡觉");
}

// JDK8 静态方法
static void show() {
System.out.println("接口静态方法");
}
}

//实现类
class Dog implements Animal {
// 必须重写抽象方法
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}

public class Test {
public static void main(String[] args) {
Animal a = new Dog();
a.eat();
a.sleep();

// 接口静态方法 只能接口名调用
Animal.show();
}
}

/*
one.抽象类实现接口,可以不实现接口的抽象类!!
abstract class 类名 implemnts 接口名称{}
*/

8.内部类

类的五大成员[属性、方法、构造器、代码块、内部类],内部类的特点就是可以直接访问私有属性,并且可以体现

类与类之间的包含关系 注:内部类是学习的难点,同时也是重点,后面看底层代码时,有大量的内部类!!!

1.成员内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Outer {
// 成员内部类
class Inner {
public void show() {
System.out.println("成员内部类");
}
}
}

//实现
public static void main(String[] args){
// 格式:外部类.内部类 对象 = 外部对象.new 内部类();
Outer.Inner in = new Outer().new Inner();
in.show();
}

/*
one.可以直接访问外部类的私有方法,包括私有
two.可以添加任意访问修饰符(public、protected、默认、private),因为本身就是个成员!!
three.如果外部类和内部类的成员重名时,则使用this关键字访问外部类成员 格式:外部类名.this.属性
*/

2.静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Outer {
// 静态内部类
static class Inner {
public void show() {
System.out.println("静态内部类");
}
}
}

//实现
public static void main(String[] args){
// 不用new外部类
Outer.Inner in = new Outer.Inner();
in.show();
}

/*
one.可以直接访问外部类所有的静态成员,包括私有的,但不能直接访问非静态成员!!!
two.可以添加任意访问修饰符(public、protected、默认、private),因为本身就是个成员!!
three.作用域:同其他成员,为一个整体
four.如果外部类和内部类的成员重名时, 如果访问外类成员时,格式:外部类名.成员
*/

3.局部内部类

定义在方法里、代码块里

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
class Outer {
public void test() {
// 局部内部类
class Inner {
public void show() {
System.out.println("局部内部类");
}
}
// 只能在当前方法里使用
new Inner().show();

/*
作用域仅限当前方法
不能用 public/private/static 修饰
可以访问方法里 final / 有效 final 局部变量
*/
}
}


/*
one.可以直接访问外部类的私有方法,包括私有
two.不能添加访问修饰符,因为本身就是局部变量,局部变量不可以使用修饰符,但可以使用final修饰
three.作用域:仅仅在定义它的方法或代码块中
four.如果外部类和内部类的成员重名时,则使用this关键字访问外部类成员 格式:外部类名.this.属性
*/

4.匿名内部类

没有类名,直接 new 接口 / 抽象类 并当场实现

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
interface Animal {
void eat();
}


public class Test {
public static void main(String[] args) {
// 匿名内部类
//传统写法
Animal a = new Animal() {
@Override
public void eat() {
System.out.println("小狗吃骨头");
}
};
a.eat();


//箭头函数 接口只能有一个抽象方法才能使用!!!!
Aniaml b = () -> System.println.out("小喵吃小鱼");
b.eat();
}
}


/*
one.本质是类、该类没有名字、同时还是一个对象 getclass() 查看运行类型
two.匿名内部类定义在外部类的局部位置,如方法中。
three.语法:new 类 或 接口(参数列表){}; 它的运行类型是这个类或接口,但编译类型是 匿名内部类!!!!
four.匿名内部类使用一次就不能使用了!!!
five.匿名内部类主要使用是当实参一样进行传递!!
*/

十五、枚举类

枚举类是用来定义固定、有限、不变的常量集合的特殊类(比如:性别、季节、订单状态、颜色、星期)。

简单说:当一个变量只有几种固定可选值时,用枚举类最规范、最安全。

特点

实例有限且固定,不能随意创建对象

枚举常量默认 public static final

自带常用方法:values(), valueOf(), name(), ordinal()

比常量(public static final)更易读、更安全、更易维护

格式

1
2
3
4
// 季节枚举
public enum Season {
SPRING, SUMMER, AUTUMN, WINTER; //只定义常量(大写)
}
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 enum OrderEnum {
// 1.枚举常量(必须写在最前面)
PENDING(1, "待支付"),
PAID(2, "已支付"),
SHIPPED(3, "已发货"),
COMPLETED(4, "已完成"),
CANCELLED(5, "已取消");

// 成员变量
private final int code;
private final String desc;

// 构造方法(默认 private,不能写 public)
OrderEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}

// getter
public int getCode() {
return code;
}

public String getDesc() {
return desc;
}

}

/*
全流程:
one.将构造器私有化
two.在类内部创建一组对象(添加 public final static 修饰符)
three.对外暴露
four.可以提供get方法,不要提供Set方法!!

eunm类不可以继承其他类,因为已经隐式继承Enum,而java是单继承机制!! 但可以实现接口
*/

自带方法

1
2
3
4
5
6
7
//遍历所有实例名   .values()
for (OrderStatus status : OrderStatus.values()) {
System.out.println(status.name() + ":" + status.getDesc());
}

//根据名称获取枚举 .valueOf('名称')
OrderStatus status = OrderStatus.valueOf("PAID");

十六、注解

注解就是给类、方法、变量、参数 打标签、加标记。本身不写业务逻辑,只做元数据标记

给编译器、框架(Spring、MyBatis)看。

语法

1
2
@Override		//@注解名
public void test(){}

相关注解

注解 作用 适用场景
@Override 校验方法重写,签名不对编译报错 重写父类 / 接口方法
@Deprecated 标记类、方法、字段已过时 旧版本兼容、废弃接口
@SuppressWarnings 压制编译器警告 去掉黄色波浪线警告

元注解(用于自定义注解)

定义注解的注解,4 个必记

元注解 作用 记忆
@Target 限定注解能写在哪 标记位置
@Retention 限定注解保留周期 标记存活时间
@Documented 生成 JavaDoc 文档时带上注解 文档可见
@Inherited 子类自动继承父类的注解 可被继承

@Target 可选取值(全列表)

取值 可标注位置
TYPE 类、接口、枚举、注解
FIELD 成员变量、常量
METHOD 普通方法
PARAMETER 方法参数
CONSTRUCTOR 构造方法
LOCAL_VARIABLE 局部变量
ANNOTATION_TYPE 注解类型上
PACKAGE 包上
TYPE_PARAMETER 泛型参数
TYPE_USE 任意类型使用处

@Retention 三档

级别 存活阶段 场景
SOURCE 仅源码,编译后丢弃 @Override
CLASS 保留到 class 文件,运行时丢失 默认级别
RUNTIME 运行时可反射读取 Spring、自定义注解必用

自定义注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//方法、类、字段
@Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
//value() 不是方法,是字段!!!!
String values(); ///细节:可写可不写,自带!!!!
int num() default 100;
}


public class Test {
@MyAnnotation(values = "你好",num = 12)
public void hello(){
//上面的值必须通过反射拿到!!!!
System.out.println("hello");
}
}

十七、包装类

Java 为8 种基本数据类型,分别提供了一个对应的引用类型,就是包装类

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

1.装箱

1
2
int a = 10;
Integer num = Integer.valueOf(a); // 手动装箱 将基本数据类型转为引用数据类型

2.拆箱

1
int b = num.intValue(); // 手动拆箱

3.自动装箱/拆箱(JDK1.5)

1
2
3
4
5
// 自动装箱
Integer x = 100;

// 自动拆箱
int y = x;

4.通用方法(所有的包含这些)

Integer专属方法

常量 含义
Integer.MAX_VALUE int 最大值 2147483647
Integer.MIN_VALUE int 最小值 -2147483648
Integer.SIZE 占位数 32 位
Integer.BYTES 占用字节 4 字节
parseInt(String s) 字符串转 int
parseInt(String s,int radix) 指定进制字符串转十进制 int
valueOf(int i) int 转 Integer(走缓存)
valueOf(String s) 字符串转 Integer
toBinaryString(int i) 转二进制字符串
toOctalString(int i) 转八进制字符串
toHexString(int i) 转十六进制字符串
compare(int a,int b) 比较两个 int 大小
max(int a,int b) 取最大值
min(int a,int b) 取最小值
sum(int a,int b) 求和
1
2
3
4
5
6
7
8
9
10
11
public class integer {
public static void main(String[] args) {
String s = "13";

int parseInt = Integer.parseInt(s); //字符串转数字
Integer valueOf = Integer.valueOf(s); //转包装类数字
int compare = Integer.compare(20, 19);//对比大小 大于返回1 等于返回0 小于返回-1
System.out.println(compare);
System.out.println(Integer.sum(12, 29)); //求和
}
}

Character

常量 作用
Character.MAX_VALUE char 最大值
Character.MIN_VALUE char 最小值
isDigit(char ch) 判断是不是数字
isLetter(char ch) 判断是不是字母
isLetterOrDigit(ch) 判断字母或数字
isUpperCase(ch) 是否大写字母
isLowerCase(ch) 是否小写字母
toUpperCase(ch) 转大写
toLowerCase(ch) 转小写
isWhitespace(ch) 是否空格 / 空白字符

Boolean

方法 作用
parseBoolean(String s) 字符串转 boolean
valueOf(boolean b) boolean 转 Boolean
valueOf(String s) 字符串转 Boolean
booleanValue() Boolean 拆箱为 boolean
equals(Object obj) 比较是否同为 true/false

Double

方法 作用
parseBoolean(String s) 字符串转 boolean
valueOf(boolean b) boolean 转 Boolean
valueOf(String s) 字符串转 Boolean
booleanValue() Boolean 拆箱为 boolean
equals(Object obj) 比较是否同为 true/false

十八、字符串类

1. String

String 是不可变字符序列

底层 JDK8 及以前:private final char[] value

JDK9+:改成 private final byte[] value 节省空间

被 final 修饰:不能被继承

一旦创建,内容不能修改,每次拼接都是新建新字符串

1.创建方式

1
2
3
4
5
// 方式1:字面量赋值
String s1 = "abc";

// 方式2:new 对象
String s2 = new String("abc");

2.相关方法

方法分类 方法签名 作用说明
获取长度 int length() 返回字符串字符个数
获取指定字符 char charAt(int index) 根据下标获取单个字符
查找索引 int indexOf(String str) 从头找子串,返回首次索引,找不到返回 -1
反向查找 int lastIndexOf(String str) 从后往前找子串,返回最后一次索引
截取子串 String substring(int beginIndex) 从起始下标截取到末尾
截取子串 String substring(int begin, int end) 左闭右开 [begin,end) 区间截取
内容比较 boolean equals(Object obj) 比较字符串内容,区分大小写
忽略大小写比较 boolean equalsIgnoreCase(String another) 忽略大小写比较内容
开头判断 boolean startsWith(String prefix) 判断是否以指定前缀开头
结尾判断 boolean endsWith(String suffix) 判断是否以指定后缀结尾
包含判断 boolean contains(CharSequence s) 判断是否包含某个子串
判空 boolean isEmpty() 判断是否为空字符串 ""
转大写 String toUpperCase() 所有字母转大写
转小写 String toLowerCase() 所有字母转小写
转字符数组 char[] toCharArray() 字符串转为 char 数组
任意类型转字符串 static String valueOf(任意类型) 基本类型 / 对象 转字符串
去除首尾空格 String trim() 只去掉首尾空格,中间不动
替换字符 String replace(char old, char new) 替换所有匹配的字符
替换子串 String replace(CharSequence old, CharSequence new) 替换所有匹配的子串
分割字符串 String[] split(String regex) 按正则规则分割成字符串数组
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
72
73
74
75
76
public class StringDemo {
public static void main(String[] args) {
String s = "HelloJava";
String str = " Java Study ";

// 1. length() 长度
System.out.println(s.length());

// 2. charAt(index) 按索引取字符
System.out.println(s.charAt(0));

// 3. indexOf 查找首次出现位置
System.out.println(s.indexOf("Java"));

// 4. lastIndexOf 最后一次出现位置
System.out.println(s.lastIndexOf("a"));

// 5. substring 从下标截取到末尾
System.out.println(s.substring(5));

// 6. substring 左闭右开 [start,end)
System.out.println(s.substring(0, 5));

// 7. equals 内容比较 区分大小写
System.out.println("Hello".equals("hello"));

// 8. equalsIgnoreCase 忽略大小写比较
System.out.println("Hello".equalsIgnoreCase("hello"));

// 9. startsWith 是否以指定前缀开头
System.out.println(s.startsWith("He"));

// 10. endsWith 是否以指定后缀结尾
System.out.println(s.endsWith("va"));

// 11. contains 是否包含子串
System.out.println(s.contains("ava"));

// 12. isEmpty 是否为空字符串 ""
System.out.println("".isEmpty());
System.out.println(s.isEmpty());

// 13. toUpperCase 转大写
System.out.println(s.toUpperCase());

// 14. toLowerCase 转小写
System.out.println(s.toLowerCase());

// 15. toCharArray 转字符数组
char[] arr = s.toCharArray();
for (char c : arr) {
System.out.print(c + " ");
}
System.out.println();

// 16. String.valueOf 任意类型转字符串
String numStr = String.valueOf(12345);
System.out.println(numStr);

// 17. trim 去除首尾空格
System.out.println(str.trim());

// 18. replace 替换字符
System.out.println(s.replace('a', 'A'));

// 19. replace 替换子串
System.out.println(s.replace("Java", "Java编程"));

// 20. split 分割字符串
String line = "a,b,c,d";
String[] splitArr = line.split(",");
for (String item : splitArr) {
System.out.print(item + " ");
}
}
}

2. StringBuffer

String保存的是字符串的常量,里面的值不可以改变,每次String类的更新实际上是更改地址,效率较低

而StringBuffer保存的是字符串变量,里面的值可以更改,不会变动地址!!!放在 char[] value里面

1.StringBuffer的直接父类是 AbatractStringBuilder;

2.StringBuffer实现了 Serializable,则StringBuffer的对象可以串行化(序列化)

3.在父类中 AbatractStringBuilder 有属性 char[] value,不是final类型!!!!

4.StringBuffer是一个final类,不可以被继承

1.创建

构造方法 作用
StringBuffer() 空构造,默认容量 16
StringBuffer(int capacity) 指定初始容量
StringBuffer(String str) 用字符串初始化,容量 = 字符串长度 + 16

2.相关方法

方法 作用
append(任意类型) 末尾追加(最常用)
delete(int start,int end) 删除 [start,end) 区间字符
deleteCharAt(int index) 删除指定下标单个字符
insert(int offset, 数据) 在指定位置插入内容
replace(int start,int end,String str) 替换区间内容
reverse() 字符串反转
length() 返回实际字符长度
capacity() 返回底层数组容量
substring(int start) 截取子串,返回 String
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
public class StringBufferDemo {
public static void main(String[] args) {
// 1. 创建对象
StringBuffer sb = new StringBuffer("abc");

// 2. 末尾追加 append
sb.append("123");
System.out.println("追加后:" + sb);

// 3. 插入 insert(下标, 内容)
sb.insert(3, "XYZ");
System.out.println("插入后:" + sb);

// 4. 替换 replace(起始,结束,新串)
sb.replace(0, 3, "AAA");
System.out.println("替换后:" + sb);

// 5. 删除区间 delete
sb.delete(3, 6);
System.out.println("删除区间:" + sb);

// 6. 删除指定下标字符
sb.deleteCharAt(0);
System.out.println("删除下标0:" + sb);

// 7. 反转 reverse
sb.reverse();
System.out.println("反转后:" + sb);

// 8. 长度 和 容量
System.out.println("实际长度:" + sb.length());
System.out.println("底层容量:" + sb.capacity());
}
}

3. StringBuilder

一个可变的字符序列。此类提供和StringBuffer兼容的API,但不保证同步(存在线程安全问题)。

该类被设置用在StringBuffer的简易替换,用在字符串缓冲区被单个线程使用的时候。如果可以,建议先采取该类

因为他比StringBuffer快。他为单线程,一个线程在工作,其他的会暂停!!!

1.Stringilder的直接父类是 AbatractStringBuilder;

2.StringBuilder实现了 Serializable,则StringBuffer的对象可以串行化(序列化)

3.StringBuilder是一个final类,不可以被继承

4.在父类中 AbatractStringBuilder 有属性 char[] value

5.主要操作时append和insert,可以重载这些方法,以接受各种数据类型;

1.创建

构造方法 作用 默认容量
StringBuilder() 空构造 初始容量 16
StringBuilder(int capacity) 指定初始容量 自定义
StringBuilder(String str) 用字符串初始化 字符串长度 + 16

2.相关方法

方法 作用
append(任意类型) 末尾追加(最常用)
insert(int offset, 数据) 指定下标插入内容
delete(int start, int end) 删除 [左闭右开) 区间
deleteCharAt(int index) 删除指定下标单个字符
replace(int start,int end,String str) 替换区间内容
reverse() 字符串反转
length() 实际字符长度
capacity() 底层数组容量
substring(int start) 截取,返回 String
substring(int start,int end) 区间截取
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 class StringBuilderTest {
public static void main(String[] args) {
// 初始化
StringBuilder sb = new StringBuilder("abc");

// 1. 追加
sb.append(123);
System.out.println("追加:" + sb);

// 2. 插入 指定位置插
sb.insert(3, "XYZ");
System.out.println("插入:" + sb);

// 3. 替换 区间 [start,end)
sb.replace(0, 3, "AAA");
System.out.println("替换:" + sb);

// 4. 删除区间
sb.delete(3, 6);
System.out.println("删区间:" + sb);

// 5. 删除单个下标字符
sb.deleteCharAt(0);
System.out.println("删下标0:" + sb);

// 6. 反转
sb.reverse();
System.out.println("反转:" + sb);

// 7. 长度 vs 容量
System.out.println("实际长度:" + sb.length());
System.out.println("底层容量:" + sb.capacity());

// 8. 截取 返回 String
String sub = sb.substring(1);
System.out.println("截取子串:" + sub);
}
}

4.三者区别

String:不可变字符序列,效率低,但复用率高

StringBuffer: 可变字符序列,效率较高,线程安全

StringBuilder: 可变字符序列,效率最高,线程不安全

十九、Math类

Math类包含执行基本数学运算的方法,如初等函数等。

所有方法

常量 含义
Math.PI 圆周率 3.14159265358979
Math.E 自然常数 2.71828182845905
方法 作用
Math.abs(数值) 绝对值
Math.max(a,b) 取两个数最大值
Math.min(a,b) 取两个数最小值
Math.ceil(double) 向上取整 往大数靠
Math.floor(double) 向下取整 往小数靠
Math.round(double) 四舍五入 长整型
Math.pow(a,b) 求 a 的 b 次幂
Math.sqrt(double) 平方根
Math.cbrt(double) 立方根
Math.random() 随机数 [0.0, 1.0)
Math.sin/cos/tan(弧度) 三角函数
Math.log(double) 自然对数
Math.log10(double) 以 10 为底对数
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
public class MathDemo {
public static void main(String[] args) {
// 常量
System.out.println("圆周率:" + Math.PI);
System.out.println("自然常数:" + Math.E);

// 绝对值
System.out.println("绝对值:" + Math.abs(-9));

// 最大最小
System.out.println("最大值:" + Math.max(10, 20));
System.out.println("最小值:" + Math.min(10, 20));

// 取整
System.out.println("向上取整 3.2:" + Math.ceil(3.2));
System.out.println("向下取整 3.8:" + Math.floor(3.8));
System.out.println("四舍五入 3.5:" + Math.round(3.5));

// 幂、开方
System.out.println("2的3次方:" + Math.pow(2,3));
System.out.println("16平方根:" + Math.sqrt(16));
System.out.println("8立方根:" + Math.cbrt(8));

// 随机数
System.out.println("随机数[0,1):" + Math.random());
}
}

二十、System类

提供系统级工具方法与静态字段,用于标准 I/O、数组拷贝、时间获取、系统属性 / 环境变量读取、JVM 控制等

相关方法

常量 类型 作用
System.in InputStream 标准键盘输入流
System.out PrintStream 标准控制台正常输出流
System.err PrintStream 标准控制台错误输出流(红色)
方法签名 功能说明 常用场景
arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 高效数组拷贝,native 底层 数组批量复制、集合底层扩容
currentTimeMillis() 获取当前时间毫秒时间戳(1970 至今) 生成时间戳、计算业务耗时、日期转换
nanoTime() 获取高精度纳秒相对时间 代码性能测速、精确计时
exit(int status) 终止当前 JVM 进程 程序主动退出、异常终止
gc() 建议 JVM 执行垃圾回收 手动提醒 GC 释放内存
getProperty(String key) 获取 JVM / 系统属性 读取系统版本、项目路径、编码
getProperty(String key, String defaultValue) 获取系统属性,取不到给默认值 防止属性为空报错
getenv(String name) 获取操作系统环境变量 读取 PATH、自定义环境变量

二十一、BigInteger和BigDecimal类

BigInteger:超大整数,超出 long 范围也能存,无上限

BigDecimal:超大小数,解决 float/double 浮点精度丢失问题

创建

1
2
3
4
5
6
7
// 常用构造
new BigInteger("数字字符串")
BigInteger.valueOf(普通整数)


new BigDecimal("小数字符串") // 推荐,无精度丢失
BigDecimal.valueOf(普通小数)

相关方法

方法 作用
add() 加法
subtract() 减法
multiply() 乘法
divide() 除法
remainder() 取余
abs() 绝对值
negate() 取相反数

仅BigInteger

方法 作用
pow(int n) 幂运算
gcd() 最大公约数
isPrime() 判断是否质数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BigInteger a = BigInteger.valueOf(100);
BigInteger b = new BigInteger("999999999999999999999");

BigInteger res = a.add(b); // 加
res = a.subtract(b); // 减
res = a.multiply(b); // 乘



BigDecimal d1 = new BigDecimal("0.1");
BigDecimal d2 = new BigDecimal("0.2");

BigDecimal sum = d1.add(d2);
// 保留2位小数,四舍五入
BigDecimal res = sum.setScale(2, RoundingMode.HALF_UP);

二十二、日期类

1.第一代日期类(date)

方法签名 功能说明
Date() 创建当前系统日期时间
Date(long) 根据毫秒值创建日期
getTime() 返回时间毫秒值
setTime() 设置时间毫秒值
方法签名 功能说明
SimpleDateFormat(String) 构造方法,传入日期格式模板
format(Date) 日期对象 → 格式化字符串
parse(String) 日期字符串 → 解析为 Date 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ParseDemo {
public static void main(String[] args) throws ParseException {
String str = "2025-12-01 18:30:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

// 字符串转Date
Date date = sdf.parse(str);
System.out.println(date);
}
}

2.第二代日期类(Calendar)

Calendar本身是一个抽象类,它为特定瞬间与一组YEAR、MONTH等日历字段之间的转换提供了方法。

Calender是一个抽象类,并且构造器是private,无法直接new一个;可以通过 getInstance() 获取!!!!

方法签名 功能说明
getInstance() 获取日历实例对象
get(field) 获取年、月、日、时、分、秒
set(field,value) 设置指定时间字段
add(field,amount) 对时间进行加减
getTime() Calendar 转为 Date
setTime(Date) Date 转为 Calendar

3.第三代日期类

1.创建日期

写法 作用
LocalDate.now() 获取当前 年月日
LocalDateTime.now() 获取当前 年月日时分秒
LocalTime.now() 获取当前 时分秒
LocalDate.of (年,月,日) 手动指定固定年月日
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class localDate {
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();
System.out.println(now); //2026-05-02T20:28:29.781018500

LocalDate now1 = LocalDate.now();
System.out.println(now1); //2026-05-02

LocalTime now2 = LocalTime.now();
System.out.println(now2); //20:29:45.813228700

LocalDate localDate = LocalDate.of(2025, 12, 6);
System.out.println(localDate); //2025-12-06
}
}

2.修改时间(with 系列)

写法 作用
withYear() 修改年份
withMonth() 修改月份
withDayOfMonth() 修改日期
1
2
3
4
5
LocalDate localDate = LocalDate.of(2025, 12, 6);
System.out.println(localDate); //2025-12-06

LocalDate localDate1 = localDate.withYear(2026); //修改年份
System.out.println(localDate1);

3.增加、减少时间(plus /minus)

分类 方法 作用
增加 plusYears()、plusMonths()、plusDays() 加年、加月、加天
减少 minusYears()、minusMonths()、minusDays() 减年、减月、减天

4.时间比较

方法 作用
isAfter() 判断当前时间是否在之后,返回 boolean
isBefore() 判断当前时间是否在之前,返回 boolean

5.格式化时间

步骤 写法 作用
定义格式 DateTimeFormatter.ofPattern (“格式”) 自定义日期格式模板
格式化 时间对象.format (格式化器) 日期时间转字符串
1
2
3
4
5
6
7
8
9
LocalDateTime now = LocalDateTime.now();
System.out.println(now); //2026-05-02T20:28:29.781018500

//时间格式化
DateTimeFormatter dateTimeFormatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

String format = now.format(dateTimeFormatter);
System.out.println(format); //2026-05-02 20:38:41

6.时间戳

写法 说明
Instant.now() 获取当前时间戳
with()、plus()、minus() 可修改、加减只支持:秒、毫秒、纳秒

7.计算时间差

用法 说明
Duration.between (时间 1, 时间 2) 计算两个 LocalDateTime 时间间隔
toDays()、toSeconds()、toMinutes() 获取相差天数、秒数、分钟数可精确到:日、时、分、秒、毫秒、纳秒

8. 时区 ZonedDateTime

写法 作用
ZoneId.getAvailableZoneIds() 获取系统所有时区名称
ZonedDateTime.now(Clock.systemUTC()) 获取世界标准 UTC 时间
ZonedDateTime.now() 获取电脑默认时区时间
ZonedDateTime.now (ZoneId.of (“时区名”)) 指定任意时区创建时间

二十三、集合

前面我们使用了数组,但数组也有不足的地方,缺点:

1.长度必须开始时指定,一旦指定就无法修改;
2.保存必须为同一类型的元素;
3.使用数组增加元素比较繁琐

集合的好处:

1.可以动态保存任意多个对象,使用比较方便;2.提供了一系列方法如:add、remove等;3.添加、删除元素更简洁

集合的形式:

Collection 接口有两个重要的子接口 List Set,他们实现的子类都是单列集合
Map 接口的实现子类,是双列集合,存放的 k-v(键值 和python字典类似)

1. Collection

常用方法:

方法名 作用 返回值
add(E e) 添加单个元素 boolean
addAll(Collection<?> c) 批量添加另一个集合所有元素 boolean
clear() 清空集合所有元素 void
contains(Object o) 判断是否包含指定元素 boolean
containsAll(Collection<?> c) 判断是否包含另一个集合所有元素 boolean
isEmpty() 判断集合是否为空 boolean
remove(Object o) 删除指定单个元素 boolean
removeAll(Collection<?> c) 删除和另一个集合的交集元素 boolean
retainAll(Collection<?> c) 保留和另一个集合的交集,其余删除 boolean
size() 获取集合元素个数 int
toArray() 集合转 Object 数组 Object[]
toArray(T[] a) 集合转指定类型数组 T[]
iterator() 获取迭代器,用于遍历 Iterator

静态工具类

方法 作用
sort(List<T> list) 对 List 自然排序
sort(List<T>, Comparator) 自定义规则排序
reverse(List<?> list) 反转集合元素顺序
shuffle(List<?> list) 随机打乱顺序(洗牌)
max(Collection) 取集合最大值
min(Collection) 取集合最小值
binarySearch(List, key) 二分查找(必须先排序
addAll(集合, 元素1,元素2...) 批量添加元素
emptyList() / emptySet() / emptyMap() 返回空不可变集合
synchronizedList/Set/Map() 转成线程安全集合
unmodifiableList/Set/Map() 转成不可变只读集合

2. List

List 是 Java Collection 旗下的子接口,属于单列集合

1. ArrayList实现类

特点:

有序、可重复、有索引

查询快、随机访问极快

中间 / 头部增删慢(要移位复制)

线程不安全,多线程不用

允许存 null

相关方法

方法名 作用 返回值
add(E e) 末尾添加单个元素 boolean
get(int index) 根据索引获取元素 E
set(int index, E e) 根据索引修改元素,返回旧值 E
remove(int index) 根据索引删除元素,返回被删元素 E
size() 获取集合元素个数 int
isEmpty() 判断集合是否为空 boolean
clear() 清空集合所有元素 void
contains(Object o) 判断是否包含指定元素 boolean
add(int index, E e) 在指定索引位置插入元素 void
indexOf(Object o) 返回元素第一次出现的索引 int
addAll(Collection c) 批量添加另一个集合所有元素 boolean
remove(Object o) 根据元素对象删除 boolean
forEach() Lambda 遍历集合 void
removeIf() 按条件批量删除元素 boolean
lastIndexOf(Object o) 返回元素最后一次出现的索引 int
subList(int from, int to) 截取子集合(左闭右开) List
toArray() 集合转为 Object 数组 Object[]
toArray(T[] a) 集合转为指定类型数组 T[]
iterator() 获取迭代器遍历 Iterator
containsAll(Collection c) 是否包含另一个集合所有元素 boolean
removeAll(Collection c) 删除两集合交集元素 boolean
retainAll(Collection c) 保留两集合交集元素 boolean
listIterator() 获取列表迭代器 ListIterator
ensureCapacity(int num) 手动预扩容,减少自动扩容 void
trimToSize() 收缩底层数组容量到实际元素个数 void
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
public class Conllections {
public static void main(String[] args) {
// 1. 创建 ArrayList(存储字符串类型)
ArrayList<String> list = new ArrayList<>();

// 2. add():添加元素
list.add("Java");
list.add("Python");
list.add("C++");
list.add(1, "Go"); // 在索引1位置插入
System.out.println("添加后:" + list);
// 输出:[Java, Go, Python, C++]

// 3. size():获取元素个数
System.out.println("元素个数:" + list.size()); // 4

// 4. get():获取指定索引元素
System.out.println("索引1元素:" + list.get(1)); // Go

// 5. set():修改元素
list.set(2, "JavaScript");
System.out.println("修改后:" + list);
// [Java, Go, JavaScript, C++]

// 6. contains():判断是否包含
System.out.println("是否包含Java:" + list.contains("Java")); // true

// 7. remove():删除元素
list.remove(0); // 删除索引0
list.remove("C++"); // 删除指定元素
System.out.println("删除后:" + list); // [Go, JavaScript]

// 8. isEmpty():判断是否为空
System.out.println("是否为空:" + list.isEmpty()); // false

// 9. 遍历集合
System.out.print("遍历结果:");
for (String s : list) {
System.out.print(s + " ");
}
// 输出:Go JavaScript

// 10. clear():清空集合
list.clear();
System.out.println("\n清空后是否为空:" + list.isEmpty()); // true
}
}

2. LinkedList实现类

特点:

底层是双向链表

增删快、查询慢(ArrayList 相反)

实现了 ListDeque 接口,可当列表、队列、栈

适合频繁插入、删除场景

独有方法(其他方法和ArrayList一致)

独有方法 功能
addFirst(E e) 头部添加元素
addLast(E e) 尾部添加元素
getFirst() 获取第一个元素
getLast() 获取最后一个元素
removeFirst() 删除并返回第一个元素
removeLast() 删除并返回最后一个元素
peekFirst() 获取第一个元素,不删除
peekLast() 获取最后一个元素,不删除
pollFirst() 取出并删除第一个元素
pollLast() 取出并删除最后一个元素
push(E e) 入栈(头部添加)
pop() 出栈(删除头部)
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
public class LinkedListDemo {
public static void main(String[] args) {
// 创建 LinkedList
LinkedList<String> link = new LinkedList<>();

// 1. 普通添加
link.add("张三");
link.add("李四");
System.out.println("普通添加:" + link);

// 2. 头部、尾部添加
link.addFirst("王五");
link.addLast("赵六");
System.out.println("首尾添加后:" + link);

// 3. 获取首尾元素
System.out.println("第一个元素:" + link.getFirst());
System.out.println("最后一个元素:" + link.getLast());

// 4. 按索引获取
System.out.println("索引2元素:" + link.get(2));

// 5. 删除首尾
link.removeFirst();
link.removeLast();
System.out.println("删除首尾后:" + link);

// 6. 判断是否包含、长度、是否为空
System.out.println("是否包含李四:" + link.contains("李四"));
System.out.println("元素个数:" + link.size());
System.out.println("是否为空:" + link.isEmpty());

// 7. 增强for遍历
System.out.print("遍历所有元素:");
for (String name : link) {
System.out.print(name + " ");
}

// 8. 清空
link.clear();
System.out.println("\n清空后是否为空:" + link.isEmpty());
}
}

3. Vector实现类

特点:

底层也是动态数组,和 ArrayList 底层结构一样

JDK1.0 古老类,最早的动态数组

默认线程安全(所有方法加了 synchronized

扩容机制和 ArrayList 不一样

所有方法和ArrayList一样

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 class VectorDemo {
public static void main(String[] args) {
// 创建 Vector
Vector<String> vec = new Vector<>();

// 添加元素
vec.add("苹果");
vec.add("香蕉");
vec.add("橙子");

System.out.println("全部元素:" + vec);

// 根据索引获取
System.out.println("索引1:" + vec.get(1));

// 修改
vec.set(2, "葡萄");
System.out.println("修改后:" + vec);

// 删除
vec.remove(0);
System.out.println("删除后:" + vec);

// 遍历
for (String s : vec) {
System.out.print(s + " ");
}

System.out.println("\n元素个数:" + vec.size());
System.out.println("是否为空:" + vec.isEmpty());
}
}

3. Set

1.set接口是无序的,没有索引!!!

2.不允许重复元素!!(类似于python的集合)

3.和List接口一样,set也是Collection的子接口,因此,常用方法和Collection一样!!

4.遍历:1.可以使用迭代器;2.增强for循环;不可以使用索引的方式来获取

1.HashSet实现类

特点:

底层:基于 HashMap 实现

无序、无索引、元素不可重复

允许存 null只能存一个 null

没有 get(index)、没有 set(index)不能用普通 for 循环遍历

查询、增删效率极高

常用方法

方法名 作用 返回值
add(E e) 添加元素,重复元素不存入 boolean
remove(Object o) 删除指定元素 boolean
contains(Object o) 判断是否包含指定元素 boolean
size() 获取集合中元素个数 int
isEmpty() 判断集合是否为空 boolean
clear() 清空集合所有元素 void
iterator() 获取迭代器,用于遍历 Iterator
clone() 克隆当前集合副本 Object
isEmpty() 判断是否无元素 boolean
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 HashSetDemo {
public static void main(String[] args) {
// 1. 创建 HashSet
HashSet<String> set = new HashSet<>();

// 2. 添加元素,自动去重
set.add("Java");
set.add("Python");
set.add("C++");
set.add("Java"); // 重复,添加无效

System.out.println("集合元素:" + set);
// 输出无序,且没有重复元素

// 3. 判断是否包含
System.out.println("是否包含Java:" + set.contains("Java"));

// 4. 删除元素
set.remove("C++");
System.out.println("删除后:" + set);

// 5. 个数、判空
System.out.println("元素个数:" + set.size());
System.out.println("是否为空:" + set.isEmpty());

// 6. 遍历(只能增强for / 迭代器)
System.out.print("遍历元素:");
for (String s : set) {
System.out.print(s + " ");
}

// 7. 清空
set.clear();
System.out.println("\n清空后是否为空:" + set.isEmpty());
}
}

2. LinkHashSet实现类

特点:

去重、不允许重复

存取有序(存入顺序 = 取出顺序)

无索引,不能用普通 for 循环

底层:哈希表 + 双向链表

常用方法

方法名 作用 返回值
add(E e) 添加元素,自动去重 boolean
remove(Object o) 删除指定元素 boolean
contains(Object o) 判断是否包含元素 boolean
size() 获取元素个数 int
isEmpty() 判断是否为空集合 boolean
clear() 清空所有元素 void
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 LinkedHashSetDemo {
public static void main(String[] args) {
// 创建集合
LinkedHashSet<String> set = new LinkedHashSet<>();

// 1. add 添加元素(去重、保留顺序)
set.add("张三");
set.add("李四");
set.add("王五");
set.add("张三"); // 重复,无效

System.out.println("集合元素:" + set);
// 输出顺序和存入顺序一致

// 2. contains 判断是否包含
System.out.println("是否包含李四:" + set.contains("李四"));

// 3. remove 删除元素
set.remove("王五");
System.out.println("删除后:" + set);

// 4. size、isEmpty
System.out.println("元素个数:" + set.size());
System.out.println("是否为空:" + set.isEmpty());

// 5. 遍历(增强for)
System.out.print("遍历:");
for (String name : set) {
System.out.print(name + " ");
}

// 6. clear 清空
set.clear();
System.out.println("\n清空后是否为空:" + set.isEmpty());
}
}

3.TreeSet实现类

特点:

底层:红黑树

元素自动升序排序

不允许重复、无索引

不能存普通自定义对象,需要比较器规则

常用方法

方法名 作用 返回值
add(E e) 添加元素,自动去重 + 排序 boolean
remove(Object o) 删除指定元素 boolean
contains(Object o) 判断是否包含元素 boolean
size() 获取元素个数 int
isEmpty() 判断集合是否为空 boolean
clear() 清空所有元素 void
first() 获取最小元素 E
last() 获取最大元素 E
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
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet<Integer> ts = new TreeSet<>();

// 添加元素,自动从小到大排序、去重
ts.add(5);
ts.add(1);
ts.add(9);
ts.add(3);
ts.add(5); // 重复 无效

System.out.println("自动排序后:" + ts);

// 获取最小、最大
System.out.println("最小元素:" + ts.first());
System.out.println("最大元素:" + ts.last());

// 判断包含
System.out.println("是否包含3:" + ts.contains(3));

// 删除
ts.remove(3);
System.out.println("删除3后:" + ts);

// 个数、判空
System.out.println("元素个数:" + ts.size());
System.out.println("是否为空:" + ts.isEmpty());

// 遍历
System.out.print("遍历元素:");
for (Integer num : ts) {
System.out.print(num + " ");
}

// 清空
ts.clear();
System.out.println("\n清空后是否为空:" + ts.isEmpty());
}
}

修改排序规则

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 TreeSetSortDemo {
public static void main(String[] args) {
// 自定义比较器:降序排列
TreeSet<Integer> ts = new TreeSet<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// 默认升序:o1 - o2
// 降序:o2 - o1
return o2 - o1;
}
});

// Lambda 简写版
// TreeSet<Integer> ts = new TreeSet<>((o1, o2) -> o2 - o1);

ts.add(5);
ts.add(1);
ts.add(9);
ts.add(3);

// 自动按降序排
System.out.println("自定义降序:" + ts);
}
}

4.Map

1.Map和Collection是同级别的,用于保存映射关系的数据:key:value(类似于python的字典)

2.Map中的key和value可以是任何引用数据的类型,会封装到HashMap$None对象中

3.Map里面的key不可以重复,但value可以!!

4.常用String类作为Map的key 也可以使用new Object()作为key!

5.key和value是单向一对一关系,通过指定的key可以找到对应的value 使用方法get(key);

注:最常用的是HashMap,但没有实现同步所有线程是不安全的!!

常用方法

方法名 作用 返回值
put(K key, V value) 添加 / 替换键值对 V
get(Object key) 根据键获取值 V
remove(Object key) 根据键删除整组键值对 V
containsKey(Object key) 判断是否包含指定键 boolean
containsValue(Object value) 判断是否包含指定值 boolean
size() 获取键值对个数 int
isEmpty() 判断 Map 是否为空 boolean
clear() 清空所有键值对 void
keySet() 获取所有键的集合 Set
values() 获取所有值的集合 Collection
entrySet() 获取所有键值对对象集合 Set<Map.Entry<K,V>>
putIfAbsent(K,V) 键不存在才添加,存在不覆盖 V

1.HashMap实现类

特点:

底层:哈希表(数组 + 链表 + 红黑树)

键无序、键唯一、值可重复

允许 null 键、null 值(最多一个 null 键)

线程不安全、效率高

无索引

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
public class HashMapDemo {
public static void main(String[] args) {
// 创建HashMap集合
HashMap<String, Integer> map = new HashMap<>();

// 1. put 添加键值对
map.put("张三", 18);
map.put("李四", 20);
map.put("王五", 19);
// 键重复,覆盖旧值
map.put("张三", 25);

System.out.println("原始Map:" + map);

// 2. get 通过键取值
System.out.println("李四年龄:" + map.get("李四"));

// 3. containsKey 判断是否包含键
System.out.println("是否包含王五:" + map.containsKey("王五"));

// 4. containsValue 判断是否包含值
System.out.println("是否包含年龄19:" + map.containsValue(19));

// 5. size 元素个数
System.out.println("键值对个数:" + map.size());

// 6. remove 根据键删除
map.remove("王五");
System.out.println("删除王五后:" + map);

// 7. putIfAbsent 键不存在才添加
map.putIfAbsent("赵六", 22);
map.putIfAbsent("李四", 99); // 键已存在,不修改
System.out.println("putIfAbsent后:" + map);

// 8. 遍历方式1:keySet
System.out.println("\n--- keySet遍历 ---");
Set<String> keySet = map.keySet();
for (String key : keySet) {
System.out.println(key + " : " + map.get(key));
}

// 9. 遍历方式2:entrySet
System.out.println("\n--- entrySet遍历 ---");
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}

// 10. isEmpty、clear
System.out.println("\n是否为空:" + map.isEmpty());
map.clear();
System.out.println("清空后是否为空:" + map.isEmpty());
}
}

2.hashTable实现类

特点:

JDK1.0 古老集合,线程安全(方法都加 synchronized

键和值都不能存 null

底层哈希表,键无序、唯一

效率低,开发基本不用,老旧项目偶尔见到

方法和 HashMap 大部分一样

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
public class HashtableDemo {
public static void main(String[] args) {
Hashtable<String, Integer> ht = new Hashtable<>();

// 添加键值对
ht.put("张三", 20);
ht.put("李四", 22);
ht.put("王五", 18);

System.out.println("Hashtable:" + ht);

// 根据键取值
System.out.println("李四年龄:" + ht.get("李四"));

// 判断包含键/值
System.out.println("包含张三?" + ht.containsKey("张三"));
System.out.println("包含18?" + ht.containsValue(18));

// 删除
ht.remove("王五");
System.out.println("删除后:" + ht);

// 个数、判空
System.out.println("数量:" + ht.size());
System.out.println("是否为空:" + ht.isEmpty());

// 遍历
for (Map.Entry<String, Integer> entry : ht.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}

// ht.put(null, 99); // 报错!不能存null键
// ht.put("赵六", null); // 报错!不能存null值

// 清空
ht.clear();
System.out.println("清空后:" + ht.isEmpty());
}
}

3.Properties实现类

特点:

继承 Hashtable,属于 Map 集合

键和值只能是 String 类型

专门用来读取 / 写入配置文件(.properties 配置文件)

自带加载配置文件、保存配置文件方法

常用方法

方法名 作用 返回值
setProperty(String key,String value) 设置键值对(存配置) Object
getProperty(String key) 根据键获取值 String
getProperty(String key,String defaultValue) 按键取值,没有就返回默认值 String
load(InputStream is) 加载 .properties 配置文件 void
store(OutputStream os,String comments) 把配置写入文件 void
size() 获取配置个数 int
clear() 清空所有配置 void
1
2
3
4
#写一个  db.properties文件
username=root
password=666888
driver=com.mysql.cj.jdbc.Driver
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 LoadProperties {
public static void main(String[] args) {
Properties prop = new Properties();

try {
// 加载配置文件
prop.load(new FileInputStream("db.properties"));

// 读取配置
String user = prop.getProperty("username");
String pwd = prop.getProperty("password");
String driver = prop.getProperty("driver");

System.out.println("用户名:" + user);
System.out.println("密码:" + pwd);
System.out.println("驱动:" + driver);

} catch (Exception e) {
e.printStackTrace();
}
}
}

4.TreeMap

特点:

底层:红黑树

键自动排序(默认自然升序)

键唯一、值可重复

无索引

可以自定义键的排序规则

不允许 null 键(可以有 null 值)

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 TreeMapDemo {
public static void main(String[] args) {
TreeMap<Integer, String> map = new TreeMap<>();

// 添加键值对,键自动升序排序
map.put(5, "王五");
map.put(1, "张三");
map.put(3, "李四");
map.put(5, "老王"); // 键重复覆盖

System.out.println("TreeMap自动排序:" + map);

// 获取最小键、最大键
System.out.println("最小键:" + map.firstKey());
System.out.println("最大键:" + map.lastKey());

// 根据键取值
System.out.println("键3对应值:" + map.get(3));

// 判断包含
System.out.println("是否包含键1:" + map.containsKey(1));

// 删除
map.remove(3);
System.out.println("删除键3后:" + map);

// 遍历
for (Integer key : map.keySet()) {
System.out.println(key + " -> " + map.get(key));
}

// 清空
map.clear();
System.out.println("清空后是否为空:" + map.isEmpty());
}
}

二十四、泛型

泛型 = 规定集合 / 类只能存什么类型

可以写的位置:

集合 / 接口 声明处(最常用)

自定义类 上

自定义方法 上

通配符 引用处(参数、变量)

1.泛型集合

1
2
3
4
5
6
7
8
9
10
11
12
13
// 无泛型,默认存Object
ArrayList list = new ArrayList();
list.add("java");
list.add(123); // 乱存,编译不报错

// 取出来要强转,容易类型转换异常
String s = (String) list.get(1); // 运行报错


// 只能存 String
ArrayList<String> list = new ArrayList<>();
list.add("Java");
// list.add(123); 编译直接报错,不让存

2.泛型接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 泛型接口:T 是类型占位符,代表任意类型
public interface GenericInterface<T> {
// 泛型方法:参数和返回值都是 T 类型
T getValue();
void setValue(T value);
}



// 实现接口时直接指定 T = String
public class StringImpl implements GenericInterface<String> {
private String value;

@Override
public String getValue() {
return value;
}

@Override
public void setValue(String value) {
this.value = value;
}
}

3.自定义类声明

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
// 自定义泛型类:T 代表任意引用类型
public class Goods<T,B> { //可以有多个
// 泛型成员变量
private T data;

// 无参构造
public Goods() {}

// 有参构造
public Goods(T data) {
this.data = data;
}

// 泛型 get/set
public T getData() {
return data;
}

public void setData(T data) {
this.data = data;
}

// 泛型方法 修饰符<T,R...> 返回值类型 方法名(T s, R s1){}
public <T> void show(T datas) {
System.out.println("存储的数据:" + datas);
}
}

4.泛型通配符

无界通配符<?>

上界通配符<? extends 父类> (上限)

下界通配符<? super 子类> (下限)

1
2
3
4
5
6
7
8
9
10
11
import java.util.ArrayList;
import java.util.List;

public class Test {
public static void main(String[] args) {
// 可以接收任意泛型的 List 一般自读不可以写
List<?> list1 = new ArrayList<String>();
List<?> list2 = new ArrayList<Integer>();
List<?> list3 = new ArrayList<Object>();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 只能接收:List<Person> List<Student> List<Teacher>
public static void showPerson(List<? extends Person> list) {
// 可以读,向上转型为 Person
for (Person p : list) {
System.out.println(p);
}

// 报错:不能添加元素
// list.add(new Student());
}

public static void main(String[] args) {
List<Student> sList = new ArrayList<>();
List<Teacher> tList = new ArrayList<>();
List<Person> pList = new ArrayList<>();

showPerson(sList);
showPerson(tList);
showPerson(pList);

// 报错:String 不是 Person 子类
// List<String> strList = new ArrayList<>();
// showPerson(strList);
}
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
// 只能接收:List<Student> List<Person> List<Object>
public static void addStudent(List<? super Student> list) {
// 可以添加 Student 子类对象
list.add(new Student());

// 取出来只能用 Object 接收
Object o = list.get(0);
// Student s = list.get(0); // 报错
}

public static void main(String[] args) {
List<Person> pList = new ArrayList<>();
List<Object> oList = new ArrayList<>();
List<Student> sList = new ArrayList<>();

addStudent(pList);
addStudent(oList);
addStudent(sList);

// 报错:Teacher 不是 Student 父类
// List<Teacher> tList = new ArrayList<>();
// addStudent(tList);
}

/* 细节
one. <T> 只能代表引用数据类型,而不是基本数据类型 如:int、float
two. 泛型可以用引用类名
three. ArrayList a = new ArrayList(); 等价于:ArrayList<Object> a = new ArrayList<Object>();
four.静态方法和变量不可以使用类的泛型
five.使用泛型的数组不可以初始化
six.泛型不具备继承性 如:List<Object> list = new arrayList<String>(); 不可以这样写!!!!
*/

二十五、线程

其他相关概念:

1.程序:指完成特定任务,用某种语言编写的一组指令的集合
2.进程:指在运行的程序,如打开了QQ,系统会自动分配内存空间,当又启动了迅雷,就又分配一个内存空间
如果关闭了,他就会释放空间;它有着自身的产生、存在和消亡的过程!!

线程

1.线程是由多个进程创建的,是进程的一个实体。
2.单线程:同一时间,只允许一个线程
3.多线程:同一时间,可以执行多个线程;如:打开了QQ,可以同时开多个窗口
4.并发:同一时刻,多个任务交替执行,造成一种”貌似同时”的错觉,简单说就是单核Cpu就是实现多任务就是并发
5.并行:同一时刻,多个任务同时执行,多核Cpu可以实现并行

1.创建方式一(继承 Thread 类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1. 自定义线程类
class MyThread extends Thread {
@Override
public void run() {
// 线程执行的任务
for (int i = 0; i < 5; i++) {
System.out.println("线程执行:" + i);
}
}
}

// 测试
public class TestThread {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); // 启动线程,自动调用 run()
}
}

//⚠️ 注意:只能调用 start (),不能直接调用 run (),直接调用只是普通方法,不是多线程。

2.创建方式二(实现 Runnable 接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1. 实现 Runnable
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("Runnable 线程:" + i);
}
}
}

public class Test {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
}
}

3.常用方法

方法 作用
start() 启动线程
run() 线程任务逻辑
sleep(long ms) 休眠,暂停指定毫秒
getName() 获取线程名
setName() 设置线程名
currentThread() 获取当前正在执行的线程
yield() 礼让线程
join() 插队线程

4.用户线程和守护线程

我希望主线程执行完后,即使子线程没有结束,也让他自动结束!!

1.用户线程:也叫工作线程,当线程的任务执行完毕或者通知方式结束

2.守护线程:一般是为工作线程服务,当所有的用户线程结束,守护线程自动结束

对比项 用户线程 守护线程
默认类型 普通线程默认是用户线程 需手动 setDaemon(true)
JVM 行为 JVM 会等待全部执行完 JVM 不等待,随用户线程结束而终止
生命周期 独立,自身逻辑控制 依赖用户线程,用户线程全灭则终止
典型场景 核心业务(订单、支付、计算) GC、日志、监控、心跳、定时任务
优先级 默认 5(NORM_PRIORITY) 通常较低,可手动调整
子线程默认 创建的子线程默认是用户线程 创建的子线程默认是守护线程
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
public class UserThreadDemo {
public static void main(String[] args) {
Thread userThread = new Thread(() -> {
System.out.println("用户线程开始...");
try {
Thread.sleep(3000); // 模拟耗时任务
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("用户线程结束");
});
userThread.start();
System.out.println("主线程结束");
}
}
// 输出:
// 主线程结束
// 用户线程开始...
// 用户线程结束(3秒后,JVM 等待它完成)



public class DaemonThreadDemo {
public static void main(String[] args) {
Thread daemonThread = new Thread(() -> {
while (true) { // 无限循环
try {
Thread.sleep(1000);
System.out.println("守护线程运行中...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
daemonThread.setDaemon(true); // 必须在 start() 前设置
daemonThread.start();

try {
Thread.sleep(3000); // 主线程存活3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程结束,JVM 退出");
}
}
// 输出:
// 守护线程运行中...
// 守护线程运行中...
// 守护线程运行中...
// 主线程结束,JVM 退出(守护线程被强行终止,不会继续打印)

5.线程声明周期

新建:new 线程对象

就绪:调用 start (),等待 CPU 调度

运行:抢到 CPU,执行 run ()

阻塞:sleep、wait、锁等待

死亡:run 执行完毕 / 异常终止

6.线程同步机制

在多线程编程。一些敏感的数据不允许被多个线程访问,此时就使用同步访问技术,保证数据在任何时刻

最多有一个线程访问,以保证数据完整性!!!

理解:即当有一个线程对内存进行操作时,其他线程不可以操作,直到该线程完成其他线程才能操作!!!

1.线程问题

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
public class SellTicket {
public static void main(String[] args) {

//创建三个Runnble售卖口
SellTicket2 sellTicket2 = new SellTicket2();
new Thread(sellTicket2).start();
new Thread(sellTicket2).start();
new Thread(sellTicket2).start();

/*
此时有个问题:
窗口Thread-1售出一张票,剩余票数-1
票以售空
窗口Thread-2售出一张票,剩余票数-2
票以售空
开三个线程,会出现:1号窗口卖的慢、2号窗口有负数、3号窗重卖的问题,如何解决?
需要使用线程同步机制
*/

}


}

//使用Runnable实现
class SellTicket2 implements Runnable {
private int ticketnum = 100;
public void run() {
while(true){
if(ticketnum <= 0){
System.out.println("票以售空");
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票," +
"剩余票数" + (--ticketnum));
}
}
}

2.互斥锁(synchronized)

在java语言中引入了对象互斥锁的概念,来保证共享数据的完整性。
synchronized关键字来与对象的互斥锁联系,当某个对象用synchronized修饰时,该对象在任意时刻只能由一个线程访问
同步的局限性:导致程序执行效率降低
同步方法(非静态的)的锁可以是this,也可以时其他对象(要求时同一个对象)

1.写到代码块

1
2
3
4
5
6
// 锁当前对象
synchronized (this) {}
// 锁任意对象
synchronized (obj) {}
// 锁Class对象
synchronized (ClassName.class) {}

2.写到成员方法\静态方法

1
2
3
public synchronized void test(){}

public static synchronized void test(){} //锁住当前类的 Class 对象

3.解决线程问题

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
public class SellTicket {
public static void main(String[] args) {
//两种方法会出现问题,出现超卖的现象,这该如何解决?? 使用互斥锁 添加synchronized属性

//创建三个Runnble售卖口
SellTicket2 sellTicket2 = new SellTicket2();
new Thread(sellTicket2).start();
new Thread(sellTicket2).start();
new Thread(sellTicket2).start();

}


}

//使用Runnable实现
class SellTicket2 implements Runnable {
private int ticketnum = 1000;
private boolean falg = true;

//添加互斥锁,变成同步方法
public synchronized void m(){
if(ticketnum <= 0){
System.out.println("票以售空");
falg = false;
return;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票," +
"剩余票数" + (--ticketnum));
}
//run方法调用同步方法!!!
public void run() {
while (falg) {
m();
}
}
}

3.死锁

一个线程都占用了对方的线程,但不肯相让,导致了死锁,在编程中要避免死锁的发生!!

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
public class DeadLock {
//模拟线程死锁
public static void main(String[] args) {
//模拟死锁问题
DeadLockDemo A = new DeadLockDemo(true);
DeadLockDemo B = new DeadLockDemo(false);
A.start();
B.start();
}
}

class DeadLockDemo extends Thread {
static Object o1 = new Object();
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {
this.flag = flag;
}

public void run(){
//下面逻辑分析
//1.
if(flag){
synchronized (o1){
System.out.println(Thread.currentThread().getName() + "进入1");
synchronized (o2){
System.out.println(Thread.currentThread().getName() + "进入2");
}
}
}else{
synchronized (o2){
System.out.println(Thread.currentThread().getName() + "进入3");
synchronized (o1){
System.out.println(Thread.currentThread().getName() + "进入4");
}
}
}
}
}

7.更多相关线程内容

点击这里

二十六、异常

异常是什么

程序运行中非正常情况,打断正常流程;Java 用异常类对象封装错误信息。

异常体系总根

Throwable├─ Error 系统级严重错误(程序管不了)└─ Exception 程序可处理异常(我们要管) ├─ 编译时异常(受检异常) └─ 运行时异常(RuntimeException)

三大分类(必背)

  1. Error 错误虚拟机崩溃、内存溢出、栈溢出;不用捕获、处理不了。例:StackOverflowErrorOutOfMemoryError
  2. 编译时异常(受检异常)除了 RuntimeException 及其子类,都是编译异常;必须 try-catch 或 throws 抛出去,否则编译报错。例:IOExceptionSQLException
  3. 运行时异常(非受检异常)RuntimeException 及其子类;编译不报错,运行才崩,可捕获可不捕获,靠代码规范避免。例:空指针 NullPointerException数组越界 ArrayIndexOutOfBoundsException类型转换 ClassCastException除数为 0 ArithmeticException

1.常见异常类

异常分类 异常类名 中文名称 典型触发场景
运行时异常(非受检异常,RuntimeException 子类) NullPointerException 空指针异常 调用 null 对象的方法 / 属性、数组为 null 时操作数组元素
ArrayIndexOutOfBoundsException 数组下标越界异常 访问数组时,下标小于 0 或大于等于数组长度
StringIndexOutOfBoundsException 字符串下标越界异常 调用 charAt ()、substring () 等方法时,下标超出字符串长度范围
IndexOutOfBoundsException 索引越界异常 数组 / 字符串 / 集合下标越界的父类异常,通用索引越界场景
ClassCastException 类型强制转换异常 不兼容的类型强制转换,如把 String 强制转为 Integer、子类对象向上转型后错误向下转型
ArithmeticException 算术运算异常 整数除法中除数为 0、对负数开平方根等非法算术运算
IllegalArgumentException 非法参数异常 方法入参不符合要求,如传入 null、数值超出合法范围、格式错误
IllegalStateException 非法状态异常 调用方法时,对象的状态不支持该操作,如未初始化就调用业务方法、重复调用关闭方法
UnsupportedOperationException 不支持的操作异常 调用了不支持的方法,如对不可变集合执行 add/remove 操作、未实现的接口方法被调用
NumberFormatException 数字格式转换异常 字符串转数字时,字符串内容不符合数字格式,如把 “abc” 转为 Integer
NegativeArraySizeException 数组长度为负异常 创建数组时,指定的长度为负数
ConcurrentModificationException 并发修改异常 遍历集合(如 ArrayList)时,同时对集合进行 add/remove 等修改操作(非迭代器自身的 remove)
NoSuchElementException 无此元素异常 调用迭代器的 next () 时,已无更多元素;或从空集合中获取元素
编译时异常(受检异常,Exception 直接子类) IOException 输入输出异常 文件读写、网络流操作、资源关闭失败等 IO 相关操作出错,是所有 IO 异常的父类
FileNotFoundException 文件未找到异常 尝试打开不存在的文件、路径错误、无文件访问权限
EOFException 文件已结束异常 从文件 / 流中读取数据时,已到达文件末尾仍继续读取
SocketException 套接字异常 网络 Socket 操作出错,如连接超时、端口被占用、网络断开
SQLException 数据库操作异常 数据库连接、SQL 执行、结果集处理出错,是所有数据库相关异常的父类
ClassNotFoundException 类未找到异常 反射加载类时,指定的类路径 / 类名错误、类文件不存在
NoSuchMethodException 方法未找到异常 反射调用方法时,指定的方法名 / 参数列表不存在
NoSuchFieldException 字段未找到异常 反射操作字段时,指定的字段名不存在
IllegalAccessException 非法访问异常 反射访问类的私有方法 / 字段时,无访问权限;或访问权限不符合要求
InterruptedException 线程中断异常 线程在 sleep ()、wait ()、join () 等阻塞状态时,被其他线程调用 interrupt () 中断
TimeoutException 超时异常 异步操作、线程等待、网络请求等超出指定的超时时间
系统错误(Error 子类,程序无法处理) StackOverflowError 栈溢出错误 方法递归调用过深、无限循环调用方法,导致虚拟机栈内存溢出
OutOfMemoryError 内存溢出错误 堆内存不足、元空间溢出、直接内存溢出,虚拟机无法分配新的内存
NoClassDefFoundError 类定义未找到错误 编译时类存在,运行时类文件缺失 / 版本不匹配,虚拟机无法加载类定义
ExceptionInInitializerError 静态初始化异常 类的静态代码块、静态变量初始化时抛出异常,导致类加载失败
VirtualMachineError 虚拟机错误 虚拟机内部运行出错,是所有虚拟机相关错误的父类

2. try-catch

捕获异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
try{
可能出异常的代码
}catch(异常类型 e){
异常处理逻辑
}
...
finally{

}


//新写法 自动关闭资源,不用写 finally 关流,实现 AutoCloseable 接口的都能放 try() 里。
try(定义资源1; 定义资源2){
// 业务代码、读写资源
}catch(异常类型 e){
// 异常处理
}
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 class Try {
public static void main(String[] args) {
try{
int a = 1 /0;
}catch (Exception e){
System.out.println("捕获异常");
}finally {
System.out.println("无论有没有异常都执行");
}
}
}


//新写法
try (
FileReader fr = new FileReader("a.txt");
BufferedReader br = new BufferedReader(fr)
){
String line;
while((line = br.readLine()) != null){
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}

3. throw

将异常抛给下一个调用者

1
2
3
public void read() throws IOException {}

public void test() throws Exception{} //方法抛出

4. throws

主动抛出异常

1
2
3
4
public void test() throws Exception {
System.out.println("即将抛出异常");
throw new Exception();
}

二十七、IO流

1.文件

文件是用于保存数据的地方,比如word文件、txt文件等,既可以保存一张图片也可以保存视频、声音。

文件流:文件在程序中是以流的形式来操作的

形式:

java程序(内存)——>文件(磁盘) 这叫输入流 数据从数据源和程序的路径
java程序(内存)<——文件(磁盘) 这叫输出流 数据从程序到数据源的路径

2.File类

创建文件

构造方法 作用说明 核心特点
new File(String pathname) 根据完整路径字符串创建 File 对象 直接传入文件 / 目录的完整路径,最常用
new File(File parent, String child) 根据父目录 File 对象 + 子路径创建 先定义父目录文件,再拼接子路径,更灵活
new File(String parent, String child) 根据父目录字符串 + 子路径字符串创建 用两个字符串拼接路径,代码更简洁
file.createNewFile() boolean 创建空文件,路径中的目录必须存在,否则报错
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
package File.CreateFileAndMkdir;

import java.io.File;
import java.io.IOException;
//一共有三种方式创建
public class FileCreate {
public static void main(String[] args) {
//方式1 父目录文件(要有),子文件 必须是 / 或者 \\
File file = new File("E:/IO流复习","new1.txt");
try {
file.createNewFile();
System.out.println("创建成功");
} catch (IOException e) {
throw new RuntimeException(e);
}


//方式2
String path = "E:/IO流复习/new2.txt";
File file1 = new File(path);
try {
file1.createNewFile();
System.out.println("创建成功");
} catch (IOException e) {
throw new RuntimeException(e);
}


//方式3 父目录,子文件
File filepath = new File("E:/IO流复习");
File file3 = new File(filepath,"new3.txt");
try {
file3.createNewFile();
System.out.println("创建成功");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

文件方法

方法名 功能说明
createNewFile 创建空文件,已存在返回 false
mkdir 创建单级空目录
mkdirs 创建多级目录(常用)
delete 删除文件 / 空目录,不走回收站
renameTo 重命名 / 移动文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CreateMkdir {

public static void main(String[] args) {
File file = new File("E:\\IO流复习\\ddd");
///// 创建单个文件夹,只能创建一个!!!!
boolean mkdir = file.mkdir();
System.out.println(mkdir);

///// 创建多级文件夹
File file1 = new File("E:\\IO流复习\\ccc\\bbb\\ggg");
boolean mkdirs = file1.mkdirs();
System.out.println(mkdirs);

///删除文件
System.out.println(file.delete());
}
}
方法名 功能说明
exists 判断文件或目录是否存在
isFile 判断是否是普通文件
isDirectory 判断是否是文件夹
isHidden 判断是否为隐藏文件
canRead 判断是否可读
canWrite 判断是否可写
canExecute 判断是否可执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// exists() 是否存在
System.out.println(file.exists());

// isFile() 是否是文件
System.out.println(file.isFile());

// isDirectory() 是否是文件夹
System.out.println(dir.isDirectory());

// isHidden() 是否隐藏
System.out.println(file.isHidden());

// canRead() 是否可读
System.out.println(file.canRead());

// canWrite() 是否可写
System.out.println(file.canWrite());

// canExecute() 是否可执行
System.out.println(file.canExecute());
方法名 功能说明
getName 获取文件 / 目录名称
getPath 获取相对路径(构造时传入路径)
getAbsolutePath 获取绝对路径
getCanonicalPath 获取规范绝对路径(去冗余)
getParent 获取父路径字符串
getParentFile 获取父路径对应的 File 对象
length 获取文件大小(字节)
lastModified 获取最后修改时间(毫秒值)
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
// getName() 获取名称
System.out.println(file.getName());

// getPath() 获取构造路径
System.out.println(file.getPath());

// getAbsolutePath() 绝对路径
System.out.println(file.getAbsolutePath());

// getCanonicalPath() 规范路径
System.out.println(file.getCanonicalPath());

// getParent() 父路径字符串
System.out.println(file.getParent());

// getParentFile() 父路径File对象
System.out.println(file.getParentFile());

// length() 文件大小 字节
System.out.println(file.length());

// lastModified() 最后修改时间 毫秒
long l = file.lastModified();
//格式化
Instant instant = Instant.ofEpochMilli(l);
LocalDateTime localDate = instant.atZone(ZoneId.of("Asia/Shanghai")).toLocalDateTime();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format = dateTimeFormatter.format(localDate);
System.out.println(format);
方法名 功能说明
list 返回目录下所有子名称 String []
listFiles 返回目录下所有子文件 File []
list(FilenameFilter) 按过滤器筛选文件名数组
listFiles(FilenameFilter) 按过滤器筛选 File 对象数组
1
2
3
4
5
6
/// 获取文件夹所有文件       重点
File file1 = new File("E:/Io流复习/listFile");
File[] files = file1.listFiles();
for(File file2 : files){
System.out.println(file2.getName());
}

3.IO流分类

1.数据方向

输入流:读数据(从文件 → 程序)

输出流:写数据(从程序 → 文件)

2.数据类型

字节流

  • 处理一切文件:文本、图片、视频、音频
  • 基类:
    • 输入:InputStream
    • 输出:OutputStream

字符流

  • 只处理纯文本(txt、java 等)
  • 基类:
    • 输入:Reader
    • 输出:Writer

3. 四大抽象基类

字节流:InputStream;OutputStream;字符流:Reader;Writer;

4. FileInputStream、FileOutputStream字节流

(1)FileInputStream

作用:

FileInputStream文件字节输入流专门从本地文件读取字节数据,属于 InputStream 子类。

构造:

构造方法 说明
FileInputStream(String path) 通过文件路径创建读取流
FileInputStream(File file) 通过 File 对象创建读取流

常用方法

方法 作用
read() 读 1 个字节,返回字节 int,读完返回 -1
read(byte[] b) 读数据到字节数组,返回实际读取长度
read(byte[] b, int off, int len) 从数组 off 位置开始,最多读 len 个字节
available() 获取文件剩余可读字节数
close() 关闭流,释放资源
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
/// 读取数据   FileInputStream      抽象父类    InputStream
public class ByteInputStream {
public static void main(String[] args) throws Exception {
//1.创建对象
FileInputStream fileInputStream = new FileInputStream("E:/IO流复习/ByteIn/ccc.txt");

//2.读取数据
//参数1:read() 返回值是int类型,如果读不出没有数据返回-1
//参数2:read(Byte[] buffer) 返回值是读取的长度
///2.1 使用一个int类型持续接收数据
//int b;
///// 2.2 while循环读取
//while((b = fileInputStream.read()) != -1){
// System.out.print(b);
//}

/// 2.1 使用参数2
byte[] bytes = new byte[1024];
int read = fileInputStream.read(bytes);
System.out.print(new String(bytes));

//3.关闭资源
fileInputStream.close();
}
}

(2)FileOutputStream

作用:

文件字节输出流OutputStream 子类负责:程序 → 写入数据到本地文件

构造:

构造方法 功能说明
FileOutputStream(String path) 创建字节输出流,覆盖原文件内容
FileOutputStream(String path, boolean append) append=true 追加写入,不覆盖
FileOutputStream(File file) 通过 File 对象创建,覆盖写入
FileOutputStream(File file, boolean append) File 对象 + 追加模式

常用方法

方法 作用
write(int b) 写入 1 个字节
write(byte[] b) 写入整个字节数组
write(byte[] b,int off,int len) 从数组 off 开始,写入 len 个字节
close() 关闭流,释放资源
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 ByteOutputStream {

//字节流写入文件 抽象父类 OutputStream
public static void main(String[] args) throws Exception {
//1.使用 FileOutputStream类
FileOutputStream fileOutputStream = new FileOutputStream("E:/IO流复习/ByteOut/ccc.txt",true);

//2.写出数据
/// write参数1 write(int b) 一次写入一个字节
//fileOutputStream.write(97);
/// write参数2 write(byte[] b) 一次写入一组字节
/// write参数3 write(btte[] b,int off,int len) 一次写入一组字节的部分或全部数据
//byte[] b = {98,97,96,95,94};
//fileOutputStream.write(b,2,4); // int off 起始索引; int len 读取几个字节

//读取字符 包含换行
String str = "hhhhh\nssssss"; //只需要在文字中间加入 \n或\r或\r\n即可
fileOutputStream.write(str.getBytes()); //换成字节读取

//3.关闭流
fileOutputStream.close();

/// 细节: 参数可以为字符串或File类; 如果没有文件会自动创建,如果没有文件夹报错; true 表示追加内容

}
}

5. FileReader、FileWriter字符流

(1)FileReader

作用:

FileReader:文件字符输入流,读纯文本

构造:

构造方法 作用
FileReader(String path) 根据文件路径创建字符读流
FileReader(File file) 根据 File 对象创建字符读流

方法

方法名 功能
read() 读单个字符,读完返回 -1
read(char[] cbuf) 读入字符数组,返回读取个数
read(char[] cbuf,int off,int len) 从偏移量开始读指定长度
close 关闭流
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
public class fileReader {
/// 字符流 FileReader 抽象父类 Reader
public static void main(String[] args) throws Exception {
//1.创建对象
FileReader fileReader = new FileReader("E:/IO流复习/Writer/111.txt");

//2.读取数据 他也有三个参数
/// 2.1 参数1 read()
//int b;
//while((b = fileReader.read()) != -1){
// System.out.print((char)b);
//}

/// 2.2 参数2 read(char[] cbuf, int off, int len) 字符数组 开始下标 长度
//char[] chars = new char[128];
//int read = fileReader.read(chars, 0, chars.length);
//System.out.println(new String(chars));

/// 2.3 参数3 read(CharBuffer target) 可以使用 char[] 或者 charBuffer[]
/// CharBuffer 是一个抽象类 父类是Buffer
CharBuffer charBuffer = CharBuffer.allocate(128);
int read1 = fileReader.read(charBuffer);
System.out.println(new String(charBuffer.array()));

//3.关闭资源
fileReader.close();
}

(2)FileWriter

作用:

FileWriter:文件字符输出流,写纯文本

构造:

构造方法 作用
FileWriter(String path) 覆盖写入
FileWriter(String path, boolean append) append=true 追加写入
FileWriter(File file) 覆盖写入
FileWriter(File file, boolean append) 追加写入

常用方法

方法名 功能
write(int c) 写单个字符
write(char[] cbuf) 写整个字符数组
write(char[] cbuf,int off,int len) 写数组指定范围
write(String str) 直接写字符串(超好用)
write(String str,int off,int len) 写字符串指定部分
flush 刷新缓冲区
close 关闭流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/// 字符流输入    FileWriter         抽象父类    Writer
public class fileWriter {
public static void main(String[] args) throws Exception {
//1.创建对象
FileWriter fileWriter = new FileWriter("E:/IO流复习/Writer/222.txt",true); //true 追加

//2.写入数据 write 他有5个方法 1: char[] cuf 2:String s 3.char c 4: char[] c , int off, int len
//这里使用 String s
//String str = "你好,java";
//fileWriter.write(str);

/// 扩展(了解): 写入数据 append() 可以写入 char String CharBuffer StringBuffer StringBuilder 类型
String str = "你好,python"; //使用时要加入 true
fileWriter.append(str);
//3.关闭
fileWriter.close();
}
}

6. BufferedOutputStream、BufferedInputStream包装字节流

作用:

属于字节包装流,包装 FileInputStream / FileOutputStream

  • 自带内置缓冲区,减少磁盘 IO,读写速度超快
  • 用法和普通字节流一样,只是套了一层包装

构造:

构造方法 说明
BufferedInputStream(InputStream in) 包装字节输入流,默认缓冲大小
BufferedOutputStream(OutputStream out) 包装字节输出流,默认缓冲大小

方法和非包装一摸一样!!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// 字节流缓冲 BufferInputStream
public class BufferInput {
public static void main(String[] args) throws Exception {
//1.创建对象 需要 new FileInputStream,把它进行包装!!!
BufferedInputStream bufferedInputStream = new BufferedInputStream(
new FileInputStream("E:/IO流复习/BufferByte/111.txt"));

//2.读取数据 read() 和FileInputStream一样
byte[] bytes = new byte[1024];
int read = bufferedInputStream.read(bytes);
System.out.print(new String(bytes));

//3.关闭数据 只要关闭包装的就行
bufferedInputStream.close();
}
}

7. BufferedReader、BufferedWriter包装字符流

(1)BufferedReader

作用:

包装 FileReader,属于字符包装流

自带缓冲区,读取文本更快

独有核心功能:按行读取 readLine()

构造:

构造方法 说明
BufferedReader(Reader r) 包装任意字符输入流,常用包装 FileReader

常用方法

方法名 作用
read() 读单个字符,读完返回 -1
read(char[] cbuf) 读入字符数组,返回读取长度
read(char[] cbuf,int off,int len) 指定位置和长度读取
readLine() 读一整行文本,无内容返回 null(独有)
close() 关闭流,自动关闭内层流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/// 字符流包装   BufferedReader
public class bufferReader {
public static void main(String[] args) throws Exception {
//1.创建对象
BufferedReader bufferedReader = new BufferedReader(
new FileReader("E:/IO流复习/BufferReader/111.txt"));


//2.读取数据 read() 和 FileReader一样 4个参数
//char[] chars = new char[128];
//int read = bufferedReader.read(chars);
//System.out.println(new String(chars));

//2.1 readLine() 按行读取,返回值是String 当没有时返回null 缓冲流特有方法,好用!!!!!!!
String str;
while((str = bufferedReader.readLine()) != null){
System.out.println(str);
}


//3.关闭
bufferedReader.close();
}
}

(2)BufferedWriter

作用:

字符包装流,包装 FileWriter

自带缓冲区,写入速度更快

独有方法:newLine () 自动换行(跨平台通用)

构造:

构造方法 说明
BufferedWriter(Writer w) 包装字符输出流,常用包装 FileWriter

常用方法

方法名 作用
write(int c) 写入单个字符
write(char[] cbuf) 写入整个字符数组
write(char[] cbuf,int off,int len) 写入数组指定范围
write(String str) 直接写字符串
newLine() 换行(独有,跨平台)
flush() 刷新缓冲区,强制写入文件
close() 先刷新再关流,自动关闭内层流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/// 输入缓冲流       BufferedWriter
public class bufferWriter {
public static void main(String[] args) throws Exception {
//1.创建对象
BufferedWriter bufferedWriter = new BufferedWriter(
new FileWriter("E:/IO流复习/BufferReader/222.txt",true));

//2.写入数据
String str = "哈哈哈哈哈";
bufferedWriter.write(str);
/// 2.1 新增 newLine() 换行
bufferedWriter.newLine(); //换行
String str2 = "你时水水水水";
bufferedWriter.write(str2);

//3.关闭
bufferedWriter.close();
}
}

8. InputStreamReader、OutputStreamWriter读取转换流

(1)InputStreamReader

作用:

InputStreamReader:字节输入流 → 字符输入流 的转换桥梁

  1. FileInputStream 字节流 转成 字符流
  2. 可以手动指定编码 UTF-8 / GBK,解决中文乱码
  3. 属于 字符流 Reader 的子类
  4. 常被 BufferedReader 包装 按行读取

构造:

构造方法 说明
InputStreamReader(InputStream in) 使用系统默认编码转换
InputStreamReader(InputStream in,String charset) 指定编码转换(常用)

常用方法

方法 作用
read() 读单个字符,返回 - 1 结束
read(char[] cbuf) 读取到字符数组
read(char[] cbuf,int off,int len) 读取指定范围
close() 关闭流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/// 读取转换流 InputStreamReader         抽象父类  Reader
public class InputToReader {
public static void main(String[] args) throws Exception {
//1.创建对象 将字节流转为字符流
InputStreamReader inputStreamReader = new InputStreamReader(
new FileInputStream("E:/IO流复习/new2.txt"), "UTF-8"); //指定读取字符编码

//2.读取,本质是字符流
char[] chars = new char[128];
int read = inputStreamReader.read(chars);
System.out.println(new String(chars));

//3.关闭
inputStreamReader.close();
}
}

//可以和包装流配合使用!!!!

(2)OutputStreamWriter

作用:

OutputStreamWriter字节输出流 → 字符输出流

转换桥梁可以指定编码(UTF-8 / GBK)写入文件,解决写中文乱码

属于 Writer 子类。

构造:

构造方法 说明
OutputStreamWriter(OutputStream out) 使用系统默认编码
OutputStreamWriter (OutputStream out,String 编码) 指定编码写入(常用)

常用方法

方法 作用
write(int c) 写单个字符
write(char[] cbuf) 写字符数组
write(char[] cbuf,int off,int len) 写数组指定区间
write(String str) 直接写字符串
flush 刷新缓冲区
close 关闭流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//输入转换流  OutputStreamWriter         抽象父类  Writer
public class OutputToWriter {
public static void main(String[] args) throws Exception {
//1.创建对象
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(
new FileOutputStream("E:/IO流复习/new4.txt"),"GBK"); //指定写入格式

//2.写入
outputStreamWriter.write("指定GBK编码格式!!!!");

//3.关闭
outputStreamWriter.close();
}
}

9. ObjectInputStream、ObjectOutputStream序列化流

(1)ObjectInputStream

作用:

ObjectInputStream读取对象(反序列化)

把文件中保存的对象数据,还原成 Java 对象。

属于 字节输入流、包装流

注意:

要被读取的类必须:

  1. 实现 Serializable 接口(标记接口,无方法)
  2. 序列化和反序列化 版本号一致

构造:

构造方法 说明
ObjectInputStream(InputStream in) 包装字节输入流,常用 new FileInputStream ()

常用方法

方法名 作用
readObject() 读取一个序列化对象,返回 Object,需强转
readInt() 读取 int 类型
readByte() 读取 byte 类型
readShort() 读取 short 类型
readLong() 读取 long 类型
readFloat() 读取 float 类型
readDouble() 读取 double 类型
readBoolean() 读取 boolean 类型
readChar() 读取 char 类型
readUTF() 读取 UTF-8 编码字符串
readFully(byte[] b) 一次性读满整个字节数组
readFully(byte[] b,int off,int len) 读取指定长度填满数组指定位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// 读取 反序列化     ObjectInputStream
public class ObjectInput {
public static void main(String[] args) throws Exception {
//1.创建对象
ObjectInputStream objectInputStream = new ObjectInputStream(
new FileInputStream("E:/IO流复习/student.txt"));

//2.读取
Object object = objectInputStream.readObject();
System.out.println(object);

//3.关闭
objectInputStream.close();
}
}

(2)ObjectOutputStream

作用:

ObjectOutputStream

Java 对象 写入文件 / 网络流,即序列化内存对象 → 写到文件保存

构造:

构造方法 说明
ObjectOutputStream(OutputStream out) 包装字节输出流,常用包装 FileOutputStream

常用方法

方法 作用
writeObject() 写入一个对象(最核心)
writeInt() 写 int
writeByte() 写 byte
writeShort() 写 short
writeLong() 写 long
writeFloat() 写 float
writeDouble() 写 double
writeBoolean() 写 boolean
writeChar() 写 char
writeUTF() 写 UTF-8 编码字符串
write(byte[] b) 写字节数组
write(byte[] b,int off,int len) 写数组指定范围
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
//这里 implements Serializable  表示该JavaBean实现序列化接口,IO流专属
public class Student implements Serializable {
private static final long serialVersionUID = -8433626682217889602L; // 使用序列化时的那个值
private String name;
private int age;

public Student(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

//public Student() {
//}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}



/// 写入 序列化流 ObjectOutputStream 抽象父类 OutputStream
public class Objectoutput {
public static void main(String[] args) throws Exception {

//1.创建对象
Student student = new Student("xiaochen",20);

//2.创建序列化流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(
new FileOutputStream("E:/IO流复习/student.txt"));

//3.写入
objectOutputStream.writeObject(student);

//4.关闭
objectOutputStream.close();
}
}

10.标准输入输出流

作用:

Java 自带默认不用创建、直接能用的流

  • 标准输入流System.in
  • 标准输出流System.out

底层:

名称 类型 作用
System.in InputStream 字节输入流 控制台键盘读数据
System.out PrintStream 字节打印流 控制台窗口写数据

特点:

程序启动自动创建,不用 new

不用手动关闭,JVM 自动管理

默认绑定:键盘输入、控制台输出

可以重定向:把输入输出改成文件

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class SysInDemo {
public static void main(String[] args) throws Exception {
// 标准字节输入流 → 转换流 → 缓冲流
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

System.out.print("请输入:");
String line = br.readLine();
System.out.println("你输入了:" + line);
}
}

11.打印流

特点:

只能输出、不能输入

不会抛 IOException,代码简洁

可以原样输出任意类型:int、double、对象、字符串

有自动换行、自动刷新

开发最常用:System.out 本质就是 PrintStream

区别:

类名 所属 特点
PrintStream 字节打印流 字节流,处理一切文件,System.out 就是它
PrintWriter 字符打印流 字符流,适合文本,可指定编码

构造:

构造 作用
PrintStream(OutputStream out) 包装字节输出流
PrintStream(String fileName) 直接传文件路径
构造 作用
PrintWriter(Writer out) 包装字符输出流
PrintWriter(OutputStream out) 包装字节流(自动转字符)
PrintWriter(String fileName) 直接文件路径

常用方法

方法 作用
print (任意类型) 不换行打印
println (任意类型) 打印并自动换行
printf (格式,参数) 格式化打印
flush() 刷新缓冲区
close() 关闭流
1
2
3
4
5
6
7
8
9
10
11
import java.io.PrintStream;

public class PSMain {
public static void main(String[] args) throws Exception {
PrintStream ps = new PrintStream("a.txt");
ps.println("你好打印流");
ps.println(123);
ps.println(3.14);
ps.close();
}
}

12. ZipInputStream、ZipOutputStream解压缩流

(1)ZipInputStream

作用:

专门处理 ZIP 格式 压缩包

  • ZipInputStream解压流 → 读取 zip 包、解压里面文件

构造:

构造 作用
ZipInputStream(InputStream in) 包装文件字节输入流

常用方法

方法 作用
getNextEntry() 获取压缩包里下一个条目,无返回 null
closeEntry() 关闭当前条目
read(byte[] b) 读取条目里的文件内容
close() 关闭流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/// 解压缩流  ZipInputStream
public class ZipStreamDemo1 {
public static void main(String[] args) throws Exception {
//1.创建对象
ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream("E:/IO流复习/Zip.zip"));

//2.解压 压缩包里面的内容一个文件夹就是一个层级
//ZipEntry nextEntry = zipInputStream.getNextEntry();
//System.out.println(nextEntry); //每次读取文件或文件夹,getNextEntry的返回类型是ZipEntry
ZipEntry zipEntry;
while((zipEntry = zipInputStream.getNextEntry()) != null){
System.out.println(zipEntry); //读取的文件全部都是已经解压的
}

//3.关闭
zipInputStream.close();

}
}

(2)ZipOutputStream

作用:

ZipOutputStream压缩流 → 把多个文件 / 文件夹打成 zip 压缩包

构造:

构造 作用
ZipInputStream(InputStream in) 包装文件字节输入流

常用方法

方法 作用
getNextEntry() 获取压缩包里下一个条目,无返回 null
closeEntry() 关闭当前条目
read(byte[] b) 读取条目里的文件内容
close() 关闭流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mport java.io.*;
import java.util.zip.ZipOutputStream;
import java.util.zip.ZipEntry;

public class ZipDemo {
public static void main(String[] args) throws IOException {
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("out.zip"));

// 往压缩包里加一个文件
zos.putNextEntry(new ZipEntry("a.txt"));
FileInputStream fis = new FileInputStream("a.txt");
byte[] buf = new byte[1024];
int len;
while ((len = fis.read(buf)) != -1) {
zos.write(buf, 0, len);
}
fis.close();
zos.closeEntry();

zos.finish();
zos.close();
}
}

13.更多内容

点击这里

二十八、反射

Java 反射(Reflection)是运行时动态获取类信息、创建对象、调用方法 / 访问字段的机制,核心是通过java.lang.Class对象反向操作类的元数据。

正常(正向):编译期已知类,直接new对象、调用方法。

反射(反向):编译期未知类,运行时通过Class对象 “看透” 类结构并操作。

本质:JVM 加载类后,在方法区生成唯一Class对象,反射即操作该对象及关联的Field/Method/Constructor

1.反射获取类对象方式

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
// 1. 类名.class(编译期已知)
Class<User> clazz1 = User.class;

// 2. 对象.getClass()(运行时对象)
User user = new User();
Class<? extends User> clazz2 = user.getClass();

// 3. Class.forName("全类名")(运行时动态加载,常用)
Class<?> clazz3 = Class.forName("com.example.User");

// 4.通过类加载器[4种]获取类的Class对象
//(1)先得到类加载器
ClassLoader classloder = car.getClass().getClassLoader();
//(2)通过类加载器得到Class对象
Class cls2 = classloder.loadClass(classAllPath);
System.out.println(cls2);//class com.Xc.Car


/*
注意:
1.Class也是一个类,因此也继承Object类
2.Class对象不是new出来的,而是系统创建出来的
3.对于某个类的Class类对象,在内存中只有一份,因为类只加载一次!!
4.每个类的实例都会记得自己有哪个Class实例所生成
5.通过Class可以完整地得到一个类的完整结构,通过一系列API
6.Class对象是存放在堆里面的
7.类的字节码二进制,是放在方法区的,有的地方称为元数据
*/

2.有Class对象的类型

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
//普通类
String.class
Object.class
User.class

//接口
List.class
Runnable.class

//数组
int[].class
String[].class
User[][].class

//基本数据类型(八大基本类型)
byte.class
short.class
int.class
long.class
float.class
double.class
char.class
boolean.class

//包装类
Integer.class
Double.class
Boolean.class

//枚举
Season.class // 自定义枚举

//注解
Override.class
Autowired.class

//void
void.class

3.类加载

.class 字节码文件加载到内存,转换成运行时数据结构,并生成 Class 对象的过程

(1)类加载全过程 5 步

加载 → 验证 → 准备 → 解析 → 初始化

1. 加载(Load)

  1. 读取 .class 字节码到内存
  2. 在堆内存生成唯一 Class 对象
  3. 方法区存放类的元数据

2. 验证(Verify)

保证字节码安全、合规:

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证

3. 准备(Prepare)

给静态变量分配内存、赋默认初始值

  • int → 0

  • boolean → false

  • 引用 → null

    ⚠️ 不会赋代码里的自定义初始值

4. 解析(Resolve)

符号引用 替换成 直接内存地址引用静态绑定、常量池解析都在这步

5. 初始化(Init)

执行静态代码块、给静态变量赋代码中定义的初始值只在第一次主动使用类时触发,且只执行一次。

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
public class ClassLoad03 {
public static void main(String[] args) throws ClassNotFoundException {
//1. 加载B类,并生成 B的class对象
//2. 链接 num = 0
//3. 初始化阶段
// 依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并合并
/*
clinit() {
System.out.println("B 静态代码块被执行");
//num = 300;
num = 100;
}
合并: num = 100

*/

//new B();//类加载
//System.out.println(B.num);//100, 如果直接使用类的静态属性,也会导致类的加载

//看看加载类的时候,是有同步机制控制
/*
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//正因为有这个机制,才能保证某个类在内存中, 只有一份Class对象
synchronized (getClassLoadingLock(name)) {
//....
}
}
*/
B b = new B();
}
}

class B {
static {
System.out.println("B 静态代码块被执行");
num = 300;
}

static int num = 100;

public B() {//构造器
System.out.println("B() 构造器被执行");
}
}

(2)类的主动引用(会触发初始化)

new 对象

调用类的静态方法

使用类的静态常量(非 final

反射 Class.forName()

初始化子类,先初始化父类

(3)动态加载和静态加载

静态加载

编译期间就把类依赖写死,编译时必须能找到类,找不到直接编译报错语法:new 类名()、直接使用类、继承、接口实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class loo2 {
public static void main(String[] args) {
Car car = new Car();
CCC c = new CCC(); //没有这个类,编译报错
}
}


public class Car {
public String brand = "宝马";
public int price = 5000000;
public String color = "浅蓝色";

@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", price=" + price +
", color='" + color + '\'' +
'}';
}
}

动态加载

运行期间才加载类,编译时不依赖类,运行时通过反射加载语法:Class.forName("全类名")、类加载器加载

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
public class loo {
public static void main(String[] args) throws Exception {
Scanner scanner = new Scanner(System.in);
System.out.println("输入一个数字:");
int i = scanner.nextInt();

switch(i){
case 1 -> System.out.println(Class.forName("com.Xc.Car"));
case 2 -> System.out.println(Class.forName("com.Xc.cat"));
case 3 -> System.out.println(Class.forName("com.Xc.duhs")); //只要不运行这里,编译不会报错
}
}
}


public class Car {
public String brand = "宝马";
public int price = 5000000;
public String color = "浅蓝色";
}

public class cat {
public String name = "招财猫";
public int age;

}

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
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public class Person {

// ========== 各种修饰符 字段 ==========
// 公有成员变量
public String publicName;

// 保护成员变量
protected int protectedAge;

// 默认包访问权限(无修饰符)
String defaultAddr;

// 私有成员变量
private double privateSalary;

// 静态公有字段
public static String staticPublicInfo;

// 常量 public static final
public static final String NATIONALITY = "中国";

// 私有静态常量
private static final int MAX_AGE = 150;


// ========== 各种权限 构造器 ==========
// 无参 public 构造器
public Person() {
}

// 有参 public 构造器
public Person(String name) {
this.publicName = name;
}

// protected 构造器
protected Person(int age) {
this.protectedAge = age;
}

// 默认权限构造器
Person(String name, int age) {
this.publicName = name;
this.protectedAge = age;
}

// 私有构造器
private Person(double salary) {
this.privateSalary = salary;
}


// ========== 各种修饰符 普通成员方法 ==========
// public 普通方法
public void publicSay() {
System.out.println("公有方法");
}

// protected 方法
protected void protectedWork() {
System.out.println("保护方法");
}

// 默认权限方法
void defaultSleep() {
System.out.println("默认权限方法");
}

// private 私有方法
private void privateShowInfo() {
System.out.println("私有方法:" + privateSalary);
}


// ========== 静态方法 ==========
public static void staticHello() {
System.out.println("静态公有方法");
}

private static void staticPrivateTip() {
System.out.println("静态私有方法");
}


// ========== final 方法(不能重写) ==========
public final void finalMethod() {
System.out.println("final 不可重写方法");
}

}

1.基本信息

功能 方法
完整全限定类名 getName
简单类名 getSimpleName
获取直接父类 getSuperclass
获取实现的所有接口 getInterfaces
获取修饰符 int 编码 getModifiers
判断是否接口 isInterface
判断是否数组 isArray
判断是否基本类型 isPrimitive
判断是否枚举 isEnum
判断是否注解 isAnnotation
获取所在包 getPackage
是否存在指定注解 isAnnotationPresent
获取指定注解 getAnnotation
获取所有注解 getAnnotations
获取本类直接注解 getDeclaredAnnotations
1
2
3
4
5
6
7
8
9
10
11
public class BeaseText {
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("com.Xc.Car");

System.out.println(aClass.getName()); //com.Xc.Car
System.out.println(aClass.getSimpleName()); //Car
System.out.println(aClass.getSuperclass()); //class java.lang.Object
System.out.println(aClass.getModifiers()); //1
System.out.println(aClass.getPackage()); //package com.Xc
}
}

2.获取构造方法

功能 方法
获取本类所有构造器 (所有权限) getDeclaredConstructors
获取本类所有 public 构造器 getConstructors
获取指定参数类型的私有 / 任意构造器 getDeclaredConstructor(Class…)
获取指定参数类型的 public 构造器 getConstructor(Class…)
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 BeaseText {
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("com.Xc.Person");

Constructor<?> constructor1 = aClass.getConstructor();
System.out.println("获取无参构造器:" + constructor1); //获取无参构造器:public com.Xc.Person()
//public com.Xc.Person(java.lang.String) 这里的参数必须和构造器参数一致
Constructor<?> constructor = aClass.getConstructor(String.class);
System.out.println("获取指定构造器:" + constructor);

System.out.println("=====");
Constructor<?>[] constructors = aClass.getConstructors();
for(Constructor c : constructors){
System.out.println(c);
}

System.out.println("=====");
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
for(Constructor c : declaredConstructors){
System.out.println(c);
}

}
}

3.创建实例

功能 方法
通过构造器创建实例 构造器.newInstance()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class BeaseText {
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("com.Xc.Person");

Constructor<?> constructor1 = aClass.getConstructor();
Constructor<?> constructor = aClass.getConstructor(String.class);

//无参构造器创建对象
Person object1 = (Person)constructor1.newInstance();
System.out.println(object1);

//有参数创建对象
Person object = (Person)constructor.newInstance("小陈");
System.out.println(object);
}
}

4.获取成员字段

功能 方法
获取本类所有字段 (所有权限) getDeclaredFields
获取本类 + 父类所有 public 字段 getFields
获取指定字段名 (任意权限) getDeclaredField(“字段名”)
获取指定 public 字段名 getField(“字段名”)
功能 方法
获取对象的字段值 get(“实例名”)
设置对象的字段值 set(“实例名”,值)
获取字段类型 getType
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
public class BeaseText {
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("com.Xc.Person");

Constructor<?> constructor = aClass.getDeclaredConstructor(String.class);
System.out.println("获取指定构造器:" + constructor);


Object p1 = constructor.newInstance("小陈");
System.out.println(p1);

/// 获取字段
//单字段 public
Field publicName = aClass.getField("publicName");
publicName.set(p1,"小王"); //set
System.out.println(publicName.get(p1));
System.out.println(publicName.getType()); //class java.lang.String

//单字段 所有修饰符
Field protectedAge = aClass.getDeclaredField("protectedAge");
protectedAge.setAccessible(true); //需要破解才能修改、查看 private字段
protectedAge.set(p1,10);
System.out.println(protectedAge.get(p1));



//多字段 public
Field[] fields = aClass.getFields();
for (Field field : fields) {
//public java.lang.String com.Xc.Person.publicName
//public static java.lang.String com.Xc.Person.staticPublicInfo
//public static final java.lang.String com.Xc.Person.NATIONALITY
System.out.println(field);
}
System.out.println("================================");
//多字段全部 public private pro 默认
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
//public java.lang.String com.Xc.Person.publicName
//protected int com.Xc.Person.protectedAge
//java.lang.String com.Xc.Person.defaultAddr
//private double com.Xc.Person.privateSalary
//public static java.lang.String com.Xc.Person.staticPublicInfo
//public static final java.lang.String com.Xc.Person.NATIONALITY
//private static final int com.Xc.Person.MAX_AGE
System.out.println(declaredField);
}
}
}

5.获取成员方法

功能 方法
获取本类所有方法 (所有权限) getDeclaredMethods
获取本类 + 父类所有 public 方法 getMethods()
获取指定方法名 + 参数类型 (任意权限) getDeclaredMethod(“方法名”,Class…参数)
获取指定 public 方法名 + 参数类型 getMethod(“方法名”,Class…参数)
功能 方法
反射调用方法 invoke
获取返回值类型 getReturnType
获取方法参数类型 getParameterTypes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class BeaseText {
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("com.Xc.Person");

Constructor<?> constructor = aClass.getDeclaredConstructor(String.class);
System.out.println("获取指定构造器:" + constructor);


Object p1 = constructor.newInstance("小陈");
System.out.println(p1);

Method method = aClass.getMethod("publicSay");
method.invoke(p1); //执行方法
System.out.println(method.getReturnType()); //void


Method privateShowInfo = aClass.getDeclaredMethod("privateShowInfo",String.class);
privateShowInfo.setAccessible(true); //私有方法必须爆破
privateShowInfo.invoke(p1,"小陈");
}
}

6.反射爆破

反射爆破(Reflection Blast),在 Java 里就是:利用反射 + setAccessible (true),强行突破 private/protected 封装,访问 / 修改私有字段、调用私有方法、甚至调用私有构造器

功能 方法
开启暴力访问,绕过 private 检查 setAccessible(true)
1
2
3
4
// 取消访问检查,暴力“开门”
field.setAccessible(true);
method.setAccessible(true);
constructor.setAccessible(true);

案例

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 BeaseText {
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("com.Xc.Person");

Constructor<?> constructor = aClass.getDeclaredConstructor(String.class);
System.out.println("获取指定构造器:" + constructor);


Object p1 = constructor.newInstance("小陈");
System.out.println(p1);

//爆破字段
Field publicName = aClass.getField("publicName");
publicName.set(p1,"小王"); //set
System.out.println(publicName.get(p1));
System.out.println(publicName.getType()); //class java.lang.String

//爆破方法
Method privateShowInfo = aClass.getDeclaredMethod("privateShowInfo",String.class);
privateShowInfo.setAccessible(true); //私有方法必须爆破
privateShowInfo.invoke(p1,"小陈");
}
}

7.动态代理

动态代理:不改动原目标类源码,在运行时自动生成代理类,对原方法做前后增强(日志、事务、权限、拦截)。

1.两种分类

JDK 动态代理

  • 要求:目标类必须实现接口
  • 原理:基于接口生成代理类
  • 只能代理接口里的方法

Cglib 动态代理

  • 要求:目标类不用实现接口,普通类就行
  • 原理:继承目标类生成子类做代理
  • 不能代理 final 类、final 方法

JDK动态代理

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 interface UserService {
void addUser();
}

//目标类
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("执行:新增用户");
}
}


//进行代理
public class MyInvocationHandler implements InvocationHandler {
// 被代理的目标对象
private Object target;

public MyInvocationHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强
System.out.println("前置:权限校验/日志");

// 执行原目标方法
Object result = method.invoke(target, args);

// 后置增强
System.out.println("后置:记录日志/提交事务");
return result;
}
}

二十九、正则表达式

正则表达式(Regex/RegExp)是一套用符号描述文本匹配规则的语法,用于匹配、提取、替换或校验字符串,几乎所有编程语言都原生支持。下面从核心语法、常用示例、贪婪 / 非贪婪、零宽断言、实战案例五部分快速掌握。


1.语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class One {
public static void main(String[] args) {
/// 1.创建规则
Pattern p = Pattern.compile("[a-zA-Z0-9]+");

String phone = "13812345678";
/// 2.进行识别
Matcher matcher = p.matcher(phone);

while(matcher.find()){
//group(0) = 拿到本次匹配到的完整结果。
System.out.println("找到:" + matcher.group(0));
}
}
}

2.相关规则

(1)字符匹配

规则 含义 Java 写法
. 任意一个字符(除换行) .
\d 数字 0-9 \\d
\D 非数字 \\D
\w 字母、数字、下划线 \\w
\W 非字母数字下划线 \\W
\s 空白(空格、制表、换行) \\s
\S 非空白 \\S
[abc] a、b、c 中任意一个 [abc]
[^abc] 不是 a、b、c [^abc]
[a-z] 小写字母 [a-z]
[A-Z] 大写字母 [A-Z]
[a-zA-Z] 所有字母 [a-zA-Z]
[0-9] 数字 [0-9]

(2)量词\限定符

规则 含义
? 0 次或 1 次
* 0 次或多次
+ 1 次或多次(至少 1 次)
{n} 恰好 n 次
{n,} 至少 n 次
{n,m} n ~ m 次

(3)边界匹配

规则 含义 Java 写法
^ 字符串开头 ^
$ 字符串结尾 $
\b 单词边界 \\b
\B 非单词边界 \\B

(4)选择匹配符

符号 作用
` `

(5)分组与引用

符号 作用
(表达式) 捕获分组,可通过 group (1)、\1、$1 引用
(?:表达式) 非捕获分组,只分组不编号、不占用分组索引
\1 \2 ... 反向引用,引用第 1、2… 个捕获分组内容
(?i) 分组内忽略大小写
(?s) 单行模式,. 匹配换行
(?m) 多行模式,^ $ 匹配每行首尾

(6)贪婪匹配

规则 含义
.* 贪婪(尽可能多匹配)
.*? 非贪婪(尽可能少匹配)

(7)断言

写法 名称 作用
(?=xxx) 正向先行断言 后面必须是 xxx
(?!xxx) 负向先行断言 后面不能是 xxx
(?<=xxx) 正向后行断言 前面必须是 xxx
(?<!xxx) 负向后行断言 前面不能是 xxx

3.Pattern类

静态方法

方法 作用
static Pattern compile(String regex) 编译正则,返回 Pattern 对象
static Pattern compile(String regex, int flags) 编译正则 + 指定模式标志
static boolean matches(String regex, CharSequence input) 快速全串匹配,不用手动创建 Pattern/Matcher
static String[] split(CharSequence input, String regex) 用正则分割字符串

实例方法

方法 作用
Matcher matcher(CharSequence input) 创建匹配器(你常用的那种写法)
String pattern() 返回当前编译的正则字符串
int flags() 返回当前编译的模式标志
String[] split(CharSequence input) 用当前正则分割字符串
String[] split(CharSequence input, int limit) 分割,指定分割次数限制

匹配模式

常量 作用
Pattern.CASE_INSENSITIVE 2 忽略大小写匹配
Pattern.MULTILINE 8 多行模式:^ $ 匹配每行开头结尾
Pattern.DOTALL 16 单行模式:. 可以匹配换行符
Pattern.COMMENTS 4 允许正则里写注释,忽略空白
Pattern.UNIX_LINES 1 \n 作为行结束符

4.Match类

作用:负责匹配、查找、提取、分组、替换,由 Patternmatcher() 方法创建。

1
2
Pattern p = Pattern.compile("正则");
Matcher m = p.matcher(待匹配字符串);

相关方法

方法 作用
boolean matches() 整个字符串完全匹配正则
boolean find() 向后查找下一个匹配的子串
boolean lookingAt() 开头开始匹配,不用整串匹配
String group() 获取整体匹配的内容
String group(int n) 获取第 n 个分组内容(分组从 1 开始)
int groupCount() 获取一共有多少个分组
int start() 获取匹配内容的起始索引
int end() 获取匹配内容的结束索引
String replaceAll(String repl) 把所有匹配到的替换成新字符串
String replaceFirst(String repl) 只替换第一个匹配
Matcher reset() 重置匹配器,回到开头
Matcher reset(CharSequence input) 重置并绑定新字符串

5.反向引用

反向引用:用 () 括号分组捕获后,在同一个正则里,用 \1 \2 \3 引用前面第 1、2、3 个分组匹配到的内容。

语法

写法 含义
(...) 捕获分组,编号从 1 开始
\1 引用第 1 个分组匹配到的内容
\2 引用第 2 个分组匹配到的内容
\3 引用第 3 个分组
1
2
3
4
5
6
7
8
9
10
11
public class Test {
public static void main(String[] args) {
String str = "11 22 34 55 67";
// (\\d) 分组一个数字,\\1 引用和它一样的 获取连续数字
Pattern p = Pattern.compile("(\\d)\\1");
Matcher m = p.matcher(str);

while (m.find()) {
System.out.println("重复数字:" + m.group());
}
}

三十、断言类(了解)

作用:开发调试用,校验条件是否为 true,不用于业务异常、不替代 if 判断。

1.原生断言

1
2
3
4
5
//简单断言
assert 布尔表达式;

//带错误信息
assert 布尔表达式 : 错误信息;

需要先开启虚拟机断言:

点击运行按钮编辑配置,点击修改选项,勾选下方,在输入 -ea即可

案例:

1
2
3
4
5
6
7
8
9
public class One {

public static void main(String[] args) {
int age = -5;
assert age > 10 : "错误"; //此时运行程序报错!!!
System.out.println("程序正常执行");
}

}

2.JDK 官方断言工具类(生产可用,无需 -ea)

方法 作用 抛出异常
requireNonNull(T obj) 非空校验 NullPointerException
requireNonNull(T obj, String msg) 非空 + 自定义提示 NullPointerException
requireNonNull(T obj, Supplier msgSupplier) 懒加载提示 NullPointerException
isNull(Object obj) 判断是否为 null 返回 boolean
nonNull(Object obj) 判断是否非 null 返回 boolean
1
2
String str = null;
Objects.requireNonNull(str, "字符串不能为空")

三十一、基础网络编程

1.基本概念

1.概念:两台设备之间通过网络实现数据传输

2.网络通信:将数据从一台设备传输到另一台设备

3.java.net包下面提供了一系列的类或接口,共程序员使用,完成网络通讯

4.网络根据不同范围分成 局域网、城域网、广域网

局域网:范围最小,仅仅一个教室或机房

城域网:可以覆盖一个城市

广域网:全国、全球等,万维网是广域网的代表!!

2. IP地址

IP 地址:互联网 / 局域网中设备的唯一身份编号,用来定位、通信。

分为两大类:

  • IPv4:32 位,常用 192.168.1.1
  • IPv6:128 位,解决地址枯竭,格式 2409:xxxx::1

(1)基本格式

格式:4 段十进制,用点分隔A.B.C.D 每一段范围:0 ~ 255 例:192.168.0.1

本质:32 位二进制,每 8 位一段

  • 1 段:8 位 → 0~255
  • 整体 32 位 → 总约 42.9 亿个地址

IP

(2)IP 地址 5 大类地址

A 类

  • 第一段:0~127
  • 默认子网掩码:255.0.0.0
  • 大型网络

B 类

  • 第一段:128~191
  • 掩码:255.255.0.0
  • 中型企业网

C 类

  • 第一段:192~223
  • 掩码:255.255.255.0
  • 家用 / 小型局域网最常用

D 类

  • 224~239:组播地址

E 类

  • 240~255:保留科研用

(3)特殊IP

127.0.0.1 本地回环、本机测试

0.0.0.0 任意地址、监听所有网卡

255.255.255.255 全网广播

网段最后一位 255:本网段广播

网段最后一位 0:代表整个网段

(4)公网 IP / 私网 IP

私网 IP(只能内网用,不能直接上网)

  • A 类:10.0.0.0 ~ 10.255.255.255
  • B 类:172.16.0.0 ~ 172.31.255.255
  • C 类:192.168.0.0 ~ 192.168.255.255

公网 IP

运营商分配,全球唯一,可直接互联网访问

(5)CIDR 斜杠表示法

写法:IP/位数例:192.168.1.0/24``/24 = 前 24 位是网络位 = 掩码 255.255.255.0

常用对应:

  • /24 → 255.255.255.0
  • /16 → 255.255.0.0
  • /8 → 255.0.0.0

3.域名

域名:给 IP 地址 起的好记的名字

IP 难记:110.242.68.4域名好记:www.baidu.com

本质:域名 = IP 的别名访问域名 → DNS 解析 → 找到 IP → 访问服务器

(1)域名结构

示例:www.baidu.com从右到左层级:

  1. 顶级域名(最右边)
  2. 二级域名
  3. 三级域名(子域名)

拆分示例

1
mail.qq.com
  • com 顶级域名
  • qq 二级域名(主体域名,企业自己注册)
  • mail 三级 / 子域名

(2)顶级域名分类

1. 通用顶级域名

  • .com 商业公司(最常用)
  • .net 网络机构
  • .org 非营利组织
  • .gov 政府
  • .edu 教育院校
  • .info 信息类

2. 国家顶级域名

  • .cn 中国
  • .jp 日本
  • .us 美国
  • .hk 香港

(3)本地DNS

“C:\Windows\System32\drivers\etc\hosts”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# localhost name resolution is handled within DNS itself.
# 127.0.0.1 localhost
# ::1 localhost

192.168.200.129 www.itchen.com #当访问 www.itchen.com 优先匹配本地DNS
# 192.168.200.129 www.itchenmall.com
# 192.168.200.129 www.itchencrm.com

4.端口

端口(Port):操作系统里的逻辑编号(0~65535),用来区分同一台电脑上的不同网络程序 / 服务

(1)端口范围与分类

端口是16 位数字,范围 0 ~ 65535,共 65536 个。

1. 知名端口(0~1023)

  • 系统 / 标准服务专用,固定分配
  • Linux 绑定需 root 权限
  • 常用:
    • 22:SSH(远程登录)
    • 80:HTTP(网页)
    • 443:HTTPS(加密网页)
    • 21:FTP(文件传输)
    • 53:DNS(域名解析)

2. 注册端口(1024~49151)

  • 自定义软件 / 数据库 / 中间件
  • 自己部署服务首选区间
  • 常用:
    • 3306:MySQL
    • 6379:Redis
    • 8080:Web 备用端口
    • 3389:Windows 远程桌面

3. 动态 / 临时端口(49152~65535)

  • 客户端临时随机占用,用完释放
  • 例如:浏览器访问网站时,本机随机用一个临时端口连接服务器 80 端口

(2)防火墙

防火墙默认拦截陌生端口

服务器部署必须放行端口(如安全组、firewalld)

示例:云服务器需放行 80/443/3306 才能对外提供服务

5.网络通信协议

网络通信协议:设备之间上网、传数据必须遵守的规则、格式、约定。没有协议,电脑互相能连上,但听不懂对方发的内容

一句话:协议就是网络的 “通用语言”

(1)网络分层模型

OSI 七层 / TCP/IP 五层(实用记五层)

  1. 应用层 浏览器、APP、所有业务协议
  2. 传输层 TCP、UDP(端口在这里)
  3. 网络层 IP 地址、路由
  4. 数据链路层 网卡、MAC 地址、交换机
  5. 物理层 网线、WiFi、信号电流

(2)核心协议分配

协议 端口 作用
HTTP 80 普通网页、接口请求
HTTPS 443 加密安全网页、接口
FTP 21 文件上传下载
SFTP 22 安全文件传输
SSH 22 远程登录服务器
DNS 53 域名转 IP
SMTP 25 发邮件
MySQL 3306 数据库连接
Redis 6379 缓存通信

(3)两大协议

TCP(可靠)

  • 面向连接,先握手再通信
  • 不丢包、不乱序、可靠传输
  • 适用:网页、文件、数据库、远程连接
  • 特点:三次握手、四次挥手

UDP(快速)

  • 无连接,不用握手,直接发
  • 速度快、开销小、可能丢包
  • 适用:游戏、直播、视频通话、DNS
  • 特点:快、不保证可靠

网络层协议

  • IP:给设备分配地址,负责路由转发
  • ICMP:ping 命令用的协议,测试网络通不通

数据链路层

  • MAC 地址:网卡物理唯一地址
  • ARP:把 IP 解析成 MAC 地址

6. InetAddress类

静态方法

方法 作用 异常
InetAddress.getByName(String host) 根据域名 / IP / 主机名获取对象 UnknownHostException
InetAddress.getLocalHost() 获取本机地址 UnknownHostException
InetAddress.getByAddress(byte[] addr) 根据字节数组 IP获取对象 UnknownHostException
InetAddress.getByAddress(String host, byte[] addr) 主机名 + IP 字节数组创建对象 UnknownHostException
InetAddress.getAllByName(String host) 获取一个域名对应的所有 IP(数组) UnknownHostException
InetAddress.getLoopbackAddress() 获取回环地址(127.0.0.1 或 ::1)

实例方法

方法 返回值 作用
getHostName() String 获取主机名 / 域名
getCanonicalHostName() String 获取规范主机名(最完整域名)
getHostAddress() String 获取IP 地址字符串(最常用)
getAddress() byte[] 获取IP 地址的字节数组
方法 返回 作用
isLoopbackAddress() boolean 是否回环地址(127.x.x.x)
isSiteLocalAddress() boolean 是否私网 IP(内网 IP)
isAnyLocalAddress() boolean 是否 0.0.0.0 任意地址
isLinkLocalAddress() boolean 是否链路本地地址(169.254.x.x)
isMulticastAddress() boolean 是否组播地址(D 类)
isReachable(int timeout) boolean 能否 ping 通(测试可达性)
1
2
3
4
5
InetAddress ip = InetAddress.getByName("www.baidu.com");

System.out.println(ip.getHostName()); // www.baidu.com
System.out.println(ip.getHostAddress()); // 110.242.68.3
System.out.println(ip.isSiteLocalAddress()); // false

7. Socket(套接字)

1.Socket开发网络应用程序被广泛采用。

2.通讯的两端都要有Socket,是两台机器间通讯的端点

3.网络通讯其实是Socket间的通信

4.Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO流传输

5.一般主动发起通讯的应用程序属于客户端,等待通讯请求的为服务端

(1)TCP编程

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
//server 服务器
public class Server {
public static void main(String[] args) throws Exception {
///TCP 发送数据

//1.创建ServerSocket对象
ServerSocket serverSocket = new ServerSocket(10002);

//2.监听客户端连接
System.out.println("等待客户端连接....");
Socket accept = serverSocket.accept();

//3.读取用户发来的数据
InputStream inputStream = accept.getInputStream();
/// 转换流,处理乱码
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
int b;
while((b = inputStreamReader.read()) != -1){
System.out.print((char)b);
}
}
}


//client 客户端
public class Client {
public static void main(String[] args) throws Exception {
/// TCP 接收数据

//1.创建Socket对象
/// 细节: 如果连接不上会报错
Socket socket = new Socket("127.0.0.1",10002); //填入发送的地址和端口号

//2.通过连接通道获取输出流
OutputStream iS = socket.getOutputStream();
//2.1写入数据
iS.write("你好,TCP".getBytes(StandardCharsets.UTF_8));

//3.释放资源
socket.close();
iS.close();
}
}

相关方法

ServerSocket(服务端)
方法 作用
ServerSocket(int port) 绑定指定端口,创建服务端
accept() 阻塞等待客户端连接,返回 Socket
close() 关闭服务端
getLocalPort() 获取监听的端口
isClosed() 判断是否已关闭
Socket(客户端)
方法 作用
Socket(String host, int port) 连接指定服务端 IP + 端口
getInputStream() 获取输入流,读对方发的数据
getOutputStream() 获取输出流,给对方发数据
close() 关闭套接字,断开连接
getInetAddress() 获取对方 IP 地址
getPort() 获取对方端口
getLocalPort() 获取本地端口
setSoTimeout(int timeout) 设置读取超时时间 (毫秒)
isClosed() 判断是否断开连接
isConnected() 判断是否已连接

(2)UDP编程

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
public class SendMessageDemo {
public static void main(String[] args) throws Exception {
/// UDP发送数据

//1.创建DatagramSocket对象 ====== (快递公司)
/// 细节: 他会随机使用一个端口,可以自定义端口
DatagramSocket datagramSocket = new DatagramSocket(6667);

//2.打包数据 ===== 包装快递
/// 2.1需要发送的数据,并转成字节类型
String str = "你好啊";
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
/// 2.2 发送给哪个主机
InetAddress byName = InetAddress.getByName("127.0.0.1");
/// 2.3 接收的端口号
int port = 6666;
DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length,byName,port);

//3.发送数据 ==== 运送货物
datagramSocket.send(datagramPacket);

//4.关闭连接
datagramSocket.close();

}
}


public class ReceiveMessageDemo {
/// UDP接收数据
public static void main(String[] args) throws Exception {
//1.创建DatagramSocket ==== 快递公司
/// 细节: 必须填写端口号,需要和发送的保持一致
DatagramSocket datagramSocket = new DatagramSocket(6666);

//2.接收数据
/// 2.1 使用一个字节数组接收数据
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length);
/// 细节:该方法会自动阻塞,直到有数据发送才继续向下往前走
System.out.println("等待数据接收...");
datagramSocket.receive(datagramPacket);
System.out.println("接收成功!!!!1");

//3.解析数据包
/// 3.1 获取数据
byte[] data = datagramPacket.getData();
int length = datagramPacket.getLength();
/// 3.2 获取主机名等
InetAddress address = datagramPacket.getAddress();
int port = datagramPacket.getPort();
System.out.println(new String(data));
System.out.println(length);
System.out.println(address);
System.out.println(port);

//4.关闭连接
datagramSocket.close();
}
}

相关方法

方法 作用
send(DatagramPacket p) 发送数据包
receive(DatagramPacket p) 阻塞接收数据包
close() 关闭 UDP 套接字
getLocalPort() 获取本地绑定端口
setSoTimeout (int 毫秒) 设置接收超时

8.更多内容

三十二、JDBC数据库连接

JDBC为访问不同的数据库提供了统一的接口,Java程序员可以使用JDBC来对数据库的各种操作

JDBC API是一系列的接口,相关类和接口在java.sql和javx.sql包里面!!!

1.连接数据库

首先要下载mysql-connector-j-9.2.0.jar驱动,并配置好!!!

数据库代码:

1
2
3
4
5
6
7
8
9
10
CREATE TABLE actor (
id INT PRIMARY KEY AUTO_INCREMENT, -- 主键自增
name VARCHAR(50), -- 姓名
sex VARCHAR(10), -- 性别
phone VARCHAR(20) -- 电话
);

-- 数据库 cc_01
-- 用户名 itchen
-- 密码 1234

连接数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class connect{
public static void main(String[] args){
//1.新的驱动名:com.mysql.cj.jdbc.Driver,旧的驱动名:com.mysql.jdbc.Driver可能会失效!!!!
Class.forName("com.mysql.cj.jdbc.Driver");

//2.创建url user password
String url = "jdbc:mysql://localhost:3306/db01";
String user = "用户名";
String password = "密码";

//3.使用driverManager类注册驱动
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
}
}


2. DriverManager类

作用:驱动管理类,注册驱动、获取数据库连接

静态方法

方法签名 返回值 核心作用
getConnection(String url) Connection 仅通过数据库 URL 获取连接
getConnection(String url, String user, String password) Connection 通过 URL、用户名、密码获取连接最常用
getConnection(String url, Properties info) Connection 通过 URL + Properties 配置(存账号密码)获取连接
registerDriver(Driver driver) void 手动注册数据库驱动
deregisterDriver(Driver driver) void 注销指定数据库驱动
getDriver(String url) Driver 根据连接 URL 获取匹配的驱动实例
Enumeration<Driver> getDrivers() Enumeration 获取当前已加载的所有数据库驱动
setLoginTimeout(int seconds) void 设置数据库登录连接超时时间(秒)
getLoginTimeout() int 获取当前设置的登录超时时间
setLogWriter(PrintWriter out) void 设置 JDBC 日志输出流
getLogWriter() PrintWriter 获取 JDBC 日志输出流
println(String message) void 向 JDBC 全局日志打印信息

3. Connection接口

代表 Java 程序 和 数据库之间的桥梁DriverManager.getConnection() 获取实例。

常用方法 返回值 作用
prepareStatement(String sql) PreparedStatement 创建预编译对象,日常最常用
createStatement() Statement 创建普通语句对象
setAutoCommit(boolean b) void 设置是否自动提交事务
commit() void 手动提交事务
rollback() void 事务回滚
close() void 关闭数据库连接
isClosed() boolean 判断连接是否已关闭
isValid(int timeout) boolean 检测连接是否有效
getMetaData() DatabaseMetaData 获取数据库元数据(版本、表结构等)
setTransactionIsolation(int level) void 设置事务隔离级别

4. Statement类(了解)

JDBC 执行 SQL 的对象 Statement stmt = conn.createStatement();创建

方法 返回值 作用
executeUpdate(String sql) int 执行增删改,返回受影响行数
executeQuery(String sql) ResultSet 执行查询,返回结果集
execute(String sql) boolean 通用执行,可执行任意 SQL
close() void 关闭 Statement 资源
getUpdateCount() int 获取最后一次增删改影响行数
getResultSet() ResultSet 获取最后一次查询结果集

增删改查操作

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
public class homeWorko1 {
public static void main(String[] args) throws Exception {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/cc_01";
String name = "itchen";
String password = "1234";

Connection connection = DriverManager.getConnection(url, name, password);

Statement statement = connection.createStatement();
//增加
String sql = "insert into actor values(null,'小陈','男','112223')";
int i = statement.executeUpdate(sql);
System.out.println(i > 0 ? "成功":"失败");


//修改
String sqlUpdate = "update actor set name='小丽',sex='女',phone='112224' where id = 2";
boolean execute = statement.execute(sqlUpdate);
System.out.println(execute); //false 增删改为 false 查为true


//删除
String sqlDelete = "delete from actor where id = 4";
boolean execute2 = statement.execute(sqlDelete);
System.out.println(execute2);


//查询
String sqlSelect = "select * from actor";
ResultSet resultSet = statement.executeQuery(sqlSelect);
while(resultSet.next()){ //next表示下一个
int id = resultSet.getInt(1);
String name1 = resultSet.getString(2);
String sex = resultSet.getString(3);
String string = resultSet.getString(4);
System.out.println(id + "\t" + name1 + "\t" + sex + "\t" + string);
}
statement.close();
connection.close();
}
}

5. sql注入问题

用户输入恶意内容,拼接进 SQL 语句,篡改原本逻辑,非法操作数据库

根源:Statement 字符串拼接 SQL,把用户输入直接当成 SQL 代码执行了。

经典案例:

1
2
3
4
select * from actor where name = '1'or' and sex = 'or '1' = '1' 

-- 这条语句我不知道名字和性别,但我还是可以查到,因为 '1' = '1' 永远成立
-- 解决方法:使用PreparedStatement,从现在开始不能使用Statement!!!!!

6. PreparedStatement(预处理)

不在使用+ 拼接sql语句,减少语法错误;解决注入问题; 减少编译次数,效率较高

方法 适用场景 返回值
executeUpdate() 增、删、改 int 受影响行数
executeQuery() 查询 select ResultSet 结果集
execute() 通用任意 SQL boolean 是否为查询
setInt (下标,值) 给占位符设整数
setString (下标,值) 给占位符设字符串
setDouble (下标,值) 给占位符设小数
setBoolean (下标,值) 给占位符设布尔值
setDate (下标,日期) 给占位符设日期

增删改查

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
72
73
74
75
76
77
78
79
public class CURL {
public static void main(String[] args) throws Exception {
Class<?> jdbc = Class.forName("com.mysql.cj.jdbc.Driver");

//账号
String username = "itchen";
String password = "1234";
String url = "jdbc:mysql://localhost:3306/cc_01";

//连接
Connection connection =
DriverManager.getConnection(url, username, password);

Scanner scanner = new Scanner(System.in);
System.out.print("输入数字(1/增 2/改 3/删 4/查):");
int i = scanner.nextInt();
switch (i){
case 1 -> System.out.println(add(connection));
case 2 -> System.out.println(update(connection));
case 3 -> System.out.println(delete(connection));
case 4 -> select(connection);
}
connection.close();

}

//增加
public static boolean add(Connection connection) throws Exception {
String name = "小美";
String sex = "女";
String tel = "111227";
String sql = "insert into actor values (?,?,?,?)"; //可以使用占位符
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,null);
preparedStatement.setString(2,name);
preparedStatement.setString(3,sex);
preparedStatement.setString(4,tel);
return preparedStatement.execute();
}

//修改
public static boolean update(Connection connection) throws Exception {
String name = "小记";
String sex = "男";
String tel = "111228";
String sql = "update actor set name=?,sex=?,phone=? where id = 6";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,name);
preparedStatement.setString(2,sex);
preparedStatement.setString(3,tel);

return preparedStatement.execute();
}

//删除
public static boolean delete(Connection connection) throws Exception {
int id = 6;
String sql = "delete from actor where id = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,id);
return preparedStatement.execute();
}

//查询
public static void select(Connection connection) throws Exception {
String sql = "select * from actor";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
ResultSet resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
String sex = resultSet.getString(3);
String tel = resultSet.getString(4);
System.out.println(id + "\t" + name + "\t" + sex + "\t" + tel);
}
}
}

//注: 模糊查询 preparedStatement.setString(1,"%name%");

7.事务

事务是一种操作的集合,它是一个不可分割的工作单位,事务会将所有的操作作为一个整体一起向系统提交或

撤销操作请求,即这些操作要么同时成功,要么同时失败

Connection相关方法

setAutoCommit(boolean b) void 设置是否自动提交事务
commit() void 手动提交事务
rollback() void 事务回滚
rollback(保存点) void 回滚到保存点
setSavepoint() void 设置保存点
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
public class CURL {
public static void main(String[] args) throws Exception {
Class<?> jdbc = Class.forName("com.mysql.cj.jdbc.Driver");

//账号
String username = "itchen";
String password = "1234";
String url = "jdbc:mysql://localhost:3306/cc_01";

//连接
Connection connection =
DriverManager.getConnection(url, username, password);

connection.setAutoCommit(false); //1.关闭自动提交
try{
String sql = "select * from actor";
int i = 1 / 0;
System.out.println("执行成功");
//2.提交
connection.commit();
}catch (Exception e){
System.out.println("发生错误回滚");
//3.回滚
connection.rollback();
}finally {
connection.close();
}
}
}

8.批处理

当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理,通常情况下比单独提交处理更有效率。

PerparedStatement相关方法

方法 作用
addBatch() 把当前参数加入批处理队列
executeBatch() 一次性执行所有批处理
clearBatch() 清空批处理队列
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
public class Batch {
//使用批处理添加5000数据
@Test
public void batch() throws Exception{
Class.forName("com.mysql.cj.jdbc.Driver");
//使用必须加上 ?rewriterBatchedStatements=true
String url = "jdbc:mysql://localhost:3306/cc_01?rewriterBatchedStatements=true";
String name = "itchen";
String password = "1234";
Connection connection = DriverManager.getConnection(url, name, password);

String sql = "insert into admin2 values(null,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
long start = System.currentTimeMillis();
for(int i = 0; i < 5000; i++){
preparedStatement.setString(1,"java" + i);
preparedStatement.setString(2,"666");
//使用批处理包
preparedStatement.addBatch();
if((i + 1) % 1000 == 0){
preparedStatement.executeBatch();
//还需要清空
preparedStatement.clearBatch();
}
}
long end = System.currentTimeMillis();
System.out.println("总时长:"+(end-start)+"ms");
JDBCUtils.close(null,preparedStatement,connection);//87ms
}
}

9.JDBC自制工具类

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
public class JDBCUtils {
//定义相关的属性(4个),因为只需要一份
private static String user;
private static String password;
private static String url;
private static String driver;

//在static代码块初始化
static{
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src/mysql.properties"));
//读取相关属性
user = properties.getProperty("user");
password = properties.getProperty("password");
url = properties.getProperty("url");
driver = properties.getProperty("driver");
} catch (IOException e) {
throw new RuntimeException(e);
}
}

//连接数据库
public static Connection getConnection(){
try {
return DriverManager.getConnection(url,user,password);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}

//关闭相关资源
public static void close(ResultSet rs, Statement st, Connection c){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(st != null){
try {
st.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(c != null){
try {
c.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}

10. URL后置参数

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
jdbc:mysql://localhost:3306/数据库名?useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true&characterEncoding=utf-8


1. useSSL=false
作用:关闭 SSL 安全加密连接
本地开发没必要开启,设为 false
不写高版本 MySQL 会告警、甚至连不上


2. serverTimezone=UTC
最关键,MySQL8 必加
作用:指定数据库时区
不加直接报错:时区异常、时间匹配错误
常用:UTC 或 Asia/Shanghai


3. rewriteBatchedStatements=true
批处理必加
作用:开启 MySQL 真正批处理优化
不加:addBatch() 只是假批量,底层还是逐条执行,没提速
做 JDBC 批量插入 / 更新必须带这个


4. characterEncoding=utf-8
作用:指定连接编码为 utf-8
防止中文乱码、插入中文问号、乱码


5. zeroDateTimeBehavior=convertToNull
作用:处理数据库 0000-00-00 非法日期
自动转成 null,不抛异常


6. allowPublicKeyRetrieval=true
作用:解决本地连接报错 public key retrieval is not allowed
本地开发偶尔需要加

11.数据库连接池

原生 JDBC 问题

每次操作数据库:新建连接 → 用完关闭连接

  • 创建连接耗时非常大
  • 频繁创建销毁,性能低
  • 连接资源无法复用,容易耗尽

连接池原理

提前初始化一批连接,放到池子中

1.优点

减少创建连接开销,速度快

连接复用,资源可控

可以设置最大连接数,防止数据库被压垮

统一管理、超时回收、自动补连接

2.常见连接池

HikariCP:SpringBoot 默认,性能最强

Druid:阿里德鲁伊,带监控、防 SQL 注入,企业最常用

C3P0、DBCP:老式,现在基本不用

3.Druid连接池

引入maven或者下载jar包druid-1.2.16.jar

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>

druid.properties文件

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
# 驱动类全限定名(MySQL8 固定写法)
driverClassName=com.mysql.cj.jdbc.Driver

# 数据库连接地址
# cc_01 是数据库名
# rewriteBatchedStatements=true 开启MySQL真正批处理优化,批量增删改必加
url=jdbc:mysql://localhost:3306/cc_01?rewriteBatchedStatements=true

# 数据库登录账号
username=itchen

# 数据库登录密码
password=1234

# 初始连接数:项目启动时,连接池直接创建10个连接放入池中
initialSize=10

# 最小空闲连接数:连接池最少保留5个空闲连接,不回收、不销毁
minIdle=5

# 最大活跃连接数:连接池同一时间最多允许50个连接同时干活
maxActive=50

# 最大等待时间:没有空闲连接时,最多等待5000毫秒(5秒)
# 超时还没拿到连接,直接抛异常
maxWait=5000

连接池

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
//基于德鲁伊数据库连接池的工具类
public class JDBCUtilsByDruid {
private static DataSource ds;

//在静态代码块完成 ds初始化
static{
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src/druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

//编写连接方法
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}

//关闭连接
public static void close(ResultSet rs, Statement st, Connection conn){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(st != null){
try {
st.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
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
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
//测试
public class TestDruid {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;

try {
// 从德鲁伊连接池获取连接
conn = DruidJdbcUtil.getConnection();
String sql = "select * from actor where id=?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 2);
rs = pstmt.executeQuery();

while (rs.next()) {
System.out.println(rs.getString("name"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 归还连接到池子,不是销毁
DruidJdbcUtil.close(conn, pstmt, rs);
}
}
}