主要参考2023视频
一、Java三大平台
1、概述
-
Java SE(Standard Edition)
Java语言的标准版,用于桌面应用的开发,是其他两个版本的基础
学习目的:为今后要从事的Java EE开发,打基础 -
Java ME(Micro Edition)
Java语言的小型版,用于嵌入式消费类电子设备 -
Java EE(Enterprise Edition)
Java语言的企业版,用于Web方向的网站开发
2、Java的跨平台性
平台
指的是操作系统
跨平台
Java程序可以在任意操作系统上运行
跨平台工作原理
我们写的Java程序在运行时不看你在哪个操作系统,就看装没装虚拟机,只要装了虚拟机,就能运行在虚拟机中。即在需要运行的Java应用程序的操作系统上安装与操作系统对应的虚拟机即可

二、JDK(Java Develop Kits)的组成
-
JVM(Java Virtual Machine)
Java虚拟机,真正运行Java程序的地方 -
核心类库
Java自己写好的程序,给程序员自己的程序调用 -
JRE(Java Runtime Environment)
我们将以上两部分统称为JRE,即Java的运行环境 -
开发工具
Java,Javac……
以上共同组成JDK

三、标识符命名规范
1、硬性要求
必须要这么做,否则代码会报错
- 必须由数字、字母、下划线(_)、美元符号($)组成
- 数字不能开头
- 不能是关键字
- 区分大小写
2、小驼峰命名法
- 标识符是一个单词的时候,所有的字母小写
示例:name - 标识符由多个单词组成时,从第二个单词开始,首字母大写
示例:firstName
3、大驼峰命名法
- 标识符是一个单词的时候,首字母大写
示例:Student - 标识符由多个单词组成时,每个单词的首字母大写
示例:GoodStudent
4、使用方法
- 小驼峰命名法主要给变量起名
- 大驼峰命名法主要给类起名
四、数据类型
1、基本数据类型的四类八种
| 数据类型 | 关键字 | 内存占用 | 取值范围 |
|---|---|---|---|
| 整数 | byte | 1字节 | 负的2的7次方 ~ 2的7次方-1(-128~127) |
| short | 2字节 | 负的2的15次方 ~ 2的15次方-1(-32768~32767) | |
| int | 4字节 | 负的2的31次方 ~ 2的31次方-1 | |
| long | 8字节 | 负的2的63次方 ~ 2的63次方-1 | |
| 浮点数 | float | 4字节 | 1.401298e-45 ~ 3.402823e+38 |
| double | 8字节 | 4.9000000e-324 ~ 1.797693e+308 | |
| 字符 | char | 2字节 | 0-65535 |
| 布尔 | boolean | 1字节 | true,false |
在java中整数默认是int类型,浮点数默认是double类型
整数类型和小数类型的取值范围大小关系
double > float > long > int > short > byte
注意
- 如果要定义long类型的变量,在数据值后面需要加一个L作为后缀,L可以是大写的,也可以是小写的,建议使用大写,以免与数字1混淆
- 定义float变量时,数据值也需要加一个F作为后缀,大小写都可以
2、引用数据类型
- 数组
- 类
- 接口
关于引用数据类型的一个问题
空指针异常(NullPointException)
原因
当引用数据类型的变量,被赋值为null后,就代表跟堆内存的连接被切断了,这时候还想去访问堆内存的数据,就会出现空指针异常
3、类型转换细节
public class TypeDemo {
public static void main(String[] args) {
byte b1 = 1;
byte b2 = 2;
// byte b3 = b1 + b2; 报错
/*
错误原因:
b1和b2是两个byte类型,在运算的时候,会提升为int类型
提升之后就是两个int类型在运算了,运算结果还是int
将int类型的结果,赋给byte类型的变量,不能直接给
byte b3 = b1 + 2; 这样写也会报错
*/
// 解决方式1:用int类型接收
int b3 = b1 + b2;
// 解决方式2:运算完后,做一个强转
byte b4 = (byte) (b1 + 2);
System.out.println(b3);
System.out.println(b4);
// 下面的代码是正确的
byte b = 3 + 4;
System.out.println(b);
/*
原因:
Java存在常量优化机制
在编译的时候(javac)就会将3和4这两个字面量进行运算
产生的字节码文件:byte b = 7;
*/
}
}
运行结果:
3
3
7
五、逻辑运算符
1、类型
| 符号 | 介绍 | 说明 |
|---|---|---|
| & | 逻辑与 | 并且,遇false则false |
| l | 逻辑或 | 或者,遇true则true |
| ! | 逻辑非 | 取反 |
| ^ | 逻辑异或 | 1.相同为true,不同为false。2.一个数字,被另外一个数字异或两次,该数本身不变。 |
在Java中,以上运算符可以直接操作数值(和C++有所不同)。使用 & 和 &&( | 和 || )得到的结果是相同的,但更多的时候使用双与、双或,因为当左边的结果能决定最终结果时,就不计算后面的表达式,效率更高
逻辑异或还是按位异或,即先将十进制数值转换为二进制形式,然后按位进行异或运算,运算完成后再将结果转为十进制数值
| 符号 | 介绍 | 说明 |
|---|---|---|
| && | 短路与 | 具有短路效果,左边为false,右边就不执行了 |
| ll | 短路或 | 具有短路效果,左边为true,右边就不执行了 |
示例
boolean a = 4 > 3 & 3 < 30;
System.out.println(4 > 3 & 1 > 2); // 逻辑与,结果为false
System.out.println(4 > 3 | 1 > 2); // 逻辑与,结果为true
// 一个数字,被另外一个数字异或两次,该数本身不变
System.out.println(5^5^10); // 结果为10
System.out.println(10^5^10); // 结果为5
2、数据交换
数据交换的一个问题,使用异或运算符的特点解决
//实现两个变量的数据交换,不允许定义第三方变量
int x = 10;
int y = 20;
x = x ^ y; // x = 10 ^ 20
y = x ^ y; // y = 10 ^ 20 ^ 20 y = 10
x = x ^ y; // x = 10 ^ 20 ^ 10 x = 20
System.out.println("x=" + x); // x = 20
System.out.println("y=" + y); // y = 10
六、数值拆分
需求
将一个三位数拆分为个位、十位、百位后打印输出
分析
- 个位的计算:数值 % 10
- 十位的计算:数值 / 10 % 10
- 百位的计算:数值 / 10 / 10 % 10
数值位数更多时以此类推
取出数值的最高位的简化处理
- 123:百位123 / 100 = 1
- 1234:千位1234 / 1000 = 1
- 12345:万位12345 / 10000 = 1
七、switch语句
格式
switch (表达式) {
case 值1:
语句体1;
break;
case 值2:
语句体2;
break;
……
default:
语句体n+1;
break;
}
注:可以不写default语句,对语法没有影响
执行流程:
拿着switch的()中的表达式的值与case后的值匹配,
匹配成功就执行后面的代码,再由break结束掉整个switch语句。
如果给出的case都匹配失败就会执行最后的default
注意
- case后面的值不允许重复
- case后面的值只能是字面量,不能是变量
- switch的()中能接收的类型:byte、short、char、int;JDK5开始可以是枚举,JDK7开始可以是String
- case穿透现象:如果不写break跳出switch语句,代码就会一直指行下去
运行结果:public class SwitchDemo { public static void main(String[] args) { switch (1) { case 1: System.out.println(1); case 2: System.out.println(2); break; case 3: System.out.println(3); break; default: System.out.println("default"); break; } } }1 2
JDK14开始,case后面允许编写多个数据,多个数据间用
,分隔
八、原码、反码、补码
概述
我们在代码中书写的数字默认为十进制,如果要书写其它进制的数字,需要在数字前面添加标识

