java基础知识(个人总结)

Java 基础语法

注释

作用 -- 对程序中的代码进行必要的文字说明,被注释的内容不参与程序的编译和运行; 在 java 中注释有三类

① 单行注释:使用‘//’,对符号后面整行的内容进行注释,缺点是不能换行
② 多行注释:使用‘/*···*/’,对两个星号之间的内容进行注释,被注释的内容可以换行
③ 文档注释:使用‘/**···*/’,平时可以作为多行注释使用,但是在将类编译成帮助文档时,文档注释中的
内容会在帮助文档中生成,其他注释的内部不会在文档中生成
要求:重要的地方必须要有注释内容,20%的代码是注释内容

常量

定义 -- 在程序执行的过程中,值保持不变的量

分类一 -- 根据表现形式不同,java 将常量分为两类
① 字面值常量:所见即所得,看见的就是常量本身
② 符号型常量:一个常量名,具体的值看不见,只有在程序运行时才知道;
区别:字面值常量直接写,符号型常量需要事先定义
分类二 -- 根据常量值的数据类型,java 将常量分为
① 整型常量:所有的整数,1,2,3,4,5
② 浮点型常量:所有的小数,1.2,2.1,4.0
③ 字符窜常量:所有使用双引号引起来的字符序列,""," ","hellworld"
④ 字符常量:所有用单引号引起来的单个字符,'a','我'
⑤ 布尔常量:true,false
⑥ null常量:通常用于引用数据类型的复制,表示的是没有给变量分配内存空间

标识符

定义 -- 用来给程序中的变量、常量、方法、数组.... 等等进行命令的字符序列 标识符的命名规则

① 可以由字母、数字、_、$ 组成
② 不能以数字开头
③ 组成的标识符不能是关键字(当前jdk版本被赋予了特殊功能或者含义的单词)或者保留字(当前版本没有
被赋予特殊功能,但是在升级后的版本可能会被赋予特殊功能)
④ 可以是任何国家正在使用的语言文字,不建议使用
但是,在真正进行命名时,除了上诉的规则以外,我们还要坚持一个核心原则:见明知意;在该原则下针对不
同的结构命名的方式略有不同;
i. 小驼峰式命名:一个单词时所有字母小写,2个或者2个以上单词时,从第二个单词开始首字母大写,其他
字母小写;适用于变量、方法的命名
ii. 大驼峰式命名:所有单词的首字母都大写,其余字母小写,适用于类、接口、枚举、注解的命名
iii. 全大写:所有的英文字母都大写,适用于符号型常量的命名
iiii. 使用‘_’作为连接符命名:可用于替换驼峰式命名,使用下划线将单词隔开

数据类型

java 是一个强类型的语言,在使用内存时必须事先声明需要的内存大小,数据类型就是用来开辟内存空 间 根据需求不同,需要的数据类型也不尽一样,java 提供了两类数据类型,一是基本数据类型,二是引用 数据类型 基本数据类型又分为四类八种,除了基本数据以外的都成为引用数据类型

基本数据类型分类
① 整型:java 默认使用 int 来表示整数的类型,只要在程序中看见了整数字面值常量,那这个值的类型就
是 int
byte 1个字节 -128 --- 127
short 2个字节 -2^15 --- 2^15 - 1
int 4个字节 -2^31 --- 2^31 - 1
long 8个字节 -2^63 --- 2^63 - 1
② 浮点型:java 默认使用 double 来表示浮点数的类型
float 4个字节 1.4E-45 --- 3.4028235E38
double 8个字节 4.9E-324 --- 1.7976931348623157E308
③ 字符型
char 2个字节 -2^15 --- 2^15 - 1
字符型和整型之间可以自由转换,并且是自动转换
④ 布尔类型
boolean 未知 true/false
引用数据类型
引用数据类型是由大量的基本数据类型和其他成员组合而成的复杂的数据类型,表示的是内存中某个地址;
简单的把类、数组、枚举、接口、注解...等都归纳到引用数据类型中,换言之只要不是上述的基本数据类
型,都可以称为引用数据类型;

基本数据类型之间的转换(排除boolean)

自动转换
系统自己能够实现数据的转换,无需变成人员做处理;
自动转换通常发生在从小类型的数据转换成大类型的数据;
强制转换
系统无法自动进行数据类型的转换,必须要由编程人员手动处理;
强制转换通常发生在从大类型的数据转换成小类型的数据;
强制转换格式:数据类型 变量 = (数据类型)值
注意:两个数据类型必须是同一个
整型和字符型的转换
整型和字符的转换,是按照当前的编码格式实现自动转换的,不同的编码格式前面 128 个都是一样的,都是
参考的 Ascll 码

其中,非打印字符熟悉一下即可,打印字符尽量记忆下来;
打印字符中,数字,大小写英文字母,都是连续的,记住数字、大小写字母的起始值与对应的十进制,二进制
与十六进制是可以相互转换的,后面再讲;
数据类型转换(运算)的潜在规则
当数据的类型都是 int 或者 int 以下的,最终的转换结果会向 int 类型进行提升;
当数据的类型有 float 或者 double 时,最终的转换结果会向更高的类型进行提升;

编程中常用的几种编码和字符集

编码
把字符转换成二进制进行储存的过程
解码
把二进制转换成字符的过程
中文乱码
当编码和解码使用的规则不同就会出现,英文字符、数字、特殊符号都是按照 Ascll 码进行,所以不会出现
乱码;
常用的字符集有 GBK、Unicode,字符集是由若干字符组合而成,针对其中的字符才有编码格式的存在;
GBK 字符集采用的编码格式就叫 GBK,针对英文字符、数字、特殊符号都是按照 Ascll 码进行,采用一个
字节进行编码,针对中文字符使用2个字节进行编码;
Unicode 字符集采用 UTF-8 编码格式进行字符编码,针对英文字符、数字、特殊符号都是按照 Ascll 码
进行,但是针对中文字符使用3个字节进行编码;

Java 变量

定义 -- 用来描述程序中可以发生变化的量

变量的声明
方式一:声明和赋值同时进行;通常用于局部变量的声明
数据类型 变量名 = 值;
数据类型:可以是基本数据类型,也可以是引用数据类型,根据程序的需求选择合适的数据类型即可
变量名:合法的标识符,做到见明知意
值:可以是变量,可以是字面值常量,也可以是内存地址,也可以是符号型常量
例如:
int age = 18;
String name = "XX";
String sex = "男";
方式二:先声明后赋值;通常用于成员变量的声明
数据类型 变量;
·····
变量 = 值;
例如:
int age;
.....
age = 18;
方式三:同时声明多个同类型变量
数据类型 变量1 = 值,变量2 = 值,变量3....;
变量的使用
① 直接使用变量名代替存储的值
int age = 18;
....
age = 19;
System.out.println(age);
② 没有声明的变量不允许使用
System.out.println(age);JVM
变量的内存理解
因为 java 最终是在虚拟机上被执行的,所有它使用的是虚拟机的内存;虚拟机的内存在生产的时候就已经被
划分好了;包括以下几个区域:
① 栈内存,使用栈结构进行数据临时存储,用来执行 java 代码中的各个方法,按照执行顺序一次进栈,方法
执行完后按照后进先出的原则出栈;
② 堆内存,用来进行大类型的数据储存,像数组、对象;
③ 方法区,用来储存字节码文件以及静态资源
④ 本地方法区,虚拟机底层 c 语言代码的调用
⑤ 程序计数器(指针),控制程序的执行过程
public class Demo{
	public static void main(String[] args){
		int age = 18;
		System.out.println(age); // 结果为 18
		age = 19;
		System.out.println(age); // 结果为 19
	}
}
变量声明的本质
根据数据类型的大小在内存中开辟对应的空间,并且给该空间进行命名,后续就可以使用空间的名称来表示空
间中存放的数据,名字和内存不会发生变化,内存中存放的数据是可以被改变的;
换一句话说就是:变量的声明就是用一个简单不变的名称代表内存中变化的数据

常量的声明

格式
final 数据类型 常量名 = 值;
数据类型:基本数据类型时表示值不允许改变,引用数据类型时表示引用的内存地址不允许改变
常量名:字母全大写,多个单词中间使用'_'隔开
值:必须给

JDK/JRE/JVM

解释名称功能
Java development kit -Java 开发工具JDK提供 java 开发需要的一系列支持,包括 JRE 和 一些可执行程序,以及 JDK 源码、内置数据库等.....
Java runtimeenvironment - Java 运行环境JRE为 java 的字节码运行提供环境,包括 JVM 和一些类库
Java virtual machine -Java 虚拟机JVM真正执行 java 字节码的平台

运算符

注意事项

运算时有浮点型参与,都会自动转换成浮点型。 char类型会自动转换成需要的类型。 字符串相加会变成拼接(注意:“8”是字符串不是字符)。

基本算术运算符

+、-、*、/、%

自增自减的运算

分为独立和非独立

独立:i++和++i是一样的

非独立:比如`System.out.println(i++)`,先输出再变化,很明显,i在前面,++在后面,所以是这样的结果,++i则是先变化再输出。
int x=5;y=6;int z=x++*--x+--y*++x-++y*x--;求x,y,z的值

x++ = 5, x=6 先显示了再加的,但是确实是加了的

--x=6-1=5, x=5 先减了再显示的

--y=5, y=5

++x=6, x=6

++y=6, y=6

x--=6, x=5

z=25+30-36=19,

x=5,y=6.

计算另外一个

y++=5,y=6

++y=7,y=7

x--=6,x=5

2X5X5+5X6+5X2-7X6=50+30+10-42=48

比较运算符

结果为boolean型:true false

①比较大小:>、<、>=、<=,主要用于基本数据类型中数字的比较,不能用于引用数据类型的比较
②比较是否相等:==、!=,可以是基本数据类型,也可以是引用数据类型的比较;基本数据类型时比较数值,引用数据类型比较地址(所以判断列表相等不能用==)
赋值运算符

--将具体的值赋给某个变量的操作符

①基础的运算符:=,将符号右边的值或者内存地址赋给左边的变量
②扩展的赋值运算符:+=、-=、*=、/=、%=,先进行符号两边变量和值的运算,再赋值
比如:int i = 1;i-=5;
意思为i=i-5=-4
逻辑运算符

--针对符号两边的值进行逻辑运算,如果符号两边是boolean类型的表达式,运算的结果为boolean类型;如果符号两边为整数,运算结果为整数。

①逻辑与:&&、&
i.运算规则:同真为真,否则为假
ii.在运算时,&&与&规则是一致的,但是过程略有不同:一个&符号时需要将符号两边的表达式都运算结束,才能有最终的结果;两个&符号时,只要遇到某一个表达式的结果为false就不再继续运算,直接返回false
iii.两个&&符号只能用于boolean类型表达式的运算,一个符号既可以运算boolean类型表达式,也可以运算整数,运算整数时又叫做按位与;
②逻辑或:||、|(同假为假,否则为真;二点和三点与逻辑与类似,只是false改为true、按位与改为按位或)
③逻辑非:!,对符号后面表达式的运算结果进行取反
数制(题外话:一点基础)
①二进制
②八进制
③十进制
④十六进制
转换:
①十进制转其他进制:二分法:除进制名取余数,余数反着记录;用拆分法更快(对二进制,看作基数的几次方,)
②其他进制转十进制:进制名的位次方*位上面的数,相加;
快速方法(当位数比较多的时候):减一法,使用特殊值的二进制(高一位各位全为1的值)减去对应位的1
例如:0101 0111=127 -8 -32=87
负数:1111 0001=-1-14=-15(除符号位全为一时为-1)
③2进制与8进制互转:
从右往左3位一组,不够位时补位
④2进制与16进制互转:
4位一组(注意abcd……)
0x(16进制) 0b(二进制)  0(八进制)

原码、反码、补码(只有补码是真实的,其他两个只是计算负数补码的桥梁,正数三码都一样)

计算-64的8位二进制

0100 0000

1100 0000

1011 1111

1100 0000

计算中关于数值的运算都是使用补码进行的,而且在运算时一般都是做加法运算;负数的补码就是为了解决在正负数运算时出现的问题:

1+(-1)=0001+1111=0000=0

32 -2 =0010 0000-0000 0010=0001 1110

-50 0011 0010

1100 1101

1100 1110

减法不好借,所以将减法化成加法 (要用补码哦)

位运算符

--针对数字的二进制补码进行移位或者按位操作

①右移:>>,每右移动一位,数值变成原来的一半,不够整除时向下取整
例如:10>>3 = 1
原理:0b00001010>>3 ---->0b0000 0001
例如:-5>>1 = -3
原理:0b1111 1011 >>1 ---->0b1111 1101 --->-3
②左移:<<,每左移一位,数值变成原来的两倍,注意不要超过当前系统默认的范围
1 << 32 --->1*2^32
③无符号右移:>>>,作用与右移操作一致,但是不用于操作负数,效率要比右移高很多
④按位与:&,同真为真,否则为假;将数字转换2进制,再对位进行比较,全为1即是真,否则为假;为真时用1表示,为假时用0表示
4&5 = 0b0000 0100  
    & 0b0000 0101 
    = 0b0000 0100
⑤按位或:|,全假为假,否则为真,全为0即是假,否则为真。
4|5 = 0b0000 0100  
    | 0b0000 0101 
    = 0b0000 0101
⑥按位异或:^,相同为假,不同为真
4^5 = 0b0000 0100  
    ^ 0b0000 0101 
    = 0b0000 0001

底层代码都用它,平时用的概率不大

三元(目)运算符

--当程序再执行时遇到有选择判断的情况时,可以使用三目运算实现判断

格式:表达式1?表达式2:表达式3
运算流程:
i:先进行表达式1的运算,表达式1必须是Boolean表达式
ii:如果表达式1结果为true,立即执行表达式2,并且将表达式2的结果作为三元运算的结果;如果表达式1结果为false,立即执行表达式3,并且将表达式3的结果作为三元运算的结果
注意:
①表达式2和表达式3的运算结果必须是同类型的数据
②三元运算是一种运算方式,得到的是一个具体的数据,不能独立存在于程序中:作为参数或赋值给其他变量

键盘录入

--在程序执行的过程中实现数据的动态获取,让数据的来源变得更便捷;录入的功能是由jdk给提供的一个工具类,我们只需要在自己的程序里引入该工具类就能使用录入的方法。

①引入工具类,创建工具类的对象;
②使用类中定义好的方法进行数据的录入与获取;
③对录入的数据进行需要的操作。
import java.util.Scanner;
public class HelloWorld {
    //程序执行入口
    public static void main(String[] args) {
        System.out.println("请录入:");
        Scanner sc = new Scanner(System.in);//标准的字节输入流
        //整数录入
        int i = sc.nextInt();
        int j = sc.nextInt(); //批量录入
        System.out.println(i);
        //字符串录入
        String next = sc.next();//支持批量录入
        System.out.println(next);
        //字符串录入方法2,不支持批量录入
        String nextLine = sc.nextLine();
        System.out.println(nextLine);
        }
    }
