[toc]
# day01 变量,常量,运算符
# 1. 常量
# 1.1 基本概述
不可以改变的数据,通常用于标记状态,数据记录。
例如:月份可以使用常量标记,订单状态,项目中的主要阈值
可以分为:
数值,文字,文本,布尔类型
# 1.2 常量分类
数值
整数
·1 2 3 4 5 6 7 8 9 10
-1 -2 -3 -4 -5 -6
小数/浮点数
0.5 3.1415926 0.618 11.11 12.12
文字 / 字符常量
Java 中规定,字符常量使用英文单引号包含【单个元素】
'你' '我' '他'
'A' 'B' 'C'
'1' '2' '3'
'ABC' 错误情况!!!
文本 / 字符串常量
Java 中规定,字符串常量使用英文双引号包含的所有内容
字符串也是开发中最为稳定的数据载体,一般用于数据信息的传递,例如 前端到后端,后端到前端【XML JSON 文件】
"曲曲折折的荷塘上面,弥望着甜甜的叶子~~"
"朝辞白帝彩云间,千里江陵一日还..."
"怒发冲冠,凭栏处,潇潇雨歇..."
布尔类型
真假关系,T ==> true F ==> false
true; | |
false; |
# 1.3 字符相关内容
# 1.3.1 字符编码集问题
对于计算机而言,所有的文字都是一张图片,为了更好的展示文本信息,每一个字符都有一个独立的唯一的编号,用于文本数据在屏幕中的展示
重点:统一编码集,保证中文不乱码!!!
开发中使用的常用编码
GBK
中国国标,包含中国简体文字和少数民族文字。
BIG5
中国繁体字编码,主要用于中国台湾,中国香港,中国澳门,和新加坡
UTF-8
国际码,支持中文简体,繁体和英文,开发中最常用的编码集。
要求前端(HTML 小程序 App),后端(Java),数据库(MySQL Oracle)
# 1.3.2 ASCII 编码
计算机中所有支持的编码集,前 128 位都是 ASCII
要求
- 任何字符有且只允许使用字符本身,不允许使用编码值
- 大写英文字母的编码值小于小写英文字母
- 英文字母在编码集中非连续,中间有其他标点符号。
- 数字字符顺序是 0 ~ 9
# 1.3.3 转义字符
常见,但是不用
\" \'
\n \t
后期项目中出现 SQL 语句中的 \n \t \" \' 必须去除
# 1.3.4 \40 是什么???
\ 是转义字符标记
40 因为之前是 \ 为转义字符,当前 40 为【八进制】数据 40 ==> 040
对应八进制ASCII码表 \40 ==> sp 空格
# 2. 变量
# 2.1 变量分类
基本数据类型:整数,浮点数,字符,布尔类型
# 2.2 变量的定义格式
数据类型 变量名 = 初始化数据;
数据类型:
描述当前变量可以存储的数据是那些类型,那些范围,那些要求
变量名:
要求符合标识符规范,见名知意,通俗易懂,类型指向性,功能描述性
=:
赋值,将赋值号右侧的数据,赋值给左侧【变量】
初始化数据:
根据当前业务逻辑所需,给予变量,合理合适合法的数据内容。
# 2.3 变量数据类型
整数
数据类型 | 占用内存空间大小 | 数据存储范围 |
---|---|---|
byte | 1 字节 | -128 ~ 127 |
short | 2 字节 | -32768 ~ 32767 |
int | 4 字节 | -2^31 ~ 2^31 - 1 |
long | 8 字节 | -2^63 ~ 2^63 - 1 |
浮点数
数据类型 | 占用内存空间大小 | 数据存储范围 |
---|---|---|
float | 4 字节 | 10^38 |
double | 8 字节 | 10^308 |
字符类型
涉及到字符操作有且只允许使用字符本身。
数据类型 | 占用内存空间大小 | 数据存储范围围 |
---|---|---|
char | 2 字节 | 可以存储中文 |
布尔类型
数据类型 | 占用内存空间大小 | 数据存储范围围 |
---|---|---|
boolean | 视情况而定 | true or false |
# 2.4 变量定义案例
整数变量定义案例
/* | |
整型变量定义 | |
byte short int long | |
定义变量的格式: | |
数据类型 变量名 = 初始化数据; | |
变量存储赋值号右侧的数据内容 | |
*/ | |
byte num1 = 10; | |
short num2 = 20; | |
int num3 = 30; | |
long num4 = 50; | |
//sout 打印展示变量中存储的数据内容情况 | |
System.out.println(num1); | |
System.out.println(num2); | |
System.out.println(num3); | |
System.out.println(num4); |
浮点类型变量定义案例
/* | |
计算机数据存储原则 | |
1. 数据精度最高 | |
2. 数据空间选择最大 | |
3.14 作为浮点数,计算机会默认认为是一个 double 类型数据。 | |
double 类型数据占用字节数据为 8 个字节,float 类型变量占用内存空间 4 个字节 | |
直接赋值操作会导致内存空间占用不同,计算机不允许通过。 | |
【解决】 | |
告知计算机当前 3.14 常量对应的数据类型为 float 类型 | |
在常量之后【必须是】加上大写 F | |
*/ | |
float num1 = 3.14F; | |
double num2 = 3.14; | |
System.out.println(num1); | |
System.out.println(num2); |
字符和布尔类型变量定义案例
/* | |
char | |
字符常量操作有且只允许使用字符本身,不允许使用编码值 | |
*/ | |
char ch1 = 'A'; | |
char ch2 = '你'; | |
char ch3 = '我'; | |
char ch4 = '3'; | |
System.out.println(ch1); | |
System.out.println(ch2); | |
System.out.println(ch3); | |
System.out.println(ch4); | |
/* | |
boolean 类型变量 | |
boolean 类型变量常用名称 | |
flag 标记 | |
ret ==> result 结果 | |
*/ | |
boolean flag = true; | |
boolean ret = false; | |
System.out.println(flag); | |
System.out.println(ret); |
# 2.5 错误情况处理和分析
# 2.5.1 float 类型变量赋值要求
计算机数据存储原则
1. 数据精度最高
2. 数据空间选择最大
3.14 作为浮点数,计算机会默认认为是一个 double 类型数据。
double 类型数据占用字节数据为 8 个字节,float 类型变量占用内存空间 4 个字节
直接赋值操作会导致内存空间占用不同,计算机不允许通过。
【解决】
告知计算机当前 3.14 常量对应的数据类型为 float 类型
在常量之后【必须是】加上大写 F
# 2.5.2 long 类型变量赋值建议
推荐在给予 long 类型变量赋值常量,常量以大写 L 结尾,以及在使用整数常量超出 int 默认范围的情况下,需要使用大写 L 结尾告知计算机当前常量对应数据类型为 long 类型,扩大数据支持的范围
# 2.5.3 变量名在一定范围内唯一,不可以重复定义
main() { | |
// 声明一个 int 类型变量,变量名为 num,初始化数据为 10 | |
int num = 10; | |
// 声明一个 int 类型变量,变量名为 num,初始化数据为 10 | |
int num = 20; | |
// 语法错误!!!变量重复定义,Java 中不允许。 | |
} |
# 2.5.4 变量未赋值不得参与其他非赋值操作
main() { | |
int num; | |
// 语法报错 the local variable num may not have been initialized | |
System.out.println(num); | |
} |
# 2.5.5 严格遵守数据类型一致化原则
main() { | |
int num = 5.5; | |
// 语法错误,给予 int 类型变量赋值使用的常量为 double 类型,无法满足数据类型一致化要求。 | |
} |
# 2.6 AJCG 阿里巴巴代码规约 - 标识符规范
1. 标识符规范要求有且只允许使用英文字母(A ~ Z a ~ z),数字字符(0 ~ 9), 以及唯一可以使用的标点符号( _ ) 下划线
2. 标识符要求英文字母开头
3. 标识符没有严格的长度限制,长度范围根据实际使用情况决定。
4. 标识符要求见名知意,动宾结构,类型指向和功能指向。
见名知意/类型指向 变量名 studentName userPassword
动宾结构/功能指向 方法名 setStudentAge getParameter
5. 标识符要求符合以下命名要求
大驼峰
所有单词首字母大写,适用于类名和接口名
StudentController ArrayIndexOutOfBoundsException
InvocationTargetException
小驼峰
第一个单词首字母小写,之后的每一个单词首字母大写,适用于变量名和方法名
getStudentAddress queryAllStudentList
updateStudentInfoById
下划线
所有字母全部大写,使用下划线分割 _ ,适用于【带有名称的常量】
MONDAY DAY_OF_WEEK DAY_OF_YEAR WEEK_OF_YEAR
MAX_VALUE MIN_VALUE MAX_ARRAY_SIZE DEFAULT_CAPACITY
6. 标识符在一定范围内唯一
7. 已经被 Java 占用的关键字和保留字不得用于自定义标识符使用,代码中有颜色变化的都不可以使用。
# 3. 运算符
# 3.1 算术运算符
Java 支持的算术运算符: + - * / % () =
特征:
- 小括号优先级最高,如果在运行过程中,对于代码执行优先级有需求,简单粗暴加括号
- 先乘除,后加减,按照基本的从左至右原则完成
- = 优先级最低,作用是将赋值号右侧的数据,赋值给左侧的变量,严格遵从数据类型一致化要求
package com.qfedu.b_operation; | |
/** | |
* 算术运算符 | |
* 1. 变量有且只有被赋值的情况下,才会修改变量存储的数据内容情况 | |
* 2. 关注变量在运行过程中,数据变化的情况 | |
* | |
* @author Anonymous 2023/7/18 15:57 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
int num1 = 10; | |
int num2 = 20; | |
num1 = num1 * num2; // num1 = 200 num2 = 20 | |
num1 = num1 - num2; // num1 = 180 num2 = 20 | |
num1 = num1 / num2; // num1 = 9 num2 = 20 | |
num1 = num1 % num2; // num1 = 9 num2 = 20 | |
num1 = num1 + num2; // num1 = 29 num2 = 20 | |
System.out.println(num1); | |
System.out.println(num2); | |
} | |
} |
# 3.2 增强版算术运算符
Java 中支持的增强版算术运算符: +=, -= ,*=, /=, %=
main() { | |
int num1 = 10; | |
int num2 = 20; | |
num1 = num1 + num2; | |
// 等价于 | |
num1 += num2; | |
// | |
} |
public class Demo2 { | |
public static void main(String[] args) { | |
float num1 = 3.5F; | |
/* | |
3.5 为 double 类型,num1 是一个 float 类型,这里计算结果之后,计算机认为 | |
num1 + 3.5 ==> double 类型,赋值给 float 类型的 num1 无法满足数据类型 | |
一致化要求,赋值失败。不允许使用【基本数据类型强转】 | |
*/ | |
// num1 = num1 + 3.5; | |
/* | |
增强版算术运算符,可以自动完成类型转换操作,float += double | |
Java 会自动将 double 转换为 float 赋值给变量 num1 | |
*/ | |
num1 += 3.5; | |
} | |
} |
# 3.3 关系运算符
Java 中关系运算符: > < >= <= != ==
关系运算符对外的结果内容为 boolean 类型,常用于条件判断。
ret = 10 == 10L; | |
System.out.println(ret); // true | |
ret = 10 == 10.0F; | |
System.out.println(ret); // true | |
ret = 10 == 10.0; | |
System.out.println(ret); // true |
# 3.4 逻辑运算符
Java 中逻辑运算符,需要判断逻辑运算符两边的表达式,最终对外结果为 boolean 类型
&& 与,同真为真,有假【即】假
|| 或 ,有真【即】真,同假为假
! 非 强牛 犟
/** | |
* 逻辑运算符 | |
* | |
* @author Anonymous 2023/7/18 16:39 | |
*/ | |
public class Demo4 { | |
public static void main(String[] args) { | |
boolean ret = 10 > 5 && 5 > 3; | |
System.out.println(ret); | |
ret = 10 > 5 && 10 > 20; | |
System.out.println(ret); | |
ret = 10 > 5 || 10 > 20; | |
System.out.println(ret); | |
ret = 10 > 15 || 10 > 20; | |
System.out.println(ret); | |
ret = !(10 == 10); | |
System.out.println(ret); | |
ret = !(10 != 10); | |
System.out.println(ret); | |
} | |
} |
# 3.5 自增自减运算符
Java 中自增自减运算符格式 ++ --
【建议】
自增自减运算符是针对于【变量】数据内容 自增 1 或者 自减 1,推荐使用
+= 1 和 -= 1 代替。
必须使用自增自减运算符,推荐【单独成行】或者【单独模块】,降低代码中的【歧义】
【语法】
1. 有且只允许操作变量,常量不允许使用
2.
自增自减运算符在变量之前,首先执行自增自减操作,之后参与代码运行
自增自减运算符在变量之后,首先参与代码运行,之后执行自增自减操作
/** | |
* 自增自减运算案例 | |
* | |
* @author Anonymous 2023/7/18 16:49 | |
*/ | |
public class Demo5 { | |
public static void main(String[] args) { | |
int num = 10; | |
System.out.println(++num); // 11 | |
System.out.println(num); // 11 | |
System.out.println(num++); // 11 | |
System.out.println(num); // 12 | |
int num1 = 5; | |
int ret = num1++ * ++num1 - num1++ + ++num1 / num1--; | |
/* | |
num1++ 取 5 num1 = 6 | |
++num1 num1 = 7 取 7 | |
num1++ 取 7 num1 = 8 | |
++num num1 = 9 取 9 | |
num--; 取 9 num1 8 | |
5 * 7 - 7 + 9 / 9 => 35 - 7 + 1 ==> 28 + 1 => 29 | |
*/ | |
System.out.println(ret); | |
} | |
} |
# 3.6 逻辑运算符短路原则
案例一【逻辑与短路原则】
逻辑与要求:同真为真,有假【即】假
逻辑与短路原则,一旦发现运算表达式结果中存在 false 条件,整个表达式最终结果为 false,false 条件之后的所有内容不在执行。降低计算机的运算逻辑复杂度。
main() { | |
int num = 10; | |
boolean ret = 10 > 20 && ++num > 10; | |
ret = false; | |
num = 10; | |
} |
案例二【逻辑或短路原则】
逻辑或要求:有真【即】真,同假为假
逻辑或短路原则,一旦发现运算表达式中存在 true 条件,整个表达式结果为 true,true 条件之后的所有内容不再执行。降低计算机的运算逻辑复杂度。
main() { | |
int num = 10; | |
boolean ret = 10 > 5 || ++num > 10000; | |
ret = true; | |
num = 10; | |
} |
# day02 分支,循环
# 1. 分支结构
分支结构来处理生活逻辑中,需要进行判断,选择,多种条件的情况。例如:商城打折,会员价,会员主题
if 分支结构
switch case 分支结构
# 1.1 if 分支
# 1.1.1 if 结构
if (/* 条件判断,结果类型为 boolean */) { | |
// 满足 if 之后条件判断为 true ,执行大括号内容代码块 | |
} | |
/* | |
执行流程: | |
1. 判断 if 之后小括号中表达式结果为 true | |
2. true 执行大括号代码块内容 | |
3. false 直接执行大括号之和代码 | |
*/ |
/** | |
* if 分支结构 | |
* | |
* @author Anonymous 2023/7/19 9:39 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
/* | |
判断某一年是否为 闰年 假设 变量为 year | |
闰年条件: | |
1. 可以被 4 整除但是不能被 100 整除 | |
year % 4 == 0 && year % 100 != 0 | |
2. 可以被 400 整除 | |
year % 400 == 0 | |
*/ | |
int year = 2023; | |
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) { | |
System.out.println("是一个闰年!!!"); | |
} | |
System.out.println("代码继续执行~~~"); | |
} | |
} |
# 1.1.2 if else 结构
if (/* 条件判断,结果类型为 boolean */) { | |
// 条件判断结果为 true 对应的代码执行 | |
} else { | |
// 条件判断结果为 false 对应的代码执行 | |
} | |
/* | |
执行流程: | |
1. 判断 if 之后小括号内容是否 true | |
2. true 执行 if 之后大括号代码块内容 | |
3. false 执行 else 之后大括号代码块内容 | |
4. 跳出大括号执行后续代码。 | |
*/ |
/** | |
* if else 分支结构 | |
* | |
* @author Anonymous 2023/7/19 9:51 | |
*/ | |
public class Demo2 { | |
public static void main(String[] args) { | |
/* | |
1. 判断一个字符是否为英文字母,假设变量为 ch | |
ch >= 'A' && ch <= 'z' 不允许,因为大小写英文字母之间在 ASCII 中有其他字符存在 | |
正确条件 | |
ch >= 'A' && ch <= 'Z' 大写字母数据范围 | |
ch >= 'a' && ch <= 'z' 小写字母数据范围 | |
*/ | |
char ch = '\\'; | |
if (ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z') { | |
System.out.println("英文字母"); | |
} else { | |
System.out.println("不是英文字母"); | |
} | |
} | |
} |
# 1.1.3 if else if 结构
if (/* 条件 1 */) { | |
// 处理方式 1 | |
} else if (/* 条件 2 */) { | |
// 处理方式 2 | |
} else if (/* 条件 3 */) { | |
// 处理方式 3 | |
} else if (/* 条件 4 */) { | |
// 处理方式 4 | |
} else { | |
// 没有任何的一个匹配,else 为最终处理方式 | |
} | |
/* | |
执行流程: | |
1. 当前判断数据内容和 if 之后的条件进行匹配 | |
2. 如果有存在的 if 条件匹配,执行对应的处理方式 | |
3. 如果没有任何一个 if 条件匹配,执行 else 中的最终处理方式 | |
*/ |
/** | |
* if else if 分支结构 | |
* | |
* @author Anonymous 2023/7/19 10:06 | |
*/ | |
public class Demo3 { | |
public static void main(String[] args) { | |
/* | |
狗鸽超市消费 | |
0 ~ 500 9.5 | |
500 ~ 2000 9.0 | |
2000 ~ 5000 8.8 | |
5000 ~ 10000 8.5 | |
10000 ~ 50000 8.0 | |
50000 ~ 200000 7.8 | |
*/ | |
// 小数据范围条件到大数据范围条件 | |
int money = 300000; | |
if (money > 0 && money < 500) { | |
System.out.println(money * 0.95); | |
} else if (money >= 500 && money < 2000) { | |
System.out.println(money * 0.9); | |
} else if (money >= 2000 && money < 5000) { | |
System.out.println(money * 0.88); | |
} else if (money >= 5000 && money < 10000) { | |
System.out.println(money * 0.85); | |
} else if (money >= 10000 && money < 50000) { | |
System.out.println(money * 0.8); | |
} else if (money >= 50000) { | |
System.out.println(money * 0.78); | |
} else { | |
System.out.println("欢迎光临~~~~"); | |
} | |
System.out.println(); | |
// 大数据范围条件到小数据范围条件 | |
if (money >= 50000) { | |
System.out.println(money * 0.78); | |
} else if (money >= 10000) { | |
System.out.println(money * 0.8); | |
} else if (money >= 5000) { | |
System.out.println(money * 0.85); | |
} else if (money >= 2000) { | |
System.out.println(money * 0.88); | |
} else if (money >= 500) { | |
System.out.println(money * 0.90); | |
} else { | |
System.out.println(money * 0.95); | |
} | |
} | |
} |
# 1.2 switch case
// 变量支持的数据类型: int char String 枚举,推荐使用 int 和 枚举 | |
switch (/* 变量 存储数据内容 */) { | |
case 常量 1: | |
处理方式 1; | |
break; | |
case 常量 2: | |
处理方式 2; | |
break; | |
case 常量 3: | |
处理方式 3; | |
break; | |
default: | |
最终处理方式; | |
break; | |
} | |
/* | |
执行流程 | |
1. switch case 结构会提取小括号中的变量存储数据内容 | |
2. 和 case 之后的条件进行匹配。如果有匹配项,执行对应的处理方式,【case 选择平级】 | |
3. 如果没有任何一个 case 选择匹配,执行最终 default 对应处理方式。 | |
*/ |
/** | |
* switch case 结构 | |
* | |
* @author Anonymous 2023/7/19 11:10 | |
*/ | |
public class Demo4 { | |
public static void main(String[] args) { | |
/* | |
点菜 | |
1. GOD Use VPN 1500 | |
2. M10 菲力 900 | |
3. 蟹粉豆腐 纯螃蟹版 7500 | |
4. 一斤鱼子酱拌饭 5000 | |
*/ | |
int choose = 50; | |
switch (choose) { | |
case 1: | |
System.out.println("GOD Use VPN 佛跳墙 1500"); | |
break; | |
case 2: | |
System.out.println("M10 菲力 900"); | |
break; | |
case 3: | |
System.out.println("蟹粉豆腐 纯螃蟹版 7500"); | |
break; | |
case 4: | |
System.out.println("一斤鱼子酱拌饭 5000"); | |
break; | |
default: | |
System.out.println("请重新选择"); | |
break; | |
} | |
} | |
} |
# 2. 循环结构
循环结构主要有 while 循环,do while 循环 ,for 循环,后续可以使用 Stream 流来处理大量数据,提升效率
# 2.1 while 循环
while (/* 循环条件,要求为 boolean */) { | |
// 循环体【循环控制 / 循环条件控制】 | |
} | |
/* | |
执行流程: | |
1. 判断 while 之后循环条件是否为 true | |
2. true 执行循环体【循环控制 / 循环条件控制】,重新判断 while 之和循环条件 | |
3. false 跳出循环 | |
*/ |
/** | |
* while 循环 | |
* | |
* @author Anonymous 2023/7/19 11:34 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
int num = 10; | |
while (num > 0) { | |
System.out.println("吃饭,睡觉,写代码~~~"); | |
// 循环控制 / 循环变量控制 | |
num -= 1; // num--; --num; | |
} | |
} | |
} |
# 2.2 do while 循环
do { | |
// 循环体【循环控制 / 循环条件控制】 | |
} while (/* 循环条件,要求为 boolean */); | |
/* | |
执行流程: | |
1. do while 循环结构第一次循环体执行,无需任何的限制 | |
2. 执行第一次循环体内容之后,执行 while 之后的循环控制判断 | |
3. true 结果情况下,继续执行循环体 | |
4. false 结果情况下,终止循环 | |
*/ |
package com.qfedu.b_loop; | |
/** | |
* do while 循环结构 | |
* | |
* @author Anonymous 2023/7/19 11:44 | |
*/ | |
public class Demo2 { | |
public static void main(String[] args) { | |
int num = 10; | |
do { | |
System.out.println("周末去露营~~~"); | |
num--; | |
} while (num > 0); | |
} | |
} |
# 2.3 for 循环【重点】
for (/* 循环条件初始化 */; /* 循环条件判断 */; /* 循环条件修改 */) { | |
// 循环体 | |
} |
package com.qfedu.b_loop; | |
/** | |
* for 循环 | |
* | |
* @author Anonymous 2023/7/19 14:34 | |
*/ | |
public class Demo3 { | |
public static void main(String[] args) { | |
/* | |
for 循环中常用的循环变量 | |
i j k l m n | |
*/ | |
for (int i = 0; i < 10; i++) { | |
System.out.println("前排推荐<<纸牌屋>>"); | |
} | |
} | |
} |
# 2.4 break 关键字
循环执行过程中,满足某些条件的情况下,终止循环运行。break 操作需要通过条件控制,这里需要 if 分支结构。
/** | |
* break 关键字演示 | |
* | |
* @author Anonymous 2023/7/19 14:40 | |
*/ | |
public class Demo4 { | |
public static void main(String[] args) { | |
for (int i = 0; i < 10; i++) { | |
/* | |
当 i 的值为 5 时,利用 break 关键字跳出循环结构 | |
5 == i ---> 5 = i | |
i == 5 ---> i = 5 | |
常量和变量等值判断,推荐常量在前,变量在后 | |
*/ | |
if (5 == i) { | |
break; | |
} | |
System.out.println(i); | |
} | |
} | |
} |
# 2.5 continue 关键字
zhong 止当前循环,直接进入下一次循环,while 循环和 do while 中使用需要非常小心,
不推荐在 while 和 do while 循环使用,有且只在 for 循环中使用
/** | |
* continue | |
* | |
* @author Anonymous 2023/7/19 14:48 | |
*/ | |
public class Demo5 { | |
public static void main(String[] args) { | |
for (int i = 0; i < 10; i++) { | |
if (i % 3 == 0) { | |
System.out.println("continue 操作"); | |
continue; | |
} | |
System.out.println(i); | |
} | |
} | |
} |
# 3. 作业题
1. 展示0 ~ 100以内的所有偶数
2. 展示0 ~ 100以内的所有奇数
3. 计算1 - 150 的和
4. 逢7过!!! 【100以内的】
5. 多种方法打印26个小写字母
6. 例如:
输入 5 8;
计算 5 + 6 + 7 + 8;
输出 26.
7. 整数逆序输出, 例如输入一个整数12345,输出5 4 3 2 1
8.
*****
*****
*****
*****
*****
9.
*
**
***
****
*****
10.
*
***
*****
*******
*********
11.【拔高题】
*
***
*****
*******
*********
*******
*****
***
*
12.【拔高题】
A
ABA
ABCBA
ABCDCBA
ABCDEDCBA
ABCDCBA
ABCBA
ABA
A
13.【拔高题】
###*###
##*#*##
#*###*#
*#####*
#*###*#
##*#*##
###*###
# day03 方法,数组初识
# 1. 方法【重点】
# 1.1 方法重点
- 入参和出参概念
- 方法封装的基本格式
- 方法的三要素 返回值 方法名 形式参数列表
- 方法功能单一化
# 1.2 方法的基本格式
修饰符 返回值类型 方法名(形式参数列表) { | |
方法体; | |
} | |
Modifers ReturnType methodName(ParameterTypes) { | |
Method body | |
} |
- 返回值类型 / ReturnType
- 方法执行之后对外的结果返回,在控制台打印展示不是返回值。
- 方法名 methodName
- 见名知意,动宾结构,当前方法执行唯一的名称,CPU 需要通过方法名找到对应的目标执行二进制字节码内容
- 形式参数列表
- 明确当前方法执行是否需要外来数据支持,方法执行过程中必要提供对应数据类型的实际参数。
# 1.3 有参数有返回值方法【 重点
】
方法执行有必要的参数需求,并且对外有结果产出。
需求:计算两个 int 类型数据的和
方法分析:
目前阶段方法所需的修饰符:
- public static [不要问]
返回值类型:
- 可以确定为整数类型,可以选择 int or long ,long 类型考虑两 int 类型数据相加之后超出 int 数据存储范围
方法名:
- 见名知意,动宾结构,可以选择方法名为 sum
形式参数列表:
- 需求中告知,计算两个 int 类型数据,当前方法的参数为两个 int 类型,需要提供参数类型和参数名称,格式
- (int num1, int num2) int 告知参数类型,num1 num2 是参数名称,同时可以作为方法内部的变量使用。
方法声明
public static int sum(int num1, int num2);
方法实现和调用
package com.qfedu.a_method; | |
/** | |
* 有参数有返回值方法案例 | |
* 注意: | |
* 1. 方法和方法之间是平级关系,方法不可以直接嵌套另一个方法。 | |
* 2. 强制要求,必须完成自定义方法的完整【文档注释 JavaDoc】内容。 | |
* | |
* @author Anonymous 2023/7/20 10:05 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
/* | |
调用方法 | |
1. 方法名,通过方法名称调用对应方法 | |
2. 方法如果有参数需求,必须提供符合参数类型要求的实际数据 | |
3. 方法如果存在返回值类型,可以选择使用对应数据类型的变量接收存储对应返回值情况 | |
*/ | |
int value = 0; | |
System.out.println("方法调用之前: " + value); | |
value = sum(10, 20); | |
System.out.println("方法调用之后: " + value); | |
} | |
/** | |
* 计算用户提供的两个 int 类型数据之和,通过方法返回值反馈 | |
* | |
* @param num1 int 类型数据 | |
* @param num2 int 类型数据 | |
* @return 用户提供的两个 int 类型数据之和 | |
*/ | |
public static int sum(int num1, int num2) { | |
/* | |
return 关键字 | |
1. 将关键字之后的数据反馈到方法之外,要求 return 之后数据类型和方法声明的返回值数据类型一致。 | |
2. return 关键字可以终止方法的运行。 | |
*/ | |
return num1 + num2; | |
} | |
} |
# 1.4 有参数无返回值方法
有参数需求,但是没有数据反馈,适用于:日志记录,数据存储,数据发送
需求:在控制台展示用户提供的 int 类型数据
方法分析:
目前阶段方法所需的修饰符:
- public static [不要问]
返回值类型:
- 当前方法有且只是在控制台展示数据内容,没有数据反馈,无需返回值
- void 表示当前方法没有返回值类型。
方法名:
- 需求展示用户提供的 int 类型数据,方法名推荐 printIntValue,logIntValue,showIntValue
形式参数列表:
- 展示用户提供的 int 类型数据,数据个数 1 个,数据类型为 int
- (int num)
方法声明:
public static void printIntValue(int num);
方法实现和调用
package com.qfedu.a_method; | |
/** | |
* 有参数无返回值方法案例 | |
* | |
* @author Anonymous 2023/7/20 10:52 | |
*/ | |
public class Demo2 { | |
public static void main(String[] args) { | |
/* | |
通过方法名调用对应方法,同时提供必要的参数 | |
*/ | |
printIntValue(10); | |
/* | |
如果方法声明明确告知需要指定类型参数,调用时提供的实际参数 | |
数据类型,数据个数,数据类型顺序必须完全一致。 | |
*/ | |
// printIntValue(10.5); | |
} | |
/** | |
* 在控制台展示用户提供的 int 类型数据内容 | |
* | |
* @param num 用户提供的 int 类型数据 | |
*/ | |
public static void printIntValue(int num) { | |
System.out.println("用户提供的 int 类型数据为: " + num); | |
} | |
} |
# 1.5 无参数有返回值方法
当前方法执行无需任何的外来数据,直接提供数据给方法外部使用,适用于:提取类内数据存储情况,文件数据内容获取,数据库存储内容获取...
需求:完成方法,方法执行之后对外反馈 int 类型数据 5
方法分析:
目前阶段方法所需的修饰符:
- public static [不要问]
返回值类型:
- 方法的最终执行目标是返回一个 int 类型数据 5,所以当前方法的返回值为 int 类型
方法名:
- 类似于数据提取方法,通常推荐的方法前缀为 get,例如: giveMeFive, getNum, getValue
形式参数列表:
- 当前方法执行无需任何的外来数据,直接调用获取数据
- () 方法没有参数需求,但是必须有小括号,变量名和方法名的最大区别就是小括号
方法声明:
public static int getValue();
方法实现和调用
package com.qfedu.a_method; | |
/** | |
* 无参数有返回值方法演示 | |
* | |
* @author Anonymous 2023/7/20 11:06 | |
*/ | |
public class Demo3 { | |
public static void main(String[] args) { | |
int value = 0; | |
System.out.println("方法调用之前: " + value); | |
value = getValue(); | |
System.out.println("方法调用之后: " + value); | |
} | |
/** | |
* 当前方法返回一个 int 类型数据 5 | |
* | |
* @return 方法返回值为 5 | |
*/ | |
public static int getValue() { | |
return 5; | |
} | |
} |
# 1.6 无参数无返回值方法
方法没有参数和返回值,一般用于类内的辅助方法,对外不公开核心模块方法
需求:在控制台展示 Hello World!
方法分析:
目前阶段方法所需的修饰符:
- public static [不要问]
返回值类型:
- 当前方法功能是直接在控制台展示,对外没有数据提供。【切记】控制台打印不是返回值!!!
方法名:
- 见名知意,动宾结构,小驼峰命名法,printHelloWorld
形式参数列表:
- 方法执行目标已明确,无需任何的外来数据内容
- ()
方法声明:
public static void printHelloWorld();
方法实现和调用
package com.qfedu.a_method; | |
/** | |
* 无参数无返回值方法 | |
* | |
* @author Anonymous 2023/7/20 11:19 | |
*/ | |
public class Demo4 { | |
public static void main(String[] args) { | |
printHelloWorld(); | |
} | |
/** | |
* 当前方法在控制台展示 Hello World! | |
*/ | |
public static void printHelloWorld() { | |
System.out.println("你好,世界! Hello World!"); | |
} | |
} |
# 1.7 方法练习题
/* | |
要求: | |
1. 方法功能单一化!!!越单一越好!!! | |
2. 不允许在方法中使用 Scanner 从键盘上获取数据!!! | |
3. 所有方法必须调用通过,可以运行 | |
4. 方法体实现,调用通过就 OK | |
5. 完成每一个方法对应的文档注释,注意格式。 | |
*/ | |
// 封装一个方法,返回两个整数里的较大那个 | |
public static int maxOfNumber(int num1, int num2) { | |
} | |
// 封装一个方法,返回两个浮点数里的较小那个 | |
public static double minOfNumber(double num1, double num2) { | |
} | |
// 封装一个方法,来判断一个字符是否是大写字母 | |
// 如果是,返回 true, 否则返回 false | |
public static boolean isMyUpper(char c) { | |
} | |
// 封装一个方法,来判断一个字符是否是数字字符 | |
// 如果是,返回 true,否则返回 false! | |
public static boolean isMyNumber(char c) { | |
} | |
// 封装一个方法,来判断一个字符是否是英文字母 | |
// 如果是,返回 true, 否则返回 false! | |
public static boolean isMyEnglish(char c) { | |
} | |
// 封装一个方法,传入一个字符,如果是大写那么转化小写然后返回,否则保持不变返回 | |
public static char toMyLower(char c) { | |
} | |
// 封装一个方法,实现将一个正整数,倒序打印输出!1234 4321 | |
public static void reverse(int number) { | |
} | |
// 表达式(立方) 编写程序,计算该数的立方 | |
public static double cube(double number) { | |
} | |
// 流程控制(闰年问题) 输入一个年份,判断该年份是平年还是闰年: | |
// 注:闰年就是第二个月有 29 天的那一年,能被 4 整除但是不能被 100 整除的是闰年,或者能被 400 整除的也是闰年 | |
// 如果是返回 true 不是返回 false | |
public static boolean isLeap(int year) { | |
} | |
// 表达式 (正数判断) 传入一个数字,判断这个数是否是正数 (是,打印 YES,不是输出 NO) | |
public static void isPositiveNumber(int num) { | |
} |
# 1.8 其他练习题
1. 斐波那契数列 30 位以内数据实现
1 1 2 3 5 8 13 21 34 55
前两位固定位 1 ,从第三位开始,数据为前两位数据之和
2. 阶乘代码实现,20 以内的阶乘
重点考虑数据类型
# 2. 数组初识
# 2.1 数组的必要性
开发中存在以下场景,代码中可能会存在相同数据类型的数据大量使用,如果按照变量逐一定义的模式,会导致
- 代码变量定义冗余
- 操作不便
- 阅读性极差
考虑数据管理,基本情况下采用【数组形式】
- 数据类型存储一致
- 数据存储空间连续
- 每一个单元空间都有唯一的标识 /index
# 2.2 Java 定义数组的形式
数据类型 [] 数组名 = new 数据类型 [容量 Capacity];
数据类型:
明确告知 Java 编译器,当前数组可以存储的数据类型是哪一个,并且有且只允许存储对应数据类型内容,严格遵守数据类型一致化原则。
[]:
1. 告知当前定义的数据类型为数组类型
2. 数组名为【引用数据】类型
跳转和指向
数组名
1. 数组操作重要名称,后续操作需要通过数组名 + 下标/索引方式完成
2. 数组名是一个【引用数据类型变量】
new
1. new 关键字会根据当前数据所需的空间大小,在内存的【堆区】申请对应内存空间
【堆区】 仓库
2. new 关键字可以将申请的内存空间进行数据擦除。
申请的仓库区域打扫卫生
数据类型
前后一致,严格遵守数据类型一致化要求。
[容量]:
数组容量一旦确定,无法修改,并且容量支持的数据类型为 int 类型。
# 2.3 数组基本案例
package com.qfedu.b_array; | |
/** | |
* @author Anonymous 2023/7/20 17:21 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
/* | |
数据类型 [] 数组名 = new 数据类型 [容量 Capacity]; | |
这里定义了一个可以存储数据类型为 int 类型,容量为 10 的数组 | |
并且数组名为 arr | |
*/ | |
int[] arr = new int[10]; | |
/* | |
操作数组中的元素内容,或者说每一个单元格数据内容 | |
需要利用下标操作,下标范围是 0 ~ 数组容量 - 1 | |
例如: | |
数组容量为 10 的情况,有效下标位置 0 ~ 9 | |
数组 arr 下标为 5 的元素赋值为 10 | |
*/ | |
arr[5] = 10; | |
System.out.println("数组中下标为 5 的元素数据存储情况 : " + arr[5]); | |
// 数组中元素被 new 关键字清理所有的内存数据,int 类型对应 "零" 值为 0 | |
System.out.println("数组中下标为 0 的元素数据存储情况 : " + arr[0]); | |
} | |
} |
# day04 数组【重点】
# 1. 数组基础
# 1.1 数组的合法下标和错误问题
合法下标 / 有效下标是 0 ~ 数组容量 - 1
package com.qfedu.a_array; | |
/** | |
* 数组有效下标使用和非法下标情况 | |
* | |
* @author Anonymous 2023/7/21 9:41 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
/* | |
定义一个 int 类型数组,可以存储的数据容量为 10 | |
*/ | |
int[] arr = new int[10]; | |
// 有效下标为 0 ~ 9 | |
arr[5] = 10; | |
System.out.println(arr[5]); | |
/* | |
代码【运行时】提示异常 数组下标越界异常 | |
ArrayIndexOutOfBoundsException: 10 | |
数组名 + 下标方式,下标只要满是为 int 类型数据 Java 编译器从语法角度 | |
分析没有问题,只有在运行代码时,发现数组下标越界异常 | |
【运行时异常】 | |
正数都有可能是一个【正常的下标位置】,需要结合上下文分析。 | |
*/ | |
// arr[10] = 20; | |
// System.out.println(arr[10]); | |
/* | |
ArrayIndexOutOfBoundsException: -1 | |
-1 虽然满足 int 类型数据需求,但是数组下标最小从 0 开始 | |
-1 是一个 100% 的错误下标 / 非法下标 | |
数组操作 -1 下标数据内容,常用于数组操作错误情况提示,错误信息提示 | |
*/ | |
arr[-1] = 200; | |
System.out.println(arr[-1]); | |
} | |
} |
# 1.2 数组内存分析图
new 关键字可以申请内存堆区空间,并且对数据空间进行擦除操作
数组名 / 局部变量在内存的栈区
# 1.3 数组容量属性 (property or attribute)
int[] arr = new int[10]; | |
System.out.println("数组的容量:" + arr.length); | |
/* | |
arr.length | |
. ==> 的 | |
有且只能获取 length 的数据内容,无法修改,数组容量一旦确定无法修改。 | |
arr.length 可以在循环操作中作为数组操作的终点标记。 | |
*/ |
# 1.4 数组和 for 循环基本操作
完成数组数据内容的赋值和取值操作
package com.qfedu.a_array; | |
/** | |
* 数组和 for 循环使用案例 | |
* | |
* @author Anonymous 2023/7/21 10:44 | |
*/ | |
public class Demo2 { | |
public static void main(String[] args) { | |
int[] arr = new int[10]; | |
// 利用 for 循环,使用 i 循环变量控制下标数据,给予数组元素赋值操作 | |
for (int i = 0; i < arr.length; i++) { | |
arr[i] = i + 1; | |
} | |
// 利用 for 循环,取值展示数组中存储的数据内容 | |
for (int i = 0; i < arr.length; i++) { | |
System.out.println("数组中存储的数据内容 : " + arr[i]); | |
} | |
} | |
} |
功能模块方法封装,数组作为方法参数形式
package com.qfedu.a_array; | |
/** | |
* 数组和 for 循环使用案例 | |
* | |
* 一框 框核心代码 | |
* 二看 上看找参数,下看找返回 | |
* 三封装 补充方法参数,选择合适的修饰符,根据参数和返回完成方法声明,核心代码作为方法体 | |
* | |
* @author Anonymous 2023/7/21 10:44 | |
*/ | |
public class Demo2 { | |
public static void main(String[] args) { | |
int[] arr = new int[10]; | |
/* | |
方法参数形式为数组形式,需要给予方法的实际参数是数组名 | |
要什么给什么,用什么拿什么 | |
*/ | |
assignInsArray(arr); | |
printIntArray(arr); | |
} | |
/** | |
* 在控制台打印展示 int 类型数组存储的数据内容 | |
* | |
* @param arr 用户提供的 int 类型数组 | |
*/ | |
public static void printIntArray(int[] arr) { | |
// 利用 for 循环,取值展示数组中存储的数据内容 | |
for (int i = 0; i < arr.length; i++) { | |
System.out.println("数组中存储的数据内容 : " + arr[i]); | |
} | |
} | |
/** | |
* 给予 int 类型数组赋值操作 | |
* | |
* @param arr 用户提供的 int 类型数组 | |
*/ | |
public static void assignInsArray(int[] arr) { | |
// 利用 for 循环,使用 i 循环变量控制下标数据,给予数组元素赋值操作 | |
for (int i = 0; i < arr.length; i++) { | |
arr[i] = i + 1; | |
} | |
} | |
} |
# 2. 数组操作 / 算法【重点】
# 2.1 已知数组找出指定元素的第一次出现的下标位置
已知数组:
int[] arr = {1, 3, 5, 7, 9, 1, 3, 5, 7, 9};
需求
找出元素 5 第一次出现的下标位置,结果 2
找出元素 15 第一次出现的下标位置,数组中没有对应的元素存在,告知方法调用者,返回结果 -1
package com.qfedu.b_arrayop; | |
/** | |
* 【已知数组找出指定元素的第一次出现的下标位置】 | |
* | |
* 已知数组: | |
* int [] arr = {1, 3, 5, 7, 9, 1, 3, 5, 7, 9}; | |
* 需求 | |
* 找出元素 5 第一次出现的下标位置,结果 2 | |
* 找出元素 15 第一次出现的下标位置,数组中没有对应的元素存在,告知方法调用者,返回结果 -1 | |
* | |
* @author Anonymous 2023/7/21 11:27 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
// 已知数组 | |
int[] arr = {1, 3, 5, 7, 9, 1, 3, 5, 7, 9}; | |
// 目标数据 | |
int target = 15; | |
int index = indexOf(arr, target); | |
// 4. 数据情况最终展示模块 | |
if (index > -1) { | |
System.out.println("目标元素第一次出现的下标位置 : " + index); | |
} else { | |
System.out.println("404 Source Not Found!"); | |
} | |
} | |
/** | |
* 找出指定元素在指定数组中的第一次出现的下标位置,如果没有找到返回 -1 | |
* | |
* @param arr 指定查询数据的目标数组 | |
* @param target 目标数据 | |
* @return 找到目标元素返回值大于等于 0,否则 -1 | |
*/ | |
public static int indexOf(int[] arr, int target) { | |
/* | |
1. 定义一个变量,用于存储目标数据的下标位置 | |
index 初始化数据为 -1,在使用 for 循环遍历整个数组数据的过程中 | |
如果找到了目标元素,将目标元素下标位置存储到 index 当中,如果没有 | |
目标数据存在,index 数据内容依然是 -1 | |
后续代码可以根据 index 数据存储情况分析数据是否找到,数据所在下标 | |
*/ | |
int index = -1; | |
// 2. 利用循环遍历整个数组元素 | |
for (int i = 0; i < arr.length; i++) { | |
/* | |
3. if 判断如果发现数组中下标 i 元素和 target 一致 | |
3.1 index 变量存储当前下标 i | |
3.2 break 跳出循环终止 | |
*/ | |
if (target == arr[i]) { | |
index = i; | |
break; | |
} | |
} | |
return index; | |
} | |
} |
# 2.2 已知数组找出指定元素的最后一次出现的下标位置
已知数组:
int[] arr = {1, 3, 5, 7, 9, 1, 3, 5, 7, 9};
需求
找出元素 5 第一次出现的下标位置,结果 7
找出元素 15 第一次出现的下标位置,数组中没有对应的元素存在,告知方法调用者,返回结果 -1
public static int lastIndexOf(int[] arr, int target); |
# 2.3 目标数组指定范围数据拷贝到另一个数组中
已知数组:
int[] arr = {1, 3, 5, 7, 9, 1, 3, 5, 7, 9};
限制从下标 2 开始到下标 6 结束,拷贝数组中的数据到新数组中
新数组:
int[] newArr = {5, 7, 9, 1};
数据范围限制/约束 要头不要尾
核心问题:
1. 用户提供的下标范围需要进行合法性判断。
2. 尾插法数据存储方式使用
package com.qfedu.b_arrayop; | |
import java.util.Arrays; | |
/** | |
* 【目标数组指定范围数据拷贝到另一个数组中】 | |
* 已知数组: | |
* int [] arr = {1, 3, 5, 7, 9, 1, 3, 5, 7, 9}; | |
* 限制从下标 2 开始到下标 6 结束,拷贝数组中的数据到新数组中 | |
* 新数组: | |
* int [] newArr = {5, 7, 9, 1}; | |
* 数据范围限制 / 约束 要头不要尾 | |
* | |
* 核心问题: | |
* 用户提供的下标范围需要进行合法性判断。 | |
* | |
* | |
* @author Anonymous 2023/7/21 14:32 | |
*/ | |
public class Demo2 { | |
public static void main(String[] args) { | |
// 目标数组 | |
int[] arr = {1, 3, 5, 7, 9, 1, 3, 5, 7, 9}; | |
// 准备两个变量,对应起始和终止下标位置 | |
int begin = 2; | |
int end = 6; | |
/* | |
用户提供下标数据合法性判断 | |
错误条件: | |
1. begin < 0 | |
2. end < 0 | |
3. begin > arr.length - 1 | |
4. end > arr.length - 1 | |
5. begin > end | |
条件限制: | |
1 4 5 | |
例如: | |
b = 2 e = 6 正确 | |
b = -5 e = 5 错误 | |
b = -3 e = -1 错误 | |
b = 5 e = -1 错误 | |
*/ | |
if (begin > end || begin < 0 || end > arr.length - 1) { | |
// 以上条件为错误条件,需要终止方法运行。可以直接 return | |
return; | |
} | |
// 新数组容量获取:终止下标 - 起始下标 | |
int newCapacity = end - begin; | |
int[] newArr = new int[newCapacity]; | |
/* | |
定义变量 index,作用如下 | |
1. 初始化为 0,用于明确当前添加数据操作对应的数据存储下标位置 | |
2. 统计当前数据添加之后,有效元素个数 | |
*/ | |
int index = 0; | |
// 利用 for 循环,从 begin 开始到 end 结束,遍历整个数组,注意【要头不要尾】 | |
for (int i = begin; i < end; i++) { | |
// 根据尾插法方式,在 index 数据位置添加元素 | |
newArr[index] = arr[i]; | |
//index +=1 移动到下一个数据存储位置,同时记录当前数组中有效元素个数 | |
index += 1; | |
} | |
System.out.println(Arrays.toString(newArr)); | |
} | |
} |
方法实现
package com.qfedu.b_arrayop; | |
import java.util.Arrays; | |
/** | |
* 【目标数组指定范围数据拷贝到另一个数组中】 | |
* 已知数组: | |
* int [] arr = {1, 3, 5, 7, 9, 1, 3, 5, 7, 9}; | |
* 限制从下标 2 开始到下标 6 结束,拷贝数组中的数据到新数组中 | |
* 新数组: | |
* int [] newArr = {5, 7, 9, 1}; | |
* 数据范围限制 / 约束 要头不要尾 | |
* | |
* 核心问题: | |
* 用户提供的下标范围需要进行合法性判断。 | |
* | |
* | |
* @author Anonymous 2023/7/21 14:32 | |
*/ | |
public class Demo2 { | |
public static void main(String[] args) { | |
// 目标数组 | |
int[] arr = {1, 3, 5, 7, 9, 1, 3, 5, 7, 9}; | |
// 准备两个变量,对应起始和终止下标位置 | |
int begin = 2; | |
int end = 6; | |
int[] newArr = subArray(arr, begin, end); | |
if (newArr == null) return; | |
System.out.println(Arrays.toString(newArr)); | |
} | |
/** | |
* 指定数据从 begin 下标位置开始到 end 下标结束,截取数组内容,数据范围 (begin <= n < end) | |
* | |
* @param arr 截取数据的目标 int 类型数组 | |
* @param begin 指定截取数据开始的下标位置 | |
* @param end 指定截取数据的结束下标位置 | |
* @return 截取得到的新数组内容,如果指定下标位置参数有错误,返回 null,表示执行失败 | |
*/ | |
public static int[] subArray(int[] arr, int begin, int end) { | |
/* | |
用户提供下标数据合法性判断 | |
错误条件: | |
1. begin < 0 | |
2. end < 0 | |
3. begin > arr.length - 1 | |
4. end > arr.length - 1 | |
5. begin > end | |
条件限制: | |
1 4 5 | |
例如: | |
b = 2 e = 6 正确 | |
b = -5 e = 5 错误 | |
b = -3 e = -1 错误 | |
b = 5 e = -1 错误 | |
*/ | |
if (begin > end || begin < 0 || end > arr.length - 1) { | |
// 利用 return 关键字终止方法的运行,同时利用 null 告知外部,当前方法运行失败 | |
return null; | |
} | |
// 新数组容量获取:终止下标 - 起始下标 | |
int newCapacity = end - begin; | |
int[] newArr = new int[newCapacity]; | |
/* | |
定义变量 index,作用如下 | |
1. 初始化为 0,用于明确当前添加数据操作对应的数据存储下标位置 | |
2. 统计当前数据添加之后,有效元素个数 | |
*/ | |
int index = 0; | |
// 利用 for 循环,从 begin 开始到 end 结束,遍历整个数组,注意【要头不要尾】 | |
for (int i = begin; i < end; i++) { | |
// 根据尾插法方式,在 index 数据位置添加元素 | |
newArr[index] = arr[i]; | |
//index +=1 移动到下一个数据存储位置,同时记录当前数组中有效元素个数 | |
index += 1; | |
} | |
return newArr; | |
} | |
} |
# 2.4 找出目标数组中最大值下标位置
int[] arr = {1, 3, 5, 7, 9, 21, 4, 6, 8, 10};
找出数值中最大值下标位置:
maxIndex = 5;
package com.qfedu.b_arrayop; | |
/** | |
* 【找出目标数组中最大值下标位置】 | |
* int [] arr = {1, 3, 5, 7, 9, 21, 4, 6, 8, 10}; | |
* 找出数值中最大值下标位置: | |
* maxIndex = 5; | |
* | |
* @author Anonymous 2023/7/22 9:09 | |
*/ | |
public class Demo3 { | |
public static void main(String[] args) { | |
// 查询最大值的目标数组 【找极值】 | |
int[] arr = {1, 3, 5, 7, 9, 21, 4, 6, 8, 10}; | |
int index = maxIndexOf(arr); | |
System.out.println("最大值数据所在下标位置:" + index); | |
} | |
/** | |
* 找出数组中最大值所在下标位置 | |
* | |
* @param arr 查询最大值数据所在下标的 int 类型数组 | |
* @return 最大值所在下标位置。 | |
*/ | |
public static int maxIndexOf(int[] arr) { | |
// 1. 定义一个变量 index 初始化为 0 ,假设下标为 0 的元素是最大值 | |
int index = 0; | |
// 2. 利用循环,遍历整个数组内容,要求从下标 1 开始。 | |
for (int i = 1; i < arr.length; i++) { | |
// 3. 如果发现下标为 i 数据大于 index 对应的数据 | |
if (arr[index] < arr[i]) { | |
// 4. index 存储 下标 i 的数据 | |
index = i; | |
} | |
} | |
return index; | |
} | |
} |
# 2.5 找出目标数组中最小值下标位置
int[] arr = {1, 3, 5, 7, 9, 21, 4, 6, 8, 10};
找出数值中最小值下标位置:
minIndex = 0;
public static int minIndexOf(int[] arr); |
# 2.6 指定数组中最大值元素个数
计数器概念
int[] arr = {0, 21, 5, 21, 21, 21, 4, 6, 21, 10};
最大值个数
maxValueCount = 5;
双循环方式
package com.qfedu.b_arrayop; | |
/** | |
* 【指定数组中最大值元素个数】 | |
* int [] arr = {0, 21, 5, 21, 21, 21, 4, 6, 21, 10}; | |
* 最大值个数 | |
* maxValueCount = 5; | |
* | |
* @author Anonymous 2023/7/22 9:29 | |
*/ | |
public class Demo4 { | |
public static void main(String[] args) { | |
// 目标数组 | |
int[] arr = {0, 21, 5, 21, 21, 21, 4, 6, 21, 10}; | |
int count = getMaxValueCount(arr); | |
// 3. 展示最大值个数 | |
System.out.println("最大值个数为: " + count); | |
} | |
/** | |
* 找出 int 类型指定数中最大值元素个数 | |
* | |
* @param arr 查询数据的目标 int 类型数组 | |
* @return 数组中最大值元素个数 | |
*/ | |
public static int getMaxValueCount(int[] arr) { | |
// 1. 找到最大值数据。 | |
// 1.1 假设下标为 0 对应的元素是最大值 | |
int max = arr[0]; | |
// 1.2 遍历整个数据内容,找出最大值数据 | |
for (int i = 1; i < arr.length; i++) { | |
// 1.3 如果发现下标为 i 的大于 max 存储数据内容 | |
if (max < arr[i]) { | |
// 1.4 max 存储 arr [i] 对应的数据内容 | |
max = arr[i]; | |
} | |
} | |
// 2. 根据最大值数据,计数统计最大值个数 | |
// 2.1 定义一个变量 count 作为计数器变量 | |
int count = 0; | |
// 2.2 遍历循环整个数组,找出最大值对应的下标位置,找到一个计数一个 | |
for (int i = 0; i < arr.length; i++) { | |
// 2.3 如果下标为 i 的元素和 max 一致 | |
if (max == arr[i]) { | |
// 2.4 count += 1 计数最大值个数 | |
count += 1; | |
} | |
} | |
return count; | |
} | |
} |
单循环方式
package com.qfedu.b_arrayop; | |
/** | |
* 【指定数组中最大值元素个数】 | |
* int [] arr = {0, 21, 5, 21, 21, 21, 4, 6, 21, 10}; | |
* 最大值个数 | |
* maxValueCount = 5; | |
* | |
* @author Anonymous 2023/7/22 9:29 | |
*/ | |
public class Demo5 { | |
public static void main(String[] args) { | |
// 目标数组 | |
int[] arr = {0, 21, 5, 21, 21, 21, 4, 6, 21, 10}; | |
int count = getMaxValueCount(arr); | |
System.out.println("最大值个数为: " + count); | |
} | |
/** | |
* 找出 int 类型指定数中最大值元素个数 | |
* | |
* @param arr 查询数据的目标 int 类型数组 | |
* @return 数组中最大值元素个数 | |
*/ | |
public static int getMaxValueCount(int[] arr) { | |
// 1. 假设下标为 0 的元素是最大值 | |
int max = arr[0]; | |
// 2. 定义计数变量 | |
int count = 1; | |
/* | |
3. 遍历整个数组 | |
情况一: | |
如果发现数组中下标为 i 的元素大于 max | |
max 保存下标 i 对应的元素内容 | |
同时 count 重置 1 | |
情况二: | |
如果发现数组中下标为 i 的元素等于 max | |
count += 1 | |
*/ | |
for (int i = 1; i < arr.length; i++) { | |
if (max < arr[i]) { | |
max = arr[i]; | |
count = 1; | |
} else if (max == arr[i]) { | |
count += 1; | |
} | |
} | |
return count; | |
} | |
} |
# 2.7 复制指定数组数据内容到新数组
int[] arr = {1, 3, 5, 7, 9, 21, 4, 6, 8, 10};
复制到新数组中
int[] newArr = {1, 3, 5, 7, 9, 21, 4, 6, 8, 10};
package com.qfedu.b_arrayop; | |
import java.util.Arrays; | |
/** | |
* 【复制指定数组数据内容到新数组】 | |
* int [] arr = {1, 3, 5, 7, 9, 21, 4, 6, 8, 10}; | |
* 复制到新数组中 | |
* int [] newArr = {1, 3, 5, 7, 9, 21, 4, 6, 8, 10}; | |
* | |
* @author Anonymous 2023/7/22 9:55 | |
*/ | |
public class Demo6 { | |
public static void main(String[] args) { | |
// 原数组 | |
int[] arr = {1, 3, 5, 7, 9, 21, 4, 6, 8, 10}; | |
int[] newArr = copyArray(arr, arr.length); | |
System.out.println(Arrays.toString(newArr)); | |
} | |
/** | |
* 指定原数据数组,同时限制新数组容量情况,要求新数组容量 size >= 原数组容量 | |
* | |
* @param src int 类型原数组 | |
* @param size 指定新数组容量,要求大于等于原数组容量 | |
* @return 拷贝成功返回新数组,拷贝失败返回 null | |
*/ | |
public static int[] copyArray(int[] src, int size) { | |
if (src.length > size) { | |
return null; | |
} | |
int[] newArr = new int[size]; | |
for (int i = 0; i < src.length; i++) { | |
newArr[i] = src[i]; | |
} | |
return newArr; | |
} | |
} |
# 2.8 指定数组内容逆序
int[] arr = {1, 3, 5, 7, 9, 21, 4, 6, 8, 10};
数组内容逆序之后
arr = {10, 8, 6, 4, 2, 9, 7, 5, 3, 1};
package com.qfedu.b_arrayop; | |
import java.util.Arrays; | |
/** | |
* 【指定数组内容逆序】 | |
* int [] arr = {1, 3, 5, 7, 9, 21, 4, 6, 8, 10}; | |
* 数组内容逆序之后 | |
* arr = {10, 8, 6, 4, 2, 9, 7, 5, 3, 1}; | |
* | |
* @author Anonymous 2023/7/22 10:11 | |
*/ | |
public class Demo7 { | |
public static void main(String[] args) { | |
// 原数组 | |
int[] arr = {1, 3, 5, 7, 9, 21, 4, 6, 8, 10}; | |
reverse(arr); | |
System.out.println(Arrays.toString(arr)); | |
} | |
/** | |
* 指定数组逆序 | |
* | |
* @param arr int 类型指定数组 | |
*/ | |
public static void reverse(int[] arr) { | |
/* | |
利用循环进行数据的交换位置 | |
交换次数或者说循环次数是数组有效元素个数 / 2 | |
*/ | |
for (int i = 0; i < arr.length / 2; i++) { | |
int temp = arr[i]; | |
arr[i] = arr[arr.length - 1 - i]; | |
arr[arr.length - 1 - i] = temp; | |
} | |
} | |
} |
# 2.9 指定数组中最大值元素所有对应下标位置,要求存储到另一个数组中
int[] arr = {21, 21, 5, 21, 9, 21, 4, 6, 21, 10}; | |
最大值下标存储数组内容 | |
int[] indexArray = {0, 1, 3, 5, 8}; |
方式一:
- 找最大值具体数据
- 找最大值个数
- 根据最大值个数创建对应的存储下标使用的 int 类型数组
- 从原数组中,找出最大值对应的下标位置,移动数据到新数组中
package com.qfedu.b_arrayop; | |
import java.util.Arrays; | |
/** | |
* 指定数组中最大值元素所有对应下标位置,要求存储到另一个数组中 | |
* - 找最大值具体数据 | |
* - 找最大值个数 | |
* - 根据最大值个数创建对应的存储下标使用的 int 类型数组 | |
* - 从原数组中,找出最大值对应的下标位置,移动数据到新数组中 | |
* | |
* @author Anonymous 2023/7/22 11:25 | |
*/ | |
public class Demo8 { | |
public static void main(String[] args) { | |
// 目标数组 | |
int[] arr = {21, 21, 5, 21, 9, 21, 4, 6, 21, 10}; | |
int[] indexArray = maxValueIndexes(arr); | |
System.out.println(Arrays.toString(indexArray)); | |
} | |
/** | |
* 找出指定数组中最大值所在下标位置,返回值是存储所有最大值下标位置的 int 类型数组 | |
* | |
* @param arr 查询最大值数据下标的目标数组 | |
* @return 存储当前数组中所有最大值的下标数组 | |
*/ | |
public static int[] maxValueIndexes(int[] arr) { | |
// 1. 找最大值具体数据和最大值个数 | |
int max = arr[0]; | |
int count = 1; | |
for (int i = 1; i < arr.length; i++) { | |
if (max < arr[i]) { | |
max = arr[i]; | |
count = 1; | |
} else if (max == arr[i]) { | |
count += 1; | |
} | |
} | |
// 2. 根据最大值个数,创建存储下标使用的数组 | |
int[] indexArray = new int[count]; | |
/* | |
3. 从原数组中,找出最大值下标位置,存储到 indexArray 中 | |
利用尾插法方式完成 indexArray 数组数据存储操作 | |
int index | |
1. 作为当前添加数据的下标位置,同时添加之后自增 1 | |
2. 作为当前数组中存储数据的有效元素个数 | |
*/ | |
int index = 0; | |
for (int i = 0; i < arr.length; i++) { | |
if (max == arr[i]) { | |
indexArray[index] = i; | |
index += 1; | |
} | |
} | |
return indexArray; | |
} | |
} |
方式二:
- 创建一个存储下标的数组,数组容量和原数组一致
- 找出最大值
- 从原数组中找出所有最大值下标位置,移动到新数组
package com.qfedu.b_arrayop; | |
/** | |
* 指定数组中最大值元素所有对应下标位置,要求存储到另一个数组中 | |
* - 创建一个存储下标的数组,数组容量和原数组一致 | |
* - 找出最大值 | |
* - 从原数组中找出所有最大值下标位置,移动到新数组 | |
* | |
* @author Anonymous 2023/7/22 11:25 | |
*/ | |
public class Demo9 { | |
public static void main(String[] args) { | |
// 目标数组 | |
int[] arr = {21, 21, 5, 21, 9, 21, 4, 6, 21, 10}; | |
// 1. 准备数组,数组容量和原数组容量一致。 | |
int[] indexArray = new int[arr.length]; | |
int index = getMaxCount(arr, indexArray); | |
/* | |
4. 展示存储数组目标元素下标的情况 | |
有可能会出现整个 indexArray 数组中所有数据内容都是 0 | |
需要 index 来明确数组中下标为 0 的元素存储数据 0 是有效数值 | |
index == 1 | |
index 存储当前数组中【有效元素个数】 | |
*/ | |
for (int i = 0; i < index; i++) { | |
System.out.println(indexArray[i]); | |
} | |
} | |
/** | |
* 获取最大值元素个数,同时将最大值对应的下标位置存储到参数 indexArray 中 | |
* | |
* @param arr 查询最大值数据的原数组 | |
* @param indexArray 存储最大值下标位置的数组 | |
* @return 最大值下标个数 | |
*/ | |
private static int getMaxCount(int[] arr, int[] indexArray) { | |
// 2. 找出最大值数据情况 | |
int max = arr[0]; | |
for (int i = 0; i < arr.length; i++) { | |
if (max < arr[i]) { | |
max = arr[i]; | |
} | |
} | |
// 3. 利用循环和尾插法,从目标数组中找出最大值下标位置,存储到 indexArray 中 | |
int index = 0; | |
for (int i = 0; i < arr.length; i++) { | |
if (max == arr[i]) { | |
indexArray[index] = i; | |
index += 1; | |
} | |
} | |
return index; | |
} | |
} |
# 2.10 在数组指定下标位置添加元素
目标数组:
int[] arr = {1, 3, 5, 7, 9, 11, 13, 15, 17, 0};
注意:
1. 0 无效元素,仅占位使用
2. 插入数据下标的位置必须在合法范围以内
例如:
添加指定元素 20 到下标为 5 的位置
{1, 3, 5, 7, 9, 20, 11, 13, 15, 17};
# 2.11 删除数组中指定下标元素内容
目标数组:
int[] arr = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19};
注意:
1. 0 是无效元素,仅占位使用
2. 删除之后,要求数组元素向前移动
3. 删除数据下标的位置必须在合法范围以内
ArrayIndexOutOfBoundsException
例如:
删除指定下标 5 的元素
{1, 3, 5, 7, 9, 13, 15, 17, 19, 0}
# 2.12 选择排序算法推演
目标数组:
int[] arr = {1, 3, 5, 7, 9, 2, 4, 6, 8, 10}
# 2.12.1 找出数组中最大值和下标为 0 的元素交换位置
# 2.12.2 接上一题:找出数组中剩余最大值和下标为 1 的元素交换位
# 2.12.3 接上一题:找出数组中剩余最大值和下标为 2 的元素交换位
# day05 面向对象
# 1. 面向对象和面向过程对比
面向对象:找合适的人做合适的事
面向过程:自力更生,亲力亲为
烧茄子
找一个餐厅吃
1. 付钱
2. 拿饭
3. 吃
4. 走
自己做
1. 长条紫茄子,西红柿,菜椒,蒜
2. 处理各种菜品,炸茄子,西红柿,菜椒切块,蒜(蒜蓉 蒜片)
3. 炒(料汁,水淀粉)
4. 吃
5. 洗碗刷锅
# 2. 类和对象
类是针对于一类事物的统一描述,统一概述,主要包括 数据描述和行为描述
对象 独立的,唯一的,特殊的个体
类 | 对象 |
---|---|
人 | 麻花藤,李想,雷布斯,马云爸爸,乔布斯 |
狗 | 狗鸽,八公,哮天犬,狗鸽的豆豆,王可可 |
猫 | 黑猫警长,Tom,加菲猫,狗鸽的八九 |
# 3. Java 中定义类的格式
class 类名 { | |
// 属性描述 / 数据描述【成员变量 Field】 | |
// 行为描述 【成员方法 Method】 | |
} | |
/* | |
类名: | |
1. 要求符合大驼峰命名法 | |
2. 要求做到见名知意 | |
成员变量: | |
1. 要求符合小驼峰命名法 | |
2. 要求做到见名知意 | |
成员方法: | |
1. 要求符合小驼峰命名法 | |
2. 要求做到见名知意,动宾结构 | |
*/ |
# 3.1 成员变量和成员方法定义
/** | |
* 自定义 class 类型,类型名为 Person,对应人类 | |
* 属性描述 ==> 【成员变量】 | |
* 行为描述 ==> 【成员方法】 | |
*/ | |
class Person { | |
// 属性描述 ==> 【成员变量】 | |
/** | |
* id 成员变量,数据类型为 int 类型,对应 Person 的 id ,可以理解为身份证 | |
*/ | |
int id; | |
/** | |
* name 成员变量,数据类型为 String 类型,对应 Person 的 name,可以理解为姓名 | |
*/ | |
String name; | |
/** | |
* age 成员变量,数据类型为 int 类型,可以理解为年龄 | |
*/ | |
int age; | |
// 行为描述 ==> 【成员方法】 | |
/** | |
* 睡觉行为描述方法 | |
*/ | |
public void sleep() { | |
System.out.println("我被床封印了!!!"); | |
} | |
/** | |
* 吃饭行为描述 | |
* | |
* @param food 字符串类型对应的食物名称 | |
*/ | |
public void eat(String food) { | |
System.out.println("中午吃" + food); | |
} | |
/** | |
* 写代码行为描述 | |
*/ | |
public void coding() { | |
System.out.println("键盘敲烂,月薪过万"); | |
} | |
} |
# 3.2 实例化对象和对象操作成员
实例化对象的固定格式【new 对象】
类名 类对象 = new 类名(); | |
/* | |
类名: | |
自定义获取是 Java 中已经存在的类型名称,类名首字母大写。 | |
类对象: | |
操作当前类对应对象的变量名称,可以利用对象名来操作相关成员变量和成员方法 | |
new: | |
1. new 根据会当前当前类型所需在内存的【堆区】申请对应的内存空间 | |
2. new 关键字会将申请的内存空间擦除干净 | |
类名 (); | |
1. 构造方法 Constructor | |
2. 方法名为类名,用于告知 new 关键字当前实例化对象具体数据类型 | |
3. 构造方法可以用于初始化实例化对象数据空间内容。 | |
*/ |
实例化对象操作成员
成员变量:
类似于 arr.length
格式: 对象名.成员变量名
成员方法
格式: 对象名.成员方法(实际参数类型);
public class Demo1 { | |
public static void main(String[] args) { | |
/* | |
当前操作实例化 Person 对象,对象名为 person | |
对象名称要求符合见名知意,类型指向 | |
*/ | |
Person person = new Person(); | |
/* | |
通过 Person 对象 person 操作成员变量和成员方法 | |
*/ | |
// 给予当前 Person 对象 person id 成员变量赋值具体数据 1 | |
person.id = 1; | |
// 赋值 Person 对象成员变量 name 数据 | |
person.name = "张三"; | |
// 赋值 Person 对象成员变量 age 数据 | |
person.age = 25; | |
System.out.println("成员变量数据内容:"); | |
// 提取成员变量保存的数据内容 | |
System.out.println("ID: " + person.id); | |
System.out.println("Name: " + person.name); | |
System.out.println("Age: " + person.age); | |
System.out.println(); | |
/* | |
通过 Person 对象操作成员方法 | |
*/ | |
System.out.println("成员方法执行效果"); | |
person.sleep(); | |
person.eat("牛肉面"); | |
person.coding(); | |
} | |
} |
# 4. 对象内存分析图
# 5. JavaBean 规范封装规范
# 5.1 概述
为了满足后续代码开发的规范,实例化对象,操作数据的一致性,Java 制定了 JavaBean 规范
1. 所有成员变量全部私有化【private】
2. 必须提供对应成员变量的【Setter and Getter 方法】,用于满足成员变量赋值和取值规范化操作
3. 必须提供无参数构造方法【Constructor】
# 5.2 private 权限修饰
权限修饰:可以用于控制成员变量,成员方法,构造方法使用权限
private 权限修饰符:
private 可以用于修饰成员变量,成员方法,构造方法,修饰的内容有且只允许在类内使用,类外无法直接调用。
成员方法 采用 private 修饰可以满足类内核心方法,或者辅助方法实现,类外无法调用
构造方法 private 修饰可以用于后续的【单例模式】实现
public 权限修饰符:
public 可以用于修饰成员变量,成员方法,构造方法,修饰的内容类外只要符合语法规范,都可以直接使用
package com.qfedu.a_object; | |
class Student { | |
int id; | |
private String name; | |
public void game() { | |
System.out.println("WOT World Of Tank"); | |
} | |
private void test() { | |
System.out.println("测试方法"); | |
} | |
} | |
/** | |
* @author Anonymous 2023/7/24 14:37 | |
*/ | |
public class Demo2 { | |
public static void main(String[] args) { | |
Student student = new Student(); | |
student.id = 1; | |
/* | |
提示报错,name 成员变量为私有化成员变量,类外无法直接使用 | |
*/ | |
student.name = "张三"; | |
student.game(); | |
/* | |
提示报错,test () 成员方法为私有化方法,类外无法直接使用 | |
*/ | |
student.test(); | |
} | |
} |
所有成员变量全部私有化
主要目的是保证成员变量赋值和取值操作规范化,统一化,采用相同的方式方法来完成操作。可以满足后期的框架数据操作需求,例如: JSP 数据取值,Spring 中的 DI 依赖注入。
class Student { | |
/* | |
JavaBean 规范第一条,所有成员变量全部私有化限制。 | |
*/ | |
private int id; | |
private String name; | |
private int age; | |
} |
# 5.3 Setter and Getter 方法
JavaBean 规定要求的给予成员变量进行赋值和取值操作的固定方法,有严格的语法要求和语法形式。
private String name; | |
// Getter 方法,用于取值成员变量数据内容 | |
public String getName() { | |
return name; | |
} | |
/* | |
Getter 取值方法格式 | |
public 成员变量对应数据类型返回值 get 成员变量名称 () { | |
return 成员变量; | |
} | |
要求 | |
1. 方法名必须符合小驼峰命名法 | |
getname = 正确 => getName | |
2. 如果成员变量数据类型为 boolean 类型,Getter 方法方法名以 is 开头 | |
private boolean gender; | |
public boolean isGender () {return gender;} | |
*/ |
private String name; | |
// Setter 方法,用于赋值成员变量数据内容 | |
public void setName(String name) { | |
this.name = name; | |
} | |
/* | |
Setter 赋值方法格式 | |
public void set 成员变量名称 (成员变量数据类型所需参数) { | |
this. 成员变量名 = 参数; | |
} | |
要求 | |
1. 方法名必须符合小驼峰命名法 | |
setname = 正确 => setName | |
*/ |
# 5.4 Constructor 构造方法
构造方法作用:
- 提供给 new 关键字实例化对象数据类型。构造方法要求方法名必须是类名。
- 构造方法参数可以给予实例化对象初始化数据能力。
// 构造方法基本格式 | |
public 类名(实例化对象初始化参数列表) { | |
初始化语句; | |
} | |
// 针对于 Student 类的无参数构造方法 | |
public Student() {} | |
/* | |
调用无参数构造方法实例化对象操作,Student 对象数据情况 | |
Student student = new Student (); | |
student ==> {id=0, name=null, age=0}; | |
*/ | |
// 针对于 Student 类有参数构造方法 | |
public Student(int id, String name, int age) { | |
this.id = id; | |
this.name = name; | |
this.age = age; | |
} | |
/* | |
调用执行以上有参数构造方法,实例化得到的 Student 对象,成员变量带有初始化数据 | |
Student student = new Student (1, "张三", 25); | |
student ==> {id=1, name="张三", age=25} | |
*/ |
# 5.5 this [鸡肋] 关键字
核心作用是用于区分【参数变量】和【成员变量】
参数变量名称和成员变量名称一致,为了在方法中区分成员变量和参数变量,可以使用 this. 成员变量 方式来区分。【固定格式规范,可以自动生成】
public Student(int id, String name, int age) { | |
/* | |
方法内使用的 id 变量为当前方法参数变量 id 不是成员变量 | |
方法变量使用遵循的是就近原则。 | |
id = id; | |
this.id 可以告知程序,当前 id 为成员变量 id ,非参数 id | |
*/ | |
this.id = id; | |
this.name = name; | |
this.age = age; | |
} |
# 5.6 符合 JavaBean 规范实体类定义案例
符合 JavaBean 规范的实体类,可以用于后期的项目开发自动化处理 (自动创建,自动赋值,自动加载...)
package com.qfedu.a_object;
/**
* 符合 JavaBean 规定的实体类
*
* @author Anonymous 2023/7/24 15:45
*/
public class SingleDog {
// 所有成员变量全部私有化
private int id;
private String name;
private int age;
private boolean gender;
// 无参数构造方法 NoArgConstructor
public SingleDog() {}
// 全参数构造方法 AllArgConstructor
public SingleDog(int id, String name, int age, boolean gender) {
this.id = id;
this.name = name;
this.age = age;
this.gender = gender;
}
// Setter and Getter 方法
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isGender() {
return gender;
}
public void setGender(boolean gender) {
this.gender = gender;
}
}
# 6. 成员变量和局部变量
# 6.1 什么是局部变量
for (int i = 1; i <= 10; i++) { | |
} | |
for (int i = 1; i <= 10; i++) { | |
} | |
以上两个 i 完全不冲突,都是 for 循环中的局部变量; | |
for (int i = 5; i <= 10; i++) { | |
if (5 == i) { | |
int num = 10; | |
} | |
System.out.println(num); | |
} | |
语法报错,因为 num 定义在 if 大括号以内,是一个局部变量,超出 if 范围无效 |
main() { | |
int num = 10; | |
/* | |
1. 涉及到局部变量问题 | |
2. num 作为方法的参数【值传递 / 值参数】 | |
*/ | |
test(num); | |
// 打印结果为 10 | |
System.out.println(num); | |
} | |
public static void test(int num) { | |
num = 20; | |
} |
# 6.2 成员变量和局部变量对比
对比 | 成员变量 | 局部变量 |
---|---|---|
内存 | 存在于在内存的【堆区】 | 存在于内存的【栈区】 |
作用 | 成员变量用于描述当前类的数据情况,同时可以存储当前对象的数据内容 | 在代码运行过程中临时存储使用到的数据 |
初始化数据 | 实例化对象过程中如果没有明确给予成员变量赋值操作,成员变量数据内容对应成员变量数据类型的【零值】 | 没有赋值不能参与其他非赋值操作 |
生存周期 | 成员变量随着类对象实例化开始,当前对应实例化对象被 JVM 的【GC】 销毁时结束 | 有且只在定义的大括号范围以内 |
作用域 | 对象在哪一个区域,对应的成员变量也在哪一个区域。 | 有且只在定义的大括号范围以内 |
# 6.3 【零值】
实例化对象,没有给予成员变量初始化赋值的情况下,每一个成员变量根据当前数据类型情况,对外数据内容是【零值】
例如:
byte short int 零值 0
long 零值 0L
boolean 零值 false
char 零值 '\0' 表示编码集当中编号为 0 的字符,是一个不可见字符 nul
float 零值 0.0F
double 零值 0.0
其他引用数据类型 零值 null 内存中编号为 0 的内存名称
# 6.4 JVM 的 GC
JVM 的垃圾回收机制。

