黑马程序员Java学习笔记 - 上部(从基础到源码)


一、 Java基础

1.常用CMD命令

操作 说明
盘符名称: 盘符切换。E:回车,表示切换到E盘。
dir 查看当前路径下的内容。
cd 目录 进入单级目录。cd itheima
cd … 回退到上一级目录。
cd 目录1\目录2… 进入多级目录。cd itheima\JavaSE
cd \ 回退到盘符目录。
cls 清屏。
exit 退出命令提示符窗口。

2.环境变量

在软件启动包所在目录下输入软件启动包的名字如idea64.exe就可以启动idea软件。

如果想要在CMD的任意目录下,都可以启动某一个软件,那么就可以把这个软件的路径配置到环境变量中的PATH里面:

  • 右键我的电脑,选择属性。
  • 点击左侧的高级系统设置
  • 选择高级,再点击下面的环境变量。
  • 找系统变量里面的PATH
  • 把软件的完整路径,配置到Path当中就可以了。
  • (可做可不做)就是把自己配置的路径,移动到最上面。

在CMD中打开软件时,会先找当前路径,再找环境变量,在环境变量中是从上往下依次查找的,如果路径放在最上面查找的速度比较快。

3.Java概述

3.1 JRE和JDK

JVM(Java Virtual Machine),Java虚拟机

Java(编程语言)

JRE(Java Runtime Environment),Java运行环境,包含了JVM和Java的核心类库(Java API)

JDK(Java Development Kit)称为Java开发工具,包含了JRE和开发工具

3.2 JDK下载与安装

官网:http://www.oracle.com

解压安装包jdk-17_windows-x64_bin.exe一直下一步就可以了,但是安装路径尽量不要有中文和空白字符。

3.3 JDK的安装目录

目录名称 说明
bin 该路径下存放了JDK的各种工具命令。 javac和java就放在这个目录。
conf 该路径下存放了JDK的相关配置文件。
include 该路径下存放了一些平台特定的头文件。
jmods 该路径下存放了JDK的各种模块。
legal 该路径下存放了JDK各模块的授权文档。
lib 该路径下存放了JDK工具的一些补充JAR包。

4.HelloWorld小案例

  1. 创建一个文件HelloWorld.java文件:

    1
    2
    3
    4
    5
    6
    public class HelloWorld {
    public static void main(String[] args) {
    System.out.println("HelloWorld");
    }
    }
    Lobster AIjava运行12345
  2. 在所在目录下打开cmd窗口

  3. 运行指令javac HelloWorld.javajava HelloWorld.class

    • javac + 文件名 + 后缀名 (就是编译java文件,编译后会产生一个.class文件)
    • java + 文件名(运行编译之后的class文件)

注意

执行javac和java指令需要先配置环境变量:

Java(编程语言)

JAVA_HOME:告诉操作系统JDK安装在了哪个位置(未来其他技术要通过这个找JDK)

image-20250509112101361

Path:告诉操作系统JDK提供的javac(编译)、java(执行)命令安装到了哪个位置

image-20250509112125750

5. Java语言的发展

Java有很多版本,其中三个最重要的版本:

  • Java5.0:这是Java的第一个大版本更新。
  • Java8.0:这个是目前绝大数公司正在使用的版本。因为这个版本最为稳定。
  • Java15.0:这个是我们课程中学习的版本。

高版本的Java完全兼容低版本的Java,但是低版本的Java不一定兼容高版本的Java。

Java的三大平台

JavaSE:其他两个版本的基础

JavaME:Java语言的小型版,用于嵌入式消费类电子设备或者小型移动设备的开发。

JavaEE:用于Web方向的网站开发。(主要从事后台服务器的开发)

6.Java语言跨平台的原理

  • 操作系统本身其实是不认识Java语言的。
  • 但是针对于不同的操作系统,Java提供了不同的虚拟机。

虚拟机会把Java语言翻译成操作系统能看得懂的语言。

image-20250509113054832

二、Java基础语法

1.注释

  1. 单行注释
1
2
// 这是单行注释文字
Lobster AIjava运行1
  1. 多行注释
1
2
3
4
5
6
/*
这是多行注释文字
这是多行注释文字
这是多行注释文字
*/
Lobster AIjava运行12345
  1. 文档注释
标签 描述
@author 表示作者
@deprecated 标识过期的类或成员
@exception 标识抛出的异常
@param 标识方法的参数
@return 标识方法的返回值
@see 标识指定参数的内容
@serial 标识序列化属性
@version 标识版本
@throws 标识引入一个特定的变化
1
2
3
4
5
/**
* @author 黑马程序员
* @version v1.0
*/
Lobster AIjava运行1234

注意:多行注释不能嵌套多行注释,但是可以嵌套单行注释。

2. 数据类型

java的数据类型分为基本数据类型和引用数据类型。

四类八种基本数据类型:

数据类型 关键字 内存占用 取值范围
整数 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

注意点

byte的范围:-128~127;

整数类型和小数类型的取值范围大小关系:double > float > long > int > short > byte;

如果要定义一个long类型的变量,那么在数据值的后面需要加上L后缀(大小写都可以,建议大写);

如果要定义一个float类型的变量,那么在数据值的后面需要加上F后缀(大小写都可以)。

数据类型的选择

  • 如果要定义 一个整数类型的变量,不知道选择哪种数据类型了,默认使用int。
  • 如果要定义 一个小数类型的变量,不知道选择哪种数据类型了,默认使用double。
  • 如果要定义一个long类型的变量,那么在数据值的后面需要加上L后缀。(大小写都可以,建议大写。)
  • 如果要定义一个float类型的变量,那么在数据值的后面需要加上F后缀。(大小写都可以)

3.标识符

3.1 硬性要求

必须要这么做,否则代码会报错。

  • 必须由数字、字母、下划线_、美元符号$组成。
  • 数字不能开头
  • 不能是关键字
  • 区分大小写的。

3.2 软件建议

3.2.1 小驼峰命名法

适用于变量名和方法名

  • 如果是一个单词,那么全部小写,比如:name
  • 如果是多个单词,那么从第二个单词开始,首字母大写,比如:firstName、maxAge
3.2.2 大驼峰命名法

适用于类名

  • 如果是一个单词,那么首字母大写。比如:Demo、Test。
  • 如果是多个单词,那么每一个单词首字母都需要大写。比如:HelloWorld
3.2.3 阿里巴巴命名规范细节
  1. 尽量不要用拼音。但是一些国际通用的拼音可视为英文单词。
  2. 平时在给变量名、方法名、类名起名字的时候,不要使用下划线或美元符号。

4.键盘录入

1.导包:找到类所在位置

1
2
import java.util.Scanner;
Lobster AIjava运行1

2.创建对象

1
2
Scanner sc = new Scanner(System.in);
Lobster AIjava运行1

3.接收 数据:以接受字符串 为例

1
2
int inputStr = sc.nextInt();
Lobster AIjava运行1

5.IntelliJ IDEA

IntelliJ IDEA是 Java语言开发的集成环境,它把代码的编写,编译,执行,调试等多种功能综合到一起,从而简化开发流程。

5.1 安装

官网:https://www.jetbrains.com/idea

  1. 双击资料中的安装包,点击next,准备安装(可以修改文件的安装路径);
  2. 勾选64-bit launcher(其他的不要勾选),表示在桌面新建一个64位的快捷方式;
  3. 点击Install(选择JetBrains),准备安装;
  4. 等进度条读取完毕之后,会有最终界面提示,点击finish即可。

破解版安装步骤参考资料中的视频(2022版本)。

5.2 IDEA中层级结构

结构分类

  • project(项目、工程)
  • module(模块)
  • package(包)
  • class(类)

project(项目、工程):淘宝、京东、黑马程序员网站都属于一个个项目,IDEA中就是一个个的Project。

module(模块):在一个项目中,可以存放多个模块,不同的模块可以存放项目中不同的业务功能代码,微信的微信、通讯录、发现等都可以是一个模块。

package(包):一个模块中又有很多的业务,比如微信模块的聊天功能就可以是一个包。

class(类):就是真正写代码的地方。

project中可以创建多个module,module中可以创建多个package,package中可以创建多个class。

5.3 idea操作

参考视频学习即可。

三、运算符

1.算数运算符

1
2
3
4
5
6
+ - * :跟小学数学中一模一样没有任何区别;

/:整数相除结果只能得到商,如果结果想要小数,必须要有小数参与运算(一个或两个)。

%:取模、取余。只能对整数操作,取两个数相除的余数。
Lobster AIjava运行12345

技巧

  1. 判断整数a是否是偶数:

a%2,结果为0表示a是偶数,结果是1表示a是奇数。

  1. 获取任意一个整数a上每一位数:

个位:a % 10

十位:a / 10 % 10

百位:a / 100 % 10

千位:a / 1000 % 10

…以此类推…

2.自增自减运算符

1
2
3
++  自增运算符
-- 自减运算符
Lobster AIjava运行12

a++和++a的区别

1
2
3
a++是先使用a,再进行++运算。
++a是先进行++运算,再使用a。
Lobster AIjava运行12

3.扩展赋值运算符

+=、-=、*=、/=、%=

就是把左边跟右边进行运算,把最终的结果赋值给左边,对右边没有任何影响。

注意点

扩展的赋值运算符中隐层还包含了一个强制转换,比如 a += b ;实际上相当于 a = (byte)(a + b);

4.关系运算符

符号 解释
== 就是判断左边跟右边是否相等,如果成立就是true,如果不成立就是false
!= 就是判断左边跟右边是否不相等,如果成立就是true,如果不成立就是false
> 就是判断左边是否大于右边,如果成立就是true,如果不成立就是false
>= 就是判断左边是否大于等于右边,如果成立就是true,如果不成立就是false
< 就是判断左边是否小于右边,如果成立就是true,如果不成立就是false
<= 就是判断左边是否小于等于右边,如果成立就是true,如果不成立就是false

关系运算符最终的结果一定是布尔类型的,结果要么是true,要么是false。

5.逻辑运算符

&:逻辑与(而且),两边都为真,结果才是真,只要有一个为假,那么结果就是假。

|:逻辑或(或者),两边都为假,结果才是假,只要有一个为真,那么结果就是真。

^:异或,如果两边相同,结果为false,如果两边不同,结果为true。

!:取反,false取反就是true,true取反就是false。

6.短路逻辑运算符

分类: && ||

当左边不能确定整个表达式的结果,右边才会执行。

当左边能确定整个表达式的结果,那么右边就不会执行了。从而提高了代码的运行效率。

7.三元运算符

格式:关系表达式 ? 表达式1 :表达式2 ;

三元运算符的最终结果一定要被使用,要么赋值给一个变量,要么直接打印出来。

8.隐式转换

自动类型提升,即把一个取值范围小的 数据或者变量,赋值给另一个取值范围大的变量。此时不需要我们额外写代码单独实现,是程序自动帮我们完成的。例如long a = 10;,int型常量10自动转换成long型赋值给变量a。

两种提升规则

  • 取值范围小的,和取值范围大的进行运算,小的会先提升为大的,再进行运算。

  • byte、short、char三种类型的数据在运算的时候,都会直接先提升为int,然后再进行运算。

    取值范围从小到大的关系*: byte < short < int < long < float < double

9.强制转换

如果要把一个取值范围大的数据或者变量赋值给另一个取值范围小的变量。是不允许直接操作,需要用到强制类型转换。

语法

1
2
3
4
5
目标数据类型 变量名 = (目标数据类型)被强转的数据;

例如:double a = 12.3;
int b = (int) a;
Lobster AIjava运行1234

注意:强制转换有可能会导致数据发生错误。(数据的精度丢失)

10.字符串相关操作

字符串的+操作:如果加操作出现了字符串,那么后面的加都会拼接成一个字符串(字符串加操作之前的加不受影响)。例如1 + 2 + "abc" + 2 + 1,结果为3abc21

字符的+操作:当+操作中出现了字符,会拿着字符到计算机内置的ASCII码表中去查对应的数字,然后再进行计算。例如‘a’ + 1的结果为98

11.原码、反码和补码

11.1 原码

十进制数据的二进制表现形式,最左边是符号位,0为正,1为负,

利用原码对正数的计算不会出错。

原码的弊端:但如果是负数计算,结果会出错,结果和预期相反。

11.2 反码

为了解决不能计算负数的问题而存在

正数的反码不变,负数的反码在原码的基础上,符号位不变,数值取反,0变1,1变0.

反码的弊端:负数运算时,如果结果不跨0,结果没问题,如果结果跨0,就会跟实际结果有1的偏差。

11.3 补码

为了解决负数计算时跨0的问题而出现的。

正数的补码不变,负数的补码在反码的基础上+1。

另外,补码还能多记录一个特殊值-128,,该 数据在1个字节下,没有原码和反码。

注意点:计算机中的存储和计算都是以补码的形式进行的。

11.3.1 隐式转换底层原理
  • 正数:在前面补0,符号位保持为0。
  • 负数:在前面补1,但符号位保持不变。
11.3 2 强制转换底层原理
  • 去掉前面的高位,截取后剩下的第一位作为符号位。

12.位运算符

运算符 含义 运算规则
& 逻辑与 0为false 1为true
| 逻辑或 0为false 1为true
<< 左移 向左移动,低位补0
>> 右移 向右移动,高位补0或1
>>> 无符号右移 向右移动,高位补0
  • 这些运算符都是针对补码操作的。
  • 左移一次相当于乘2,右移一次相当于除2

13.运算符的优先级

Java中涉及了很多的运算符,每一种运算符都有各自的优先级。但是这些优先级不需要记忆,只要知道小括号优先于所有即可。

四、流程控制语句

1.顺序结构

最基本的流程控制,没有特定的语法结构,按照代码的先后顺序,依次执行,程序中大多数的代码都是这样执行的。

2.判断和选择结构

2.1 if语句

2.1.1 格式1
1
2
3
4
if (关系表达式) {
语句体;
}
Lobster AIjava运行123

如果关系表达式为true,就执行语句体,否则跳过if语句。

细节

  1. 如果要对一个布尔类型的变量进行判断,不要写==,直接把变量写在小括号中即可。

  2. 如果大括号中的语句体只有一条,那么大括号可以省略不写

    如果大括号省略了,那么if只能控制距离他最近的那一条语句。

    建议,大括号还是不要省略

1
2
int a = 100; //相当于两条语句:1.定义变量a 2.给变量a赋值为100
Lobster AIjava运行1
  1. 大括号的开头可以另起一行书写,但是建议写在第一行的末尾
2.1.2 格式二
1
2
3
4
5
6
if (关系表达式) {
语句体1;
} else {
语句体2;
}
Lobster AIjava运行12345

如果关系表达式为true,执行语句体1,否则,执行语句体2。

2.1.3 格式三
1
2
3
4
5
6
7
8
9
10
if (关系表达式1) {
语句体1;
} else if (关系表达式2) {
语句体2;
}

else {
语句体n+1;
}
Lobster AIjava运行123456789

如果关系表达式1为true,执行语句体1,否则如果关系表达式2为true,执行语句体2,…,所有关系表达式都为false才执行语句体n+1。

2.2 switch语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
格式:
switch (表达式) {
case 1:
语句体1;
break;
case 2:
语句体2;
break;
...
default:
语句体n+1;
break;
}
Lobster AIjava运行12345678910111213

首先计算出表达式的值,然后和case依次比较,一旦有对应的值,就会执行相应的语句,在执行的过程中,遇到break就会结束;如果所有的case都和表达式的值不匹配,就会执行default语句体部分,然后程序结束掉。

细节

  • default可以放在任意位置,建议放在最后。
  • switch 不支持 long、float、double、boolean 等类型,如果需要处理这些类型,可以使用 if-else 语句。
  • case穿透:不写break会引发case穿透现象,即下面所有case语句都会执行(不会判断下面case的值),知道遇到break为止,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//输出2 3 4 -1    
public static void main(String[] args) {
int a = 2;
switch (a) {
case 1:
System.out.println(1);
break;
case 2:
System.out.println(2);
case 3:
System.out.println(3);
case 4:
System.out.println(4);
default:
System.out.println(-1);
break;
}
}
Lobster AIjava运行123456789101112131415161718
  • switch在JDK12的新特性:
1
2
3
4
5
6
7
// -> 自带break
switch (week) {
case 1, 2, 3, 4, 5 -> System.out.println("工作日");
case 6, 7 -> System.out.println("休息日");
default -> System.out.println("没有这个星期");
}
Lobster AIjava运行123456

3.循环结构

3.1 for循环

1
2
3
4
for (初始化语句;条件判断语句;条件控制语句) {
循环体语句;
}
Lobster AIjava运行123

执行流程

① 执行初始化语句

② 执行条件判断语句,看其结果是true还是false

如果是false,循环结束

如果是true, 执行③

③ 执行循环体语句

④ 执行②

细节:初始化语句只执行一次,其中定义的变量只在本for循环内存在。

练习:求和连续的自然数之和

1
2
3
4
5
6
7
8
9
10
11
12
public class ForTest02 {
public static void main(String[] args) {
//存放求和结果
int sum = 0;
for(int i=1; i<=5; i++) {
sum = sum + i; //累加
}
//打印求和结果
System.out.println("1-5之间的数据和是:" + sum);
}
}
Lobster AIjava运行1234567891011

3.2 while循环

1
2
3
4
5
while(条件判断语句){
循环体;
条件控制语句;
}
Lobster AIjava运行1234

执行流程:先判断条件判断语句,为true就执行循环体和条件控制语句,之后返回条件判断语句,…,直到条件判断语句为false位置结束循环。