注意:next/nextInt 是通过空格进行拆分,以回车来判断是否录入结束;nextLine通过回车进行判断。

流程控制

--控制代码的执行顺序,默认执行流程从上往下,从左往右,如果执行的中途有分支或者循环的操作,就需要借助分支或者循环的结构来实现

分支结构
  描述的是当程序执行到某个状态时,会出现多种不同的结果,根据结果就需要使用分支来进行表示,要将所有可能出现的结果都表示出来,不能遗漏,否则一旦出现不能表示的情况,程序就无法正常执行;
  分支结构明显的缺陷是写的代码多,真正执行是只会执行其中的某一种情况描述。
  java语言提供了2种语句来实现分支结构的描述:
  ①if语句
  ②switch语句
if语句
单条件判断
格式:if(条件表达式){..代码块..}
双条件判断
格式:if(条件表达式){..代码块..}else{..代码块..}
特点:有且仅有一个代码块会被执行
多条件判断

结构

if(条件表达式1){
	代码块1;
}
else if(条件表达式2){
	代码块2;
}
……
else if(条件表达式n){
	代码块n;
}
else{
	代码块n+1;
}
注意:
①每一个条件表达式都必须是boolean类型表达式;
②每一次进行条件表达式运算时,都会将改表达式运算之前所有的表达式的不成立情况作为已知条件;
③有且仅有一个代码块会被执行,在代码块执行后,直接结束;
switch语句

结构

switch(变量或变量参与的表达式){
	case 常量1:
		代码块1;
		break;
	case 常量2:
		代码块2;
		break;
	……
	case 常量n:
		代码块n;
		break;
    default:
    	代码块:
    	break;
注意:
①switch结构中的变量必须是byte/short/int/char/String/枚举类型;
②case 表示选项,在程序执行中,switch结构中的变量优先与case从上往下进行比较,一旦某个case选项后面的常量值与变量相等,那么程序进入到当前case内部执行代码块;
③break表示强制推出该结构,程序当前结构立即结束;
④default 表示默认选项,当所有的case选项都不匹配的时候,执行默认选项;
⑤case 穿透,当进入到case内部执行代码块时没有遇见break语句,程序会跳过变量与常量的匹配,继续进入到下一个case内部执行代码块,一直到遇见break或者switch结构的右大括号才会结束;
⑥穿透不仅会发生在case与case之间,与default也会出现穿透

执行流程

① 第一步计算变量或者变量表达式的值:紧接着和最上面的一个case进行比较;
② 当变量值和常量值不等,继续往下找第二个case常量进行匹配;
③ 当变量值和常量相等,进入case内部执行代码块;根据内部是否存在break语句决定是否出现case穿透
④如果所有的case常量和变量或者变量表达式的结果都不相等,那么程序会检查是否存在default,如果有就执行内部代码,判断是否存在break,如果不存在还是会出现case穿透(写在最后面结束,写在前面穿入到break);如果没有程序结束;

循环结构
  当程序执行到某个状态时,针对某一段代码或者功能时需要进行重复性的操作,就可以使用该结构来快速实现代码的复用;不用编程人员重复性地编写相同的代码;
  所以循环结构最明显的优点是提高代码的复用性,缺点是不易理解。
  ①for循环
  ②while循环
  ③do...while循环
  对于新手,当你知道循环多少次时,建议使用for循环,反之使用while循环;
  熟悉之后,用谁都可以
for循环

结构一

for(数据类型 变量 = 值;boolean条件表达式;变量的改变方式){
	循环体;
}
数据类型:正常来说都是使用 int 类型。

结构二

for(;;){
	循环体;
}
死循环,循环体一直执行,永不停止。

结构三-增强for循环(foreach)

for(数据类型 变量:容器){
	循环体;
}
专用于容器的遍历操作,比如数组,集合等容器;
遍历-指挨个的进行容=容器内元素的访问;
数据类型:保持跟容器内数据的类型一致
while循环

结构

初始化变量;
while(条件表达式){
	循环体;
}
条件表达式:boolean类型,可以直接写false/true;写true表示死循环结构,通常在内部会有break语句实现跳出循环;
循环体:需要重复执行的代码块
do...while循环

结构

初始化变量:
do{
	循环体;
	变量的改变;
}while(条件表达式);
该结构先进行循环体,再判断循环条件,条件成立继续循环,条件不成立循环结束;所以do……while无论如何都会执行一次循环体;
与while的区别只在于第一次循环时条件是否成立造成的结果。
for/while/do...while的区别
①相同点
i、都能够实现循环操作
ii、条件表达式都必须是Boolean、或者是true/false
②不同点
i、for/while是先判断条件,再执行循环体,有可能一次都不循环,do...while是先执行循环体,再判断条件,至少执行一次循环体。
循环跳转语句
循环跳转语句指再循环执行到某个状态时,强制结束循环的操作:结束的方式有两种:
①break语句,从内部破坏循环结构,使得循环提前结束,
②使用continue语句,跳过当次循环执行,进入下次循环条件判断,条件成立继续循环,不成立循环结束。
嵌套循环

--循环内部的循环体又是一个循环结构

for循环嵌套
for(初始化变量1;条件表达式1;变量改变1){
	for(初始化变量2;条件表达式2;变量改变2){
		循环体;
	}
}
执行流程:
①初始化变量1
②运算表达式1
③表达式1为false,整个循环结束
④表达式1为true,初始化变量2
⑤运算表达式2
⑥表达式2为false,内部循环结束,进行变量1的改变,回到第②步
⑦表达式二为true,执行内部循环体,进行变量2的改变,回到第⑤步
每外部循环执行一次,内部循环将满足条件的所有情况都执行一遍;
嵌套循环还可以作为二维层面图形的打印手段,用外层循环表示二维层面的行,内层循环表示列;

二维图形打印

二维 -- 只通过x、y轴就能描述的平面
外层循环:行(x)
内层循环:列(y)

数组

定义--表示一个能够存放多个数据的容器,数组在声明时需要指定大小和类型,而且一旦声明不允许修改;

数组的声明
①静态初始化 -- 只声明数组的容量和类型
数据类型[] 数组名 = new 数据类型[size];
数据类型:前后数据类型保持一致,可以是基本数据类型也可以是引用数据类型
[]:表示数据容器,一个表示一维数组,两个表示二维数组;
数组名:合法的标识符
=:赋值,将数组的内存地址交给数组名来引用
new:关键字,用于开辟堆内存空间,每次使用new都会在堆内存开辟一个新的内存空间
size:表示容器的大小,允许存放的数据量

②动态初始化--既声明容器的大小和类型,也将存放的数据表示出来
数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3,....};
简化:数据类型[] 数组名 = 数据类型{元素1,元素2,元素3,...};
元素:存放在数组内部的数据
数组的详细介绍
数组是存放在堆内存中的,数组名只是用来引用数组所在的内存地址,并不能表示内部的数据,所以直接打印数组名跟打印变量时结果不同;打印数组名得到的是当前数组所在的内存地址,打印变量得到的是数据;