# 7. 十二生肖
要求:
【严格遵守 JavaBean 规范】
1. 每一个类对应三个或者三个以上成员变量
2. 每一个类对应三个或者三个以上成员方法,必须保证有一个成员方法至少有一个参数
3. 每一个类必须有三个或者三个以上的构造方法,并且强制要求有一个无参数构造方法
4.【重点】成员变量名称所有类不可重复
例如:
Tiger name 成员变量描述名字,其他的 十一个生肖没有名字
5.【重点】成员方法名称所有类不可重复
例如:
Tiger sleep() 成员方法描述睡觉行为,其他 十一个生肖不能睡觉
6. 每一个类单独一个文件
文件名:
Demo1.java
==> public class Demo1
==> class Tiger
7. 每一个类必须使用所有构造方法,创建对应对象,并且使用创建对象,调用所有成员变量数据进行展示,和所有成员方法进行执行。
8. 每一个方法,必须有对应的标准文档注释!!!
以上代码会完成
1. 12 类
2. 36个 成员变量 36个 成员方法 36个 构造方法
3. new 对象 过程 36 次
调用成员变量 108 次
调用成员方法 108 次
# day06 面向对象
# 1. 继承
# 1.1 Java 中继承的目的
- 数据类型一致化的延续
- 成员方法的延续性,一致性和独立性。
- 方法支持数据类型多样性的基本实现规则
- 继承操作基本上和成员变量无关,成员变量大多数要求 private 私有化修饰
# 1.2 游戏开发中的继承
【模版概念】在基础的模版之上,根据当前的特征需求,加入特征元素 (属性,方法),从而满足基于模版之上的独立开发模式,降低基础类型的开发压力。
模版一般规定基本属性,基本方法,继承模版的类型,在基础之上进行个性化的增强。
# 1.3 Java 中继承的基本格式和语法要求
继承使用的关键字 extends
基本格式:
class A extends B { | |
} | |
/* | |
A 类是 B 类的一个子类 | |
B 类是 A 类的唯一父类 | |
*/ |
语法要求
- 子类可以通过继承得到父类中的非私有化成员变量和成员方法
- 子类不可以通过继承得到父类私有化成员变量和成员方法
- Java 的继承重点关注数据类型延续和方法一致
/** | |
* 父类 | |
*/ | |
class SuperClass { | |
// 私有化成员变量和公共成员变量 | |
private String msg = "私有化成员变量"; | |
public String info = "public 修饰成员变量"; | |
// 私有化成员方法和公共成员方法 | |
private void test() { | |
System.out.println("私有化成员方法"); | |
} | |
public void game() { | |
System.out.println("LOL PUBG WOT"); | |
} | |
} | |
/** | |
* SubClass 继承 SuperClass | |
* | |
* SubClass 是 SuperClass 的一个子类 | |
* SuperClass 是 SubClass 唯一父类 | |
*/ | |
class SubClass extends SuperClass { | |
} | |
/** | |
* @author Anonymous 2023/7/25 9:56 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
SubClass subClass = new SubClass(); | |
/* | |
- 子类可以通过继承得到父类中的非私有化成员变量和成员方法 | |
- 子类不可以通过继承得到父类私有化成员变量和成员方法 | |
*/ | |
subClass.game(); | |
System.out.println(subClass.info); | |
/* | |
subClass.test(); | |
System.out.println(subClass.msg); | |
*/ | |
} | |
} |
# 1.4 父类方法无法满足子类需求解决方案
重写 Override
- 解决在继承的情况下,父类的方法实际执行效果无法满足子类需求,不修改方法声明,利用重写在子类对目标方法体进行子类特征化实现。
语法要求
- 子类重写父类方法,方法声明必须完全一致,权限修饰符必须大于等于父类,一般情况下权限修饰符都是和父类一致
- 子类可以根据自身需求修改方法体内容。
- 必须使用 @Override 注解开启重写格式严格检查
package com.qfedu.a_extends; | |
class Father { | |
public void job() { | |
System.out.println("机械工程师"); | |
} | |
public void game() { | |
System.out.println("捕鱼达人 黄金矿工 麻将 斗地主"); | |
} | |
} | |
class Son extends Father { | |
/* | |
@Override 注解 | |
开启重写代码格式严格检查!!!要求 Java 编译器检查当前重写格式是否符合要求。 | |
Java 原作者建议,@Override 不可以省略 | |
*/ | |
@Override | |
public void job() { | |
System.out.println("卖艺!!!"); | |
} | |
@Override | |
public void game() { | |
System.out.println("LOL PUBG WOT"); | |
} | |
} | |
/** | |
* @author Anonymous 2023/7/25 10:08 | |
*/ | |
public class Demo2 { | |
public static void main(String[] args) { | |
Son son = new Son(); | |
/* | |
父类方法的实际运行效果无法满足子类【特征需求】 | |
*/ | |
son.job(); | |
son.game(); | |
/* | |
子类自定义功能方法,功能形式继承父类方法相似,可以解决当前问题 | |
但是会导致代码冗余,方法冗余,开发压力较大。 | |
可以利用【重写 Override】解决问题。 | |
son.jobSon (); | |
son.gameSon (); | |
*/ | |
} | |
} |
# 1.5 继承带来的问题 - abstract 关键字使用
父类通过继承给到子类的方法,应该是一个【规范 / 模版】,子类【必须】重写 / 实现目标方法,从而满足子类的特征化方法的实现。
需要利用 abstract 关键字来解决问题。
package com.qfedu.b_abstract; | |
/* | |
abstract 关键字修饰的方法,要求子类【强制】实现 implement | |
第一个错误: | |
Abstract method in non-abstract class | |
abstract 修饰方法定义在一个非 abstract 修饰类内 | |
Class 'LOLHero' must either be declared abstract or implement abstract method 'q ()' in 'LOLHero' | |
LOLHero 必须定义为 abstract 修饰,或者实现 abstract 修饰方法 q () | |
Alt + Enter 快速修复 选择处理方式: | |
Make 'LOLHero' abstract | |
第二个错误: | |
Abstract methods cannot have a body | |
abstract 修饰方法不能有方法体 | |
Alt + Enter 快速修复 选择处理方式: | |
Delete method body | |
第三个错误: | |
Class 'HappyWindBoy' must either be declared abstract or implement abstract method 'q ()' in 'LOLHero' | |
HappyWindBoy 类必须是一个 abstract 修饰类,或者实现 LOLHero 类内的 abstract 修饰方法 q (); | |
Class 'Varus' must either be declared abstract or implement abstract method 'q ()' in 'LOLHero' | |
Varus 类必须是一个 abstract 修饰类,或者实现 LOLHero 类内的 abstract 修饰方法 q (); | |
Alt + Enter 快速修复 选择处理方式: | |
Implement methods | |
*/ | |
/* | |
LOLHero 类,目的是定义 LOL 英雄技能规范,限制每一个英雄有 QWER 方法 | |
每一个方法都是 abstract 修饰 | |
*/ | |
abstract class LOLHero { | |
abstract public void q(); | |
abstract public void w(); | |
abstract public void e(); | |
abstract public void r(); | |
} | |
/** | |
* 快乐风男类 | |
* 继承 abstract 修饰的 LOL Hero 类需要【实现】所有 abstract 修饰的方法 | |
*/ | |
class HappyWindBoy extends LOLHero { | |
@Override | |
public void q() { | |
System.out.println("斩钢闪"); | |
} | |
@Override | |
public void w() { | |
System.out.println("风之障壁"); | |
} | |
@Override | |
public void e() { | |
System.out.println("踏前斩"); | |
} | |
@Override | |
public void r() { | |
System.out.println("狂风绝息斩"); | |
} | |
} | |
/** | |
* 维鲁斯 | |
*/ | |
class Varus extends LOLHero { | |
@Override | |
public void q() { | |
System.out.println("穿刺之箭"); | |
} | |
@Override | |
public void w() { | |
System.out.println("枯萎箭袋"); | |
} | |
@Override | |
public void e() { | |
System.out.println("恶灵箭雨"); | |
} | |
@Override | |
public void r() { | |
System.out.println("腐败锁链"); | |
} | |
} | |
/** | |
* @author Anonymous 2023/7/25 14:37 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
HappyWindBoy happyWindBoy = new HappyWindBoy(); | |
Varus varus = new Varus(); | |
happyWindBoy.e(); | |
happyWindBoy.q(); | |
happyWindBoy.r(); | |
happyWindBoy.w(); | |
varus.e(); | |
varus.q(); | |
varus.w(); | |
varus.r(); | |
} | |
} | |
/* | |
执行结果: | |
踏前斩 | |
斩钢闪 | |
狂风绝息斩 | |
风之障壁 | |
恶灵箭雨 | |
穿刺之箭 | |
枯萎箭袋 | |
腐败锁链 | |
*/ |
# 2. 类作为方法参数案例
后期的项目代码中,基本上所有的参数都是类对象形式,类对象作为数据的载体或者方法的执行者,作为方法参数方式提供给方法执行。
案例:
修理汽车,修理轮胎
修理厂类
- 属性:店名,地址,联系方式
- 行为:修车
汽车类
- 属性:品牌,颜色,轮胎个数
- 行为:飙车
汽车类
package com.qfedu.c_repaired; | |
/** | |
* @author Anonymous 2023/7/25 15:59 | |
*/ | |
public class Car { | |
private String name; | |
private String color; | |
private int wheelCount; | |
// 根据所需完成 Constructor 和 Getter and Setter 方法 | |
/* | |
功能方法,飙车 | |
轮胎个数 == 4 可以飙车,如果小于 4 需要去修理厂 | |
*/ | |
public void race() { | |
if (4 == wheelCount) { | |
System.out.println("开着" + color + name + "在金梭路 15 KM/H 狂飙"); | |
} else { | |
System.out.println("轮胎有问题!!!需要去修理厂!!!"); | |
} | |
} | |
} |
修理厂
package com.qfedu.c_repaired; | |
/** | |
* @author Anonymous 2023/7/25 16:07 | |
*/ | |
public class Factory { | |
private String name; | |
private String address; | |
private String telephone; | |
// 根据所需完成 Constructor 和 Getter and Setter 方法 | |
/* | |
核心功能方法 | |
修理汽车方法,该方法所需参数是 Car 类型的对象。 | |
*/ | |
/** | |
* 修理汽车方法,方法要求提供的参数是 Car 类型的对象 | |
* | |
* @param car Car 类型对象。 | |
*/ | |
public void fix(Car car) throws InterruptedException { | |
// 判断实际参数 Car 类型对象轮胎个数数据情况 | |
if (car.getWheelCount() != 4) { | |
// 需要进行修理 | |
System.out.println("轮胎出现问题,修理中~~~~"); | |
Thread.sleep(1000); | |
// 修理完毕,汽车的轮胎个数 ==> 4 | |
car.setWheelCount(4); | |
System.out.println("本次维修费用 20W 美元"); | |
} else { | |
System.out.println("您的车辆没有任何的问题!!!"); | |
} | |
} | |
} |
执行代码
package com.qfedu.c_repaired; | |
/** | |
* @author Anonymous 2023/7/25 16:03 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) throws InterruptedException { | |
// 实例化 Car 类型对象 | |
Car car = new Car("布加迪威龙", "黑色", 4); | |
for (int i = 0; i < 10; i++) { | |
car.race(); | |
// 程序暂停 0.5 S | |
Thread.sleep(500); | |
} | |
System.out.println("轮胎 Boom!!!掉了两个!!"); | |
car.setWheelCount(2); | |
car.race(); | |
// 实例化一个修理厂对象 | |
Factory factory = new Factory(); | |
factory.setName("狗鸽黑心修理厂"); | |
factory.setAddress("唐宁街 10 号"); | |
factory.setTelephone("138后面随便"); | |
/* | |
通过修理厂对象,调用 fix 方法,修理汽车,方法需要的参数 | |
是 Car 类型的对象 | |
*/ | |
factory.fix(car); | |
for (int i = 0; i < 10; i++) { | |
car.race(); | |
// 程序暂停 0.5 S | |
Thread.sleep(500); | |
} | |
} | |
} |
# 3. 自定义类型作为成员变量
键盘类
- 属性:品牌名称,按键个数
屏幕类
- 屏幕:品牌名称,尺寸
电脑类
- 属性:键盘类对象,屏幕类对象
键盘类
/** | |
* 键盘类 | |
* | |
* @author Anonymous 2023/7/25 16:49 | |
*/ | |
public class Keyboard { | |
private String name; | |
private int keyCount; | |
// 根据所需完成 Constructor 和 Getter and Setter 方法 | |
} |
屏幕类
/** | |
* 屏幕类 | |
* | |
* @author Anonymous 2023/7/25 16:50 | |
*/ | |
public class Screen { | |
private String name; | |
private float size; | |
// 根据所需完成 Constructor 和 Getter and Setter 方法 | |
} |
电脑类
package com.qfedu.d_computer; | |
/** | |
* 电脑类 | |
* | |
* @author Anonymous 2023/7/25 16:52 | |
*/ | |
public class Computer { | |
/** | |
* 电脑类键盘成员变量,使用的数据类型是自定义键盘类 | |
*/ | |
private Keyboard keyboard; | |
/** | |
* 电脑类屏幕成员变量,使用的数据类型是自定义屏幕类 | |
*/ | |
private Screen screen; | |
public Computer() { | |
} | |
/** | |
* 电脑类有参数构造方法,所需参数是键盘类对象和屏幕类对象,需要提供给 | |
* 当前电脑类的是自定义类型的对象。 | |
* | |
* @param keyboard 键盘类对象 | |
* @param screen 屏幕类对象 | |
*/ | |
public Computer(Keyboard keyboard, Screen screen) { | |
this.keyboard = keyboard; | |
this.screen = screen; | |
} | |
public Keyboard getKeyboard() { | |
return keyboard; | |
} | |
/** | |
* 方法的参数所需是 Keyboard 类型,用于替换当前电脑对象的键盘成员变量 | |
* | |
* @param keyboard 键盘类对象 | |
*/ | |
public void setKeyboard(Keyboard keyboard) { | |
this.keyboard = keyboard; | |
} | |
public Screen getScreen() { | |
return screen; | |
} | |
/** | |
* 方法的参数所需是 Screen 类型,用于替换当前电脑对象的屏幕成员变量 | |
* | |
* @param screen 屏幕类对象 | |
*/ | |
public void setScreen(Screen screen) { | |
this.screen = screen; | |
} | |
/** | |
* 展示当前电脑的配置 | |
*/ | |
public void show() { | |
/* | |
通过当前 Computer 类对象中的成员变量 | |
键盘和屏幕来获取相关的数据信息,键盘和屏幕对象都是【数据的载体】 | |
*/ | |
System.out.println("屏幕品牌 : " + screen.getName() + ", 屏幕尺寸 : " + screen.getSize()); | |
System.out.println("键盘品牌 : " + keyboard.getName() + ", 键盘按键个数 : " + keyboard.getKeyCount()); | |
} | |
} |
案例代码
package com.qfedu.d_computer; | |
/** | |
* 【重点】 | |
* 要什么,给什么,用什么,拿什么 | |
* | |
* @author Anonymous 2023/7/25 17:05 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
/* | |
备货: | |
键盘类对象,屏幕类对象 | |
*/ | |
Keyboard keyboard = new Keyboard("cherry 3494 红轴", 104); | |
Screen screen = new Screen("Dell", 24F); | |
/* | |
组装电脑,使用键盘和屏幕类对象作为构造方法参数,实例化 Computer 电脑类对象 | |
*/ | |
Computer computer = new Computer(keyboard, screen); | |
/* | |
查看电脑配置 | |
*/ | |
computer.show(); | |
System.out.println("--------------------"); | |
/* | |
更换键盘 | |
*/ | |
Keyboard keyboard1 = new Keyboard("IKBC C87", 87); | |
computer.setKeyboard(keyboard1); | |
/* | |
查看电脑配置 | |
*/ | |
computer.show(); | |
System.out.println("--------------------"); | |
/* | |
更换屏幕 | |
*/ | |
Screen screen1 = new Screen("AOC", 49F); | |
computer.setScreen(screen1); | |
/* | |
查看电脑配置 | |
*/ | |
computer.show(); | |
} | |
} |
# 4. final 关键字
最终的,不变的
# 4.1 成员变量 [重点]
final 修饰成员变量特征:
- 可以认为当前成员变量为【带有名称的常量】
- 要求在使用当前常量之前,必须赋值操作【默认值】和【有参数构造方法赋值】
package com.qfedu.e_final; | |
class TypeA { | |
/* | |
Variable 'MSG' might not have been initialized | |
MSG 变量可能尚未初始化 | |
【方案一】定义时采用默认值方式给予当前 MSG 赋值操作 | |
【方案二】利用有参数构造方法,对 final 修饰成员变量进行构造初始化操作。 | |
*/ | |
public final String MSG = "不要买钻戒"; | |
public final String INFO; | |
/* | |
利用有参数构造方法,在使用当前成员变量之前,对成员变量进行初始化操作, | |
并且赋值操作之后,当前变量的数据内容无法修改,可以认为是带有名称的常量 | |
【注意 语法特征】 | |
一旦当前类内有程序员自定义构造方法,Java 编译器不在提供任何的无参数构造方法 | |
供程序员使用。 | |
并且如果使用有参数构造方法方式给予 final 修饰成员变量进行赋值操作,当前代码 | |
不支持自定义无参数构造方法。 | |
*/ | |
public TypeA(String INFO) { | |
this.INFO = INFO; | |
} | |
} | |
/** | |
* @author Anonymous 2023/7/25 19:43 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
TypeA typeA = new TypeA("买黄金最保值"); | |
System.out.println(typeA.MSG); | |
/* | |
Cannot assign a value to final variable 'MSG' | |
不允许给 final 修饰的成员变量进行二次赋值操作, | |
final 修饰的成员变量是带有名称的常量 | |
*/ | |
//typeA.MSG = "尤其不要买 DR"; | |
System.out.println(typeA.INFO); | |
} | |
} |
# 4.2 成员方法
final 修饰的成员方法
- 不可以被子类重写,可以认为是最终方法 / 终极方法。
package com.qfedu.e_final; | |
class TypeB { | |
public final void test() { | |
System.out.println("我想去成都看熊猫"); | |
} | |
} | |
class TypeC extends TypeB { | |
/* | |
'test ()' cannot override 'test ()' in 'com.qfedu.e_final.TypeB'; overridden method is final | |
TypeC 类无法重写 TypeB 类中的 final 修饰 test 方法。 | |
*/ | |
// @Override | |
// public void test() { | |
// System.out.println ("我想去重庆吃火锅"); | |
// } | |
} | |
/** | |
* @author Anonymous 2023/7/25 19:58 | |
*/ | |
public class Demo2 { | |
public static void main(String[] args) { | |
TypeC typeC = new TypeC(); | |
typeC.test(); | |
} | |
} |
# 4.3 类
断子绝孙
final 修饰的类不能被继承,Java 中的部分基础类型采用的就是 final 修饰,例如 String 类型
package com.qfedu.e_final; | |
final class TypeD { | |
} | |
/* | |
Cannot inherit from final 'com.qfedu.e_final.TypeD' | |
不能继承一个 final 修饰的类 | |
*/ | |
class TypeE extends TypeD { | |
} | |
/** | |
* @author Anonymous 2023/7/25 20:03 | |
*/ | |
public class Demo3 { | |
} |
# 4.4 局部变量
final 修饰的局部变量不可以二次赋值。一般用于固定数据操作,或者用于延长局部变量【生存周期】
package com.qfedu.e_final; | |
/** | |
* @author Anonymous 2023/7/25 20:07 | |
*/ | |
public class Demo4 { | |
public static void main(String[] args) { | |
final int num; | |
num = 10; | |
System.out.println(num); | |
/* | |
Variable 'num' might already have been assigned to | |
num 变量可能已经被赋值过了! | |
*/ | |
// num = 20; | |
/* | |
面试题 | |
Student 成员变量有 {id, name, age} | |
final Student stu = new Student (); | |
1. stu = new Student (); | |
2. stu.id = 10; | |
3. stu.name = "张三"; | |
4. stu.age = 16; | |
答案: | |
A. 1 × 234 √ | |
B. 1 √ 234 × | |
C. 1234 √ | |
D. 1234 × | |
A | |
final 修饰引用数据类型 / 对象类型 | |
【注意一】指向不可变 | |
【注意二】指向空间内容数据可变 | |
*/ | |
} | |
} |
# day07static
# 1. 类加载问题
做某件事情
【准备工作】,需要准备相关的物料,同时不会准备无关的内容。
游戏中的读条行为
也是在游戏开始之前,准备游戏的相关资源。
类加载:
Java 程序在执行之前,JVM 会根据当前程序所需选择加载准备的 .class 二进制可执行字节码文件,所有的 .class 字节码文件准备就绪才会开始执行程序。
JVM 根据程序选择需要加载的 .class 字节码文件,会在程序运行过程中,选择加载哪一个 .class 字节码文件到内存中执行相关程序。
类加载:
1. 程序的准备工作,在程序执行之前准备就绪 【做菜之前备菜】
2. .class 字节码文件会根据程序的执行流程、顺序,在 JVM 的控制下加载到内存中。【做菜流程】
# 2. static 关键字概述
可以用于修饰成员变量,成员方法,代码块,技术中重点:
- 【没有对象】
- static 修饰内容是随着类文件加载,属于程序的【准备阶段】
- static 修饰成员变量,一般用于类内的统计数据使用,共享资源操作,类唯一变量
- static 修饰成员方法,一般作为工具类方法,使用方便,操作快捷,执行效率高
- static 修饰代码块,一般用于项目的启动阶段必要资源加载,配置文件读取,程序准备操作。
# 3. static 修饰成员变量
# 3.1 共享单车引入 static 成员变量概念
共享单车:
1. 占用的空间 属于公共区域,非私人空间
2. 归属权 归属于运营公司
3. 使用权 每一个用户都有使用权
4. 车辆出现问题 所有用户都无法使用
5. 共享单车 属于共享资源
概述:
1. 空间占用 公共
2. 归属权 类
3. 使用权 每一个对象
4. 出现更改 所有使用位置同步修改
5. 共享资源
# 3.2 static 修饰静态成员变量基本语法实现和特征
特征:
- 推荐使用类名调用,不推荐使用类对象调用【没有对象】
- static 修饰的静态成员变量,在类文件加载阶段【程序准备阶段】,在内存的【数据区】申请内存空间,在程序运行之前已经准备就绪,已具备变量存储能力和数据提供能力。生存周期早于类对象出现、【没有对象】
- static 修饰的静态成员变量,对于当前类,当前程序,有且只有一个。无论通过哪一种方式对当前数据进行修改,所有使用到静态成员变量的位置都会收到影响。【共享资源,独一份】
- static 修饰的静态成员变量,也可以称之为【类变量】
- Java 程序退出,首先会销毁实例化对象占用的内存空间,之后在销毁 static 修饰内容占用内存空间,静态成员变量晚于类对象销毁。静态成员变量生存周期和类对象不一致【没有对象】
package com.qfedu.a_static; | |
class TypeA { | |
/** | |
* 使用 static 修饰的静态成员变量 | |
*/ | |
public static int num = 10; | |
} | |
/** | |
* @author Anonymous 2023/7/26 10:02 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
// TypeA typeA = new TypeA(); | |
/* | |
Static member 'com.qfedu.a_static.TypeA.num' accessed via instance reference | |
static 修饰的成员变量不应该实例化对象调用 | |
【推荐】通过类名直接调用 | |
静态成员变量从代码执行实现角度,类对象和类名都可以直接调用,类对象调用不符合一定的代码 | |
特性要求,但是不违背语法规范。依然推荐使用类名直接调用。【没有对象】 | |
*/ | |
// System.out.println(typeA.num); | |
System.out.println(TypeA.num); | |
} | |
} |
# 4. static 修饰静态成员方法
# 4.1 案例
package com.qfedu.a_static; | |
/** | |
* 静态成员方法演示 | |
*/ | |
class TypeB { | |
// 非静态成员变量 | |
public String msg = "非静态成员变量"; | |
// 静态成员变量 | |
public static String info = "静态成员变量"; | |
// 非静态成员方法 | |
public void test() { | |
} | |
// 静态成员方法 | |
public static void testStatic() { | |
/* | |
Non-static field 'msg' cannot be referenced from a static context | |
不可以在 static 修饰的区域中,调用非静态成员变量,【没有对象】 | |
因为 static 修饰的静态成员方法可以通过类名调用,但是非静态成员变量 | |
需要类对象进行调用操作,当前方法不具备对象特征,无法使用, | |
*/ | |
// System.out.println(msg); | |
/* | |
Non-static method 'test ()' cannot be referenced from a static context | |
不可以在 static 修饰的静态区域中,调用非静态成员方法。【没有对象】 | |
非静态成员方法需要实例化对象调用,但是静态成员方法没有对象存在,无法调用。 | |
*/ | |
// test(); | |
/* | |
难兄难弟,互不嫌弃 | |
静态成员方法可以直接使用类内的静态成员变量 | |
*/ | |
System.out.println(info); | |
System.out.println("静态成员方法"); | |
} | |
} | |
/** | |
* @author Anonymous 2023/7/26 10:52 | |
*/ | |
public class Demo2 { | |
public static void main(String[] args) { | |
/* | |
静态成员方法可以通过类名直接调用 | |
*/ | |
TypeB.testStatic(); | |
TypeB typeB = new TypeB(); | |
/* | |
Static member 'com.qfedu.a_static.TypeB.testStatic ()' accessed via instance reference | |
static 修饰的静态成员方法不推荐使用类对象调用。【没有对象】 | |
*/ | |
typeB.testStatic(); | |
} | |
} |
# 4.2 静态成员方法特征总结【重点】
- 静态成员方法推荐使用类名调用,不推荐使用类对象调用【没有对象】
- 静态成员方法可以直接使用类内的静态成员 (静态成员方法,静态成员变量)【难兄难弟,互不嫌弃】
- 静态成员方法不可以使用类内的非静态成员。【没有对象】
- 静态成员方法在类文件 (.class 字节码文件) 加载阶段 / 程序准备阶段,在内存的【方法区】准备必要的内存空间存储当前方法的可执行内容,加载之后方法已具备【执行能力】,所有和方法执行相关的内容都已经完成【权限修饰,返回值数据类型,方法名,形式参数列表,方法体,异常】
- 静态成员方法通常用于【工具类】封装,提升代码执行效率。
# 4.3 static 静态成员方法工具类
为什么静态成员方法执行效率高
1. 时间效率高
2. 空间效率高
非静态成员方法调用需要类对象支持,对象实例化操作需要执行时间和内存空间,相较于静态成员方法效率较低。
成员方法,通过对象调用,期望使用的是对象执行方法的特征性
静态成员方法,通过类名调用,完成的代码是通识性,通配性方法。
学习 Java 中的数组工具类 Arrays
public static void sort(int[] arr);
Arrays 数组工具类排序方法,采用的排序方式为【双轴快排】。默认升序排序
public static String toString(int[] arr);
将数组内容元素使用 [] 包含,每一个元素直接使用 逗号 + 空格方式隔开,最终数据反馈形式为字符串形式,描
述当前数组存储数据内容情况
public static int binarySearch(int[] arr, int target);
二分法查询,找出指定数据在数组中的下标位置,要求数组必须通过 sort 排序,找到数据返回值大于等于 0,如
果没有找到返回负数,
【注意】不保证找到的目标数据在数组下标是第几个
package com.qfedu.a_static; | |
import java.util.Arrays; | |
/** | |
* Arrays 工具类 | |
* | |
* public static void sort (int [] arr); | |
* Arrays 数组工具类排序方法,采用的排序方式为【双轴快排】。默认升序排序 | |
* public static String toString (int [] arr); | |
* 将数组内容元素使用 [] 包含,每一个元素直接使用 逗号 + 空格方式隔开,最终数据反馈形式为字符串形式,描 | |
* 述当前数组存储数据内容情况 | |
* public static int binarySearch (int [] arr, int target); | |
* 二分法查询,找出指定数据在数组中的下标位置,要求数组必须通过 sort 排序,找到数据返回值大于等于 0,如 | |
* 果没有找到返回负数, | |
* 【注意】不保证找到的目标数据在数组下标是第几个 | |
* | |
* @author Anonymous 2023/7/26 11:32 | |
*/ | |
public class Demo3 { | |
public static void main(String[] args) { | |
int[] arr = {1, 3, 5, 7, 9, 2, 4, 6, 7, 10}; | |
System.out.println(Arrays.toString(arr)); | |
Arrays.sort(arr); | |
System.out.println(Arrays.toString(arr)); | |
int index = Arrays.binarySearch(arr, -100000); | |
System.out.println(index); | |
} | |
} |
# 5. static 修饰静态代码块
# 5.1 什么是代码块
// 构造代码块 | |
// 局部代码块 | |
// 静态代码块 |
构造代码块
package com.qfedu.a_static; | |
class TypeC { | |
public int num; | |
public String msg; | |
/* | |
构造代码块,推荐定义位置 在成员变量之后,构造方法之前 | |
在 Java 编译器编译当前代码时,会将构造代码块内容填充到每一个构造方法中第一行 [first statement] | |
可以认为,在执行当前类内任意构造方法,都会首先执行构造代码块内容 | |
*/ | |
{ | |
System.out.println("--------------构造代码块内容--------------"); | |
} | |
public TypeC() { | |
System.out.println("--------------构造方法内容--------------"); | |
} | |
public TypeC(int num) { | |
System.out.println("--------------构造方法内容--------------"); | |
this.num = num; | |
} | |
public TypeC(String msg) { | |
System.out.println("--------------构造方法内容--------------"); | |
this.msg = msg; | |
} | |
public TypeC(int num, String msg) { | |
System.out.println("--------------构造方法内容--------------"); | |
this.num = num; | |
this.msg = msg; | |
} | |
} | |
/** | |
* @author Anonymous 2023/7/26 14:32 | |
*/ | |
public class Demo4 { | |
public static void main(String[] args) { | |
TypeC t1 = new TypeC(); | |
TypeC t2 = new TypeC(10); | |
TypeC t3 = new TypeC("123"); | |
TypeC t4 = new TypeC(10 ,"123"); | |
} | |
} |
局部代码块
package com.qfedu.a_static; | |
/** | |
* @author Anonymous 2023/7/26 14:40 | |
*/ | |
public class Demo5 { | |
public static void main(String[] args) { | |
int num = 10; | |
/* | |
局部代码块,可以限制变量的作用范围,同时提升当前变量占用 | |
内存的空间的回收效率 | |
*/ | |
{ | |
int i = 20; | |
System.out.println(i); | |
} | |
/* | |
局部代码块中定义变量,超出大括号范围无法使用。 | |
*/ | |
// System.out.println(i); | |
} | |
} |
# 5.2 静态代码块
特征:
- 静态代码块,在类文件加载过程中一定会执行,有且只执行一次
- 静态代码块不可以使用类内的其他非静态成员
- 静态代码块可以使用类内的其他静态成员
- 静态代码块一般用于项目启动阶段,资源加载,配置文件读取,项目配置加载...
package com.qfedu.a_static; | |
class TypeD { | |
// 非静态成员变量 | |
public String msg = "非静态成员变量"; | |
// 静态成员变量 | |
public static String info = "静态成员变量"; | |
/* | |
静态代码块,推荐定义位置在成员变量之后,构造方法之前 | |
静态代码块,在类文件加载阶段一定执行,并且有且只执行一次。 | |
一般用于项目启动阶段,资源加载,配置文件读取,项目配置加载... | |
*/ | |
static { | |
/* | |
static 修饰的静态代码块,不可以使用非静态成员变量和非静态成员方法 | |
【没有对象】 | |
System.out.println (msg); | |
test (); | |
*/ | |
/* | |
static 修饰的静态代码块,可以直接调用类内其他静态成员【难兄难弟,互不嫌弃】 | |
*/ | |
System.out.println("~~~~静态代码块执行~~~~"); | |
System.out.println(info); | |
testStatic(); | |
} | |
// 非静态成员方法 | |
public void test() { | |
System.out.println("非静态成员方法"); | |
} | |
public static void testStatic() { | |
System.out.println("静态成员方法"); | |
} | |
} | |
/** | |
* @author Anonymous 2023/7/26 14:43 | |
*/ | |
public class Demo6 { | |
public static void main(String[] args) { | |
System.out.println("Demo6 代码执行~~~"); | |
TypeD typeD1 = new TypeD(); | |
TypeD typeD2 = new TypeD(); | |
TypeD typeD3 = new TypeD(); | |
TypeD typeD4 = new TypeD(); | |
TypeD typeD5 = new TypeD(); | |
TypeD typeD6 = new TypeD(); | |
TypeD typeD7 = new TypeD(); | |
TypeD typeD8 = new TypeD(); | |
} | |
} |
# 6. 静态面试题
简单模式
- 静态成员变量在类文件加载阶段,申请内存【数据区】内存空间,已具备数据存储能力和数据提供能力。
- 静态代码块在类文件加载过程中,一定执行,有且只执行一次,并不是第一个执行。
class Demo { | |
public static Demo d1 = new Demo(); | |
public static Demo d2 = new Demo(); | |
{ | |
System.out.println("构造代码块"); | |
} | |
static { | |
System.out.println("静态代码块"); | |
} | |
public Demo() { | |
System.out.println("构造方法"); | |
} | |
public static void main(String[] args) { | |
Demo d1 = new Demo(); | |
} | |
} | |
/* | |
以上代码的执行流程: | |
*/ |
进阶模式
class Demo { | |
public static Demo d1 = new Demo(); | |
public Demo d2 = new Demo(); | |
{ | |
System.out.println("构造代码块"); | |
} | |
static { | |
System.out.println("静态代码块"); | |
} | |
public Demo() { | |
System.out.println("构造方法"); | |
} | |
public static void main(String[] args) { | |
Demo d1 = new Demo(); | |
} | |
} | |
/* | |
以上代码的执行流程: | |
*/ |
地狱模式
- 继承情况下实例化对象代码执行流程
- 继承情况下加载相关静态资源流程
class Father { | |
{ | |
System.out.println("Father 构造代码块"); | |
} | |
static { | |
System.out.println("Father 静态代码块"); | |
} | |
} | |
class Son extends Father { | |
{ | |
System.out.println("Son 构造代码块"); | |
} | |
static { | |
System.out.println("Son 静态代码块"); | |
} | |
} | |
main() { | |
/* | |
隐含知识点: | |
子类实例化对象过程中,会默认调用父类的无参数构造方法。 | |
【有其父才有其子】 | |
父类构造方法的使用,是用于初始化父类可以继承给子类对象使用内容的内存空间。 | |
父类和子类都没有【显式】定义构造方法,Java 编译器会自动提供无参数构造方法,供程序员使用 | |
同时构造代码块内容会同步到无参数构造方法中 | |
整个代码块执行流程: | |
Father 静态代码块 | |
Son 静态代码块 | |
Father 构造代码块 | |
Son 构造代码块 | |
*/ | |
Son son = new Son(); | |
} |
# 7. 静态相关总结
# 7.1 静态成员变量
- 静态成员变量推荐使用类名调用,不推荐使用类对象调用【没有对象】
- 静态成员变量在类文件加载阶段,申请内存【数据区】空间,已经具备数据的存储能力和数据提供能力
- 静态成员变量在代码中唯一,对于类唯一,对于当前程序也是唯一。
- 静态成员变量一般用于共享资源,记录数据操作,一处修改,所有位置都会受到影响。
- 静态成员变量也可以称之为【类变量】
# 7.2 静态成员方法
- 静态成员方法推荐使用类名调用,不推荐使用类对象调用【没有对象】
- 静态成员方法可以直接使用类内的其他静态资源
- 静态成员方法不可以使用类内非静态资源
- 静态成员方法在类文件加载阶段,申请内存【方法区】空间,在方法区中已经具备执行能力,执行相关的所有内容都已准备就绪【权限修饰,返回值类型,方法名,形式参数列表,方法体,异常】
- 静态成员方法也可以称之为【类方法】
- 静态成员方法常用于工具类封装,代码执行效率高
# 7.3 静态代码块
- 静态代码块在类文件加载阶段一定执行,有且只执行一次。
- 静态代码块可以直接使用类内的其他静态资源
- 静态代码块不可以使用类内非静态资源
- 静态代码块常用于项目启动阶段资源加载,配置加载,程序准备...
# 7.4 静态资源内存图
# 接口 interface
# 1. 生活中的接口
接口:
USB HDMI TypeC USB-C 3.5MM mirco-usb nona-usb VGA RJ45
接口功能:
1. 拓展功能,可以在当前设备的基础上,通过接口连接不同的设备,拓展功能
例如 USB 接口连接 摄像头设备,鼠标设备,移动硬盘设备
2. 制定规范,制定标准,满足一致化操作。
知晓:
1. 接口能够做什么是接口连接的设备来决定的,非接口本身。接口只是决定了该做什么,而不是去做什么
2. 接口存在固定,公开的数据。
# 2. Java 中的接口
# 2.1 基本语法规则和关键字
接口定义的关键字: interface
接口的定义格式:
interface 接口名 { | |
// 成员变量 | |
// 成员方法 | |
} | |
/* | |
接口名要求符合大驼峰命名法,见名知意。 | |
也可以认为接口是一个特殊的【类】 | |
*/ |
遵从接口关键字: implements
class TypeA implements A { | |
} |
代码案例
package com.qfedu.b_interface; | |
/** | |
* 自定义 A 接口 | |
*/ | |
interface A { | |
/* | |
Variable 'num' might not have been initialized | |
num 变量可能尚未初始化??? | |
Java 中接口成员变量【缺省属性 / 默认属性】 public static final | |
*/ | |
int num = 10; | |
/* | |
interface abstract methods cannot have body | |
接口中 abstract 修饰的成员方法不能有方法体 | |
Java 中接口成员方法【缺省属性 / 默认属性】 public abstract | |
*/ | |
void test(); | |
} | |
/** | |
* TypeA 类遵从接口 A ,需要实现接口 A 所有缺省属性 / 默认属性为 public abstract 修饰方法。 | |
* | |
* TypeA 类是接口 A 的实现类 | |
*/ | |
class TypeA implements A { | |
@Override | |
public void test() { | |
System.out.println("TypeA 类实现接口 A 中的成员方法"); | |
} | |
} | |
/** | |
* @author Anonymous 2023/7/26 16:39 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
TypeA typeA = new TypeA(); | |
System.out.println(TypeA.num); | |
typeA.test(); | |
} | |
} |
# day08 接口和多态
# 1. 接口
# 1.1 接口的特色语法
# 1.1.1 一个类可以同时遵从多个接口
package com.qfedu.a_interface; | |
interface A { | |
/** | |
* 接口 A testA 方法 | |
*/ | |
void testA(); | |
/** | |
* 听 A 的 | |
*/ | |
void test(); | |
} | |
interface B { | |
void testB(); | |
/** | |
* 听 B 的 | |
*/ | |
void test(); | |
} | |
/** | |
* TypeA 同时遵从接口 A 和 接口 B ,不同的接口之间使用 | |
* 逗号隔开 | |
*/ | |
class TypeA implements B, A { | |
/** | |
* TypeA 遵从接口 A 实现的 testA 方法 | |
*/ | |
@Override | |
public void testA() { | |
System.out.println("TypeA 接口实现 testA 方法"); | |
} | |
@Override | |
public void test() { | |
System.out.println("TypeA 遵从接口 A B 中有方法声明完全一致的方法,需要实现一次即可"); | |
} | |
@Override | |
public void testB() { | |
System.out.println("TypeA 接口实现 testB 方法"); | |
} | |
} | |
/** | |
* 一个类遵从多个接口 | |
* | |
* @author Anonymous 2023/7/27 9:36 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
TypeA typeA = new TypeA(); | |
typeA.testA(); | |
typeA.testB(); | |
} | |
} |
# 1.1.2 接口可以继承其他接口,并且允许多继承
package com.qfedu.a_interface; | |
interface USB1_0 { | |
void usb10Connect(); | |
} | |
interface USB2_0 { | |
void usb20Connect(); | |
} | |
/** | |
* USB 3.0 接口继承 USB1_0, USB2_0 接口。 | |
*/ | |
interface USB3_0 extends USB1_0, USB2_0 { | |
void usb30Connect(); | |
} | |
class Udisk implements USB3_0 { | |
@Override | |
public void usb10Connect() { | |
System.out.println("USB 1.0 协议实现"); | |
} | |
@Override | |
public void usb20Connect() { | |
System.out.println("USB 2.0 协议实现"); | |
} | |
@Override | |
public void usb30Connect() { | |
System.out.println("USB 3.0 协议实现"); | |
} | |
} | |
/** | |
* 接口继承其他继承,并且允许多继承 | |
* | |
* @author Anonymous 2023/7/27 9:46 | |
*/ | |
public class Demo2 { | |
public static void main(String[] args) { | |
Udisk udisk = new Udisk(); | |
udisk.usb10Connect(); | |
udisk.usb20Connect(); | |
udisk.usb30Connect(); | |
} | |
} |
# 1.1.3 default 关键字使用
在 JDK 1.8 版本以上,接口可以使用 default 关键字修饰方法,为默认方法,允许有方法体。
package com.qfedu.a_interface; | |
interface C { | |
void test(); | |
/** | |
* default 修饰的方法支持方法体存在 | |
*/ | |
default void testDefault() { | |
System.out.println("接口中的默认方法,default 修饰"); | |
} | |
} | |
class TypeB implements C { | |
@Override | |
public void test() { | |
System.out.println("接口中的缺省属性为 public abstract 修饰方法实现"); | |
} | |
@Override | |
public void testDefault() { | |
System.out.println("[重写]接口当前的 default 修饰默认方法"); | |
} | |
} | |
/** | |
* default 关键字修饰接口中方法,为默认方法,允许有方法体 | |
* | |
* @author Anonymous 2023/7/27 9:51 | |
*/ | |
public class Demo3 { | |
public static void main(String[] args) { | |
TypeB typeB = new TypeB(); | |
typeB.test(); | |
typeB.testDefault(); | |
} | |
} |
# 1.1.4 类可以继承父类的同时,遵从接口
package com.qfedu.a_interface; | |
class Father { | |
public void game() { | |
System.out.println("坦克大战"); | |
} | |
} | |
interface D { | |
void testD(); | |
} | |
/** | |
* 类首先继承父类,之后遵从接口 | |
*/ | |
class Son extends Father implements D { | |
@Override | |
public void game() { | |
System.out.println("WOT 坦克世界"); | |
} | |
@Override | |
public void testD() { | |
System.out.println("遵从接口要求实现的方法"); | |
} | |
} | |
/** | |
* 类继承父类同时遵从接口 | |
* | |
* @author Anonymous 2023/7/27 9:59 | |
*/ | |
public class Demo4 { | |
public static void main(String[] args) { | |
Son son = new Son(); | |
son.game(); | |
son.testD(); | |
} | |
} |
# 1.2 后续接口知识点
- 匿名内部类
- Lambda 表达式
- 函数式接口
- Stream 流
- 方法引用
- MVC 设计模式接口连接套路
# 2. 多态
# 2.1 动物园
package com.qfedu.b_; | |
/** | |
* 动物类的父类 | |
*/ | |
class Animal {} | |
/* | |
猴子,老虎,熊猫都是动物类的子类。 | |
*/ | |
class Monkey extends Animal {} | |
class Tiger extends Animal {} | |
class Panda extends Animal {} | |
/** | |
* 多态的总结; | |
* 1. 方法所需类型为父类类型,提供给当前方法作为方法实际参数的类型,可以是父类类型本身 | |
* 或者其子类对象 | |
* 拓宽方法的参数支持范围,也可以降低开发的压力。 | |
* 2. 方法返回值类型为父类类型,可以直接返回父类对象本身,获取其子类对象,可以拓宽方法 | |
* 返回值数据范围 | |
* 3. 强制类型转换,大数据类型转换为小数据类型,需要明确类型的一致性。 | |
* | |
* @author Anonymous 2023/7/27 10:54 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
Animal animal = new Animal(); | |
Monkey monkey = new Monkey(); | |
Tiger tiger = new Tiger(); | |
Panda panda = new Panda(); | |
feedAnimal(animal); | |
/* | |
Monkey 类对象继承 Animal 类,可以认为是 Animal 类型 | |
*/ | |
feedAnimal(monkey); | |
/* | |
Tiger 类对象继承 Animal 类,可以认为是 Animal 类型 | |
*/ | |
feedAnimal(tiger); | |
/* | |
Panda 类对象继承 Animal 类,可以认为是 Animal 类型 | |
*/ | |
feedAnimal(panda); | |
/* | |
调用领养动物方法 | |
显式类型: Animal 类型 | |
真实类型: Panda 类型 | |
父类引用指向子类对象。 | |
Panda 继承 Animal ,是 Animal 类的一个子类,可以认为是 Animal 类型 | |
符合数据类型一致化原则,这里包含【类型自动向上转型】 | |
可以将 Panda 看做是 Animal 类型 | |
*/ | |
Animal animal1 = getAnimal(); | |
/* | |
【强制类型转换】 | |
已知 animal1 对象真实数据类型为 Panda 类型,考虑到后期更好的使用, | |
强制类型转换为 Panda 类型 | |
(Panda) 强制类型转换格式,告知 Java 编译器,当前 animal1 对应的 | |
对象转换为 Panda 类型 | |
【注意】 | |
如果 animal1 对应的对象非 Panda 类型,运行时报错 | |
ClassCastException 类型转换异常 | |
*/ | |
Panda panda1 = (Panda) animal1; | |
System.out.println("展示:" + panda1); | |
} | |
/** | |
* 喂食动物的方法,需要的参数是 Animal 动物类对象。 | |
* | |
* @param animal Animal 动物类对象 | |
*/ | |
public static void feedAnimal(Animal animal) { | |
/* | |
animal.getClass () 可以获取当前对象对应的具体数据类型 | |
*/ | |
System.out.println(animal.getClass() + " 来吃饭了~~~"); | |
} | |
/** | |
* 从动物园领养一只动物,方法返回值类型为 Animal 类型 | |
* | |
* @return Animal 对象 | |
*/ | |
public static Animal getAnimal() { | |
return new Panda(); | |
/* | |
return new Animal(); | |
return new Tiger(); | |
return new Monkey(); | |
*/ | |
} | |
} |
# 2.2 USB 接口和 USB 设备
package com.qfedu.b_; | |
interface USB { | |
/** | |
* USB 接口规定 USB 设备必须实现的方法,在方法中描述 | |
* USB 设备对应的功能。 | |
*/ | |
void connect(); | |
} | |
/** | |
* 键盘 遵从 USB 接口 | |
* 键盘是【USB 设备】 | |
*/ | |
class Keyboard implements USB { | |
@Override | |
public void connect() { | |
System.out.println("键盘连接电脑,作为输入设备"); | |
} | |
} | |
/** | |
* IKBC 继承 Keyboard ,间接遵从 USB 接口 | |
* 也可以认为是 USB 接口实现类 / USB 设备 | |
*/ | |
class IKBC extends Keyboard { | |
@Override | |
public void connect() { | |
System.out.println("IKBC C87 茶轴/红轴"); | |
} | |
} | |
/** | |
* 鼠标 遵从 USB 接口 | |
* 鼠标是【USB 设备】 | |
*/ | |
class Mouse implements USB { | |
@Override | |
public void connect() { | |
System.out.println("鼠标连接电脑,控制光标"); | |
} | |
} | |
/** | |
* Logi 继承 Mouse 类。间接遵从 USB 接口 | |
* 也可以认为是 USB 接口实现类 / USB 设备 | |
*/ | |
class Logi extends Mouse { | |
@Override | |
public void connect() { | |
System.out.println("Logi G502 Hero"); | |
} | |
} | |
class Computer { | |
/** | |
* 电脑类使用 USB 接口方法,参数对应的数据类型为 USB 接口类型 | |
* 相当于当前电脑预留了 USB 接口用于连接 USB 设备。 | |
* | |
* 【方法所需参数为接口类型,执行方法所需实际参数为接口实现类对象】 | |
* | |
* @param usb USB 接口类型,实际参数是 USB 接口的实现类,可以理解为 USB 设备 | |
*/ | |
public void useUsbInterface(USB usb) { | |
/* | |
貌似是 USB 接口的引用调用了 USB 接口的 connect () 方法 | |
看上去 connect () 方法没有方法体!!! | |
在方法的实际执行过程中,USB usb 引用指向的是一个 USB 接口的实现类对象 | |
实现类已经实现了 connect () 方法功能!!! | |
当前代码只是描述 USB 设备应该执行的方法是哪一个。 | |
*/ | |
usb.connect(); | |
} | |
} | |
/** | |
* 接口多态特征: | |
* 1. 方法参数类型为接口类型,方法执行实际参数是接口的实现类对象。 | |
* 2. 方法参数类型为接口类型,可以传入的实际参数为接口实现类或者接口的间接实现类 | |
* 拓宽了方法的参数范围 | |
* | |
* @author Anonymous 2023/7/27 14:40 | |
*/ | |
public class Demo2 { | |
public static void main(String[] args) { | |
Computer computer = new Computer(); | |
Mouse mouse = new Mouse(); | |
Keyboard keyboard = new Keyboard(); | |
/* | |
Computer 类对象调用 useUsbInterface 方法,传入参数 | |
Keyboard 对象和 Mouse 对象,都是 USB 接口实现类对象 / USB 设备 | |
*/ | |
computer.useUsbInterface(mouse); | |
computer.useUsbInterface(keyboard); | |
IKBC ikbc = new IKBC(); | |
Logi logi = new Logi(); | |
computer.useUsbInterface(ikbc); | |
computer.useUsbInterface(logi); | |
/* | |
Lambda 表达式 | |
*/ | |
computer.useUsbInterface(() -> System.out.println("这里是一个没有外壳的鼠标")); | |
/* | |
Function Reference 方法引用【四饼】 | |
*/ | |
computer.useUsbInterface(Demo2::test); | |
} | |
public static void test() { | |
System.out.println("这里是一个非量产的键盘"); | |
} | |
} |
# 2.3 多态总结
- 编译看左,运行看右
- 编译看左:Java 编译代码过程中,以左侧的大类型为主,右侧数据只要是子类或者实现类即可
- 运行看右:运行过程中会根据右侧的真实类型,来执行目标方法,提供目标数据
- 父类的引用指向子类对象
- 接口的引用指向实现类对象
作用:
- 拓宽方法的参数支持范围
- 方法所需参数为父类类型,可以给予当前方法的实际参数父类对象或者其子类对象
- 方法所需参数为接口类型,可以给予当前方法的实际参数是接口的实现类对象或者间接实现类对象
- 拓宽方法的返回值类型范围
- 方法返回值类型为父类类型,可以返回的数据类型包括父类对象本身或者其子类对象
- 方法返回值类型为接口类型,可以返回的数据类型是接口的实现类或者间接实现类
[toc]
# 泛型【重点】
# 1. 泛型
# 1.1 泛型体验卡
传统形式
package com.qfedu.c_generic; | |
/** | |
* 泛型体验卡 | |
* | |
* 补充知识点:方法的重载【reload】 | |
* 1. 在同一个类内或者接口内 | |
* 2. 要求方法名称必须完全一致 | |
* 3. 要求方法的形式参数列表不一致,数据类型,数据格式,数据顺序。 | |
* 4. Java 编译器会根据实际参数数据类型,选择目标方法执行 | |
* 【方法调用执行的两大要素 方法名 + 实际参数数据类型】 | |
* @author Anonymous 2023/7/27 15:56 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
printValue(10); | |
printValue(10.5F); | |
printValue(10.5); | |
printValue("你好"); | |
/* | |
以上方法调用都是展示【数据】,只不过是不同类型的【数据】 | |
核心目标 展示【数据】 | |
*/ | |
} | |
public static void printValue(int num) { | |
System.out.println("用户提供数据:" + num); | |
} | |
public static void printValue(float num) { | |
System.out.println("用户提供数据:" + num); | |
} | |
public static void printValue(double num) { | |
System.out.println("用户提供数据:" + num); | |
} | |
public static void printValue(String str) { | |
System.out.println("用户提供数据:" + str); | |
} | |
} |
引入泛型操作对应的效果
package com.qfedu.c_generic; | |
/** | |
* 泛型体验卡 | |
* @author Anonymous 2023/7/27 15:56 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
printValue(10); | |
printValue(10.5F); | |
printValue(10.5); | |
/* | |
* 原本情况 | |
* <T>void com.qfedu.c_generic.Demo1.printValue (T t) | |
* 方法调用实际参数提供之后的数据形式 | |
* <String> void com.qfedu.c_generic.Demo1.printValue (String t) | |
* | |
* 方法调用提供的实际参数数据类型为 String 类型,当前方法使用的泛型 T 约束的具体数据类型为 String 类型 | |
* 所有使用到泛型 T 的位置都是字符串 String 类型 | |
* | |
* 泛型支持数据类型多样性,但是一旦确定泛型对应的具体数据类型,严格遵守数据类型一致化原则 | |
*/ | |
printValue("你好"); | |
/* | |
* 以上方法调用都是展示【数据】,只不过是不同类型的【数据】 核心目标 展示【数据】 | |
*/ | |
} | |
/** | |
* 当前方法声明告知采用【泛型 T】 操作 | |
* | |
* @param <T> 告知 Java 编译器当前方法使用的泛型占位符为 T | |
* @param t 参数类型为泛型 T 类型,对应的具体数据类型由【实际参数决定】 | |
*/ | |
public static <T> void printValue(T t) { | |
System.out.println("用户提供数据:" + t); | |
} | |
} |
# 1.2 泛型概述
泛型基本格式:
<自定义无意义大写单个英文字母占位符>
推荐:
<T> Type 类型
<E> Element 元素
<K> Key 键
<V> Value 值
<R> Return 返回值
主要用于方法的【增强】,偶尔用于成员变量和局部变量
可以约束的内容:
1. 单一方法
2. 类
3. 接口
气管炎模式
自由模式
# 1.3 泛型增强单一方法
格式要求
修饰符 <自定义泛型占位符> 返回值类型 方法名(形式参数列表) { 方法体}
/*
要求: 1. 方法参数列表中必须有一个参数对应自定义泛型,用于在方法调用过程中,明确泛型对应的具体数据类型。 如果没有【语法报错】 2. 方法的返回值可以利用自定义泛型 3. 方法体中的局部变量可以声明自定义泛型变量 */
package com.qfedu.c_generic; | |
/* | |
* 泛型约束单一方法案例 | |
*/ | |
public class Demo2 { | |
public static void main(String[] args) { | |
/* | |
* <Integer> Integer com.qfedu.c_generic.Demo2.getType (Integer t) | |
* tips: Integer ==> int | |
* | |
* 实际参数类型为 int 类型,当前方法所有泛型对应具体数据类型都是 int 类型 | |
*/ | |
int i = getType(10); | |
/* | |
* <String> String com.qfedu.c_generic.Demo2.getType (String t) | |
* | |
* 实际参数类型为 String 类型,当前方法所有泛型对应具体数据类型都是 String 类型 | |
*/ | |
String str = getType("泛型咯咯哒"); | |
Demo2 d = new Demo2(); | |
/* | |
* <Demo2> Demo2 com.qfedu.c_generic.Demo2.getType (Demo2 t) | |
* | |
* 实际参数类型为 Demo2 类型,当前方法所有泛型对应具体数据类型都是 Demo2 类型 | |
*/ | |
Demo2 d1 = getType(d); | |
} | |
/** | |
* 当前方法所有使用到的数据都是泛型占位符,具体数据类型有调用过程中,、 | |
* 实际参数对应 数据类型进行【约束操作】 | |
* | |
* @param <T> 自定义泛型占位符 | |
* @param t 形式列表参数,参数数据类型为自定义泛型 T ,需要实际参数约束对应的具体数据类型 | |
* @return 自定义泛型 T 对应的具体数据类型,需要根据参数形式来确定 | |
*/ | |
public static <T> T getType(T t) { | |
return t; | |
} | |
} |
# 1.4 泛型增强类,目标核心还是方法
格式要求
class 类名<自定义泛型占位符> {推荐成员方法可以使用自定义泛型;
不推荐/不建议成员变量使用自定义泛型;}
/*
要求: 1. 泛型对应的具体数据类型需要在实例化对象过程中约束 例如: class TypeA<T> {...} Eclipse TypeA<String> t1 = new TypeA<String>(); 约束当前 TypeA 类对象 t1,所有使用到泛型位置对应具体数据类型为 String 类型 TypeA<Demo1> t2 = new TypeA<Demo1>(); 约束当前 TypeA 类对象 t2,所有使用到泛型位置对应具体数据类型为 Demo1 类型 IDEA TypeA<String> t1 = new TypeA<>(); 约束当前 TypeA 类对象 t1,所有使用到泛型位置对应具体数据类型为 String 类型 TypeA<Demo1> t2 = new TypeA<>(); 约束当前 TypeA 类对象 t2,所有使用到泛型位置对应具体数据类型为 Demo1 类型 2. 所有使用泛型的方法,泛型对应具体数据类型根据当前实例化对象约束明确 3. 【强制建议】任何一个带有自定义泛型的类,在使用之前必须实例化对象约束泛型对应的具体类型。 */
package com.qfedu.c_generic; | |
/** | |
* 自定义类型带有泛型占位符 T | |
*/ | |
class TypeA<T> { | |
/** | |
* 当前方法使用的泛型是 类声明泛型,泛型对应的具体数据类型需要实例化对象明确 | |
* | |
* @param t 泛型对应参数,具体数据类型依赖于实例化对象操作 | |
* @return 泛型对应参数,具体数据类型依赖于实例化对象操作 | |
*/ | |
public T getType(T t) { | |
return t; | |
} | |
/* | |
* 知识点: | |
* 1. static 修饰的静态成员方法在类文件加载阶段已经具备执行能力。 | |
* 2. 类声明泛型,需要在实例化对象之后,才可以明确泛型对应的具体数据类型。 | |
* | |
* 解释错误: | |
* static 修饰的静态成员方法使用类名声明的泛型,两者生命周期不同,无法满足 | |
* 静态方法在加载之后的执行能力,无法明确泛型对应的具体数据类型 | |
* | |
* 解决方案: | |
* static 修饰静态成员方法,自定义泛型使用,但是推荐和类名使用不同的泛型 | |
* 占位符,提升代码的阅读性 | |
*/ | |
public static <A> A test(A a) { | |
return a; | |
} | |
} | |
public class Demo3 { | |
public static void main(String[] args) { | |
/* | |
* 实例化对象约束泛型对应的具体数据类型为 String 类型 | |
*/ | |
TypeA<String> t1 = new TypeA<String>(); | |
/* | |
* 当前 getType 方法泛型对应具体数据类型为 String 类型 | |
*/ | |
String string = t1.getType("字符串"); | |
System.out.println(string); | |
/* | |
* 实例化对象约束泛型对应的具体数据类型为 int 类型 | |
*/ | |
TypeA<Integer> t2 = new TypeA<Integer>(); | |
/* | |
* 当前 getType 方法泛型对应具体数据类型为 int 类型 | |
*/ | |
Integer i = t2.getType(100); | |
System.out.println(i); | |
/* | |
* 实例化对象过程没有明确泛型对应的具体数据类型,当前对象 | |
* 所有泛型对应数据位置都是 Object 类型 | |
* 【墙裂 / 强烈不推荐】 | |
*/ | |
TypeA t3 = new TypeA(); | |
t3.getType(100); | |
t3.getType("321321"); | |
t3.getType(5.5); | |
} | |
} |
# 1.5 泛型增强接口,目标核心还是方法
格式要求:
interface 接口名<自定义泛型占位符> {成员变量不得使用自定义泛型;
成员方法和default 修饰方法可以使用自定义泛型;}
/*
要求: 1. 成员变量缺省属性为 public static final 要求定义时必须初始化,如果使用自定义泛型,泛型在没有明确 对应具体数据类型的情况下,无法选择合适的数据进行初始化操作。 2. 缺省属性为 public abstract 修饰的成员方法和 default 修饰成员方法推荐使用泛型,接口的泛型就是为 了方法 3. 接口中泛型对应的具体数据类型有两种形式约束 3.1 妻管严模式 【使用最多】 3.2 媳妇回娘家 (自由) 模式 */
package com.qfedu.c_generic; | |
/** | |
* 带有自定义泛型的接口 | |
* @author Anonymous | |
* | |
* @param <T> 自定义泛型占位符 | |
*/ | |
interface A<T> { | |
/** | |
* 方法使用的泛型为接口声明的泛型 | |
* | |
* @param t 自定义泛型类型 | |
* @return 自定义泛型类型 | |
*/ | |
T getType(T t); | |
} | |
/* | |
* 媳妇回娘家 (自由) 模式 | |
* 你媳妇告诉你要吃饭【约束】,类型自行选择 | |
* | |
* 自定义类带有泛型,泛型和接口泛型一致,没有明确的数据类型约束 | |
* 当前泛型对应的具体数据类型由实例化对象过程约束,比较自由。 | |
*/ | |
class TypeB<T> implements A<T> { | |
@Override | |
public T getType(T t) { | |
/* | |
* 方法泛型 T 依然还是占位符形式 | |
*/ | |
return t; | |
} | |
} | |
/* | |
* 妻管严模式 【使用最多】 | |
* 你媳妇告诉你要吃饭【约束】,并且限制了吃饭的类型 | |
* | |
* 类遵从带有接口的泛型, 在遵从接口的同时,泛型直接明确约束指定数据类型 | |
* 类实现接口中带有泛型的方法,泛型对应的具体数据类型和接口遵从声明一致 | |
*/ | |
class TypeC implements A<String> { | |
/* | |
* 实现接口中带有自定义泛型的方法,泛型对应的具体数据类型已明确 | |
* 无法修改!!! | |
*/ | |
@Override | |
public String getType(String t) { | |
// TODO Auto-generated method stub | |
return null; | |
} | |
} | |
public class Demo4 { | |
public static void main(String[] args) { | |
/* | |
* 泛型对应的具体数据类型需要实例化对象过程约束,当前约束泛型对应具体 | |
* 数据类型为 String 类型 | |
*/ | |
TypeB<String> t1 = new TypeB<String>(); | |
String str = t1.getType("字符串类型"); | |
/* | |
* 泛型对应的具体数据类型需要实例化对象过程约束,当前约束泛型对应具体 | |
* 数据类型为 Integer 类型 | |
*/ | |
TypeB<Integer> t2 = new TypeB<Integer>(); | |
Integer i = t2.getType(10); | |
TypeC t3 = new TypeC(); | |
String type = t3.getType("气管炎模式"); | |
} | |
} |
# day09 异常
# 1. 异常概述
异常是代码运行过程中,抛出到程序中的错误 / 异常信息,可以告示程序员当前程序运行有那些问题,有那些必要要求有预案处理的问题。
今天的课程讲完之后只需要记住 Alt + Enter + Enter
ExceptionHandler
# 2. 异常相关类型和相关方法
class Throwable | |
Java 中的所有异常/错误的基类,主要规定了异常/错误必须有【message 信息】 | |
class Exception extends Throwable | |
Java 中的异常,异常可以处理,处理之后 JVM 认为代码中没有任何的异常 | |
class Error extends Throwable | |
Java 中的错误,无法处理,只能避免。 | |
Throwable 相关方法 | |
构造方法: | |
Throwable(); | |
无参数构造方法,一般情况下是 JVM 调用,实例化当前异常对象进行异常提示/抛出 | |
Throwable(String message); | |
message 作为 Throwable 对象的异常/错误信息,实例化 Throwable 对象 | |
成员方法: | |
String getMessage(); | |
获取当前异常/错误的信息描述 | |
String toString(); | |
获取当前异常/错误的简要信息描述,包括异常/错误类型和异常/错误信息 | |
void printStackTrace(); | |
在控制台展示当前异常/错误的前因后果 |
package com.qfedu.a_throwable; | |
/** | |
* @author Anonymous 2023/7/28 10:06 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
Throwable throwable = new Throwable("兜里没有钱异常"); | |
System.out.println("Message: " + throwable.getMessage()); | |
System.out.println("toString: " + throwable.toString()); | |
test(); | |
} | |
public static void test() { | |
Throwable throwable = new Throwable("23333333"); | |
throwable.printStackTrace(); | |
} | |
} |
# 3. Exception 和 Error 的区别
Exception 异常,可以处置
Java 中所有的异常类型都是以 Exception 结尾。
NullPointerException ArrayIndexOutOfBoundsException
处理方式:
1. 抛出
2. 捕获
异常分类:
1. 运行时异常 RuntimeException
2. 编译时异常
Error 错误,只能避免
Java 中所有的错误类型都是以 Error 结尾。
IOError OutOfMemoryError
# 4. 抛出异常
涉及到的关键字:
throw 在方法中通过条件判断之后,明确抛出异常对象。
throws 在方法的【声明位置】告知方法调用者,当前方法有那些异常抛出
package com.qfedu.a_throwable; | |
import java.io.FileInputStream; | |
import java.io.FileNotFoundException; | |
import java.io.IOException; | |
/** | |
* @author Anonymous 2023/7/28 10:56 | |
*/ | |
public class Demo2 { | |
public static void main(String[] args) { | |
} | |
/** | |
* 方法内部通过判断抛出了指定异常,需要在方法的声明位置,明确告知方法的调用 | |
* 者当前方法有异常抛出 | |
* | |
* 方法可以在声明告知对外声明多个异常情况,不同的异常通过,隔开 | |
* | |
* @param filePath 文件路径 | |
* @throws FileNotFoundException 用户提供路径对应的文件不存在 | |
* @throws InterruptedException 线程中断异常 | |
*/ | |
public static void createFile(String filePath) | |
throws FileNotFoundException, InterruptedException { | |
/* | |
首先对应用户提供的路径进行基本情况判断 | |
用户提供的 filePath 是否为 null,或者 filePath 对应的路径是否为 "" 空字符串 | |
*/ | |
if (null == filePath || "".equals(filePath)) { | |
/* | |
在判断用户提供的数据之后,选择抛出异常 FileNotFoundException, | |
实例化 FileNotFoundException 异常对象,并且赋予异常信息 | |
当前方法有明确的异常抛出,但是没有在方法的声明位置告知方法调用者, | |
当前方法有异常情况 | |
*/ | |
throw new FileNotFoundException("文件路径不合法"); | |
} | |
/* | |
代码运行暂停 1 s | |
Thread.sleep (1000) 抛出 InterruptedException | |
*/ | |
Thread.sleep(1000); | |
} | |
/** | |
* 【调用方法带有明确的异常抛出,选择抛出异常处理】 | |
* 异常演示方法,需要在方法声明位置告知调用当前方法的异常抛出情况,明确告知用户那些情况会导致 | |
* 当前异常的出现。 | |
* | |
* @param filePath 文件路径 | |
* @throws FileNotFoundException 用户提供路径对应的文件不存在 | |
*/ | |
public static void openFile(String filePath) throws IOException { | |
/* | |
代码中如果出现错误情况,错误情况对应的是【编译时异常提示】,需要明确处理当前异常类型 | |
Alt + Enter 来处理当前异常情况 | |
选择 在方法声明位置抛出异常。 | |
FileInputStream 抛出 FileNotFoundException | |
*/ | |
FileInputStream fileInputStream = new FileInputStream(filePath); | |
/* | |
close 抛出 IOException | |
*/ | |
fileInputStream.close(); | |
} | |
} |
# 5. 捕获异常
语法格式:
try {// 有可能出现异常的代码
} catch (/* 异常类型对象 */) {// 针对于当前异常的处理方式
} catch (/* 异常类型对象 */) {// 针对于当前异常的处理方式
}
# 6. 捕获和抛出异常
# day10 集合【重点】
# 1. 集合概述
数组作为基本的大量数据存储方式,有以下问题
1. 数据类型支持单一
2. 配套功能方法较少,需要程序自行完成,并且具有一定的数据类型约束性。
3. 数组容量无法修改,动态数据存储无法满足。
Java 中的【集合 Collection】就可以处理以上问题
1. 数据类型支持多样性,同时可以保证数据类型一致化要求,存在泛型约束
2. 配套方法众多,配套工具类,以及 Stream 流可以满足集合的操作方便性
3. 容量问题不需要调用者考虑,自动扩容。
# 2. 集合的类型架构
interface Collection<E> | |
Java 中所有集合的总接口,规定了集合的基本方法,基本形式。并且带有泛型约束。 | |
--| interface List<E> extends Collection<E> | |
List 集合接口,特征:有序,可重复 | |
--| class ArrayList<E> implements List<E> | |
底层数据存储结构采用的是 Object 类型数组,特征: 增删慢,查询快 | |
--| class LinkedList<E> implements List<E> | |
底层数据存储结构采用的是【双向有头链表】,特征: 增删快,查询慢 | |
--| class Vector<E> implements List<E> | |
底层数据存储结构采用的是 Object 类型数组,源自于 JDK1.0,相较于 ArrayList 性能较差,线程安全 | |
--| interface Set<E> extends Collection<E> | |
Set 集合接口,特征:无序,不可重复 | |
--| class HashSet<E> implements Set<E> | |
底层数据存储结构采用的是【哈希表】结构,存储效率极高 | |
--| class TreeSet<E> implements Set<E> | |
底层数据存储结构采用的是【平衡二叉树】结构,【重点】要求添加的元素有自然顺序,或者可排序规则 | |
TreeSet 或补充重点知识 Comparator 和 Comparable 接口 |
# 3. Collection 集合相关方法
增 | |
add(E e); | |
当前集合中,添加实例化集合对象泛型约束对应具体数据类型对象。 | |
addAll(Collection<? extends E> c); | |
当前集合中,添加另一个集合对象,要求参数集合对象存储的元素为当前集合约束类型或者其子类类型 | |
删 | |
remove(Object obj); | |
删除当前集合中指定的元素 | |
removeAll(Collection<?> c); | |
调用方法集合中,删除与参数集合的交集 | |
retainAll(Collection<?> c); | |
调用方法集合中,仅保留与参数集合的交集 | |
clear(); | |
清空整个集合中的元素。 | |
查 | |
int size(); | |
获取集合中存储的有效元素个数。 | |
boolean isEmpty(); | |
判断当前集合是否为空,如果为空,返回 true | |
boolean contains(Object obj); | |
判断当前集合是否包含参数对象 | |
boolean containsAll(Collection<?> c); | |
判断参数集合是否为当前集合的子集合 | |
Object[] toArray(); | |
集合中所有元素打包为 Object 类型数组返回 |
# 【补充知识点】泛型的上限
<? extends E>
class Animal {}
class Tiger extends Animal {}
class Panda extends Animal {}
class Monkey extends Animal {}
? 是一个通配符,可以表示任意类型
extends 表示继承关系
E 是泛型,由实例化对象约束明确
Collection<Animal>
addAll(Collection<? extends Animal> c);
要求参数集合中的存储类型为 Animal 类型或者其子类类型
相当于这里要求的最高类型为 Animal 类型,做了一个上限约束!!
【建议】
在真实的开发场景中,要求类型完全一致化,虽然有上限或者下限约束,但是基本上都是对应指定类型。
严格遵守数据类型一致化要求。
# 4. List 集合
# 4.1 List 集合特征
有序:添加顺序和存储顺序一致,存在【下标】关系
可重复: List 集合中存储的元素可以重复
# 4.2 List 集合常用方法
List 集合相关方法因为存在下标概念,方法中都补充了针对于下标的相关操作。
增 | |
add(E e); | |
添加在实例化对象过程中约束泛型对应具体数据类型元素对象。 | |
addAll(Collection<? extends E> c); | |
添加参数集合到当前调用方法集合中,要求参数集合对象存储数据类型必须是当前集合对象实例化过程中,约束泛型对应具体数据类型,或者其子类类型。 | |
add(int index, E e); | |
在指定下标位置,添加在实例化对象过程中约束泛型对应具体数据类型元素对象。 | |
addAll(int index, Collection<? extends E> c); | |
在指定下标位置,添加参数集合到当前调用方法集合中,要求参数集合对象存储数据类型必须是当前集合对象实例化过程中,约束泛型对应具体数据类型,或者其子类类型。 | |
删 | |
E remove(int index); | |
在当前List集合中,删除指定下标元素,返回值是被删除元素对象本身 | |
remove(Object obj); | |
在当前集合中,删除指定元素 | |
removeAll(Collection<?> c); | |
在当前集合中,删除参数集合和当前集合的交集 | |
retainAll(Collection<?> c); | |
在当前集合中,仅保留参数集合和当前集合的交集 | |
clear(); | |
清空当前集合中所有数据内容 | |
改 | |
E set(int index, E e); | |
在 List 集合中,使用符合实例化对象过程中约束泛型对应具体数据类型对象,替换指定下标元素,返回值是被替换元素对象本身 | |
查 | |
int size(); | |
当前集合中有效元素个数 | |
boolean isEmpty(); | |
判断当前集合是否为空 | |
boolean contains(Object obj); | |
判断参数对象是否在当前集合中存在 | |
boolean containsAll(Collection<?> c); | |
判断参数集合是否是当前集合的子集合 | |
Object[] toArray(); | |
返回当前集合中所有元素对象的Object类型数组 | |
E get(int index); | |
在当前集合中,获取指定下标元素 | |
int indexOf(Object obj); | |
获取指定元素在当前集合中第一次出现的下标位置 | |
int lastIndexOf(Object obj); | |
获取指定元素在当前集合中最后一次出现的下标位置 | |
List<E> subList(int fromIndex, int toIndex); | |
从 fromIndex 下标开始,到 toIndex 下标结束,获取子集合对象,要求要头不要尾 |
# 4.3 ArrayList 特征分析
# 4.3.1 ArrayList 结构特征
ArrayList 底层数据存储采用的是 Object 数组形式,基于数组数据存储的特征,数据存储效果【增删慢,查询快】
增删慢
- 增加删除操作,有可能会导致数组中的元素整体移动,移动过程,效率较低。【时间浪费】
- 删除操作有可能会导致数组中容量和有效元素比例失衡,例如:容量为 10 亿,有效元素个数为 10 个,会导致大量的空间浪费,也会触发【缩容 trimToSize ()】操作。【空间浪费,时间浪费】
- 添加操作有可能会触发【扩容 grow ()】,因为数组容量一旦确定无法修改,当底层 Object 数组不足元素添加使用,需要进行扩容操作,扩容操作流程中,涉及到新的空间占用和数据移动。【空间浪费,时间浪费】
查询快
# 4.3.2 ArrayList 使用场景分析
已知数据容量,可以在实例化 ArrayList 集合对象时,明确当前数据的容量存储范围,采用尾插法方式将数据逐一放入到集合中,对于数组元素增删慢弊端就可以避免,方便后期作为数组搜索,排序,查询等相关操作。
- 数据库查询目标内容
- 前端提交数据解析
# 4.4 ArrayList 原码实现
# 4.4.1 数据存储结构和构造方法基本设计
底层数据存储结构为数组结构 Object [],提供给用户两个构造方法,分别是
- 无参数构造方法,需要提供 ArrayList 基本数据存储能力,需要【默认容量 DEFAULT_CAPACITY】
- 根据用户指定 int 范围底层数组容量作为初始化容量大小的构造方法
package com.qfedu.b_util; | |
/** | |
* @author Anonymous 2023/7/31 16:00 | |
*/ | |
public class MyArrayList<E> { | |
/* | |
底层 Object 类型数组选择 | |
- 静态 static 修饰 | |
会导致所有 MyArrayList 类对象使用的数组是同一个,无法满足数据独立性和特征性需求。【不合适】 | |
- 非静态 | |
实例化 MyArrayList 对象,每一个对象中都是独立的一个 Object 类型数组,具备特征性和独立性 | |
private 修饰 | |
不能对外公开,会导致当前数据丢失,有一定的风险,针对于当前底层存储的数组所有操作都应该是通过 | |
MyArrayList 方法来进行操作,保证数据安全。 | |
数组需要初始化容量吗??? | |
*/ | |
/** | |
* 底层用于存储数据的 Object 类型数组 | |
*/ | |
private Object[] elements; | |
/** | |
* 数组允许的最大容量 | |
*/ | |
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; | |
/** | |
* 无参数构造方法,实例化对象过程中,底层 Object 类型数组默认容量 | |
*/ | |
private static final int DEFAULT_CAPACITY = 10; | |
/** | |
* 无参数构造方法,在实例化 MyArrayList 对象时,底层 elements 存储数组的容量 | |
* 容量为默认容量 10 | |
*/ | |
public MyArrayList() { | |
elements = new Object[DEFAULT_CAPACITY]; | |
} | |
/** | |
* 根据用户提供的初始化底层数组容量,在实例化 MyArrayList 对象过程中,对于底层 | |
* 存储数据的 Object 数组进行容量限制,采用 initialCapacity | |
* | |
* @param initialCapacity 用户指定的初始化容量 | |
*/ | |
public MyArrayList(int initialCapacity) { | |
/* | |
initialCapacity 用户提供的初始化容量数据必须在合法的容量以内 | |
大于等于 0 并且 在 int 范围以内 | |
Java 规定数组允许的容量范围是 0 ~ int 最大值 - 8 | |
int 最大值 - 8,需要预留一定的内存空间,给予数组相关配套信息空间存储, | |
例如:数组名.length 获取数组容量属性操作,该属性需要占用一定的数组内存。 | |
*/ | |
if (initialCapacity < 0 || initialCapacity > MAX_ARRAY_SIZE) { | |
/* | |
抛出非法参数异常 | |
*/ | |
throw new IllegalArgumentException("用户指定的底层数组容量不合法"); | |
} | |
elements = new Object[initialCapacity]; | |
} | |
} |
# 4.4.2 add 相关操作
相关方法:
public boolean add(E e); public boolean add(int index, E e); public boolean addAll(MyArrayList<? extends E> list); public boolean addAll(int index, MyArrayList<? extends E> list);有且只需要完成带有下标限制的添加方法,就可以给用户提供不同的操作套餐。
public boolean add(E e) { add(XXX, e);}
public boolean addAll(MyArrayList<? extends E> list) { addAll(XXX, list);}
【重点】尾插法变量选择
记录添加数据的下标位置
记录有效元素个数
- 静态成员变量
- 因为静态成员变量对于当前类有且只有一个,不具备特征性和独立性,无法满足多个对象直接数据不同的情况【不合适】
- 成员变量
- 每一个对象都有独立的成员变量,并且相互之间独立,满足数据生命周期所需。【选择】
- 局部变量
- 每一次调用方法,局部变量都是重新定义,不能记录之前的数据内容,无法满足数据统计和尾插法需求【不合适】
/** | |
* 在当前 MyArrayList 集合末尾添加目标元素,添加的元素必须符合实例化对象泛型要求 | |
* | |
* @param e 用户指定添加的元素内容 | |
* @return 添加成功返回 true | |
*/ | |
public boolean add(E e) { | |
return add(size, e); | |
} | |
/** | |
* 用户指定下标位置添加目标元素内容,要求下标在合法范围以内,添加的元素必须符合实例化对象 | |
* 泛型要求 | |
* | |
* @param index 用户指定添加数据的下标位置 | |
* @param e 用户指定添加的元素内容 | |
* @return 添加成功返回 true | |
*/ | |
public boolean add(int index, E e) { | |
// 1. 判断用户指定的下标位置是否合法,用户所有操作数据的范围上限 size 范围以内。 | |
if (index < 0 || index > size) { | |
throw new ArrayIndexOutOfBoundsException("参数下标越界异常:" + index); | |
} | |
// 2. 从原本最后一个有效下标位置开始,到用户指定的添加数据下标结束,移动数据 | |
for (int i = size; i > index; i--) { | |
elements[i] = elements[i - 1]; | |
} | |
// 3. 用户指定下标位置赋值为 目标数据内容 | |
elements[index] = e; | |
// 4. 有效元素个数 + 1 | |
size += 1; | |
return true; | |
} |
# 4.4.3 扩容方法思路和实现
扩容思路
- 添加数据操作中,如果发现当前数组的容量无法满足有效元素个数需求,需要进行扩容操作。
教室容量 60 人 班级学生人数 55 人
新来小伙伴 10 人,目前教室容量无法满足需求。
我去找校长!!!
目前班级作为无法满足学生需求,我需要换教室。最小容量需求【55 + 10 ==> 65】
校长根据情况,你原来教室容量多少? 60 个座位,校长说给你一个 90 人的教室,可以不?
非常可以!!!需要判断新教室容量是否满足最小容量需求
学生搬东西到新教室
教室门口的信息,修改为原班级信息
核心思路:
- 知晓最小容量需求,原有效元素个数 + 添加的数据元素个数。
- 获取原数组容量
- 计算得到新数组容量,大约是原数组容量的 1.5 倍
- 计算得到的新数组容量是否满足最小容量需求,如果不能满足,新数组容量按照最小容量需求完成。
- 判断新数组容量是否超出 MAX_ARRAY_SIZE,无能为力,【Error OOM】
- 根据新数组容量,创建新数组
- 原数组数据内容移动到新数组中
- MyArrayList 底层 elements 保存新数组地址
/** | |
* 添加配套方法,用于添加操作容量不足的情况下,进行扩容操作、 | |
* | |
* @param minCapacity 最小容量需求 | |
* 1. 知晓最小容量需求,原有效元素个数 + 添加的数据元素个数。 | |
*/ | |
private void grow(int minCapacity) { | |
/* 2. 获取原数组容量 */ | |
int oldCapacity = elements.length; | |
/* 3. 计算得到新数组容量,大约是原数组容量的 1.5 倍 */ | |
int newCapacity = oldCapacity / 2 + oldCapacity; | |
/* 4. 计算得到的新数组容量是否满足最小容量需求,如果不能满足,新数组容量按照最小容量需求完成。 */ | |
if (newCapacity < minCapacity) { | |
newCapacity = minCapacity; | |
} | |
/* 5. 判断新数组容量是否超出 MAX_ARRAY_SIZE,无能为力,【Error OOM】*/ | |
if (newCapacity > MAX_ARRAY_SIZE) { | |
throw new OutOfMemoryError("超出当前数组允许的最大容量范围"); | |
} | |
/* | |
6. 根据新数组容量,创建新数组 | |
7. 原数组数据内容移动到新数组中 | |
8. MyArrayList 底层 elements 保存新数组地址 | |
public static <T> T [] copyOf (T [] t, int size); | |
根据用户指定的新数组容量创建和当前参数数组类型一致的新数组,并且 | |
将原数组数据移动到新数组中,返回值是新数组地址。 | |
*/ | |
elements = Arrays.copyOf(elements, newCapacity); | |
} |
# day11LinkedList, Set
# 1. LinkedList
# 1.1 有头双向链表图例
# 1.2 LinkedList 涉及到的方法
LinkedList 是 List 接口的实现类,List 接口中所有方法都可以使用,针对于 LinkedList 头尾操作效率较高的情况,针对性的提供了以下方法
addLast(E e); | |
add(E e); | |
在 LinkedList 集合末尾添加元素 | |
addFirst(E e); | |
在 LinkedList 集合头部添加元素 | |
E getLast(); | |
获取 LinkedList 最后一个结点元素存储数据内容 | |
E getFirst(); | |
获取 LinkedList 第一个结点元素存储数据内容 | |
E removeLast(); | |
删除 LinkedList 集合最后一个结点数据,同时返回值是被删除的数据内容 | |
E removeFirst(); | |
删除 LinkedList 集合第一个结点数据,同时返回值是被删除的数据内容 |
# day12Set ,Map 和 String 字符串
# 1. Set
核心知识点:
- equals 和 hashCode 方法
- Comparator 和 Comparable 接口使用
# 1.1 Set 概述
Set 所使用的方法都是 Collection 集合方法。
interface Set<E>
--| class HashSet<E> implements Set<E> 哈希表形式存储数据内容,需要对象的【hashCode】方法支持
--| class TreeSet<E> implements Set<E> 底层存储数据结构为【平衡二叉树】,要求添加的数据内容有自然顺序或者比较方式
特征:无序,不可重复
package com.qfedu.a_set; | |
import java.util.HashSet; | |
import java.util.Set; | |
/** | |
* @author Anonymous 2023/8/2 9:56 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
Set<String> set = new HashSet<>(); | |
/* | |
无序 添加顺序和存储不一致,有可能会受到添加数据顺序情况影响 | |
不可重复 Set 集合中不允许出现相同元素 | |
*/ | |
set.add("母鸡汤"); | |
set.add("驴肉汤"); | |
set.add("疙瘩汤"); | |
set.add("胡辣汤"); | |
set.add("老鸭汤"); | |
set.add("羊肉汤"); | |
set.add("牛肉汤"); | |
set.add("丸子汤"); | |
System.out.println(set); | |
} | |
} |
# 1.2 HashSet 存储数据要求和流程
# 1.2.1 【补充知识点】 equals 和 hashCode 方法
Object 类内方法
boolean equals(Object obj);
判断调用方法对象和当前参数对象是否为同一个对象
【注意】
== 判断是否等值关系,有且只可以使用在基本数据类型
引用数据类型需要进行等值判断,必须采用 equals 方法
int hashCode();
获取当前对象的 哈希值,哈希值可以认为是当前对象在内存中的【身份证号码】唯一标识
/* | |
Alt + Insert 快捷键重写 equals 和 hashCode | |
*/ | |
@Override | |
public boolean equals(Object o) { | |
/* | |
this 表示调用当前方法的对象,o 是参数对象 | |
== 判断,判断调用方法对象和参数对象是否为同地址对象,100% 同一个对象 | |
*/ | |
if (this == o) { | |
return true; | |
} | |
/* | |
1. 参数变量为 null 【错误条件】 | |
2. getClass () != o.getClass () 判断调用方法对象数据类型和参数对象数据类型是否不一致。 | |
如果参数为 null 或者参数对象类型和当前调用方法类型不一致,直接返回 false | |
*/ | |
if (o == null || getClass() != o.getClass()) { | |
return false; | |
} | |
/* | |
[强制类型转换] 通过以上两个 if 条件判断,可以明确参数真实类型为 Student,同时 | |
参数对象和当前调用方法对象不是同一个对象,可以进行强转之后取值判断内容情况 | |
*/ | |
Student student = (Student) o; | |
// 根据调用方法对象数据内容和参数对象数据内容进行比对,如果完全一致,返回 true | |
return id == student.id && age == student.age && name.equals(student.name); | |
} | |
/* | |
hashCode 要求 | |
如果两个对象 equals 方法判断结果为 true ,hashCode 结果必须一致。 | |
IDEA 重写 hashCode 方法逻辑是【参与 equals 判断比较的成员变量全部参与 hashCode 数据实现】 | |
*/ | |
@Override | |
public int hashCode() { | |
/* | |
Objects Object 工具类 | |
public static int hash (Object... args); | |
根据提供的数据内容,生成对应的哈希值,不要关注哈希值如何生成。 | |
【重点】Object... args 可变长参数 | |
当前方法参数类型要求为 Object 类型,可以支持 Java 中的任意数据类型 | |
同时当前参数个数不做限制,可以一个,可以多个,可以没有 | |
不定长参数在方法内部是一个【对应数据类型数组】 | |
hashCode 重写不会影响对象的真实地址,只是修改了 哈希值规则,原本采用内存地址方式 | |
现在采用指定数据内容生成方式、 | |
*/ | |
return Objects.hash(id, name, age); | |
} |
# 1.2.2 hashSet 存储元素和 equals hashCode 方法关系
package com.qfedu.a_set; | |
import java.util.HashSet; | |
/** | |
* @author Anonymous 2023/8/2 11:05 | |
*/ | |
public class Demo3 { | |
public static void main(String[] args) { | |
HashSet<Student> set = new HashSet<>(); | |
/* | |
1. Student 类型已经完成了 equals 和 hashCode 方法重写 | |
2. Set 中数据存储不可以重复。 | |
【重点】 | |
1. HashSet 存储元素需要调用元素的 hashCode 方法,获取对应的 哈希值 | |
为什么要获取哈希值? | |
hashSet 需要根据当前对象的 哈希值,给予对象分配房间【单元格】 | |
2. 如果出现了添加元素 hashCode 数据一致,对应的存储单元格位置一致,需要 | |
目前已存储到单元格中的元素,调用 equals 方法和后续元素比较,如果结果为 true | |
无法添加后续元素 | |
*/ | |
boolean ret = set.add(new Student(1, "张三", 16)); | |
System.out.println(ret); | |
ret = set.add(new Student(1, "张三", 16)); | |
System.out.println(ret); | |
set.add(new Student(3, "张三", 16)); | |
set.add(new Student(4, "张三", 16)); | |
set.add(new Student(5, "张三", 16)); | |
set.add(new Student(6, "张三", 16)); | |
set.add(new Student(7, "张三", 16)); | |
set.add(new Student(8, "张三", 16)); | |
System.out.println(set); | |
} | |
} |
# 1.3 TreeSet
# 1.3.1 树形结构
# 1.3.2 TreeSet 存储自定义类型问题和解决方案
【解决方式一】
自定义类型遵从【Comparable 接口】,增强自定义类型为可比较类型。
public interface Comparable<T> { | |
/** | |
* 自定义类遵从【Comparable 接口】,需要完成 compareTo 方法,当前接口有泛型约束,通常情况下 | |
* 当前泛型约束方式为【气管炎方式】,遵从接口时直接明确泛型对应的具体数据类型为当前增强类型 | |
* | |
* Comparable<T> 接口实现类,在存储到需要进行排序的结构中,或者进行排序操作,都会自动执行 compareTo | |
* 方法,相当于当前类型已具备【自然顺序】 | |
* | |
* @param o 泛型约束的具体数据对象 | |
* @return 返回值为 0 表示两个元素一致,其他数据存在大小关系。 | |
*/ | |
int compareTo(T o); | |
} |
【解决方式二】【常用方式】
TreeSet 构造方法提供 Comparator 接口实现类对象作为方法参数,Comparator 接口实现类提供针对于当前 TreeSet 存储类型比较形式。【Comparator 比较器接口】
@FunctionalInterface | |
public interface Comparator<T> { | |
/** | |
* 比较器接口要求完成的方法,泛型对应的具体数据类型需要在实现类遵从接口时明确,采用【气管炎模式】 | |
* 方法返回值类型为 int 类型, | |
* | |
* @param o1 泛型约束数据类型对应对象 | |
* @param o2 泛型约束数据类型对应对象 | |
* @return 返回值为 0 表示两个元素一致,其他数据存在大小关系。 | |
*/ | |
int compare(T o1, T o2); | |
} |
以上解决方式推荐使用 方式二
方式一增强目标存储类型,会导致代码冗余,同时代码的灵活性不足,如果需要修改功能,需要对源码进行修改
# 2. Comparator 接口完成统一排序算法
package com.qfedu.b_compare; | |
import java.util.Arrays; | |
import java.util.Comparator; | |
/** | |
* 期望可以完成针对于任意数据类型,用户指定条件的排序算法,不限于已知类型。 | |
* 使用的技术: | |
* 1. 泛型 | |
* a. 增强方法 | |
* b. 满足数据类型多样性,同时严格遵守数据类型一致化原则。 | |
* 2. Comparator 接口 | |
* 指定规则,拓宽功能 | |
* | |
* @author Anonymous 2023/8/2 16:02 | |
*/ | |
public class Demo2 { | |
public static void main(String[] args) { | |
SingleTeacher[] teas = { | |
new SingleTeacher(1, "朱某", 27), | |
new SingleTeacher(2, "朱某", 18), | |
new SingleTeacher(3, "朱某", 17), | |
new SingleTeacher(4, "朱某", 16), | |
new SingleTeacher(5, "朱某", 22), | |
new SingleTeacher(6, "朱某", 20), | |
new SingleTeacher(7, "朱某", 21), | |
new SingleTeacher(8, "朱某", 23), | |
}; | |
Cat[] cats = { | |
new Cat(1, "豆包", 27), | |
new Cat(2, "豆包", 18), | |
new Cat(3, "豆包", 17), | |
new Cat(4, "豆包", 16), | |
new Cat(5, "豆包", 22), | |
new Cat(6, "豆包", 20), | |
new Cat(7, "豆包", 21), | |
new Cat(8, "豆包", 23), | |
}; | |
System.out.println("老师类型,ID 升序降序"); | |
selectSort(teas, new MySingleTeacherIDDesc()); | |
System.out.println(); | |
System.out.println("猫咪类型,年龄升序排序"); | |
selectSort(cats, Comparator.comparingInt(Cat::getAge)); | |
} | |
/** | |
* 使用泛型约束数组类型,同时要求 Comparator 接口采用泛型约束,方法中泛型对应的 | |
* 具体数据类型由实际参数数组来约束。数组的实际类型,同时约束方法中 Comparator 接口 | |
* 泛型类型和方法中使用的变量泛型类型 | |
* | |
* @param arr 需要进行排序的任意类型数组 | |
* @param com 针对于用户提供的实际参数数组类型 Comparator 比较器实现 | |
* @param <T> 自定义泛型 | |
*/ | |
public static <T> void selectSort(T[] arr, Comparator<T> com) { | |
// 数据拷贝,保护源数据 | |
T[] sortTemp = Arrays.copyOf(arr, arr.length); | |
// 选择排序算法 | |
for (int i = 0; i < sortTemp.length - 1; i++) { | |
// 找极值 | |
int index = i; | |
for (int j = i + 1; j < sortTemp.length; j++) { | |
/* | |
需要使用 Comparator 接口来替换排序规则,替换代码中排序算法找极值的规则 | |
*/ | |
if (com.compare(sortTemp[index], sortTemp[j]) > 0) { | |
index = j; | |
} | |
} | |
// 交换位置 | |
if (index != i) { | |
T temp = sortTemp[index]; | |
sortTemp[index] = sortTemp[i]; | |
sortTemp[i] = temp; | |
} | |
} | |
Arrays.stream(sortTemp).forEach(System.out::println); | |
} | |
} |
# day13String,Map 和反射
# 1. String
# 1.1 字符串比较和内存分析
package com.qfedu.a_string; | |
/** | |
* @author Anonymous 2023/8/3 9:07 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
String str1 = "常用方法很重要"; | |
String str2 = "常用方法很重要"; | |
String str3 = new String("常用方法很重要"); | |
String str4 = new String(str1); | |
/* | |
Java 中规定,字符串常量内容如果一致,在内存中有且只有一个。 | |
== 判断有且只是判断变量存储的数据内容情况,无法判断引用数据类型指向空间内容情况 | |
*/ | |
System.out.println("str1 == str2 : " + (str1 == str2)); // true | |
System.out.println("str2 == str3 : " + (str2 == str3)); // false | |
System.out.println("str3 == str4 : " + (str3 == str4)); // false | |
System.out.println("str1 == str4 : " + (str1 == str4)); // false | |
/* | |
equals 方法是 Object 类内方法,在 String 中进行了重写,比较字符串内容。 | |
*/ | |
System.out.println("str1.equals(str2) : " + str1.equals(str2)); | |
System.out.println("str2.equals(str3) : " + str2.equals(str3)); | |
System.out.println("str3.equals(str4) : " + str3.equals(str4)); | |
System.out.println("str1.equals(str4) : " + str1.equals(str4)); | |
} | |
} |
# 1.2 获取方法
int length(); | |
获取当前字符串有效字符个数 | |
char charAt(int index); | |
获取当前字符串中指定下标的对应的字符,StringIndexOutOfBoundsException | |
int indexOf(int ch); | |
在当前字符串中找出指定字符第一次出现的下标位置。 | |
【说明】 | |
1. 字符对于计算机而言都是【编码值】,可以认为是 int 类型 | |
2. 数据类型所需为 int 类型,但是实际上在 int 类型内存中,有且只有 低 16 位有效 | |
int indexOf(String str); | |
在当前字符串中找出指定字符串第一次出现的下标位置。 | |
int lastIndexOf(int ch); | |
在当前字符串中找出指定字符最后一次出现的下标位置。 | |
int lastIndexOf(String str); | |
在当前字符串中找出指定字符串最后一次出现的下标位置。 |
# 1.3 判断方法
boolean endsWith(String str); | |
判断当前字符串是否以指定参数字符串结尾 | |
boolean startsWith(String str); | |
判断当前字符串是否以指定参数字符串开头 | |
boolean isEmpty(); | |
JDK 1.6 版本以上有效,判断当前字符串是否为空,空字符串 "" | |
boolean equals(Object obj); Override method in Type Object | |
重写 Object 方法,判断两个字符串是否一致 | |
boolean equalsIgnoreCase(String str); | |
忽略大小写比较字符串是否一致。 |
# 1.4 转换方法
String(char[] arr); | |
【构造方法】将字符数组转换为字符串 | |
String(char[] arr, int off, int len); | |
【构造方法】将字符数组转换为字符串,要求从指定下标 off 位置开始,计数 len 长度字符内容,转换为目标字符串 | |
char[] arr = {'a', 'b', 'c', 'd', 'e'}; | |
off = 1 | |
len = 3 | |
String str = "bcd"; | |
static String valueOf(char[] arr); | |
【静态成员方法】将字符数组转换为字符串 | |
static String valueOf(char[] arr, int off, int len); | |
【静态成员方法】将字符数组转换为字符串,要求从指定下标 off 位置开始,计数 len 长度字符内容,转换为目标字符串 | |
char[] arr = {'a', 'b', 'c', 'd', 'e'}; | |
off = 1 | |
len = 3 | |
String str = "bcd"; | |
char[] toCharArray(); | |
将字符串内容,转换为字符数组 |
# 1.5 其他方法
- 以下方法操作返回值类型都是字符串类型,或者字符串数组类型
- 以下方法操作对于原字符串内容无任何影响,所有的执行效果,都是通过返回值得到新的字符串内容,字符串是一个【常量】
String replace(char oldChar, char newChar); | |
调用方法字符串使用新的字符,替换指定的老字符,返回值类型为替换之后的字符串结果 | |
String[] split(String regex); | |
【重点方法】调用方法字符串根据,参数字符串内容进行字符串切割操作。结果内容为字符串数组 | |
例如: | |
String className = "className:com.qfedu.entity.Student"; | |
String[] arr = className.split(":"); | |
arr ==> {"className", "com.qfedu.entity.Student"}; | |
String substring(int beginIndex); | |
调用方法字符串,从指定下标位置开始,到字符串末尾结束,获取子字符串内容 | |
String substring(int beginIndex, int endIndex); | |
调用方法字符串,从指定下标位置开始,到指定下标结束结束,获取子字符串内容,范围要求要头不要尾 | |
String toUpperCase(); | |
调用方法字符串所有英文字符转换为大写 | |
String toLowerCase(); | |
调用方法字符串所有英文字符转换为小写 | |
String trim(); | |
去除字符串两端无效空格 |
# 1.6 字符串常量面试题
package com.qfedu.a_string; | |
/** | |
* 【字符串是一个常量】 | |
* 字符串 + 拼接累加操作会在内存中形成新的字符串 | |
* 10 + 9 + 1 ==> 20 | |
* | |
* JDK 1.8 及其以上版本 字符串常量累加操作会转换为 StringBuilder 操作,效率较高,内存占用较少 | |
* | |
* @author Anonymous 2023/8/3 10:23 | |
*/ | |
public class Demo6 { | |
public static void main(String[] args) { | |
String str = "胡辣汤"; | |
str += "油饼"; | |
str += "水煎包"; | |
str += "葱油饼"; | |
str += "油条"; | |
str += "油馍头"; | |
str += "牛肉盒子"; | |
str += "韭菜盒子"; | |
str += "茄子包子"; | |
str += "茶叶蛋"; | |
System.out.println("这里有几个字符串???"); | |
} | |
} |
# 2. Map 【非常多】
键值对概念:实际开发中大量存在,前端用户提交的数据内容,后端数据库存储内容,JSON 格式文本,XML 格式文本都是采用键值对数据形式存储,阅读良好,操作方便。
# 2.1 Map 相关类型
interface Map<K, V> Map 双边队列总接口
--| class HashMap<K, V> implements Map<K, V>
底层数据存储方式采用的是哈希表方式,键值对数据存储的位置由 Key 哈希值决定
--| class TreeMap<K, V> implements Map<K, V>
底层数据存储方式采用的是二叉树方式,要求键值对中的 Key 必须有自然顺序或者可比较方式。
# 2.2 Map 相关方法
增 | |
put(K k, V v); | |
添加键值对数据到当前 Map 双边队列中,要求 K 和 V 类型必须符合实例化 Map 双边队列约束的泛型类型 | |
putAll(Map<? extends K, ? extends V> map); | |
添加参数 Map 到当前 Map 双边队列中,要求参数 Map 存储 K 和 V 数据类型必须和当前调用方法 Map 一致 | |
删 | |
V remove(Object k); | |
根据 K 删除 Map 中的键值对数据内容,返回值是 K 对应的 V | |
void remove(Object k, Object v); | |
根据 K 和 V 删除 Map 中的键值对数据 | |
改 | |
put(K k, V v); | |
如果对应 K 存在,使用参数 V 替换原本的数据 | |
查 | |
int size(); | |
获取当前 Map 双边队列中键值对个数 | |
boolean isEmpty(); | |
判断当前 Map 键值对个数是否为 0 | |
boolean containsKey(Object key); | |
判断当前 Map 中是否包含指定的 K | |
boolean containsValue(Object value); | |
判断当前 Map 中是否包含指定的 V | |
V get(Object k); | |
根据 K 找到对应的 V | |
Collection<V> values(); | |
获取当前 Map 中所有 Value 对应的 Collection 集合 | |
Set<K> keySet(); | |
获取当前 Map 中所有 Key 对应的 Set 集合 |
# day143. 反射
# 3.1 反射内存分析图
# 3.2 Class 对象获取方法【万恶之源】
反射操作首先需要获取 Class 对象,Class 对象相当于当前类型在内存方法区中内存空间,可以获取当前类型的所有相关内容。
Class 类静态成员方法【使用最多】 | |
public static Class<?> forName(String packageAndClassName); | |
根据完整的包名.类名字符串获取对应类型的 Class 对象,并且当前方法具备加载对应类型的能力。 | |
通过实例化对象获取对应的 Class 对象,Object 类提供方法【类型比较,类型判断】 | |
public Class<? extends T> getClass(); | |
例如: | |
Person p = new Person(); | |
Class<? extends Person> cls = p.getClass(); | |
Java 中类和接口的属性【常用于数据类型约束】 | |
类名/接口名.class; |
package com.qfedu.c_reflect; | |
/** | |
* 获取 Class 对象,万恶之源 | |
* | |
* @author Anonymous 2023/8/3 14:50 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) | |
throws ClassNotFoundException { | |
/* | |
Class 类静态成员方法【使用最多】 | |
public static Class<?> forName (String packageAndClassName); | |
根据完整的包名。类名字符串获取对应类型的 Class 对象,并且当前方法具备加载对应类型的能力。 | |
ClassNotFoundException 指定类型未找到异常。 | |
*/ | |
Class<?> cls1 = Class.forName("com.qfedu.c_reflect.Person"); | |
/* | |
通过实例化对象获取对应的 Class 对象,Object 类提供方法【类型比较,类型判断】 | |
public Class<? extends T> getClass (); | |
例如: | |
Person p = new Person (); | |
Class<? extends Person> cls = p.getClass (); | |
*/ | |
Person person = new Person(); | |
Class<? extends Person> cls2 = person.getClass(); | |
/* | |
Java 中类和接口的属性【常用于数据类型约束】 | |
Class<T> 类名 / 接口名.class; | |
*/ | |
Class<Person> cls3 = Person.class; | |
System.out.println(cls1); | |
System.out.println(cls2); | |
System.out.println(cls3); | |
/* | |
以上三种方法获取 Class 对象,只要类型一致,得到的都是同一个 Class 对象 | |
因为 Java 代码中,任何一个类型在程序加载阶段有且只加载一次。 | |
cls.getName (); | |
*/ | |
System.out.println(cls1 == cls2); | |
System.out.println(cls2 == cls3); | |
System.out.println(cls3 == cls1); | |
} | |
} |
# 3.3 Constructor 构造方法对象获取
# 3.3.1 构造方法对象分析
- 构造方法名称为类名,在 Class 对象中是包含的
- 构造方法之间区别是形式参数列表,包括参数个数,参数类型,参数顺序。
- 构造方法在类内有可能是多个,在反射操作中,Class 对象内存在构造方法数组
# 3.3.2 Class 对象获取 Constructor 构造方法对象相关方式
public Constructor getConstructor(Class... parameterTypes); | |
有且只可以获取非私有化构造方法 | |
根据指定的构造方法参数数据类型,获取对应的构造方法对象。Class... 是 Class 类型对应的不定长参数,可以要求 提供方法参数对应的 Class 类型,参数个数不限制 | |
例如: | |
public Person() {...} | |
public Person(int) {...} | |
public Person(int, String) {...} | |
Class<Person> cls = Class.forName("com.qfedu.c_reflect.Person"); | |
Constructor c1 = cls.getConstructor(); | |
// ==> public Person() {...} | |
Constructor c2 = cls.getConstructor(int.class); | |
// ==> public Person(int) {...} | |
Constructor c3 = cls.getConstructor(int.class, String.class); | |
// ==> public Person(int, String) {...} | |
public Constructor getDeclaredConstructor(Class... parameterTypes); | |
【暴力反射】可以获取类内任意权限修饰的构造方法,包括私有化构造方法 | |
根据指定的构造方法参数数据类型,获取对应的构造方法对象。Class... 是 Class 类型对应的不定长参数,可以要求 提供方法参数对应的 Class 类型,参数个数不限制 | |
例如: | |
private Person(String) {...} | |
Class<Person> cls = Class.forName("com.qfedu.c_reflect.Person"); | |
Constructor c1 = cls.getDeclaredConstructor(String.class); | |
// private Person(String) {...} |
# 3.3.3 通过 Constructor 构造方法对象实例化目标类型对象
public Object newInstance(Object... parameterValues); | |
通过 Constructor 构造方法对象调用,需要的参数是构造方法所需的实际参数列表,方法参数为 Object..., Object 支持 Java 任意类型,... 可变长参数,不限制参数个数 | |
例如: | |
Constructor c1 = cls.getConstructor(); | |
// ==> public Person() {...} | |
Object obj1 = c1.newInstance(); // 真实类型为 Person 类型,返回值类型为 Object | |
Constructor c2 = cls.getConstructor(int.class); | |
// ==> public Person(int) {...} | |
Object obj2 = c2.newInstance(10); // 真实类型为 Person 类型,返回值类型为 Object | |
Constructor c3 = cls.getConstructor(int.class, String.class); | |
// ==> public Person(int, String) {...} | |
Object obj3 = c3.newInstance(10, "朱某"); // 真实类型为 Person 类型,返回值类型为 Object |
# 3.4 Method 成员方法对象获取
# 3.4.1 成员方法对象分析
- 方法的唯一性 方法名 + 形式参数列表
- 方法是在类内,需要通过 Class 对象来获取目标 Method 对象。
# 3.4.2 Class 对象获取 Method 构造方法对象相关方式
public Method getMethod(String methodName, Class... parameterTypes); | |
通过 Class 对象调用,根据目标方法名称和对应的形式参数列表数据类型,获取对应非私有化成员方法对象 | |
Class... parameterTypes 不定长参数,约束当前方法所需参数类型。 | |
【补充】getMethod 可以获取继承得到父类或者接口中方法。 | |
例如: | |
public void game() {...} | |
public void game(String) {...} | |
Class<Person> cls = Class.forName("com.qfedu.c_reflect.Person"); | |
Method m1 = cls.getMethod("game"); | |
// ==> public void game() {...} | |
Method m2 = cls.getMethod("game", String.class); | |
// ==> public void game(String) {...} |
# 3.4.3 通过 Method 对象执行对应目标方法
原本执行方法的形式
调用者.方法名(实际参数); 调用者执行目标方法,需要提供必要参数
目前具备: Method 方法对象,缺少内容
- 调用者
- 实际参数
method.xxx(调用者,实际参数); 方法执行需要提供调用者和必要参数
Method 执行目标方法实现 | |
public Object invoke(Object obj, Object... parameterValues); | |
通过 Method 成员方法对象执行对应目标方法,所需参数是执行目标方法的实例化对象,和对应方法的实际参数 | |
Object obj 是执行当前方法的类对象 | |
Object... parameterValues 当前方法所需的实际参数,采用不定长形式,参数个数不限制,数据类型不限制, | |
需要根据实际方法情况提供对应的实际参数 | |
【注意】不要在 Object 中迷失自己,时刻关注数据的【真实类型】 |
# 3.5 Field 成员变量对象获取
# 3.5.1 成员变量对象分析
- 唯一标记,变量名
- 按照 JavaBean 规范,成员变量全部私有化
# 3.5.2 Class 对象获取 Field 成员变量方法
public Field getDeclaredField(String fieldName); | |
【暴力反射】 | |
通过 Class 对象调用,根据成员变量名称获取对应类内任意修饰的成员变量对象,可以获取私有化内容。 | |
例如: | |
private int id; | |
private String name; | |
public int test; | |
Class<Person> cls = Class.forName("com.qfedu.c_reflect.Person"); | |
Field id = cls.getDeclaredField("id"); | |
// ==> private int id; | |
Field name = cls.getDeclaredField("name"); | |
// ==> private String name; | |
Field test = cls.getDeclaredField("test"); | |
// ==> public int test |
# 3.5.3 Field 成员变量对象完成取值和赋值操作
成员变量赋值和取值操作
调用者.setXXX(对应成员变量数据类型实际参数); 对应成员变量数据类型返回 调用者.getXXX()
public void set(Object obj, Object value);
通过 Field 成员变量对象调用,需要的参数是当前成员变量所在实例化对象,和赋值当前成员变量对应的实际参数。
Object obj 当前成员变量所在实例化对象
Object value 赋值当前成员变量对应的实际参数,要求数据类型符合成员变量需求。
public Object get(Object obj);
通过 Field 成员变量对象调用,获取当前成员变量存储的数据内容,参数是从哪一个实例化对象中获取对应的数据内容
Object obj 当前成员变量所在实例化对象
返回值类型是当前成员变量存储的数据信息。
# 【补充知识点】私有化反射对象操作权限
AccessibleObject 类内方法。AccessibleObject 是 Constructor Method Field 的父类 | |
public void setAccessible(boolean flag | |
如果操作的反射对象(Constructor Method Field)为 private 修饰对象,可以利用 | |
setAccessible(true) 给予操作权限 |
# day15File 类,IO
# 1. File 类
# 1.1 概述
Java 提供的针对于文件、文件夹进行增删改查操作的方式,不可以操作文件内容,文件内容操作需要 IO 支持。必须明确两个内容
1. 路径问题【重点】
2. File 类增删改查方法。
# 1.2 路径问题
相对路径
例如:
我在隔壁办公室
以当前【工作目录】为参照物去往其他目录的相对路径
. 对应当前工作目录
.. 当前工作目录的上级目录
使用较多,服务器端静态相关资源。
绝对路径
例如:
河南省郑州市高新区莲花街55号威科姆园区D座千锋教育
Windows 有盘符关系
C D E
C:/aaaa/1.txt;
Linux/UNIX 系统(centOS RedHat),没有盘符概念,只有
根目录
# 1.3 File 类相关方法
# 1.3.1 构造方法
以下方法和对应文件 / 文件夹是否存在无关、
File(String filePath); | |
根据用户提供的对应文件/文件夹的完整路径,创建 File 对象 | |
File(String parent, String childName); | |
根据用户提供的父目录路径,和对应子文件/子文件夹名称,创建 File 对象 | |
File(File parent, String childName); | |
根据用户提供的父目录 File 类对象,和对应子文件/子文件夹名称,创建 File 对象 |
# 1.3.2 创建相关方法
boolean createNewFile(); | |
通过 File 对象调用,根据当前 File 对象存储的【文件路径】,创建对应普通文件,创建成功返回 true,否则 false | |
失败情况: | |
1. 文件路径不合法 | |
2. 对应文件已存在 | |
3. 对应文件夹没有【写入权限】 Linux 文件权限 r-xr-xr-x | |
boolean mkdir(); | |
通过 File 对象调用,根据当前 File 对象存储的【文件夹路径】,创建对应文件夹,创建成功返回 true,否则 false | |
失败情况: | |
1. 文件路径不合法 | |
2. 对应文件夹已存在 | |
3. 对应文件夹没有【写入权限】 Linux 文件权限 r-xr-xr-x | |
boolean mkdirs(); | |
BT 方法,通过 File 对象调用,根据当前 File 对象存储的【文件夹路径】,创建对应文件夹,创建成功返回 true,否则 false | |
【重点】可以创建中间文件夹/中间路径文件夹 | |
boolean renameTo(File destFile); | |
【重命名,移动】 | |
通过 File 对象调用,将当前 File 对象对应文件/文件夹,转移到 destFile 对应的路径/名称 |
# 1.3.3 删除操作
boolean delete(); | |
通过 File 类对象调用,对应文件或者文件夹,立即删除 | |
注意: | |
1. 删除文件无法撤回,不是放入回收站 | |
2. 不能删除【非空文件夹】 | |
void deleteOnExit(); | |
通过 File 类对象调用,对应文件或者文件夹,在【程序退出】时删除,可以用于处理缓冲文件,日志文件。 | |
注意: | |
1. 删除文件无法撤回,不是放入回收站 | |
2. 不能删除【非空文件夹】 |
# 1.3.4 获取文件相关信息
// 以下方法实际为字符串操作方法,并且和文件是否存在无关 | |
String getName(); | |
获取文件/文件夹名 | |
String getPath(); | |
获取当前文件/文件夹的路径 | |
String getParent(); | |
获取当前文件/文件夹的上级目录名称 | |
// 以下方法有用 | |
String getAbsolutePath(); | |
获取当前路径对应的绝对路径字符串 | |
File getAbsoluteFile(); | |
获取当前路径对应的绝对路径 File 对象 | |
long lastModified(); | |
获取当前文件/文件夹最后修改时间,返回值是时间戳 | |
long length(); | |
获取当前文件占用的空间字节数,如果是文件夹结果为 0L or 4096L |
# 1.3.5 判断方法【重点】
boolean exists(); | |
判断 File 对象对应的文件/文件夹是否存在 | |
boolean isFile(); | |
判断 File 对象是否对应一个普通文件 | |
boolean isDirectory(); | |
判断 File 对象是否对应一个文件夹 | |
boolean isAbsolute(); | |
判断 File 对象是否采用绝对路径方式 | |
boolean isHidden(); | |
判断 File 对象对应的文件/文件夹是否为隐藏属性 |
# 1.3.6 文件列表方法
public static File[] listRoots(); | |
有且只在 Windows 操作系统中有效。获取当前 Windows 操作系统中所有 | |
盘符对应的 File 对象数组 | |
public String[] list(); | |
获取当前 File 对象对应文件夹中所有子文件和子文件夹名称 字符串数组 | |
public File[] listFiles(); | |
获取当前 File 对象对应文件夹中所有子文件和子文件夹 File 对象数组 |
# 1.3.7 文件名过滤器 FilenameFilter
- 函数式接口 @FunctionalInterface 注解严格格式检查。
- 常用方法参数,需要通过实例化对象完成接口对应功能,【插件式编程】
@FunctionalInterface
public interface FilenameFilter {/**
* 提供给列表方法使用,过滤限制可以存储在文件列表的文件 / 文件夹内容 * * @param dir 获取文件列表信息对应的文件 File 对象 * @param child 当前文件中所有文件 / 文件夹的名称 * @return 自行设置目标限制,满足条件返回 true,不满足返回 false */ boolean accept(File dir, String child);}
# 2. IO 流
# 2.1 IO 流分类
流向分类:
输入流 input
输出流 output
已当前程序所在内存为参照物,数据流出 输出流 output,数据流入 输入流 input
操作形式区分:
字节流【最多】按照字节为操作单位,处理文件内容,可以适用所有的文件。
字符流 有且只可以处理使用【记事本】打开的可视化文本无乱码文件
对象流 一般用于序列化操作,后续可以在 MyBatis 二级缓存中使用
是否使用缓冲
缓冲流【推荐使用】
需要学习的类:
字节输入流
InputStream
文件操作字节输入流 FileInputStream
字节输出流
OutputStream
文件操作字节输出流 FileOutputStream
字符输入流
Reader
文件操作字符输入流 FileReader
字符输出流
Writer
文件操作字符输出流 FileWriter
字节缓冲输入流
BufferedInputStream
字节缓冲输出流
BufferedOutputStream
# 2.2 文件操作字节输入流
FileInputStream 构造方法 | |
FileInputStream(String filePath); | |
根据指定的文件路径,创建对应当前文件的文件操作字节输入流对象 | |
FileInputStream(File file); | |
根据指定的文件 File 对象,创建对应当前文件的文件操作字节输入流对象 | |
读取操作相关方法 | |
int read(); | |
从文件中读取一个字节数据返回,返回值类型为 int 类型,实际有效数据为 byte | |
int read(byte[] buffer); 【使用最多】 | |
从文件中读取数据到 byte 类型的缓冲数组中,返回值类型是本次读取操作存储到 byte 缓冲数组中的字节个数 | |
int read(byte[] buffer, int offset, int length); | |
从文件中读取数据到 byte 类型的缓冲数组中,要求从 offset 下标位置开始,到 length 计数结束,返回值 | |
是读取到数组中的字节个数 | |
【注意】 | |
如果读取到文件末尾,以上所有方法的返回值都是 -1 EOF (End Of File) |
操作流程:
- 明确读取内容的文件路径
- 打开 FileInputStream 输入流管道对象
- 读取文件,推荐使用缓冲方式,效率高
- 关闭资源
# 2.3 文件操作字节输出流
FileOutputStream 构造方法 | |
FileOutputStream(String filePath); | |
根据指定的文件路径,创建对应当前文件的文件操作字节输出流对象 | |
FileOutputStream(File file); | |
根据指定的文件 File 对象,创建对应当前文件的文件操作字节输出流对象 | |
FileOutputStream(String filePath, boolean append); | |
根据指定的文件路径,创建对应当前文件的文件操作字节输出流对象,append 用于控制当前文件内容写入采取的是 | |
追加写(true)还是删除写(false) | |
FileOutputStream(File file, boolean append); | |
根据指定的文件 File 对象,创建对应当前文件的文件操作字节输出流对象,append 用于控制当前文件内容写入 | |
采取的是追加写(true)还是删除写(false) | |
【注意】 | |
1. FileOutputStream 文件操作字节输出流,默认对于文件内容采用的方式是删除写,如果想要在原本的文件内 | |
容上追加写,需要使用 boolean append 参数为 true 进行约束和限制。 | |
2. FileOutputStream 有创建文件的能力,路径合法,文件夹有操作权限,FileOutputStream 对应目标文件 | |
不存在,可以自行创建 | |
写入操作相关方法 | |
void write(int b); | |
写入一个字节数据到文件中 | |
void write(byte[] buffer); | |
写入一个字节数组数据到文件中 | |
void write(byte[] buffer, int offset, int length); | |
写入一个字节数组,从指定 offset 下标开始,计数字节个数 length 数据写入到文件中。 |
操作流程:
- 明确写入内容的文件路径
- 打开 FileOutputStream 输出流管道对象
- 写入数据到文件中,推荐使用缓冲方式,效率高
- 关闭资源
# 2.4 缓冲流
- 【重点】任何一个缓冲流都没有读写文件的能力,只是提供必要的缓冲数据空间。读取能力需要对应 IO 流对象来提供。
BufferedInputStream(InputStream in);需要提供 InputStream 字节输入流对象作为缓冲输入流实例化对象参数
BufferedOutputStream(OutputStream out);需要提供 OutputStream 字节输出流对象作为缓冲输出流实例化对象参数
/*
所有相关的读写方法,都是来自于 输入流和输出流 read write 输出流如果需要进行追加写,需要 OutputStream 必须有追加写能力 */
# day16 线程
# 1. 什么是进程
在计算机中独立运行的完整程序,需要系统分配资源,重点资源
1. CPU 占用率
2. 内存 占用内存空间
3. 硬盘 数据占用和硬盘数据存储速度
4. 网络 带宽情况
5. 电源 供电情况
6. GPU 对于 JavaWEB 服务器使用很少
CPU 运行时间片
CPU 执行一个完成的任务周期的时间,不同的程序在时间片中【抢占】执行。CPU 可以在单位时间片执行多个程序。
【程序抢占执行】【时间片】
# 2. 什么是线程
进程一个完整的程序,可以认为是一个【完整的工厂】
线程可以认为是进程的组成部分,也可以认为是工厂的一条流水线
线程使用的资源由当前进程分配,CPU 内存,硬盘,网络,电源,GPU,并且线程在进程中使用,采用的方式也是【抢占式执行】,也需要【CPU 执行时间片分配】
注意
1. 一个进程最少由一个线程存在,如果一个进程中没有任何一个线程,进程关闭
2. Java 程序最少 2 个线程
a. main 主线程
b. JVM GC 垃圾回收守护/后台线程
# 3. 线程和进程关系
线程是进程的组成部分
进程是线程的生存空间
JavaWEB 主要开发使用的【线程】,因为 JavaWEB Application 是一个完整的进程,需要在项目中使用多线程技术,来处理用户请求,数据存储,任务实现,同时需要辅助相关的线程技术,锁机制,队列机制,线程池
# 4. 线程的优势和劣势
优势:
1. 提高资源利用率
2. 提高用户体验
劣势:
1. 会导致硬件负担过重
2. 卡顿导致用户体验较差
3. 容易导致【死锁】
富贵险中求,物极必反
# 5. Java 完成多线程代码
# 5.1 线程代码基本实现
- 方式一 自定义类型继承 Thread 类型,重写 run 方法,实现自定义线程类
- 方式二 自定义类型遵从 Runnable 接口,实现 run 方法,完成自定义线程类
package com.qfedu.a_thread; | |
/* | |
* 线程代码基本实现 | |
* - 方式一 自定义类型继承 Thread 类型,重写 run 方法,实现自定义线程类 | |
* - 方式二 自定义类型遵从 Runnable 接口,实现 run 方法,完成自定义线程类 | |
*/ | |
class MyThread1 extends Thread { | |
/* | |
What will be run ~~~ | |
跑啥 | |
run 方法内容是当前线程类的功能代码,线程执行任务目标。 | |
*/ | |
@Override | |
public void run() { | |
for (int i = 0; i < 1000; i++) { | |
System.out.println("~~~~ JDG BLG LNG ~~~~"); | |
} | |
} | |
} | |
class MyThread2 implements Runnable { | |
@Override | |
public void run() { | |
for (int i = 0; i < 1000; i++) { | |
System.out.println("~~~~ EDG VS WBG ~~~~"); | |
} | |
} | |
} | |
/** | |
* 当前代码有四个线程 | |
* 1. mt1 | |
* 2. mt2 | |
* 3. main 主线程 | |
* 4. JVM GC | |
* | |
* @author Anonymous 2023/8/8 10:48 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
/* | |
如果需要执行线程代码,利用 Thread 类 start 方法开启线程 | |
不可以使用 run 方法直接调用方式开启线程代码。 | |
*/ | |
// 当前 MyThread1 继承 Thread 类,可以实例化对象,直接调用 start 方法开启线程 | |
MyThread1 mt1 = new MyThread1(); | |
// 利用 Thread 类构造方法,使用 Runnable 接口实例化对象作为构造方法参数 | |
// 明确当前线程对象执行的线程目标。 | |
Thread mt2 = new Thread(new MyThread2()); | |
for (int i = 0; i < 1000; i++) { | |
System.out.println("~~~main 线程 BLG S13总冠军~~~"); | |
} | |
mt1.start(); | |
mt2.start(); | |
} | |
} |
# 5.2 线程操作相关方法
构造方法 | |
Thread(); | |
实例化线程对象,未明确线程目标和线程名称 | |
Thread(Runnable target); | |
实例化线程对象,使用 Runnable 接口实现类明确线程的执行目标 | |
Thread(Runnable target, String name); | |
实例化线程对象,使用 Runnable 接口实现类明确线程的执行目标,同时指定线程的名称 | |
成员方法 | |
String getName(); | |
获取线程对象名称 | |
void setName(String name); | |
设置线程对象名称 | |
int getPriority(); | |
获取线程对象优先级 | |
线程优先级 1 最低,10 最高,默认为 5 | |
private static final int MIN_PRIORITY = 1; | |
private static final int MAX_PRIORITY = 10; | |
private static final int NORMAL_PRIORITY = 5; | |
void setPriority(int newPriority); | |
设置线程对象优先级 | |
线程优先级只是提升线程抢占 CPU 执行权的概率,并不是执行顺序。 | |
boolean isDaemon(); | |
判断当前线程是否为【守护/后台线程】 | |
void setDaemon(boolean on); | |
设置当前线程是否为守护线程,on 为 true 表示当前线程为守护线程 | |
public static void sleep(int ms); | |
在哪一个线程代码中执行,当前线程阻塞指定的时间,时间单位为毫秒 | |
public static Thread currentThread(); | |
在哪一个线程代码中执行,获取当前线程对象。 |
package com.qfedu.a_thread; | |
/** | |
* @author Anonymous 2023/8/8 11:24 | |
*/ | |
public class Demo2 { | |
public static void main(String[] args) { | |
Thread t1 = new Thread(); | |
Thread t2 = new Thread(() -> System.out.println("线程代码")); | |
Thread t3 = new Thread(() -> System.out.println("路见不平一声吼,鸽姐没有男朋友。两只黄鹂鸣翠柳,鸽姐是个单身狗。"), "朱某"); | |
t3.setName("某鸽"); | |
t3.setPriority(10); | |
/* | |
在哪一个线程代码中执行,获取当前线程对象,当前执行位置是 main 方法 | |
内容,获取的线程对象是 main 线程 | |
Thread [main,5,main] | |
*/ | |
Thread thread = Thread.currentThread(); | |
/* | |
Thread [Thread-0,5,main] | |
ThreadName, ThreadPriority, ThreadGroup | |
线程名,线程优先级,线程组 | |
*/ | |
System.out.println(t1); | |
System.out.println(t2); | |
System.out.println(t3); | |
System.out.println(thread); | |
} | |
} |
# 5.3 守护线程
package com.qfedu.a_thread; | |
class DownloadThread implements Runnable { | |
@Override | |
public void run() { | |
for (int i = 0; i <= 100; i++) { | |
System.out.println("游戏更新下载中... " + i + " %"); | |
try { | |
/* | |
run 方法是实现 Runnable 接口方法,接口中 run 方法没有抛出 | |
异常信息,重写 / 实现方法声明必须和接口 / 父类中的声明一致,所有当前 | |
实现方法过程中,不可以抛出异常内容,只能捕获 | |
*/ | |
Thread.sleep(100); | |
} catch (InterruptedException e) { | |
/* | |
因为 RuntimeException 是运行时异常,当前异常类型不需要方法 | |
声明告知明确的异常类型,可以用【糖衣炮弹】方式将当前编译时异常 | |
包装为运行时异常抛出,方法外部的得到的异常类型不变。 | |
*/ | |
throw new RuntimeException(e); | |
} | |
} | |
} | |
} | |
/** | |
* 守护线程 / 后台线程 | |
* 1. 必须存在其他非守护线程 | |
* 2. 一旦代码中有且只有守护线程,进程直接销毁,程序退出。 | |
* | |
* 守护线程可以用于 | |
* 1. 日志记录 | |
* 2. 监控机制 | |
* 3. 阈值监控 | |
* | |
* @author Anonymous 2023/8/8 11:29 | |
*/ | |
public class Demo3 { | |
public static void main(String[] args) throws InterruptedException { | |
Thread thread = new Thread(new DownloadThread()); | |
// 设置当前下载线程为守护线程,当前代码中有且只有守护线程的情况下,会自动关闭退出 | |
thread.setDaemon(true); | |
thread.start(); | |
for (int i = 0; i < 50; i++) { | |
System.out.println("大厅游戏等待中~~~~"); | |
Thread.sleep(100); | |
} | |
} | |
} |
# 6. 同步机制
# 6.1 案例分析
<<孤注一掷>>
万达影院一场有 100 个座位/ 100 张票
有三个销售渠道
1. 猫眼 2. 淘票票 3. 美团
三个销售渠道可以认为是三个线程, 100 张票是【共享资源】
【解决】
三个线程对象,同时销售 100 张票,解决线程冲突问题。
# 6.2 同步代码块解决问题
同步代码块格式
synchronized (/* 锁对象 */) {// 同步代码
}
- 同步代码块中有且只允许一个线程进入执行,其他线程等待状态 / 阻塞状态
- 锁对象,要求必须是【对象】,基本数据类型和包装类型都不可以。并且锁对象对于当前限制的线程对象具备唯一性。
package com.qfedu.a_thread; | |
class Lock {} | |
class SaleThread implements Runnable { | |
private static int ticket = 1000; | |
/** | |
* 最好使用 final 修饰,确定当前对象的唯一性 | |
*/ | |
private static final Lock lock = new Lock(); | |
@Override | |
public void run() { | |
// 获取当前执行 run 方法线程对象 | |
Thread thread = Thread.currentThread(); | |
while (true) { | |
/* | |
类锁 | |
SaleThread.class | |
使用当前线程对象数据类型对应的 Class 对象作为锁对象 | |
可以限制当前所有的线程对象操作。 | |
对象锁 | |
可以利用自定义锁类型 例如 Lock | |
在当前线程代码中,定义 static 修饰的静态成员变量 Lock 类型对象 | |
根据 Lock 对象的唯一性,满足锁对象需求 | |
*/ | |
synchronized (new Demo1()) { | |
if (ticket > 0) { | |
//thread.getName () 获取当前线程名称 | |
System.out.println(thread.getName() + "售出编号为 " + ticket + " 的电影票"); | |
ticket--; | |
} else { | |
System.out.println(thread.getName() + "售罄"); | |
break; | |
} | |
} | |
} | |
} | |
} | |
/** | |
* @author Anonymous 2023/8/8 14:49 | |
*/ | |
public class Demo4 { | |
public static void main(String[] args) { | |
Thread t1 = new Thread(new SaleThread(), "美团"); | |
Thread t2 = new Thread(new SaleThread(), "淘票票"); | |
Thread t3 = new Thread(new SaleThread(), "猫眼"); | |
t1.start(); | |
t2.start(); | |
t3.start(); | |
} | |
} |
# 6.3 同步方法解决问题
格式
权限修饰符 [static] synchronized 返回值类型 方法名(形式参数列表) {}
- 静态成员方法推荐使用类名调用。
- 非静态成员方法有且只可以通过类对象调用。
【重点】
- 静态同步方法,锁对象为当前类对应的 Class 对象。
- 非静态同步方法,锁对象时当前调用方法的类对象。
package com.qfedu.a_thread; | |
class SaleThreadOne implements Runnable { | |
private static int ticket = 100; | |
@Override | |
public void run() { | |
while (ticket > 0) { | |
/* | |
静态同步方法没有任何问题 | |
*/ | |
// sale(); | |
/* | |
非静态同步方法无法解决同步问题。 | |
*/ | |
saleTicket(); | |
} | |
} | |
/** | |
* 静态同步方法 | |
*/ | |
public static synchronized void sale() { | |
// 获取当前执行 run 方法线程对象 | |
Thread thread = Thread.currentThread(); | |
if (ticket > 0) { | |
//thread.getName () 获取当前线程名称 | |
System.out.println(thread.getName() + "售出编号为 " + ticket + " 的电影票"); | |
ticket--; | |
} else { | |
System.out.println(thread.getName() + "售罄"); | |
} | |
} | |
/** | |
* 非静态同步方法 | |
*/ | |
public synchronized void saleTicket() { | |
// 获取当前执行 run 方法线程对象 | |
Thread thread = Thread.currentThread(); | |
if (ticket > 0) { | |
//thread.getName () 获取当前线程名称 | |
System.out.println(this + " " + thread.getName() + "售出编号为 " + ticket + " 的电影票"); | |
ticket--; | |
} else { | |
System.out.println(thread.getName() + "售罄"); | |
} | |
} | |
} | |
/** | |
* @author Anonymous 2023/8/8 14:49 | |
*/ | |
public class Demo5 { | |
public static void main(String[] args) { | |
Thread t1 = new Thread(new SaleThreadOne(), "美团"); | |
Thread t2 = new Thread(new SaleThreadOne(), "淘票票"); | |
Thread t3 = new Thread(new SaleThreadOne(), "猫眼"); | |
t1.start(); | |
t2.start(); | |
t3.start(); | |
} | |
} |
# 6.4 静态和非静态同步方法锁限制情况
# 7. 单例模式
要求当前类型在整个程序运行过程中,有且只能有一个对象。可以用于后期项目的 Service 层 Dao 层 Controller 层实例化对象使用,保证以上对象的唯一性,可以满足项目代码执行功能唯一性。
- 饿汉模式
- 懒汉模式
# 7.1 懒汉模式
- 私有化构造方法
- 防止类外通过 new + 构造方法形式直接实例化对象操作,不满足单例模式
- 对外提供必要的【公开静态】成员方法,返回值类型为当前对象类型。
- public 修饰可以满足类外调用当前方法获取对象需求
- static 修饰,可以保证当前方法通过类名直接调用,不需要实例化对象调用。因为当前类构造方法已经私有化修饰,无法通过 new + 构造方法形式实例化对象操作,获取对象的方法,使用 static 修饰更合适
- 使用静态成员变量记录第一次创建对象地址,之后方法进行提供数据操作
- 【重点】获取对象 getInstance 方法必须采用 synchronized ,避免多线程实例化对象冲突问题
package com.qfedu.b_single; | |
/** | |
* 懒汉模式 | |
* | |
* @author Anonymous 2023/8/8 16:58 | |
*/ | |
public class SingleDemo1 { | |
/** | |
* 私有化静态成员变量 SingleDemo1 类型,用于在用户第一次调用 getInstance 方法 | |
* 获取对象时,创建对象存储对应的地址。 | |
* 之后用户再次调用 getInstance 方法,需要从 sd 对象中获取之前的对象内容,保证 | |
* 当前 SingleDemo1 有且只有一个实例化对象。【单例模式】 | |
*/ | |
private static SingleDemo1 sd = null; | |
/** | |
* 私有化构造方法,防止类外可以通过 new + 构造方法形式直接 | |
* 实例化对象操作 | |
* 【注意】单例对象不要通过反射操作获取构造方法实例化对象实现。 | |
*/ | |
private SingleDemo1() {} | |
/** | |
* 获取当前 SingleDemo1 实例化对象操作,用户第一次调用会执行 | |
* new + 构造方法实例化对象, 并且存储对象到 sd 中,之后的获取 | |
* 都是从 sd 中获取之前的对象内容 | |
* | |
* 【注意】 | |
* 当前方法需要使用 synchronized 同步限制,并且方法为静态同步方法 | |
* 锁对象是当前 SingleDemo1.Class 对象,通过任何一个方式执行方法 | |
* 其他调用方法操作都会被限制阻塞。保证实例化对象的唯一性。 | |
* | |
* @return SingleDemo1 对象 | |
*/ | |
public static synchronized SingleDemo1 getInstance() { | |
if (null == sd) { | |
sd = new SingleDemo1(); | |
} | |
return sd; | |
} | |
} |
# 7.2 饿汉模式
package com.qfedu.b_single; | |
/** | |
* 饿汉模式 | |
* 是 Spring 管理 Bean 单例对象 默认方式 | |
* | |
* @author Anonymous 2023/8/8 17:11 | |
*/ | |
public class SingleDemo2 { | |
/** | |
* private 对外私有不能随意获取 | |
* static 保证当前对象在整个程序中唯一,同时利用类加载机制在程序准备阶段,对当前类型进行实例化对象操作。 | |
* final 可以认为当前对象是一个带有名称的常量,也是一个唯一限制 | |
* | |
* 后续的 getInstance 获取过程中,直接获取在类文件加载阶段实例化的单例对象。 | |
* 【优点】饿汉模式自带线程安全,性能相较于懒汉模式较高 | |
*/ | |
private static final SingleDemo2 SD = new SingleDemo2(); | |
private SingleDemo2() {} | |
public static SingleDemo2 getInstance() { | |
return SD; | |
} | |
} |
# day17 函数式接口和 Lambda
# 1. 函数式接口
# 1.1 要求
- 要求当前接口有且只有一个尚未完成的缺省属性为 pubilc abstract 修饰方法
- 开发要求函数式接口必须有 @FunctionalInterface 注解约束,开始函数式接口严格格式检查
# 1.2 Predicate 函数式接口
@FunctionalInterface | |
public interface Predicate<T> { | |
/** | |
* 可以用于条件判断操作根据用户指定的数据类型,自行完成判断条件,给予 | |
* boolean 类型数据反馈,满足其他功能 | |
* | |
* @param t 泛型数据类型,可以是用户约束的任意一个数据类型 | |
* @return 返回值为 boolean,可以自行完成对于当前数据的判断约束条件 | |
*/ | |
boolean test(T t); | |
} |
案例代码和 Lambda 引入
package com.qfedu.c_fi; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.List; | |
import java.util.function.Predicate; | |
import java.util.stream.Collectors; | |
/** | |
* @author Anonymous 2023/8/9 16:05 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
Student[] students = { | |
new Student(1, "朱某", 16), | |
new Student(2, "朱某", 19), | |
new Student(3, "朱某", 6), | |
new Student(4, "朱某", 5), | |
new Student(5, "朱某", 7), | |
new Student(6, "朱某", 9), | |
new Student(7, "朱某", 10), | |
new Student(8, "朱某", 12), | |
new Student(9, "朱某", 11), | |
new Student(10, "朱某", 15), | |
}; | |
List<Student> list = Arrays.stream(students).collect(Collectors.toList()); | |
/* | |
Predicate 接口中尚未实现的方法 | |
boolean test (T t); | |
参数 | |
1. 参数个数 1 个 | |
2. 参数类型 当前情况下,泛型对应的具体数据类型为 Student 类型 | |
3. 参数顺序 一个参数 | |
返回值类型: | |
boolean 要求当前 Lambda 表达式必须返回一个 boolean 类型结果 | |
Lambda 表达式基本格式: | |
(临时参数变量) -> {Lambda 表达式对应方法,方法体实现内容,功能内容}; | |
当前 Predicate 接口 Lambda 表达式实现 | |
(stu) -> {return stu.getAge () < 15;} | |
Lambda 表达式简化 / 优化 | |
1. 参数有且只有一个,小括号可以省略 | |
2. 方法体 / 功能内容有且只有一行,大括号可以省略,return 可以省略 | |
(stu) -> {return stu.getAge () < 15;} | |
==> | |
stu -> stu.getAge () < 15 | |
*/ | |
List<Student> list1 = filterStudentArray(list, (stu) -> { | |
return stu.getAge() < 15; | |
}); | |
list1.forEach(System.out::println); | |
System.out.println(); | |
List<Student> list2 = filterStudentArray(list, stu -> stu.getAge() < 15); | |
list2.forEach(System.out::println); | |
} | |
public static List<Student> filterStudentArray(List<Student> list, Predicate<Student> filter) { | |
ArrayList<Student> studentList = new ArrayList<>(); | |
for (Student student : list) { | |
/* | |
Predicate 过滤器 / 判断器接口中的 test 方法来完成针对于 Student 数据信息 | |
过滤限制操作,满足条件,放入到 studentList 集合中 | |
*/ | |
if (filter.test(student)) { | |
studentList.add(student); | |
} | |
} | |
return studentList; | |
} | |
} |
[toc]
# 线程状态,线程通信和线程池
# 1. 线程状态
# 2. 线程通信 wait notify
# 2.1 相关方法
以下方法为 Object 类相关方法,Java 中的所有类型都可以调用执行以下方法。都是线程相关操作方法。
public void wait(); | |
【通过锁对象调用】 | |
1. 在哪一个线程中执行,对应线程进入【等待阻塞】状态, | |
2. 开启锁对象,允许其他线程进入 CPU 执行权抢占操作。 | |
3. 当前等待阻塞没有时间限制,需要其他线程通过唤醒操作回到可运行状态 | |
public void wait(long ms); | |
【通过锁对象调用】 | |
1. 在哪一个线程中执行,对应线程进入【等待阻塞】状态, | |
2. 开启锁对象,允许其他线程进入 CPU 执行权抢占操作。 | |
3. 如果当前线程达到指定时间,自动回到【可运行状态】 | |
4. 未到指定时间的情况下,中途被其他线程唤醒也可以进入【可运行状态】 | |
public void notify(); | |
【通过锁对象调用】 | |
1. 唤醒与当前锁对象相关,同时处于【等待阻塞】状态任意一个线程。 | |
2. 同时开启锁对象,允许其他线程进入 CPU 执行权抢占操作。 | |
public void notifyAll(); | |
【通过锁对象调用】 | |
1. 唤醒与当前锁对象相关,同时处于【等待阻塞】状态所有线程。 | |
2. 同时开启锁对象,允许其他线程进入 CPU 执行权抢占操作。 |
# 2.2 生产者和消费者
# 2.2.1 图例分析
# 2.2.2 商品作为共享资源处理
public static void main(String[] args) { | |
// 商品对象 | |
Goods goods = new Goods("科尼塞克", 50000000, true); | |
System.out.println(goods); | |
// 生产者和消费者对象 | |
Supplier supplier = new Supplier(goods); | |
Consumer consumer = new Consumer(goods); | |
supplier.showGoods(); | |
consumer.showGoods(); | |
} |
# 2.2.3 代码实现
Consumer 消费者
package com.qfedu.a_; | |
/** | |
* 消费者 | |
* | |
* @author Anonymous 2023/8/9 10:14 | |
*/ | |
public class Consumer implements Runnable { | |
/** | |
* final 修饰 goods 成员变量,保证当前成员变量唯一 | |
* 同时限制使用有参数构造方法对当前 Goods 进行实例化对象操作 | |
* 保证生产者和消费者之间使用的 Goods 对象为同一个对象 | |
*/ | |
private final Goods goods; | |
public Consumer(Goods goods) { | |
this.goods = goods; | |
} | |
public void showGoods() { | |
System.out.println(goods); | |
} | |
@Override | |
public void run() { | |
while (true) { | |
try { | |
Thread.sleep(100); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
synchronized (goods) { | |
// 判断商品是否可以购买 goods.isProduct () == false | |
if (!goods.isProduct()) { | |
// 买买买!!! | |
System.out.println(Thread.currentThread().getName() + " 消费者以" + goods.getPrice() + "价格购买了" + goods.getName()); | |
// 修改生产标记 | |
goods.setProduct(true); | |
// 唤醒生产者 | |
System.out.println("唤醒生产者"); | |
goods.notify(); | |
} else { | |
// 消费者进入休眠状态 | |
System.out.println(Thread.currentThread().getName() + " 消费者进入休眠状态"); | |
try { | |
goods.wait(); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
} | |
} | |
} |
Supplier 生产者
package com.qfedu.a_; | |
/** | |
* 生产者 | |
* | |
* 需要加锁 | |
* 防止出现生产者在生产过程中,被消费者抢占 CPU 执行权。 | |
* 有可能会导致 | |
* 商品情况为 五菱宏光 Mini 29800 | |
* 再次生产时,商品名称刚刚修改,价格未修改的情况下,消费者前来购买 | |
* 商品情况为 红旗 L5 29800 | |
* 【商品价格错误】 | |
* | |
* @author Anonymous 2023/8/9 10:15 | |
*/ | |
public class Supplier implements Runnable { | |
/** | |
* final 修饰 goods 成员变量,保证当前成员变量唯一 | |
* 同时限制使用有参数构造方法对当前 Goods 进行实例化对象操作 | |
* 保证生产者和消费者之间使用的 Goods 对象为同一个对象 | |
*/ | |
private final Goods goods; | |
public Supplier(Goods goods) { | |
this.goods = goods; | |
} | |
public void showGoods() { | |
System.out.println(goods); | |
} | |
@Override | |
public void run() { | |
while (true) { | |
try { | |
Thread.sleep(100); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
synchronized (goods) { | |
// 判断当前商品是否需要生产 | |
if (goods.isProduct()) { | |
// 生产过程 | |
if (Math.random() > 0.5) { | |
goods.setName("红旗 L5"); | |
goods.setPrice(12000000); | |
} else { | |
goods.setName("五菱宏光 Mini"); | |
goods.setPrice(29800); | |
} | |
System.out.println("生产者生产了" + goods.getName() + ", 价格为:" + goods.getPrice() + "元"); | |
// 修改生产标记 | |
goods.setProduct(false); | |
// 唤醒消费者 | |
System.out.println("唤醒消费者"); | |
goods.notifyAll(); | |
} else { | |
// 生产者进入休眠状态 | |
System.out.println("生产者进入休眠状态"); | |
try { | |
goods.wait(); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
} | |
} | |
} |
# 3. 线程池
# 3.1 生活案例
餐厅服务员
- 餐厅服务员最低人员要求,保障餐厅可以正常运营
- 餐厅服务人员会根据当前就餐人数动态调整,顾客多,服务员同步添加。
- 顾客过多,分发排号,用户等待。
- 如果服务员在一定时间内没有任何的任务执行,服务员休息。
# 3.2 固定容量线程池
package com.qfedu.b_threadpool; | |
import java.util.List; | |
import java.util.concurrent.ExecutorService; | |
import java.util.concurrent.Executors; | |
/** | |
* 固定容量线程池 | |
* | |
* @author Anonymous 2023/8/9 14:49 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
/* | |
public static ExecutorService newFixedThreadPool (int nThreads); | |
通过 Executors 类调用方法,创建线程对象个数固定的线程池对象 | |
*/ | |
ExecutorService threadPool = Executors.newFixedThreadPool(5); | |
ExecutorService threadPool1 = Executors.newCachedThreadPool(); | |
/* | |
Future<?> submit (Runnable task); | |
ExecutorService 线程池对象提交任务方法,方法参数为 Runnable 接口实现类 | |
Runnable 接口中核心方法是 | |
void run (); | |
Lambda 表达式提交目标任务 | |
*/ | |
threadPool.submit(() -> System.out.println(Thread.currentThread().getName() + " 执行任务中......")); | |
threadPool.submit(() -> { | |
for (int i = 0; i < 100; i++) { | |
try { | |
Thread.sleep(100); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
System.out.println(Thread.currentThread().getName() + " 执行任务中......"); | |
} | |
}); | |
threadPool.submit(() -> System.out.println(Thread.currentThread().getName() + " 执行任务中......")); | |
/* 展示当前 ThreadPool 相关信息 | |
[Running, pool size = 5, active threads = 1, queued tasks = 0, completed tasks = 39] | |
Running 线程状态 | |
pool size = 5 当前线程池线程对象容量, | |
active threads = 1 目前正在执行任务的线程对象个数 | |
queued tasks = 0 当前线程池尚未执行的任务个数,在【任务队列中】等待的任务个数 | |
completed tasks = 39 已完成的任务个数 | |
*/ | |
System.out.println("ThreadPool : " + threadPool); | |
threadPool.submit(() -> System.out.println(Thread.currentThread().getName() + " 执行任 | |
threadPool.submit(() -> System.out.println(Thread.currentThread().getName() + " 执行任务中......")); | |
// 展示当前 ThreadPool 相关信息 | |
System.err.println("ThreadPool : " + threadPool); | |
/* | |
void shutdown (); | |
ExecutorService 关闭线程池方法,会将任务队列中所有尚未执行的任务全部执行完毕之后 | |
关闭线程池对象,无法再次添加新的任务 | |
*/ | |
// threadPool.shutdown(); | |
/* | |
List<Runnable> shutdownNow (); | |
立即关闭,返回值内容是 List 集合中,尚未执行的 任务 Runnable 实现类对象 List 集合. | |
无法再次添加新的任务 | |
*/ | |
List<Runnable> runnables = threadPool.shutdownNow(); | |
System.out.println(runnables.size()); | |
} | |
} |
# 3.3 线程池相关 7 大参数
public ThreadPoolExecutor(int corePoolSize, | |
int maximumPoolSize, | |
long keepAliveTime, | |
TimeUnit unit, | |
BlockingQueue<Runnable> workQueue, | |
ThreadFactory threadFactory, | |
RejectedExecutionHandler handler) { ... } |
您提供的代码是Java中 ThreadPoolExecutor 类的构造函数。它使用给定的初始参数创建 ThreadPoolExecutor 的新实例。
以下是参数的解释:
- 【corePoolSize】 :保持在池中的线程数,即使它们是空闲的,除非设置了 allowCoreThreadTimeOut 。
- 【maximumPoolSize】 :允许在池中的最大线程数。
- keepAliveTime :当线程数大于核心线程数时,多余的空闲线程在终止之前等待新任务的最长时间。
- unit : keepAliveTime 参数的时间单位。
- workQueue :在执行任务之前用于保存任务的队列。此队列仅保存由 execute 方法提交的 Runnable 任务。 newFixedThreadPool 固定容量线程池 任务队列形式 dBlockingQueue<Runnable>
newCachedThreadPool 缓冲/动态容量线程池 任务队列形式 SynchronousQueue<Runnable>
- threadFactory :在执行器创建新线程时使用的工厂。
- handler :当执行由于线程限制和队列容量达到而被阻塞时使用的处理程序。
构造函数对参数进行一些验证,如果不满足任何条件,则抛出异常。
总体而言,该构造函数允许您使用特定的配置参数创建一个 ThreadPoolExecutor ,以管理用于并发执行任务的线程池。
# day18 函数式接口,Lambda 和 Stream 流
# 1. 函数式接口
# 昨日重现 Predicate 过滤 / 判断 / 限制
@FunctionalInterface | |
public interface Predicate <T> { | |
boolean test(T o); | |
} |
# 1.1 Comparator 比较器接口
@FunctionalInterface | |
public interface Comparator<T> { | |
/** | |
* 比较器接口对应的 compare 方法,可以提供给排序算法 (sort),存储结构有排序需求 (Tree 树形结构) | |
* | |
* @param o1 泛型限制数据类型对象 | |
* @param o2 泛型限制数据类型对象 | |
* @return 返回值为 int 类型,0 表示两者一致 | |
*/ | |
int compare(T o1, T o2); | |
} |
案例代码和 Lambda 表达式
package com.qfedu.a_functionalInterface; | |
import java.util.Arrays; | |
import java.util.Comparator; | |
/** | |
* @author Anonymous 2023/8/10 9:49 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
Student[] students = { | |
new Student(4, "朱SD", 12, true), | |
new Student(6, "朱SD", 9, true), | |
new Student(7, "朱SD", 1, true), | |
new Student(1, "朱某", 16, true), | |
new Student(3, "朱SD", 15, true), | |
new Student(2, "某鸽", 13, true), | |
new Student(5, "朱SD", 10, true), | |
new Student(8, "朱·SingleDog·鸽", 3, true) | |
}; | |
/* | |
分析 Lambda | |
int compare (T o1, T o2); | |
参数 | |
1. 参数类型 当前泛型对应数据类型为 Student 类型 | |
2. 参数个数 2 个 | |
3. 参数顺序 都为 Student 类型 | |
返回值: | |
1. 数据类型 int | |
2. 需要从两个 Student 对象中得到一个 int 类型数据 | |
Lambda 形式 | |
(stu1, stu2) -> {return stu1.getAge () - stu2.getAge ();} | |
selectSort (students, (stu1, stu2) -> { | |
return stu2.getAge () - stu1.getAge (); | |
});*/ | |
/* | |
Lambda 形式 | |
(stu1, stu2) -> {return stu1.getAge () - stu2.getAge ();} | |
简化优化: | |
(stu1, stu2) -> stu1.getAge () - stu2.getAge () | |
*/ | |
selectSort(students, (stu1, stu2) -> stu2.getAge() - stu1.getAge()); | |
Arrays.stream(students).forEach(System.out::println); | |
System.out.println(); | |
/* | |
Arrays 数组工具类排序方法 | |
public static <T> void sort (T [] arr, Comparator<T> comparator); | |
*/ | |
Arrays.sort(students, Comparator.comparing(Student::getId)); | |
Arrays.sort(students, (stu1, stu2) -> stu1.getId() - stu2.getId()); | |
Arrays.stream(students).forEach(System.out::println); | |
} | |
/** | |
* 选择排序算法,支持任意类型数组,需要提供对应类型的 Comparator 比较器实现 | |
* | |
* @param arr 任意类型数组 | |
* @param comparator 需要满足当前数据类型的 Comparator 比较器实现 | |
* @param <T> 自定义泛型 | |
*/ | |
public static <T> void selectSort(T[] arr, Comparator<T> comparator) { | |
for (int i = 0; i < arr.length - 1; i++) { | |
int index = i; | |
for (int j = i + 1; j < arr.length; j++) { | |
if (comparator.compare(arr[index], arr[j]) > 0) { | |
index = j; | |
} | |
} | |
if (index != i) { | |
T temp = arr[index]; | |
arr[index] = arr[i]; | |
arr[i] = temp; | |
} | |
} | |
} | |
} |
# 1.2 FilenameFilter 文件名过滤器接口
@FunctionalInterface | |
public interface FilenameFilter { | |
/** | |
* 文件名过滤器方法 | |
* | |
* @param dir 进行文件列表搜索的目标文件夹 File 对象 | |
* @param childName 目标文件夹中子文件和子文件夹的名称 | |
* @return 根据限制条件返回 boolean 类型结果 | |
*/ | |
boolean accept(File dir, String childName); | |
} |
package com.qfedu.a_functionalInterface; | |
import java.io.File; | |
import java.util.Arrays; | |
/** | |
* FilenameFilter 文件名过滤器接口 | |
* | |
* @author Anonymous 2023/8/10 10:48 | |
*/ | |
public class Demo2 { | |
public static void main(String[] args) { | |
File file = new File("E:/aaa"); | |
/* | |
Lambda 分析 | |
boolean accept (File dir, String name); | |
参数 | |
1. 参数类型 File, String | |
2. 参数个数 2 个 | |
3. 参数顺序 File, String | |
返回值 | |
1. 返回值类型 boolean | |
2. 要求 Lambda 表达式必须返回 boolean 类型数据 | |
Lambda 格式: | |
要求过滤保存所有的 Java 文件 | |
(dir, name) -> {return new File (dir, name).isFile () && name.endsWith (".java");} | |
Lambda 优化 / 简化 | |
(dir, name) -> new File (dir, name).isFile () && name.endsWith (".java") | |
*/ | |
File[] listFiles = file.listFiles((dir, name) -> { | |
return new File(dir, name).isFile() && name.endsWith(".java"); | |
}); | |
File[] listFiles2 = file.listFiles((dir, name) -> new File(dir, name).isFile() && name.endsWith(".java")); | |
// 断言!!!判断的言论 | |
assert listFiles != null; | |
Arrays.stream(listFiles).forEach(System.out::println); | |
System.out.println(); | |
assert listFiles2 != null; | |
Arrays.stream(listFiles2).forEach(System.out::println); | |
} | |
} |
# 1.3 Function<T, R> 转换器接口【重点】
- 可以对应 Java 中任意返回值类型,任意参数类型,要求参数只有一个的任意方法。
- Function 函数式接口更多的情况下是需要通过【方法引用操作】
@FunctionalInterface | |
public interface Function<T, R> { | |
/** | |
* 转换器接口,可以将 T 类型转换为目标 R 类型,T 和 R 对应的具体数据类型都有用户自行决定 | |
* | |
* @param t 用户指定的参数类型 | |
* @return 返回值类型可以进行明确约束,也可以通过 Lambda 表达式返回数据具体情况约束 R 对应具体数据类型 | |
*/ | |
R apply(T t); | |
} |
package com.qfedu.a_functionalInterface; | |
import java.util.function.Function; | |
/** | |
* @author Anonymous 2023/8/10 11:05 | |
*/ | |
public class Demo3 { | |
public static void main(String[] args) { | |
Student student = new Student(1, "朱·SingleDog·鸽", 16, true); | |
/* | |
Lambda 表达式分析 | |
R apply (T t); | |
参数 | |
1. 参数类型 Student | |
2. 参数个数 1 个 | |
3. 参数顺序 Student | |
返回值 | |
1. 泛型明确约束返回值类型 R 对应 String 类型 | |
2. 要求 Lambda 操作需要将 Student 类型转换为 String | |
Lambda 格式: | |
(stu) -> {return stu.toString ();}; | |
Lambda 简化 / 优化 | |
stu -> stu.toString () | |
*/ | |
String s = studentInfo(student, (stu) -> { | |
return stu.toString(); | |
}); | |
String s1 = studentInfo(student, stu -> stu.toString()); | |
System.out.println(s); | |
System.out.println(s1); | |
} | |
/** | |
* 方法工功能是将 Student 类型数据转换为字符串形式,转换方法由 Function 接口约束提供 | |
* | |
* @param stu Student 学生对象类型 | |
* @param function 转换 Function 接口实现 | |
* @return Student 学生对象信息对应的字符串数据内容 | |
*/ | |
public static String studentInfo(Student stu, Function<Student, String> function) { | |
return function.apply(stu); | |
} | |
} |
# 1.4 Consumer 消费者 / 处理器接口
@FunctionalInterface | |
public interface Consumer <T> { | |
/** | |
* 消费者 / 处理器接口方法,需要用户提供目标的数据类型,作为最终的处理方式,可以满足 | |
* 日志记录,信息存储,数据发送操作。 | |
* | |
* @param t 用户约束对应的数据形式。 | |
*/ | |
void accept(T t); | |
} |
package com.qfedu.a_functionalInterface; | |
import java.io.*; | |
import java.util.function.Consumer; | |
/** | |
* Consumer 消费者 / 处理器接口 | |
* | |
* @author Anonymous 2023/8/10 11:18 | |
*/ | |
public class Demo4 { | |
public static void main(String[] args) { | |
String str = "方法引用 Function Reference"; | |
/* | |
Lambda 表达式分析 | |
void accept (T t); | |
参数 | |
1. 参数类型 String | |
2. 参数个数 1 个 | |
3. 参数顺序 String | |
返回值 | |
1. 无返回值!!!! | |
Lambda 形式 | |
(s) -> {....} | |
*/ | |
saveData(str, s -> { | |
// Lambda 内容,代码量过大,并且功能具备复用性!!!可以采用方法引用优化! | |
File file = new File("./data/source.txt"); | |
BufferedOutputStream bos = null; | |
try { | |
bos = new BufferedOutputStream(new FileOutputStream(file, true)); | |
bos.write(s.getBytes()); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} finally { | |
try { | |
if (bos != null) { | |
bos.close(); | |
} | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
}); | |
/* | |
方法引用 | |
重点: | |
1. 方法的参数类型和返回值类型满足所需 | |
2. 确定方法的调用方式 | |
常用【类名调用】【类对象调用】 | |
3. Java 会自行将方法所需参数给予方法引用对应的方法填充 | |
4. JDK 1.8 以上版本支持 | |
*/ | |
saveData(str, Demo4::saveDataToFile); | |
} | |
public static void saveData(String str, Consumer<String> handler) { | |
handler.accept(str); | |
} | |
public static void saveDataToFile(String str) { | |
File file = new File("./data/source.txt"); | |
BufferedOutputStream bos = null; | |
try { | |
bos = new BufferedOutputStream(new FileOutputStream(file, true)); | |
bos.write(str.getBytes()); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} finally { | |
try { | |
if (bos != null) { | |
bos.close(); | |
} | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
} |
# 1.5 Supplier 生产者接口 / 获取器接口
@FunctionalInterface | |
public interface Supplier<T> { | |
T get(); | |
} |
package com.qfedu.a_functionalInterface; | |
import java.util.function.Supplier; | |
/** | |
* Supplier 生产者接口 | |
* | |
* @author Anonymous 2023/8/10 14:29 | |
*/ | |
public class Demo5 { | |
public static void main(String[] args) { | |
int[] arr = {1 ,3, 5, 7, 19, 2, 4, 6, 8, 10}; | |
int maxIndex = getMaxIndex(() -> { | |
// Lambda 表达式大括号可以使用所在方法的局部变量 | |
int index = 0; | |
for (int i = 1; i < arr.length; i++) { | |
if (arr[index] < arr[i]) { | |
index = i; | |
} | |
} | |
return index; | |
}); | |
System.out.println(maxIndex); | |
} | |
public static int getMaxIndex(Supplier<Integer> sp) { | |
return sp.get(); | |
} | |
} |
# 2. Stream
# 2.1 概念
- 流水线编程思路。流式编程,一点到底
- 处理的主要数据都是【数组,集合】
- Stream 流操作涉及到,过滤,排序,转换,跳过,限制,去重,计数,转换最终集合形式。。。
# 2.2 Stream 体验卡
package com.qfedu.b_stream; | |
import java.util.ArrayList; | |
import java.util.stream.Stream; | |
/** | |
* @author Anonymous 2023/8/10 14:43 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
ArrayList<String> list = new ArrayList<>(); | |
list.add("羊肉炕馍"); | |
list.add("羊双肠"); | |
list.add("炸弹鸡"); | |
list.add("炸弹鸡"); | |
list.add("炸弹鸡"); | |
list.add("炸弹鸡"); | |
list.add("黄家包子"); | |
list.add("黄家包子"); | |
list.add("黄家包子"); | |
list.add("宝正瑧天津包子"); | |
list.add("宝正瑧天津包子"); | |
list.add("宝正瑧天津包子"); | |
list.add("宝正瑧天津包子"); | |
list.add("老马烧烤"); | |
list.add("刘洋记全羊鲜汤文化路任寨北街"); | |
list.add("班记油泼面"); | |
list.add("五顺斋烤鸭文化路"); | |
list.add("东北饺子馆"); | |
list.add("永安街阿生哥柳州螺蛳粉"); | |
/* | |
获取 Stream 流对象,所有 Collection 接口的实现类都可以直接获取 Stream 流 | |
Stream<T> stream (); | |
*/ | |
Stream<String> stream = list.stream(); | |
stream.sorted((s1, s2) -> s1.length() - s2.length()) // 按照字符串字符个数升序排序 | |
.skip(2) // 跳过前两个数据 | |
.limit(15) // 限制当前 Stream 中最多 15 个数据 | |
.filter(s -> s.length() > 5) // 仅保留字符串字符个数 > 5 | |
.distinct() // 去重数据 | |
.forEach(System.out::println); // 展示 | |
} | |
} |
# 2.3 Stream 相关方法
Stream 流获取方法
Stream<T> stream(); | |
集合对象获取,Collection 集合在 JDK 1.8 版本以上利用 default 默认方法提供 Stream 流对象获取方法 | |
public static <T> Stream<T> stream(T[] t); | |
Arrays 工具类方法,调用 stream 方法,将数组处理为 Stream 流方式,对应的泛型有数组类型明确。 |
Stream 过程方法 / 中间方法
Stream<T> skip(long n); | |
Stream 对应的数据内容,跳过指定 n 个,删除前 n 个数据 | |
Stream<T> limit(long n); | |
Stream 对应的数据内容,限制数据总长度为 n 个 | |
Stream<T> filter(Predicate<T> filter); | |
Stream 流过滤限制方法,需要提供 Predicate 过滤器接口实现方式,过滤器接口核心方法 | |
boolean test(T t); | |
满足条件数据可以存储到 Stream 中 | |
Stream<T> sorted(); | |
Stream 流存储元素排序方法,要求存储元素内容必须有自然顺序,或者是 Comparable 接口实现类,并且排序默认 | |
为升序形式。 | |
Stream<T> sorted(Comparator<T> com); | |
Stream 流存储元素排序方法,通过 Comparator 比较器接口提供对应的比较方式。要求实现的方法 | |
int compare(T o1, T o2); | |
Stream<T> distinct(); | |
Stream 流存储元素去重操作。通过存储元素的 equals 方法判断是否存在其他相同元素。 | |
Stream<R> map(Function<T, R> fun); | |
Stream 流存储元素转换类型方法,可以按照 Function 接口提供的规则,将目前存储的 T 类型数据转换为目标 R 类 | |
型数据,Function 接口要求实现的方法 | |
R apply(T t); |
Stream 终结方法 / 终止方法
- 以下方法返回值类型都不是 Stream 流
- 以下方法一旦执行,当前 Stream 直接被关闭。无法继续使用。
long count(); | |
Stream 流中对应的元素有多少个 | |
void forEach(Consumer<T> handle); | |
Stream 流中对应的元素,按照 Consumer 接口规则进行逐一处理,最终处理方式,Consumer 接口要求完成的方法 | |
void accept(T t); | |
List<T> collect(Collectors.toList()) | |
Stream 中存储的元素内容转换为 List 集合返回 | |
Set<T> collect(Collectors.toSet()) | |
Stream 中存储的元素内容转换为 Set 集合返回 |
# 2.4 skip and limit
/* | |
Stream 流操作的数据内容,对原数据内容没有任何的影响!!! | |
*/ | |
list.stream() | |
.skip(2) | |
.limit(15) | |
.forEach(System.out::println); |
# 2.5 filter and distinct
list.stream() | |
.filter(s -> s.contains("羊") || s.length() > 5 || s.contains("包子")) | |
.distinct() | |
.forEach(System.out::println); |
# 2.6 sorted
package com.qfedu.b_stream; | |
import com.qfedu.a_functionalInterface.Student; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.stream.Stream; | |
/** | |
* @author Anonymous 2023/8/10 15:52 | |
*/ | |
public class Demo4 { | |
public static void main(String[] args) { | |
ArrayList<String> list = new ArrayList<>(); | |
list.add("ABC"); | |
list.add("B"); | |
list.add("C"); | |
list.add("DDD"); | |
list.add("BC"); | |
list.add("CB"); | |
list.add("A"); | |
list.add("AB"); | |
list.add("BCD"); | |
list.add("E"); | |
list.add("D"); | |
list.add("DA"); | |
/* | |
sorted () 要求 Stream 流存储对应的数据类型必须有自然顺序或者 | |
比较方式 Comparable 接口 | |
*/ | |
list.stream() | |
.sorted() | |
.forEach(System.out::println); | |
System.out.println(); | |
Student[] students = { | |
new Student(4, "朱SD", 12, true), | |
new Student(6, "朱SD", 9, true), | |
new Student(7, "朱SD", 1, true), | |
new Student(1, "朱某", 16, true), | |
new Student(3, "朱SD", 15, true), | |
new Student(2, "某鸽", 13, true), | |
new Student(5, "朱SD", 10, true), | |
new Student(8, "朱·SingleDog·鸽", 3, true) | |
}; | |
Stream<Student> stream = Arrays.stream(students); | |
/* | |
sorted (Comparator<T> com); | |
int compare (T o1, T o2); | |
T 对应 Student 类型 | |
*/ | |
stream.sorted((s1, s2) -> s2.getAge() - s1.getAge()) | |
.forEach(System.out::println); | |
} | |
} |
# 2.7 map
package com.qfedu.b_stream; | |
import com.qfedu.a_functionalInterface.Student; | |
import com.qfedu.util.BeanUtils; | |
import java.util.Arrays; | |
import java.util.stream.Stream; | |
/** | |
* @author Anonymous 2023/8/10 16:06 | |
*/ | |
public class Demo5 { | |
public static void main(String[] args) { | |
String[] arr = { | |
"id=1,name=张三,age=16,gender=false", | |
"id=2,name=朱某,age=16,gender=true", | |
"id=3,name=朱某,age=6,gender=true", | |
"id=4,name=朱某,age=8,gender=true", | |
"id=5,name=朱某,age=10,gender=true", | |
}; | |
Stream<String> stream = Arrays.stream(arr); | |
/* | |
方法引用 JDK1.8 以上有效 | |
parseStudent 方法 | |
参数是 String 类型 | |
返回值为 Student 类型 | |
满足当前 map 方法所需 | |
parseStudent 是 static 修饰,通过类名调用 | |
Demo5::parseStudent | |
*/ | |
Stream<Student> studentStream = stream.map(Demo5::parseStudent); | |
studentStream.forEach(System.out::println); | |
} | |
public static Student parseStudent(String str) { | |
//split= {"id=1", "name = 张三", "age=16", "gender=false"} | |
String[] split = str.split(","); | |
Student student = new Student(); | |
for (int i = 0; i < split.length; i++) { | |
// 找到 = 对应的下标位置 | |
int index = split[i].indexOf('='); | |
try { | |
/* | |
给予符合 JavaBean 规定对象,指定成员变量名称,赋值数据 | |
@param bean 符合 JavaBean 规范对象 | |
@param fieldName 指定成员变量名称 | |
@param value 赋值使用的 String 类型数据 | |
@throws NoSuchFieldException 没有指定成员变量异常 | |
@throws IllegalAccessException 非法权限访问异常 | |
public static void setProperty (Object bean, String fieldName, String value) | |
*/ | |
BeanUtils.setProperty(student, split[i].substring(0, index), split[i].substring(index + 1)); | |
} catch (NoSuchFieldException | IllegalAccessException e) { | |
e.printStackTrace(); | |
} | |
} | |
return student; | |
} | |
} |
# 2.8 count,forEach,collect
以下 Stream 流方法都是终结方法,调用会导致 Stream 流关闭
package com.qfedu.b_stream; | |
import com.qfedu.a_functionalInterface.Student; | |
import java.util.Arrays; | |
import java.util.List; | |
import java.util.stream.Collectors; | |
import java.util.stream.Stream; | |
/** | |
* @author Anonymous 2023/8/10 16:34 | |
*/ | |
public class Demo6 { | |
public static void main(String[] args) { | |
String[] arr = { | |
"id=1,name=张三,age=16,gender=false", | |
"id=2,name=朱某,age=16,gender=true", | |
"id=3,name=朱某,age=6,gender=true", | |
"id=4,name=朱某,age=8,gender=true", | |
"id=5,name=朱某,age=10,gender=true", | |
}; | |
Stream<String> stream = Arrays.stream(arr); | |
long count = stream.count(); | |
System.out.println(count); | |
stream = Arrays.stream(arr); | |
/* | |
Consumer | |
forEach (Consumer<T> handle) | |
void accept (T t); | |
方法引用分析 | |
void println (String x) | |
调用者是 System.out,利用方法引用调用当前方法 | |
*/ | |
stream.forEach(System.out::println); | |
stream = Arrays.stream(arr); | |
List<Student> list = stream.map(Demo5::parseStudent) | |
.collect(Collectors.toList()); | |
list.forEach(System.out::println); | |
/* | |
补充方法: | |
public static <T> List<T> asList (T [] arr); | |
Arrays 数组工具类方法,可以将指定类型数据转换为指定存储类型的 List 集合 | |
*/ | |
List<String> strings = Arrays.asList(arr); | |
} | |
} |
# 3. 方法引用
# 3.1 使用前提
- 方法参数必须是有一个函数式接口
- 引用的方法需要明确方法的调用者和方法参数返回值情况是否满足函数式接口约束的方法所需。
- 重点
- 方法调用者【类名】【类对象】
- 方法的参数列表和返回值是否满足函数式接口约束方法情况。
- :: 方法引用特殊符号
# 3.2 案例
Function<T, R> 转换器接口
package com.qfedu.c_fr; | |
import java.util.Arrays; | |
import java.util.HashMap; | |
import java.util.Map; | |
import java.util.function.Function; | |
/** | |
* Function<T, R> 转换器接口 | |
* 字符串转 Map<String, Object> | |
* | |
* @author Anonymous 20 23/8/10 16:49 | |
*/ | |
public class Demo1 { | |
public static void main(String[] args) { | |
String str = "id=1,name=单身鸽,age=16,gender=true,address=学府花/佳/家/嘉园"; | |
/* | |
重点: | |
Function<String, Map<String, Object>> ===> Map<String, Object> apply (String str); | |
根据当前接口所需方法情况选择目标方法 | |
public static Map<String, Object> toMap (String str) | |
参数和返回值类型都满足接口所需 | |
当前 toMap 方法为 static 修饰方法,需要通过类名调用 | |
方法引用: | |
Demo1::toMap | |
调用者::方法名 | |
*/ | |
Map<String, Object> map = parseMap(str, Demo1::toMap); | |
map.forEach((key, value) -> System.out.println(key + ":" + value)); | |
} | |
public static Map<String, Object> parseMap(String str, Function<String, Map<String, Object>> fun) { | |
return fun.apply(str); | |
} | |
public static Map<String, Object> toMap(String str) { | |
String[] split = str.split(","); | |
/* | |
split.length 直接明确当前 Map 双边队列键值对个数,和当前数组容量一致 | |
不多不少刚刚好!!! | |
*/ | |
Map<String, Object> map = new HashMap<>(split.length); | |
Arrays.stream(split).forEach(s -> map.put( | |
s.substring(0, s.indexOf("=")), | |
s.substring(s.indexOf("=") + 1))); | |
return map; | |
} | |
} |
·