练习:一张纸折叠多少次厚度能超过珠穆朗玛峰的高度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int height = 8844430; //珠穆朗玛峰高度
double paper = 0.1; //纸张厚度

int count = 0; //计数器,统计折叠次数

while(paper < height){
//折叠纸张
paper = paper * 2;
count++;
}

//4.打印一下纸张的厚度
System.out.println(count);//27
Lobster AIjava运行12345678910111213

3.3 do…while循环

1
2
3
4
5
do{
循环体;
条件控制语句;
}while(条件判断语句);
Lobster AIjava运行1234

执行流程:和while循环一致,只是会先执行循环体和条件控制语句再执行条件判断语句。

3.4 三种格式的区别

for和while循环,是先判断,再执行。

do…while是先执行,再判断。

当知道循环次数或者循环范围的时候,用for循环。

当不知道循环次数,也不知道循环范围,但是知道循环的结束条件时,用while循环。

3.5 循环高级

3.5.1 无限循环

又叫死循环。循环一直停不下来。

for格式

1
2
3
4
for(;;){
System.out.println("循环执行一直在打印内容");
}
Lobster AIjava运行123

while格式

1
2
3
4
while(true){
System.out.println("循环执行一直在打印内容");
}
Lobster AIjava运行123

do…while格式

1
2
3
4
do{
System.out.println("循环执行一直在打印内容");
}while(true);
Lobster AIjava运行123

注意事项

  • 最为常用的格式:while
  • 无限循环下面不能再写其他代码了,因为永远执行不到。
3.5.2 条件控制语句

break:不能单独存在,可以用在switch和循环中,表示结束本次switch或循环语句。

continue:不能单独存在,只能存在于循环当中。表示跳过本次循环,继续执行下次循环。

细节:如果循环中含有switch语句,则switch语句中的break会结束switch语句,而不是整个循环,如果希望跳出整个循环,需要为循环设置别名,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Scanner sc = new Scanner(System.in);
int number;
loop: //为循环起别名loop
while (true) {
System.out.println("请输入一个数字(0结束循环):");
number = sc.nextInt();
switch (number) {
case 0:
break loop; //输入0结束死循环loop
case 1:
System.out.println("一");
break; //跳出switch语句
case 2:
System.out.println("二");
break; //跳出switch语句
case 3:
System.out.println("三");
break; //跳出switch语句
default:
System.out.println("输入数字太大");
break; //跳出switch语句
}
}
Lobster AIjava运行1234567891011121314151617181920212223

4.Random

Random类是实现随机数生成的。

  1. 导包
1
2
3
import java.util.Random;
导包的动作必须出现在类定义的上边。
Lobster AIjava运行12
  1. 创建对象
1
2
3
Random r = new Random ();
上面这个格式里面,只有r是变量名,可以变,其他的都不允许变。
Lobster AIjava运行12
  1. 生成随机数
1
2
3
4
5
int number = r.nextInt(随机数的范围);
上面这个格式里面,只有number是变量名,可以变,其他的都不允许变。
随机数范围的特点:从0开始,不包含指定值。比如:参数为10,生成的范围[0,10)
技巧:[a,b]表示为r.nextInt(b - a + 1) + a
Lobster AIjava运行1234

五、数组

一种容器,可以同来存储同种 数据类型的多个值。

1.一维数组

1.1 数组的定义

格式一

1
2
3
4
5
数据类型[] 数组名

例如:
int[] array
Lobster AIjava运行1234

格式二

1
2
3
4
5
数据类型  数组名[]

例如:
int array[]
Lobster AIjava运行1234

方法括号跟数组名,谁写在前面,谁写在后面都是一样的。平时习惯性使用第一种方式。

1.2 数组的初始化

1.2.1 静态初始化

完整格式

1
2
3
4
5
数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3,元素4...};

例如:
int[] arr = new int[]{11,22,33};
Lobster AIjava运行1234

方括号中,多个元素之间一定要用逗号隔开。

简化格式

1
2
3
4
5
数据类型[] 数组名 = {元素1,元素2,元素3,元素4...};

例如:
int[] array = {1,2,3,4,5};
Lobster AIjava运行1234
1.2.2 动态初始化

格式

1
2
3
4
5
数据类型[] 数组名 = new 数据类型[数组的长度];

例如:
int[] agesArr = new int[3];
Lobster AIjava运行1234

数组的默认初始化值

  • 整数类型:0
  • 小数类型:0.0
  • 布尔类型:false
  • 字符类型:‘\u0000’
  • 引用类型:null
1.2.3 两种初始化方式的区别

静态初始化:手动指定数组的元素,系统会根据元素的个数,计算出数组的长度。

动态初始化:手动指定数组长度,由系统给出默认初始化值。

使用场景

只明确元素个数,但是不明确具体的数据,推荐使用动态初始化。

已经明确了要操作的所有数据,推荐使用静态初始化。

1.3 地址值

数组的本质就是内存中一段连续的存储空间,数组的值就是这个连续存储空间的地址。打印数组的时候,实际出现的是数组的地址值:

1
2
3
int[] arr = {1,2,3,4,5};
System.out.println(arr);//[I@6d03e736
Lobster AIjava运行12
  • [ :表示现在打印的是一个数组。
  • I:表示现在打印的数组是int类型的。
  • @:仅仅是一个间隔符号而已。
  • 6d03e736:就是数组在内存中真正的地址值。(十六进制的)

我们习惯性会把[I@6d03e736这个整体称之为数组的地址值。

1.4 数组元素访问

索引

数组的每个元素都有其对应的索引。

  • 索引一定是从0开始的。
  • 连续不间断。
  • 逐个+1增长。

例如:上面定义的arr,1的索引为0,2的索引为1,…,以此类推。

格式

1
2
数组名[索引];
Lobster AIjava运行1

作用

  • 获取数组中对应索引上的值。例如arr[2]就获取到了3。
  • 修改数组中对应索引上的值,一旦修改,原来的值就会被覆盖,无法恢复。例如arr[2] = 2;,就修改了arr[2],以后访问到的arr[2]都表示2,而不是3。

1.5 数组的遍历

遍历:就是把数组里面所有的内容一个一个全部取出来。

数组的长度:数组名.length;

通用代码:

1
2
3
4
5
for(int i = 0; i < arr.length; i++){
//在循环的过程中,i依次表示数组中的每一个索引
System.out.println(arr[i]);//就可以把数组里面的每一个元素都获取出来,并打印在控制台上了。
}
Lobster AIjava运行1234

2.二维数组

二维数组的本质还是一维数组,只是数组的每一个元素是一个一维数组的地址值,而二位数组也是一个地址值。

二维数组的操作和一维数组很类似,但也有不同,这里只介绍不同地方。

2.1 初始化

2.1.1 静态初始化

格式

1
2
3
4
5
数据类型[][] 数组名 = new 数据类型[][]{{元素1, 元素2, ...},{元素1, 元素2, ...},...}

例如:
int[][] arr=new int[][]{{11, 22},{33, 44, 55}}
Lobster AIjava运行1234

简化格式

1
2
3
4
5
数据类型[][] 数组名 = {{元素1, 元素2, ...},{元素1, 元素2, ...},...}

例如:
int[][] arr={{11, 22},{33, 44, 55}}
Lobster AIjava运行1234
  • 二维数组的每一个一维数组长度不一定一致。
2.1.2 动态初始化

格式

1
2
3
4
5
数据类型[][] 数组名 = new 数据类型[m][n];

例如:
int[][] arr=new int[2][3]
Lobster AIjava运行1234

m表示这个二维数组可以存放多少个一维数组;

n表示每一个一维数组可以存放多少个元素。

  • 这个时候二维数组的每一个一维数组长度必须一致。

2.2 数组元素访问

1
2
3
4
arr[0]可以获取二维数组arr中的第一个一维数组,本身是一个地址。

arr[0][0]可以获取二维数组arr中的第一个一维数组中的第一个元素,本身是一个值。
Lobster AIjava运行123

通过赋值运算符可以进行赋值,赋值后二维数组那个位置的值将不存在,会被新值替代。

2.3 遍历二维数组

arr.length可以获得二维数组arr包含几个一维数组,不能获得二维数组arr最多存几个元素。

二维数组中存的实际上是每一个一维数组的地址值。

1
2
3
4
5
6
7
int[][] arr = {{1, 2, 3}, {5, 6, 7, 8}};
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
System.out.println(arr[i][j]);
}
}
Lobster AIjava运行123456

六、方法

方法(method)是程序中最小的执行单元,必须先定义才能调用,类似c语言中的函数。

1.方法的定义与调用

1.1 无参数方法定义和调用

定义

1
2
3
4
5
6
7
8
9
public static void 方法名 () {
// 方法体;
}

例如:
public static void method () {
System.out.println("我是一个method方法");
}
Lobster AIjava运行12345678

调用

1
2
3
4
5
方法名();

例如:
method();
Lobster AIjava运行1234

1.2 带参数方法定义和调用

定义

参数:由 数据类型和变量名组成,多个参数间用逗号隔开: 数据类型 变量名;

参数范例:int a;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void 方法名 (参数1) {
方法体;
}
public static void 方法名 (参数1, 参数2, 参数3...) {
方法体;
}

例如:
public static void isEvenNumber(int number){
...
}
public static void getMax(int num1, int num2){
...
}
Lobster AIjava运行1234567891011121314

调用

方法调用时,参数的数量与类型必须与方法定义中的设置相匹配。

1
2
3
4
5
6
7
方法名(参数);
方法名(参数1,参数2);

例如:
isEvenNumber(10);
getMax(10,20);
Lobster AIjava运行123456
形参和实参
  1. 形参:方法定义中的参数,等同于变量定义格式,例如:number、num1、num2。
  2. 实参:方法调用中的参数,等同于使用变量或常量,例如: 10、20。

1.3 带返回值方法的定义和调用

定义

方法定义时return后面的返回值与方法定义上的数据类型要匹配。

1
2
3
4
5
6
7
8
9
10
11
12
public static 数据类型 方法名 ( 参数 ) { 
return 数据 ;
}

例如:
public static boolean isEvenNumber( int number ) {
return true ;
}
public static int getMax( int a, int b ) {
return a > b ? a : b;
}
Lobster AIjava运行1234567891011

调用

方法的返回值通常会使用变量接收,否则该返回值将无意义。

1
2
3
4
5
6
7
方法名 ( 参数 ) ;
数据类型 变量名 = 方法名 ( 参数 ) ;

例如:
isEvenNumber ( 5 ) ;
boolean flag = isEvenNumber ( 5 );
Lobster AIjava运行123456

1.4 方法的通用格式

1
2
3
4
5
public static 返回值类型 方法名(参数) {
方法体;
return 数据 ;
}
Lobster AIjava运行1234
  • 返回值类型可以任意,但是必须和return的结果类型一致(引用数据类型也可以不一致,这里牵扯到多态,后面讲)。
  • 方法名推荐使用小驼峰命名,见名知意。
  • 参数可以是0个、1个、多个,但必须包含数据类型和变量名。
  • 如果返回值类型是void,return语句可以省略。

1.5 方法的注意事项

  • 方法不能嵌套定义,否则会引发编译错误!!!
  • void表示无返回值,可以省略return,也可以单独的书写return,后面不加数据。

2.方法重载

方法重载概念:方法重载指同一个类中定义的多个方法之间的关系,满足下列条件的多个方法相互构成重载

  1. 多个方法在同一个类中
  2. 多个方法具有相同的方法名
  3. 多个方法的参数不相同,即类型不同、数量不同或顺序不同

注意事项

重载仅对应方法的定义,与方法的调用无关。

重载仅针对同一个类中方法的名称和参数(形参),而和返回值无关(返回值可以不同)。

例如:重载两个方法获取两个数的最大值

1
2
3
4
5
6
7
8
9
10
11
12
    //int
public static boolean compare(int a, int b) {
System.out.println("int");
return a > b;
}

//long
public static boolean compare(long a, long b) {
System.out.println("long");
return a > b;
}
Lobster AIjava运行1234567891011

七、面向对象

java中,万物皆对象,即客观存在的一切事物都可以是一个对象。

类是对现实生活中一类具有共同属性和行为的事物的抽象,如猫就可以是一个类,对象就是一个具体的猫。

1.类的定义

类的组成是由属性和行为两部分组成

  • 属性:在类中通过成员变量来体现(类中方法外的变量)
  • 行为:在类中通过成员方法来体现(和前面的方法相比去掉static关键字即可)

类的定义步骤:

①定义类

②编写类的成员变量

③编写类的成员方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class 类名 {
// 成员变量
变量1的数据类型 变量1
变量2的数据类型 变量2;

// 成员方法
方法1;
方法2;
}

例如:
//手机类
public class Phone {
//成员变量
String brand; //品牌
int price; //价格

//成员方法
public void call() {
System.out.println("打电话");
}

public void sendMessage() {
System.out.println("发短信");
}
}
Lobster AIjava运行1234567891011121314151617181920212223242526

2.对象的使用

  • 创建对象的格式:

    1
    2
    类名 对象名 = new 类名();
    Lobster AIjava运行1
  • 调用成员的格式:

    • 对象名.成员变量
    • 对象名.成员方法();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    public static void main(String[] args) {
//创建对象
Phone p = new Phone();

//使用成员变量
System.out.println(p.brand); //null
System.out.println(p.price); //0

p.brand = "小米";
p.price = 2999;

System.out.println(p.brand); //小米
System.out.println(p.price); //2999

//使用成员方法
p.call(); //打电话
p.sendMessage(); //发短信
}
Lobster AIjava运行123456789101112131415161718

3.对象内存图

3.1 单个对象内存图

  • 成员变量使用过程

image-20250523112932156

对象存放在堆内存当中(本身的值就是所占空间的地址值),成员变量被放在堆中,通过对象名.可以在内存中找到变量。

  • 成员方法调用过程

image-20250523113421935

方法运行时进栈,运行完出栈。

3.2 多个对象内存图

  • 成员变量使用过程

image-20250523113616614

每一个对象都有一个单独的堆内存空间,成员变量存储在各自的内存区域中。

  • 成员方法调用过程

image-20250523113852412

多个对象共用一套成员方法,运行时进栈,运行完出栈。

4.成员变量和局部变量

区别 成员变量 局部变量
类中位置不同 类中,方法外 方法内,方法声明上
初始化值不同 有默认初始化值 没有,使用之前必须赋值
内存位置不同 堆内存 栈内存
生命周期不同 随着对象的创建而存在,随着对象的消失而消失 随着方法的调用而存在,随着方法的运行结束而消失
作用域 整个类中有效 当前方法中有效

5.封装

5.1 封装思想

封装概述

  • 是面向对象三大特征之一(封装,继承,多态)
  • 对象代表什么,就得封装对应的 数据,并提供数据对应的行为 ,比如人画圆,画圆方法要放在圆这个类里。

封装代码实现

将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问。成员变量private,提供对应的getXxx()/setXxx()方法

5.2 private关键字

private是一个修饰符,可以用来修饰成员(成员变量,成员方法)。

被private修饰的成员,只能在本类进行访问,如果其他类想要访问,就要提供相应的get 和 set方法(用public修饰)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/*
学生类
*/
class Student {
//成员变量
private String name;
private int age;

//get/set方法
public void setName(String n) {
name = n;
}

public String getName() {
return name;
}

public void setAge(int a) {
age = a;
}

public int getAge() {
return age;
}

public void show() {
System.out.println(name + "," + age);
}
}
/*
学生测试类
*/
public class StudentDemo {
public static void main(String[] args) {
//创建对象
Student s = new Student();

//使用set方法给成员变量赋值
s.setName("林青霞");
s.setAge(30);

s.show();

//使用get方法获取成员变量的值
System.out.println(s.getName() + "---" + s.getAge());
System.out.println(s.getName() + "," + s.getAge());

}
}
Lobster AIjava运行12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849

5.3 this关键字

this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)

  • 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量(就近原则)
  • 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量

this的本质:代表方法调用者的地址值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class User {
private String name;
...
public void setName(String name) {
this.name = name; //this指代方法调用者u,通过点得到u的成员变量
}
}
public class text6 {
public static void main(String[] args) {
User u=new User();
u.setName("zhangsan");
}
}
Lobster AIjava运行12345678910111213

当调用setName方法时,this关键字记录的就是u的地址值。

6.构造方法

构造方法是用来创建对象的,主要是完成对象 数据的初始化。

格式

1
2
3
4
5
6
7
8
9
10
11
12
13
public class 类名{
修饰符 类名( 参数 ) {
构造体
}
}

例如:
public class Phone{
public Phone(){
System.out.println("构造一个手机对象");
}
}
Lobster AIjava运行123456789101112

当创建对象时,会自动执行构造方法的构造体。

注意事项

  • 构造方法的创建:如果没有定义构造方法,系统会给出一个默认的无参数构造方法,如果定义了构造方法,系统将不再提供默认的构造方法。如果需要,可以自定义无参数构造方法。
  • 推荐的使用方式:无论是否使用,都手工书写无参数构造方法
  • 可以使用带参构造,为成员变量进行初始化

标准 JavaBean类

① 类名需要见名知意

② 成员变量使用private修饰

③ 提供至少两个构造方法

  • 无参构造方法
  • 带全部参数的构造方法

④ get和set方法

提供每一个成员变量对应的setXxx()/getXxx()

⑤ 如果还有其他行为,也需要写上

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Student {
//成员变量
private String name;
private int age;

//构造方法
public Student() {
}

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

//成员方法
public void setName(String name) {
this.name = name;
}

public String getName() {
return name;
}

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

public int getAge() {
return age;
}

public void show() {
System.out.println(name + "," + age);
}
}