内存地址:[I@1db9742]
[ ---表示一堆数组
I ---表示数组类型
@ ---表示连接符,用来连接真正的内存地址
1db9732 --- 16进制数表示的内存地址

如果要想获取数组内部的数据,就需要按照数组容器制定的规则;
--数组名[元素索引]方式进行容器内部元素的获取;
数组的长度:获取:数组名.length
数组的基本操作
① 元素的获取 -- 数组名[元素索引];
② 修改元素 -- 数组名[元素索引] = 值;
数组的遍历
遍历:按照要求挨个访问容器内部的元素
方式一:普通的for循环实现
方式二:增强for循环
for(数据类型 变量:数组名){
		变量代表元素;
}
数据类型须与数组的数据类型一致;而且这种方式无须索引
只是遍历,增强for好用,但是如果要操作,普通for好用
数组最值查找

假设法

取数组内任意一个(第一个或者最后一个)元素作为最大/小值,遍历数组,将遍历出来的每一个元素和假设的最大/最小值进行比较,如果元素比假设的最大(小)值还大(小),就将元素值重新赋值给假设的最大(小)值变量;当数组遍历借宿,最值查询就结束;最大(小)值就会存在假设的变量中。
数组内元素交换
最大(小)值元素交换位置
① 找最值
② 定义中间变量交换
直接输出数组:Arrays.toString(arr),char[] b
数组反转
将数组内部的元素顺序反过来
① 声明两个索引x=0,y=length-1
② 在x<y或者x<length/2时换位
for(int i=0,j=arr.length-1;i<j;i++,j--){
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
或者
for(int i=0;i<arr.length/2;i++){
    int temp = arr[i];
    arr[i] = arr[arr.length-i-1];
    arr[arr.length-i-1] = temp;
}
数组排序
冒泡排序
相邻的两个元素比较大小,按照规则(升序/降序)进行元素位置交换,每一趟冒泡排序是将参与排序的最大(小)值放到数组的最后面;
一旦元素放在了最佳位置,下次排序就不再参与,来提高排序效率;
		int count = 0;
        while(count<arr.length){
            boolean flag = true;
            for(int i=0;i<arr.length-1-count;i++){ //减去count就是为了减去最后一位
                if(arr[i] > arr[i+1]) {
                    int temp = arr[i];
                    arr[i] = arr[i + 1];
                    arr[i + 1] = temp;
                    flag = false;
                }
            }
            if(flag){
                break;
            }
     }
快速排序(升序)
① 在数组内随机选择一个元素(第一个或者最后一个)作为基准数povit
② 增设两个索引x/y,x从前往后移动,y从后往前移动
③ 当x指针指向的元素比基准数大的时候停下,y指针指向的元素比基准数小的时候停下
④ 当两个指针都停止后,比较x/y的值,如果x<y:交换x/y指针所指的元素,再回到第③步
⑤ 当两个指针都停止后,如果x>=y ,就交换基准数和y指针所指的元素,到此一趟快速排序结束
⑥ 此时基准数就处于最佳位置,不再参与后续排序;紧接着将原数组按基准数划分为两个数组,比基准数小的处于一个数组,比基准数大的属于一个数组
⑦ 对分开的两个数组进行递归操作
数组内元素查找
在数组中查看是否包含某个指定的元素,如果有,返回该元素在数组中第一次出现的位置,如果没有,那就返回-1:
        for (int i=0;i<arr.length;i++){
            if(arr[i]==n){
                System.out.println(i);
                flag=false;
                break;
            }
        }
        if(flag){
            System.out.println(-1);
        }
        int x = -1;
        for (int i=0;i<arr.length;i++){
            if(arr[i]==n){
                x=i;
                break;
            }
        }

控制输出的思路:打印2~100之间所有的质数(只能被1和自己整除)

for(int i =2;i<=100;i++){
            boolean flag=true;
            for(int j=2;j<i;j++){
                if(i%j==0){
                    flag=false;
                }
            }
            if(flag){
                System.out.println(i);
            }
        }
数组的内存理解

单个数组内存

① 将字节码文件加载到方法区;
② 将main方法压入栈内存,在栈内存中声明数组变量
③ 在堆内存开辟指定大小的内存空间,并进行默认初始化,然后将内存地址赋值给初始变量
④ 使用数组变量能够对内存地址中的数据进行操作:[索引]

两个数组内存

多个数组存在时,如果每个变量引用的地址不相同,使用变量操作数组时不会影响到其他数组

多个引用指向同一个地址

当多个变量同时引用一个内存地址时,某一个变量操作了内存,其他变量在访问时,访问到的是操作之后的内存
数组转字符串
不能直接转,
    
要不遍历数组加入到字符串中或者利用StringBuff、StringBuilder
for (int i = 0; i < array.length; i++) {
    z += array[i];
}    
    
StringBuilder sb = new StringBuilder();
for (int i = 0; i < split.length; i++) {
    sb.append(split[i]).append(",");
}
sb.deleteCharAt(sb.length()-1);
datas =  sb.toString();

要不在转了之后去除首尾方括号
字符串 = Arrays.toString(数组).substring(1,数组长度-1);

方法(也就是c的函数)

--一段具备某个特殊功能的代码片段,可以被程序重复使用,可以提高代码的复用性;在程序中通过使用{}将某段括起来,然后加上方法名、返回值类型、访问权限修饰词表进行方法的定义;

方法的声明
权限访问修饰词 返回值类型 方法名(参数列表){
	代码片段;
	return 语句;
}

权限访问修饰词:控制方法被访问的权限,当前建议写成 public static(这个不叫访问权限修饰词,只是放在这里):
① public表示公开的、公共的,更容易被访问,static 表示静态的,无需类对象就可直接访问方法;
② 外界访问时可以通过类名.方法名访问;内部访问时可直接写方法名;

返回值类型:表示方法代码片段在执行过后是否有数据返回给调用者,如果需要有数据返回,返回值类型就要声明返回数据的类型;如果不需要数据返回就写成void;

方法名:合法的标识符;

参数列表:声明方法在执行时是否需要外界传入数据:

功能代码块:方法体,这段代码块要做的事;

return 语句:void与其他返回值类型的区别。

注意事项

① 不要在方法内部声明方法
② 按照声明传参及使用
③ 方法的内部可以调用方法
方法的调用
① 无返回值:直接使用
② 有返回值:定义变量接收或者直接打印
递归
递:一层一层向内递进至条件不成立
归:归纳,将递进的结果从内往外一层层地总结

总结

① 定义方法
② 找终止条件
③ 编写计算的公式,规则

注意

递归次数不要太多,如果需要递归很多次,在不出现栈内存溢出情况下可以使用,一旦出现栈内存溢出就换成循环结构来计算
重载
① 含义
在同一个类中将多个具有相同功能的方法名,通过制定不同的参数列表来细微的进行区分;
② 好处
让编程人员在声明方法时,减少方法名的使用
③ 案例
system.out.println()
④ 方法重载的三要素
i. 重载只能发生在同一个类中
ii. 多个方法具有相同的名称
iii. 相同名称的方法,参数列表必须不同(个数、类型、顺序三者起码有一个满足不同)
⑤ 利用方法重载,定义getSum方法可以实现两个整数和以及两个浮点数和的计算
方法:值传递和引用传递(传入值副本或者地址副本)
方法在声明的时候会指定参数列表,此时参数列表称为形参;
方法在调用时会产生真实数据,此时参数被称为实参;
实参和形参会发生数据的传递,简单把基本数据类型的传递看作为值传递;引用数据类型的传递看作为引用传递。

在值传递时,方法只是使用了实参的具体值,无法修改实参;(不同的栈内存,只是传递数据)
引用传递时,方法内部允许修改实参对象的数据。(比如tmd列表,传递的是地址;string类型改不了(不管怎么改都是这样,即使改地址),在实体类中可以)
设引用类型 A 和 B,地址分别是 a 和 b
传入方法中,对于参数为 x,y,此时x,y中存入的分别是 A 和 B 地址的副本
改变 x 地址中的值,则 A 的值也会改变,因为 A 与 x 中的地址是一样的
改变 y 的地址,此时副本的地址变了。但是 B 的地址仍然是 b ,所以 B 的值不会改变
方法的可变参数
可变参数是在jdk1.5版本的新增的特性,是可以将一个方法的多个同类型参数使用可变参数来定义,要求一个方法只能存在一个可变参数,并且可变参数要在最后定义;
可变参数是指参数的数量允许是0个或者多个,可变参数的底层是由数组维护的
格式:
访问权限修饰词 返回值类型 方法名(数据类型... 变量){
}
案例:声明一个方法计算2个整数的和
public static void getSum(int... arg){
    int sum = 0;
    for(int i = 0;i<arg.length;i++){
        sum += arg[i];
    }
    System.out.prinln(sum);
}

int... arg 即可变参数

二、Java:面向对象(7.18起)

① 面向对象编程思想
② 类与对象
③ 面向对象的三大特征
④ 抽象类
⑤ 接口
⑥ 设计模式
⑦ 函数式接口
⑧ lambda表达式(对函数式接口编程),匿名内部类
⑨ 关键字 this/super/static

注意:在每个点中会有扩展,而这个扩展不一定是属于该点的内容

① 面向对象的编程思想

面向过程:不管事情的难易程度,每一步都需要自己去完成,强调步骤的编程思想;
面向对象:通常只需要完成一些简单的步骤,把复杂的步骤交给某个对象来完成,强调的是对象的使用;
面向对象底层是基于面向过程的,面向对象在思想上是一种提升,由面向过程的执行者变成指挥者;

② 类与对象

面向对象基于类与对象来实现的,通过创建某个类的对象然后去使用对象的方法来帮助我们完成某个具体的功能。

什么是类? 什么是对象? 类与对象的关系?

类是具有某个共性的抽象产物,是一个抽象的、不具体的概念;

类的声明使用关键字class,内部通常伴随有属性和方法;
属性:某一类事务具备的共性特征
方法:某类事物具备的共同行为,跟前面讲过方法声明一致(一般情况不再使用 static,除非当前使用的类是工具类)

实体类不要有数据生成

实体类在定义了有参构造后一定要有空参构造
对象
对象是根据类来创建的,是类的某个实例,具备类的共性和行为;

如何创建对象?
实例化对象:
          类名 对象名(变量名) = new 类名();
匿名对象:
		  new 类名();
注意:实例化对象是将创建好的对象赋值给某个变量,该对象被变量引用后可重复使用,每次访问都是同一个对象;匿名对象是没有变量引用的,不允许被重复使用;
匿名对象不要用来修改属性,这样的操作是无意义的,因为无法获取修改后的属性值,当只需要调用一次方法或者属性时,或者需要类的对象作为某个方法的参数时,匿名对象使用更方便。
关系
类是对象的模板,对象是类的实例;每一个对象的创建都需要依靠具体的类,每一个对象创建出来之后就拥有所属类中声明好的属性和方法;
成员变量、成员方法
成员变量:声明在类中方法外的变量,也就是属性,也叫成员属性,适用于整个类
局部变量:声明在方法内部的变量,作用域仅限于方法内部,出方法外无法访问;
成员方法:声明在类中的非静态方法,没有用 static 修饰的方法;需要用对象进行调用,或者成员方法内部直接访问
静态方法:使用 static 关键字修饰的方法;可以使用对象访问也可以使用类名访问,静态不能访问非静态

注意:
① 成员方法可以访问成员变量,但是如果成员方法中有和成员属性同名的局部变量,系统默认使用就近原则;
② 在局部与成员变量同名时,如果想要打破就近原则,java提供了 this 关键字用来区分成员变量和局部变量;

成员变量与局部变量的区别:
① 作用域不同,成员变量作用域为整个类的成员范围,局部只作用于当前这个方法
② 内存不同,成员变量属于对象存储在堆内存,局部变量属于方法储存在栈内存
构造方法
① 概念
构造方法是系统来创建类对象时由虚拟机自动调用的方法,专门用来创建类的对象;
② 格式
类名(参数列表){
	如果没有参数列表,内部通常是空的,此时的构造方法称为空参构造,由此构造方法创建的对象属性为声明类时定义的属性值;
	如果有参数列表,内部代码通常是使用参数列表中的参数给对应的属性进行重新赋值,此时的构造方法称为有参构造,由此构造方法创建的对象属性为创建对象时传入的实参;
}

注意:
构造方法无返回值类型,只有访问权限修饰词和类名,如果不写访问权限修饰词系统会默认为默认的访问权限;java 中用于访问权限修饰的词语有四个,分别是:public(公开的),不写(默认的),protected(受保护的),private(私有的);

③ 当一个类没有手动声明构造方法,系统会默认提供一个无参构造方法;一旦手动定义了构造方法,系统将不再提供无参构造方法
构造方法的重载
利用方法的重载,来创建具有不同属性的对象;重载多个构造方法可以在创建对象时更灵活的选择
this关键字
① 功能:
一是用于区分成员和局部变量
二是用来表示当前类对象
② 不同地方的 this 关键字表示类对象是不一样的,但是都是属于同一个类的对象;
③ this 还可以用来进行本类其他构造方法的调用,但是需要在构造方法的第一行使用;调用其他构造的方式 this(参数列表),系统自动根据参数列表进行构造方法的匹配
④ 属性、方法、构造方法都可以调用

③ 面向对象的三大特征

封装
概念
隐藏核心细节,将某些不想让别人直接访问到的内容使用 private 进行修饰;java 是一个开源的语言,在封装之后,需要给外界提供访问的接口;通常使用 getXX 方法来进行封装内容的获取,使用 setXX 方法进行封装内容的修改;
步骤
① 使用 private 修饰需要隐藏的内容,一般来说都是修饰属性
② 针对私有化的内容提供公开的访问方式,一班使用系统提供的访问方式
③ 提供无参构造方法
④ 可以提供有参构造,还可以提供 toString 方法
注意事项
像上述的这样一个类,被称为封装类,也叫做实体类,还叫作javabean或者pojo
继承
概念
在 java 程序设计中,存在A is-a B关系时(比如 Dog is a animal),使用 extends 关键字,让 A 去继承 B 中声明好的一些属性和方法;B 一般是通过提取某类事务的共性抽象出来的一个基类(超类),也称为父类;在使用继承时,不要单纯的为了获取某个类的功能去使用继承,使用继承的前提一定是存在 is-a 关系;
如果没有 is-a 关系存在,就不要使用继承,这种时候属于 has-a 关系,使用组合的方式
步骤
① 抽取共性和行为来声明一个基类;
② 创建各个子类去继承基类;
③ 根据子类需求,可以进行额外属性和方法的声明;
④ 根据子类的特点,重写基类中声明好的方法;
继承中构造方法的调用
① 构造方法无法被继承,但是在继承中子类需要调用父类的构造方法;
② 调用的方式是使用 super(参数列表);且必须在子类构造方法的第一行进行;
③ 当父类中有无参构造时,子类中可以不手动调用父类构造方法;
④ 当父类中没有无参构造,子类中就必须手动调用父类的构造方法;
注意:
父类构造方法的调用实际上就是父类属性的初始化要在子类属性初始化之前进行;
② 如果父类中无属性要初始化,子类中就可以不要调用父类的构造方法
super 关键字
super 表示当前类的父类对象,super 可以调用父类的相关内容;
① 父类构造方法的使用:super(参数列表)
② 调用父类的属性:super.属性
③ 调用父类的方法:super.方法名(参数列表)
方法的重写
① 概念
在子类中,如果对于父类的方法有不同的实现方式,在子类中可以重新将方法的实现过程进行定义;

② 方法重写三要素
i.必须存在继承(extends)或者实现(implements)的关系
ii.在重写父类方法时,返回值类型、方法名、参数列表都必须一致
iii.子类中方法的访问权限不能小于父类方法的访问权限,子类抛出异常的种类不能比父类多,范围不能比父类大

③ 注意
参数列表都必须一致是说参数的个数、类型、顺序(类型的)都必须一致
类中重写后的toString方法,在调用的时候直接输出就好,比如:
System.out.println(cat1);             ✔
System.out.println(cat1.toString());  ❌

④ 重写的注解 @override 
 @override 作用在方法上,强制要求被注解的方法是在重写父类的方法。
 
访问权限修饰词
访问权限修饰词同项目不同包继承关系同包本类
public
默认的(不写)
protected❌(包外的子类可以访问)✔(包括包外的子类)
private
继承中方法的访问
① 如果父类和子类有同名的方法,使用父类对象调用该方法时,只能访问到父类中定义的方法;使用子类对象进行该方法调用时,如果子类是在重写父类方法,访问到的就是子类重写后的方法;如果子类是在重载方法,访问到的方法根据参数列表来决定;
② 当父类对象引用子类对象进行方法访问时(Animal animal = new dog()),使用该对象(animal)进行方法访问,只能访问父类中声明的方法;但是在运行时系统会检测子类是否有重写方法,重写了就以子类重写的为准
总结
编译看左边,运行看右边
编译看左边:写代码的时候,代码是否报错,看的是“="左边的变量所属类中是否定义了该方法
运行看右边:程序执行期间,会根据“=”右边被引用的对象所属类中是否重写了方法来最终的运行结果,子类重写以子类的为准,子类没重写以父类的为准。
继承中变量的访问
编译运行看左边:
编译期间检测左边的变量所属类中是否有该变量,有就允许访问,没有就不允许;
运行期间,同样根据左边变量来决定最终的运行结果
子父类对象转换
① 向上造型:父类引用子类对象
父类类型 对象 = new 子类类型();
对象:管理的是子类对象的内存地址

② 向下转型:父类转为子类对象
需要强制转换:子类类型 对象 = (子类对象)父类对象;在进行向下转型时容易出现 ClassCastException -- 类型转换异常,出现的原因是向上造型的子类和向下转型的子类不是同一个类型;因此在向下转型之前最好判断一下是否能够进行转换;
向下转型一定要存在对应的向上造型
instanceof 关键字:
格式:obj instanceof A
功能:判断 obj 对象是否属于 A类型,如果是返回true,否则返回 false
继承中的注意事项
① 一个类中如果没有显式的声明父类,没用 extends 去继承某个类,那么这个类的父类就是 object,object 里面声明好的类是可以拿来使用的

② Java 类之间只允许单继承,不能多继承;
例如:
A extends B;B extends C ✔
A extends B,C;A extends B extends C ❌
子类不可作为另一个子类的基类:A,B是C的子类,A不能作为B的基类
    
③ Java 中能够组合的情况下,尽量少使用继承;因为继承在编译期就已经从父类中获取到相关内容,组合是在运行期间才会获取引用类的内容

④ 在继承中父类的功能性通常比子类的功能性弱,子类可以在继承父类属性和方法后继续扩展新的功能和属性
  
⑤ 继承中关于成员的访问默认使用就近原则,先从子类查找,子类没有再找父类,一直找到 object 类
    
⑥ 子类可以通过父类中public 的方法来调用父类中private的方法
多态
① 概念
同一个事物在不同情况下表现出的多种不同状态,在 Java 中,多态可以表现为对象的多态和行为的多态:
对象的多态
用同一类型的变量引用不同的子类对象,一般用于方法的参数;
行为的多态
同一个对象在进行方法调用时,根据引用的子类对象不同,方法的执行结果不同
多态实现的步骤
i.必须存在继承或者实现的关系
ii.使用父类引用子类对象
iii.子类重写父类方法
好处
让程序具有更好的扩展性以及动态性
static关键字
static 叫静态的,被其修饰的内容叫静态内容,静态内容是在字节码文件加载的时候就会初始化的;
只要被 static 修饰的内容都可以直接使用类名进行访问,也可以使用对象名进行访问;
类名/对象名.静态内容
数据库作用的属性用 static x
static修饰属性
如果该类的每一个对象在创建后要求某个属性值保持一致,并且同步,此时就可以用static修饰属性;
如果每一个子类在继承父类属性时,要求值保持一致,并且同步,此时就可以用 static 将该内容进行修饰;每一个子类对象中继承得来的该属性都会受到同步修改;
静态属性属于类,不属于对象;
static 修饰方法
被 static 修饰的方法,称为静态方法,静态方法属于类也不在属于对象;
静态方法比 main 还要先执行;
静态方法只能访问静态内容,非静态内容中可以访问静态内容
静态和非静态的区别
① 初始化时机不同
静态内容在字节码文件被加载时初始化;非静态内容在创建对象时被初始化;

② 内存空间不同
静态内容存放在方法区的静态区;非静态内容归属于对象;

③ 归属不同
静态内容归属于类,非静态内容归属于对象;

④ 访问方式不同
静态内容可以使用类名或者对象名进行访问;非静态内容(在非本类时)只能通过对象访问;
final 关键字

概念

final 最终的,终态的,被其修饰的内容不允许再发生改变;
① 修饰变量 -- 变量就会变成常量
② 修饰方法 -- 方法会变成终态方法,不允许被重写,可以被继承
③ 修饰类 -- 类会变成终态类,不允许被继承
代码块
概念
代码块是一个用大括号括起来的代码片段,这个代码片段普遍的操作是给某个指定内容进行赋值;
根据代码块出现的位置可分为:
① 构造代码块:类似于构造方法,用于成员属性的赋值,执行时机优先于构造方法,而且是在系统调用构造方法时才执行;
构造代码块一般用于创建对象之前对象属性的赋值
② 局部代码块:在方法内部定义的代码块,也是为了进行赋值操作,不同的是局部代码块可以限制变量的生命周期;
③ 静态代码块:用 static 修饰的代码块,这部分内容会最先被执行,一般用于优先加载外界资源;
构造代码块
定义方式:{代码片段}
定义的位置:类中方法外,跟成员属性和成员方法一致
局部代码块
定义方式:{代码块}
定义的位置:方法内
静态代码块
定义方式:static {代码片段}
定义的位置:类中方法外,跟成员属性和成员方法一致
对象初始化过程
对象初始化
① 默认初始化:初始化的第一步,创建对象时在内存中进行属性类型默认值的初始化;
② 显示初始化:初始化的第二步,默认初始化完成后根据声明属性给定的值机械能属性的二次初始化;
(代码块初始化)
③ 构造方法初始化:初始化的第三步,显示初始化完成后,根据构造方法传递的实参进行最后的初始化

④ 抽象类

概念
用 abstract 关键字修饰的类称为抽象类,抽象类中可以有抽象方法,抽象方法是只需要声明方法名称,不需要写方法体;
在继承的程序设计中,超类的声明,就可以采用抽象类来进行定义,在超类中就不用思考在方法中该写什么代码;
① 抽象类的定义:
public abstract class Demo{...}
② 抽象方法的定义
public abstract void method{...}
应用
① 在抽取超类时,将类定义为抽象类
② 在声明超类中的方法时,将需要子类重写的方法声明为抽象方法;
③ 不需要子类重写的方法,声明为普通方法;

⑤ 接口

概念
接口是和类、抽象类同级别的概念,接口的内部没有变量,没有构造方法,只有方法的声明或静态常量(final)的声明;
接口里面声明变量的写法会自动变成静态常量
在 jdk1.8 版本之前,接口中只允许有抽象方法;
jdk 1.8版本添加了静态方法和默认方法,jdk 1.9版本新增了私有方法...
接口内抽象方法部实际上就是对于方法的总结和归纳,需要使用方法时通过实现类进行方法的实现;
类 -- class
抽象类 -- abstract class
接口 -- interface
声明

image-20230721151942128

使用
接口的使用即使内部方法(抽象方法)的使用,静态方法和默认方法在 java8 新特性再说;
跟抽象类抽象方法的使用一致,需要通过实现类重写然后调用;

image-20230721153203095

② 使用匿名内部类
new InterfaceDemo(){
 // 重写接口抽象方法 -- 必须重写所有的抽象方法
 public void method(){
 
 }
}.method();
注意:① 要面向接口编程,不要面向对象编程(利用多态)
	 ② 被接口继承,被类实现
类、抽象类、接口特点和关系
① 类:使用 class 进行声明,内部存在属性和方法以及构造方法,可以直接使用构造方法进行对象创建,类与类之间只能单继承,不能多继承;
② 抽象类:使用 abstract class 进行声明,内部存在变量和普通方法或者抽象方法,也有构造方法,但是不能使用构造方法进行对象的创建;对象创建需要使用子类或者匿名内部类进行创建;
③ 接口:
a.使用 interface 声明,内部只有静态常量,没有变量和构造方法,可以存在抽象方法、静态方法、默认方法;
b.只有一个抽象方法时,接口被叫做函数式接口,
c.接口没有抽象方法,不能通过构造方法进行对象创建,对象创建要依赖于实现类或者匿名内部类;
d.接口与接口是继承关系之间可以单继承或者多继承;
e.接口与类之间是实现关系,类(抽象类)可以单实现和多实现接口;
f.普通类在实现接口时,要重写全部实现接口中所有的抽象方法,抽象类实现接口时不用重写抽象方法;
g.当一个类实现的多个接口中具有相同名的抽象方法时,只需要实现一个,实现的是第一个接口的抽象方法

⑥ 模板方法

设计模式
模式:一致解决问题的方案
模板方法设计模式:通过定义模板方法来解决这一类(子类中既有父类方法的继承,也有父类方法的重写,并且所有子类在处理问题步骤一致)的问题;
通过在超类(抽象类)中声明解决问题的算法骨架(模板方法),模板方法值允许子类继承,不允许子类修改;算法骨架中制定好了解决问题的每一个步骤,其中有些步骤里的内容是固定不变的,有些步骤里的内容是可变的;不变的步骤在超类中通过普通方法来声明,可变的通过抽象方法来声明;
定义抽象类
①定义抽象类:抽象类即为超类,需要声明模板方法,声明抽象方法,声明普通方法,钩子方法(可选)
模板方法:制定算法骨架,就是制定办理业务的步骤
抽象方法:声明办理业务的方法名称,强制要求子类按照自己的业务进行重写,表示业务步骤中可变的部分
普通方法:算法骨架中始终保持不变的步骤;
钩子方法:boolean 返回值方法,可以返回结果判断算法骨架中业务是否执行;可以是普通方法,也可以是抽象方法(交由子类重写)
单例模式
概念
单例模式应用于程序中类对象的创建,是一种创建对象的方式,这种方式保证任何时候想要使用类对象时使用的都是同一个对象,并且保证程序应用期间该类的对象只有一个;

 可以在多次实例化对象之后,节省内存空间;
实现步骤
① 创建类的时候,私有化构造方法 -- 不允许外界通过构造方法来创建对象
② 类的内部进行对象的创建,需要创建一个静态属性(该类的对象)
③ 创建一个公开的静态方法,其内部返回一个类的实例
饿汉式
不管需不需要这个对象,在进行类加载的时候都会将该类的对象创建出来;
实现如下图所示:
缺点:类加载的时候对象就会被创建,就会进行内存分配,占用更多的内存
优点:线程安全的,多线程下能够保证对象的唯一性

image-20230721142016158

懒汉式
在需要该对象时才去创建,不需要时系统不会创建对象,可以节省内存;
实现如下图所示:
优点:更合理的运用内存,需要的时候才会创建对象
缺点:线程不安全(进行了if判断,多人同时使用时,会有时间差,可能都创建了,多线程下不能保证对象的唯一性)
线程安全问题解决办法:使用同步锁 synchronized ,修饰获取对象的实例的方法
public static synchronized getInstance()

image-20230721142503483

Java:api(常用类)(自7.24起)

object 类

image-20230724105915890

常用方法
boolean equals(object obj) 判断调用者和参数是否相等,默认比较两者的内存地址;

Class<?> getClass() 返回次 Object 的运行时类对象,应用在反射中;
    
int hashCode() 返回对象的哈希码数值;
    
String toString() 返回对象的内存地址;

String 类型的对象不能用hashCode与equals比较,即使这两个都相等,也有可能不是同一个字符串
equals 方法
public boolean equals(Object obj){
	return (this==obj)
}
默认用“==”进行两个对象的比较;“==”在比较引用数据类型时比较的对象的内存地址;因此 equals 方法默认比较调用两个对象的内存地址;
equals 和"=="的区别
① equals 是一个方法(object类),"=="是一个运算符;
② equals 比较的是两个引用数据类型对象,"=="既可以比较基本数据类型,也可以比较引用数据类型对象;
③ equals 底层默认使用的即是 "==" 运算符进行两个对象内存地址的比较。
hashCode方法
public native int hasCode();
使用 hash 算法将对象计算为一个整型的数字;
toString 方法
public String toString(){
	return getClass().getName()+"@"+Inter.toHexString(hashCode())
}
根据当前对象的 hashCode 值,通过 Integer 类的 toHexString 得到一个用 16 进制字符表示的字符串和全类名+“@” 拼接而成的字符串

时间类

Date/Calendar/SimpleDateFormat
Date 是最开始的序时间操作的api,但是使用起来不方便,它是一个有时间偏移量的时间对象并且创建出来的对象允许修改,不能保留原有的时间对象,现在不建议使用 Date;

Calendar 是 Date 的升级类,同样不方便进行实践对象的操作,并且关于实践格式化的操作不提供,要进行时间格式化操作时又让编程人员使用 Date 类; 
抽象类,不能直接使用构造方法创建对象,内部提供了静态方法getInstance()来获取当前系统对象

SimpleDateFormat 是进行时间格式化的类。
详情请看api文档
新时间api
新的 api 将时间的操作划分为3种,一个是全时间(年月日时分秒),还有只针对年月日操作,以及只针对时分秒操作的时间类;
① LocalDateTime -- 全时间对象
② LocalDate -- 年月日对象
③ LocalTime -- 时分秒时间对象
还提供了用于实践格式化的工具 DateTimeFormatter,有更多的格式化和解析时间方式;
所有的上述内容都是在 jdk 1.8版本之后才能使用;
与旧的时间api比较,新的时间api在时间操作上更简单,没有时间偏移量,针对时间对象的修改,系统留有原有的时间对象并且产生一个新的时间对象
LocalDateTime类
简介

image-20230724154826594

使用
该类不提供构造方法,不能使用传统的 new 类名方式创建对象;需要使用内部提供的静态方法进行对象的创建;
① now() -- 获取当前系统的时间对象
LocalDateTime now = LocalDateTime.now();
② of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond))
LocalDateTime of = LocalDateTime.of(2024,7,24,16,8,53,1)
常用方法
增删改查