示例
public class ScaleDemo {
public static void main(String[] args) {
System.out.println(110); // 110
System.out.println(0b110); // 6
System.out.println(0110); // 72
System.out.println(0x110); // 272
}
}
书写二进制数字格式是从JDK7以后才有的
二进制数据共有三种状态
原码、反码、补码
1、原码
原码是数据的二进制体现形式,一个字节由8个二进制位组成
高位:二进制数据中,最左侧的数据,通过高位代表符号位。0代表正数,1代表负数
| 1 | 1 | 0 | 0 | 1 | 0 | 1 | 1 |
|---|
其余位:表示数值大小
弊端:原码与原码遇到负数运算,会出现错误。因为计算机在运算的时候,都是以二进制补码的形式在运算,而补码需要用反码推出
2、反码
正数的反码与其原码相同,而负数的反码是对其原码逐位取反,但符号位除外
若原码:
| 1 | 1 | 0 | 0 | 1 | 0 | 1 | 1 |
|---|
则反码:
| 1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 |
|---|
3、补码
正数的补码与其原码相同,而负数的补码是在其反码的末尾加1
若反码:
| 1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 |
|---|
则补码:
| 1 | 0 | 1 | 1 | 0 | 1 | 0 | 1 |
|---|
4、总结
计算机数据的存储使用二进制补码形式存储,并且最高位是符号位。
正数:最高位是0
负数:最高位是1
- 正数的原码、反码、补码都一样
- 负数的的原码、反码、补码之间的转换:
- 原码转补码:除最高位,对其余位取反再加1
- 补码转原码:除最高位,对其余位取反再加1
5、强转中的精度损失
问题
public class ScaleDemo {
public static void main(String[] args) {
int a = 130;
int b = (byte)a;
System.out.println(a); // 130
System.out.println(b); // -126
}
}
解释
- 整数130,默认为int,int占用4个字节,也就是四组8个二进制位:
00000000 00000000 00000000 10000010 - 强转到byte,4个字节强转为1个字节,就是砍掉前3组8位:
10000010 - 将上面的补码10000010推算成原码为11111110,即十进制的-126
九、跳转控制语句break、continue
break:跳出循环
范围:只能在switch语句和循环语句中使用
continue:跳过本次循环
范围:只能在循环语句中使用
补充
- 我们可以给循环起名字,即标号,在循环语句的前面写一个冒号,冒号前面写名字,一般放在循环语句的上一行
- 可以通过break和continue来使用标号,如:
break 标号名;就可以直接结束标号名代表的循环
标号示例
public static void main(String[] args) {
tagName:
while(true){
switch(number) {
case 1:
System.out.println("1");
break;
case 2:
System.out.println("2");
break tagName; // 执行到这里即可跳出while死循环
case 3:
System.out.println("3");
break;
}
}
}
十、Random随机数
Random randomNum = new Random();
int randNum = randomNum.nextInt(100) + 1; // 随机生成1~100的数
以上代码产生一个1~100的随机数
randomNum.nextInt(100)产生的随机数范围为0~99,参数100表示个数,从0开始
后面加1,即表示产生一个1~100的随机数
然后保存在randNum变量中
十一、数组介绍
数组指的是一种容器,用来存储同种数据类型的多个值
定义数组
数组的定义格式
1.数据类型[] 数组名;
2.数据类型 数组名[];
这种定义格式,定义出来的,只是数组类型的变量而已,内存中还没有创建出数组容器,需要初始化
在java中一般使用第一种定义格式
初始化
在内存中,为数组容器开辟空间,并将数据存入容器的过程
打印数组名的介绍
我们创建两个数组并分别打印数组名如下
//创建两个数组并静态初始化
int[] arr1 = {2,3,3};
double[] arr2 = {2.1,3.1,3.3};
//打印数组名
System.out.println(arr1); //打印结果:[I@50cbc42f
System.out.println(arr2); //打印结果:[D@75412c2f
得到的两个结果如下:
[I@50cbc42f
[D@75412c2f
说明:
@:就是一个分隔符,分隔数据,没别的意思
[:说明这是一个数组
I和D:和数组的数据类型有关
@后面的为十六进制数,表示数组的内存地址
注意
打印字符类型数组的数组名,不会看到地址值,而是数组中存储的数据内容
十二、数组的静态初始化
初始化就是赋值符号右边的内容
数组的静态初始化格式
1.完整格式:
数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3...};
2.简化格式:
数据类型[] 数组名 = {元素1,元素2,元素3...};
简化格式只是简化了代码书写,真正运行期间还是按照完整格式运行的,即new出来,会在堆内存开辟空间并且产生地址,数组名就代表了这个地址
十三、数组的动态初始化
数组的动态初始化,即在初始化的时候,只需要指定数组的长度,系统就会分配默认值(初始值)
数组的动态初始化格式
数据类型[] 数组名 = new 数据类型[长度];
默认值的分类
- 整数:0
- 小数:0.0
- 布尔:false
- 字符:\u0000
此为Unicode字符,一种编码格式:以 \u开头,后面加上一组十六进制数据
\u0000常见的体现是空白字符 - 字符串:null
十四、两种初始化的对比
1、两种初始化的区别
- 静态初始化
手动指定元素,系统会根据元素的个数,计算出数组的长度 - 动态初始化
手动指定长度,系统分配默认初始值
2、使用选择
- 静态初始化
如果要操作的数据,需求中已经明确给出了,使用静态初始化 - 动态初始化
只明确元素个数,不明确具体数值,使用动态初始化
十五、Java内存分配介绍
1、堆
new出来的东西会在堆内存中开辟空间并产生地址
2、栈
3、方法区
方法区是字节码文件加载时进入的内存。当我们运行一个Java程序时,会把它的字节码文件加载进方法区,此时文件中所有的方法在方法区中处于候命状态,等里面的方法被调用执行时,会进入栈内存中运行,运行完成后,方法就从栈内存中弹出去
4、本地方法栈
简单说是辅助虚拟机干活的,咱们不用管
5、寄存器
寄存器由CPU调用,咱们不用管
方法的参数传递问题
- 基本数据类型:传递的是数据值
- 引用数据类型:传递的是地址值
十六、二维数组介绍
概述
二维数组其实也是一种容器,该容器用于存储一维数组,可以理解为容器的嵌套
使用思路
若要操作的多组数据,又属于同一组数据,就可以考虑使用二维数组维护
十七、二维数组的静态初始化
二维数组的静态初始化格式
格式:
数据类型[][] 数组名 = new 数据类型[][] {{元素1, 元素2}, {元素1, 元素2}};
示例:
int[][] arr = new int[][] {{11, 22}, {33, 44}};
简化格式:
数据类型[][] 数组名 = {{元素1, 元素2}, {元素1, 元素2}};
示例:
int[][] arr = {{11, 22}, {33, 44}};
这样写格式更清晰:
int[][] arr = {
{11, 22, 33},
{44, 55, 66}
};
打印数组名得到的是二维数组的地址
int[][] arr = {
{11,22,33},
{44,55,66}
};
System.out.println(arr); // 打印结果:[[I@50cbc42f
用一维数组的方式打印输出
public static void main(String[] args) {
int[][] arr = {
{11, 22, 33},
{44, 55, 66}
};
System.out.println(arr[0]); // 打印结果:[I@50cbc42f
System.out.println(arr[1]); // 打印结果:[I@75412c2f
}
这样得到的是一维数组的地址,也就是里面的元素二维数组在存储一维数组时,它具体存储的是一维数组的地址值,所以arr[0]和arr[1]都分别是一个一维数组

可以将创建好的一维数组直接存入二维数组中
public class Test {
public static void main(String[] args) {
int[] a = {11, 22, 33};
int[] b = {44, 55, 66};
int[][] ab = new int[2][3];
ab[0] = a;
ab[1] = b;
// 遍历输出二维数组ab
for (int i = 0; i < ab.length; i++) {
for (int j = 0; j < ab[i].length; j++) {
System.out.println(ab[i][j]);
};
}
}
}
运行结果:
11
22
33
44
55
66
十八、二维数组的动态初始化
二维数组的动态初始化格式
格式:
数据类型[][] 数组名 = new 数据类型[m][n];
m表示这个二维数组,可以存放多少个一维数组
n表示每一个一维数组,可以存放多少个元素
示例:
int[][] arr = new int[3][4];
十九、面向对象三大特征
有了封装才有面向对象,有了面向对象才有继承和多态
1、封装
2、继承
3、多态
二十、类和对象
1、构造方法
概述
- 构造方法又称构造器,是创建对象的时候,自动执行的方法
- 类中若没有编写构造方法,系统会提供一个默认的、无参数的构造方法
- 构造方法也是方法,可以重载
构造方法的作用
- 本质的作用:构造所属类的实例(实例就是对象),也就是创建对象
- 结合执行时机:我们可以利用构造方法在创建对象的时候,给对象中的数据初始化。
对于本质作用的理解
当存在一个类,才有创建对象的说法,即创建该类的对象。而所有的类都可以通过new出来的形式创建对象。而每个类中都至少有一个构造方法,如果我们没提供的话,就由系统默认提供一个
public class Book { // 这是一个Book类
String id;
String bookName;
int price;
Book(){}; // Book类的无参构造方法
}
Book book = new Book(); // Book是一个类,该行就是new一个Book对象出来
2、成员变量和局部变量的区别
| 区别 | 成员变量 | 局部变量 |
|---|---|---|
| 类中位置不同 | 方法外 | 方法内 |
| 初始化值不同 | 有默认初始化值 | 没有,使用之前需完成赋值 |
| 内存位置不同 | 堆内存 | 栈内存 |
| 声明周期不同 | 随着对象的创建而存在,随着对象的消失而消失 | 随着方法的调用而存在,随着方法的运行结束而消失 |
| 作用域 | 自己所归属的大括号中 | 自己所归属的大括号中 |
3、注意
- 打印对象名,得到的是对象的内存地址。但是,目前所接触到的String、StringBuilder、ArrayList类的对象,打印对象名,得到的是元素内容,不是地址值(顺带一提,打印字符数组的数组名也得到的也是数组内容,不是地址)
- 普通变量不初始化不能使用,但成员变量就算没有赋值初始化,也可以直接使用,使用的是对象的默认值。所以创建类时,里面的成员变量一般不赋值,创建具体对象后再赋值
二十一、封装
1、封装介绍
所谓封装就是:使用类设计对象时,将需要处理的数据,以及处理这些数据的方法,设计到对象中。换句话说,就是将变量和方法写进类中,使用类创建对象后,就可以通过对象给变量赋值,此时类中的数据就封装到了对象中,对象中的数据存储在堆内存中(因为是new出来的),且对象间互不干扰,便于管理、维护
设计规范
合理隐藏,合理暴露
2、权限修饰符
权限修饰符可以修饰成员变量、成员方法
2.1、private(常用)
- private所修饰的成员(包括成员变量和成员方法)只能在同一个类中进行访问。设置为私有成员变量是为了保证数据安全性
- 被其所修饰的成员的使用范围:同一个类中
2.2、default
- 称作默认权限,在成员前什么都不写,使用的就是default
- 被其所修饰的成员的使用范围:同一个类中,同一个包中
2.3、protected
- 被其所修饰的成员的使用范围:同一个类中,同一个包中,不同包的子类
- protected修饰符在开发中用得很少
2.4、public(常用)
- 被其所修饰的成员的使用范围:在任意位置访问,不能超出模块(module)
二十二、标准JavaBean
1、基本概念
JavaBean
就是实体类,即封装数据的类。实体类只负责数据存取,而对数据的处理交给其他类来完成,即再建一个类来处理这些数据,以实现数据和数据业务处理相分离
标准JavaBean需满足的标准
- 这个类中的成员变量都要设置为私有,并且要对外提供相应的setXxx(给变量赋值的方法)和getXxx(获取变量的值的方法)方法
- 类中提供无参,带参构造方法
2、IDEA中的ptg插件
可在IDEA中一键生成标准JavaBean
步骤
在IDEA中找到File、Settings、Plugins、搜索ptg,下载即可
使用时在代码中右键,选择Ptg To JavaBean即可生成
二十三、常用API
API(Application Programming Interface)
应用程序编程接口。就是别人写好的一些类,咱们只需要创建这些类的对象,然后调用里面的方法来解决问题
API帮助文档
可以理解为是Java的一份字典,已写好的API在这个文档里都有详细介绍,只要会使用该文档,能找到自己要使用的类怎么用即可
API帮助文档使用流程
- 进入文档在左上角点击“显示”,然后在索引位置搜索自己要查看的类
- 看包
目的:是不是java.lang包(核心包),核心包不需要编写导包代码(import)。不是java.lang包,都需要编写导包代码 - 看这个类的介绍
目的:搞清楚这个给类的作用 - 看这个类的构造方法
目的:为了将该类的对象创建出来 - 看这个类的成员方法(方法摘要)
方法名
参数
返回值
介绍
二十四、String类
1、String类的特点
- Java中String类很特殊,只有它多了一种创建对象的方法。因为Jave中所有双引号字符串,都是String这个类的对象
String s = "abc"; System.out.println("abc".length()); // 结果为3 System.out.println(s.length()); // 结果为3 - 字符串在创建之后,其内容不可更改。若想改变,只能用新的字符串(即对象)来替换
String s = "abc"; // "abc"这是一个对象 s = "def"; /* 上面这行代码表示用"def"这个新的字符串对象 替换了原有的s所记录的内容,原来的"abc"其实还在,里面的内容一点没变 */ - 字符串虽然不可改变,但是其内容可以被共享。与字符串常量池有关
字符串常量池:当我们使用双引号创建字符串对象时,会检查常量池中是否存在该数据
- 不存在:创建
- 存在:复用
2、String类的一些成员方法
| 方法 | 说明 |
|---|---|
| public boolean equals(要比较的字符串) | (字符串比较)将此字符串与指定的对象比较,完全一样结果才true,否则为false |
| public boolean equalsIgnoreCase(要比较的字符串) | (字符串比较)与上面的一样,但是不考虑大小写 |
| public char[] toCharArray() | (字符串的遍历:调用后遍历字符数组即可)将字符串转换为字符数组 |
| public char charAt(int index) | (字符串的遍历:多调用几次)返会指定索引处的char值,即根据索引找字符串中的字符 |
| public int length() | 返回此字符串的长度(这是String类中的一个方法,与数组中的“length”不同,数组中的length是数组的一个属性) |
| public String subString(int beginIndex) | (截取)返回一个新的字符串,它是此字符串的一个子字符串,该方法会一直截取到字符串的末尾 |
| public String subString(int beginIndex, int endIndex) | (截取)subString方法的重载,根据传入的开始和结束索引,对字符串做截取。截取范围:包含头,不包含尾 |
| public String replace(有两个参数) | (替换)参数1:旧值,参数2:新值。用新值替换旧值 |
| public String[] split(String regex) | (切割)根据传入的字符串作为规则,切割当前字符串 |
二十五、StringBuilder类
1、StringBuilder引入
- 为什么要有StringBuilder?一句话,它可以提高字符串的操作效率
2、StringBuilder介绍
-
和String一样,同属于java.lang包下,不需要编写导包代码
-
它是一个可变的字符序列,也是与String最大的区别,因为String的内容不可更改
-
StringBuilder是字符串的缓冲区,我们可以将其理解为是容器,这个容器可以存储任意数据类型,但是只要进入到这个容器,全部变成字符串
3、StringBuilder类的常用成员方法
| 方法名 | 说明 |
|---|---|
| public StringBuilder append(任意类型) | 添加数据,并返回对象本身,所以可以实现链式编程 |
| public StringBuilder reverse() | 反转容器中的内容 |
| public int length() | 返回长度(字符出现的个数) |
| public String toString() | 可以把StringBuilder类型转换为String类型 |
二十六、集合
1、集合介绍
集合是一种容器,用来装数据的,类似于数组。数组定义完成并启动后,长度就固定了,而集合的大小可变,开发中用的更多。Java中的集合有很多,一大堆,先介绍ArrayList
2、ArrayList集合
长度可变的原理初认识
- 它的底层也是数组。当创建ArrayList集合容器的时候,底层会存在一个长度为10的空数组
- 当装不下时,他会自动扩容一个大小为原数组1.5倍的一个新数组,并将所有原数组数据拷贝到新数组中。随后,原来的数组会变成内存中的垃圾,会被Java的垃圾回收器不定时的情理掉,也就是释放这块内存
3、ArrayList集合的使用
ArrayList list = new ArrayList(); // 创建一个ArrayList对象
list.add("Lisa"); // 调用add方法添加数据
ArrayList这个类把数据封装的很好,我们调用者不需要关心底层数组,只用关心真实数据有几个就可以了
用以上方法创建集合可以添加任意数据类型,但数据不够严谨
可以按以下方式创建,只需要在类型后加一对尖括号,指明数据类型即可。从JDK7版本开始,= 右边的类型尖括号的里面可以不用写类型了,尖括号会自动匹配到左边的类型
ArrayList<String> list = new ArrayList<String>(); // 创建一个ArrayList对象,并指定只能存储String类型的元素
list.add("Lisa"); // 调用add方法添加数据
/*
<> 这个尖括号叫做泛型
泛型中不允许编写基本数据类型,但是,
可以使用基本数据类型所对应的包装类,包装类就是将基本数据类型包装成类,变成引用数据类型
基本数据类型 包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
boolean Boolean
char Character
*/
4、ArrayList集合的常用成员方法
| 方法名 | 说明 |
|---|---|
| public boolean add(E e) | (增)将指定的元素添加到此集合的末尾 |
| public void add(int index, E element) | (增)在此集合中的指定位置插入指定的元素 |
| public E remove(int index) | (删)删除指定索引处的元素,返回被删除的元素 |
| public boolean remove(Object o) | (删)删除时指定的元素,返回删除是否成功 |
| public E set(int index, E element) | (改)修改指定索引处的元素,返回被修改的元素 |
| public E get(int index) | (查)返回指定索引处的元素 |
| public int size() | (查)返回集合中的元素个数 |
注意一个问题
删除ArrayList中的元素时注意,集合不会让元素中间有空缺,删除一个元素后,集合会把后面的元素整体向前进行移动,所以当要删除的元素连在一起时,会漏删
解决方法
- 使用正序遍历删除,在删除操作后面增加一个减减(
--)操作 - 使用倒序遍历操作
二十七、泛型
1、泛型概述
介绍
- 泛型是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查
- 没有泛型的时候或者如果我们没有给集合指定数据类型,默认所有的数据类型都是Object类型,此时可以往集合中添加任意数据类,弊端就是我们在获取数据的时候,无法使用它的特有行为,推出了泛型后,可以在添加数据的时候把类型统一
格式
<数据类型>:指定一种类型的格式,尖括号里面可以任意书写,一般只写一个字母,例如:<E><类型1, 类型2…>:指定多种类型的格式,多种类型之间用逗号隔开,例如:<E, T>
注意
- 泛型只能支持引用数据类型,想写基本数据类型,需要写对应的包装类
- Java中的泛型其实是伪泛型(泛型的擦除)
泛型的好处
- 把运行时期的问题提前到了编译期间
- 避免了强制类型转换可能出现的异常
泛型可以在很多地方进行定义
- 写在类后面,就叫泛型类【写在类名后面】
- 写在方法上面,就叫泛型方法【写在返回值类型前面】
- 写在接口后面,就叫泛型接口【写在接口名后面】
2、泛型类
写一个MyArrayList
import java.util.Arrays;
public class MyArrayListTest {
public static void main(String[] args) {
MyArrayList<String> ml = new MyArrayList<>();
ml.add("a");
ml.add("b");
ml.add("c");
System.out.println(ml.get(0));
System.out.println(ml);
}
}
// 当编写一个类时,如果某个变量的数据类型不确定时,就可以定义带有泛型的类
class MyArrayList<E> {
Object[] obj = new Object[10]; // 初始化大小为10的数组
int size; // 类中定义的,默认为0
// 添加元素
public boolean add(E e) {
obj[size] = e;
size++;
return true;
}
// 根据索引获取元素
public E get(int index) {
return (E)obj[index];
}
@Override
public String toString() {
return "MyArrayList{" +
"obj=" + Arrays.toString(obj) +
'}';
}
}
运行结果:
a
MyArrayList{obj=[a, b, c, null, null, null, null, null, null, null]}
3、泛型方法
类中方法的形参类型不确定时,可以使用类名后面定义的泛型<E>,也可以在方法声明上定义自己的泛型
格式
修饰符 <类型> 返回值类型 方法名(类型 变量名) {
}
示例
public static <E> void fun(E e) {
}
4、泛型接口
泛型接口的两种使用方式
- 实现类给出具体类型
- 实现类延续泛型,创建实现类对象时再确定类型
实现类给出具体类型
class MyArrayList2 implements List<String> {
// 重写方法的代码
}
实现类延续泛型,创建实现类对象时再确定类型
class MyArrayList3<E> implements List<E> {
// 重写方法的代码
}
5、泛型的继承和通配符
泛型的继承
泛型不具备继承性,但是数据具备继承性
泛型的通配符:?(就是一个问号)
- 表示不确定的类型
- 可以限定类型的范围
? extends E:表示可以传递E或E的所有子类类型? super E:表示可以传递E或E的所有父类类型
应用场景
- 如果我们在定义类、方法、接口的时候,类型不确定,就可以定义泛型类,泛型方法,泛型接口
- 如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可使用泛型的通配符
二十八、集合进阶
集合体系结构
-
Collection(单列集合)
在添加数据时每次只添加一个数据 -
Map(双列集合)
在添加数据时每次添加一对数据

二十八-1、Collection单列集合(接口)
1、Collection集合概述
Collection集合体系
- Collection为接口,List和Set是它的子接口,其余分别为它们的实现类
- 其中Vector已淘汰,直接无视它

Collection集合概述
- 是单例集合的顶层(祖宗)接口,它表示一组对象,这些对象也称为Collection的元素,它的功能可以被全部单列集合继承使用
- JDK 不提供此接口的任何直接实现,它提供更具体的子接口(如Set和List)实现
List系列集合添加元素的特点
- 有序:存和取的顺序是一样的
- 可重复:集合中存储的元素可以重复
- 有索引:可以通过索引获取集合中的每一个元素
Set系列集合添加元素的特点
- 无序:存和取的顺序有可能不一样
- 不可重复:集合中不能存储重复的元素(可用于数据的去重)
- 无索引:不能通过索引获取元素
创建Collection集合的对象
- 多态的方式
- 具体的实现类,如ArrayList
Collection集合常用方法
| 方法名 | 说明 |
|---|---|
| boolean add(E e) | 添加元素 |
| boolean remove(Object o) | 从集合中移除指定的元素,删除成功返回ture,删除失败返回false |
| boolean removeIf(Object o) | 根据条件进行移除 |
| void clear() | 清空集合中的元素 |
| boolean contains(Object o) | 判断集合中是否存在指定的元素,底层是依赖equals方法进行判断是否存在,所有,如果集合中存储的是自定义对象,要在JavaBean类中重写equals方法 |
| boolean isEmpty() | 判断集合是否为空 |
| public int size() | 集合的长度,也就是集合中元素的个数 |
对于Collection单列集合只需关注以下五种
方框中是对名字的解释,方便记忆理解

2、Collection集合的遍历
Collection集合的遍历方式有三种
- 迭代器遍历
- 增强for遍历
- Lambda表达式遍历
2.1、迭代器遍历
迭代器介绍
- 迭代器不依赖索引
- 迭代器是集合的专用遍历方式
- 迭代器在Java中的类是Iterator,是一个接口
- 通过
Iterator<E> iterator()方法获取迭代器,方法返回迭代器对象,默认指向当前集合的0索引 - Iterator中的常用方法
| 方法名 | 说明 |
|---|---|
| boolean hasNext() | 判断当前位置是否有元素,有返回ture,没有返回false |
| E next() | 获取当前位置的元素,并将迭代器对象移向下一个索引位置 |
示例
public class IteratorDemo1 {
public static void main(String[] args) {
// 创建集合对象
Collection<String> c = new ArrayList<>();
// 添加元素
c.add("hello");
c.add("world");
c.add("java");
c.add("javaee");
// Iterator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到
Iterator<String> it = c.iterator();
// 用while循环改进元素的判断和获取
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
}
}
运行结果:
hello
world
java
javaee
注意
- 使用迭代器进行遍历时,hashNext()方法和next()方法要配套使用,而且是一一对应使用
- 迭代器遍历完毕,指向的位置不会复位,再使用只能获取一个新的迭代器对象
- 迭代器遍历时,不能用集合的方法进行增加或者删除,可以用迭代器提供的
remove()方法进行删除
迭代器中删除的方法remove()可以删除迭代器对象当前指向的元素
public class IteratorDemo2 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("b");
list.add("c");
list.add("d");
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
if("b".equals(s)){
//指向谁,那么此时就删除谁.
it.remove();
}
}
System.out.println(list);
}
}
运行结果:
[a, c, d]
2.2、增强for遍历
-
介绍
- 增强for的底层就是迭代器,为了简化迭代器的代码书写
- 它是JDK5之后出现的,其内部原理是一个Iterator迭代器
- 实现Iterable接口的类才可以使用迭代器和增强for
- 所有的数组和单列集合Collection才能用增强for遍历
-
格式
for(元素的数据类型 变量名 : 数组或集合) { // 已经将当前遍历到的元素封装到变量中了,直接使用变量即可 } -
代码
public class MyCollectionDemo1 { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); list.add("d"); list.add("e"); list.add("f"); // 数据类型一定是集合或者数组中元素的类型 // str仅仅是一个变量名而已,在循环的过程中,依次表示集合或者数组中的每一个元素 // list就是要遍历的集合或者数组 for(String str : list){ System.out.println(str); } } }运行结果:
a b c d e f
2.3、Lambda表达式遍历
得益于JDK8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。Lambda表达式遍历就是使用了一个forEach方法进行遍历
foreach方法
default void forEach(Consumer<? super T> action) 结合lambda表达式进行遍历
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
public class CollectionDemo1 {
public static void main(String[] args) {
// 创建集合并添加元素
Collection<String> coll = new ArrayList<>();
coll.add("Pig");
coll.add("Dog");
coll.add("Cat");
// 先使用匿名内部类的形式
coll.forEach(new Consumer<String>() {
@Override
// s依次表示集合中的每一个数据
public void accept(String s) {
System.out.println(s);
}
});
// 简化的lambda表达式形式
coll.forEach( s -> System.out.println(s)
);
}
}
/*
forEach底层原理:
方法的底层其实也会自己遍历集合,依次得到每一个元素
把得到的每一个元素,传递给下面的accept方法
s依次表示集合中的每一个数据
*/
运行结果:
Pig
Dog
Cat
Pig
Dog
Cat
3、List集合(接口)
3.1、List的特有方法
方法介绍
| 方法名 | 描述 |
|---|---|
| void add(int index, E element) | 在此集合中的指定位置插入指定的元素 |
| E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
| E set(int index, E element) | 修改指定索引处的元素,返回被修改的元素,原索引处的元素依次后移 |
| E get(int index) | 返回指定索引处的元素 |
示例
import java.util.ArrayList;
import java.util.List;
public class ListDemo1 {
public static void main(String[] args) {
// 创建一个集合
List<String> list = new ArrayList<>();
// 添加元素
list.add("aaa");
list.add("bbb");
list.add("ccc");
// 在索引1处添加元素,原来的元素依次后移
list.add(1,"X");
// 删除索引为3的元素
list.remove(3);
System.out.println(list);
}
}
运行结果:
[aaa, X, bbb]
3.2、List的遍历
List集合的遍历方式
- 迭代器遍历
- 列表迭代器遍历(ListIterator接口)
- 增强for遍历
- Lambda表达式遍历
- 普通for循环(因为List集合存在索引)
其中列表迭代器遍历和普通for循环遍历是List集合特有的遍历方式
五种遍历方式对比

列表迭代器遍历(ListIterator接口)和增强for遍历示例
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ListDemo2 {
public static void main(String[] args) {
// 创建集合对象
List<String> list = new ArrayList<>();
// 添加元素
list.add("aaa");
list.add("bbb");
list.add("ccc");
// 普通for循环遍历
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
System.out.println("--------");
// 列表迭代器(ListIterator接口)
// 它额外添加了一个方法,在遍历的过程中,可以添加元素
ListIterator<String> it = list.listIterator();
while (it.hasNext()) {
String str = it.next();
if ("bbb".equals(str)) {
// 只能用迭代器的方法添加元素
it.add("qqq");
}
}
System.out.println(list);
}
}
运行结果:
aaa
bbb
ccc
--------
[aaa, bbb, qqq, ccc]
3.3、List子类的特点
ArrayList集合
底层是数组结构实现,查询快、增删慢
LinkedList集合
底层是链表结构实现,查询慢、增删快
3.4、ArrayList集合(实现类)
底层原理
- 利用空参构造创建的集合,在底层创建一个默认长度为0的数组
- 添加第一个元素时,底层会创建一个新的长度为10的数组
- 存满时,会扩容1.5倍
- 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准
3.5、LinkedList集合(实现类)
LinkedList集合的底层数据结构是双链表,查询慢,增删快,但是如果操作的是首位元素,速度也很快
LinkedList集合的特有方法
| 方法名 | 说明 |
|---|---|
| public void addFirst(E e) | 在该列表开头插入指定的元素 |
| public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
| public E getFirst() | 返回此列表中的第一个元素 |
| public E getLast() | 返回此列表中的最后一个元素 |
| public E removeFirst() | 从此列表中删除并返回第一个元素 |
| public E removeLast() | 从此列表中删除并返回最后一个元素 |
4、Set集合(接口)
4.1、概述
在Collection体系中的位置

Set集合的实现类
- HashSet:无序、不重复、无索引
- LinkedHashSet:有序、不重复、无索引
- TreeSet:可排序、不重复、无索引
4.2、HashSet集合(实现类)
HashSet的特点
- HashSet集合底层采取哈希表存储数据
- 哈希表是一种对于增删改查数据性能都较好的结构
哈希表组成
- JDK8之前:数值 + 链表
- JDK8开始:数据 + 链表 + 红黑树
哈希值
- 哈希值可以理解为对象的整数表现形式
- 哈希值是根据hashCode方法算出来的int类型的整数
- 该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
- 一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值
对象的哈希值的特点
- 如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
- 如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
- 在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希碰撞)
如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
public class HashSetDemo1 {
public static void main(String[] args) {
// 创建对象
Student s1 = new Student(18, "M");
Student s2 = new Student(18, "M");
// 如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
System.out.println(s1.hashCode()); // 990368553
System.out.println(s2.hashCode()); // 1096979270
}
}
// 标准JavaBean
class Student {
private int age;
private String name;
public Student() {
}
public Student(int age, String name) {
this.age = age;
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
public String toString() {
return "Student{age = " + age + ", name = " + name + "}";
}
}
如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
import java.util.Objects;
public class HashSetDemo1 {
public static void main(String[] args) {
// 创建对象
Student s1 = new Student(18, "M");
Student s2 = new Student(18, "M");
// 如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
System.out.println(s1.hashCode()); // 1596
System.out.println(s2.hashCode()); // 1596
}
}
// 标准JavaBean
class Student {
private int age;
private String name;
public Student() {
}
public Student(int age, String name) {
this.age = age;
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(age, name);
}
public String toString() {
return "Student{age = " + age + ", name = " + name + "}";
}
}
HashSet集合存储自定义类型元素,要想实现元素的唯一,要求必须重写hashCode方法和equals方法
4.3、LinkedHashSet集合(实现类)
LinkedHashSet集合是HashSet集合的子类
LinkedHashSet的特点
- 有序、不重复、无索引
- 这里的有序指的是保证存储和取出的元素顺序一致
- 原理:底层数据结构依然是哈希表,只是每个元素又额外多了一个双链表,用来记录存储的顺序
注意
如果要数据去重,一般使用HashSet,如果要求去重且存取有序,才使用LinkedHashSet
4.4、TreeSet集合(实现类)
TreeSet的特点
- 可排序、不重复、无索引
- 可排序:对于数值类型,按照元素的默认规则(由小到大)排序;对于字符、字符串类型,按照字符在ASCII码表中的数字升序进行排序
- TreeSet集合底层是基于红黑树实现排序的,增删改查的性能都比较好
TreeSet的排序规则
- 方式一:默认排序(自然排序),空参构造
TreeSet()
JavaBean类实现Comparable接口指定比较规则 - 方式二:比较器排序,带参构造
TreeSet(Comparator comparator)
创建TreeSet对象的时候传递Comparator的实现类对象,重写compare方法,根据返回值进行排序
使用原则
- 一般使用第一种,如果第一种不能满足当前需求,就使用第二种
- 如果两种方式都存在,Java会使用第二种
空参构造示例1
import java.util.TreeSet;
public class TreeSetDemo {
public static void main(String[] args) {
// 使用空参构造创建TreeSet集合对象
TreeSet<Integer> ts = new TreeSet<>();
// 添加元素
ts.add(4);
ts.add(5);
ts.add(1);
ts.add(3);
ts.add(2);
// 打印集合
System.out.println(ts);
}
}
运行结果:
[1, 2, 3, 4, 5]
空参构造示例2
import java.util.TreeSet;
public class TreeSetDemo2 {
public static void main(String[] args) {
// 创建集合对象
TreeSet<Person> ts = new TreeSet<>();
// 创建自定义类对象,实现了Comparable<>泛型类,按年龄从小到大排序
Person p1 = new Person("zhangsan", 13);
Person p2 = new Person("lisi", 14);
Person p3 = new Person("wangwu", 15);
// 添加元素
ts.add(p3);
ts.add(p2);
ts.add(p3); // 重复元素,不会添加
ts.add(p1);
// 打印集合
System.out.println(ts);
}
}
// 自定义类实现Comparable泛型接口,重写compareTo方法,根据返回值进行排序
class Person implements Comparable<Person>{
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
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 String toString() {
return "Person{name = " + name + ", age = " + age + "}";
}
@Override
// 重写compareTo方法,按年龄升序排序
public int compareTo(Person o) {
return this.getAge() - o.getAge();
}
/*
this:表示当前要添加的元素
o:表示已经在红黑树中存在的元素
如果返回值为负数,表示当前存入的元素是较小值,存红黑树的左边
如果返回值为0,表示当前存入的元素跟集合中元素重复了,不存
如果返回值为正数,表示当前存入的元素是较大值,存红黑树的右边
*/
}
运行结果:
[Person{name = zhangsan, age = 13}, Person{name = lisi, age = 14}, Person{name = wangwu, age = 15}]
带参构造示例
import java.util.Comparator;
import java.util.TreeSet;
public class Test5 {
public static void main(String[] args) {
Person p1 = new Person("zs", 18);
Person p2 = new Person("ls", 28);
Person p3 = new Person("ww", 38);
// 创建集合对象,使用比较器排序
TreeSet<Person> ts = new TreeSet<>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge(); // 按年龄升序
}
// o1:表示当前要添加的元素
// o2:表示已经在红黑树中存在的元素
});
// 添加元素
ts.add(p2);
ts.add(p1);
ts.add(p3);
// 打印集合
System.out.println(ts);
}
}
// 标准JavaBean
class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
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 String toString() {
return "Person{name = " + name + ", age = " + age + "}";
}
}
运行结果:
[Person{name = zs, age = 18}, Person{name = ls, age = 28}, Person{name = ww, age = 38}]
5、总结
-
如果想要集合中的元素可重复
用ArrayList集合,基于数组(用的最多) -
如果想要集合中的元素可重复,而且当前的增删操作明显多于查询
用LinkedList集合,基于链表 -
如果想对集合中的元素去重
用HashSet集合,基于哈希表(用的最多) -
如果想对集合中的元素去重,而且保证存取有序
用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet -
如果想对集合中的元素进行排序
用TreeSet集合,基于红黑树
二十八-2、Map双列集合(接口)
1、Map集合概述
Map集合的特点
- 双列集合一次需存一对数据,分别为键和值,并且一个键对应一个值
- 键不可以重复,值可以重复
- 一个键和一个值组成的整体称为键值对,也叫键值对对象,在Java中也叫Entry对象
Map体系
其中Hashtable和Properties与IO相关,不在此介绍