public class StudentDemo {
public static void main(String[] args) {
//无参构造方法创建对象后使用setXxx()赋值
Student s1 = new Student();
s1.setName("林青霞");
s1.setAge(30);
s1.show();

//使用带参构造方法直接创建带有属性值的对象
Student s2 = new Student("林青霞",30);
s2.show();
}
}
Lobster AIjava运行12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849

推荐以后自定义类都进行标准化,这就是封装。

为标准类的成员赋值的两种方法:

  1. 无参构造方法创建对象后使用setXxx()赋值
  2. 使用带参构造方法直接创建带有属性值的对象

7.static关键字

关于 static 关键字的使用,它可以用来修饰的成员变量和成员方法。被static修饰的成员是属于类的,所有对象共用,放在静态区中;没有static修饰的成员变量和方法则是属于对象的。

7.1 static修饰成员变量

有static修饰成员变量,说明这个成员变量是属于类的,这个成员变量称为类变量或者静态成员变量。 直接用类名访问即可。

静态变量定义格式

1
2
3
4
5
6
7
8
修饰符 static 数据类型 变量名 = 初始值;

例如:
public class Student {
public static String schoolName = "传智播客"// 属于类,只有一份。
// .....
}
Lobster AIjava运行1234567

静态成员变量的访问

1
2
3
4
5
6
7
类名.静态变量

例如:
其他类或Student类{ //其他类可以通过类名使用
System.out.println(Student.schoolName); //传智播客
}
Lobster AIjava运行123456

实例变量定义格式

无static修饰的变量是属于对象的,也叫实例变量。

实例变量的访问

1
2
对象.实例成员变量
Lobster AIjava运行1

7.2 static修饰成员方法

静态方法及其访问

有static修饰成员方法,说明这个成员方法是属于类的,这个成员方法称为类方法或者静态方法。 直接用类名访问即可。

1
2
3
4
5
6
7
8
9
10
11
12
定义:
public class Student{
public static void study(){
System.out.println("我们都在学习");
}
}

访问:
其他类或Student类{
Student.study(); //我们都在学习
}
Lobster AIjava运行1234567891011

实例方法及其访问

无static修饰的成员方法属于每个对象的,这个成员方法也叫做实例方法

需要注意的是:实例方法是属于每个对象,必须创建类的对象才可以访问。

格式:对象.实例方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Student{
private String name;
//get和set方法
...
//实例方法
public void eat(String foot){
System.out.println(this.name + "正在吃" + foot);
}
}
其他类或Student类{
Student student = new Student("张三");
student.eat("汉堡包"); //张三正在吃汉堡包
}
Lobster AIjava运行12345678910111213

7.3 小结

1.当 static 修饰成员变量或者成员方法时,该变量称为静态变量,该方法称为静态方法。该类的每个对象都共享同一个类的静态变量和静态方法。任何对象都可以更改该静态变量的值或者访问静态方法,但是不推荐这种方式去访问。

2.无static修饰的成员变量或者成员方法,称为实例变量,实例方法,实例变量和实例方法必须创建类的对象,然后通过对象来访问。

3.static修饰的成员属于类,会存储在静态区,是随着类的加载而加载的,且只加载一次,所以只有一份,节省内存。存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。它优先于对象存在,所以,可以被所有对象共享。

4.无static修饰的成员,是属于对象,对象有多少个,他们就会出现多少份。所以必须由对象调用。

注意

  • 静态只能访问静态:静态方法中只能使用静态的成员变量,且只能调用静态方法。
  • 非静态可以访问所有:非静态方法中既可以访问静态变量和静态方法,又可以访问非静态变量和非静态方法。

实际开发中static的使用

实际开发中,static除了用在main主方法所在测试类中,还用在工具类中。

工具类:我们把经常使用的方法封装到一个类中,并用public static修饰,需要使用时可以通过类名调用,大大节省开发压力。

工具类常常放在自创建utils包下,命名成类的功能Util,如ArrayUtil表示这个类封装了数组操作的方法。

8.继承

8.1 概述

继承:就是子类继承父类的属性行为(方法),使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。

简单理解,就是父类有的子类也有,只要继承这个父类就可以使这个类具有父类的东西。

image-20250527175543147

继承的好处

  1. 提高代码的复用性(减少代码冗余,相同代码重复利用)。
  2. 使类与类之间产生了关系。

8.2 继承的格式

1
2
3
4
5
6
7
8
class 父类 {
...
}

class 子类 extends 父类 {
...
}
Lobster AIjava运行1234567

需要注意: Java是单继承的,一个类只能继承一个直接父类,跟现实世界很像,但是Java中的子类是更加强大的。

顶层父类是Object类。所有的类默认继承Object,作为父类

8.3 子类不能继承的内容

并不是父类的所有内容都可以给子类继承的:

  • 子类不能继承父类的构造方法

  • 子类能继承父类的所有成员变量

  • 子类能继承父类的所有非私有成员方法,父类的私有成员方法子类不能继承

    子类可以直接使用父类的所有非private修饰的成员,父类的private成员子类不能直接使用,如果想要使用,需要通过父类提供的getXxx()和setXxx()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Demo03 {
public static void main(String[] args) {
Zi z = new Zi();
System.out.println(z.num1);
// System.out.println(z.num2); // 私有的子类无法使用
// 通过getter/setter方法访问父类的private成员变量
System.out.println(z.getNum2());

z.show1();
// z.show2(); // 私有的子类无法使用
}
}

class Fu {
public int num1 = 10;
private int num2 = 20;

public void show1() {
System.out.println("show1");
}

private void show2() {
System.out.println("show2");
}

public int getNum2() {
return num2;
}

public void setNum2(int num2) {
this.num2 = num2;
}
}

class Zi extends Fu {
}
Lobster AIjava运行123456789101112131415161718192021222324252627282930313233343536

8.4 继承的成员特点

8.4.1 成员变量

如果子类父类中出现不重名的成员变量,这时的访问是没有影响的

如果子类父类中出现重名的成员变量,这时的访问是有影响的

  • 子父类中出现了同名的成员变量时,子类会优先访问自己对象中的成员变量(遵守就近原则)。

super访问父类成员变量

子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用super 关键字,修饰父类成员变量,类似于之前学过的 this

super代表的是父类对象的引用,this代表的是当前对象的引用

  • 使用格式:
1
2
super.父类成员变量名
Lobster AIjava运行1
8.4.2 成员方法

如果子类父类中出现不重名的成员方法,这时的调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。

如果子类父类中出现重名的成员方法,则创建子类对象调用该方法的时候,子类对象会优先调用自己的方法。

8.4.2.1 方法重写

子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。

@Override重写注解

  • @Override:注解,重写注解校验!
  • 这个注解标记的方法,就说明这个方法必须是重写父类的方法,否则编译阶段报错。
  • 建议重写都加上这个注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Animal  {
public void run(){
System.out.println("动物跑的很快!");
}
public void cry(){
System.out.println("动物都可以叫~~~");
}
}

public class Cat extends Animal {
// 方法重写
@Override
public void cry(){
System.out.println("我们一起学猫叫,喵喵喵!喵的非常好听!");
}
}
Lobster AIjava运行12345678910111213141516

注意事项

  1. 方法重写是发生在子父类之间的关系。
  2. 子类方法覆盖父类方法,必须要保证权限大于等于父类权限,推荐权限一致。
  3. 子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。
8.4.3 构造方法

子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。

super()必须写在构造方法的第一行

继承后子类构方法器特点:子类所有构造方法的第一行都会默认先调用父类的无参构造方法

8.4.3.1 super和this的用法格式

super和this完整的用法

1
2
3
4
5
6
this.成员变量    	--    本类的
super.成员变量 -- 父类的

this.成员方法名() -- 本类的
super.成员方法名() -- 父类的
Lobster AIjava运行12345

构造方法格式

1
2
3
super(...) -- 调用父类的构造方法,根据参数匹配确认
this(...) -- 调用本类的其他构造方法,根据参数匹配确认
Lobster AIjava运行12

super(…)的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Person {
private String name ="凤姐";
private int age = 20;

public Person() {
System.out.println("父类无参");
}

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

// getter/setter省略
}

class Student extends Person {
private double score = 100;

public Student() {
super(); // 调用父类无参构造方法,默认就存在,可以不写,必须在第一行
System.out.println("子类无参");
}

public Student(String name , int age,double score) {
super(name ,age);// 调用父类有参构造方法Person(String name , int age)初始化name和age
this.score = score;
System.out.println("子类有参");
}
// getter/setter省略
}
Lobster AIjava运行12345678910111213141516171819202122232425262728293031

super(…)图解

image-20250527234013089

this(…)的使用

默认是去找本类中的其他构造方法,根据参数来确定具体调用哪一个构造方法。

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

public Student() {
// 调用其他构造方法:Student(String name, int age, char sex)
this("徐干",21,'男'); //基本不用
}

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

//get和set方法省略
}
Lobster AIjava运行123456789101112131415161718

小结

  • 子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。
  • super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
  • super(…)和this(…)是根据参数去确定调用父类哪个构造方法的。
  • super(…)可以调用父类构造方法初始化继承自父类的成员变量的 数据
  • this(…)可以调用本类中的其他构造方法。

注意:由于静态方法中没有this和super关键字,所以不能在静态方法中使用this和super,否则会报错!

8.5 继承的特点

  1. Java只支持单继承,不支持多继承。
1
2
3
4
5
6
// 一个类只能有一个父类,不可以有多个父类。
class A {}
class B {}
class C1 extends A {} // ok
// class C2 extends A, B {} // 错误
Lobster AIjava运行12345
  1. 一个类可以有多个子类。
1
2
3
4
5
// A可以有多个子类
class A {}
class C1 extends A {}
class C2 extends A {}
Lobster AIjava运行1234
  1. 可以多层继承。
1
2
3
4
class A {}
class C1 extends A {}
class D extends C1 {}
Lobster AIjava运行123

顶层父类是Object类。所有的类默认继承Object,作为父类。

9.多态

9.1 多态的定义和前提

多态是继封装继承之后,面向对象的第三大特性。

多态是指同一行为,具有多个不同表现形式。比如Cat和Dog都是动物,都有吃的行为,但是吃的形式不一样。

多态体现的格式

1
2
3
4
5
6
7
8
9
10
11
12
父类类型 变量名 = new 子类/实现类构造器;
变量名.方法名();

如Person有三个实现类Administrator、Student和Teacher,这三个实现类都有show方法:
Person p = new Student();
p.show("Student"); //Student

可以定义一个方法,用来接受所有子类对象:
public static void show(Person p){ //可以接受所有Person类的子类对象
p.show();
}
Lobster AIjava运行1234567891011

多态的前提

  1. 有继承或者实现关系

  2. 方法的重写【意义体现:不重写,无意义】

  3. 父类引用指向子类对象【格式体现】

    父类类型:指子类对象继承的父类类型,或者实现的父接口类型。

9.2 多态的使用场景

  • 当一个方法的形参是一个类,我们可以传递这个类所有的子类对象。
  • 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象(后面会学)。
  • 而且多态还可以根据传递的不同对象来调用不同类中的方法。

9.3 多态的运行特点

调用成员变量时:编译看左边,运行看左边

调用成员方法时:编译看左边,运行看右边

1
2
3
4
5
6
7
8
Fu f = new Zi();
//编译看左边的父类中有没有name这个属性,没有就报错
//在实际运行的时候,把父类name属性的值打印出来
System.out.println(f.name);
//编译看左边的父类中有没有show这个方法,没有就报错
//在实际运行的时候,运行的是子类中的show方法
f.show();
Lobster AIjava运行1234567

9.4 引用类型转换

9.4.1 问题引入

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误,简言之,如果子类有些独有的功能,此时多态的写法就无法访问子类独有功能了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Animal{
public void eat()
System.out.println("动物吃东西!")

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

public void catchMouse() {
System.out.println("抓老鼠");
}
}

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

class Test{
public static void main(String[] args){
Animal a = new Cat();
a.eat();
a.catchMouse();//编译报错,编译看左边,Animal没有这个方法
}
}
Lobster AIjava运行12345678910111213141516171819202122232425262728

解决方法:通过强制类型转换实现访问子类独有功能,但是,强转只能把父类强转成子类,两个不相关的子类类型(如Cat和Dog)之间不能相互转换,否则会引发编译错误 ClassCastException

9.4.2 转型

多态的转型分为向上转型(自动转换)与向下转型(强制转换)两种。

向上转型(自动转换)

多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。当父类引用指向一个子类对象时,便是向上转型。

1
2
3
父类类型  变量名 = new 子类类型();
如:Animal a = new Cat();
Lobster AIjava运行12

向下转型(强制转换)

父类类型向子类类型向下转换的过程,这个过程是强制的。将父类引用转为子类引用,可以使用强制类型转换的格式。

1
2
3
4
子类类型 变量名 = (子类类型) 父类变量名;
如:Aniaml a = new Cat();
Cat c =(Cat) a;
Lobster AIjava运行123

转型时的异常

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat

// 向下转型
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
}
}
Lobster AIjava运行1234567891011

虽然可以通过编译,但是a本质上定义成了Cat类型,所以不能成功转型,报出异常 ClassCastException

9.4.3 instanceof关键字

为了避免ClassCastException的发生, Java提供了 instanceof 关键字,给引用变量做类型的校验:

1
2
3
4
变量名 instanceof 数据类型 
如果变量属于该数据类型或者其子类类型,返回true
如果变量不属于该数据类型或者其子类类型,返回false
Lobster AIjava运行123

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat

// 向下转型,a被当做Cat类型去匹配
if (a instanceof Cat){ //true
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog){ //false
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse
}
}
}
Lobster AIjava运行12345678910111213141516

instanceof新特性

JDK14的时候提出了新特性,把判断和强转合并成了一行。

1
2
3
4
5
6
7
8
9
10
//先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
//如果不是,则不强转,结果直接是false
if(a instanceof Dog d){
d.lookHome();
}else if(a instanceof Cat c){
c.catchMouse();
}else{
System.out.println("没有这个类型,无法转换");
}
Lobster AIjava运行123456789

10.键盘录入

键盘录入的使用参考 二.4 ,这里只扩展。

键盘录入类Scanner的方法

next()、nextLine():可以接受任意 数据,但是都会返回一个字符串。

nextInt():只能接受整数,默认是int型数据。

nextDouble():能接收整数和小数,但是都会看做double型小数返回。

底层细节

  1. next(),nextInt(),nextDouble()在接收数据的时候,遇到空格、回车、制表符其中一个就会停止接收数据。

  2. next(),nextInt(),nextDouble()在接收数据的时候,遇到空格、回车、制表符其中一个就会停止接收数据。但是这些符号 和 后面的数据还在内存中并没有接收。如果后面还有其他键盘录入的方法,会自动将这些数据接收。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Scanner sc = new Scanner(System.in);
    String s1 = sc.next();
    String s2 = sc.next();
    System.out.println(s1);
    System.out.println(s2);
    //此时值键盘录入一次a b(注意a和b之间用空格隔开)
    //那么第一个next();会接收a,a后面是空格,那么就停止,所以打印s1是a
    //但是 空格+b 还在内存中。
    //第二个next会去掉前面的空格,只接收b
    //所以第二个s2打印出来是b
    Lobster AIjava运行12345678910
  3. nextLine()方法是把一整行全部接收完毕,直到遇到回车才结束接受。

混用引起的后果

next(),nextInt(),nextDouble()为一套配套使用,nextLine()单独使用,如果混用,会有严重的后果。

1
2
3
4
5
6
Scanner sc = new Scanner(System.in);//①
int i = sc.nextInt();//②
String s = sc.nextLine();//③
System.out.println(i);//④
System.out.println(s);//⑤
Lobster AIjava运行12345

当代码运行到第二行,会让我们键盘录入,此时录入123。

但是实际上我们录的是123+回车。

而nextInt是遇到空格,回车,制表符都会停止。

所以nextInt只能接受123,回车还在内存中没有被接收。

此时就被nextLine接收了。

所以,如果混用就会导致nextLine接收不到数据。

11.对象数组

自定义类可以理解成是自定义数据类型,它也可以用来创建数组,只是数组中每个元素都是一个对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.itheima.test6;

public class Phone {
private String brand;//品牌
private int price;//价格
private String color;//颜色

public Phone() {
}

public Phone(String brand, int price, String color) {
this.brand = brand;
this.price = price;
this.color = color;
}

public String getBrand() {
return brand;
}

public void setBrand(String brand) {
this.brand = brand;
}

public int getPrice() {
return price;
}

public void setPrice(int price) {
this.price = price;
}

public String getColor() {
return color;
}

public void setColor(String color) {
this.color = color;
}
}


package com.itheima.test6;

import java.math.BigDecimal;

public class PhoneTest {
public static void main(String[] args) {
//1.创建一个数组
Phone[] arr = new Phone[3];

//2.创建手机的对象
Phone p1 = new Phone("小米",1999,"白色");
Phone p2 = new Phone("华为",4999,"蓝色");
Phone p3 = new Phone("魅族",3999,"红色");

//3.把手机对象添加到数组当中
arr[0] = p1;
arr[1] = p2;
arr[2] = p3;

//4.获取三部手机的平均价格
int sum = 0;
for (int i = 0; i < arr.length; i++) {
//i 索引 arr[i] 元素(手机对象)
Phone phone = arr[i];
sum = sum + phone.getPrice();
}

//5.求平均值
double avg2 = sum * 1.0 / arr.length;
System.out.println(avg2);//3665.6666666666665
}
}
Lobster AIjava运行1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374

12.包

包在操作系统中其实就是一个文件夹。包是用来分门别类的管理技术,不同的技术类放在不同的包下,方便管理和维护。

建包操作如下:

image-20250529005846113

包名的命名规范

1
2
3
4
包名.包的作用
//需要全部英文小写,见名知意
// 例如:com.itheima.oa
Lobster AIjava运行123

导包

  1. 什么时候需要导包?

    情况一:在使用Java中提供的非核心包中的类时

    情况二:使用自己写的其他包中的类时

  2. 什么时候不需要导包?

    情况一:在使用Java核心包( java.lang)中的类时

    情况二:在使用同一个包中的类时

全类名

如果在同一个类中使用了不同包下相同名字的类,就需要使用全类名:

1
2
3
全类名:包名 + 类名
com.itheima.homework.demo1.Student s1 = new com.itheima.homework.demo1.Student();
Lobster AIjava运行12

13.权限修饰符

在Java中提供了四种权限修饰符,不同的权限修饰符有不同的访问权限,例如前面学的public表示全部类都可以访问。

不同权限修饰符访问权限

  • public:公共的,所有地方都可以访问。

  • protected:本类 ,本包,其他包中的子类都可以访问。

  • 默认(没有修饰符):本类 ,本包可以访问。

    注意:默认是空着不写,不是default

  • private:私有的,只有当前类可以访问。

权限修饰符大小比较:public > protected > 默认 > private

总结

public protected 默认 private
同一类中
同一包中的类
不同包的子类
不同包中的无关类

建议这样使用权限:

  • 成员变量使用private ,隐藏细节。
  • 构造方法使用 public ,方便创建对象。
  • 成员方法使用public ,方便调用方法。

14.final关键字

final: 不可改变,最终的含义。可以用于修饰类、方法和变量。被final修饰的成员是常量,值不可变,类似c++中的const。

  • 类:被修饰的类,不能被继承。
  • 方法:被修饰的方法,不能被重写。
  • 变量:被修饰的变量,有且仅能被赋值一次。

修饰类

1
2
3
4
5
6
7
8
final class 类名 {
}

例如:
final class Fu {
}
// class Zi extends Fu {} // 报错,不能继承final的类
Lobster AIjava运行1234567

public final class Stringpublic final class Mathpublic final class Scanner 等API,都是只提供给我们使用的,不能继承去更改内容。

修饰方法

1
2
3
4
修饰符 final 返回值类型 方法名(参数列表){
//方法体
}
Lobster AIjava运行123

修饰变量

1
2
3
例如:
public static final Double pi = 3.14159265358;
Lobster AIjava运行12

pi被public和static修饰,说明任何类都可以可以通过类名得到pi,被final修饰说明pi不能再被赋值,否则会报错。

final修饰成员变量时可以只定义变量,赋值放在构造方法中,但是每个构造方法中都要赋值一次(非常不建议这样做):

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Student {
final int num = 10;
final int num2;

public Student() {
this.num2 = 20;
}

public Student(String name) {
this.num2 = 20;
}
}
Lobster AIjava运行123456789101112
  • 被final修饰的变量推荐用大写形式,多个单词用下划线分隔,例如final String HELLO_WORLD = "hello world"

15.代码块

1.局部代码块

  • 用大括号把一段代码括起来,用以控制变量的生命周期。
1
2
3
4
5
6
7
8
9
public class Test{
public static void main(String[] args){
{
int num = 10; //只在包裹它的第一个大括号中有效
}
num = 20; //报错,因为num未定义
}
}
Lobster AIjava运行12345678

2.构造代码块

  • 写在成员位置的代码块

  • 可以把多个构造方法中的重复代码抽取出来

  • 执行时机:在创建本类对象的时候会先执行构造代码块再执行构造方法

1
2
3
4
5
6
7
8
9
10
public class Student{
private String name;
{
System.out.println("构造代码块");
}
public Student(){
System.out.println("空参构造");
}
}
Lobster AIjava运行123456789

每次创建对象都会先打印构造代码块(创建几次打印几次),再打印空参构造。

3.静态代码块

  • 格式:static{}

  • 特点:需要通过关键字修饰,随着类的加载而加载,并且自动触发,只执行一次

  • 使用场景:在类加载时,做一些 数据初始化时使用

1
2
3
4
5
6
7
8
9
10
public class Student{
private String name;
static{
System.out.println("构造代码块");
}
public Student(){
System.out.println("空参构造");
}
}
Lobster AIjava运行123456789

只有第一次创建对象时打印构造代码块,后面创建对象不会打印。

16.抽象类

如果父类知道子类需要使用的方法,但是不知道怎么实现,既可以使用抽象的思想实现。

  • 抽象方法 : 没有方法体的方法。
  • 抽象类:包含抽象方法的类。

16.1 abstract使用格式

abstract是抽象的意思,用于修饰方法方法和类,修饰的方法是抽象方法,修饰的类是抽象类。

抽象方法

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

示例:
public abstract void run();
Lobster AIjava运行1234

抽象类

1
2
3
4
5
6
7
8
abstract class 类名字 { 
}

示例:
public abstract class Animal {
public abstract void run();
}
Lobster AIjava运行1234567

抽象类的使用

继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 父类,抽象类
abstract class Employee {
// 抽象方法必须要放在抽象类中
abstract public void work();
}

// 定义一个子类继承抽象类
class Manager extends Employee {
// 2.重写父类的抽象方法,如果不重写会无法编译
@Override
public void work() {
System.out.println("管理其他人");
}
}
Lobster AIjava运行1234567891011121314

16.2 抽象类的细节

  1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
  2. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
  3. 抽象类中,可以有构造方法、实例方法、静态方法和成员变量。
  4. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
  5. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类,编译无法通过而报错。
  6. 抽象类存在的意义是为了被子类继承。

八、字符串String

1.String类

String类是字符串类, Java中的所有字符串(双引号包裹的内容)都被实例化为此类的对象。

String 类在 java.lang 包下,所以使用时不需要导包。

特点

  • 字符串不可变,它们的值在创建后不能被更改(“a” + “b”产生三个字符串:“a”、“b”、“ab”)
  • 虽然 String 的值是不可变的,但是它们可以被共享
  • 字符串效果上相当于字符数组( char[] ),但是底层原理是字节数组( byte[] )

1.1 构造方法

方法名 说明
public String() 创建一个空白字符串对象,不含有任何内容
public String(char[] chs) 根据字符数组的内容,来创建字符串对象
public String(byte[] bytes) 根据字节数组的内容,来创建字符串对象
public String(byte[] bytes, int offset, int length) 根据字节数组的部分内容创建字符串对象,从offset索引开始,长度为length
String s = “abc”; 直接赋值的方式创建字符串对象,内容就是abc

创建字符串对象两种方式的区别

  • 通过构造方法创建

    通过 new 创建的字符串对象,每一次 new 都会在堆里申请一个内存空间,虽然内容相同,但是地址值不同

  • 直接赋值方式创建

    “”方式给出的字符串,只要字符序列相同(顺序和大小写),无论在程序代码中出现几次,JVM 都只会建立一个 String 对象,并在字符串池中维护。

1.2 字符串的比较

  1. ==号比较

    • 比较基本 数据类型:比较的是具体的值
    • 比较引用数据类型:比较的是对象地址值
  2. equals方法比较

    1
    2
    public boolean equals(String s)     比较两个字符串内容是否相同、区分大小写
    Lobster AIjava运行1
  3. equalslgnoreCase方法比较

    1
    2
    public boolean equalslgnoreCase(String s)     比较两个字符串内容是否相同、不区分大小写
    Lobster AIjava运行1

1.3 常用方法

方法 功能
int length() 会返回字符串的长度
char charAt(int index) 返回指定索引处的字符
static String valueOf(Object obj) 将对象转换为字符串
String substring(int beginIndex) 返回从指定索引开始的子字符串
String substring(int beginIndex, int endIndex) 返回从 beginIndex到endIndex-1 的子字符串
String replace(char oldChar, char newChar) 替换字符串中的所有指定字符
String replace(CharSequence target, CharSequence replacement) 替换字符串中的所有指定子字符串
boolean startsWith(String prefix) 判断字符串是否以指定前缀开头
boolean endsWith(String suffix) 判断此字符串是否以指定的后缀结束
byte[] getBytes() 把字符串转换成字节数组
String toLowerCase() 将字符串转换为小写
String toUpperCase() 将字符串转换为大写

1.4 案例

金额转换

例如:把2135变成:零佰零拾零万贰仟壹佰叁拾伍元

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.itheima.stringdemo;

import java.util.Scanner;

public class StringDemo9 {
public static void main(String[] args) {
//1.键盘录入一个金额
Scanner sc = new Scanner(System.in);
int money;
while (true) {
System.out.println("请录入一个金额");
money = sc.nextInt();
if (money >= 0 && money <= 9999999) {
break;
} else {
System.out.println("金额无效");
}
}

//定义一个变量用来表示钱的大写
String moneyStr = "";

//2.得到money里面的每一位数字,再转成中文
while (true) {//2135
//从右往左获取数据,因为右侧是数据的个位
int ge = money % 10;
String capitalNumber = getCapitalNumber(ge);
//把转换之后的大写拼接到moneyStr当中
moneyStr = capitalNumber + moneyStr;
//第一次循环 : "伍" + "" = "伍"
//第二次循环 : "叁" + "伍" = "叁伍"
//去掉刚刚获取的数据
money = money / 10;

//如果数字上的每一位全部获取到了,那么money记录的就是0,此时循环结束
if (money == 0) {
break;
}
}

//3.在前面补0,补齐7位
int count = 7 - moneyStr.length();
for (int i = 0; i < count; i++) {
moneyStr = "零" + moneyStr;
}
System.out.println(moneyStr);//零零零贰壹叁伍

//4.插入单位
//定义一个数组表示单位
String[] arr = {"佰","拾","万","仟","佰","拾","元"};
// 零 零 零 贰 壹 叁 伍

//遍历moneyStr,依次得到 零 零 零 贰 壹 叁 伍
//然后把arr的单位插入进去

String result = "";
for (int i = 0; i < moneyStr.length(); i++) {
char c = moneyStr.charAt(i);
//把大写数字和单位拼接到result当中
result = result + c + arr[i];
}

//5.打印最终结果
System.out.println(result);

}


//定义一个方法把数字变成大写的中文
//1 -- 壹
public static String getCapitalNumber(int number) {
//定义数组,让数字跟大写的中文产生一个对应关系
String[] arr = {"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"};
//返回结果
return arr[number];
}

}
Lobster AIjava运行123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778

2.StringBuilder

StringBuilder 可以看成是一个容器,创建之后里面的内容是可变的,例如“a” + “b”会直接把“b”拼接到到“a”,不会重新为“ab”开创空间。

应用场景:当我们在拼接字符串和反转字符串的时候会使用到。

2.1 构造方法

参考帮助文档,我们常采用以下方式创建对象:

1
2
StringBuilder sb = new StringBuilder("abc");
Lobster AIjava运行1

2.2 常用方法

方法名 说明
public StringBuilder append(任意类型) 添加 数据,并返回对象本身
public StringBuilder reverse() 反转容器中的内容
public int capacity() 返回容量(最多存多少)
public int length() 返回长度(已经存多少)
public String toString() 通过toString()就可以实现把StringBuilder转换成String
1
2
3
4
sb.append(1);
sb.append("def");
System.out.println(sb); //abc1def
Lobster AIjava运行123

打印StringBuilder对象实际打印是对象的内容,而不是对象的地址值。

2.3 链式编程

如果前一个方法的结果又是下一个方法的调用者,而且这个结果只用一次,就可以考虑链式编程,例如上述程序可以简写为:

1
2
System.out.println(sb.append(1).append("def").toString()); //abc1def
Lobster AIjava运行1

2.4 案例

反转字符串:例如“abdrf”反转后是“frdba”。

1
2
3
4
    public static String reverse(String str){
return new StringBuilder(str).reverse().toString();
}
Lobster AIjava运行123

3.StringJoiner

  • StringJoiner跟StringBuilder一样,也可以看成是一个容器,创建之后里面的内容是可变的。
  • 作用:提高字符串的操作效率,可以方便、高效地拼接字符串,而且代码编写特别简洁,但是目前市场上很少有人用。
  • JDK8出现的

3.1 构造方法

方法名 说明
public StringJoiner(间隔符号) 创建一个StringJoiner对象,指定拼接时的间隔符号
public StringJoiner(间隔符号, 开始符号, 结束符号) 创建一个StringJoiner对象,指定拼接时的间隔符号、开始符号、结束符号

如果想要[元素1,元素2,元素3,...]格式的字符串,手动添加间隔符,,开始符和结束符,就需要手动添加,而我们希望只关注元素i的添加,这是就可以使用如下方式:

1
2
StringJoiner sj = new StringJoiner(",", "[", "]"); //如果不指定开始结束符号,默认为空
Lobster AIjava运行1

3.2 成员方法

方法名 说明
public StringJoiner add(添加的内容) 添加 数据,返回对象本身
public int length() 返回长度(字符出现的个数)
public String toString() 返回一个字符串,该字符串就是拼接之后的结果
1
2
3
4
 sj.add("a");
sj.add("b");
System.out.println(sj); //[a,b]
Lobster AIjava运行123

4.字符串底层原理

字符串存储的底层原理

  • 直接赋值会复用字符串常量池中的字符串
  • new出来的不会复用,而是开辟一个新的空间

==号比较的到底是什么?

  • 基本数据类型比较数据值
  • 引用数据类型比较地址值

字符串拼接的底层原理

  • 如果没有变量的参与,都是字符串直接相加,编译之后就是拼接之后的结果,会复用串池中的字符串。
  • 如果有变量参与,每一行拼接的代码,都会在内存中创建新的字符串,浪费内存。
    • JDK8之前:系统底层会自动创建一个StringBuilder对象,用append方法拼接,再用toString方法转换成String类型,而toString底层是new了一个字符串对象。
    • JDK8版本:系统会字符串拼接之后的总大小,把要拼接的内容放到数组中,此时也是产生了一个新的字符串。

StringBuilder提高效率的原理

  • 所有要拼接的内容都会往StringBuilder中放,不会创建很多无用的空间,节约内存。

StringBuilder源码分析

  • 默认创建一个长度是16的字节数组
  • 添加的内容长度小于16,直接存
  • 添加的内容大于16会扩容(原来的容量*2+2,内容超过扩容后的容量会按照实际扩容)

九、接口和内部类

1.接口

接口是更加彻底的抽象,JDK7之前,包括JDK7,接口中全部是抽象方法。接口同样是不能创建对象的。

1.1 定义格式

1
2
3
4
5
//接口的定义格式:
interface 接口名称{
// 抽象方法
}
Lobster AIjava运行1234

接口的声明:interface
接口名称:首字母大写,满足“驼峰模式”。

JDK7之前,接口中的只有包含:抽象方法和常量

接口中的抽象方法默认会自动加上public abstract修饰,程序员无需自己手写!!

在接口中定义的成员变量默认会加上: public static final修饰。也就是说在接口中定义的成员变量实际上是一个常量,并且可以直接用接口名访问。

1.2 基本的实现

类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类,实现使用 implements关键字。

  • 类与接口之间的关系是多实现的,一个类可以同时实现多个接口。
  • 接口与接口之间是可以多继承的:也就是一个接口可以同时继承多个接口。
  • 类与接口是实现关系
  • 接口与接口是继承关系

实现接口的格式

1
2
3
4
class 类名 implements 接口1,接口2,接口3...{

}
Lobster AIjava运行123

类实现接口的要求

  1. 必须重写实现的全部接口中所有抽象方法。
  2. 如果一个类实现了接口,但是没有重写完全部接口的全部抽象方法,这个类也必须定义成抽象类。

类实现接口案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/** 法律规范:接口*/
public interface Law {
void rule();
}

/** 这一个运动员的规范:接口*/
public interface SportMan {
void run();
}
/** 实现类*/
public class JumpMan implements Law ,SportMan {
@Override //重写Law接口中的所有抽象方法
public void rule() {
System.out.println("尊长守法");
}

@Override //重写SportMan接口中的所有抽象方法
public void run() {
System.out.println("训练跑步!");
}
}
Lobster AIjava运行123456789101112131415161718192021

接口继承接口案例

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Abc {
void go();
void test();
}
public interface Law {
void rule();
void test();
}
//如果一个类实现了SportMan接口,需要重写4个抽象方法(test重复)
public interface SportMan extends Law , Abc {
void run();
}
Lobster AIjava运行123456789101112

1.3 接口的细节

  1. 当两个接口中存在相同抽象方法的时候,该怎么办?

只要重写一次即可。此时重写的方法,既表示重写1接口的,也表示重写2接口的。

  1. 实现类能不能继承A类的时候,同时实现其他接口呢?

继承的父类,就好比是亲爸爸一样
实现的接口,就好比是干爹一样
可以继承一个类的同时,再实现多个接口,只不过,要把接口里面所有的抽象方法,全部实现。

  1. 实现类能不能继承一个抽象类的时候,同时实现其他接口呢?

实现类可以继承一个抽象类的同时,再实现其他多个接口,只不过要把里面所有的抽象方法全部重写。

  1. 实现类Zi,实现了一个接口,还继承了一个Fu类。假设在接口中有一个方法,父类中也有一个相同的方法。子类如何操作呢?

处理办法一:如果父类中的方法体,能满足当前业务的需求,在子类中可以不用重写。
处理办法二:如果父类中的方法体,不能满足当前业务的需求,需要在子类中重写。

1.4 接口的应用

接口多态

当一个方法的参数是接口是,可以传递接口所有实现类的对象,这种方式称之为接口多态

适配器设计模式

如果一个接口中,有多个抽象方法,但是在实现类中,只需要用其中一个,就可以用适配器设计模式:

在接口跟实现类中间新建一个中间类(XxxAdapter适配器类),让这个适配器类去实现接口,对接口里面的所有的方法做空重写,让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。

注意:因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象

1.5 接口中的新增方法

1.5.1 JDK8以后接口中新增的方法

JDK8以后接口中可以定义有方法体的方法。

允许在接口中定义默认方法,需要用default修饰

1
2
3
4
5
public default 返回值类型 方法名(参数列表){ }

范例:
public default void show(){ }
Lobster AIjava运行1234
  • 默认方法不是抽象方法,不强制被重写,但如果重写,需要去掉default关键字。
  • public可以省略,但是default不能省略。
  • 如果实现了多个接口,且多个接口中存在名字相同的默认方法,子类就必须对该方法进行重写。

允许在接口中定义静态方法,需要用static修饰

1
2
3
4
5
public static 返回值类型 方法名(参数列表){ }

范例:
public static void show(){ }
Lobster AIjava运行1234
  • 静态方法只能通过接口名调用,不能通过类名或对象名调用。
  • public可以省略,static不能省略。
1.5.2 JDK9以后接口中新增的方法

JDK9以前,接口中只能定义public方法,JDK9以后,还可以在接口中定义private方法。

普通私有方法

1
2
3
4
5
private 返回值类型 方法名(参数列表){ }

范例:
private void show(){ }
Lobster AIjava运行1234
  • 普通私有方法给默认方法服务。

静态私有方法

1
2
3
4
private static 返回值类型 方法名(参数列表){ }
范例:
private static void method(){ }
Lobster AIjava运行123
  • 静态私有方法给静态方法服务。

2.内部类

将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类

适用于:内部一个事物内部还有一个独立的事物,内部的事物脱离外部的事物无法独立使用。如汽车和发动机,发动机离开汽车就无意义。

2.1 内部类的分类

  1. 成员内部类,类定义在了成员位置 (类中方法外称为成员位置,无static修饰的内部类)
  2. 静态内部类,类定义在了成员位置 (类中方法外称为成员位置,有static修饰的内部类)
  3. 局部内部类,类定义在方法内
  4. 匿名内部类,没有名字的内部类,可以在方法中,也可以在类中方法外。

2.2 成员内部类

内部类的使用格式

1
2
 外部类.内部类。 // 访问内部类的类型都是用 外部类.内部类
Lobster AIjava运行1

获取成员内部类对象的两种方式

方式一:外部直接创建成员内部类的对象

1
2
外部类.内部类 变量 = new 外部类().new 内部类();
Lobster AIjava运行1

方式二:在外部类中定义一个方法提供内部类的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
方式一:
class Outer {
// 成员内部类
public class Inner{
public void method(){
System.out.println("内部类中的方法被调用了");
}
}
}
public class Test {
public static void main(String[] args) {
Outer.Inner oi = new Outer().new Inner();
oi.method();
}
}

方式二:
public class Outer {
String name;
private class Inner{
static int a = 10;
}
public Inner getInstance(){
return new Inner();
}
}
public class Test {
public static void main(String[] args) {
Outer o = new Outer();
System.out.println(o.getInstance());
}
}
Lobster AIjava运行1234567891011121314151617181920212223242526272829303132

编写成员内部类的注意点

  1. 成员内部类可以被一些修饰符所修饰,比如: private,默认,protected,public,static等

  2. 在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量。

  3. 创建内部类对象时,对象中有一个隐含的外部类名.this记录外部类对象的地址值。

    image-20250530003019548

内部类被private修饰,外界无法直接获取内部类的对象

内部类被static修饰是成员内部类中的特殊情况,叫做静态内部类。

内部类如果想要访问外部类的成员变量,外部类的变量必须用final修饰,JDK8以前必须手动写final,JDK8之后不需要手动写,JDK默认加上。

2.3 静态内部类

静态内部类是一种特殊的成员内部类,有static修饰,属于外部类本身的。

静态内部类与其他类的用法完全一样。只是访问的时候需要加上外部类.内部类。

静态内部类特点

  • 静态内部类可以直接访问外部类的静态成员。
  • 静态内部类不可以直接访问外部类的非静态成员,如果要访问需要创建外部类的对象。
  • 静态内部类中没有this关键字

内部类的使用格式

1
2
外部类.内部类。
Lobster AI1

静态内部类对象的创建格式

1
2
外部类.内部类  变量 = new  外部类.内部类构造器;
Lobster AIjava运行1

调用方法的格式:

  • 调用非静态方法的格式:先创建对象,用对象调用
  • 调用静态方法的格式:外部类名.内部类名.方法名();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 外部类:Outer01
class Outer01{
private static String sc_name = "黑马程序";
// 内部类: Inner01
public static class Inner01{
// 这里面的东西与类是完全一样的。
private String name;
public Inner01(String name) {
this.name = name;
}
public void showName(){
System.out.println(this.name);
System.out.println(sc_name); //访问外部类静态成员
}
}
}

public class InnerClassDemo01 {
public static void main(String[] args) {
Outer01.Inner01 in = new Outer01.Inner01("张三");
in.showName();
}
}
Lobster AIjava运行1234567891011121314151617181920212223

2.4 局部内部类

局部内部类 :定义在方法中的类。

1
2
3
4
5
6
7
8
9
10
11
class 外部类名 {
数据类型 变量名;
修饰符 返回值类型 方法名(参数列表) {

class 内部类 {
// 成员变量
// 成员方法
}
}
}
Lobster AIjava运行12345678910

2.5 匿名内部类【重点】

内部类的简化写法。他是一个隐含了名字的内部类。

格式

匿名内部类必须继承一个父类或者实现一个父接口

1
2
3
4
5
new 父类名或者接口名(){
@Override
重写方法;
};
Lobster AIjava运行1234

包含了:继承或者实现关系、方法重写、创建对象。

本质上这个整体是父类的子类对象或接口的实现类对象。

什么时候用到匿名内部类

  1. 如果我们希望定义一个只要使用一次的类,就可考虑使用匿名内部类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
interface Swim {
public abstract void swimming();
}

public class Demo07 {
public static void main(String[] args) {
// 使用匿名内部类
new Swim() {
@Override
public void swimming() {
System.out.println("自由泳...");
}
}.swimming();

// 接口 变量 = new 实现类(); // 多态,走子类的重写方法
Swim s2 = new Swim() {
@Override
public void swimming() {
System.out.println("蛙泳...");
}
};
s2.swimming();
}
}
Lobster AIjava运行123456789101112131415161718192021222324
  1. 通常在方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Swim {
public abstract void swimming();
}

public class Demo07 {
public static void main(String[] args) {
// 匿名内部类使用场景:作为方法参数传递
goSwimming(new Swim() {
@Override
public void swimming() {
System.out.println("蝶泳...");
}
});
}

// 定义一个方法,模拟请一些人去游泳
public static void goSwimming(Swim s) {
s.swimming();
}
}
Lobster AIjava运行1234567891011121314151617181920

匿名内部类的特点

  1. 定义一个没有名字的内部类
  2. 这个类实现了父类,或者父类接口
  3. 匿名内部类会创建这个没有名字的类的对象

十、常用API

API (Application Programming Interface) :应用程序编程接口,指的就是 JDK 中提供的各种功能的 Java类,这些类将底层的实现封装了起来,我们不需要关心这些类是如何实现的,只需要学习这些类如何使用即可。

API帮助文档:内置许多Java的API使用说明,包括成员变量、构造方法、成员方法等。打开资料中的API帮助文档就可以使用了。

1.Math类

Math类是位于 java.lang包下的一个类,包含执行基本数字运算的方法,我们可以使用Math类完成基本的数学运算。

不能直接通过new关键字去创建Math类的对象。可以直接通过类名去调用它内部的方法。

由于Math类被final修饰,所以不能被继承。

常用方法

1
2
3
4
5
6
7
8
9
10
11
public static int abs(int a)					// 返回参数的绝对值
public static double ceil(double a) // 返回大于或等于参数的最小整数
public static double floor(double a) // 返回小于或等于参数的最大整数
public static int round(float a) // 按照四舍五入返回最接近参数的int类型的值
public static int max(int a,int b) // 获取两个int值中的较大值
public static int min(int a,int b) // 获取两个int值中的较小值
public static double pow (double a,double b) // 计算a的b次幂的值
public static double sqrt (double a) // 计算a的算术平方根,遇到负数返回NaN
public static double cbrt (double a) // 计算a的立方根
public static double random() // 返回一个[0.0,1.0)的随机值
Lobster AIjava运行12345678910

2.System类

System类位于java.lang包,可以获取系统当前时间、操作JVM虚拟机等。

不能直接通过new关键字去创建System类的对象。可以直接通过类名去调用它内部的方法。

由于System类被final修饰,所以不能被继承。

1
2
3
4
public static long currentTimeMillis()			// 获取当前时间所对应的毫秒值
public static void exit(int status) // 终止当前正在运行的Java虚拟机,0表示正常退出,非零表示异常退出
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length); // 进行数值元素拷贝
Lobster AIjava运行123

currentTimeMillis

当前时间为0时区所对应的时间即就是英国格林尼治天文台旧址所在位置的1970年1月1号 00:00:00,中国的时间原点是1970年1月1号 08:00:00。

可以在一段程序开始和结束都获取一个时间,相减得到这段程序的运行时间,用于测试或比较代码效率。

arraycopy

public static void arraycopy( 数据源数组,起始索引,目的地数组,起始索引,拷贝个数):

  1. 如果数据源数组和目的地数组都是基本数据类型,那么两者的类型必须保持一致,否则会报错
  2. 在拷贝的时候需要考虑数组的长度,如果超出范围也会报错
  3. 如果数据源数组和目的地数组都是引用数据类型,那么子类类型可以赋值给父类类型(浅拷贝)

3.Runtime

Runtime表示 Java中运行时对象,可以获取到程序运行时涉及到的一些信息。

1
2
3
4
5
6
7
8
public static Runtime getRuntime()		//当前系统的运行环境对象
public void exit(int status) //停止虚拟机
public int availableProcessors() //获得CPU的线程数
public long maxMemory() //JVM能从系统中获取总内存大小(单位byte)
public long totalMemory() //JVM已经从系统中获取总内存大小(单位byte)
public long freeMemory() //JVM剩余内存大小(单位byte)
public Process exec(String command) //运行cmd命令
Lobster AIjava运行1234567

exec好玩命令

1
2
3
4
5
6
7
8
9
//7.运行cmd命令
//shutdown :关机
//加上参数才能执行
//-s :默认在1分钟之后关机
//-s -t 指定时间 : 指定关机时间(单位s)
//-a :取消关机操作
//-r: 关机并重启
Runtime.getRuntime().exec("shutdown -s -t 3600");//一小时后电脑关机
Lobster AIjava运行12345678

4.Object类

Object类是所有类的顶级父类,所有类都直接或者间接的继承自该类;该类所具备的方法其他所有类都继承了。

一般很少去主动的创建Object类的对象,调用其对应的方法。更多的是创建Object类的某个子类对象,然后通过子类对象调用Object类中的方法。

1
2
3
4
public String toString()				//返回该对象的字符串表示形式(可以看做是对象的内存地址值)
public boolean equals(Object obj) //比较两个对象地址值是否相等;true表示相同,false表示不相同
protected Object clone() //对象克隆
Lobster AIjava运行123

toString

  1. 在通过输出语句输出一个对象时,默认调用的就是toString()方法
  2. toString获取的是对象的地址值,输出地址值一般没有意义,我们可以通过重写toString方法去输出对应的成员变量信息(快捷键:atl + insert , 空白处 右键 -> Generate -> 选择toString)
  3. toString方法的作用:以良好的格式,更方便的展示对象中的属性值
  4. 一般情况下Jdk所提供的类都会重写Object类中的toString方法

equals

  1. 默认情况下equals方法比较的是对象的地址值
  2. 比较对象的地址值是没有意义的,因此一般情况下我们都会重写Object类中的equals方法

clone

对象克隆是指把A对象的属性值完全拷贝给B对象,也叫对象拷贝,对象复制。分为浅克隆和深克隆。

如果一个类想要被克隆,必须实现Cloneable接口。

  • 浅克隆

不管对象内部的属性是基本 数据类型还是引用数据类型,都完全拷贝过来

基本数据类型拷贝过来的是具体的数据,引用数据类型拷贝过来的是地址值。

Object类默认的是浅克隆。

image-20250530110023878

  • 深克隆

基本数据类型拷贝过来,字符串复用,引用数据类型会重新创建新的。

image-20250530110153249

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class ObjectDemo4 {
public static void main(String[] args) throws CloneNotSupportedException {
// protected object clone(int a) 对象克隆

//1.先创建一个对象
int[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0};
User u1 = new User(1, "zhangsan", "1234qwer", "girl11", data);

//2.克隆对象
User u2 =(User)u1.clone();

//验证一件事情:Object中的克隆是浅克隆,如果两个地址一致,表示是浅克隆,如果不一致就是深克隆
System.out.println(u1.getData());
System.out.println(u2.getData());

//使用第三方类进行深克隆
//1.拷贝资料中的克隆工具包到模块的lib包(手动创建)中
//2.右键工具包选择最后一个:Add as Library...
//3.编写代码
Gson gson =new Gson();
String s=gson.toJson(u1); //把对象变成一个字符串
User user =gson.fromJson(s, User.class); //再把字符串变回对象就可以了
}
}

//实现Cloneable接口
public class User implements Cloneable {
private int id;
private String username;
private String password;
private String path;
private int[] data;

//构造方法、get和set方法省略

public String toString() {
return "角色编号为:" + id + ",用户名为:" + username + "密码为:" + password + ", 游戏图片为:" + path + ", 进度:" + arrToString();
}

public String arrToString() {
StringJoiner sj = new StringJoiner(", ", "[", "]");

for (int i = 0; i < data.length; i++) {
sj.add(data[i] + "");
}
return sj.toString();
}

//调用父类中的clone方法,并把克隆之后的对象返回出去
@Override
protected Object clone() throws CloneNotSupportedException {
//先把被克隆对象中的数组获取出来
int[] data = this.data;
//创建新的数组
int[] newData =new int[data.length];
//拷贝数组中的数据
for (int i = 0; i < data.length; i++) {
newData[i] = data[i];
}
//调用父类中的方法克隆对象
User u = (User)super.clone();
u.data = newData;
return u;
}
}
Lobster AIjava运行1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  • Cloneable

如果一个接口里面没有抽象方法,表示当前的接口是一个标记性接口。

一旦实现Cloneable接口,那么当前类的对象就可以被克隆,如果没有实现,当前类的对象就不能克隆。

使用clone()方法时,必须实现Cloneable接口,否则会抛出CloneNotSupportedException异常。

需要重写clone()方法并将其访问修饰符改为public

默认的clone()方法是浅拷贝,如果需要深拷贝,需要手动实现。

5.Objects类

Objects类位于 java.util包下,因此需要导包使用。

不能直接通过new关键字去创建Objects类的对象。可以直接通过类名去调用它内部的方法。

由于Objects类被final修饰,所以不能被继承。

1
2
3
4
5
6
7
8
9
public static String toString(Object o) 					// 获取对象的字符串表现形式
public static boolean equals(Object a, Object b) // 比较两个对象是否相等
public static boolean isNull(Object obj) // 判断对象是否为null
public static boolean nonNull(Object obj) // 判断对象是否不为null

public static <T> T requireNonNull(T obj) // 检查对象是否不为null,如果为null直接抛出异常;如果不是null返回该对象;
public static <T> T requireNonNullElse(T obj, T defaultObj) // 检查对象是否不为null,如果不为null,返回该对象;如果为null返回defaultObj值
public static <T> T requireNonNullElseGet(T obj, Supplier<? extends T> supplier) // 检查对象是否不为null,如果不为null,返回该对象;如果为null,返回由Supplier所提供的值
Lobster AIjava运行12345678

toString

检查对象 o 是否为 null:如果 o 为 null,返回字符串 “null”;如果 o 不为 null,调用 o.toString() 并返回结果。

equals

1.方法的底层先判断s1是否为空,如果是就返回false

2.如果s1不是null,那么会再次调用s1中的equals方法

3.如果没有重写,就比较地址值,如果重写了,就比较属性值

6.BigInteger类

BigInteger类位于java.math包下,需要导包使用。可以用来表示很大的整数并进行数学运算。

6.1 构造方法

1
2
3
4
5
6
7
public BigInteger(int num, Random rnd) 		//获取随机大整数,范围:[0 ~ 2的num次方-1]
public BigInteger(String val) //获取指定的大整数,字符串中必须是整数,否则会报错
public BigInteger(String val, int radix) //获取指定进制的大整数

下面这个不是构造,而是一个静态方法获取BigInteger对象
public static BigInteger valueOf(long val) //静态方法获取BigInteger的对象,内部有优化
Lobster AIjava运行123456
  • 如果BigInteger表示的数字没有超出long的范围,可以用静态方法获取。
  • 如果BigInteger表示的超出long的范围,可以用构造方法获取。
  • 对象一旦创建,BigInteger内部记录的值不能发生改变。
  • 只要进行计算都会产生一个新的BigInteger对象

public BigInteger(String val, int radix)

  1. 字符串中的数字必须是整数
  2. 字符串中的数字必须要跟进制吻合。

public static BigInteger valueOf(long val)

  1. 能表示范围比较小,只能在long的取值范围之内,如果超出long的范围就不行了。
  2. 在内部对常用的数字: -16 ~ 16 进行了优化:提前把-16~16 先创建好BigInteger的对象,如果多次获取不会重新创建新的。

6.2 成员方法