String format(DateTimeFormatter formater) 使用指定的格式化程序格式化此日期时间
static LocalDateTime parse(CharSequence text) 用于字符序列解析
int	getDayOfMonth() 日期中的天数(比如7月24日的24)
DayOfWeek getDayOfWeek() 当前时间是星期几
int	getDayOfYear() 一年的第几天
int	getHour() 小时数
int	getMinute() 分钟数
Month getMonth() 月份,英文
int	getMonthValue() 月份,数字
int	getNano() 纳秒数
int	getSecond() 秒数
int	getYear() 年份
minusXX(long value) 在当前系统时间对象上对指定的年、月、日、时、分、秒、纳秒进行减法运算,允许减负值,并且可以自动进行进位与借位操作
plusXX(long value) 在当前系统时间对象上对指定的年、月、日、时、分、秒、纳秒进行加法运算,允许加负值,并且可以自动进行进位与借位操作
withXX(long value) 在当前系统时间对象上设置指定的年、月、日、时、分、秒、纳秒    
withDayOfYear(int day) 把当前对象的时期设置为该年的第day天   
withDayOfMonth(int day)  把当前对象的时期设置为该y的第day天
DateTimeFormatter时间格式化
介绍
DateTimeFormatter 用于新时间类(LocalDateTime/LocalDate/LocalTime)的格式化和解析:对于解析与格式化比 SimpleDateFormat 更加丰富;总共分为3类:
① 使用预定常量 -- 标准格式化
② 使用本地化格式 -- 本地化方法
③ 自定义格式化 -- 模式字符
标准格式化与解析
使用的是在 DateTimeFormatter 中预定义好的静态常量来进行时间的格式化与解析操作;
① static DateTimeFormatter	ISO_LOCAL_DATE
ISO日期格式化程序格式化或解析没有偏移量的日期,如“2011-12-03”。
② static DateTimeFormatter	ISO_LOCAL_DATE_TIME
ISO日期格式化程序格式化或解析没有偏移量的日期时间,例如“2011-12-03T10:15:30”。
③ static DateTimeFormatter	ISO_LOCAL_TIME
ISO时间格式化程序格式化或解析一个没有偏移量的时间,例如“10:15”或“10:15:30”。

image-20230726093542300

    新时间类本身也有format和parse方法
    解析的时候最好用时间类的静态方法parse

image-20230726094942283

本地格式化与解析
使用  DateTimeFormatter 中定义好的本地格式化方法进行

image-20230726101101825

其中 FormatStyle 是一个枚举类,其中定义了四个枚举项:FULL/LONG/MEDIUM/SHORT
FULL -- 全文风格,最细节。        2023年7月26日 星期三
LONG -- 长文字风格,有很多细节。   2023年7月26日 上午10时38分36秒
MEDIUM -- 中等文字风格,有一些细节 2023-7-26 10:39:23
SHORT -- 短文本样式,通常为数字。  23-7-26 上午10:39
不是每一个枚举型都适用于不同的时间对象格式
LocalDate 适用 FULL/LONG/MEDIUM/SHORT
LocalDateTime 适用 LONG/MEDIUM/SHORT
LocalTime 适用 LONG/MEDIUM/SHORT

格式化(时间对象->创建格式化器->调用格式化器格式化时间对象,化为格式化器指定的模样)

image-20230726103216325

解析(定义要解析的时间->创建格式化器->使用对应时间类方法解析时间,化为默认值)

image-20230726103543643

格式化器与时间对象类的对应

要解析的时间与格式化器的对应

自定义格式化与解析
自定义格式化使用 DateTimeFOrmatter 提供的模式字母进行格式化和计息样式的设置,常用的模式字母:

image-20230726112005565

格式化

DateTimeFormatter def1 = DateTimeFormatter.ofPattern("yyyy/MM/dd");
System.out.println(now);
String format1 = def1.format(now);
System.out.println(format1);

解析

image-20230726140314722

Math工具类

介绍

image-20230726140652394

静态常量属性

image-20230726140913335

静态方法
abs(int i) -- 返回参数的绝对值
ceil(double a) -- 向上取整
floor(double a) -- 向下取整
round(double a) -- 四舍五入
pow(double a,double b) -- 求a的b次方
max(double a,double b) -- 返回a,b的最大值
min(double a,double b) -- 返回a,b的最小值
random() -- 产生0~1之间的随机小数,能取到0,不能取到1

Random类

介绍

image-20230726143012047

构造方法

image-20230726143144603

常用方法
nextInt() -- 生成一个随机整数,在 int 范围内随机
nextInt(int bound) -- 在指定范围内(0~bound,左闭右开)生成随机整数

Arrays工具类

image-20230726144457144

静态方法
copyOf(int[] original,int newLength) -- 将原数组复制到一个长度为 newLength 的新数组中
copyOfRange(int[] original,int from,int to) -- 将原数组[from,to)范围内的元素,复制到一个长度为 (to - from) 的新数组中
equals(int[] a,int[] b) -- 比较两个数组的元素是否一致,一致返回True
sort(int[] a) -- 排序,默认为升序                                     
toString(int[] a) -- 将数组转换为字符串形式                           
asList(T... a) -- 根据可变参数创建 list 列表
stream(object[] arr) -- 将数组变为 stream 流,让编程人员对容器的操作变得更加简单快速                                                    
犯过的错
调用 Arrays.toString() 为 Arrays.toString(数组)
而不是
数组.toString();
记忆不清晰

包装类

介绍

包装类是可以将基本数据类型包装为对象的一些类,让数据可以通过一些定义好的方法来操作自己;基本数据类型是不具备操作数据本身的方法,想要操作数据只能通过运算符来实现;
包装类可以实现自动的从基本数据类型到包装类型的转换(自动装箱),以及包装类型到基本数据类型的转换(自动拆箱);
不同基本数据类型对应的包装类
btye     --->   Byte
short    --->   Short
int      --->   Integer
long     --->   Long
float    --->   Float
double   --->   Double
char     --->   Character  
boolean  --->   Boolean
在包装类中,提供的大量操作数据的方法,主要体现为数据与字符串之间的转换方式;
Integer 类

image-20230726162001383

继承 Number 抽象类,实现了 Serializable 接口,并且 Interger 自身实现了Comparable 机械接口,所以 Intger 创建的对象是一个可被序列化和直接比较的对象;
Integer 包装类的特殊情况:内部除了有常用的一些方法,它还有一个内部类,该内部类用于自动装箱时临时缓存 Intger 对象,缓存的大小为一个字节能表示的数据范围(-128~127);如果在该范围内重复进行装箱时,已有存在的对象不再进行二次装箱;
构造方法

image-20230726163405832