Map的常见方法
| 方法名 | 说明 |
|---|---|
| V put(K key, V value) | 添加元素,如果键不存在,直接把键值对添加到Map集合中,返回null;如果键存在,会把原有的键值对对象覆盖,并把覆盖的值返回 |
| V remove(Object key) | 根据键删除键值对元素,并把值返回 |
| void clear() | 移除所有的键值对元素 |
| boolean containsKey(Object key) | 判断集合是否包含指定的键 |
| boolean containsValue(Object value) | 判断集合是否包含指定的值 |
| boolean isEmpty() | 判断集合是否为空 |
| int size() | 集合的长度,也就是集合中键值对的个数 |
示例
import java.util.HashMap;
import java.util.Map;
public class MapDemo1 {
public static void main(String[] args) {
// 创建Map集合的对象
Map<String, String> m = new HashMap<>();
// 添加元素
m.put("1", "One");
m.put("2", "Two");
System.out.println(m.put("3", "Four"));
System.out.println(m.put("3", "Three"));
// 打印集合
System.out.println(m);
// 删除元素
System.out.println(m.remove("1"));
System.out.println(m);
// 清空元素
m.clear();
System.out.println(m);
}
}
运行结果:
null
Four
{1=One, 2=Two, 3=Three}
One
{2=Two, 3=Three}
{}
2、Map集合的遍历
Map集合的遍历方式有三种
- 键找值
- 键值对
- Lambda表达式
2.1、键找值
所谓键找值就是通过键找值,步骤如下
- 获取所有的键,把这些键放到一个单列集合中
- 遍历单列集合,得到每一个键(可以用迭代器,增强for,Lambda表达式)
- 利用键获取对应的值
相关方法
V get(Object key) 根据键获取值
示例
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapDemo2 {
public static void main(String[] args) {
// 创建Map集合对象
Map<String, String> m1 = new HashMap<>();
// 添加元素
m1.put("1", "One");
m1.put("2", "Two");
m1.put("3", "Three");
// 通过键找值遍历
// 1、获取所有的键,把这些键放到一个单列集合中
Set<String> keys = m1.keySet();
// 2、遍历单列集合,得到每一个键,这里用增强for
for(String key : keys) {
System.out.print("键:" + key);
// 3、利用键获取对应的值,用get方法
System.out.println(" 值:" + m1.get(key));
}
}
}
运行结果:
键:1 值:One
键:2 值:Two
键:3 值:Three
2.2、键值对
所谓键值对就是通过键值对对象进行遍历,先依次获取所有的键值对对象,再通过getKey()方法获取里面的键,通过getValue()方法获取里面的值
相关方法
Set<Map.Entry<K, V>> entrySet()获取所有键值对对象的集合V getValue()返回与此项对应的值K getKey()返回与此项对应的键
示例
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapDemo3 {
public static void main(String[] args) {
// 创建Map集合对象
Map<String, String> m1 = new HashMap<>();
// 添加元素
m1.put("1", "One");
m1.put("2", "Two");
m1.put("3", "Three");
// 通过键值对遍历
// 1、通过一个entrySet()方法获取所有的键值对对象
Set<Map.Entry<String, String>> entries = m1.entrySet();
// 2、遍历entries这个集合,得到里面的每一个键值对对象
for (Map.Entry<String, String> entry : entries) {
// 用entry调用get方法分别获取键和值
String key = entry.getKey();
String value = entry.getValue();
System.out.println("键:" + key + " 值:" + value);
}
}
}
运行结果:
键:1 值:One
键:2 值:Two
键:3 值:Three
2.3、Lambda表达式
相关方法
default void forEach(BiConsumer<? super K, ? super V> action) 结合lambda遍历Map集合
forEach底层其实就是利用第二种方式进行遍历,依次得到每一个键和值,再调用accept方法
示例
import java.util.HashMap;
import java.util.Map;
//import java.util.function.BiConsumer;
public class MapDemo4 {
public static void main(String[] args) {
// 创建Map集合对象
Map<String, String> map = new HashMap<>();
// 添加元素
map.put("1", "One");
map.put("2", "Two");
map.put("3", "Three");
/* 先使用匿名内部类遍历
map.forEach(new BiConsumer<String, String>() {
@Override
public void accept(String key, String value) {
System.out.println("键:" + key + " 值:" + value);
}
});
*/
// 利用Lambda表达式遍历
map.forEach(
(key, value) -> System.out.println("键:" + key + " 值:" + value)
);
}
}
运行结果:
键:1 值:One
键:2 值:Two
键:3 值:Three
3、HashMap集合(实现类)
HashMap的特点
- 由键决定:无序、不重复、无索引
- HashMap和HashSet一样,底层都是哈希表结构
- 当创建一个HashMap对象时,在顶层,会创建一个长度为16,
- 依赖hashCode方法和equals方法保证键的唯一
- 如果
键存储的是自定义对象,需要重写hashCode和equals方法,但是如果值存储的是自定义对象,就不需要写hashCode和equals方法
4、LinkedHashMap集合(实现类)
LinkedHashMap的特点
- LinkedHashMap是HashMap的子类
- 由键决定:有序、不重复、无索引
- 有序指的是存储和取出元素的顺序一致
- 原理:底层数据结构依然是哈希表,只是每个键值对元素又额外多了一个双链表,用来记录存储的顺序
5、TreeMap集合(实现类)
TreeMap的特点
- TreeMap底层和TreeSet一样,也是红黑树结构
- 由键决定特性:不重复,无索引,可排序
- 默认按照键的大小进行排序,也可以自己规定键的排序规则
两种排序规则
- 实现Comparable接口,指定比较规则
- 创建集合时传递Comparator比较器对象,指定比较规则
二十八-3、其它
1、可变参数
在JDK5之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以对其简化
格式
数据类型...args
底层
可变参数底层其实就是一个数组,只不过不需要我们自己创建,Java会帮我们创建好,使用时就当成数组用
示例
public class ArgsDemo {
public static void main(String[] args) {
System.out.println(getSum(1, 3, 56, 13, 2)); // 75
}
// 参数为可变参数,完成求和功能
public static int getSum(int... args) {
int sum = 0;
for (int i = 0; i < args.length; i++) {
sum += args[i];
}
return sum;
}
}
注意
- 在方法的形参中只能有一个可变参数
- 如果方法中有多个参数,可变参数要放到最后
2、Collections工具类
java.utils.Collections是集合的工具类,注意和Colection单列集合区分
常用方法
public static <T> boolean addAll(Collection<T> c, T... elements):批量添加元素public static void shuffle(List<?> list):打乱List集合元素的顺序public static <T> void sort(List<T> list):将集合中元素按照默认规则排序public static <T> void sort(List<T> list, Comparator<T> ):根据指定的规则进行排序
3、不可变集合
3.1、什么是不可变集合
不可变集合就是不可以被修改的集合,创建完成后,长度不能改,内容也不能改
应用场景
- 如果某个数据不能被修改,把它防御性第拷贝到不可变集合中是个很好的选择
- 当集合对象被不可信的库调用时,不可变形式是安全的
- 简单理解就是,如果不想让别人修改集合中的内容,就可以提供一个不可变集合,别人拿到这个不可变集合就只能进行查询操作,不能修改
不可变集合分类
在List、Set、Map接口中,都存在静态的of方法,可以获取一个不可变的集合
static <E> List<E> of(E... elements):创建一个指定元素的List集合对象static <E> Set<E> of(E... elements):创建一个指定元素的Set集合对象static <K, V> Map<K, V> of(E... elements):创建一个指定元素的Map集合对象
3.2、不可变的list集合
static <E> List<E> of(E... elements):创建一个指定元素的List集合对象
示例
import java.util.Iterator;
import java.util.List;
public class ImmutableDemo {
public static void main(String[] args) {
// 创建不可变的List集合,不能修改
List<String> list = List.of("Li", "Mi", "Xi");
// 可以查询
System.out.println(list.get(0));
System.out.println(list.get(1));
System.out.println(list.get(2));
System.out.println("-----分隔线-----");
// 可以遍历
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
运行结果:
Li
Mi
Xi
-----分隔线-----
Li
Mi
Xi
3.3、不可变的Set集合
static <E> Set<E> of(E... elements):创建一个指定元素的Set集合对象
示例
import java.util.Set;
public class ImmutableDemo2 {
public static void main(String[] args) {
// 创建不可变的Set集合,不能修改
Set<String> set = Set.of("Li", "Mi", "Xi"); // 参数要唯一
// 可以遍历
for (String s : set) {
System.out.println(s);
}
}
}
运行结果:
Xi
Li
Mi
3.4、不可变的Map集合
static <K, V> Map<K, V> of(E... elements):创建一个指定元素的Map集合对象
示例
import java.util.Map;
import java.util.Set;
public class ImmutableDemo3 {
public static void main(String[] args) {
// 创建不可变的Map集合,不能修改
Map<String, String> map = Map.of(
"浙江", "浙",
"江苏", "苏",
"上海", "沪",
"北京", "京"
); // 参数有上限,最多20个,即10对键值对
// 遍历
Set<String> keys = map.keySet();
for (String key : keys) {
System.out.println(key + "=" + map.get(key));
}
}
}
注意
Map.of方法的参数最多20个,也就是10对键值对- 如果要将参数也设计为可变参数,那么当我们传参的时候,键和值都是可变参数,但是,一个方法的可变参数只能有一个,因为可变参数要放在参数列表的最后,两个可变参数不可能同时在最后的位置
如果我们要传的参数大于20个怎么办?思路是将键值对看作一个整体,有两种方式
- JDK10以前:
public static <K, V> Map<K, V> ofEntries(Map.Entry<? extends K, ? extends V>... entries) - JDK10及以后:
public static <K, V> Map<K, V> copyOf(Map<? extends K, ? extends V> map)
示例
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class ImmutableDemo4 {
public static void main(String[] args) {
// 创建普通的Map集合
Map<String, String> hm = new HashMap();
// 添加元素
hm.put("浙江", "浙");
hm.put("江苏", "苏");
hm.put("上海", "沪");
hm.put("北京", "京");
hm.put("四川", "川");
// 利用上面的数据来获取一个不可变集合
// 1、获取所有键值对对象(Entry对象)
Set<Map.Entry<String, String>> entries = hm.entrySet();
// 2、转换成数组
Map.Entry[] arr = entries.toArray(new Map.Entry[0]);
// toArray方法在底层会比较集合的长度跟数组的长度,
// 如果集合的长度 > 数组的长度:数据在数组中放不下,此时会根据实际数据的个数重新创建数据
// 如果集合的长度 < 数组的长度:数据在数组中放得下,不会创建新数据,而是直接用
// 3、使用ofEntries方法,map就是一个不可变集合
Map map = Map.ofEntries(arr);
// 上面3步的简化写法,map2就是一个不可变集合
Map<Object, Object> map2 = Map.ofEntries(hm.entrySet().toArray(new Map.Entry[0]));
// JDK10开始的copyOf方法,map3就是一个不可变集合
Map<String, String> map3 = Map.copyOf(hm);
}
}
4、Stream流(接口)
Stream流的作用
结合了Lambda表达式,简化集合、数组的操作
4.1、Stream流初体验
案例示例
/*
创建集合添加元素,完成以下需求:
1、把所有以“张”头的元素存储到新集合中
2、把以“张”开头的,长度为3的元素存储到新集合中
3、遍历打印最终结果
*/
import java.util.ArrayList;
public class StreamDemo1 {
public static void main(String[] args) {
// 创建集合
ArrayList<String> list1 = new ArrayList<>();
// 添加元素
list1.add("张无忌");
list1.add("周芷若");
list1.add("赵敏");
list1.add("张强");
list1.add("张三丰");
// 1、把所有以“张”头的元素存储到新集合中
ArrayList<String> list2 = new ArrayList<>();
for (String name : list1) {
if (name.startsWith("张")) {
list2.add(name);
}
}
// 2、把以“张”开头的,长度为3的元素存储到新集合中
ArrayList<String> list3 = new ArrayList<>();
for (String name : list2) {
if (name.length() == 3) {
list3.add(name);
}
}
// 3、遍历打印最终结果
for (int i = 0; i < list3.size(); i++) {
System.out.println(list3.get(i));
}
System.out.println("-----分隔线-----");
// 使用Stream流
list1.stream().filter(name -> name.startsWith("张")).filter(name -> name.length() == 3).forEach(name -> System.out.println(name));
}
}
运行结果:
张无忌
张三丰
-----分隔线-----
张无忌
张三丰
4.2、获取Stream流
Stream流的使用步骤
- 先得到一条Stream流(流水线),并把数据放上去
- 使用
中间方法对流水线上的数据进行操作 - 使用
终结方法对流水线上的数据进行操作
中间方法和终结方法
- 中间方法:方法调用完毕后,还可以调用其它方法,如:过滤,转换
- 终结方法 :Stream流中的最后一步,调用完毕后,不能调用其它方法,如:输出,打印
生成Stream流的方式
- Collection单列集合
使用Collection中的默认方法:default Stream<E> stream() - Map双列集合
无法直接使用Stream流,需要通过keySet()或entrySet()先转成Collection单列集合,再获取Stream流 - 数组
通过Arrays工具类中的静态方法Arrays.stream() - 同种数据类型的多个数据(一堆零散数据)
通过Stream接口中的静态方法public static<T> Stream<T> of(T... values)
Collection单列集合获取Stream流示例
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Consumer;
import java.util.stream.Stream;
public class StreamDemo2 {
public static void main(String[] args) {
// Collection单列集合获取Stream流
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "a", "b", "c", "b", "e", "f");
// 获取到一条流水线,并把集合中的数据放到流水线上
Stream<String> stream1 = list.stream();
stream1.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
// s依次表示流水线上的每一个数据
System.out.print(s + " ");
}
});
System.out.println();
// 一般按下面这种写法
list.stream().forEach(s -> System.out.print(s + " "));
}
}
运行结果:
a b c b e f
a b c b e f
Map双列集合获取Stream流示例
import java.util.HashMap;
public class StreamDemo3 {
public static void main(String[] args) {
HashMap<String, Integer> hm = new HashMap<>();
hm.put("aaa", 111);
hm.put("bbb", 222);
hm.put("ccc", 333);
hm.put("ddd", 444);
hm.put("eee", 555);
// 获取Stream流,第一种方式
hm.keySet().stream().forEach(s -> System.out.println(s));
// 获取Stream流,第二种方式
hm.entrySet().stream().forEach(s -> System.out.println(s));
}
}
运行结果:
aaa
ccc
bbb
eee
ddd
aaa=111
ccc=333
bbb=222
eee=555
ddd=444
数组获取Stream流示例
import java.util.Arrays;
public class StreamDemo4 {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
// 获取Stream流
Arrays.stream(arr). forEach(s -> System.out.println(s));
}
}
运行结果:
1
2
3
4
5
同种数据类型的多个数据(一堆零散数据)获取Stream流示例
import java.util.stream.Stream;
public class StreamDemo5 {
public static void main(String[] args) {
// 基本数据类型
Stream.of(1, 2, 3, 4, 5).forEach(s -> System.out.print(s + " "));
System.out.println();
// 引用数据类型
Stream.of("a", "b", "c", "d").forEach(s -> System.out.print(s + " "));
/*
Stream接口中静态方法of的细节:
方法的形参是一个可变参数,可以传递一堆零散的数据,也可以传递数组
但是数组必须是引用数据类型的,如果是基本数据类型,会把整个数组当成一个元素,放到Stream中
所以一般不要用of方法传递数组
*/
}
}
运行结果:
1 2 3 4 5
a b c d
4.3、Stream流的中间方法
常见方法
| 方法名 | 说明 |
|---|---|
Stream<T> filter(Predicate predicate) | 用于对流中的数据进行过滤 |
Stream<T> limit(long maxSize) | 获取前几个元素 |
Stream<T> skip(long n) | 跳过前几个元素 |
static <T> Stream<T> concat(Stream a, Stream b) | 合并a和b两个流为一个流 |
Stream<T> distinct() | 元素去重(底层依赖hashCode方法和equals方法) |
Stream<R> map(Function<T, R> mapper) | 转换流中的数据类型 |
注意
- 中间方法,会返回新的Stream流,原来的Stream流只能用一次,建议使用链式编程
- 修改Stream流中的数据,不会影响原来集合或者数组中的数据
4.4、Stream流的终结方法
常见方法
| 方法名 | 说明 |
|---|---|
| void forEach(Consumer action) | 对此流的每个元素执行操作,也就是遍历 |
| long count() | 返回此流中的元素数 |
| toArray() | 收集流中的元素,放到数组中 |
| R collect(Collector collector) | 收集流中的元素,放到集合中 |
forEach和count方法示例
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Consumer;
public class StreamDemo6 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "唱", "跳", "rap", "篮球", "Music");
// void forEach(Consumer action)
// 1、直接调用
list.stream().forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.print(s + " ");
}
});
System.out.println(); // 换行
// 2、使用Lambda表达式的方式
list.stream().forEach(s -> System.out.print(s + " "));
System.out.println();
// long count()
System.out.println(list.stream().count());
}
}
运行结果:
唱 跳 rap 篮球 Music
唱 跳 rap 篮球 Music
5
4.5、Stream流的收集操作
对数据使用Stream流的方式操作完毕后,可以把流中的数据收集到集合中,属于Stream流的终结方法
常用方法
| 方法名 | 说明 |
|---|---|
| toArray() | 收集流中的元素,放到数组中 |
| R collect(Collector collector) | 收集流中的元素,放到集合中 |
工具类Collectors提供了具体的收集方法
| 方法名 | 说明 |
|---|---|
public static <T> Collector toList() | 把元素收集到List集合中 |
public static <T> Collector toSet() | 把元素收集到Set集合中 |
public static Collector toMap(Function keyMapper, Function valueMapper) | 把元素收集到Map集合中 |
toArray方法示例
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.function.IntFunction;
public class StreamDemo7 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "唱", "跳", "rap", "篮球", "Music");
// toArray方法
// 1、空参,返回Object类型的数组
Object[] arr = list.stream().toArray();
System.out.println(Arrays.toString(arr));
// 2、带参,返回指定类型的数组
String[] strings = list.stream().toArray(new IntFunction<String[]>() {
@Override
public String[] apply(int value) {
return new String[value];
}
});
System.out.println(Arrays.toString(strings));
/*
InterFunction泛型:具体类型的数组
apply的形参:流中数据的个数,要跟数组的长度保持一致
apply的返回值:具体类型的数组
方法体:创建数组并返回
toArray方法的参数:租用就是创建一个指定类型的数组
toArray方法的底层:会依次得到流里面的数据,并把数据放到数组中
toArray方法的返回值:是一个装着流里面所以数据的数组
也可以换成Lambda表达式的写法,更简洁
*/
}
}
运行结果:
[唱, 跳, rap, 篮球, Music]
[唱, 跳, rap, 篮球, Music]
collect方法示例
5、方法引用
5.1、概述
方法引用
- 把已经有的方法拿过来,当做函数式接口中抽象方法的方法体
- 使用方法引用比Lambda表达式的简化方式还要简洁
方法引用的使用规则
- 引用处必须是函数式接口
- 被引用的方法必须已存在
- 被引用方法的形参和返回值需要跟抽象方法保持一致
- 被引用方法的功能要能满足当前的需求
示例
import java.util.Arrays;
import java.util.Comparator;
public class FunctionDemo1 {
public static void main(String[] args) {
// 需求:创建一个数组,进行倒序排序
Integer[] arr = {3, 4, 5, 1, 2, 6};
// 匿名内部类
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
System.out.println(Arrays.toString(arr));
// Lambda表达式
Arrays.sort(arr, (Integer o1, Integer o2) -> {
return o2 - o1;
});
System.out.println(Arrays.toString(arr));
// Lambda表达式的简化格式
Arrays.sort(arr, (o1, o2) -> o2 - o1);
System.out.println(Arrays.toString(arr));
// 方法引用
Arrays.sort(arr, FunctionDemo1::subtraction);
System.out.println(Arrays.toString(arr));
}
// 将要被引用的方法
public static int subtraction(int num1, int num2) {
return num2 - num1;
}
}
运行结果:
[6, 5, 4, 3, 2, 1]
[6, 5, 4, 3, 2, 1]
[6, 5, 4, 3, 2, 1]
[6, 5, 4, 3, 2, 1]
5.2、方法引用符
::该符号(两个冒号)为引用运算符,而它所在的表达式被称为方法引用
5.3、方法引用的分类
方法引用的分类
- 引用静态方法
- 引用成员方法
- 引用其它类的成员方法
- 引用本类的成员方法
- 引用父类的成员方法
- 引用构造方法
- 其它调用方式
- 使用类名引用成员方法
- 引用数组的构造方法
5.4、引用静态方法
格式
类名::静态方法名
范例
Integer::parseInt
示例
// 集合中有以下数字,要求把它们都变成int型
// "1" "2" "3" "4" "5"
import java.util.ArrayList;
import java.util.Collections;
public class FunctionDemo2 {
public static void main(String[] args) {
// 创建集合,将list1中的元素转为int型装进newList中
ArrayList<String> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();
ArrayList<Integer> list3 = new ArrayList<>();
Collections.addAll(list1, "1", "2", "3", "4", "5");
// 常规方式
for (int i = 0; i < list1.size(); i++) {
int temp = Integer.parseInt(list1.get(i));
list2.add(temp);
}
System.out.println(list2);
list2.clear();
// 方法引用
list1.stream().map(Integer::parseInt).forEach(s -> list2.add(s));
System.out.println(list2);
}
}
运行结果:
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
5.5、引用成员方法
引用成员方法又分为
- 引用其它类的成员方法
- 引用本类的成员方法
- 引用父类的成员方法
格式:对象::成员方法名
- 其它类:
其它类对象::方法名 - 本类:
this::方法名 - 父类:
super::方法名
引用处不能是静态方法,因为静态方法中没有this
引用其它类的成员方法示例
/*
集合中有一些名字,按要求过滤数据
数据:"张无忌", "周芷若", "赵敏", "张强", "张三丰"
要求:只要以“张”开头的,而且名字是三个字
*/
import java.util.ArrayList;
import java.util.Collections;
public class FunctionDemo3 {
public static void main(String[] args) {
// 创建集合,添加数据
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰");
// java中没有直接能满足需求的方法,所以我们可以自己写一个
list.stream().filter(Tool::stringJudge).forEach(s -> System.out.println(s));
}
}
// 在类中自己写一个成员方法
class Tool {
// 将被引用的成员方法
public static boolean stringJudge(String s) {
return s.startsWith("张") && s.length() == 3;
}
}
运行结果:
张无忌
张三丰
5.6、引用构造方法
为什么要引用构造方法
就是为了创建对象
格式
类名::new
范例
Student::new 就是创建一个Student类的对象
5.6、其它调用方式-使用类名引用成员方法
该方式的规则
- 需要有函数式接口
- 被引用的方法必须已经存在
- 被引用的方法的形参,需要跟抽象方法的第二个形参到最后一个形参保持一致,如果没有第二个参数,说明被引用的方法需要是无参的成员方法
- 返回值需要保持一致
- 被引用方法的功能需要满足当前的需求
格式
类名::成员方法名
范例
String::substring
5.7、其它调用方式-引用数组的构造方法
概述
Java的底层有一个类,专门用来描述数组,它里面也有构造方法,只不过这个类是Java底层已经提供好的,当我们创建一个数据,其实就是调用了这个构造方法,我们只要知道引用数组的构造方法就是创建一个数组
格式
数据类型[]::new
范例
int[]::new
示例
/*
集合中存储一些整数,收集到数组中
*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class FunctionDemo4 {
public static void main(String[] args) {
// 创建集合,添加元素
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 2, 3, 4, 5);
// 收集到数组中,数组的类型需要跟流中数据的类型保持一致
Integer[] arr = list.stream().toArray(Integer[]::new);
System.out.println(Arrays.toString(arr));
}
}
运行结果:
[1, 2, 3, 4, 5]
二十九、static关键字
static 是静态的意思,它是一个修饰符,可以修饰成员变量,也可以修饰成员方法
被其修饰的成员的特点
- 该成员被类的所有对象所共享
- 多了一种调用方式,可以通过类名进行调用(推荐使用类名调用)
- 随着类的加载而加载,优先于对象存在(在创建对象之前就可通过类名调用到静态成员使用)
成员变量什么时候加static
- 需要共享数据
- 该变量需要被所有对象所共享
成员方法什么时候加static
常用于制作工具类,工具类中的成员方法都要加 static
工具类
不是描述事物,而是帮我们完成事情的类(打工),这种类我们称之为工具类。对工具类我们一般直接用类名调用方法,不需要创建对象。所以,对于工具类,一般要私有化其构造方法(加private关键字),就不能创建该类的对象了
总结
就是所写的方法需要让调用者更方便地调用,希望可以通过类名直接调用,就可以加static
注意
- 在静态方法中,只能直接访问静态成员变量。若要访问非静态的成员变量,需要把对象创建出来,用对象名进行访问,相当于间接访问
- static中不允许使用this关键字(this代表当前对象的引用,肯定要创建对象之后,才能使用this关键字)
重新认识 main 方法public static void main(String[] args) {}
- public:被JVM调用,访问权限要足够大
- static:被JVM调用,为了JVM调的方便,不用去创建对象
- void:被JVM调用,不需要给JVM返回值
- main:一个通用的名称,虽然不是关键字,但是能被JVM识别
- String[]:一个字符串的数组,以前用来做键盘录入,现在有Scanner了,就没什么用了,只是作为一个传统被保留下来
- args:数组名,可以改
如果编写的方法和主方法平级,该方法要加static,因为主方法是静态的,所以,为了能在主方法中调用其它方法,这些方法也得是静态的
三十、继承
1、继承的格式
关键字:extends
格式:
public class 子类名 extends 父类名 {
}
示例:
public class Son extends Father {
}
2、创建类的细节
- 一个 .java 文件中可以编写多个类(建议还是一个 .java 文件对应一个类)
- 需要保证类与类之间是平级关系
- 一个 .java 文件中只能有一个类能被 public 修饰,因为加了 public ,就限制该类名要与文件名必须保持一致
3、继承中的成员访问特点
成员变量
子父类中,如果出现了同名的成员变量,根据就近原则,优先使用子类的。但如果要用父类的,加一个 super 作为标识。如:super.成员变量名
成员方法
子父类中,如果出现了方法声明一模一样的方法(即方法名、参数、返回值都相同),在创建子类对象,调用方法时,会优先使用子类的方法逻辑(大括号里的内容),这虽然是就近原则的现象,但其实是子类的方法,对父类的方法,进行了重写操作
构造方法
- 构造方法不能被继承(所以没有重写的说法,但是可以重载)
- 子类初始化之前,需要先完成父类的初始化,所以子类要先访问父类的空参构造方法
- 在所有类的构造方法里的第一行代码,包括父类,但除了 Object 类,都隐藏了一句代码:
super();通过这行代码,子类会访问父类的空参数构造方法,从而完成父类的初始化 - 其实Java中所有的类,都直接或者间接的继承到了 Object 类,Object 类是最大的祖宗
public class Test05 {
public static void main(String[] args) {
Son s1 = new Son();
Son s2 = new Son(10);
}
}
class Father {
public Father() {
// super(); Java中所有类的构造函数中(除了Object类)都隐藏了这句代码
System.out.println("Father类的空参构造方法");
}
public Father(int x) {
// super(); Java中所有类的构造函数中(除了Object类)都隐藏了这句代码
System.out.println("Father类的带参构造方法");
}
}
class Son extends Father {
public Son() {
// super(); 隐藏的代码
System.out.println("Son类的空参构造方法");
}
public Son(int x) {
// super(); 隐藏的代码
System.out.println("Son类的带参构造方法");
}
}
运行结果:
Father类的空参构造方法
Son类的空参构造方法
Father类的空参构造方法
Son类的带参构造方法
4、方法重写
先做一个区分,以免混淆
- 方法重载(Overload):即函数重载。已经很熟悉了,不多说明
- 方法重写(Override):是继承中的概念。子父类中出现方法声明一模一样的方法(返回值、方法名,参数列表都相同,只是方法的大括号内的内容不同),子类会重写该方法
方法重写的使用场景
当子类需要使用父类的方法,但是觉得父类的方法逻辑不好,需要修改或增强,就可以对父类的方法进行重写
注意
- 父类中私有方法不能被重写
- 子类重写父类方法时,访问权限必须大于等于父类(private < default < protected < public)
5、Java中继承的特点
Java只支持单继承,不支持多继承(C类继承了A类,就不能再继承B类),但是支持多层继承(B类继承A类,C类又继承B类)
三十一、this 和 super 关键字
- this:代表当前类对象的引用(地址)
- super:代表父类存储空间的标识
简单记就是:
- this调本类的成员
- super调父类的成员
| 关键字 | 访问成员变量 | 访问成员方法 | 访问构造方法 |
|---|---|---|---|
| this | this.本类成员变量; | this.本类成员方法; | this(); this(带参); |
| super | super.父类成员变量; | super.父类成员方法; | super(); super(带参); |
this调用本类构造方法的使用场景:开发中的开闭原则:对功能扩展做开放,对修改代码做关闭
用this() 和 super() 访问构造方法时,都必须放在构造方法的第一行,所以二者不能共存
三十二、final 关键字
final 关键字在Java中是一个修饰符,可以修饰 方法、类、变量
1、final 修饰的特点
- 修饰方法:表明该方法是最终方法,不能被重写
- 修饰类:表明该类是最终类,不能被继承
- 修饰变量:表明该变量是常量,不能再被赋值
2、final 修饰变量的一些细节
- 修饰基本数据类型的变量:数据值不可改变
- 修饰引用数据类型的变量:地址值不可改变,但是内容可以修改
- 修饰成员变量:该变量要么在定义的时候初始化(推荐),要么在构造方法中赋值
3、final 修饰变量的命名规范
- 如果变量是一个单词,所有字母大写。如:MAX
- 如果变量是多个单词,所有字母大写,中间使用下划线分隔。如:MAX_VALUE
三十三、抽象类
1、抽象类介绍
关键字:abstract
抽象类是一种特殊的父类,内部可以编写抽象方法
如果一个类中存在抽象方法,那么该类就必须声明为抽象类
抽象类的子类必须重写父类的抽象方法
2、什么是抽象方法
当我们将共性的方法,抽取到父类后,发现这个方法无法在父类中给出具体明确的代码(描述不清了),而且这个方法还是子类必须要有的方法,就可将该父类设计为抽象类
【与其说是抽取,其实是父类有多个子类,并且这些子类都重写了父类中的同一个方法,并且各有特点,而父类中的此方法有不好写,就干脆不写了,大括号一删,分号一加,定义为抽象方法,(这种形式很像C++中的函数声明)所以此类也必须定义为抽象类,而它的子类就必须重写该抽象方法】
abstract class Animal { // 抽象类
public abstract void eat(); // 抽象方法
}
class Cat extends Animal{
public void eat() {
System.out.println("Cat eat"); // 重写父类的抽象方法
}
}
class Dog extends Animal{
public void eat() {
System.out.println("Dog eat"); // 重写父类的抽象方法
}
}
3、抽象类的注意事项
1. 抽象类不能实例化,也就是不能创建对象
如果抽象类可以创建对象,就可以调用内部没有方法体的方法(抽象方法),但这样做是没有意义的。既然不能创建对象,那还有没有构造方法呢?(毕竟构造方法的本质作用就是创建对象,上篇笔记的第二十一点有所提及)
2. 抽象类存在构造方法
存在的意义:让子类能通过 super 进行访问
// 父类
abstract class Fu {
public Fu() {} // 父类的构造方法
public abstract void show(); // 抽象方法
}
// 子类继承父类
class Zi extends Fu{
public Zi() { // 子类的构造方法
super(); // 访问父类的构造方法
}
@Override
public void show() {
}
}
3. 抽象类可以存在普通方法
存在意义:让子类继承到,进行使用
4. 作为抽象类的子类,要么重写抽象类中的所有抽象方法,要么也是一个抽象类
抽象方法要么存在抽象类种,要么就被重写。如果子类不重写父类中的抽象方法,那就要把它声明为一个抽象类。因为子类其实也继承了父类中的抽象方法,抽象方法要存活在抽象类中,那可以把子类也变成抽象类,就可以不用重写重写方法了
【就是说一个类,它继承了一个抽象类,那它必须重写这个抽象类中的抽象方法,否则编译器会报错。还有一种办法就是上面说的,将这个类也变成一个抽象类,就可以不重写抽象方法(这种情况很少)】
4、abstract 关键字的冲突
和以下这些关键字冲突,不能写在一起
- final
被 final 修饰的类不能被继承,也就没有子类来重写父类中的抽象方法 - private
被 private 修饰的方法子类无法访问,也就没法重写 - static
被 static 修饰的方法可以用类名调用,类名调用抽象方法没有意义
三十四、接口
1、接口介绍
- Java中非常重要的技术:接口,会用到 interface 和 implements 关键字
- 编写类使用 class 关键字,编写接口只需将 class 关键字换成 interface
- 接口,体现的思想是对规则的声明,Java中的接口更多体现的是对行为的抽象,也就是抽象方法
- 如果一个类中都是抽象方法,且没有成员变量(可以有成员常量),没有普通方法(包括构造方法)。这种类,我们通常会设计为Java中的接口。因为这个类存在的唯一价值,就只是声明规则了
- 接口不允许实例化,也就是不能创建对象。那该怎么使用接口呢?就是找类实现接口,实现这套规则
- 接口和类之间是实现关系
- 实现了接口的类,叫做“实现类”,也就多了一个名字而已。实现类也可以看作接口的子类,应该说接口是“实现类”的干爹
interface Inter { // 创建一个接口
public abstract void show(); // 抽象方法
public abstract void method(); // 抽象方法
}
class test2 implements Inter{ // 创建一个类实现接口,该类也叫“实现类”
@Override
public void show() { // 重写接口中的抽象方法
}
@Override
public void method() { // 重写接口中的抽象方法
}
}
【和抽象类相似,一个接口的实现类,(我们可以称这个接口是此实现类的“干爹”)要么重写抽象类中的所有抽象方法,要么也是一个抽象类,理由和上面的“抽象类的注意事项”中描述的差不多。理一下接口和抽象类的概念。一切都是从继承开始,单说继承很简单。接着是抽象类,抽象类就是一个父类,(因为它必须有子类去重写抽象方法)这就用到了继承的概念,但它是一个特殊的父类,里面存在抽象方法,所以它的子类必须重写抽象方法,或是,也变成一个抽象类(这种情况很少,因为还要在创建一个,应该叫孙类,来重写抽象方法)。而接口是什么,就是一个抽象类,只不过这个抽象类中全是抽象方法,没有变量,没有构造方法(但可以有常量,下面有写到),因为很特殊,我们给了它一个“接口”的名字,所以 接口也可以理解为一个类。再看,继承一个抽象类会用 extends 关键字,也就是普通的继承所用的关键字,而接口,只是将 extends 换成了 implements ,而我们称这个使用了implements 的类叫做实现类,毕竟不是严格的继承关系,算是干爹和侄子的关系】
2、接口中的成员特点
- 构造方法
接口中没有构造方法 - 成员变量
只能定义常量,我们用定义变量的方式定义所谓“变量”其实是常量,因为系统会默认在前面加入三个关键字:public、static、final - 成员方法
只能是抽象方法,因为系统会默认在前面加入两个关键字:public、abstract
3、接口和类之间的关系
- 类和类之间可以存在的关系:继承关系,只支持单继承,不支持多继承,可以多层继承
- 类和接口之间可以存在的关系:实现关系,可以单实现,可以多实现,甚至可以在继承一个类的同时,实现多个接口
- 接口和接口之间可以存在的关系:继承关系,可以单继承,可以多继承
示例
// 接口A
interface A {
void showA();
}
// 接口B
interface B {
void showB();
}
// 类C,多实现,同时实现接口A和B。可理解为同时认了A,B两个干爹
class C implements A, B {
@Override
public void showA() {
}
@Override
public void showB() {
}
}
// 接口D,并且同时继承了接口A和B
interface D extends A, B {
void showD();
} // 若要创建类实现这个接口,那么要重写三个抽象方法:showA,showB,showD
三十五、多态
多态就是同类型的对象,表现出不同形态
1、多态现象分为两种
1.1、对象多态
父类引用指向子类对象
好处
将一个方法的形参定义为父类类型,这个方法就可以就接收到该父类(或干爹)的任意子类对象了。提高程序的扩展性
1.2、行为多态
定义方法的时候,使用父类作为类型参数,可以接收所有子类对象
好处
同一个方法,具有多种不同表现形式或形态的能力,提高了程序的扩展性
2、多态前提
以下三个条件要同时满足
- 有继承或实现关系
- 有方法重写
- 有父类引用指向子类对象,或接口类引用指向实现类对象
3、多态的成员访问特点
-
使用多态创建对象调用成员变量的特点
多态创建完对象,调用成员变量时,在编译时会检查父类中有没有这份数据,如果有,编译成功,没有的话,编译就会报错,运行时走的也是父类中的数据 -
使用多态创建对象调用成员方法的特点
编译时会检查父类中有没有此方法,如果没有,编译会出错,如果有此方法,编译通过,但运行时一定执行走子类的方法逻辑。因为担心你调用的方法在父类中是一个抽象方法,所以为了使调用有意义,一定会走子类的方法逻辑 -
使用多态创建对象调用静态成员的特点
先说一个细节:静态成员推荐使用类名调用,虽然可以使用对象名调用,但这是一种假象,代码在生成字节码文件后,会自动将对象名调用改成类名调用
所以说:使用类名调用静态成员,类名是谁,调用的就是谁的逻辑,类名是父类的,就调用的父类的逻辑,类名是子类的,就调用子类的逻辑
1、2点示例
public class PolymorphismDemo {
public static void main(String[] args) {
Fu f = new Zi(); // 父类引用指向子类对象
System.out.println(f.num); // 10
// 调用对象内部的成员变量,使用的是父类中的成员
f.show(); // Zi...show,
// 运行时走的子类的代码逻辑
}
}
class Fu {
int num = 10; // 父类中的num
public void show() { // 父类中的show方法
System.out.println("Fu...show");
}
}
class Zi extends Fu {
int num = 20; // 子类中的num
public void show() { // 重写父类的show方法
System.out.println("Zi...show");
}
}
总结
Fu f = new Zi();
- 访问成员变量:编译和运行都看左边(父类)
- 访问成员方法:编译看左边(父类),运行看右边(子类)。走的是重写后的方法逻辑
- 多态创建对象,调用静态成员:调的是父类中的成员
4、多态的转型
先看一个多态的弊端:不能使用子类的特有成员
结合多态的成员访问特点,调用成员变量或成员方法时,编译期间,会检查父类中有没有这个成员,如果要调子类特有的成员,父类中肯定没有,编译就会报错
要解决这个问题,需要使用到多态的转型,解决该问题使用第二种
多态的转型分为两种
1、向上转型:从子到父(父类引用指向子类对象)
Fu f = new Zi();
2、向下转型:从父到子(将父类引用所指向的对象,转交给子类类型)
Zi z = (Zi)f; // 需要强制转换
多态中转型时可能存在的安全隐患:ClassCaseException:类型转换异常
发生在在引用数据类型的强制转换中,实际类型和目标类型不匹配就会出现此异常
解决方法:使用 instanceof 关键字作一个判断
关键字 instanceof:判断左边的引用,是否是右边的数据类型,返回 true 或 false
示例
public class Test {
public static void main(String[] args) {
Animal animal = new Cat();
animal.eat();
Cat cat = (Cat)animal; // 向下转型,才能使用特有的方法
cat.hunt();
if (animal instanceof Dog) {
Dog dog = (Dog)animal;
dog.lookHome();
} else {
System.out.println("转换失败");
}
// JDK14新特性:可以将判断和强转合在一起写
if (animal instanceof Dog d) {
d.lookHome();
}
}
}
class Animal {
public void eat() {
System.out.println("吃东西");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("Cat吃东西");
}
public void hunt() {
System.out.println("Cat抓老鼠");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("Dog吃东西");
}
public void lookHome() {
System.out.println("Dog看家");
}
}
运行结果:
Cat吃东西
Cat抓老鼠
转换失败
三十六、接口新特性
1、JDK8的新特性
接口中可以定义有方法体的方法(默认,静态),在实现类中可以不重写这两类方法
-
允许在接口中定义非抽象方法,但需要用 defaulet 关键字修饰,这些方法就是默认方法
- 默认方法的定义格式
public default 返回值类型 方法名(参数列表) {} - 示例
public class InterfaceDemo { public static void main(String[] args) { InterImpl ii = new InterImpl(); ii.show(); } } interface Inter { default void show() { System.out.println("接口的默认方法"); } } class InterImpl implements Inter { // 可以不重写接口中的show()方法,当然也可重写,需要去掉default关键字 } - 注意
如果实现了多个接口,且多个接口中存在相同的默认方法,此时实现类必须重写默认方法
- 默认方法的定义格式
-
允许定义静态方法,既然已经允许接口带有方法体了,干脆也放开静态方法,但只允许用接口名调用,不允许实现类通过对象调用
2、JDK9的新特性
- 接口中可以定义私有方法
接口中的多个默认方法中有相同的代码,此时可以将相同的代码抽取出来写成一个方法,在每个默认方法中调用该方法,所以,这个抽取出来的方法只是接口内部使用,不希望暴露给其它类使用,可以加上 private 关键字设为私有interface Inter { // 私有方法,用private就不能写default,但效果和不写相同,可以有代码体 private void fun() { System.out.println("这是默认方法"); } default void show1() { fun(); // 调用私有方法 } default void show2() { fun(); // 调用私有方法 } }
三十七、代码块
使用 {} 括起来的代码称为代码块
分类
- 局部代码块
- 构造代码块
- 静态代码块
- 同步代码块(和多线程相关)
1、局部代码块(了解)
位置
方法中的一对大括号
作用
限定变量的声明周期,提早的释放内存
示例
public static void main(String[] args) {
{
int num = 10;
System.out.println(num);
}
// System.out.println(num); num变量的内存已释放,不能使用
}
2、构造代码块(了解)
位置
类中,方法外的一对大括号
特点
在创建对象,执行构造方法的时候,就会执行构造代码块(优先于构造方法执行)
作用
将多个构造方法中,重复的代码,抽取到构造代码块中,从而提升代码的复用性
示例
public class Test2 {
public static void main(String[] args) {
Student stu1 = new Student();
Student stu2 = new Student(10);
}
}
class Student {
{
// 类中,方法外的一对大括号
System.out.println("构造代码块");
}
public Student() {
System.out.println("空参构造");
}
public Student(int num) {
System.out.println("带参构造");
}
}
运行结果:
构造代码块
空参构造
构造代码块
带参构造
3、静态代码块
位置
类中,方法外的一对大括号,再括号前多加一个 static 关键字
特点
随着类的加载而执行,因为类只加载一次,所有也只执行一次
作用
对数据进行初始化(当需要初始化的数据是一个对象,而且这个对象的初始化很复杂,就可以借助静态代码块来初始化)
示例
public class Test2 {
public static void main(String[] args) {
Student stu1 = new Student();
Student stu2 = new Student(10);
}
}
class Student {
static {
// 类中,方法外的一对大括号,多加一个static关键字
System.out.println("静态static代码块");
}
public Student() {
System.out.println("空参构造");
}
public Student(int num) {
System.out.println("带参构造");
}
}
运行结果:
静态static代码块
空参构造
带参构造
三十八、内部类
内部类就是定义在一个类里面的类
1、成员内部类(了解)
class Outer {
// 局部内部类
class Inner {
}
}
-
创建对象的格式
外部类名.内部类名 对象名 = new 外部类对象().new 内部类对象();
例:Outer.Inner in = new Outer().new Inner(); -
成员访问细节
内部类中,访问外部类成员:直接访问,包括私有
外部类中,访问内部类成员:需要创建对象访问
2、静态内部类(了解)
有 static 修饰的成员内部类
class Outer {
// 静态内部类
static class Inner {
}
}
- 创建对象的格式
外部类名.内部类名 对象名 = new 外部类名.内部类对象();
例:Outer.Inner in = new Outer.Inner(); - 注意
静态只能访问静态
运行结果:public class Test2 { public static void main(String[] args) { Outer.Inner.show(); } } class Outer { int num1 = 10; static int num2 = 20; // 静态变量 // 静态内部类 static class Inner { public static void show() { System.out.println(num2); // 可以直接访问 // System.out.println(num1); 不能直接访问,需要创建对象访问 Outer outer = new Outer(); System.out.println(outer.num1); } } }20 10
3、局部内部类(了解且鸡肋)
放在方法、代码块、构造器等执行体中
public class Test2 {
public static void main(String[] args) {
A a = new A();
a.show();
}
}
class A {
public void show() {
// 局部内部类
class B {
public void fun() {
System.out.println("fun()...");
}
}
B b = new B();
b.fun();
}
}
运行结果:
fun()...
4、匿名内部类
概述
- 匿名内部类本质上是一个特殊的局部内部类(定义在方法内部)
- 匿名就是没有名字的意思,所以匿名内部类是一个没有名字的类
前提
需要存在一个接口或类
格式
new 类名/接口() {}
new 类名() {}代表继承这个类,这个整体是继承了这个类的对象,{}才是真正的匿名内部类new 接口() {}代表实现这个接口,这个整体是实现了这个接口的对象,{}才是真正的匿名内部类
先看一个问题,再来详细看匿名内部类
当一个方法的参数是接口,应当传入什么,没错,应该传入该接口的实现类对象(多态)
public class Test2 {
public static void main(String[] args) {
uerInter(new InterImpl());
}
public static void uerInter(Inter i) { // Inter i = new InterImpl();
i.show();
}
}
interface Inter {
void show();
}
class InterImpl implements Inter {
@Override
public void show() {
System.out.println("Override");
}
}
运行结果:
Override
为了调用uerInter()方法,先编写了一个类实现接口,然后重写了show()方法,再创建实现类对象传到方法中,如果用匿名内部类,一步就可以解决
public class Test2 {
public static void main(String[] args) {
// 使用匿名内部类
uerInter(new Inter() {
public void show() {
System.out.println("重写方法");
}
});
}
public static void uerInter(Inter i) {
i.show();
}
}
interface Inter {
public void show();
}
运行结果:
重写方法
作用
可以让代码更加简洁,在定义类的时候就对其进行实例化。但是,如果接口中的抽象方法很多,再用匿名内部类,代码就会显得臃肿,这时就最好写一个实现类来传入实现类对象,所以在抽象方法不是很多时使用匿名内部类可以让代码更简洁
匿名内部类用法
public class Test00 {
public static void main(String[] args) {
// 使用匿名内部类后可以直接赋给接口对象
Person p = new Person() {
@Override
public void fun() {
System.out.println("调用了fun()方法1号");
}
};
p.fun(); // 用对象调用方法
// 也可以直接调用重载后的方法
new Person() {
@Override
public void fun() {
System.out.println("调用了fun()方法2号");
}
}.fun();
}
}
// 接口/函数式接口
interface Person {
void fun();
}
运行结果:
调用了fun()方法1号
调用了fun()方法2号
三十九、Lambda表达式
1、基本用法
概述
- Lambda表达式是JDK8开始后的一种新语法形式
- 并不是所有的匿名内部类都用Lambda表达式作简化,Lambda表达式只能操作函数式编程接口(有且仅有一个抽象方法的接口)
作用
简化匿名内部类的代码写法
格式
简化格式:
() -> {}
具体格式:
(匿名内部类被重写方法的形参列表) -> {
被重写方法的方法体代码
}
注:-> 是语法格式,无实际含义
示例
public class Test2 {
public static void main(String[] args) {
// 使用Lambda表达式
uerInter(() -> {
System.out.println("重写方法");
});
}
public static void uerInter(Inter i) {
i.show();
}
}
interface Inter {
public void show();
}
2、Lambda表达式的省略写法
规则
- 参数类型可以不写
- 如果只有一个参数,参数类型可以省略,同时 () 也可以省略
- 如果Lambda表达式的方法体只有一行代码,可以省略大括号不写,同时要省略分号(;),此时,如果这行代码是return语句,必须省略return不写
3、Lambda表达式和匿名内部类的区别
-
使用限制不同
匿名内部类:可以操作类、接口
Lambda:只能操作函数式接口(有且仅有一个抽象方法的接口) -
实现原理不同
匿名内部类:编译之后,产生一个单独的 .class 文件
Lambda:编译之后,没有一个单独的 .class 文件
四十、Object类
1、介绍
- 所有的类,都直接或间接的继承了Object类(祖宗类)
- Object类的方法是一切子类都可以直接使用的
2、toString成员方法
返回该对象的字符串表示
public String toString() {
return getClass().getName + "@" + Integer.toHexString(hashCode());
}
/*
getClass().getName:全类名(包名+类名)
"@":就是一个分隔符
Integer.toHexString:转十六进制
hashCode():返回的是对象内存地址+哈希算法,算出来的整数值(哈希值)
*/
细节
使用打印语句,打印对象名的时候,println()方法,源码层面,会自动调用该对象的toString方法
package test;
public class Test1 {
public static void main(String[] args) {
A a = new A();
System.out.println(a); // 直接打印对象名
System.out.println(a.toString()); // 调用toString方法
}
}
class A {
}
运行结果:
test.A@282ba1e
test.A@282ba1e
重写toString方法
父类 toString() 方法存在的意义就是被子类重写,以便返回对象的内容信息,而不是地址信息,这样直接打印对象名或者调用toString方法就能返回属性信息
package test;
public class Test1 {
public static void main(String[] args) {
A a = new A();
System.out.println(a);
System.out.println(a.toString());
}
}
class A {
@Override
public String toString() {
return "重写toString方法,返回对象的属性信息";
}
}
运行结果:
重写toString方法,返回对象的属性信息
重写toString方法,返回对象的属性信息
总结
- 直接输出对象,默认输出的是地址其实没有意义,所以,我们一般会重写toString方法,把对象的属性拼接为字符串返回
- 将来如果打印一个对象名,得到的不是一串地址,说明这个类肯定重写了toString方法
3、equals成员方法
细节
使用 == 比较两个对象,比较的是地址
Object类中的equals方法
public boolean equals(Object obj)
用于对象之间的比较,返回true或false,默认比较的是对象内存地址,通常会在子类中重写equals方法,让对象之间比较内容
package test;
public class Test1 {
public static void main(String[] args) {
Person p1 = new Person(18, "LI");
Person p2 = new Person(18, "LI");
// 没有重写equals方法,直接比较对象,两个对象的地址肯定不同
System.out.println(p1.equals(p2)); // false
}
}
class Person {
private int age;
private String name;
public Person() {
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
重写equals方法
package test;
public class Test1 {
public static void main(String[] args) {
Person p1 = new Person(18, "LI");
Person p2 = new Person(18, "LI");
// 简单重写了equals方法,比较对象的内容
System.out.println(p1.equals(p2)); // true
}
}
class Person {
private int age;
private String name;
public Person() {
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public boolean equals(Object obj) { // Object obj = p2 多态
Person p = (Person)obj; // 向下转型,为了调用子类特有的成员
return this.age == p.age && this.name == p.name;
}
}
重写equals方法的改进版
package test;
public class Test1 {
public static void main(String[] args) {
Person p1 = new Person(18, "LI");
Person p2 = new Person(18, "LI");
System.out.println(p1.equals(p2)); // true
System.out.println(p1.equals("和String对象比较")); // false
}
}
class Person {
private int age;
private String name;
public Person() {
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public boolean equals(Object obj) {
/*
判断obj的类型是否正确
如果对象类型不同,内容肯定不同,直接返回false
类型相同再作比较
从而避免出现异常
*/
if (obj instanceof Person) {
Person p = (Person)obj; // 向下转型
return this.age == p.age && this.name == p.name;
} else {
return false;
}
}
}
四十一、Objects类
概述
Objects 类与 Object 类也是继承关系,Objects 类是从JDK1.7开始之后才有的
需要导包import java.util.Objects;
Objects 类的常见方法
| 方法名 | 说明 |
|---|---|
| public static boolean equals(Object a, Object b) | 比较两个对象,底层会先进行非空判断,从而可以避免空指针异常,再进行equals比较 |
| public static boolean isNull(Object obj) | 判断变量是否为null(鸡肋) |
细节
Objects 类的 equals 方法内部还是依赖我们重写的 equals 方法,所以,如果我们没有重写 equals 方法,它还是比较的对象地址,好处就是它内部带有一个非空判断,避免空指针异常
package test;
import java.util.Objects; // 导包
public class Test1 {
public static void main(String[] args) {
Person p1 = new Person(18, "LI");
Person p2 = new Person(18, "LI");
/*
使用Objects类的equals方法比较对象
内部还是依赖我们自己重写的equals方法
只是它内部会有一个非空判断
避免空指针异常
所以我们还是要自己重写equals方法
*/
System.out.println(Objects.equals(p1, p2)); // true
System.out.println(Objects.equals(p1, "String对象")); // false
}
}
class Person {
private int age;
private String name;
public Person() {
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Person) {
Person p = (Person)obj;
return this.age == p.age && this.name == p.name;
} else {
return false;
}
}
}
四十二、Math类
包含执行基本数字运算的方法
常见成员方法
| 方法名 | 说明 |
|---|---|
| public static int abs(int a) | 返回 int 值的绝对值 |
| public static double ceil(double a) | 向上取整 |
| public static double floor(double a) | 向下取整 |
| public static int round(float a) | 四舍五入 |
| public static double pow(double a, double b) | 返回a的b次幂 |
| public static double random() | 返回double类型的随机值,范围 [ 0.0,1.0 ) |
四十三、System类
常用成员方法
| 方法名 | 说明 |
|---|---|
| pubic static void exit(int status) | 终止当前运行的Java虚拟机,非零表示异常终止 |
| public static long currentTimeMillis() | 返回当前系统的时间毫秒值形式(返回1970年1月1日0时0分0秒到现在所经历的时间毫秒值) |
| public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) | 从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束 |
arraycopy 方法说明
Object src:数据源数组
int srcPos:起始索引
Object dest:目的数组
int destPos:起始索引
int length:拷贝的个数
四十四、BigDecimal类
导包import java.math.BigDecimal;
BigDecimal 类用于解决小数运算中的不精确问题
public class MathDemo {
public static void main(String[] args) {
double num1 = 0.1;
double num2 = 0.2;
System.out.println(num1 + num2); // 0.30000000000000004
}
}
创建对象
1、public BigDecimal(double val) // 不推荐,计算不精确
2、public BigDecimal(String val)
3、public static BigDecimal valueOf(double val)
示例
package demo;
import java.math.BigDecimal;
public class MathDemo {
public static void main(String[] args) {
// 第2种方式
BigDecimal bd1 = new BigDecimal("0.1");
BigDecimal bd2 = new BigDecimal("0.2");
System.out.println(bd1.add(bd2)); // 0.3
// 第3种方式
BigDecimal.valueOf(0.1);
BigDecimal.valueOf(0.2);
System.out.println(bd1.add(bd2)); // 0.3
}
}
常用成员方法
加法:BigDecimal add(BigDecimal augend)
减法:BigDecimal subtract(BigDecimal subtrahend)
乘法:BigDecimal multiply(BigDecimal multiplicand)
除法:BigDecimal divide(BigDecimal divisor) 除不尽会报错
除法:BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
-
除法参数说明
BigDecimal divisor:另一个BigDecimal对象
int scale:小数点后精确多少位
int roundingMode:舍入模式 -
常见舍入模式
RoundingMode.HALF_UP:四舍五入
RoundingMode.UP:进一法,后面的不要,向前加一
RoundingMode.DOWN:后面的不要
四十五、包装类
将基本数据类型,包装成类(变成引用数据类型),就可以调用方法解决问题
八种基本数据类型都有对应的包装类
| 基本数据类型 | 引用数据类型 |
|---|---|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
下面以 Integer 类为例介绍包装类
1、Integer 类
手动装箱:调用方法,手动将基本数据类型,包装成类
public Integer(int value) 通过构造方法(不推荐)
public static Integer valueOf(int i) 通过静态方法
手动拆箱:调用方法,手动将包装成类,拆成基本数据类型
public int intValue() 以 int 类型返回该 Integer 的值
public class IntegerDemo {
public static void main(String[] args) {
// 手动装箱
int num = 10;
Integer i1 = Integer.valueOf(num); // 将基本数据类型包装成类
System.out.println(i1); // 10
// 手动拆箱
int res = i1.intValue(); // 将包装类拆成基本数据类型
System.out.println(res); // 10
}
}
从JDK5开始,出现了自动拆装箱
自动装箱:可以将基本数据类型,直接赋值给包装类的变量
自动拆箱:可以将包装类的数据,直接赋值给基本数据类型变量
public class IntegerDemo3 {
public static void main(String[] args) {
// 自动装箱
int num = 28;
Integer integer1 = 18;
Integer integer2 = num;
System.out.println(integer1); // 18
System.out.println(integer2); // 28
// 自动拆箱
int i1 = integer1; // 18
int i2 = integer2; // 28
System.out.println(i1);
System.out.println(i2);
}
}
Integer 类常用方法
| 方法名 | 说明 |
|---|---|
| public static String toBinaryString(int i) | 转二进制 |
| public static String toOctalString(int i) | 转八进制 |
| public static String toHexString(int i) | 转十六进制 |
| public static int parseInt(String s) | 将数字字符串,转换为数字 |
2、细节题
细节
使用==比较两个对象,比较的是地址
看下面的代码
public class IntegerDemo2 {
public static void main(String[] args) {
Integer i1 = 127;
Integer i2 = 127;
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i1 == i2); // true
System.out.println(i3 == i4); // false
}
}
结论
自动装箱的时候,如果装箱的数据范围,是 -128~127,==比较的结果是true,反之都是false
解释
- 自动装箱的原理:系统自动帮我们调用了 Integer.valueOf(int i) 方法
- valueOf方法源码:
public static Integer valueOf(int i) { if (i >= -128 && i <=127) { return IntegerCache.cache[i + (-IntegerCache.low)] } return new Integer(i); } - Integer 类中,底层存在一个长度为256个大小的数组,Integer[] cache,在数组中,存储了256个Integer对象,范围是 -128~127
- 如果自动装箱的数据的范围在 -128~127 之间,不会创建新的对象,而是从底层的数组中,取出一个提前创建好的对象返回,如果不在,就会重新 new 出新的 Integer 对象并且返回
- 因为自动装箱的代码用的很频繁,为了在-128~127的数值范围中不频繁创建对象,这样设计也是为了节省内存的消耗
四十六、Arrays 工具类
工具类,专门用于操作数组元素
常用成员方法
| 方法名 | 说明 |
|---|---|
| public static String toString(类型[ ] a) | 将数组元素拼接为带有格式的字符串(就是打印出每个数组元素) |
| public static boolean equals(类型[ ] a, 类型[ ] b) | 比较两个数组内容是否相同 |
| public static int binarySearch(int[ ] a, int key) | 查找元素在数组中的索引(用的是二分查找,所以需要保证数组中元素是有序的) |
| public static void sort(类型[ ] a) | 对数组进行默认升序排序 |
四十七、正则表达式
本质就是一个字符串,可以指定一些规则,来校验其它字符串
1、字符类(默认匹配一个字符)
[abc] 只能是a,b,c 其中之一([ ] 中括号代表对单个字符作限制)
[^abc] 除了a,b,c之外的任何字符
[a-zA-Z] a到z,A到Z
[a-zA-Z0-9] a到z,A到Z,数字0-9都可以
[a-d[m-p]] a到d之间,或m到p之间都可以(也可以写成[a-dm-p])
[a-z&&[def]] a-z的范围中,只能是d,e,f(鸡肋)
[a-z&&[^bc]] a-z,除了b和c
[a-z&&[^m-p]] a到z,除了m-p
2、预定义字符类
正则表达式中:
.(英文标点的句号) 代表任意字符
\ 转义字符
\d 一个数字字符:[0-9](使用时需要转义,即\\d ,以下相同)
\D 一个非数字字符:[^0-9]
\s 一个空白字符
\S 非空白字符:[^\s]
\w 英文,数字,下划线:[a-zA-Z_0-9]
\W 除了英文,数字,下划线:[^\w]
3、数量
在内容规则限定后面加上:
+ 一次或多次
? 一次或零次
* 任意次数
{n} 正好n次
{n,} 至少n次
{n,m} 至少n次但不超过m次
示例
public class RegexTest {
public static void main(String[] args) {
/*
QQ号正则:
不能以0开头
全部都是数字
5~12位
*/
String qqRegex = "[1-9]\\d{4,11}";
System.out.println("1145114".matches(qqRegex)); // true
/*
手机号正则:
全部是数字,必须是11位
第一位:必须1开头
第二位:3 4 5 6 7 8 9
*/
String teleRegex = "[1][3-9]\\d{9}";
System.out.println("13114511409".matches(teleRegex)); // true
}
}
4、String 类中与正则有关的常见方法
| 方法名 | 说明 |
|---|---|
| public String replace(String regex, String newStr) | 按照正则表达式匹配的内容进行替换 |
| public String[] split(String regex) | 按照正则表达式匹配的内容进行分割字符串,返回一个字符串数组 |
5、使用正则做爬取
Pattern 类:对正则表达式做封装
Matcher 类:文本匹配器,按照正则表达式的规则从头开始读取字符串,在大串中去找符合匹配规则的子串
以下介绍一些时间API
JDK8版本之前,介绍Date类、SimpleDateFormat类、Calendar类
JDK8版本之后,种类很多,更好用
| JDK8之前传统的API | JDK8开始之后新增的时间API |
|---|---|
| 1、设计欠妥,使用不方便,很多都被淘汰 | 1、设计更合理,功能丰富使用更方便 |
| 2、都是可变对象,修改后会丢失最开始的时间信息 | 2、都是不可变对象,修改后会返回新的时间对象,不会丢失最开始的时间 |
| 3、线程不安全 | 3、线程安全 |
| 4、只能精确到毫秒 | 4、能精确到毫秒、纳秒 |
四十八、Date 类
代表时间和日期
导包import java.util.Date;
常用构造方法
| 构造器(构造方法) | 说明 |
|---|---|
| public Date() | 创建一个 Date 对象,封装的是当前的时间 |
| pubic Date(long time) | 把时间毫秒值转换成 Date 日期对象,就是从时间原点结合给的毫秒值做计算,时间原点:1970年1月1日0时0分0秒 |
public class DateTest {
public static void main(String[] args) {
Date d1 = new Date();
System.out.println(d1); // Wed Mar 29 17:43:14 CST 2023
Date d2 = new Date(0L);
System.out.println(d2); // Thu Jan 01 08:00:00 CST 1970
// 我们在东八区,有时差
Date d3 = new Date(1000L);
System.out.println(d3); // Thu Jan 01 08:00:01 CST 1970
}
}
常见成员方法
| 方法名 | 说明 |
|---|---|
| public long getTime() | 返回从时间原点到对象所封装的时间的总毫秒数 |
| public void setTime(long time) | 设置日期对象的时间为时间原点结合给的毫秒值所得的时间 |
public class DateTest {
public static void main(String[] args) {
Date d1 = new Date();
System.out.println(d1); // Wed Mar 29 17:43:14 CST 2023
System.out.println(d1.getTime()); // 1680237275765
Date d2 = new Date(0L);
System.out.println(d2); // Thu Jan 01 08:00:00 CST 1970
System.out.println(d2.getTime()); // 0
Date d3 = new Date(1000L);
System.out.println(d3); // Thu Jan 01 08:00:01 CST 1970
System.out.println(d3.getTime()); // 1000
d3.setTime(0L); // 将日期对象d3封装的时间设置为时间原点
System.out.println(d3); // Thu Jan 01 08:00:00 CST 1970
}
}
四十九、SimpleDateFormat 类
用于日期格式化
导包 import java.text.SimpleDateFormat;
常见构造方法
| 构造器 | 说明 |
|---|---|
| public SimpleDateFormat() | 构造一个SimpleDateFormat,使用默认格式 |
| public SimpleDateFormat(String pattern) | 构造一个SimpleDateFormat,使用指定的格式 |
常用成员方法
| 方法名 | 说明 |
|---|---|
| public final String format(Date date) | 将日期对象,转化为字符串 |
| public final Date parse(String source) | 将日期字符串,解析为日期对象 |
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class SimpleDateFormatDemo {
public static void main(String[] args) throws ParseException {
// 创建日期格式化对象,使用默认格式
SimpleDateFormat simpleDateFormat1 = new SimpleDateFormat();
// 使用指定格式
SimpleDateFormat simpleDateFormat2 = new SimpleDateFormat("yyyy年MM月dd日");
// 创建Date对象,封装此刻的时间
Date d1 = new Date();
Date d2 = new Date();
// 将日期对象,转换为字符串
String res1 = simpleDateFormat1.format(d1);
System.out.println(res1); // 2023/4/1 下午1:51
String res2 = simpleDateFormat2.format(d1);
System.out.println(res2); // 2023年04月01日
// parse()方法实例
String today = "2023年04月01日";
SimpleDateFormat simpleDateFormat3 = new SimpleDateFormat("yyyy年MM月dd日");
Date date = simpleDateFormat3.parse(today);
System.out.println(date); // Sat Apr 01 00:00:00 CST 2023
}
}
JDK6帮助文档中的格式说明:

五十、Calendar 类
1、介绍
代表系统此刻时间对应的日历,通过它可以单独获取或修改时间中的年、月、日、时、分、秒等
导包 import java.util.Calendar;
Calendar 类是一个抽象类,不能创建对象
类中有一个静态方法 public static Calendar getInstance() 可以用它创建对象
创建对象:Calendar c = Calendar.getInstance();
左边是父类引用,右边获取子类对象,所以构成多态
2、常用成员方法
| 方法名 | 说明 |
|---|---|
| public int get(int field) | 获取日历中的某个字段信息(推荐参数写Calendar类中的静态常量) |
| public void set(int feild, int value) | 修改日历中的某个字段信息 |
| public void add(int field, int amount) | 为某个字段增加或减少指定的值 |
| public final Date getTime() | 获取日期对象 |
| public final void setTime(Date date) | 给日历设置日期对象 |
public int get(int field)方法示例
import java.util.Calendar;
public class CalendarDemo {
public static void main(String[] args) {
Calendar c = Calendar.getInstance(); // 创建Calendar对象c
// 为了更直观,所以推荐参数写Calendar类中的静态常量
// Calendar.YEAR 获取年份信息,在Calendar内部对应1
int res1 = c.get(Calendar.YEAR);
int res2 = c.get(1);
System.out.println(res1); // 2023
System.out.println(res2); // 2023
int month = c.get(Calendar.MONTH);
// Calendar的月份是从0~11月,要得到正确的月份需要加一
System.out.println(month + 1); // 4
// 获取星期,需要提前设计一个数组才能打印出正确的星期
char[] weeks = {' ', '日', '一', '二', '三', '四', '五', '六'}; // java中的char是两字节
// 0, 1, 2, 3, 4, 5, 6, 7,
int weekIndex = c.get(Calendar.DAY_OF_WEEK);
System.out.println(weeks[weekIndex]); // 一
}
}
获取正确的星期需要提前设计一个数组,因为老外的星期是从星期七开始到星期一结束
| 星期 | 一 | 二 | 三 | 四 | 五 | 六 | 七 |
|---|---|---|---|---|---|---|---|
| 返回结果 | 2 | 3 | 4 | 5 | 6 | 7 | 1 |
public void set(int feild, int value)方法示例
import java.util.Calendar;
public class CalendarDemo {
public static void main(String[] args) {
Calendar c = Calendar.getInstance();
c.set(Calendar.YEAR, 2022); // 修改年份信息
System.out.println(c.get(Calendar.YEAR)); // 2022
c.set(2008, 8, 8); // 直接修改日期相关信息
System.out.println(c.get(Calendar.YEAR)); // 2008
}
}
public void add(int field, int amount)方法示例
import java.util.Calendar;
public class CalendarDemo {
public static void main(String[] args) {
Calendar c = Calendar.getInstance();
c.add(Calendar.YEAR, -1); // 当前年份2023减一
System.out.println(c.get(Calendar.YEAR)); // 2022
}
}
以下是JDK8开始之后新增的时间API
五十一、LocalDate、LocalTime、LocalDateTime 类
LocalDate 类,代表本地日期(年、月、日、星期)
LocalTime 类,代表本地时间(时、分、秒、纳秒)
LocalDateTime 类,代表本地日期和时间(年、月、日、星期、时、分、秒、纳秒)
这三个类都是不可改变的,所以调用方法修改当前对象,都会返回一个新的对象
1、创建对象
1、方法名:public static xxxx now() 获取系统当前时间对应的该对象
xxxx代表以上三个类
示例
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
2、方法名:public static xxxx of() 获取指定时间的对象
示例
LocalDate localDate = LocalDate.of(2023, 1, 1);
LocalTime localTime = LocalTime.of(8, 5, 6);
LocalDateTime localDateTime = LocalDateTime.of(2023, 1, 1, 8, 5, 6);
2、修改年、月、日、时、分、秒的相关方法
| 方法名 | 说明 |
|---|---|
| withHours、withMinute、withSecond、withNano | 修改时间,返回时间对象,都是 with 开头 |
| plusHours、plusMinutes、plusSecond、plusNano | 把某个信息加多少,返回新时间对象 |
| minusHours、minusMinutes、minusSecond、minusNanos | 把某个信息减多少,返回新时间对象 |
| equals、isBefore、isAfter | 判断两个时间对象、是否相等、在前、还是在后 |
示例
package com.test;
import java.time.LocalDateTime;
public class Test {
public static void main(String[] args) {
LocalDateTime nowTime = LocalDateTime.now();// 创建LocalDateTime对象
System.out.println(nowTime); // 打印当前时间
System.out.println("减一小时" + nowTime.minusHours(1));
System.out.println("减一分钟" + nowTime.minusMinutes(1));
System.out.println("减一纳秒" + nowTime.minusNanos(1));
System.out.println("加一年" + nowTime.plusYears(1));
System.out.println("加一小时" + nowTime.plusHours(1));
System.out.println("加一分钟" + nowTime.plusMinutes(1));
System.out.println("修改年份" + nowTime.withYear(2000));
System.out.println("修改月份" + nowTime.withMonth(12));
// 打印结果
//2023-04-11T20:26:00.798754100
//减一小时2023-04-11T19:26:00.798754100
//减一分钟2023-04-11T20:25:00.798754100
//减一纳秒2023-04-11T20:26:00.798754099
//加一年2024-04-11T20:26:00.798754100
//加一小时2023-04-11T21:26:00.798754100
//加一分钟2023-04-11T20:27:00.798754100
//修改年份2000-04-11T20:26:00.798754100
//修改月份2023-12-11T20:26:00.798754100
}
}
3、LocalDateTime 转 LocalDate 和 LocalTime 的方法
| 方法名 | 说明 |
|---|---|
| public LocalDate toLocalDate() | 转成一个LocalDate对象 |
| public LocalTime toLocalTime() | 转成一个LocalTime对象 |
五十二、DateTimeFormatter 类
用于时间的格式化和解析
| 方法名 | 说明 |
|---|---|
| static DateTimeFormatter ofPattern(格式) | 获取格式化对象 |
| String format(时间对象) | 按照指定方式格式化 |
获取对象
static DateTimeFormatter ofPattern(格式)
格式化
String format(时间对象)
解析:(解析用的是日期对象里的parse方法)
LocalDateTime.parse("需要解析的字符串", 格式化对象);
LocalDate.parse("需要解析的字符串", 格式化对象);
LocalTime.parse("需要解析的字符串", 格式化对象);
示例
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DateTimeFormatterDemo {
public static void main(String[] args) {
// 创建日期对象
LocalDateTime now = LocalDateTime.now();
System.out.println("格式化前:" + now); // 格式化前:2023-04-14T10:05:45.457768900
// 创建格式化对象
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
// 格式化
String result = formatter.format(now);
System.out.println("格式化后:" + result); // 格式化后:2023年04月14日
// 解析
String time = "2008年08月08日";
// 解析用的是日期对象里的方法parse
LocalDate parse = LocalDate.parse(time, formatter);
System.out.println(parse); // 2008-08-08
}
}
五十三、Instant、ZoneId、ZonedDateTime 类
Instant,时间戳(时间节点)/时间线,类似Date
ZoneId,时区
ZonedDateTime,带时区的时间
1、Instant 时间戳
用于表示时间的对象,类似Date
常见方法
| 方法名 | 说明 |
|---|---|
| static Instant now() | 获取当前时间的 Instant 对象(世界标准时间,本初子午线) |
| static Instant ofXxxx(long epochMilli) | 根据(秒/毫秒/纳秒)获取 Instant 对象 |
| ZoneDateTime atZone(ZoneId zone) | 指定时区 |
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class InstantDemo {
public static void main(String[] args) {
// 获取当前时间的 Instant 对象
Instant instant = Instant.now();
// 打印结果有时差
System.out.println("当前时间是:" + instant);
// 需要使用ZoneId和ZonedDateTime获得正确的时间
ZonedDateTime zonedDateTime = Instant.now().atZone(ZoneId.of("Asia/Shanghai"));
System.out.println(zonedDateTime);
}
}
2、ZoneId 时区
常见方法
| 方法名 | 说明 |
|---|---|
| static Set< String > getAvailableZoneIds() | 获取Java中支持的所有时区(在国内就用Aisa/Shanghai) |
| static ZoneId systemDefault() | 获取系统默认时区 |
| static ZoneId of(zoneId) | 获取一个指定时区 |
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Set;
public class ZoneIdDemo {
public static void main(String[] args) {
// 1、获取Java中支持的所有时区
Set<String> set = ZoneId.getAvailableZoneIds();
System.out.println(set);
// 2、获取系统默认时区
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId); // Asia/Shanghai
// 3、获取一个指定时区
ZoneId of = ZoneId.of("Africa/Nairobi");
// ZoneId只代表时区对象
System.out.println(of); // Africa/Nairobi
// 将时区和时间对象结合
ZonedDateTime zonedDateTime = Instant.now().atZone(of);
// 带时区的时间对象
System.out.println(zonedDateTime);// 2023-04-14T06:39:21.838402500+03:00[Africa/Nairobi]
}
}
3、ZonedDateTime 带时区的时间
常见方法
| 方法名 | 说明 |
|---|---|
| static ZoneDateTime now() | 获取当前时间的 ZoneDateTime 对象 |
| static ZoneDateTime of(…) | 获取指定时间和时区的 ZoneDateTime 对象 |
五十四、Period、Duration、ChronoUnit 类
Period 用于计算两个“日期”间隔(年,月,日)
Duration 用于计算两个“时间”间隔(秒,纳秒)
ChronoUnit 用于计算两个“日期”间隔(功能最强大,最全,重点掌握)
1、Period 示例
import java.time.LocalDate;
import java.time.Period;
public class PeriodDemo {
public static void main(String[] args) {
// 此刻年月日
LocalDate today = LocalDate.now();
System.out.println(today); // 2023-04-14
// 昨天年月日
LocalDate otherDate = LocalDate.of(2023, 4, 13);
System.out.println(otherDate); // 2023-04-13
// Period对象表示的时间间隔对象
Period period = Period.between(today, otherDate);// 第二个参数减第一个参数
// 间隔多少年
System.out.println(period.getYears()); // 0
// 间隔的月份
System.out.println(period.getMonths()); // 0
// 间隔的天数
System.out.println(period.getDays()); // -1
}
}
2、Duration 示例
import java.time.Duration;
import java.time.LocalDateTime;
public class DurationDemo {
public static void main(String[] args) {
// 此刻的日期时间对象
LocalDateTime today = LocalDateTime.now();
System.out.println(today);
// 昨天的日期时间对象
LocalDateTime otherDate = LocalDateTime.of(2023, 4, 13, 0, 0, 0);
System.out.println(otherDate);
// Duration对象
Duration duration = Duration.between(otherDate, today);// 第二个参数减第一个参数
// 相差的天数
System.out.println(duration.toDays()); // 1
// 相差的小时数
System.out.println(duration.toHours()); // 37
// 相差的分钟数
System.out.println(duration.toMinutes()); // 2220
// 相差的纳秒数
System.out.println(duration.toNanos()); // 133211364525300
}
}
3、ChronoUnit 示例
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
public class ChronoUnitDemo {
public static void main(String[] args) {
// 此刻的日期时间对象
LocalDateTime today = LocalDateTime.now();
System.out.println(today);
// 另一个时间
LocalDateTime otherDate = LocalDateTime.of(2023, 3, 3, 0, 0, 0);
System.out.println(otherDate);
// 计算时间间隔
System.out.println("相差的年数:" + ChronoUnit.YEARS.between(otherDate, today));
System.out.println("相差的月数:" + ChronoUnit.MONTHS.between(otherDate, today));
System.out.println("相差的周数:" + ChronoUnit.WEEKS.between(otherDate, today));
System.out.println("相差的天数:" + ChronoUnit.DAYS.between(otherDate, today));
System.out.println("相差的时数:" + ChronoUnit.HOURS.between(otherDate, today));
System.out.println("相差的毫秒数:" + ChronoUnit.MILLIS.between(otherDate, today));
System.out.println("相差的纳秒数:" + ChronoUnit.NANOS.between(otherDate, today));
}
}
The End
1677

被折叠的 条评论
为什么被折叠?