1
2
3
4
5
6
7
8
9
10
public BigInteger add(BigInteger val)					//加法
public BigInteger subtract(BigInteger val) //减法
public BigInteger multiply(BigInteger val) //乘法
public BigInteger divide(BigInteger val) //除法,获取商
public BigInteger[] divideAndRemainder(BigInteger val) //除法,获取商和余数
public boolean equals(Object x) //比较是否相同
public BigInteger pow(int exponent) //次幂、次方
public BigInteger max/min(BigInteger val) //返回较大值/较小值
public int intValue() //转为int类型整数,超出范围数据有误
Lobster AIjava运行123456789

6.3 底层存储方式

对于计算机而言,其实是没有 数据类型的概念的,都是0101010101,数据类型是编程语言自己规定的,所以在实际存储的时候,先把具体的数字变成二进制,每32个bit为一组,存储在数组中。

image-20250530114846098

理论上,BigInteger能表示的最大数字为:42亿的21亿次方。但是这个数字太大了,很难达到,所以任何BigInteger没有上限。

7.BigDecimal类

BigDecimal类位于 java.math包下,需要导包使用。可以用来提高小数计算精度。

7.1 构造方法

image-20250530115204726

1
2
3
public static BigDecimal valueOf(long val)          //静态方法获取BigInteger的对象,内部有优化
public static BigDecimal valueOf(double val) //静态方法获取BigInteger的对象
Lobster AIjava运行12

public static BigDecimal valueOf(long val)

如果传递的是0~10之间的整数(包含0和10),那么方法会返回已创建好的对象,不会新创建对象。

7.2 成员方法

1
2
3
4
5
public BigDecimal add(BigDecimal value)				// 加法运算
public BigDecimal subtract(BigDecimal value) // 减法运算
public BigDecimal multiply(BigDecimal value) // 乘法运算
public BigDecimal divide(BigDecimal value) // 除法运算
Lobster AIjava运行1234

divide

如果使用BigDecimal类型的数据进行除法运算的时候,得到的结果是一个无限循环小数,那么就会报错:ArithmeticException

我们可以用另一种divide方法解决:

1
2
3
4
5
6
7
8
9
10
BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)

divisor: 除数对应的BigDecimal对象;
scale: 精确的位数;
roundingMode: 取舍模式;
取舍模式被封装到了RoundingMode这个枚举类中(关于枚举我们后期再做重点讲解),在这个枚举类中定义了很多种取舍方式。最常见的取舍方式有如下几个:
UP(向上取舍) , FLOOR(向下取舍) , HALF_UP(四舍五入),我们可以通过如下格式直接访问这些取舍模式:枚举类名.变量名

System.out.println(b1.divide(b2 , 2 , RoundingMode.HALF_UP));
Lobster AIjava运行123456789

小结:后期在进行两个数的除法运算的时候,我们常常使用的是可以设置取舍模式的divide方法。

7.3 底层存储方式

数据看成字符串,遍历得到里面的每一个字符,把这些字符在ASCII码表上的值,都存储到数组中。

image-20250530185657511

8.Date类

java.util.Date类 表示特定的瞬间,精确到毫秒。

8.1 构造方法

  • public Date():从运行程序的此时此刻到时间原点经历的毫秒值,转换成Date对象,分配Date对象并初始化此对象,以表示分配它的时间(精确到毫秒)。
  • public Date(long date):将指定参数的毫秒值date,转换成Date对象,分配Date对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即1970年1月1日00:00:00 GMT)以来的指定毫秒数。

tips: 由于中国处于东八区(GMT+08:00)是比世界协调时间/格林尼治时间(GMT)快8小时的时区,当格林尼治标准时间为0:00时,东八区的标准时间为08:00。

简单来说:使用无参构造,可以自动设置当前系统时间的毫秒时刻;指定long类型的构造参数,可以自定义毫秒时刻。例如:

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

public class Demo01Date {
public static void main(String[] args) {
// 创建日期对象,把当前的时间转换成日期对象
System.out.println(new Date()); // Sun Feb 09 18:00:40 CST 2025
// 创建日期对象,把当前的毫秒值转成日期对象
System.out.println(new Date(0L)); // Thu Jan 01 08:00:00 CST 1970
}
}
Lobster AIjava运行12345678910

tips:在使用println方法时,会自动调用Date类中的toString方法。Date类对Object类中的toString方法进行了覆盖重写,所以结果为指定格式的字符串。

8.2 常用方法

  • public long getTime() 把日期对象转换成对应的时间毫秒值。
  • public void setTime(long time) 把方法参数给定的毫秒值设置给日期对象

9.SimpleDateFormat类

java.text.SimpleDateFormat 类是日期/时间格式化类,我们可以使用它来格式化时间或解析某一个时间。

9.1 构造方法

  • public simpleDateFormat() :默认格式,,默认格式会随JVM语言环境发生变化,所以不推荐。
  • public SimpleDateFormat(String pattern):用给定的模式和默认语言环境的日期格式符号构造SimpleDateFormat。参数pattern是一个字符串,代表日期时间的自定义格式。

常用的格式规则

标识字母(区分大小写) 含义
y
M
d
H
m
s

yyyy-MM-dd HH:mm:ss,可以表示2025-05-30 23:02:33

9.2 常用方法

  • public String format(Date date):将Date对象格式化为字符串。
  • public Date parse(String source):将字符串解析为Date对象,创建对象的格式要跟字符串的格式完全一致。

10.Calendar类

java.util.Calendar类表示一个“日历类”,可以进行日期运算。它是一个抽象类,不能创建对象,我们可以使用它的子类:java.util.GregorianCalendar类。

常用方法

方法名 说明
public static Calendar getInstance() 获取一个它的子类GregorianCalendar对象。时间为系统当前时间
public final Date getTime() 获取日期对象
public final setTime(Date date) 给日历设置日期对象
public long getTimeInMillis() 拿到时间毫秒值
public void setTimeInMillis(long millis) 给日历设置时间毫秒值
public int get(int field) 获取某个字段的值。field参数表示获取哪个字段的值
public void set(int field,int value) 修改某个字段的值
public void add(int field,int amount) 为某个字段增加/减少指定的值

Calendar底层会根据不同时区来获取不同的日历对象,把时间中的纪元、年、月、日、时、分、秒、星期等都放到一个数组中:

image-20250530235530672

field常用取值

  • Calendar.YEAR:年
  • Calendar.MONTH:月,月份范围是0~11,0代表1月,以此类推。
  • Calendar.DAY_OF_MONTH:月中的日期
  • Calendar.HOUR:小时
  • Calendar.MINUTE:分钟
  • Calendar.SECOND:秒
  • Calendar.DAY_OF_WEEK:星期,星期会把星期日看做一周的第一天,星期一看做一周的第二天。

11.JDK8时间相关类

JDK8时间类类名 作用
ZoneId 时区
Instant 时间戳
ZoneDateTime 带时区的时间
DateTimeFormatter 用于时间的格式化和解析
LocalDate 年、月、日
LocalTime 时、分、秒
LocalDateTime 年、月、日、时、分、秒
Duration 时间间隔(秒,纳,秒)
Period 时间间隔(年,月,日)
ChronoUnit 时间间隔(所有单位)

11.1 ZoneId 时区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static Set<string> getAvailableZoneIds() 获取Java中支持的所有时区
static ZoneId systemDefault() 获取系统默认时区
static Zoneld of(string zoneld) 获取一个指定时区

//1.获取所有的时区名称
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
System.out.println(zoneIds.size());//600
System.out.println(zoneIds);// Asia/Shanghai

//2.获取当前系统的默认时区
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId);//Asia/Shanghai

//3.获取指定的时区
ZoneId zoneId1 = ZoneId.of("Asia/Pontianak");
System.out.println(zoneId1);//Asia/Pontianak
Lobster AIjava运行12345678910111213141516

11.2 Instant 时间戳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
static Instant now() 获取当前时间的Instant对象(标准时间,不带时区)
static Instant ofXxxx(long epochMilli) 根据(秒/毫秒/纳秒)获取Instant对象
ZonedDateTime atZone(ZoneIdzone) 指定时区
boolean isxxx(Instant otherInstant) 判断系列的方法
Instant minusXxx(long millisToSubtract) 减少时间系列的方法
Instant plusXxx(long millisToSubtract) 增加时间系列的方法

//1.获取当前时间的Instant对象(标准时间)
Instant now = Instant.now();
System.out.println(now);

//2.根据(秒/毫秒/纳秒)获取Instant对象
Instant instant1 = Instant.ofEpochMilli(0L);
System.out.println(instant1);//1970-01-01T00:00:00z

Instant instant2 = Instant.ofEpochSecond(1L);
System.out.println(instant2);//1970-01-01T00:00:01Z

Instant instant3 = Instant.ofEpochSecond(1L, 1000000000L);
System.out.println(instant3);//1970-01-01T00:00:027

//3. 指定时区
ZonedDateTime time = Instant.now().atZone(ZoneId.of("Asia/Shanghai"));
System.out.println(time);


//4.isXxx 判断
Instant instant4=Instant.ofEpochMilli(0L);
Instant instant5 =Instant.ofEpochMilli(1000L);

//5.用于时间的判断
//isBefore:判断调用者代表的时间是否在参数表示时间的前面
boolean result1=instant4.isBefore(instant5);
System.out.println(result1);//true

//isAfter:判断调用者代表的时间是否在参数表示时间的后面
boolean result2 = instant4.isAfter(instant5);
System.out.println(result2);//false

//6.Instant minusXxx(long millisToSubtract) 减少时间系列的方法
Instant instant6 =Instant.ofEpochMilli(3000L);
System.out.println(instant6);//1970-01-01T00:00:03Z

Instant instant7 =instant6.minusSeconds(1);
System.out.println(instant7);//1970-01-01T00:00:02Z
Lobster AIjava运行123456789101112131415161718192021222324252627282930313233343536373839404142434445

11.3 ZoneDateTime 带时区的时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static ZonedDateTime now() 获取当前时间的ZonedDateTime对象
static ZonedDateTime ofXxxx(...) 获取指定时间的ZonedDateTime对象
ZonedDateTime withXxx(时间) 修改时间系列的方法
ZonedDateTime minusXxx(时间) 减少时间系列的方法
ZonedDateTime plusXxx(时间) 增加时间系列的方法

//1.获取当前时间对象(带时区)
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);

//2.获取指定的时间对象(带时区)1/年月日时分秒纳秒方式指定
ZonedDateTime time1 = ZonedDateTime.of(2023, 10, 1,
11, 12, 12, 0, ZoneId.of("Asia/Shanghai"));
System.out.println(time1);

//通过Instant + 时区的方式指定获取时间对象
Instant instant = Instant.ofEpochMilli(0L);
ZoneId zoneId = ZoneId.of("Asia/Shanghai");
ZonedDateTime time2 = ZonedDateTime.ofInstant(instant, zoneId);
System.out.println(time2);


//3.withXxx 修改时间系列的方法
ZonedDateTime time3 = time2.withYear(2000);
System.out.println(time3);

//4. 减少时间
ZonedDateTime time4 = time3.minusYears(1);
System.out.println(time4);

//5.增加时间
ZonedDateTime time5 = time4.plusYears(1);
System.out.println(time5);
Lobster AIjava运行123456789101112131415161718192021222324252627282930313233

11.4 DateTimeFormatter 用于时间的格式化和解析

1
2
3
4
5
6
7
8
9
10
11
static DateTimeFormatter ofPattern(格式) 获取格式对象
String format(时间对象) 按照指定方式格式化

//获取时间对象
ZonedDateTime time = Instant.now().atZone(ZoneId.of("Asia/Shanghai"));

// 解析/格式化器
DateTimeFormatter dtf1=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm;ss EE a");
// 格式化
System.out.println(dtf1.format(time));
Lobster AIjava运行12345678910

11.5 LocalDate 年、月、日

11.6 LocalTime 时、分、秒

11.7 LocalDateTime 年、月、日、时、分、秒

LocalDate、LocalTime、LocalDateTime通用:

方法名 说明
static Xxx now() 获取当前时间的对象
static Xxx of(…) 获取指定时间的对象
get开头的 获取日历中的年月日时分秒等信息
isBefore,isAfter 比较两个对象谁在前,谁在后
with开头的 修改时间系列的方法
minus开头的 减少时间系列的方法
plus开头的 增加时间系列的方法

以LocalDate为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//1.获取当前时间的日历对象(包含 年月日)
LocalDate nowDate = LocalDate.now();
System.out.println("今天的日期:" + nowDate);
//2.获取指定的时间的日历对象
LocalDate ldDate = LocalDate.of(2023, 1, 1);
System.out.println("指定日期:" + ldDate);

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

//3.get系列方法获取日历中的每一个属性值//获取年
int year = ldDate.getYear();
System.out.println("year: " + year);
//获取月
//方式一:
Month m = ldDate.getMonth();
System.out.println(m);
System.out.println(m.getValue());

//方式二:
int month = ldDate.getMonthValue();
System.out.println("month: " + month);


//获取日
int day = ldDate.getDayOfMonth();
System.out.println("day:" + day);

//获取一年的第几天
int dayofYear = ldDate.getDayOfYear();
System.out.println("dayOfYear:" + dayofYear);

//获取星期
DayOfWeek dayOfWeek = ldDate.getDayOfWeek();
System.out.println(dayOfWeek);
System.out.println(dayOfWeek.getValue());

//is开头的方法表示判断
System.out.println(ldDate.isBefore(ldDate));
System.out.println(ldDate.isAfter(ldDate));

//with开头的方法表示修改,只能修改年月日
LocalDate withLocalDate = ldDate.withYear(2000);
System.out.println(withLocalDate);

//minus开头的方法表示减少,只能减少年月日
LocalDate minusLocalDate = ldDate.minusYears(1);
System.out.println(minusLocalDate);


//plus开头的方法表示增加,只能增加年月日
LocalDate plusLocalDate = ldDate.plusDays(1);
System.out.println(plusLocalDate);

//-------------
// 判断今天是否是你的生日
LocalDate birDate = LocalDate.of(2000, 1, 1);
LocalDate nowDate1 = LocalDate.now();

MonthDay birMd = MonthDay.of(birDate.getMonthValue(), birDate.getDayOfMonth());
MonthDay nowMd = MonthDay.from(nowDate1);

System.out.println("今天是你的生日吗? " + birMd.equals(nowMd));//今天是你的生日吗?
Lobster AIjava运行1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162

11.8 Duration 时间间隔(秒,纳,秒)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 本地日期时间对象。
LocalDateTime today = LocalDateTime.now();
System.out.println(today);

// 出生的日期时间对象
LocalDateTime birthDate = LocalDateTime.of(2000, 1, 1, 0, 0, 0);
System.out.println(birthDate);

Duration duration = Duration.between(birthDate, today);//第二个参数减第一个参数
System.out.println("相差的时间间隔对象:" + duration);

System.out.println("============================================");
System.out.println(duration.toDays());//两个时间差的天数
System.out.println(duration.toHours());//两个时间差的小时数
System.out.println(duration.toMinutes());//两个时间差的分钟数
System.out.println(duration.toMillis());//两个时间差的毫秒数
System.out.println(duration.toNanos());//两个时间差的纳秒数
Lobster AIjava运行1234567891011121314151617

11.9 Period 时间间隔(年,月,日)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 当前本地 年月日
LocalDate today = LocalDate.now();
System.out.println(today);

// 生日的 年月日
LocalDate birthDate = LocalDate.of(2000, 1, 1);
System.out.println(birthDate);

Period period = Period.between(birthDate, today);//第二个参数减第一个参数

System.out.println("相差的时间间隔对象:" + period);
System.out.println(period.getYears());
System.out.println(period.getMonths());
System.out.println(period.getDays());

System.out.println(period.toTotalMonths());
Lobster AIjava运行12345678910111213141516

11.10 ChronoUnit 时间间隔(所有单位)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 当前时间
LocalDateTime today = LocalDateTime.now();
System.out.println(today);
// 生日时间
LocalDateTime birthDate = LocalDateTime.of(2000, 1, 1,0, 0, 0);
System.out.println(birthDate);

System.out.println("相差的年数:" + ChronoUnit.YEARS.between(birthDate, today));
System.out.println("相差的月数:" + ChronoUnit.MONTHS.between(birthDate, today));
System.out.println("相差的周数:" + ChronoUnit.WEEKS.between(birthDate, today));
System.out.println("相差的天数:" + ChronoUnit.DAYS.between(birthDate, today));
System.out.println("相差的时数:" + ChronoUnit.HOURS.between(birthDate, today));
System.out.println("相差的分数:" + ChronoUnit.MINUTES.between(birthDate, today));
System.out.println("相差的秒数:" + ChronoUnit.SECONDS.between(birthDate, today));
System.out.println("相差的毫秒数:" + ChronoUnit.MILLIS.between(birthDate, today));
System.out.println("相差的微秒数:" + ChronoUnit.MICROS.between(birthDate, today));
System.out.println("相差的纳秒数:" + ChronoUnit.NANOS.between(birthDate, today));
System.out.println("相差的半天数:" + ChronoUnit.HALF_DAYS.between(birthDate, today));
System.out.println("相差的十年数:" + ChronoUnit.DECADES.between(birthDate, today));
System.out.println("相差的世纪(百年)数:" + ChronoUnit.CENTURIES.between(birthDate, today));
System.out.println("相差的千年数:" + ChronoUnit.MILLENNIA.between(birthDate, today));
System.out.println("相差的纪元数:" + ChronoUnit.ERAS.between(birthDate, today));
Lobster AIjava运行12345678910111213141516171819202122