通常不使用构造方法进行对象创建,而是使用自动装箱的功能进行对象创建
Integer i= 5;
系统会自动地将 int 类型数据 5 装箱成 Integer 对象后复制给 i 变量;
与自动装箱对应地功能称为自动拆箱;
int i = new Integer(6); 
拆箱即是将 Integer 对象自动转换为 int 类型的值,再赋值给 i 变量;
静态方法
static int compare(int x,int y) -- 比较两个整数数值的大小,返回值为1(x>y),0(x=y),-1(x<y)
static int max(int a, int b):返回两个 int的较大值,就像调用Math.max一样 
static int min(int a, int b):返回两个 int的较小值,就像调用Math.min一样
static int parseInt(String s):将纯数字的字符串转换为整数
static int reverse(int i):获取 i 的二进制补码反转的十进制值
static int sum(int a,int b):计算 a+b 的值
static int toString(int i):将 int 类型的数字变成字符串
static Integer valueOf(int i):将整型变量转换为 Integer 对象,自动装箱使用的方法
static Integer valueOf(String s):将字符串变量转换为 Integer 对象,要求纯数字
非静态方法
compareTo(Integer anotherInteger) 底层使用 compare 方法进行两个值的比较,返回1,0,-1
intValue() 将 Integer 转换为 int,自动拆箱使用的方法
IntegerCache 内部类

image-20230727104423627

128 超过缓存的范围,所以装箱时是不同的对象
每次 new 都是创建不同的对象
与基本数据类型比较时,自动拆箱
126 在缓存的范围内,所以在装箱时不会自动装箱         
注意,还要自学其他的包装类

String 类

介绍

image-20230727110505662

该类被 final 修饰,所以 String 不能被继承,String 底层由不可变数组来维护,创建的对象不允许被修改;
该类还实现了 Serializable,Comparable 接口,证明该类可以被序列化和比较;
String 类在方法区有特定的常量池专门存放 String 对象;当创建字符串对象时,系统会优先检查常量池中是否拥有要创建的字符串,如果有,系统不会再创建,直接使用常量池中存在的对象,如果常量池中没有要创建的字符串,系统才会创建新的字符串,并且存放到常量池中;
构造方法

image-20230727113518903

静态方法
static String valueOf(Object obj) -- 将参数转换为字符串
非静态方法
增   
String concat(String str) -- 字符串拼接,跟 + 运算拼接的效果一致

删
String substring(int beginIndex) -- 返回一个字符串,该字符串是原字符串从 beginIndex 索引开始往后所有的内容
String substring(int beginIndex,int endIndex) -- 返回一个字符串,该字符串是原字符串从 beginIndex 索引开到 endIndex 索引之间所有的内容,同样左闭右开,endIndex不包含   

查
char charAt(int index) -- 查找指定索引处的字符,与数组一致,从 0 开始
int length() -- 获取字符串的长度,与 charAt 方法一起可以实现字符串的遍历 
int indexOf(int ch,int fromIndex) -- 从指定的索引处开始,返回字符串中第一次出现指定字符串的索引,如果没有就返回-1
int LastindexOf(int ch,int fromIndex) -- 从指定的索引处开始,返回字符串中最后一次出现指定字符串的索引,如果没有就返回-1  
boolean contains(CharSequence s) -- 检查字符串中是否包含指定的字符序列
boolean endswith(String suffix) -- 检查字符串是否以参数结尾
boolean startswith(String prffix) -- 检查字符串是否以指定的字符串开头
boolean isEmpty() -- 判断字符串是否为空,为空返回true
boolean matches(String regex) -- 判断字符串是否满足给的的正则表达式格式,满足返回 true

改 
char[] toCharArray() -- 将此字符串转换为新的字符数组 
String[] split(String regex) -- 将字符串以正则表达式表示的字符串或者字符串进行拆分,将拆分的结果储存到一个字符串数组并返回 String replace(char oldChar,char newChar) -- 对字符串中字符进行替换 
String replaceAll(String regex,String replacement) -- 对字符串中,满足正则表达式字符序列进行替换
String toLowerCase() -- 将字符串中所有的字母变成小写
String toUpperCase() -- 将字符串中所有的字母变成大写
String trim() -- 清除字符串首尾空格   
byte[] 	getBytes() -- 使用平台默认字符集将字符串转为字节数组,注意返回值是byte[],是-127~128的数,转换出来是ascll码值

比较
int compareTo(String anotherString) -- 比较两个字符串对象的大小
int compareToIgnoreCase(String str) -- 忽略大小写进行两个字符串的比较
boolean equals(object anObject) -- 重写了 Object 的方法,进行字符串内容的比较
boolean equalsIgnoreCase(String anotherString) -- 忽略大小写进行字符串的比较

StringBuilder(线程不安全,单线程快)/StringBuffer(线程安全)

介绍

image-20230728100357477

StringBuilder 与 StringBuffer 在功能上是一致的,单线程首选 StringBuilder
和 String 相比,StringBuilder 与 StringBuffer 都比 String 的效率高,并且 后两者是由可变字符数组实现
构造方法

image-20230728101433574

StringBuilder 默认的容量大小为 16,使用字符串构建 StringBuilder 时,初始容量大小为 16+字符串长度 ,但是后续在不扩容的条件下可加的长度仍为16;
一旦进行 StringBUilder 拼接或者插入的时候,容量不够能够进行自动扩容,扩容机制为 ((value(字符串构造时的参数).length+16)(初始容量) << 1)+2; <<1是*2 
常用方法
增
StringBuilder append(object obj) -- 在原有的字符串末尾拼接新的字符串
StringBuilder insert(int offset,Object boj) -- 将 Object 参数的字符串插入到此字符指定索引处
    
删    
StringBuilder delete(int start,int end) -- 删除指定范围的字符串
StringBuilder deleteCharAt(int index) -- 删除指定索引处的值
    
查
与 String 类似    
    
改    
StringBuilder replace(int start,int end,String str) -- 用字符串替换指定范围的字符
StringBuilder reverse() -- 字符串反转
StringBuilder setCharAt(int index,char ch) -- 修改指定索引处的字符
String、StringBuilder、StringBuffer 关系和区别
① String、StringBuilder、StringBuffer 底层都是由 char 数组实现的;
String 底层数组用 final 进行修饰了,表示是一个不可变的数组,导致 String 表示的字符串为不可变的字符串,每次的操作都会生成新的 String 对象;
③ StringBuilder、StringBuffer,底层是一个可变的字符数组,多以每一次对字符串的操作都是在原对象上进行,不会生成新的字符串;效率会比 String 高
③ StringBuilder 是线程不安全的,String、StringBuffer是线程安全(里面的方法都有同步锁)的

集合

介绍
它是类似于数组的容器,但是比数组在操作数据上更加方便、灵活,可以使用一个集合存放各种类型的数据;
在jdk 1.5 版本新增泛型的概念,用泛型来规范集合符号与数据的储存;
集合在进行数据储存时能够实现自动扩容,还提供了快速进行数据增删改查的操作方法;
根据存储数据的方式将集合分成了单列集合和双列集合:
单列集合最顶层的接口为 Collection;
双列集合最顶层的接口是 Map;
Collection 下由很多子接口,最具有代表性的接口 List 和 Set;
Map 下常用的实现类为 HashMap 

image-20230728145933913

Collection 接口
介绍

image-20230728151742502

COllection 是所有单列集合最顶层的接口,它继承了 Iterable 接口,证明 Collection 下面所有的子接口和子接口实现类都能够使用迭代器进行遍历;
Collection 没有直接的实现类,所有的实现都需要使用其子接口的实现类;
COllection 无构造方法,不能直接创建对象,创建对象的方式需要使用多态(父接口引用实现类对象)
Collection  c = new ArrayList();
常用方法
增
boolean add(E e) -- 将指定的元素添加到容器中
boolean addAll(Collection<? extends E>c) -- 将容器中的内容添加到容器中

删    
boolean remove(Object o) -- 删除指定的元素
boolean removeAll(Collection<?> c)  -- 删除指定容器中出现的元素
default boolean removeIf(Predicate<? super E> filter) -- 按参数指定条件删除
void clear()  -- 清空容器
    
改
Object[] toArray() -- 返回一个包含此集合中所有元素的数组。    
    
查
int size() -- 容器内元素的个数
default Stream<E> stream() --  将集合转换为 Stream 流,让数据的操作更简单快速
boolean contains(Object o) -- 查看容器中是否包含指定的元素
boolean containsAll(Collection<?> c)  -- 查看容器中是否包含指定容器的所有元素
boolean isEmpty() -- 查看是否为空,空返回true  

迭代
Iterator<E> iterator() -- 获取迭代器,主要用于容器的遍历
Collection 集合的遍历
① 迭代器遍历 -- 使用 iterator() 方法

image-20230728163031325

② 增强for循环

image-20230728163039499

③ 转换为数组j日e

List接口(相关操作看源码)

image-20230728164209684

image-20230728163821973

List 接口是一个有序(添加元素顺序和元素的位置一致)且允许元素重复储存的容器,继承了 Collection 中声明的容器常规操作方法;还额外定义了使用索引操作元素的方法;
List 本身也是一个接口,无法使用 new 构造方法的方式创建对象;
需要使用其实现类来进行对象的创建和引用:常用实现类为 ArrayList 和 LinkedList;
List list= new ArrayList();
常用方法(继承于collection的没写)
内部方法中,大部分都是继承的 Collection 中声明的方法,自己额外定义了根据索引操作元素的方法;
void add(int index, E element) 将指定的元素插入此列表中的指定位置
boolean addAll(int index, Collection<? extends E> c)   将指定集合中的所有元素插入到此列表中的指定位置
    
E get(int index)   返回此列表中指定位置的元素。   
int lastIndexOf(Object o) 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。  
ListIterator<E> listIterator() List特有的迭代器,用来解决集合迭代时出现的并发修改异常
    
E remove(int index) 删除该列表中指定位置的元素
    
E set(int index, E element) 用指定的元素(可选操作)替换此列表中指定位置的元素
List<E> subList(int fromIndex, int toIndex) 截取指定范围的元素 
并发修改异常
什么是并发修改异常?怎么避免 -- java.util.ConcurrentModificationException

使用迭代器遍历时用集合对象进行元素修改或添加,系统就会抛出该异常;
即不允许普通迭代器迭代过程中使用集合对象进行元素修改或添加

使用for循环遍历再使用修改(set)或者添加(add)方法
或者使用 List 特有的迭代器进行容器遍历,再使用特有迭代器提供的 修改(set)或者添加(add)方法进行元素删除以避免异常

普通迭代器可以使用其的删除(remove)方法

总之,想要避免并发修改异常,要使用迭代器遍历容器必须使用迭代器对象操作元素;使用 for 循环遍历容器就使用容器对象操作元素
list 遍历
① Collection 能用的 List 也能用
② 自己特有迭代器
③ 普通 for 循环
ArrayList实现类

介绍

image-20230731140947428

因为 ArrayList 是由数组实现的,所以它的特性为查找元素效率高,插入元素效率低;
ArrayList 在实现 List 接口时,默认的初始容量为 10,在进行元素添加时会先判断容量是否足够,不足时系统会自动进行扩容;
线程不安全
int newCapacity = oldCapacity +(oldCapacity >>1);
即是,容量变为原来的1.5倍;

构造方法

image-20230731142351685

空参构造使用频率最高

常用方法

void forEach(Consumer<? super E> action) 遍历容器
其他方法参考 List 接口    
LinkedList实现类

介绍

image-20230731144243861

因为底层由双向链表实现,因此它的插入效率高,查询效率低,也是线程不安全的;
除了具有常规操作元素的方法以外,还拥有直接操作链表头部或者尾部的方法;
而且为了表示链表的每个节点,LinkedList 内部声明了一个内部类
 private static class Node<E> {
        E item; // 当前节点内容
        Node<E> next; // 当前节点的下一个节点,null,最后一个
        Node<E> prev; //当前节点的上一个节点,null,第一个

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

构造方法

image-20230731150922642

常用方法

void addFirst(E e) -- 在该列表开头插入指定的元素。  
void addLast(E e) -- 将指定的元素追加到此列表的末尾。  

E getFirst() -- 返回此列表中的第一个元素。  
E getLast() -- 返 回此列表中的最后一个元素。

E removeFirst() -- 从此列表中删除并返回第一个元素。 
E removeLast() -- 从此列表中删除并返回最后一个元素。  
Vocter 实现类

介绍

image-20230731172151553

Vocter和 ArrayList 功能性几乎一致,唯一的不同在于 Vocter 是线程安全的,ArrayList 是不安全的;在不考虑线程安全时,优先选择 ArrayList
Set 接口
介绍

image-20230801095238132

Set 接口是 collection 子接口,是元素无序、不重复集合的实现;常用的实现类是 HashSet 和 TreeSet;其中 Treeset 能够实现内部元素(jdk 预定义类型) 的排序,默认使用自然排序,还可以自定义排序规则;

HashSet 底层是由双列集合中的 HashMap 实现
创建 Set 接口对象的方式:
Set set = new HashSet()/TreeSet();
常用方法
所有的方法都继承于 collection 接口,自己无额外方法,因此 Set 接口方法的使用参考 collection 即可
HashSet 实现类

介绍

image-20230801103849054

此实现类是线程不安全的,不保证多线程下数据的安全性;底层是由数组+链表(可变红黑树)实现,不保证添加元素和遍历元素的顺序一致,允许存在 null 元素,但只能有一个(去重);
注意:HashSet 的去重只针对 jdk 预定义类型,如果是自定义类型要去重,需要重写 hashcode 和 equals 方法制定去重的规则;

HashSet 去重原理(默认对预定义类型去重)(重点)

预定义类型:像 String,Integer,Double......这些由 jdk 事先定义好的类型

去重的原理:
① 当 HashSet 在进行元素添加时,首先会根据元素的 Hashcode 值去计算该元素在数组中的位置;
② 查看当前要添加元素位置上是否有节点存在;
③ 如果没有节点,直接将元素添加到该位置,元素添加完成;
④ 如果该位置已经存在节点,暂时不进行数据添加,将该位置上所有的节点和即将添加的元素进行 equals 比较:如果 equals的返回值为 false,证明已有节点和要添加的元素不一样,就将新元素添加到链表中;如果返回值为 True ,证明已有节点中和要添加的元素有相同的存在,此时不添加新元素

注意:
自定义类型在使用 HashSet 进行储存时,默认使用 Object 的 hashCode 和equals 方法实现去重
hashCode:对象.hashCode();
equals:对象 == 对象,默认判断两个对象的内存地址是否相同
因此:
在自定义类型储存时通常需要重写 hashCode 和equals 方法,改变默认的比较规则;

通常会使用对象的成员属性来计算 hashCode 值,让不同成员属性的 对象的hashCode值 不同,相同成员属性的 对象的hashCode值 相同

equals 方法通常会改为在同一类型不同对象obj不为空的前提下,对对象的成员属性进行比较,如果成员属性一致返回 true(比地址改为了比内容)

构造方法

image-20230801140445804

通常使用空参构造进行对象的创建,创建的容器初始容量为16,负载因子为0.75;当数组的储存数据达到16*0.75 = 12 时,系统就会自动扩容,容量变为以前的两倍,数组大小达到64后(不再扩容)且数组内任意位置的链表长度超过8,转换红黑树;
通过 HashMap 的 resize 扩容

常用方法

image-20230801142254901

TreeSet实现类

image-20230801142607558

TreeSet 底层由 Treemap实现,使用排序树(红黑树)的结构实现数据的储存,会对内部数据进行自然排序(升序),因此 TreeSet 只能存放实现了比较器的类型,也就是预定义类,自定义类没有实现比较器是无法存入到 TreeSet 当中;

TreeSet 在去重时原理跟 HashSet 不同,是根据比较器对两个对象比较的结构来实现的;如果返回0,则表示元素重复,否则元素可添加

构造方法

image-20230801144504024

常用方法

boolean remove(Object o) -- 如果存在,则从该集合中删除指定的元素。  
int size() -- 返回此集合中的元素数(其基数)。 
boolean add(E e) -- 将指定的元素添加到此集合(如果尚未存在)。 
boolean contains(Object o) -- 如果此集合包含指定的元素,则返回 true 。

去重和排序的原理

TreeSet 进行元素储存时,默认会对元素进行排序;
① 如果使用的空参构造创建的容器,排序的规则为自然排序;
自然排序 -- 可以理解为根据储存元素的 hashcode 值进行的升序排序
但是如果储存的元素是自定义类型,那么自定义类必须实现 Comparable 接口,然后重写它的 compareTo 方法;通过 compareTo 方法的返回值来实现去重和排序;
返回值为 0,去重;返回值为 正数 或者 负数,去重并且排序

② 如果使用 TreeSet(Comparator<? super E> comparator) 构造方法创建的容器,在储存数据时,既可以存储预定义类型,也可以存储未实现 Comparable 接口的自定义类
此时去重和排序用的是 Comparator 比较器的 compare 方法

前面的元素 - 后面的元素 为升序,反之为降序
泛型
概念
泛型翻译为广泛的类型、不明确的类型;在定义的时候不用知道真正数据类型是啥,只需要通过一个字符(通常使用大写英文字母,例如:T,E,A,B...)来声明即可;在进行调用时传入真正的数据类型,泛型就会自动变为传递数据的数据类型;
泛型的声明格式:使用 <> 进行泛型的声明,然后在 <> 内用大写的英文字母表示泛型;泛型根据声明的位置不同,可分为泛型类、泛型方法;
泛型类即是在声明类的同时声明泛型,该泛型在类中可以被使用;
泛型方法是在声明方法时声明的泛型,只能在方法内使用;
例如集合的泛型使用:
例如:
CLass TreeSet<E>{

}
其中 E 就是泛型,是一个不明确的类型,意味着在创建 TreeSet 容器时,可以指定一个具体的数据类型,一旦指定,那么构建好的容器就只能存放该类型的数据;也可以不指定
TreeSet<Integer> treeSet = new TreeSet<Integer>();
意味着,treeSet 容器中只能存放 Integer 类型的数据;
    
注意:泛型只能是引用数据类型,不能是基本数据类型。
使用泛型的好处
① 泛型的声明式为了在声明时更加容易,在使用该结构时更加灵活多变;
② 泛型将遍历接收数据的处理逻辑提前化,不需要进行强制类型转换;
③ 泛型可以规范数据的数据类型,方便数据的统一处理;
泛型方法
    public static <T> void foreach(T[] array){
        for(T t: array){
            System.out.println(a);
        }
    }
	Integer[] arr = {1,2,3,4,5};
	foreach(arr);
泛型方法的使用实际就是替换以前的方法重载,减少同名方法的声明次数;
注意:
普通方法能够使用类的泛型也可以使用方法中声明的泛型;静态方法只能使用方法中泛型
集合泛型的使用
① 在使用集合时,即创建集合对象的时候要求必须使用泛型;
② 使用泛型的格式
集合类<数据类型>/接口<数据类型> 对象 = new 集合类();
③ <? extends Object> -- 其中 ? 也是泛型的一种表示方式,叫通用泛型
extends 表示继承
? extends Object 表示 object 必须是 ? 的父类或者它本身,?子类于
④ <E super ?>
E super ? 表示 E 必须是 ? 或者是 ? 的父类,?表示的是类的泛型,E父类于
Map接口
介绍

image-20230802102809883

Map 是双列集合最顶层的接口,没有像单列集合一样实现 Interable  接口,因此 Map 集合不支持迭代器进行遍历;

Map 集合进行元素存放时。要求元素对象是一个key=value 的一组映射关系(entry),其中 key 不允许重复, value 可以重复;

Entry 是在 Map 内部声明的一个接口,用来表示 key = value 的映射关系对象,内部提供了 getkey 方法用来获取映射关系对象的 key 值;以及 getValue 方法用来获取映射关系对象的 value 值;

Map 不提供构造方法来进行容器创建,需要使用其实现类进行对象创建,常用的实现类有 HashMap、LinkedHashMap以及Hashtable;其中Hashtable是线程安全的;
Map<指定类型,指定类型> map = new HashMap();
常用方法
//查
boolean containsKey(object key) -- 如果此映射包含指定键的映射,则返回 true
boolean containsValue(object value) -- 如果此地图将一个或多个键映射到指定的值,则返回 true
Set<Map.Entry<k,V>> entrySet() -- 返回此集合中包含的映射关系对象的 Set 视图。
V get(object key) -- 获取指定 key 值对应的 value
V getorDefault(object key, V defaultValue) -- 获取指定 key 值对应的 value, 如果 key 不存在就返
 defaultvalue.
boolean isEmpty() -- 如果此集合不包含键值映射,则返回 true
Set<K> keyset() -- 返回此地图中包含的键的 set 视图。
collection<V> values() -- 返回此集合中包含的值的Collection 视图
int size() -- 返回此集合中键值映射的数量。

//可以添加,也可以改(覆盖)
V put(K key,V value) -- 添加元素,key=value
key不同时添加,key相同时修改
    
//删
V remove(object key) --I如果存在(从可选的操作) ,从该地图中删除一个键的映射
Map集合遍历
方式一:entrySet()方法遍历

image-20230802114015448

方式二:keySet()方法

image-20230802114905417

HashMap实现类
介绍

image-20230802141034378

HashMap 是 Map 接口最常用的实现类,允许出现 null 键和值,但是键只能有一个是 null(TreeMap不允许空键),值可以有多个 null,HashMap 是线程不安全的,底层由哈希表(数组+链表,链表可变为红黑树)实现数据的储存;
数组的初始容量为16 ,负载因子0.75,当储存的数据量达到 16*0.75 = 12(阈值) 时,会自动进行数组的扩容,扩容为原来的 2 倍;最大扩容到 64;当容量变为64时,如果某个位置上的节点数达到 8 ,此时链表就会自动转换为红黑树;如果再对红黑树中的中节点的进行删除,使节点个数少于 8 时,红黑树又会自动变成链表;

红黑树是二叉树的一种特殊结构,具备一些特性:
性质1.节点是红色或者黑色
性质2.根节点是黑色。
性质3.所有叶子都是黑色。(叶子是NIL结点)
性质4.每个红色结点的两个子结点都是黑色。 (从每个叶子到根的所有路径上不能有两个连续的红色结点)
性质5. 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点
性质6,红黑树能够实现自平衡,当左子树和右子树的深度差超过 1 时,红黑树会自动进行结点的旋转,让左子树和右子树的深度差小于等于 1 ,旋转后再改变结点的颜色;
性质7:红黑树能够自动进行结点元素的排序操作,每一个结点左边的结点值都比它小,每一个结点右边的结点值都比它大
构造方法

image-20230802152249668

常用方法
所有的方法都来自于父接口 Map 自己没有定义额外的方法;
源码分析

添加元素

① 根据要添加的键值对的 key 值计算 hash 值;
② 判断当前要添加元素的数组是否为空,为空进行扩容; 如果数组不为空,进行元素的添加;
③ 根据 key 计算的 hash 值找到要添加的键值对在数组中对应的位置(i = (n - 1) & hash]);判断该位置上是否已有结点存在,如果没有直接将键值对(将键值对转换为结点 Node)添加到该位置;
④ 如果该位置已有结点,将该位置上所有的结点和即将添加的键值对进行比较,如果存在相同的 hash 值与key 值,表示键重复,添加时会用新的 value 覆盖原来的 value;如果不存在相同的 hash 值与 key值,就将键值对(将键值对转换为结点 Node)添加到该位置(如果是链表,加上链表的最后面;如果是红黑树,会根据结点进行排序找到合适的位置进行添加)
⑤ 如果添加后链表长度大于等于8,则链表自动转换为红黑树

删除元素

① 判断当前容器是否为空,如果为空,直接返回 null;
② 如果容器不为空,接着进行要删除的 key 对应的结点位置计算,如果该位置不存在结点,直接返回 null;
③ 如果该位置存在结点,进行这个结点和要删除的 key 比较,如果结点的 hash 值和 key 值与要删除的key 的 hash 值和 key 值一致,直接返回当前结点;
④ 如果该位置第一个结点不匹配,继续进行该位置下一个结点的匹配,一直到匹配上(返回匹配上的结点)或者匹配完所有结点(返回null)
⑤ 第一种情况直接将p(要删除的值).next赋给数组的所在索引的要删除的值
  第二种情况p的下一个节点 = 要删除节点的下一个节点

查询元素

① 判断容器是否为空,为空直接返回 null;
② 容器不为空,根据 key 找到数组中对应的位置,查看是否存在结点,如果没有,返回 null;
③ 如果该位置存在结点,找到第一个结点进行比较,如果该节点的 hash 值和要获取结点的 hash 值相等,并且 key 值也相等,该结点就是要获取的结点;
④ 如果该结点不是我们要找的结点,判断该结点是否有下一个结点,如果不存在,直接返回 null;
⑤ 如果该结点存在下一个结点,判断当前储存结点用的时什么结构?是红黑树还是链表?判断完结构后继续进行元素的查找,一直到找到结点(返回结点)或者找完所有的结点(返回null);
LinkedHashMap 实现类
介绍

image-20230803095558519

LinkedHashMap 是 HashMap 的子类,具备 HashMap 的一切功能,在此基础上还提供了一个双向链表来维护元素添加的先后顺序,先添加的元素在链表的前面,后添加的在链表的后面;

因此 LinkedHashMap 在遍历时,元素的顺序保证为元素添加时的顺序
构造方法

image-20230803101944570

Hashtable 实现类
介绍

image-20230803103311384

底层也是由数组+链表的方式实现数据存储,但是不允许有 null 键和 null 值,默认初始容量为 11,负载因子为0.75
Hashtable 还是一个线程安全的集合,所有的操作方法都添加了同步锁,
几乎一致,、 不考虑线程安全使用 HashMap
扩容机制
int newCapacity = (oldApacity << 1)+1
栈结构(Collection的子接口),是单列集合
单口进出,进出口都在同一边;储存数据时先存入的数据位于栈底,后存储的数据位于栈顶,获取数据时,从上往下进行;先获取栈顶数据,最后获取栈顶数据;
归纳为一句话:先进后出,后进先出
statck 类,是Vector的子类
队列结构(Collection的子接口),是单列集合
先进先出,后进后出,阻塞、非阻塞队列。有界、无界队列
Queue类

详情请看pdf

LinkedHashMap 实现类.pdf

Collections 工具类
介绍

image-20230803162232284

COllections 是专门针对集合快速进行操作的工具类,所有的方法均为静态方法,其中主要的方法有:排序,线程安全转换、打乱元素以及创建集合的方法;
常用方法
static <T extends Comparable<? super T>>
void sort(List<T> list) -- 对元素进行默认的升序排序
static <T> void sort(List<T> list, Comparator<? super T> c) -- 根据指定的比较器制定的规则进行列表元素排序

static void shuffle(List<?> list) -- 将集合打乱

static <T> Collection<T> synchronizedCollection(Collection<T> c) -- 单列集合线程安全转换
static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) -- 双列集合线程安全转换

static void reverse(List<?> list) -- 反转指定列表中元素的顺序  

static <T> boolean addAll(Collection<? super T> c, T... elements) 将所有指定的元素添加到指定的集合

各项比较

各项比较.xlsx

异常

介绍
异常即是程序中出现的不正常对象,会强制结束程序运行:异常分为两种:一是运行时异常,二是编译期异常;
运行时异常又称为非受检异常,在代码运行期间才会出现,在编译期不进行检查,这类异常可以处理也可以不处理,目前通常交给虚拟机来处理;
编译异常又称为受检异常,在编写代码期间就必须要进行处理,否则代码编译不被通过无法运行;目前的方式也是交给虚拟机来处理
异常体系

image-20230804101226652

Throwable 类
介绍

image-20230804101347475

Throwable 是整个异常体系最底层的父类,内部定义类关于异常操作的方法,下面有两个分支,一是 Eroor,表示程序出现的严重错误,不在编程人员的处理的范畴;二是 Exception,表示程序出现的异常状态,需要编程人员进行处理的部分;
比较特殊的是作为父类,它几乎包含了所有子类的方法
构造方法

image-20230804102027198

主要方法
String getMessage() -- 返回此throwable的详细消息字符串
StackTraceElement[] getStackTrace() -- 虚拟机默认处理异常的方法,获取异常对象 
void printStackTrace() -- 输出异常信息
String toString() -- 字符串转换

image-20230804103356499

异常处理
在程序遇到不正常状态时,对程序的修正被称为异常的处理; java 中关于异常的处理有两种方式:
① 抛 -- 声明异常类型,或者抛出异常对象,最终都是将异常交给虚拟机,让虚拟机接受到异常并且在控制台显示异常信息
② 捕获 -- 手动进行异常对象的拦截,再进行异常信息的处理,通常处理方式为控制台打印异常信息;
① throws:用来声明异常类型,在声明方法时使用,throws只是声明可能出现的异常类型是啥,并不是一定会向虚拟机抛出异常对象,在程序执行时,不一定会有异常出现;throws 可以同时声明多个异常类型,各个类型之间使用逗号隔开,多用于编译期异常的处理;
② throw:用来抛出异常对象,多用于运行时异常处理,只要执行 throw 一定会有一个异常对象交给虚拟机;一次只能抛出一个对象;