时间类案例:计算活了多少天?用JDK7和JDK8两种方式实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test4 {
public static void main(String[] args) throws ParseException {
//JDK7
//1.计算出生年月日的毫秒值
String birthday = "2000年1月1日";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
Date date = sdf.parse(birthday);
long birthdayTime = date.getTime();
//2.获取当前时间的毫秒值
long todayTime = System.currentTimeMillis();
//3.计算间隔多少天
long time = todayTime - birthdayTime;
System.out.println(time / 1000 / 60 / 60 / 24);


//JDK8
LocalDate ld1 = LocalDate.of(2000, 1, 1);
LocalDate ld2 = LocalDate.now();
long days = ChronoUnit.DAYS.between(ld1, ld2);
System.out.println(days);
}
}
Lobster AIjava运行12345678910111213141516171819202122

12.包装类

Java中有两种 数据类型,分别是基本数据类型和引用数据类型,每一个基本数据类型都有对应的包装类(位于 java.lang包下),实现基本数据类型向引用数据类型的转换。

基本类型 对应的包装类(位于java.lang包中)
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

由于基本数据类型的操作都一样,所以下面以最常用的Integer进行讲解。

12.1 Integer类

Integer类构造方法及静态方法

方法名 说明
public Integer(int value) 根据 int 值创建 Integer 对象(过时)
public Integer(String s) 根据 String 值创建 Integer 对象(过时)
public static Integer valueOf(int i) 返回表示指定的 int 值的 Integer 实例
public static Integer valueOf(String s) 返回保存指定String值的 Integer 对象
static string tobinarystring(int i) 得到二进制
static string tooctalstring(int i) 得到八进制
static string toHexstring(int i) 得到十六进制
static int parseInt(string s) 将字符串类型的整数转成int类型的整数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//1.把整数转成二进制,十六进制
String str1 = Integer.toBinaryString(100);
System.out.println(str1);//1100100

//2.把整数转成八进制
String str2 = Integer.toOctalString(100);
System.out.println(str2);//144

//3.把整数转成十六进制
String str3 = Integer.toHexString(100);
System.out.println(str3);//64

//4.将字符串类型的整数转成int类型的整数
//强类型语言:每种数据在java中都有各自的数据类型
//在计算的时候,如果不是同一种数据类型,是无法直接计算的。
int i = Integer.parseInt("123");
System.out.println(i);
System.out.println(i + 1);//124
//细节1:
//在类型转换的时候,括号中的参数只能是数字不能是其他,否则代码会报错
//细节2:
//8种包装类当中,除了Character都有对应的parseXxx的方法,进行类型转换
String str = "true";
boolean b = Boolean.parseBoolean(str);
System.out.println(b);
Lobster AIjava运行12345678910111213141516171819202122232425
  • 在类型转换的时候,括号中的参数只能是数字不能是其他,否则代码会报错。
  • 8种包装类当中,除了Character都有对应的parseXxx的方法,进行类型转换。

12.2 自动装箱与自动拆箱

基本类型与对应的包装类对象之间,来回转换的过程称为”装箱“与”拆箱“:

  • 装箱:从基本类型转换为对应的包装类对象。
  • 拆箱:从包装类对象转换为对应的基本类型。

Java 5(JDK 1.5)开始,基本类型与包装类的装箱、拆箱动作可以自动完成。

1
2
3
4
Integer i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4);
i = i + 5;//等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 5;
//加法运算完成后,再次装箱,把基本数值转成对象。
Lobster AIjava运行123

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

12.3.1 基本类型转换为String
  • 方式一:直接在数字后加一个空字符串,如100 + ""
  • 方式二:通过String类静态方法valueOf(),如String.valueOf(number)
12.3.2 String转换成基本类型

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

  • public static byte parseByte(String s):将字符串参数转换为对应的byte基本类型。
  • public static short parseShort(String s):将字符串参数转换为对应的short基本类型。
  • public static int parseInt(String s):将字符串参数转换为对应的int基本类型。
  • public static long parseLong(String s):将字符串参数转换为对应的long基本类型。
  • public static float parseFloat(String s):将字符串参数转换为对应的float基本类型。
  • public static double parseDouble(String s):将字符串参数转换为对应的double基本类型。
  • public static boolean parseBoolean(String s):将字符串参数转换为对应的boolean基本类型。

代码使用(仅以Integer类的静态方法parseXxx为例)如:

  • 转换方式

    • 方式一:先将字符串数字转成Integer,再调用valueOf()方法
    • 方式二:通过Integer静态方法parseInt()进行转换
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class IntegerDemo {
    public static void main(String[] args) {
    //String --- int
    String s = "100";
    //方式1:String --- Integer --- int
    Integer i = Integer.valueOf(s);
    //public int intValue()
    int x = i.intValue();
    System.out.println(x);
    //方式2
    //public static int parseInt(String s)
    int y = Integer.parseInt(s);
    System.out.println(y);
    }
    }
    Lobster AIjava运行123456789101112131415

如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出java.lang.NumberFormatException异常。

12.3.3 底层原理

建议:获取Integer对象的时候不要自己new,而是采取直接赋值或者静态方法valueOf的方式

因为在实际开发中,-128~127之间的 数据,用的比较多。如果每次使用都是new对象,那么太浪费内存了。

所以,提前把这个范围之内的每一个数据都创建好对象,如果要用到了不会创建新的,而是返回已经创建好的对象。

1
2
3
4
5
6
7
8
9
10
11
12
Integer i6 = Integer.valueOf(127);
Integer i7 = Integer.valueOf(127);
System.out.println(i6 == i7);//true

Integer i8 = Integer.valueOf(128);
Integer i9 = Integer.valueOf(128);
System.out.println(i8 == i9);//false

Integer i10 = new Integer(127);
Integer i11 = new Integer(127);
System.out.println(i10 == i11);//false
Lobster AIjava运行1234567891011

十一、正则表达式

如果需要校验某个字符串是否符合某些规则,如 长度不得超过15且只能包含英文大小写、数字、下划线 等,就可以使用正则表达式来实现。

1.字符类

  1. [abc]:代表a或者b,或者c字符中的一个。
  2. [^abc]:代表除a,b,c以外的任何字符。
  3. [a-z]:代表a-z的所有小写字符中的一个。
  4. [A-Z]:代表A-Z的所有大写字符中的一个。
  5. [0-9]:代表0-9之间的某一个数字字符。
  6. [a-zA-Z0-9]:代表a-z或者A-Z或者0-9之间的任意一个字符。
  7. [a-dm-p]:a 到 d 或 m 到 p之间的任意一个字符。

2.逻辑运算符

  1. &&:并且
  2. | :或者
  3. \ :转义字符

注意

  1. 单个&仅仅代表‘&’这个字符,不是并且。
  2. \在字符串中是转义字符,要表示单纯的\需要用\\

3.预定义字符

  1. . : 匹配任何字符。
  2. \d:任何数字[0-9]的简写;
  3. \D:任何非数字[^0-9]的简写;
  4. \s: 空白字符:[ \t\n\x0B\f\r] 的简写
  5. \S: 非空白字符:[^\s] 的简写
  6. \w:单词字符:[a-zA-Z_0-9]的简写
  7. \W:非单词字符:[^\w]

4.数量词

  1. X? : 0次或1次
  2. X* : 0次到多次
  3. X+ : 1次或多次
  4. X{n} : 恰好n次
  5. X{n,} : 至少n次
  6. X{n,m}: n到m次(n和m都是包含的)

案例

身份证号校验:410801 1993 02 28 457x

  • 前面6位:省份,市区,派出所等信息,第一位不能是0,后面5位是任意数字 [1-9]\\d{5}
  • 年的前半段:18 19 20 (18|19|20)
  • 年的后半段:任意数字出现两次 \\d{2}
  • 月份:01~ 09 10 11 12 (0[1-9]|1[0-2])
  • 日期:0109 1019 20~29 30 31 (0[1-9]|[12]\\d|3[01])
  • 后面四位:任意数字出现3次 最后一位可以是数字也可以是大写x或者小写x \\d{3}[\\dXx]

结果:[1-9]\\d{5}(18|19|20)\\d{2}(@[1-9]|1[0-2])(@[1-9]|[12]\\d|3[01])\\d{3}[\\dxXx]

匹配字符串使用的方法:

方法 说明
public boolean matches(String regex) 拿着正则表达式regex匹配字符串,匹配成功返回true,否则返回false
1
2
3
4
5
6
7
8
9
10
		String regex6 = "[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dxXx]";        
System.out.println("025158196903253541".matches(regex6)); //false不符合前六位
System.out.println("31548717980205356x".matches(regex6)); //false不符合年的前半段
System.out.println("425633209x06162485".matches(regex6)); //false不符合年的后半段
System.out.println("911125200113062144".matches(regex6)); //false不符合月份
System.out.println("21165818650532211x".matches(regex6)); //false不符合日期
System.out.println("65844119980627231a".matches(regex6)); //false不符合后四位
System.out.println("411123200101092504".matches(regex6)); //true符合所有要求
System.out.println("23325420561231254X".matches(regex6)); //true符合所有要求
Lobster AIjava运行123456789

5.爬虫

5.1 本地 数据爬取

Pattern:表示正则表达式。

Matcher:文本匹配器,作用按照正则表达式的规则去读取字符串,从头开始读取。在大串中去找符合匹配规则的子串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    public static void main(String[] args) {
//找出str中所有的JavaXX
String str = "Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11," +
"因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";

//1.获取正则表达式的对象
Pattern p = Pattern.compile("Java\\d{0,2}");
//2.获取文本匹配器的对象
//m:文本匹配器的对象
//str:大串
//p:规则
//拿着m去读取str,找符合p规则的子串
Matcher m = p.matcher(str);

//3.利用循环获取
//find方法:
//拿着文本匹配器从头开始读取,寻找是否有满足规则的子串
//如果没有,方法返回false
//如果有,返回true。在底层记录子串的起始索引和结束索引+1,如初始记录0和4
//后面会依次往后读取,如第二次从4索引开始读取
//group方法:
//方法底层会根据find方法记录的索引进行字符串的截取(使用subString方法,初始截取subString(0,4)),并返回截取后的子串
while (m.find()) {
String s = m.group();
System.out.println(s);
}


}
Lobster AIjava运行1234567891011121314151617181920212223242526272829

5.2 网络 数据爬取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 爬取指定URL的网页HTML内容
*
* @param urlName 目标网页的URL地址
* @return 返回网页的HTML内容字符串
* @throws IOException 如果在打开连接、读取内容或关闭资源时发生I/O错误
*/
public static String webCrawler(String urlName) throws IOException {
// 存储读取到的HTML内容
StringBuilder result = new StringBuilder();

// 创建URL对象并打开连接,需要保证网络连接正常
URL url = new URL(urlName);
URLConnection urlConnection = url.openConnection();

// 创建输入流读取器,可以参考IO流章节
InputStreamReader inputStreamReader = new InputStreamReader(urlConnection.getInputStream());

// 字符缓冲区,每次读取1024个字符
char[] buffer = new char[1024];
int length;

// 循环读取网页内容,直到文件末尾(返回-1)
while ((length = inputStreamReader.read(buffer)) != -1) {
result.append(buffer, 0, length);
}

// 关闭输入流,释放资源
inputStreamReader.close();

return result.toString();
}
Lobster AIjava运行1234567891011121314151617181920212223242526272829303132

如果需要根据正则爬取,可以将得到的字符串使用文本匹配器过滤。

5.3 按要求爬取

1
2
3
4
5
6
7
8
9
10
11
12
13
	    // ?理解为前面的数据Java
//(?:正则) 获取所有
//(?=正则) 获取前面的部分
//(?!正则) 获取不是指定内容的前面部分

//需求1:爬取版本号为8,11.17的Java文本,但是只要Java,不显示版本号。
String regex1 = "(Java)(?=8|11|17)";
//需求2:爬取版本号为8,11,17的Java文本。正确爬取结果为:Java8 Java11 Java17 Java17
String regex2 = "(Java)(8|11|17)";
String regex3 = "(Java)(?:8|11|17)";
//需求3:爬取除了版本号为8,11,17的Java文本。
String regex4 = "(Java)(?!8|11|17)";
Lobster AIjava运行123456789101112

5.4 贪婪爬取和非贪婪爬取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
只写+和表示贪婪匹配,如果在+和后面加问号表示非贪婪爬取
+? 非贪婪匹配
*? 非贪婪匹配
贪婪爬取:在爬取数据的时候尽可能的多获取数据
非贪婪爬取:在爬取数据的时候尽可能的少获取数据

举例:abbbbbbbbbbbbaaaaaaaaaaaaaaaaaa
贪婪爬取获取结果:abbbbbbbbbbbb
非贪婪爬取获取结果:ab

String s = "abbbbbbbbbbbbaaaaaaaaaaaaaaaaaa";

String regex1 = "ab+"; //贪婪爬取
Pattern p1 = Pattern.compile(regex1);
Matcher m1 = p1.matcher(s);
while (m1.find()) {
System.out.println(m1.group()); //abbbbbbbbbbbb
}

String regex2 = "ab+?"; //非贪婪爬取
Pattern p2 = Pattern.compile(regex2);
Matcher m2 = p2.matcher(s);
while (m2.find()) {
System.out.println(m2.group()); //ab
}
Lobster AIjava运行12345678910111213141516171819202122232425

6.String类的方法中使用正则表达式

split方法中使用正则表达式

1
2
3
public String[] split(String regex)
//参数regex表示正则表达式。可以将当前字符串中匹配regex正则表达式的符号作为"分隔符"来切割字符串。
Lobster AIjava运行12

replaceAll方法中使用正则表达式

1
2
3
public String replaceAll(String regex,String newStr)
//参数regex表示一个正则表达式。可以将当前字符串中匹配regex正则表达式的字符串替换为newStr。
Lobster AIjava运行12

7.正则表达式分组括号()

细节:如何识别组号?

只看左括号,不看右括号,按照左括号的顺序,从左往右,依次为第一组,第二组,第三组等等。

捕获分组就是把这一组数组捕获出来,再用一次,

1
2
3
正则内部使用:\\组号
正则外部使用:$组号
Lobster AIjava运行12

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//判断一个字符串的开始字符和结束字符是否一致(只考虑一个字符)?
String regex1 = "(.).+\\1";

//判断一个字符串的开始部分和结束部分是否一致(可以有多个字符)?
String regex2 = "(.+).+\\1";

//判断一个字符串的开始部分和结束部分是否一致(开始部分内部每个字符也需要一致)?
String regex3 = "((.)\\2*).+\\1";

//需求:消除结巴,即 我要学学编编编编程程程程程程 变成 我要学编程
String str = "我要学学编编编编程程程程程程";
String result = str.replaceAll("(.)\\1+", "$1");
System.out.println(result);
Lobster AIjava运行12345678910111213

7.1 非捕获分组

非捕获分组:分组之后不需要再用本组 数据,仅仅是把数据括起来。

1
2
3
4
5
6
7
8
9
10
	//非捕获分组:仅仅是把数据括起来。但不占用组号
//这里\\1报错原因:(?:)就是非捕获分组,此时是不占用组号的。

//(?:) (?=) (?!)都是非捕获分组。使用最多的是第一个
String regex1 ="[1-9]\\d{16}(?:\\d|x|x)\\1"; //出错,原因是这里有非捕获分组,\\1不存在
String regex2 ="[1-9]\\d{16}(\\d|X|x)\\1";

System.out.println("41080119930228457xx".matches(regex2)); //true
System.out.println("41080119930228457xX".matches(regex2)); //false
Lobster AIjava运行123456789

8.忽略大小写的写法

1
2
3
4
5
6
7
8
//(?i) :表示忽略后面数据的大小写
//忽略abc的大小写
String regex = "(?i)abc";
//a需要一模一样,忽略bc的大小写
String regex = "a(?i)bc";
//ac需要一模一样,忽略b的大小写
String regex = "a((?i)b)c";
Lobster AIjava运行1234567

十二、数据结构和Lambda表达式

1.数据结构

1.1 查找算法

1.1.1 基本查找

基本思想:从数据结构线的一端开始,顺序扫描,依次将遍历到的结点与要查找的值相比较,若相等则表示查找成功;若遍历结束仍没有找到相同的,表示查找失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//参数:
//一:数组
//二:要查找的元素
//返回值:
//元素是否存在
public static boolean basicSearch(int[] arr, int number){
//利用基本查找来查找number在数组中是否存在
for (int i = 0; i < arr.length; i++) {
if(arr[i] == number){
return true;
}
}
return false;
}
Lobster AIjava运行1234567891011121314
1.1.2 二分查找

基本思想:也称为是折半查找,属于有序查找算法。用给定值先与中间结点比较。比较完之后有三种情况:

  • 相等:说明找到了。
  • 要查找的数据比中间节点小:说明要查找的数字在中间节点左边。
  • 要查找的数据比中间节点大:说明要查找的数字在中间节点右边。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static int binarySearch(int[] arr, int number) {
//1.定义两个变量记录要查找的范围
int min = 0;
int max = arr.length - 1;

//2.利用循环不断的去找要查找的数据
while (true) {
if (min > max) {
return -1;
}
//3.找到min和max的中间位置
int mid = (min + max) / 2;
//4.拿着mid指向的元素跟要查找的元素进行比较
if (arr[mid] > number) {
//4.1 number在mid的左边
//min不变,max = mid - 1;
max = mid - 1;
} else if (arr[mid] < number) {
//4.2 number在mid的右边
//max不变,min = mid + 1;
min = mid + 1;
} else {
//4.3 number跟mid指向的元素一样
//找到了
return mid;
}
}
}
Lobster AIjava运行12345678910111213141516171819202122232425262728
1.1.3 插值查找