总之,一旦写代码期间程序中出现异常,用 throws 声明异常类型;如果是在运行期间出现异常,用 throw 声明异常类型
捕获
方式一
格式:一般用于一个异常处理
try{
	可能出现异常的代码块;
}catch(异常对象 变量){
	异常的处理;
}
执行流程:
① 对 try 模块内部的代码进行监听,
② 如果出现异常,停止 try 模块中代码的执行,将出现的异常交给 catch 模块进行捕获;将异常封装到 catch 模块中声明的异常对象中;在大括号内进行异常的处理;
③ 如果try 模块中无异常出现,代码正常执行,跳过 catch 模块
方式二
使用多个catch

image-20230804114212457

方式三
try{
	可能出现异常的代码块;
}catch(异常对象 变量){
	异常的处理;
}......
catch(异常对象n 变量n){
	异常的处理;
}finally{
	必须要执行的代码块;-- 通常用来释放资源
}

不管 try 模块中是否出现异常,finally 中的代码块都是会执行的,且是最后执行的,但是如果打印异常,异常是最后打印的
异常处理中return
要等结构执行完后才会return:try/catch -> finally ->return

① 如果在 tyr,catch,finally 三个模块中都有 return 语句,最终以 finally 中的 return 为准

②如果在 try,catch 模块中有 return 语句,finally 中没有。如果有异常出现,以 catch 模块的返回值为准;如果没有异常行贿案,以 try 模块中的返回值为准;finally 模块的代码依旧要执行;

③ 如果只有 try 模块有 return 语句,catch 、finally 模块无 return 语句,则在捕获结构执行完成之后 要有 return 语句;catch有、try和finally没有也一样
自定义异常
在程序开发过程中,有的时候 java 提供的异常处理对象不能满足开发过程中的业务需求,或者在开发过程中需要有个性化的设置,此时我们可以自定异常类来使用
自定义异常类步骤非常简单,只需要三步即可:
① 自定义一个类,继承异常类;根据自定义异常的种类,是运行时异常就继承 RuntimeException ;如果是非运行时异常就继承 Exception;(正常来说,自定义异常类通常是运行时异常)
② 提供有参构造和无参构造
③ 在有参构造中调用父类的有参构造方法;
以上三步为必须,除此之外
还可以自定义属性和方法;

image-20230804150128794

image-20230804150204307

IO流

介绍
IO -- I 表示 InputStream ,O 表示 OutputStream
称为输入输出流,专门用来进行各类文件的读取和写出操作;
根据读写文件的类型,IO 流分为字节流和字符流;
根据输入输出的流向可分为输入流和输出流;其中还有缓冲流(辅助工具,提高节点流的工作效率,本身不具备读写文件的功能)、序列化流(用来实现对象的序列化与反序列化操作)、转换流(实现字节流到字符流的转换); 
结构体系

image-20230804155359916

File 类
介绍

image-20230804162736350

File 用来表示制定路径下的文件对象;通过 File 对象可以实现对文件本身的一些操作(创建、删除)
构造方法

image-20230804163734583

主要方法
boolean createNewFile() -- 创建新文件
boolean mkdir() -- 创建新目录,要求父级目录存在,否则无法创建。  
boolean mkdirs() -- 创建新目录,父级目录不存在时,会自动创建父级目录
    
boolean exists() -- 判断由抽象路径构建的 file 对象是否存在
boolean isDirectory() -- 判断 file 对象对应抽象路径下的文件是否为目录
boolean isFile() -- 判断 file 对象对应抽象路径下的文件是否为文件
    
boolean delete() -- 删除由此抽象路径名表示的文件或目录,只能删除空目录
    
File[] listFiles() -- 获取当前抽象路径下所有的文件对象
String[] list() -- 获取当前抽象路径下所有的文件名称,包含后缀   
String getName() -- 获取当前 file 对象表示的文件名,获取的不是路径,是路径最后的文件名

字节输入输出流
字节输入流 -- InputStream

image-20230804155721470

主要方法
int available() - 返回从该输入流中可以读取的字节数
void close() -- 关闭流
abstract int read() -- 从输入流读取数据的下一个字节,没有读取到返回 -1,读取到就返回读到的字节数。    
int read(byte[] b) -- 从输入流读取一些字节数,并将它们存储到缓冲区 b 。  
int read(byte[] b, int off, int len) -- 从输入流读取最多 len 字节的数据到一个字节数组。  
long skip(long n) --   跳过字节数      
FileInputStream子类

image-20230804162428012

FileInputStream 是最主要的字节输入流子类,可用于任意文件的读取操作;
构造方法

image-20230806135731967

主要方法

image-20230806135813285

字节输出流
OutputStream

介绍

image-20230807101954332

OUtputStream 字节输出流最顶层的父类,是一个抽象类,不能使用构造方法直接创建对象,需要使用其子类进行对象的创建,最常用的子类为 FileOutputStream,可用于一切数据(txt/mp3/mp4/png/doc/md)的输出;

主要方法

image-20230807103810728

fileOutputStream

介绍

image-20230807103941838

构造方法

image-20230807103854504

核心方法

image-20230807104008513

文件可以自动创建,文件夹不行

image-20230807112618662

字符输入输出流
介绍
① 字符流是以读写字符的方式进行文件操作,不管是中文、数字、还是字符,单个的存在都是一个字符,不会像字节流一样进行汉字的拆分(字节流操作纯文本内容乱码的原因)
② 字符流内部自带缓存空间,可以更快速的进行内容读写操作;但是读写的内容相对局限,只能操作纯文本内容;
字符输入流
Reader

image-20230807143359989

Reader 是字符输入流最顶层抽象类,内部定义了读取字符的核心方法,其最常用的子类为 FileReader

主要方法

image-20230807143903047

FileReader

介绍

image-20230807144248003

构造方法

image-20230807144331437

主要方法

都继承于父类,自己没有定义新的方法
字符输出流
Writer

介绍

image-20230807151425423

字符输出流是以字符(字符串)的形式进行内容的输出,自带缓存,每一次的输出都需要进行流的冲刷,否则数据会在缓存中进行保存,不会直接写到文件中;
冲刷的方式可以使用 flush 方法,也可以使用 close 方法。flush之后还可以用,close之后就不能用了

主要方法

image-20230807151727197

FileWriter

image-20230807152551411

与 OutputStream 一致
转换流
介绍
转换流用于字节流到字符流的转换,没有字符流到字节流的转换;在转换的同时可以实现编码格式的设置;
因此转换流用来进行目标文件与编码环境编码格式不统一时进行编码格式的统一设置;
转换流的创建实际就是字符流的创建,比普通的字符流多了一个涉及编码格式的选择;
输入转换流 -- InputStreamReader
简介

image-20230807160952740

构造方法

image-20230807161026774

主要方法

image-20230807161037549

输出转换流 -- OutputStreamWriter
简介

image-20230807164036558

构造方法

image-20230807164410954

主要方法

image-20230807164507783

缓冲流 -- Buffered
介绍
缓冲流不能直接操作文件,需要配合辅助节点流(字节流或字符流)进行文件操作;
缓冲流内部创建了 8192 字节或字符的空间,用来进行读写数据的临时储存,类似于使用字节流使用数组的方式读写数据,这样可以大大的提升读写的效率;
因此,缓冲流的作用就是提升字节流或字符流读写的能力;

缓冲字节输入、缓冲字节输出流

缓冲字符输入、缓冲字符输出流

缓冲字节流
缓冲字节输入流 -- BufferedInputStream

介绍

image-20230808092004258

不是抽象的

构造方法

image-20230808092345467

效率比普通的高,但是比使用小数组的字节流于字符流低
缓冲流建议使用自带的缓冲区域
缓冲字节输出流 -- BufferedOutputStream

介绍

image-20230808095535046

构造方法

image-20230808095604204

缓冲字符流
输入流 -- BufferedReader

看 api: 介绍及构造方法

常用方法

多了个 readLine ,返回值是 String
输入流 -- BufferedWriter

看 api: 介绍及构造方法

常用方法

多了个 newLine ,针对任何系统都能实现换行
序列化流
介绍
序列化表示将 java 对象通过字节流写出到指定的文件中的过程,以便于 java 对象进行之旧话保存和传输;
由于 java 语言的特点,对象只能在虚拟机运行期间存在,一旦虚拟机关闭,对象就消失;对象中储存的数据也会消失,想要在虚拟机关闭后仍然保存对象信息,序列化对象是当前比较常用的一种手段;

跟序列化对应的操作为反序列化,反序列化是在序列化文件在传输过程中,读取文件中对象信息的一种手段;

序列化与反序列化都是以字节的形式进行数据的读写,因此归根到底即是字节输入流和输出流的使用;

对应的字节输出流 -- 序列化流(objectOutputStream)
对应的字节输入流 -- 反序列化流(objectInputStream)

一个类的对象想能被序列化,必须要实现序列化接口 -- Serializable
序列化操作

image-20230808114739034

只有一个构造方法,传入字节输出流
主要方法

image-20230808115041824

文件保存的是该对象的属性和值,以及序列化的版本号(如果类中没有手动定义,系统会自动生成一个);类的静态内容不能被序列化;
反序列化操作
反序列化时,jvm 虚拟机中必须要存在反序列化对象类型,而且类必须和序列化之前具有相同的 serialVersionUID,否则反序列化会失败并且抛出 InvalidClassEception 异常出现的原因:
   序列化时用的类的 serialVersionUID 和反序列化时的不一致;
如何处理该异常:
   ① 不要修改类的任何内容,serialVersionUID 是类中的内容进行生成的。
   ② 手动进行 serialVersionUID,且声明为静态常量 ,final static
transient 关键字
模糊序列化属性,被 transient 修饰的属性在进行序列化时,值不会序列化到文件中;
Properties 类
介绍

image-20230808150949994

Properties 是 java 中用于配置文件读取的类,在 java 编程中,很多地方需要有配置文件的支持,配置文件有多种类型;例如,以.properties\.xml\.txt 等结尾的文件;想要获取配置文件中配置的信息,就需要进行文件的读取。

Properties 是可以进行多种不同类型配置文件读取的类;
Properties 内部有大量读/写配置文件的方法。都是根据 IO 流来进行的文件读\写去操作
Properties 还是 Hashtable 的子类,属于双列集合的范畴,通过键值对的方式进行数据的储存,并且要求键和值都是 String 类型;除了有 map 集合通用的添加数据的方式以外,自己还定义专属的数据添加方法
构造方法

image-20230808152032617

主要方法
读取写入xml文件只能使用字节流
操作内部元素

image-20230808153404576

读取配置文件

image-20230808153844713

写出数据到文件中

image-20230808164549557

Java 多线程

程序 - 进程 - 线程
程序:一组代码指令的集合,简单理解为一堆堆代码
进程:正在运行的程序称为进程
线程: 进程的最小执行单位
多线程,即是同时委派多个线程去处理一个或者多个任务;多个线程处理一个任务称为并发;多个线程处理多个任务则为并行

个人:
非静态的变量与方法都是属于对象的,此时一个任务应该是一个对象
静态的变量与方法都是属于类的,此时一个任务一个是一个类
Java多线程实现
方式一:继承 Thread 类
① 自定义类,继承 Thread;
② 重写 Thread 类提供的 run 方法;run 方法即是线程要执行的任务,因此需要将业务的处理写在 run 方法中;
③ 创建多个线程任务,同时启动(start)线程任务;
会重复、遗漏、多运行
方式二:实现 Runnable 接口
① 自定义类,实现 Runnable;
② 重写 Runnable 类提供的 run 方法,系统会强制要求重写;run 方法即是线程要执行的任务,因此需要将业务的处理写在 run 方法中;
③ 先创建自定义类对象,使用该对象创建多个线程任务,同时启动(start)线程任务;
也无法避免线程安全问题
方式三:实现Callable接口
① 自定义类实现 Callable 接口;
② 重写 call 方法,该方法具有返回值,也是将处理业务的代码写在方法中;
③ 创建自定义类对象,并且通过自定义类对象创建 FutureTask 对象,再使用 Futuretask 对象创建线程对象,最后使用线程对象启动线程

image-20230809114329982

image-20230809114524497

多线程安全处理
同步代码块
将业务过程使用同步锁(synchronized)进行修饰,对线程对象自动进行加锁,保证同一时间进入业务流程代码块的线程对象只有一个,其他线程对象处于等候状态;
同步代码块想要能够锁住线程对象,要求所有的线程对象是同一个自定义对象实例化的;

image-20230809141759811

同步方法
同步方法分为静态的和非静态的:
静态的同步方法可以锁住任意由同一个 class 文件创建的对象;
非静态的同步方法跟同步代码块一致,只能锁同一个自定义对象;
同步方法的声明:synchronized 修饰方法,方法内部都是业务流程方法

image-20230809152203731

Lock锁
Lock 锁需要手动进行加锁和释放锁,和同步代码块类似,只不过同步代码块是自动实现加锁和释放锁,也需要同一个自定义对象

image-20230809154302112

线程状态
线程在运行期间会随着时间推移出现多种不同的状态,并且可以在这些状态中来回切换;java 用一个枚举类来描述线程的状态:
该枚举类为 State,是Thread 内部定义的内部类,State 将线程状态分为 6 类:
① NEW -- 新建状态,使用 new 关键字进行线程对象创建时,线程就处于该状态

② RUNNABLE -- 就绪状态,线程对象调用 start 方法后,线程进入该状态;等待 cpu 到来,获取 cpu 后,线程进入运行状态

③ BLOCKED -- 阻塞状态,线程在运行期间调用阻塞方法,线程就会进入该状态

④ WATTING -- 等待状态,非限时等待,线程一旦进入这个状态不能自动唤醒,需要靠别的线程来唤醒,被唤醒后进入就绪状态

⑤ TIMED_WATTING -- 限时等待,线程在进入该状态之后,时间结束之后会自动唤醒;唤醒后进入就绪状态

⑥ TERMINATED -- 销毁状态,线程正常执行完任务或者遇到异常,线程就会进入到该状态,一旦进入该状态后线程会被立即杀死。

image-20230809164228027

线程死锁
线程死锁是多线程下,两个或者两个以上的线程对象相互持有对方需要的资源且都在等在对方放弃资源,导致程序无法继续执行,但是程序也不会结束;
死锁通常出现在资源锁相互被占的时候,如果锁对象不会出现死锁

死锁产生的条件:
① 同一个资源在任何时候只能被一个线程占用
② 资源在被使用期间不可进行使用权力的剥夺
③ 多个线程出现首尾获取资源的情况
④ 线程在使用期间进入阻塞状态,仍然不放弃资源;

解决方法:
破坏以上任意条件即可
线程池
介绍
池:就是一种容器,线程池即是一种用来进行线程储存和管理的容器;线程池可以进行线程自动创建、维护、以及重复利用;所有线程相关的操作都交给线程池来完成;
java 中关于线程池的创建提供了两种方式:
① 使用工具类 Executors
② ThreadPoolExecutor
Executors 工具类

介绍

image-20230810112035048

常用方法

static ExecutorService newCachedThreadPoo1() -- 创建一个可根据任务数量自动创建线程的线程池
static Executorservice newFixedThreadPool(int nThreads) -- 创建一个固定线程数量的线程池
static Executorservice newSingleThreadExecutor() --创建单例模式的线程池
ExecutorService(添加和删除线程池)

介绍

image-20230810112823015

主要方法

image-20230810113059973

线程池关闭

ExecutorService 提供了2种关闭线程池的方式:
① shutdown -- 拒绝新任务,将所有已经提交的任务执行完后,关闭线程池;
② shutdownNow  -- 拒绝接收新任务,将已经提交并且正在执行的任务执行完,关闭线程池;已经提交还没有被执行的任务不会再执行;
ThreadPoolExecutor

image-20230810142154396

ThreadPoolExecutor 是 java 中用于线程池创建的一个底层类,包括 Executors 工具类在进行线程池创建很多地方使用的都是该类

构造方法

image-20230810142945210

所有的构造方法前五个参数的函数含义一致,最多的构造方法有七个,含义如下
int corePoolsize -- 创建线程池时,池内的默认线程数量
int maximumPoolsize -- 线程池允许的最大线程数
1ong keepAliveTime -- 空闲线程存活时间
TimeUnit unit -- 时间单位
BlockingQueue<Runnable> workQueue -- 线程任务大于线程数量时,任务的存储队列
ThreadFactory threadFactory -- 线程创建工厂,系统自动创建线程的方式
RejectedExecutionHandler handler -- 异常处理策略

image-20230810152526417

枚举类
介绍
枚举是跟类、接口、抽象类等同级别的结构,使用 enum 关键字进行声明;
public enum 枚举名{
	枚举项 -- 表示枚举的对象,必须时枚举内部的第一个内容;多个之间使用逗号隔开,最后一个枚举项后面可以不写任何符号,一旦不写,该枚举类只能定义枚举项,不能定义其他内容;如果要定义其他内容,最后一个枚举项后面需要添加分号;
	普通方法,构造方法(私有的)
}
枚举项:
   相当于类对象
   枚举类创建好的已知对象,是根据枚举类中提供的构造方法来创建的枚举类对象;
   每一个枚举项又是一个常量

image-20230809170619915

内部类
介绍
内部类是定义在 java 类内部的类,根据类的定义方式和类修饰词,可将内部类划分为:
① 成员内部类 -- 类似于成员方法
  在成员内部类中根据内部类的访问权限又可分为:
  ① 私有内部类
  ② 静态内部类
② 局部内部类 -- 局部变量
还有一种内部类叫匿名内部类,它是用来快速创建抽象类子类对象或者接口的实现类对象
成员内部类

image-20230810160053710

image-20230810160143028

局部内部类

image-20230810160513142

直接访问外部类的内容,外部类不能访问其中的内容
私有内部类
封装更彻底,只能在本类或者外部类使用,其他类不能直接使用
静态内部类
类似静态方法,可以被其他类通过外部类.内部类调用

Java 反射(参数列表的顺序都要一致)

概念
在程序运行期间,可以动态的获取任意类的属性和方法,可以调用任意一个对象的属性和方法,这种能够体现 java 动态性的操作被称为反射;
Java 的反射基于 Class类型的 对象的,任意一个类只要通过 class类型的 对象就能够获取该类中的一切内容
Class对象(字节码文件的对象)的创建方式
① Class.forname("类的全路径");
② getClass();
③ 类名.class;
Class类

image-20230811101434978

Class 类是 java 中关于反射使用的类;内部定义了大量反射属性、方法、构造方法、注解的相关方法;不提供构造方法来创建对象,有一个静态方法 forName 用于对象的创建,该方法也是属于反射操作;
反射属性

image-20230811102030835

前两个只能获取 public 修饰的属性;后两个不考虑权限,都可以
JAVA 中定义了一个 Filed 类 用来描述关于反射属性得到的对象
Field 类

介绍

image-20230811103902353

主要方法

Object get(Object obj) -- 返回指定对象的 field 属性值
String getName() -- 返回该属性对象的名称
void set(Object obj,Object value) -- 对指定对象的该属性进行赋值 
暴力反射
被私有的内容在反射时进行操作会出现  IllegalAccessException 异常;
暴力反射可以让私有的内容也允许被操作;
field.setAccessible();

image-20230811110214059

反射方法
反射

image-20230811111855616

前两个针对所有的方法,后两个只能获取 public 修饰的属性

image-20230811113137893

调用

image-20230811114155297

invoke(对象,参数(可变参数的形式))

没有参数的方法不用写后面的参数
void 方法返回值为 null
静态方法可以把对象写成 null

对于不能直接调用的方法,也可以暴力反射

image-20230811114258136

其余主要方法
object invoke(object obj,object. . . args) --反射方法的调用
int getParametercount() --获取参数数量
String getName() --获取方法名称
类<?>getReturnType() --获取返回值类型
反射构造方法

image-20230811141250563

Constructor 类

image-20230811141523641

核心方法

image-20230811142053057

image-20230811144200690

反射集合
可以跨越集合的泛型进行元素操作;

image-20230811155200609

反射注解(学习了注解再讲)

Java 类加载过程

类从加载到卸载一共分为 7 个步骤:
加载 -- 验证 -- 准备 -- 解析 -- 初始化 -- 使用 -- 卸载

其中前面 5 个步骤属于类的加载过程;验证、准备、解析、又被归纳为一步,称为连接;
由此,整个类加载的过程可缩短为3个步骤
加载 -- 连接 -- 初始化
类的加载
依靠类加载器实现的,将编译后的字节码文件以二进制流的方式加载到 JVM 内存中;
虚拟机内置了 3 个类加载器用于类文件的加载,还可以自定义类加载器;
加载器有一个继承结构体系:
BootStrap ClassLoader <--继承-- Extension ClassLoader <--继承-- Application ClassLoader
这些类加载器在进行类加载是遵循双亲委派机制进行类的加载
双亲委派机制: 
当有加载任务出现时,抛开自定义类加载器,
加载任务首先会交给 Application ClassLoader 加载器,
Application 不会立即进行类的加载,而是将该任务往父类进行传递,也就是将加载任务传递给 Extension,
Extension 在接收到任务后通用不会立即执行,而是将任务再传递给 BootStrap,
由 BootStrap 尝试进行类的加载:BootStrap 会先检查自己加载类的范围内是有加载该类的权利,如果有就自己进行类加载,如果不具备加载的权利,就将加载任务还给子类 Extension
Extension 进行自我检查,如果有权力加载就进行类加载,无权利就将加载任务继续下放,以此类推,直到类被加载;
双亲委派机制的好处是可以有效的防止编程人员篡改源码
连接
先进行验证;验证文件的格式是否正确,内容是否会导致虚拟机出现错误;
准备:进行静态内容的初始化,包括字符串常量池;
解析:对应文件中的引用符号进行解析,对象、常量值的引用;
初始化(在前面讲过)
对象初始化
① 默认初始化:初始化的第一步,创建对象时在内存中进行属性类型默认值的初始化;
② 显示初始化:初始化的第二步,默认初始化完成后根据声明属性给定的值进行属性的二次初始化;
(代码块初始化)
③ 构造方法初始化:初始化的第三步,显示初始化完成后,根据构造方法传递的实参进行最后的初始化

java8 新特性

接口新特性
介绍
java 8 以前接口内部只允许出现抽象方法和静态常量;
java 8 为了弱化抽象类的使用,给接口新增了静态方法和默认方法;java 9新增了私有方法;
静态方法:跟在类中定义静态方法一致,就是使用 static 进行修饰的普通方法
默认方法:使用 default 修饰的普通方法

如果一个接口中抽象方法仅有一个,那么该接口被称为函数式接口;当一个接口为函数式接口时,接口实现类对象的创建就可以使用 Lambda 表达式快速实现(这是一种比匿名内部类更加优秀的方式)

默认方法:使用default修饰,不可省略,供子类调用或者是子类重写 
静态方法:使用static修饰,供给接口直接调用的
静态方法的定义与调用
静态方法不能被继承,不能使用实现类对象进行调用,只能通过接口名进行调用
静态方法存在的目的:声明实现类共有的业务功能
默认方法的声明和调用
默认方法能被继承,也能被重写;实现类中如果想要调用父接口中的默认方法,可以使用 接口名.super.方法
函数式接口

image-20230814101746334

通过该注解可以检查该接口是否是函数式接口

函数式接口的使用一般是用 JDK 提供的预定义函数式接口,JDK 提供了四个常用的函数式接口,每一个都能够解决一类的问题:
① 消费型函数式接口 -- 消费数据 -- Consumer
② 生产型函数式接口 -- 生产数据(无中生有)-- Supplier
③ 断言型函数式接口 -- 进行是非判断 -- Predicate
④ 函数型函数式接口 -- 定义处理数据的方式 -- Function
Consumer 接口

image-20230814103143490

方法

image-20230814103305747

Supplier 接口

介绍

image-20230814105038665

方法

image-20230814105057119

Predicate 接口

介绍

image-20230814110134087

方法

image-20230814110057633

Function 接口

介绍

image-20230814112523098

方法

image-20230814112603987

注意事项
函数式接口通常时作为方法的参数存在,存在的目的是传递一种处理数据的方式,不再单纯传递数据本身;
在使用函数式接口时,就是使用其内部的抽象方法进行数据处理;
Consumer -- accept();有参数没有返回值
Supplier -- get();没有参数有返回值
Predicate -- test();有参数有返回值
Function -- apply();有参数,返回值可有可无
Lambda 表达式
介绍
Lambda 表达式是一种快速创建函数式接口实现类对象的一种方式,比匿名内部类更简洁;
Lambda 表达式基本结构:()->{}
() -- 表示函数式接口的抽象方法,如果抽象方法有参数,小括号内需要说明参数名称
例如:Consumer 函数式接口实现类对象的创建:(a)->{}
{} -- 表示对抽象方法的重写,具体的代码需要根据业务需求来写
->:goes to
简写
① 如果抽象方法的参数只有 1 个,小括号可以省略: --- a->{return 代码块;}
② 如果{}内部代码只有一行,大括号可以省略,return 关键字可以省略;--- a->代码块
方法引用
当 Lambda 表达式实现的过程已经被一个类的方法实现了 ,此时就可以直接引用已经实现的方法,无需再写 Lambda 表达式;这种使用现成的方法替换 Lambda 表达式的过程称为方法的引用

方法引用的原理就是用现有存在的方法代替 Lambda 表达式重写的抽象方法;

格式:
① 静态方法: 类名::方法名
② 普通方法: 对象::方法名
注意:关于String类型方法引用时,每一个字符串本身就是一个 String 对象,所有可以直接引用 String 的方法(普通方法采用了静态方法的格式)
③ 构造方法的引用 类名::new
Stream 流
介绍

image-20230814151221410

Stream 流是一种快速进行容器数据处理方案,相较于传统的容器(数组、集合),Stream流进行数据处理时无需遍历容器可以直接进行数据的操作,但是Stream流不进行数据的储存;
    
Stream 流有多种创建方式,核心是数组、集合转为 Stream 流的方式
① 数组 --> Stream 流
使用 Arrays 工具类中提供的 stream 方法,实现数组到流的转换;
② 集合 --> Stream 流
List/Set,通过集合对象调用 stream() 或者是 parllelStream() 方法实现流的转换
Map,本身不能实现转换,只能先将 Map 集合转为单列集合,再使用单列集合的 stream()方法转换为流

Set<Map.Entry<k,V>> entrySet() -- 返回此集合中包含的映射关系对象的 Set 视图。
Set<K> keyset() -- 返回此地图中包含的键的 set 视图。
collection<V> values() -- 返回此集合中包含的值的Collection 视图
    
除此之外,Stream 流本身也提供了方法进行流的创建  
① static <T> Stream<T> of(T... values)
Stream 流创建后可以使用内部大量操作数据的方法进行数据的筛选、过滤、归并等操作; 大部分方法属于延迟方法,只是处理数据,没有结果返回;
另一部分方法称为终止方法,只有调用终止方法后,才能获取数据处理结果    
Stream 流创建
public class StreamDemo {
@Test
public void test_of(){
	Stream<String> stream = Stream.of("苏妲己","貂蝉","西施","杨玉环");
	System.out.println(stream);
}
@Test
public void test_arrays(){
	Integer[] arr = {1,23,5,6,7,8};
	Stream<Integer> stream = Arrays.stream(arr);
}
@Test
public void test_list(){
	List<String> list = new ArrayList();
	list.add("苏妲己");
	list.add("貂蝉");
	list.add("西施");
	Stream<String> stream = list.stream();
}
@Test
public void test_map(){
	Map<Integer,String> map = new HashMap();
	map.put(1,"貂蝉");
	map.put(2,"杨玉环");
	map.put(3,"西施");
	map.put(4,"苏妲己");
	map.values().stream();
}

Stream流使用
延迟方法
所有返回值为 Stream 或者是 Stream 子类的方法即是延迟方法,此类方法只进行数据处理,终止方法调用前不返回处理结果;
所有的延迟方法都会返回一个新的 Stream 流;在此基础上可以继续使用延迟方法或者终止方法; 
distinct() -- 去重
Stream<T> filter(Predicate<? super T> predicate) -- 过滤\\\\\\\\\\\\\\\\\\\

<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)  -- (参数拆开后),将原来流中的一块块的元素拆开,转为一个新的Stream流    {[1,2],[3,4]} -> {1,2,3,4}
<R> Stream<R> map(Function<? super T,? extends R> mapper)  -- (参数不拆开),把这个流中的元素(不拆开)都经过function的调用,然后把function调用的所有结果再组成一个流,

Stream<T> limit(long maxSize)  -- 截取
Stream<T> skip(long n) -- 跳过
Stream<T> sorted() -- 默认排序,类似于 TreeSet 的自然排序  
Stream<T> sorted(Comparator<? super T> comparator) -- 比较器排序
Stream<T> peek(Consumer<? super T> action) -- 数据遍历处理 
终止方法
所有返回值不为 Stream 或者是 Stream 子类的方法即是终止方法,终止方法是用来获取数据的处理结果
一旦某个流调用终止方法,该流不能再使用;
<R,A> R collect(Collector<? super T,A,R> collector) -- 将处理数据储存到指定集合中
long count()  -- 统计数据数量
void forEach(Consumer<? super T> action)  -- 遍历输出
T reduce(T identity, BinaryOperator<T> accumulator)  -- 归并,统计
<A> A[] toArray(IntFunction<A[]> generator)  --返回一个包含此流的数组
  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值