将查找的点改进为如下:

mid=low+(key-a[low])/(a[high]-a[low])*(high-low),

这样,让mid值的变化更靠近关键字key,这样也就间接地减少了比较次数。

基本思想:基于二分查找算法,将查找点的选择改进为自适应选择,可以提高查找效率。当然,差值查找也属于有序查找。

  • 数组中如果分布非常不均匀,那么插值查找未必是很合适的选择。
1.1.4 斐波那契查找

基本思想:也是二分查找的一种提升算法,通过运用黄金比例的概念在数列中选择查找点进行查找,提高查找效率。同样地,斐波那契查找也属于一种有序查找算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public static int search(int[] arr, int key) {
int low = 0;
int high = arr.length - 1;
//表示斐波那契数分割数的下标值
int index = 0;
int mid = 0;
//调用斐波那契数列
int[] f = getFeiBo();
//获取斐波那契分割数值的下标
while (high > (f[index] - 1)) {
index++;
}
//因为f[k]值可能大于a的长度,因此需要使用Arrays工具类,构造一个新法数组,并指向temp[],不足的部分会使用0补齐
int[] temp = Arrays.copyOf(arr, f[index]);
//实际需要使用arr数组的最后一个数来填充不足的部分
for (int i = high + 1; i < temp.length; i++) {
temp[i] = arr[high];
}
//使用while循环处理,找到key值
while (low <= high) {
mid = low + f[index - 1] - 1;
if (key < temp[mid]) {//向数组的前面部分进行查找
high = mid - 1;
/*
对k--进行理解
1.全部元素=前面的元素+后面的元素
2.f[k]=k[k-1]+f[k-2]
因为前面有k-1个元素没所以可以继续分为f[k-1]=f[k-2]+f[k-3]
即在f[k-1]的前面继续查找k--
即下次循环,mid=f[k-1-1]-1
*/
index--;
} else if (key > temp[mid]) {//向数组的后面的部分进行查找
low = mid + 1;
index -= 2;
} else {//找到了
//需要确定返回的是哪个下标
if (mid <= high) {
return mid;
} else {
return high;
}
}
}
return -1;
}
Lobster AIjava运行12345678910111213141516171819202122232425262728293031323334353637383940414243444546
1.1.5 分块查找
  1. 需要把 数据分成N多小块,块与块之间不能有数据重复的交集。
  2. 给每一块创建对象单独存储到数组当中
  3. 查找数据的时候,先在数组查,当前数据属于哪一块
  4. 再到这一块中顺序查找

核心思想:块内无序,块间有序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class A03_BlockSearchDemo {
public static void main(String[] args) {
int[] arr = {16, 5, 9, 12,21, 18,
32, 23, 37, 26, 45, 34,
50, 48, 61, 52, 73, 66};

//创建三个块的对象
Block b1 = new Block(21,0,5);
Block b2 = new Block(45,6,11);
Block b3 = new Block(73,12,17);

//定义数组用来管理三个块的对象(索引表)
Block[] blockArr = {b1,b2,b3};

//定义一个变量用来记录要查找的元素
int number = 37;

//调用方法,传递索引表,数组,要查找的元素
int index = getIndex(blockArr,arr,number);

//打印一下
System.out.println(index);
}

//利用分块查找的原理,查询number的索引
private static int getIndex(Block[] blockArr, int[] arr, int number) {
//1.确定number是在那一块当中
int indexBlock = findIndexBlock(blockArr, number);

if(indexBlock == -1){
//表示number不在数组当中
return -1;
}

//2.获取这一块的起始索引和结束索引 --- 30
// Block b1 = new Block(21,0,5); ---- 0
// Block b2 = new Block(45,6,11); ---- 1
// Block b3 = new Block(73,12,17); ---- 2
int startIndex = blockArr[indexBlock].getStartIndex();
int endIndex = blockArr[indexBlock].getEndIndex();

//3.遍历
for (int i = startIndex; i <= endIndex; i++) {
if(arr[i] == number){
return i;
}
}
return -1;
}

//定义一个方法,用来确定number在哪一块当中
public static int findIndexBlock(Block[] blockArr,int number){ //100
//从0索引开始遍历blockArr,如果number小于max,那么就表示number是在这一块当中的
for (int i = 0; i < blockArr.length; i++) {
if(number <= blockArr[i].getMax()){
return i;
}
}
return -1;
}
}

class Block{
private int max;//最大值
private int startIndex;//起始索引
private int endIndex;//结束索引

//构造方法和get、set方法省略

public String toString() {
return "Block{max = " + max + ", startIndex = " + startIndex + ", endIndex = " + endIndex + "}";
}
}
Lobster AIjava运行12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
1.1.6 哈希查找

哈希查找是分块查找的进阶版,适用于 数据一边添加一边查找的情况。

一般是数组 + 链表的结合体或者是数组+链表 + 红黑树的结合体。

基本思想:先计算出当前数据的哈希值,用哈希值跟数组的长度进行计算,计算出应存入的位置,再挂在数组的后面形成链表,如果挂的元素太多而且数组长度过长,我们也会把链表转化为红黑树,进一步提高效率。

1.1.7 树表查找

基本思想:二叉查找树是先对待查找的数据进行生成树,确保树的左分支的值小于右分支的值,然后在就行和每个节点的父节点比较大小,查找最适合的范围。 这个算法的查找效率很高,但是如果使用这种查找方法要首先创建树。

二叉查找树(BinarySearch Tree,也叫二叉搜索树,或称二叉排序树Binary Sort Tree),具有下列性质的二叉树:

  1. 任意节点左子树上所有的数据,均小于本身;
  2. 任意节点右子树上所有的数据,均大于本身;

二叉查找树性质:对二叉查找树进行中序遍历,即可得到有序的数列。

1.2 排序算法

1.2.1 选择排序

img

  1. 从0索引开始,跟后面的元素一一比较
  2. 小的放前面,大的放后面
  3. 第一次循环结束后,最小的数据已经确定
  4. 第二次循环从1索引开始以此类推
  5. 第三轮循环从2索引开始以此类推
  6. 第四轮循环从3索引开始以此类推。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//arr是引用数据类型,形参和实参公用一个空间,改变形参arr的元素实参arr也会改变
public static void selectSort(int[] arr) {
//外循环:几轮
//i:表示这一轮中,我拿着哪个索引上的数据跟后面的数据进行比较并交换
for (int i = 0; i < arr.length - 1; i++) {
//内循环:每一轮我要干什么事情?
//拿着i跟i后面的数据进行比较交换
for (int j = i + 1; j < arr.length; j++) {
if (arr[i] > arr[j]) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
Lobster AIjava运行12345678910111213141516
1.2.2 冒泡排序

img

  1. 相邻的元素两两比较,大的放右边,小的放左边
  2. 第一轮比较完毕之后,最大值就已经确定,第二轮可以少循环一次,后面以此类推
  3. 如果数组中有n个 数据,总共我们只要执行n-1轮的代码就可以
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void bubbleSort(int[] arr) {
//外循环:表示我要执行多少轮。 如果有n个数据,那么执行n - 1 轮
for (int i = 0; i < arr.length - 1; i++) {
//内循环:每一轮中我如何比较数据并找到当前的最大值
//-1:为了防止索引越界
//-i:提高效率,每一轮执行的次数应该比上一轮少一次。
for (int j = 0; j < arr.length - 1 - i; j++) {
//i 依次表示数组中的每一个索引:0 1 2 3 4
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
Lobster AIjava运行12345678910111213141516
1.2.3 插入排序

img

将0索引的元素到N索引的元素看做是有序的,把N+1索引的元素到最后一个当成是无序的。

遍历无序的数据,将遍历到的元素插入有序序列中适当的位置,如遇到相同数据,插在后面。

N的范围:0~最大索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void insertSort(int[] arr) {
//1.找到无序的哪一组数组是从哪个索引(2)开始的。
int startIndex = -1;
for (int i = 0; i < arr.length; i++) {
if (arr[i] > arr[i + 1]) {
startIndex = i + 1;
break;
}
}
//2.遍历从startIndex开始到最后一个元素,依次得到无序的哪一组数据中的每一个元素
for (int i = startIndex; i < arr.length; i++) {
//把遍历到的数据,插入到前面有序的这一组当中
//记录当前要插入数据的索引
int j = i;
while (j > 0 && arr[j] < arr[j - 1]) {
//交换位置
int temp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = temp;
j--;
}
}
}
Lobster AIjava运行1234567891011121314151617181920212223
1.2.4 快速排序

img

  1. 从数列中挑出一个元素,一般都是左边第一个数字,称为 “基准数”;
  2. 创建两个指针,一个从前往后走,一个从后往前走。
  3. 先执行后面的指针,找出第一个比基准数小的数字
  4. 再执行前面的指针,找出第一个比基准数大的数字
  5. 交换两个指针指向的数字
  6. 直到两个指针相遇
  7. 将基准数跟指针指向位置的数字交换位置,称之为:基准数归位。
  8. 第一轮结束之后,基准数左边的数字都是比基准数小的,基准数右边的数字都是比基准数大的。
  9. 把基准数左边看做一个序列,把基准数右边看做一个序列,按照刚刚的规则递归排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
    public static void main(String[] args) {
System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.MIN_VALUE);
/*
快速排序:
第一轮:以0索引的数字为基准数,确定基准数在数组中正确的位置。
比基准数小的全部在左边,比基准数大的全部在右边。
后面以此类推。
*/
int[] arr = {1, 1, 6, 2, 7, 9, 3, 4, 5, 1, 10, 8};
quickSort(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}


/*
* 参数一:我们要排序的数组
* 参数二:要排序数组的起始索引
* 参数三:要排序数组的结束索引
* */
public static void quickSort(int[] arr, int i, int j) {
//定义两个变量记录要查找的范围
int start = i;
int end = j;

if(start > end){
//递归的出口
return;
}

//记录基准数
int baseNumber = arr[i];
//利用循环找到要交换的数字
while(start != end){
//利用end,从后往前开始找,找比基准数小的数字
//int[] arr = {1, 6, 2, 7, 9, 3, 4, 5, 10, 8};
while(true){
if(end <= start || arr[end] < baseNumber){
break;
}
end--;
}
//利用start,从前往后找,找比基准数大的数字
while(true){
if(end <= start || arr[start] > baseNumber){
break;
}
start++;
}
//把end和start指向的元素进行交换
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
}

//当start和end指向了同一个元素的时候,那么上面的循环就会结束
//表示已经找到了基准数在数组中应存入的位置
//基准数归位
//就是拿着这个范围中的第一个数字,跟start指向的元素进行交换
int temp = arr[i];
arr[i] = arr[start];
arr[start] = temp;

//确定6左边的范围,重复刚刚所做的事情
quickSort(arr,i,start - 1);
//确定6右边的范围,重复刚刚所做的事情
quickSort(arr,start + 1,j);
}
Lobster AIjava运行1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768

1.3 基本 数据结构

  • 栈结构:先进后出。
  • 队列结构:先进先出。
  • 数组结构:查询快、增删慢。
  • 链表结构:查询慢、增删快。
1.3.1 二叉树

二叉树的特点

  • 二叉树中,任意一个节点的度要小于等于2
    • 节点:在树结构中,每一个元素称之为节点。
    • 度:每一个节点的子节点数量称之为度。

二叉树结构图

image-20250531194223792

1.3.2 二叉查找树

二叉查找树的特点

  • 二叉查找树,又称二叉排序树或者二叉搜索树
  • 每一个节点上最多有两个子节点
  • 左子树上所有节点的值都小于根节点的值
  • 右子树上所有节点的值都大于根节点的值

二叉查找树结构图

image-20250531194520877

二叉查找树添加节点规则

  • 小的存左边
  • 大的存右边
  • 一样的不存
1.3.3 平衡二叉树

平衡二叉树的特点

  • 二叉树左右两个子树的高度差不超过1
  • 任意节点的左右两个子树都是一颗平衡二叉树

平衡二叉树旋转

  • 旋转触发时机

    • 当添加一个节点之后,该树不再是一颗平衡二叉树。
  • 左旋:就是将根节点的右侧往左拉,原先的右子节点变成新的父节点,并把多余的左子节点出让,给已经降级的根节点当右子节点。

    image-20250531195024301

  • 右旋:就是将根节点的左侧往右拉,左子节点变成了新的父节点,并把多余的右子节点出让,给已经降级根节点当左子节点。

    image-20250531195231697

  • 平衡二叉树和二叉查找树对比结构图

    image-20250531195339452

  • 平衡二叉树旋转的四种情况:

    • 左左

      • 左左:当根节点左子树的左子树有节点插入,导致二叉树不平衡。
      • 如何旋转:直接对整体进行右旋即可。

      image-20250531195628807

    • 左右

      • 左右:当根节点左子树的右子树有节点插入,导致二叉树不平衡。
      • 如何旋转:先在左子树对应的节点位置进行左旋,在对整体进行右旋。

      image-20250531195833652

    • 右右

      • 右右:当根节点右子树的右子树有节点插入,导致二叉树不平衡。
      • 如何旋转:直接对整体进行左旋即可。

      image-20250531195957405

    • 右左

      • 右左:当根节点右子树的左子树有节点插入,导致二叉树不平衡。
      • 如何旋转:先在右子树对应的节点位置进行右旋,在对整体进行左旋。

      image-20250531200121465

1.3.4 红黑树

红黑树的特点

  • 平衡二叉B树。
  • 每一个节点可以是红或者黑。
  • 红黑树不是高度平衡的,它的平衡是通过 “自己的红黑规则” 进行实现的。

红黑树的红黑规则有哪些

  1. 每一个节点或是红色的,或者是黑色的。
  2. 根节点必须是黑色。
  3. 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的。
  4. 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)。
  5. 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。

红黑树添加节点的默认颜色

  • 添加节点时,默认为红色,效率高。

红黑树添加节点后如何保持红黑规则

image-20250531200656154

2.Arrays类

Arrays类是操作数组的工具类,之所以不把它归纳到 十、API 章节,是因为这个工具类牵扯到一定的 数据结构内容。

方法名 说明
public static String toString(数组) 把数组拼接成一个字符串
public static int binarySearch(数组,查找的元素) 二分查找法查找元素
public static int[] copyOf(原数组,新数组的长度) 拷贝数组
public static int[] copyOfRange(原数组,起始索引,结束索引) 拷贝数组(指定范围)
public static void fill(数组,元素) 填充数组
public static void sort(数组) 按照默认方式进行数组排序
public static void sort(数组,排序规则) 按照指定的规则排序

toString(数组)

  • 将数组元素按照[1, 2, 3]的格式拼接(有空格)。

binarySearch(数组,查找的元素)

  • 数组的元素必须是升序排列的。
  • 如果要查找的元素不存在,返回的是-应插入点索引-1

copyOf(原数组,新数组的长度)

  • 如果新数组的长度小于老数组的长度,会部分拷贝(只拷贝老数组前一些)。
  • 如果新数组的长度等于老数组的长度,会完全拷贝。
  • 如果新数组的长度大于老数组的长度,会补上默认值。

copyOfRange(原数组,起始索引,结束索引)

  • 包含起始索引,不包含结束索引。

sort(数组)

  • 默认情况下,基本数据类型升序排列,底层使用快速排序。

sort(数组,排序规则)

  • 只能给引用数据类型排序,如果要排序基本数据类型,要转换成对应的包装类。

  • 底层使用 插入排序+二分查找 的方式排序。

  • 第二个参数是一个Comparator<E>接口,调用方法时要传递相应的实现类对象作为排序规则(建议使用匿名内部类)

    1.Comparator接口的实现类要重写compare方法

    2.compare的两个参数:

    o1:表示在无序序列中,遍历得到的每一个元素

    o2:有序序列中的元素

    3.返回值o1-o2:

    正数:升序排列

    负数:降序排列

    0:升序排列

1
2
3
4
5
6
7
8
9
Integer[] arr = {5, 6, 3, 5, 8, 4, 2, 4, 9};
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
System.out.println(Arrays.toString(arr)); // [2, 3, 4, 4, 5, 5, 6, 8, 9]
Lobster AIjava运行12345678

3.Lambda表达式

3.1 函数式接口

函数式编程思想:就是忽略面向对象的复杂方法,强调做什么,而不是谁去做

函数式接口有且仅有一个抽象方法的接口叫做函数式接口,接口上可以加@FunctionalInterface注解。

Lambda表达式只能简化函数式接口的匿名内部类的写法。

简单来说,如果一个接口是函数式接口,创建这个接口的匿名实现类时可以不用关注这个接口的名字、接口内抽象方法的名字,只用关注抽象方法的形参和功能(包括返回值)即可。

省略规则

使用Lambda表达式需要了解函数式接口匿名实现类的省略规则。

  1. new Comparator<E>()@Override、类的大括号都可以省略。
  2. 参数类型可以省略。
  3. 如果只有一个参数,()也可以省略。
  4. 如果Lambda表达式只有一行,大括号、分号、return都可以省略,而且必须同时省略。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Comparator<E>接口是一个函数式接口,只有一个抽象方法int compare(Integer o1, Integer o2),所以使用sort方法可以使用Lambda表达式简化:

//不用Lambda表达式的写法
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});

//使用Lambda表达式的写法
1.按照12可以省略成:
// -> 不能忘记
Arrays.sort(arr, (o1, o2) -> {
return o1 - o2;
});
2.按照234可以省略成:
Arrays.sort(arr, (Integer o1, Integer o2) -> o1 - o2);
Lobster AIjava运行123456789101112131415161718

可以看到,使用Lambda可以不用关注接口名Comparator和抽象方法名compare,大大减少学习压力。