黑马程序员Java学习笔记 - 上部(从基础到源码)
黑马程序员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下载与安装
解压安装包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小案例
创建一个文件
HelloWorld.java文件:1
2
3
4
5
6public class HelloWorld {
public static void main(String[] args) {
System.out.println("HelloWorld");
}
}
Lobster AIjava运行12345在所在目录下打开cmd窗口
运行指令
javac HelloWorld.java和java HelloWorld.class- javac + 文件名 + 后缀名 (就是编译java文件,编译后会产生一个.class文件)
- java + 文件名(运行编译之后的class文件)
注意:
执行javac和java指令需要先配置环境变量:
Java(编程语言)
①JAVA_HOME:告诉操作系统JDK安装在了哪个位置(未来其他技术要通过这个找JDK)

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

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语言翻译成操作系统能看得懂的语言。

二、Java基础语法
1.注释
- 单行注释
1 | // 这是单行注释文字 |
- 多行注释
1 | /* |
- 文档注释
| 标签 | 描述 |
|---|---|
| @author | 表示作者 |
| @deprecated | 标识过期的类或成员 |
| @exception | 标识抛出的异常 |
| @param | 标识方法的参数 |
| @return | 标识方法的返回值 |
| @see | 标识指定参数的内容 |
| @serial | 标识序列化属性 |
| @version | 标识版本 |
| @throws | 标识引入一个特定的变化 |
1 | /** |
注意:多行注释不能嵌套多行注释,但是可以嵌套单行注释。
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 阿里巴巴命名规范细节
- 尽量不要用拼音。但是一些国际通用的拼音可视为英文单词。
- 平时在给变量名、方法名、类名起名字的时候,不要使用下划线或美元符号。
4.键盘录入
1.导包:找到类所在位置
1 | import java.util.Scanner; |
2.创建对象
1 | Scanner sc = new Scanner(System.in); |
3.接收 数据:以接受字符串 为例
1 | int inputStr = sc.nextInt(); |
5.IntelliJ IDEA
IntelliJ IDEA是 Java语言开发的集成环境,它把代码的编写,编译,执行,调试等多种功能综合到一起,从而简化开发流程。
5.1 安装
官网:https://www.jetbrains.com/idea
- 双击资料中的安装包,点击next,准备安装(可以修改文件的安装路径);
- 勾选64-bit launcher(其他的不要勾选),表示在桌面新建一个64位的快捷方式;
- 点击Install(选择JetBrains),准备安装;
- 等进度条读取完毕之后,会有最终界面提示,点击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 | + - * :跟小学数学中一模一样没有任何区别; |
技巧:
- 判断整数a是否是偶数:
a%2,结果为0表示a是偶数,结果是1表示a是奇数。
- 获取任意一个整数a上每一位数:
个位:a % 10
十位:a / 10 % 10
百位:a / 100 % 10
千位:a / 1000 % 10
…以此类推…
2.自增自减运算符
1 | ++ 自增运算符 |
a++和++a的区别:
1 | a++是先使用a,再进行++运算。 |
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 | 目标数据类型 变量名 = (目标数据类型)被强转的数据; |
注意:强制转换有可能会导致数据发生错误。(数据的精度丢失)
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 | if (关系表达式) { |
如果关系表达式为true,就执行语句体,否则跳过if语句。
细节:
如果要对一个布尔类型的变量进行判断,不要写==,直接把变量写在小括号中即可。
如果大括号中的语句体只有一条,那么大括号可以省略不写
如果大括号省略了,那么if只能控制距离他最近的那一条语句。
建议,大括号还是不要省略
1 | int a = 100; //相当于两条语句:1.定义变量a 2.给变量a赋值为100 |
- 大括号的开头可以另起一行书写,但是建议写在第一行的末尾
2.1.2 格式二
1 | if (关系表达式) { |
如果关系表达式为true,执行语句体1,否则,执行语句体2。
2.1.3 格式三
1 | if (关系表达式1) { |
如果关系表达式1为true,执行语句体1,否则如果关系表达式2为true,执行语句体2,…,所有关系表达式都为false才执行语句体n+1。
2.2 switch语句
1 | 格式: |
首先计算出表达式的值,然后和case依次比较,一旦有对应的值,就会执行相应的语句,在执行的过程中,遇到break就会结束;如果所有的case都和表达式的值不匹配,就会执行default语句体部分,然后程序结束掉。
细节:
- default可以放在任意位置,建议放在最后。
- switch 不支持 long、float、double、boolean 等类型,如果需要处理这些类型,可以使用 if-else 语句。
- case穿透:不写break会引发case穿透现象,即下面所有case语句都会执行(不会判断下面case的值),知道遇到break为止,例如:
1 | //输出2 3 4 -1 |
- switch在JDK12的新特性:
1 | // -> 自带break |
3.循环结构
3.1 for循环
1 | for (初始化语句;条件判断语句;条件控制语句) { |
执行流程:
① 执行初始化语句
② 执行条件判断语句,看其结果是true还是false
如果是false,循环结束
如果是true, 执行③
③ 执行循环体语句
④ 执行②
细节:初始化语句只执行一次,其中定义的变量只在本for循环内存在。
练习:求和连续的自然数之和
1 | public class ForTest02 { |
3.2 while循环
1 | while(条件判断语句){ |
执行流程:先判断条件判断语句,为true就执行循环体和条件控制语句,之后返回条件判断语句,…,直到条件判断语句为false位置结束循环。
练习:一张纸折叠多少次厚度能超过珠穆朗玛峰的高度
1 | int height = 8844430; //珠穆朗玛峰高度 |
3.3 do…while循环
1 | do{ |
执行流程:和while循环一致,只是会先执行循环体和条件控制语句再执行条件判断语句。
3.4 三种格式的区别
for和while循环,是先判断,再执行。
do…while是先执行,再判断。
当知道循环次数或者循环范围的时候,用for循环。
当不知道循环次数,也不知道循环范围,但是知道循环的结束条件时,用while循环。
3.5 循环高级
3.5.1 无限循环
又叫死循环。循环一直停不下来。
for格式:
1 | for(;;){ |
while格式:
1 | while(true){ |
do…while格式:
1 | do{ |
注意事项:
- 最为常用的格式:while
- 无限循环下面不能再写其他代码了,因为永远执行不到。
3.5.2 条件控制语句
break:不能单独存在,可以用在switch和循环中,表示结束本次switch或循环语句。
continue:不能单独存在,只能存在于循环当中。表示跳过本次循环,继续执行下次循环。
细节:如果循环中含有switch语句,则switch语句中的break会结束switch语句,而不是整个循环,如果希望跳出整个循环,需要为循环设置别名,例如:
1 | Scanner sc = new Scanner(System.in); |
4.Random
Random类是实现随机数生成的。
- 导包
1 | import java.util.Random; |
- 创建对象
1 | Random r = new Random (); |
- 生成随机数
1 | int number = r.nextInt(随机数的范围); |
五、数组
一种容器,可以同来存储同种 数据类型的多个值。
1.一维数组
1.1 数组的定义
格式一:
1 | 数据类型[] 数组名 |
格式二:
1 | 数据类型 数组名[] |
方法括号跟数组名,谁写在前面,谁写在后面都是一样的。平时习惯性使用第一种方式。
1.2 数组的初始化
1.2.1 静态初始化
完整格式:
1 | 数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3,元素4...}; |
方括号中,多个元素之间一定要用逗号隔开。
简化格式:
1 | 数据类型[] 数组名 = {元素1,元素2,元素3,元素4...}; |
1.2.2 动态初始化
格式:
1 | 数据类型[] 数组名 = new 数据类型[数组的长度]; |
数组的默认初始化值:
- 整数类型:0
- 小数类型:0.0
- 布尔类型:false
- 字符类型:‘\u0000’
- 引用类型:null
1.2.3 两种初始化方式的区别
静态初始化:手动指定数组的元素,系统会根据元素的个数,计算出数组的长度。
动态初始化:手动指定数组长度,由系统给出默认初始化值。
使用场景:
只明确元素个数,但是不明确具体的数据,推荐使用动态初始化。
已经明确了要操作的所有数据,推荐使用静态初始化。
1.3 地址值
数组的本质就是内存中一段连续的存储空间,数组的值就是这个连续存储空间的地址。打印数组的时候,实际出现的是数组的地址值:
1 | int[] arr = {1,2,3,4,5}; |
- [ :表示现在打印的是一个数组。
- I:表示现在打印的数组是int类型的。
- @:仅仅是一个间隔符号而已。
- 6d03e736:就是数组在内存中真正的地址值。(十六进制的)
我们习惯性会把[I@6d03e736这个整体称之为数组的地址值。
1.4 数组元素访问
索引:
数组的每个元素都有其对应的索引。
- 索引一定是从0开始的。
- 连续不间断。
- 逐个+1增长。
例如:上面定义的arr,1的索引为0,2的索引为1,…,以此类推。
格式:
1 | 数组名[索引]; |
作用:
- 获取数组中对应索引上的值。例如arr[2]就获取到了3。
- 修改数组中对应索引上的值,一旦修改,原来的值就会被覆盖,无法恢复。例如
arr[2] = 2;,就修改了arr[2],以后访问到的arr[2]都表示2,而不是3。
1.5 数组的遍历
遍历:就是把数组里面所有的内容一个一个全部取出来。
数组的长度:数组名.length;
通用代码:
1 | for(int i = 0; i < arr.length; i++){ |
2.二维数组
二维数组的本质还是一维数组,只是数组的每一个元素是一个一维数组的地址值,而二位数组也是一个地址值。
二维数组的操作和一维数组很类似,但也有不同,这里只介绍不同地方。
2.1 初始化
2.1.1 静态初始化
格式:
1 | 数据类型[][] 数组名 = new 数据类型[][]{{元素1, 元素2, ...},{元素1, 元素2, ...},...} |
简化格式:
1 | 数据类型[][] 数组名 = {{元素1, 元素2, ...},{元素1, 元素2, ...},...} |
- 二维数组的每一个一维数组长度不一定一致。
2.1.2 动态初始化
格式:
1 | 数据类型[][] 数组名 = new 数据类型[m][n]; |
m表示这个二维数组可以存放多少个一维数组;
n表示每一个一维数组可以存放多少个元素。
- 这个时候二维数组的每一个一维数组长度必须一致。
2.2 数组元素访问
1 | arr[0]可以获取二维数组arr中的第一个一维数组,本身是一个地址。 |
通过赋值运算符可以进行赋值,赋值后二维数组那个位置的值将不存在,会被新值替代。
2.3 遍历二维数组
arr.length可以获得二维数组arr包含几个一维数组,不能获得二维数组arr最多存几个元素。
二维数组中存的实际上是每一个一维数组的地址值。
1 | int[][] arr = {{1, 2, 3}, {5, 6, 7, 8}}; |
六、方法
方法(method)是程序中最小的执行单元,必须先定义才能调用,类似c语言中的函数。
1.方法的定义与调用
1.1 无参数方法定义和调用
定义:
1 | public static void 方法名 () { |
调用:
1 | 方法名(); |
1.2 带参数方法定义和调用
定义:
参数:由 数据类型和变量名组成,多个参数间用逗号隔开: 数据类型 变量名;
参数范例:int a;
1 | public static void 方法名 (参数1) { |
调用:
方法调用时,参数的数量与类型必须与方法定义中的设置相匹配。
1 | 方法名(参数); |
形参和实参
- 形参:方法定义中的参数,等同于变量定义格式,例如:number、num1、num2。
- 实参:方法调用中的参数,等同于使用变量或常量,例如: 10、20。
1.3 带返回值方法的定义和调用
定义:
方法定义时return后面的返回值与方法定义上的数据类型要匹配。
1 | public static 数据类型 方法名 ( 参数 ) { |
调用:
方法的返回值通常会使用变量接收,否则该返回值将无意义。
1 | 方法名 ( 参数 ) ; |
1.4 方法的通用格式
1 | public static 返回值类型 方法名(参数) { |
- 返回值类型可以任意,但是必须和return的结果类型一致(引用数据类型也可以不一致,这里牵扯到多态,后面讲)。
- 方法名推荐使用小驼峰命名,见名知意。
- 参数可以是0个、1个、多个,但必须包含数据类型和变量名。
- 如果返回值类型是void,return语句可以省略。
1.5 方法的注意事项
- 方法不能嵌套定义,否则会引发编译错误!!!
- void表示无返回值,可以省略return,也可以单独的书写return,后面不加数据。
2.方法重载
方法重载概念:方法重载指同一个类中定义的多个方法之间的关系,满足下列条件的多个方法相互构成重载
- 多个方法在同一个类中
- 多个方法具有相同的方法名
- 多个方法的参数不相同,即类型不同、数量不同或顺序不同
注意事项:
重载仅对应方法的定义,与方法的调用无关。
重载仅针对同一个类中方法的名称和参数(形参),而和返回值无关(返回值可以不同)。
例如:重载两个方法获取两个数的最大值
1 | //int |
七、面向对象
在 java中,万物皆对象,即客观存在的一切事物都可以是一个对象。
类是对现实生活中一类具有共同属性和行为的事物的抽象,如猫就可以是一个类,对象就是一个具体的猫。
1.类的定义
类的组成是由属性和行为两部分组成
- 属性:在类中通过成员变量来体现(类中方法外的变量)
- 行为:在类中通过成员方法来体现(和前面的方法相比去掉static关键字即可)
类的定义步骤:
①定义类
②编写类的成员变量
③编写类的成员方法
1 | public class 类名 { |
2.对象的使用
创建对象的格式:
1
2类名 对象名 = new 类名();
Lobster AIjava运行1调用成员的格式:
- 对象名.成员变量
- 对象名.成员方法();
1 | public static void main(String[] args) { |
3.对象内存图
3.1 单个对象内存图
- 成员变量使用过程

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

方法运行时进栈,运行完出栈。
3.2 多个对象内存图
- 成员变量使用过程

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

多个对象共用一套成员方法,运行时进栈,运行完出栈。
4.成员变量和局部变量
| 区别 | 成员变量 | 局部变量 |
|---|---|---|
| 类中位置不同 | 类中,方法外 | 方法内,方法声明上 |
| 初始化值不同 | 有默认初始化值 | 没有,使用之前必须赋值 |
| 内存位置不同 | 堆内存 | 栈内存 |
| 生命周期不同 | 随着对象的创建而存在,随着对象的消失而消失 | 随着方法的调用而存在,随着方法的运行结束而消失 |
| 作用域 | 整个类中有效 | 当前方法中有效 |
5.封装
5.1 封装思想
封装概述:
- 是面向对象三大特征之一(封装,继承,多态)
- 对象代表什么,就得封装对应的 数据,并提供数据对应的行为 ,比如人画圆,画圆方法要放在圆这个类里。
封装代码实现:
将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问。成员变量private,提供对应的getXxx()/setXxx()方法。
5.2 private关键字
private是一个修饰符,可以用来修饰成员(成员变量,成员方法)。
被private修饰的成员,只能在本类进行访问,如果其他类想要访问,就要提供相应的get 和 set方法(用public修饰)。
1 | /* |
5.3 this关键字
this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)
- 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量(就近原则)
- 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
this的本质:代表方法调用者的地址值。
1 | public class User { |
当调用setName方法时,this关键字记录的就是u的地址值。
6.构造方法
构造方法是用来创建对象的,主要是完成对象 数据的初始化。
格式:
1 | public class 类名{ |
当创建对象时,会自动执行构造方法的构造体。
注意事项:
- 构造方法的创建:如果没有定义构造方法,系统会给出一个默认的无参数构造方法,如果定义了构造方法,系统将不再提供默认的构造方法。如果需要,可以自定义无参数构造方法。
- 推荐的使用方式:无论是否使用,都手工书写无参数构造方法
- 可以使用带参构造,为成员变量进行初始化
标准 JavaBean类
① 类名需要见名知意
② 成员变量使用private修饰
③ 提供至少两个构造方法
- 无参构造方法
- 带全部参数的构造方法
④ get和set方法
提供每一个成员变量对应的setXxx()/getXxx()
⑤ 如果还有其他行为,也需要写上
例如:
1 | class Student { |
推荐以后自定义类都进行标准化,这就是封装。
为标准类的成员赋值的两种方法:
- 无参构造方法创建对象后使用setXxx()赋值
- 使用带参构造方法直接创建带有属性值的对象
7.static关键字
关于 static 关键字的使用,它可以用来修饰的成员变量和成员方法。被static修饰的成员是属于类的,所有对象共用,放在静态区中;没有static修饰的成员变量和方法则是属于对象的。
7.1 static修饰成员变量
有static修饰成员变量,说明这个成员变量是属于类的,这个成员变量称为类变量或者静态成员变量。 直接用类名访问即可。
静态变量定义格式:
1 | 修饰符 static 数据类型 变量名 = 初始值; |
静态成员变量的访问:
1 | 类名.静态变量 |
实例变量定义格式:
无static修饰的变量是属于对象的,也叫实例变量。
实例变量的访问:
1 | 对象.实例成员变量 |
7.2 static修饰成员方法
静态方法及其访问:
有static修饰成员方法,说明这个成员方法是属于类的,这个成员方法称为类方法或者静态方法。 直接用类名访问即可。
1 | 定义: |
实例方法及其访问:
无static修饰的成员方法属于每个对象的,这个成员方法也叫做实例方法。
需要注意的是:实例方法是属于每个对象,必须创建类的对象才可以访问。
格式:对象.实例方法
1 | public class Student{ |
7.3 小结
1.当 static 修饰成员变量或者成员方法时,该变量称为静态变量,该方法称为静态方法。该类的每个对象都共享同一个类的静态变量和静态方法。任何对象都可以更改该静态变量的值或者访问静态方法,但是不推荐这种方式去访问。
2.无static修饰的成员变量或者成员方法,称为实例变量,实例方法,实例变量和实例方法必须创建类的对象,然后通过对象来访问。
3.static修饰的成员属于类,会存储在静态区,是随着类的加载而加载的,且只加载一次,所以只有一份,节省内存。存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。它优先于对象存在,所以,可以被所有对象共享。
4.无static修饰的成员,是属于对象,对象有多少个,他们就会出现多少份。所以必须由对象调用。
注意:
- 静态只能访问静态:静态方法中只能使用静态的成员变量,且只能调用静态方法。
- 非静态可以访问所有:非静态方法中既可以访问静态变量和静态方法,又可以访问非静态变量和非静态方法。
实际开发中static的使用:
实际开发中,static除了用在main主方法所在测试类中,还用在工具类中。
工具类:我们把经常使用的方法封装到一个类中,并用public static修饰,需要使用时可以通过类名调用,大大节省开发压力。
工具类常常放在自创建utils包下,命名成类的功能Util,如ArrayUtil表示这个类封装了数组操作的方法。
8.继承
8.1 概述
继承:就是子类继承父类的属性和行为(方法),使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
简单理解,就是父类有的子类也有,只要继承这个父类就可以使这个类具有父类的东西。

继承的好处:
- 提高代码的复用性(减少代码冗余,相同代码重复利用)。
- 使类与类之间产生了关系。
8.2 继承的格式
1 | class 父类 { |
需要注意: Java是单继承的,一个类只能继承一个直接父类,跟现实世界很像,但是Java中的子类是更加强大的。
顶层父类是Object类。所有的类默认继承Object,作为父类。
8.3 子类不能继承的内容
并不是父类的所有内容都可以给子类继承的:
子类不能继承父类的构造方法。
子类能继承父类的所有成员变量。
子类能继承父类的所有非私有成员方法,父类的私有成员方法子类不能继承。
子类可以直接使用父类的所有非private修饰的成员,父类的private成员子类不能直接使用,如果想要使用,需要通过父类提供的getXxx()和setXxx()方法。
1 | public class Demo03 { |
8.4 继承的成员特点
8.4.1 成员变量
如果子类父类中出现不重名的成员变量,这时的访问是没有影响的。
如果子类父类中出现重名的成员变量,这时的访问是有影响的。
- 子父类中出现了同名的成员变量时,子类会优先访问自己对象中的成员变量(遵守就近原则)。
super访问父类成员变量:
子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用super 关键字,修饰父类成员变量,类似于之前学过的 this 。
super代表的是父类对象的引用,this代表的是当前对象的引用。
- 使用格式:
1 | super.父类成员变量名 |
8.4.2 成员方法
如果子类父类中出现不重名的成员方法,这时的调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。
如果子类父类中出现重名的成员方法,则创建子类对象调用该方法的时候,子类对象会优先调用自己的方法。
8.4.2.1 方法重写
子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。
@Override重写注解:
- @Override:注解,重写注解校验!
- 这个注解标记的方法,就说明这个方法必须是重写父类的方法,否则编译阶段报错。
- 建议重写都加上这个注解
1 | public class Animal { |
注意事项:
- 方法重写是发生在子父类之间的关系。
- 子类方法覆盖父类方法,必须要保证权限大于等于父类权限,推荐权限一致。
- 子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。
8.4.3 构造方法
子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。
super()必须写在构造方法的第一行。
继承后子类构方法器特点:子类所有构造方法的第一行都会默认先调用父类的无参构造方法。
8.4.3.1 super和this的用法格式
super和this完整的用法:
1 | this.成员变量 -- 本类的 |
构造方法格式:
1 | super(...) -- 调用父类的构造方法,根据参数匹配确认 |
super(…)的使用:
1 | class Person { |
super(…)图解:

this(…)的使用:
默认是去找本类中的其他构造方法,根据参数来确定具体调用哪一个构造方法。
1 | class Student{ |
小结:
- 子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。
- super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
- super(…)和this(…)是根据参数去确定调用父类哪个构造方法的。
- super(…)可以调用父类构造方法初始化继承自父类的成员变量的 数据。
- this(…)可以调用本类中的其他构造方法。
注意:由于静态方法中没有this和super关键字,所以不能在静态方法中使用this和super,否则会报错!
8.5 继承的特点
- Java只支持单继承,不支持多继承。
1 | // 一个类只能有一个父类,不可以有多个父类。 |
- 一个类可以有多个子类。
1 | // A可以有多个子类 |
- 可以多层继承。
1 | class A {} |
顶层父类是Object类。所有的类默认继承Object,作为父类。
9.多态
9.1 多态的定义和前提
多态是继封装、继承之后,面向对象的第三大特性。
多态是指同一行为,具有多个不同表现形式。比如Cat和Dog都是动物,都有吃的行为,但是吃的形式不一样。
多态体现的格式:
1 | 父类类型 变量名 = new 子类/实现类构造器; |
多态的前提
有继承或者实现关系
方法的重写【意义体现:不重写,无意义】
父类引用指向子类对象【格式体现】
父类类型:指子类对象继承的父类类型,或者实现的父接口类型。
9.2 多态的使用场景
- 当一个方法的形参是一个类,我们可以传递这个类所有的子类对象。
- 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象(后面会学)。
- 而且多态还可以根据传递的不同对象来调用不同类中的方法。
9.3 多态的运行特点
调用成员变量时:编译看左边,运行看左边
调用成员方法时:编译看左边,运行看右边
1 | Fu f = new Zi(); |
9.4 引用类型转换
9.4.1 问题引入
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误,简言之,如果子类有些独有的功能,此时多态的写法就无法访问子类独有功能了。
1 | class Animal{ |
解决方法:通过强制类型转换实现访问子类独有功能,但是,强转只能把父类强转成子类,两个不相关的子类类型(如Cat和Dog)之间不能相互转换,否则会引发编译错误 ClassCastException。
9.4.2 转型
多态的转型分为向上转型(自动转换)与向下转型(强制转换)两种。
向上转型(自动转换):
多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。当父类引用指向一个子类对象时,便是向上转型。
1 | 父类类型 变量名 = new 子类类型(); |
向下转型(强制转换):
父类类型向子类类型向下转换的过程,这个过程是强制的。将父类引用转为子类引用,可以使用强制类型转换的格式。
1 | 子类类型 变量名 = (子类类型) 父类变量名; |
转型时的异常:
1 | public class Test { |
虽然可以通过编译,但是a本质上定义成了Cat类型,所以不能成功转型,报出异常 ClassCastException 。
9.4.3 instanceof关键字
为了避免ClassCastException的发生, Java提供了 instanceof 关键字,给引用变量做类型的校验:
1 | 变量名 instanceof 数据类型 |
使用:
1 | public class Test { |
instanceof新特性:
JDK14的时候提出了新特性,把判断和强转合并成了一行。
1 | //先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d |
10.键盘录入
键盘录入的使用参考 二.4 ,这里只扩展。
键盘录入类Scanner的方法:
next()、nextLine():可以接受任意 数据,但是都会返回一个字符串。
nextInt():只能接受整数,默认是int型数据。
nextDouble():能接收整数和小数,但是都会看做double型小数返回。
底层细节:
next(),nextInt(),nextDouble()在接收数据的时候,遇到空格、回车、制表符其中一个就会停止接收数据。
next(),nextInt(),nextDouble()在接收数据的时候,遇到空格、回车、制表符其中一个就会停止接收数据。但是这些符号 和 后面的数据还在内存中并没有接收。如果后面还有其他键盘录入的方法,会自动将这些数据接收。
1
2
3
4
5
6
7
8
9
10
11Scanner 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运行12345678910nextLine()方法是把一整行全部接收完毕,直到遇到回车才结束接受。
混用引起的后果:
next(),nextInt(),nextDouble()为一套配套使用,nextLine()单独使用,如果混用,会有严重的后果。
1 | Scanner sc = new Scanner(System.in);//① |
当代码运行到第二行,会让我们键盘录入,此时录入123。
但是实际上我们录的是123+回车。
而nextInt是遇到空格,回车,制表符都会停止。
所以nextInt只能接受123,回车还在内存中没有被接收。
此时就被nextLine接收了。
所以,如果混用就会导致nextLine接收不到数据。
11.对象数组
自定义类可以理解成是自定义数据类型,它也可以用来创建数组,只是数组中每个元素都是一个对象。
1 | package com.itheima.test6; |
12.包
包在操作系统中其实就是一个文件夹。包是用来分门别类的管理技术,不同的技术类放在不同的包下,方便管理和维护。
建包操作如下:

包名的命名规范:
1 | 包名.包的作用 |
- 包名一般是公司域名的倒写。例如:黑马是www.itheima.com,包名就可以定义成com.itheima.技术名称。
- 包名必须用”.“连接。
- 包名的每个路径名必须是一个合法的标识符,而且不能是 Java的关键字。
导包:
什么时候需要导包?
情况一:在使用Java中提供的非核心包中的类时
情况二:使用自己写的其他包中的类时
什么时候不需要导包?
情况一:在使用Java核心包( java.lang)中的类时
情况二:在使用同一个包中的类时
全类名:
如果在同一个类中使用了不同包下相同名字的类,就需要使用全类名:
1 | 全类名:包名 + 类名 |
13.权限修饰符
在Java中提供了四种权限修饰符,不同的权限修饰符有不同的访问权限,例如前面学的public表示全部类都可以访问。
不同权限修饰符访问权限:
public:公共的,所有地方都可以访问。
protected:本类 ,本包,其他包中的子类都可以访问。
默认(没有修饰符):本类 ,本包可以访问。
注意:默认是空着不写,不是default
private:私有的,只有当前类可以访问。
权限修饰符大小比较:public > protected > 默认 > private
总结:
| public | protected | 默认 | private | |
|---|---|---|---|---|
| 同一类中 | √ | √ | √ | √ |
| 同一包中的类 | √ | √ | √ | |
| 不同包的子类 | √ | √ | ||
| 不同包中的无关类 | √ |
建议这样使用权限:
- 成员变量使用
private,隐藏细节。 - 构造方法使用
public,方便创建对象。 - 成员方法使用
public,方便调用方法。
14.final关键字
final: 不可改变,最终的含义。可以用于修饰类、方法和变量。被final修饰的成员是常量,值不可变,类似c++中的const。
- 类:被修饰的类,不能被继承。
- 方法:被修饰的方法,不能被重写。
- 变量:被修饰的变量,有且仅能被赋值一次。
修饰类:
1 | final class 类名 { |
像 public final class String 、public final class Math 、public final class Scanner 等API,都是只提供给我们使用的,不能继承去更改内容。
修饰方法:
1 | 修饰符 final 返回值类型 方法名(参数列表){ |
修饰变量:
1 | 例如: |
pi被public和static修饰,说明任何类都可以可以通过类名得到pi,被final修饰说明pi不能再被赋值,否则会报错。
final修饰成员变量时可以只定义变量,赋值放在构造方法中,但是每个构造方法中都要赋值一次(非常不建议这样做):
1 | public class Student { |
- 被final修饰的变量推荐用大写形式,多个单词用下划线分隔,例如
final String HELLO_WORLD = "hello world"。
15.代码块
1.局部代码块
- 用大括号把一段代码括起来,用以控制变量的生命周期。
1 | public class Test{ |
2.构造代码块
写在成员位置的代码块
可以把多个构造方法中的重复代码抽取出来
执行时机:在创建本类对象的时候会先执行构造代码块再执行构造方法
1 | public class Student{ |
每次创建对象都会先打印构造代码块(创建几次打印几次),再打印空参构造。
3.静态代码块
格式:static{}
特点:需要通过关键字修饰,随着类的加载而加载,并且自动触发,只执行一次
使用场景:在类加载时,做一些 数据初始化时使用
1 | public class Student{ |
只有第一次创建对象时打印构造代码块,后面创建对象不会打印。
16.抽象类
如果父类知道子类需要使用的方法,但是不知道怎么实现,既可以使用抽象的思想实现。
- 抽象方法 : 没有方法体的方法。
- 抽象类:包含抽象方法的类。
16.1 abstract使用格式
abstract是抽象的意思,用于修饰方法方法和类,修饰的方法是抽象方法,修饰的类是抽象类。
抽象方法:
1 | 修饰符 abstract 返回值类型 方法名 (参数列表); |
抽象类:
1 | abstract class 类名字 { |
抽象类的使用:
继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。
1 | // 父类,抽象类 |
16.2 抽象类的细节
- 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
- 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
- 抽象类中,可以有构造方法、实例方法、静态方法和成员变量。
- 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
- 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类,编译无法通过而报错。
- 抽象类存在的意义是为了被子类继承。
八、字符串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 字符串的比较
==号比较
- 比较基本 数据类型:比较的是具体的值
- 比较引用数据类型:比较的是对象地址值
equals方法比较
1
2public boolean equals(String s) 比较两个字符串内容是否相同、区分大小写
Lobster AIjava运行1equalslgnoreCase方法比较
1
2public 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 | package com.itheima.stringdemo; |
2.StringBuilder
StringBuilder 可以看成是一个容器,创建之后里面的内容是可变的,例如“a” + “b”会直接把“b”拼接到到“a”,不会重新为“ab”开创空间。
应用场景:当我们在拼接字符串和反转字符串的时候会使用到。
2.1 构造方法
参考帮助文档,我们常采用以下方式创建对象:
1 | StringBuilder sb = new StringBuilder("abc"); |
2.2 常用方法
| 方法名 | 说明 |
|---|---|
| public StringBuilder append(任意类型) | 添加 数据,并返回对象本身 |
| public StringBuilder reverse() | 反转容器中的内容 |
| public int capacity() | 返回容量(最多存多少) |
| public int length() | 返回长度(已经存多少) |
| public String toString() | 通过toString()就可以实现把StringBuilder转换成String |
1 | sb.append(1); |
打印StringBuilder对象实际打印是对象的内容,而不是对象的地址值。
2.3 链式编程
如果前一个方法的结果又是下一个方法的调用者,而且这个结果只用一次,就可以考虑链式编程,例如上述程序可以简写为:
1 | System.out.println(sb.append(1).append("def").toString()); //abc1def |
2.4 案例
反转字符串:例如“abdrf”反转后是“frdba”。
1 | public static String reverse(String str){ |
3.StringJoiner
- StringJoiner跟StringBuilder一样,也可以看成是一个容器,创建之后里面的内容是可变的。
- 作用:提高字符串的操作效率,可以方便、高效地拼接字符串,而且代码编写特别简洁,但是目前市场上很少有人用。
- JDK8出现的
3.1 构造方法
| 方法名 | 说明 |
|---|---|
| public StringJoiner(间隔符号) | 创建一个StringJoiner对象,指定拼接时的间隔符号 |
| public StringJoiner(间隔符号, 开始符号, 结束符号) | 创建一个StringJoiner对象,指定拼接时的间隔符号、开始符号、结束符号 |
如果想要[元素1,元素2,元素3,...]格式的字符串,手动添加间隔符,,开始符和结束符,就需要手动添加,而我们希望只关注元素i的添加,这是就可以使用如下方式:
1 | StringJoiner sj = new StringJoiner(",", "[", "]"); //如果不指定开始结束符号,默认为空 |
3.2 成员方法
| 方法名 | 说明 |
|---|---|
| public StringJoiner add(添加的内容) | 添加 数据,返回对象本身 |
| public int length() | 返回长度(字符出现的个数) |
| public String toString() | 返回一个字符串,该字符串就是拼接之后的结果 |
1 | sj.add("a"); |
4.字符串底层原理
字符串存储的底层原理:
- 直接赋值会复用字符串常量池中的字符串
- new出来的不会复用,而是开辟一个新的空间
==号比较的到底是什么?
- 基本数据类型比较数据值
- 引用数据类型比较地址值
字符串拼接的底层原理:
- 如果没有变量的参与,都是字符串直接相加,编译之后就是拼接之后的结果,会复用串池中的字符串。
- 如果有变量参与,每一行拼接的代码,都会在内存中创建新的字符串,浪费内存。
- JDK8之前:系统底层会自动创建一个StringBuilder对象,用append方法拼接,再用toString方法转换成String类型,而toString底层是new了一个字符串对象。
- JDK8版本:系统会字符串拼接之后的总大小,把要拼接的内容放到数组中,此时也是产生了一个新的字符串。
StringBuilder提高效率的原理:
- 所有要拼接的内容都会往StringBuilder中放,不会创建很多无用的空间,节约内存。
StringBuilder源码分析:
- 默认创建一个长度是16的字节数组
- 添加的内容长度小于16,直接存
- 添加的内容大于16会扩容(原来的容量*2+2,内容超过扩容后的容量会按照实际扩容)
九、接口和内部类
1.接口
接口是更加彻底的抽象,JDK7之前,包括JDK7,接口中全部是抽象方法。接口同样是不能创建对象的。
1.1 定义格式
1 | //接口的定义格式: |
接口的声明:
interface。
接口名称:首字母大写,满足“驼峰模式”。
JDK7之前,接口中的只有包含:抽象方法和常量
接口中的抽象方法默认会自动加上public abstract修饰,程序员无需自己手写!!
在接口中定义的成员变量默认会加上: public static final修饰。也就是说在接口中定义的成员变量实际上是一个常量,并且可以直接用接口名访问。
1.2 基本的实现
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类,实现使用 implements关键字。
- 类与接口之间的关系是多实现的,一个类可以同时实现多个接口。
- 接口与接口之间是可以多继承的:也就是一个接口可以同时继承多个接口。
- 类与接口是实现关系
- 接口与接口是继承关系
实现接口的格式:
1 | class 类名 implements 接口1,接口2,接口3...{ |
类实现接口的要求:
- 必须重写实现的全部接口中所有抽象方法。
- 如果一个类实现了接口,但是没有重写完全部接口的全部抽象方法,这个类也必须定义成抽象类。
类实现接口案例:
1 | /** 法律规范:接口*/ |
接口继承接口案例:
1 | public interface Abc { |
1.3 接口的细节
- 当两个接口中存在相同抽象方法的时候,该怎么办?
只要重写一次即可。此时重写的方法,既表示重写1接口的,也表示重写2接口的。
- 实现类能不能继承A类的时候,同时实现其他接口呢?
继承的父类,就好比是亲爸爸一样
实现的接口,就好比是干爹一样
可以继承一个类的同时,再实现多个接口,只不过,要把接口里面所有的抽象方法,全部实现。
- 实现类能不能继承一个抽象类的时候,同时实现其他接口呢?
实现类可以继承一个抽象类的同时,再实现其他多个接口,只不过要把里面所有的抽象方法全部重写。
- 实现类Zi,实现了一个接口,还继承了一个Fu类。假设在接口中有一个方法,父类中也有一个相同的方法。子类如何操作呢?
处理办法一:如果父类中的方法体,能满足当前业务的需求,在子类中可以不用重写。
处理办法二:如果父类中的方法体,不能满足当前业务的需求,需要在子类中重写。
1.4 接口的应用
接口多态:
当一个方法的参数是接口是,可以传递接口所有实现类的对象,这种方式称之为接口多态。
适配器设计模式:
如果一个接口中,有多个抽象方法,但是在实现类中,只需要用其中一个,就可以用适配器设计模式:
在接口跟实现类中间新建一个中间类(XxxAdapter适配器类),让这个适配器类去实现接口,对接口里面的所有的方法做空重写,让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。
注意:因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象
1.5 接口中的新增方法
1.5.1 JDK8以后接口中新增的方法
JDK8以后接口中可以定义有方法体的方法。
允许在接口中定义默认方法,需要用default修饰:
1 | public default 返回值类型 方法名(参数列表){ } |
- 默认方法不是抽象方法,不强制被重写,但如果重写,需要去掉default关键字。
- public可以省略,但是default不能省略。
- 如果实现了多个接口,且多个接口中存在名字相同的默认方法,子类就必须对该方法进行重写。
允许在接口中定义静态方法,需要用static修饰:
1 | public static 返回值类型 方法名(参数列表){ } |
- 静态方法只能通过接口名调用,不能通过类名或对象名调用。
- public可以省略,static不能省略。
1.5.2 JDK9以后接口中新增的方法
JDK9以前,接口中只能定义public方法,JDK9以后,还可以在接口中定义private方法。
普通私有方法:
1 | private 返回值类型 方法名(参数列表){ } |
- 普通私有方法给默认方法服务。
静态私有方法:
1 | private static 返回值类型 方法名(参数列表){ } |
- 静态私有方法给静态方法服务。
2.内部类
将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。
适用于:内部一个事物内部还有一个独立的事物,内部的事物脱离外部的事物无法独立使用。如汽车和发动机,发动机离开汽车就无意义。
2.1 内部类的分类
- 成员内部类,类定义在了成员位置 (类中方法外称为成员位置,无static修饰的内部类)
- 静态内部类,类定义在了成员位置 (类中方法外称为成员位置,有static修饰的内部类)
- 局部内部类,类定义在方法内
- 匿名内部类,没有名字的内部类,可以在方法中,也可以在类中方法外。
2.2 成员内部类
内部类的使用格式:
1 | 外部类.内部类。 // 访问内部类的类型都是用 外部类.内部类 |
获取成员内部类对象的两种方式:
方式一:外部直接创建成员内部类的对象
1 | 外部类.内部类 变量 = new 外部类().new 内部类(); |
方式二:在外部类中定义一个方法提供内部类的对象
1 | 方式一: |
编写成员内部类的注意点:
成员内部类可以被一些修饰符所修饰,比如: private,默认,protected,public,static等
在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量。
创建内部类对象时,对象中有一个隐含的
外部类名.this记录外部类对象的地址值。
内部类被private修饰,外界无法直接获取内部类的对象
内部类被static修饰是成员内部类中的特殊情况,叫做静态内部类。
内部类如果想要访问外部类的成员变量,外部类的变量必须用final修饰,JDK8以前必须手动写final,JDK8之后不需要手动写,JDK默认加上。
2.3 静态内部类
静态内部类是一种特殊的成员内部类,有static修饰,属于外部类本身的。
静态内部类与其他类的用法完全一样。只是访问的时候需要加上外部类.内部类。
静态内部类特点:
- 静态内部类可以直接访问外部类的静态成员。
- 静态内部类不可以直接访问外部类的非静态成员,如果要访问需要创建外部类的对象。
- 静态内部类中没有this关键字。
内部类的使用格式:
1 | 外部类.内部类。 |
静态内部类对象的创建格式:
1 | 外部类.内部类 变量 = new 外部类.内部类构造器; |
调用方法的格式:
- 调用非静态方法的格式:先创建对象,用对象调用
- 调用静态方法的格式:
外部类名.内部类名.方法名();
1 | // 外部类:Outer01 |
2.4 局部内部类
局部内部类 :定义在方法中的类。
1 | class 外部类名 { |
2.5 匿名内部类【重点】
内部类的简化写法。他是一个隐含了名字的内部类。
格式:
匿名内部类必须继承一个父类或者实现一个父接口。
1 | new 父类名或者接口名(){ |
包含了:继承或者实现关系、方法重写、创建对象。
本质上这个整体是父类的子类对象或接口的实现类对象。
什么时候用到匿名内部类 :
- 如果我们希望定义一个只要使用一次的类,就可考虑使用匿名内部类。
1 | interface Swim { |
- 通常在方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递。
1 | interface Swim { |
匿名内部类的特点:
- 定义一个没有名字的内部类
- 这个类实现了父类,或者父类接口
- 匿名内部类会创建这个没有名字的类的对象
十、常用API
API (Application Programming Interface) :应用程序编程接口,指的就是 JDK 中提供的各种功能的 Java类,这些类将底层的实现封装了起来,我们不需要关心这些类是如何实现的,只需要学习这些类如何使用即可。
API帮助文档:内置许多Java的API使用说明,包括成员变量、构造方法、成员方法等。打开资料中的API帮助文档就可以使用了。
1.Math类
Math类是位于 java.lang包下的一个类,包含执行基本数字运算的方法,我们可以使用Math类完成基本的数学运算。
不能直接通过new关键字去创建Math类的对象。可以直接通过类名去调用它内部的方法。
由于Math类被final修饰,所以不能被继承。
常用方法:
1 | public static int abs(int a) // 返回参数的绝对值 |
2.System类
System类位于java.lang包,可以获取系统当前时间、操作JVM虚拟机等。
不能直接通过new关键字去创建System类的对象。可以直接通过类名去调用它内部的方法。
由于System类被final修饰,所以不能被继承。
1 | public static long currentTimeMillis() // 获取当前时间所对应的毫秒值 |
currentTimeMillis
当前时间为0时区所对应的时间即就是英国格林尼治天文台旧址所在位置的1970年1月1号 00:00:00,中国的时间原点是1970年1月1号 08:00:00。
可以在一段程序开始和结束都获取一个时间,相减得到这段程序的运行时间,用于测试或比较代码效率。
arraycopy
public static void arraycopy( 数据源数组,起始索引,目的地数组,起始索引,拷贝个数):
- 如果数据源数组和目的地数组都是基本数据类型,那么两者的类型必须保持一致,否则会报错
- 在拷贝的时候需要考虑数组的长度,如果超出范围也会报错
- 如果数据源数组和目的地数组都是引用数据类型,那么子类类型可以赋值给父类类型(浅拷贝)
3.Runtime
Runtime表示 Java中运行时对象,可以获取到程序运行时涉及到的一些信息。
1 | public static Runtime getRuntime() //当前系统的运行环境对象 |
exec好玩命令
1 | //7.运行cmd命令 |
4.Object类
Object类是所有类的顶级父类,所有类都直接或者间接的继承自该类;该类所具备的方法其他所有类都继承了。
一般很少去主动的创建Object类的对象,调用其对应的方法。更多的是创建Object类的某个子类对象,然后通过子类对象调用Object类中的方法。
1 | public String toString() //返回该对象的字符串表示形式(可以看做是对象的内存地址值) |
toString
- 在通过输出语句输出一个对象时,默认调用的就是toString()方法。
- toString获取的是对象的地址值,输出地址值一般没有意义,我们可以通过重写toString方法去输出对应的成员变量信息(快捷键:atl + insert , 空白处 右键 -> Generate -> 选择toString)
- toString方法的作用:以良好的格式,更方便的展示对象中的属性值
- 一般情况下Jdk所提供的类都会重写Object类中的toString方法
equals
- 默认情况下equals方法比较的是对象的地址值
- 比较对象的地址值是没有意义的,因此一般情况下我们都会重写Object类中的equals方法
clone
对象克隆是指把A对象的属性值完全拷贝给B对象,也叫对象拷贝,对象复制。分为浅克隆和深克隆。
如果一个类想要被克隆,必须实现Cloneable接口。
不管对象内部的属性是基本 数据类型还是引用数据类型,都完全拷贝过来
基本数据类型拷贝过来的是具体的数据,引用数据类型拷贝过来的是地址值。
Object类默认的是浅克隆。

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

1 | public class ObjectDemo4 { |
- Cloneable
如果一个接口里面没有抽象方法,表示当前的接口是一个标记性接口。
一旦实现Cloneable接口,那么当前类的对象就可以被克隆,如果没有实现,当前类的对象就不能克隆。
使用
clone()方法时,必须实现Cloneable接口,否则会抛出CloneNotSupportedException异常。需要重写
clone()方法并将其访问修饰符改为public。默认的
clone()方法是浅拷贝,如果需要深拷贝,需要手动实现。
5.Objects类
Objects类位于 java.util包下,因此需要导包使用。
不能直接通过new关键字去创建Objects类的对象。可以直接通过类名去调用它内部的方法。
由于Objects类被final修饰,所以不能被继承。
1 | public static String toString(Object o) // 获取对象的字符串表现形式 |
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 | public BigInteger(int num, Random rnd) //获取随机大整数,范围:[0 ~ 2的num次方-1] |
- 如果BigInteger表示的数字没有超出long的范围,可以用静态方法获取。
- 如果BigInteger表示的超出long的范围,可以用构造方法获取。
- 对象一旦创建,BigInteger内部记录的值不能发生改变。
- 只要进行计算都会产生一个新的BigInteger对象
public BigInteger(String val, int radix)
- 字符串中的数字必须是整数
- 字符串中的数字必须要跟进制吻合。
public static BigInteger valueOf(long val)
- 能表示范围比较小,只能在long的取值范围之内,如果超出long的范围就不行了。
- 在内部对常用的数字: -16 ~ 16 进行了优化:提前把-16~16 先创建好BigInteger的对象,如果多次获取不会重新创建新的。
6.2 成员方法
1 | public BigInteger add(BigInteger val) //加法 |
6.3 底层存储方式
对于计算机而言,其实是没有 数据类型的概念的,都是0101010101,数据类型是编程语言自己规定的,所以在实际存储的时候,先把具体的数字变成二进制,每32个bit为一组,存储在数组中。

理论上,BigInteger能表示的最大数字为:42亿的21亿次方。但是这个数字太大了,很难达到,所以任何BigInteger没有上限。
7.BigDecimal类
BigDecimal类位于 java.math包下,需要导包使用。可以用来提高小数计算精度。
7.1 构造方法

1 | public static BigDecimal valueOf(long val) //静态方法获取BigInteger的对象,内部有优化 |
public static BigDecimal valueOf(long val)
如果传递的是0~10之间的整数(包含0和10),那么方法会返回已创建好的对象,不会新创建对象。
7.2 成员方法
1 | public BigDecimal add(BigDecimal value) // 加法运算 |
divide
如果使用BigDecimal类型的数据进行除法运算的时候,得到的结果是一个无限循环小数,那么就会报错:ArithmeticException。
我们可以用另一种divide方法解决:
1 | BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) |
小结:后期在进行两个数的除法运算的时候,我们常常使用的是可以设置取舍模式的divide方法。
7.3 底层存储方式
把 数据看成字符串,遍历得到里面的每一个字符,把这些字符在ASCII码表上的值,都存储到数组中。

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 | import java.util.Date; |
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底层会根据不同时区来获取不同的日历对象,把时间中的纪元、年、月、日、时、分、秒、星期等都放到一个数组中:

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 | static Set<string> getAvailableZoneIds() 获取Java中支持的所有时区 |
11.2 Instant 时间戳
1 | static Instant now() 获取当前时间的Instant对象(标准时间,不带时区) |
11.3 ZoneDateTime 带时区的时间
1 | static ZonedDateTime now() 获取当前时间的ZonedDateTime对象 |
11.4 DateTimeFormatter 用于时间的格式化和解析
1 | static DateTimeFormatter ofPattern(格式) 获取格式对象 |
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 | //1.获取当前时间的日历对象(包含 年月日) |
11.8 Duration 时间间隔(秒,纳,秒)
1 | // 本地日期时间对象。 |
11.9 Period 时间间隔(年,月,日)
1 | // 当前本地 年月日 |
11.10 ChronoUnit 时间间隔(所有单位)
1 | // 当前时间 |
时间类案例:计算活了多少天?用JDK7和JDK8两种方式实现。
1 | public class Test4 { |
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 | //1.把整数转成二进制,十六进制 |
- 在类型转换的时候,括号中的参数只能是数字不能是其他,否则代码会报错。
- 8种包装类当中,除了Character都有对应的parseXxx的方法,进行类型转换。
12.2 自动装箱与自动拆箱
基本类型与对应的包装类对象之间,来回转换的过程称为”装箱“与”拆箱“:
- 装箱:从基本类型转换为对应的包装类对象。
- 拆箱:从包装类对象转换为对应的基本类型。
从 Java 5(JDK 1.5)开始,基本类型与包装类的装箱、拆箱动作可以自动完成。
1 | Integer i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4); |
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
16public 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 | Integer i6 = Integer.valueOf(127); |
十一、正则表达式
如果需要校验某个字符串是否符合某些规则,如 长度不得超过15且只能包含英文大小写、数字、下划线 等,就可以使用正则表达式来实现。
1.字符类
- [abc]:代表a或者b,或者c字符中的一个。
- [^abc]:代表除a,b,c以外的任何字符。
- [a-z]:代表a-z的所有小写字符中的一个。
- [A-Z]:代表A-Z的所有大写字符中的一个。
- [0-9]:代表0-9之间的某一个数字字符。
- [a-zA-Z0-9]:代表a-z或者A-Z或者0-9之间的任意一个字符。
- [a-dm-p]:a 到 d 或 m 到 p之间的任意一个字符。
2.逻辑运算符
- &&:并且
- | :或者
- \ :转义字符
注意:
- 单个&仅仅代表‘&’这个字符,不是并且。
\在字符串中是转义字符,要表示单纯的\需要用\\。
3.预定义字符
.: 匹配任何字符。\d:任何数字[0-9]的简写;\D:任何非数字[^0-9]的简写;\s: 空白字符:[ \t\n\x0B\f\r] 的简写\S: 非空白字符:[^\s] 的简写\w:单词字符:[a-zA-Z_0-9]的简写\W:非单词字符:[^\w]
4.数量词
X?: 0次或1次X*: 0次到多次X+: 1次或多次X{n}: 恰好n次X{n,}: 至少n次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]) - 日期:01
09 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 | 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]"; |
5.爬虫
5.1 本地 数据爬取
Pattern:表示正则表达式。
Matcher:文本匹配器,作用按照正则表达式的规则去读取字符串,从头开始读取。在大串中去找符合匹配规则的子串。
1 | public static void main(String[] args) { |
5.2 网络 数据爬取
1 | /** |
如果需要根据正则爬取,可以将得到的字符串使用文本匹配器过滤。
5.3 按要求爬取
1 | // ?理解为前面的数据Java |
5.4 贪婪爬取和非贪婪爬取
1 | 只写+和表示贪婪匹配,如果在+和后面加问号表示非贪婪爬取 |
6.String类的方法中使用正则表达式
split方法中使用正则表达式:
1 | public String[] split(String regex) |
replaceAll方法中使用正则表达式:
1 | public String replaceAll(String regex,String newStr) |
7.正则表达式分组括号()
细节:如何识别组号?
只看左括号,不看右括号,按照左括号的顺序,从左往右,依次为第一组,第二组,第三组等等。
捕获分组就是把这一组数组捕获出来,再用一次,
1 | 正则内部使用:\\组号 |
例如:
1 | //判断一个字符串的开始字符和结束字符是否一致(只考虑一个字符)? |
7.1 非捕获分组
非捕获分组:分组之后不需要再用本组 数据,仅仅是把数据括起来。
1 | //非捕获分组:仅仅是把数据括起来。但不占用组号 |
8.忽略大小写的写法
1 | //(?i) :表示忽略后面数据的大小写 |
十二、数据结构和Lambda表达式
1.数据结构
1.1 查找算法
1.1.1 基本查找
基本思想:从数据结构线的一端开始,顺序扫描,依次将遍历到的结点与要查找的值相比较,若相等则表示查找成功;若遍历结束仍没有找到相同的,表示查找失败。
1 | //参数: |
1.1.2 二分查找
基本思想:也称为是折半查找,属于有序查找算法。用给定值先与中间结点比较。比较完之后有三种情况:
- 相等:说明找到了。
- 要查找的数据比中间节点小:说明要查找的数字在中间节点左边。
- 要查找的数据比中间节点大:说明要查找的数字在中间节点右边。
1 | public static int binarySearch(int[] arr, int number) { |
1.1.3 插值查找
将查找的点改进为如下:
mid=low+(key-a[low])/(a[high]-a[low])*(high-low),
这样,让mid值的变化更靠近关键字key,这样也就间接地减少了比较次数。
基本思想:基于二分查找算法,将查找点的选择改进为自适应选择,可以提高查找效率。当然,差值查找也属于有序查找。
- 数组中如果分布非常不均匀,那么插值查找未必是很合适的选择。
1.1.4 斐波那契查找
基本思想:也是二分查找的一种提升算法,通过运用黄金比例的概念在数列中选择查找点进行查找,提高查找效率。同样地,斐波那契查找也属于一种有序查找算法。
1 | public static int search(int[] arr, int key) { |
1.1.5 分块查找
- 需要把 数据分成N多小块,块与块之间不能有数据重复的交集。
- 给每一块创建对象单独存储到数组当中
- 查找数据的时候,先在数组查,当前数据属于哪一块
- 再到这一块中顺序查找
核心思想:块内无序,块间有序
1 | public class A03_BlockSearchDemo { |
1.1.6 哈希查找
哈希查找是分块查找的进阶版,适用于 数据一边添加一边查找的情况。
一般是数组 + 链表的结合体或者是数组+链表 + 红黑树的结合体。
基本思想:先计算出当前数据的哈希值,用哈希值跟数组的长度进行计算,计算出应存入的位置,再挂在数组的后面形成链表,如果挂的元素太多而且数组长度过长,我们也会把链表转化为红黑树,进一步提高效率。
1.1.7 树表查找
基本思想:二叉查找树是先对待查找的数据进行生成树,确保树的左分支的值小于右分支的值,然后在就行和每个节点的父节点比较大小,查找最适合的范围。 这个算法的查找效率很高,但是如果使用这种查找方法要首先创建树。
二叉查找树(BinarySearch Tree,也叫二叉搜索树,或称二叉排序树Binary Sort Tree),具有下列性质的二叉树:
- 任意节点左子树上所有的数据,均小于本身;
- 任意节点右子树上所有的数据,均大于本身;
二叉查找树性质:对二叉查找树进行中序遍历,即可得到有序的数列。
1.2 排序算法
1.2.1 选择排序

- 从0索引开始,跟后面的元素一一比较
- 小的放前面,大的放后面
- 第一次循环结束后,最小的数据已经确定
- 第二次循环从1索引开始以此类推
- 第三轮循环从2索引开始以此类推
- 第四轮循环从3索引开始以此类推。
1 | //arr是引用数据类型,形参和实参公用一个空间,改变形参arr的元素实参arr也会改变 |
1.2.2 冒泡排序

- 相邻的元素两两比较,大的放右边,小的放左边
- 第一轮比较完毕之后,最大值就已经确定,第二轮可以少循环一次,后面以此类推
- 如果数组中有n个 数据,总共我们只要执行n-1轮的代码就可以
1 | public static void bubbleSort(int[] arr) { |
1.2.3 插入排序

将0索引的元素到N索引的元素看做是有序的,把N+1索引的元素到最后一个当成是无序的。
遍历无序的数据,将遍历到的元素插入有序序列中适当的位置,如遇到相同数据,插在后面。
N的范围:0~最大索引
1 | public static void insertSort(int[] arr) { |
1.2.4 快速排序

- 从数列中挑出一个元素,一般都是左边第一个数字,称为 “基准数”;
- 创建两个指针,一个从前往后走,一个从后往前走。
- 先执行后面的指针,找出第一个比基准数小的数字
- 再执行前面的指针,找出第一个比基准数大的数字
- 交换两个指针指向的数字
- 直到两个指针相遇
- 将基准数跟指针指向位置的数字交换位置,称之为:基准数归位。
- 第一轮结束之后,基准数左边的数字都是比基准数小的,基准数右边的数字都是比基准数大的。
- 把基准数左边看做一个序列,把基准数右边看做一个序列,按照刚刚的规则递归排序
1 | public static void main(String[] args) { |
1.3 基本 数据结构
- 栈结构:先进后出。
- 队列结构:先进先出。
- 数组结构:查询快、增删慢。
- 链表结构:查询慢、增删快。
1.3.1 二叉树
二叉树的特点:
- 二叉树中,任意一个节点的度要小于等于2
- 节点:在树结构中,每一个元素称之为节点。
- 度:每一个节点的子节点数量称之为度。
二叉树结构图:

1.3.2 二叉查找树
二叉查找树的特点:
- 二叉查找树,又称二叉排序树或者二叉搜索树
- 每一个节点上最多有两个子节点
- 左子树上所有节点的值都小于根节点的值
- 右子树上所有节点的值都大于根节点的值
二叉查找树结构图:

二叉查找树添加节点规则:
- 小的存左边
- 大的存右边
- 一样的不存
1.3.3 平衡二叉树
平衡二叉树的特点:
- 二叉树左右两个子树的高度差不超过1
- 任意节点的左右两个子树都是一颗平衡二叉树
平衡二叉树旋转:
旋转触发时机
- 当添加一个节点之后,该树不再是一颗平衡二叉树。
左旋:就是将根节点的右侧往左拉,原先的右子节点变成新的父节点,并把多余的左子节点出让,给已经降级的根节点当右子节点。

右旋:就是将根节点的左侧往右拉,左子节点变成了新的父节点,并把多余的右子节点出让,给已经降级根节点当左子节点。

平衡二叉树和二叉查找树对比结构图

平衡二叉树旋转的四种情况:
左左
- 左左:当根节点左子树的左子树有节点插入,导致二叉树不平衡。
- 如何旋转:直接对整体进行右旋即可。

左右
- 左右:当根节点左子树的右子树有节点插入,导致二叉树不平衡。
- 如何旋转:先在左子树对应的节点位置进行左旋,在对整体进行右旋。

右右
- 右右:当根节点右子树的右子树有节点插入,导致二叉树不平衡。
- 如何旋转:直接对整体进行左旋即可。

右左
- 右左:当根节点右子树的左子树有节点插入,导致二叉树不平衡。
- 如何旋转:先在右子树对应的节点位置进行右旋,在对整体进行左旋。

1.3.4 红黑树
红黑树的特点:
- 平衡二叉B树。
- 每一个节点可以是红或者黑。
- 红黑树不是高度平衡的,它的平衡是通过 “自己的红黑规则” 进行实现的。
红黑树的红黑规则有哪些:
- 每一个节点或是红色的,或者是黑色的。
- 根节点必须是黑色。
- 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的。
- 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)。
- 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
红黑树添加节点的默认颜色:
- 添加节点时,默认为红色,效率高。
红黑树添加节点后如何保持红黑规则:

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 | Integer[] arr = {5, 6, 3, 5, 8, 4, 2, 4, 9}; |
3.Lambda表达式
3.1 函数式接口
函数式编程思想:就是忽略面向对象的复杂方法,强调做什么,而不是谁去做。
函数式接口:有且仅有一个抽象方法的接口叫做函数式接口,接口上可以加@FunctionalInterface注解。
Lambda表达式只能简化函数式接口的匿名内部类的写法。
简单来说,如果一个接口是函数式接口,创建这个接口的匿名实现类时可以不用关注这个接口的名字、接口内抽象方法的名字,只用关注抽象方法的形参和功能(包括返回值)即可。
省略规则:
使用Lambda表达式需要了解函数式接口匿名实现类的省略规则。
new Comparator<E>()、@Override、类的大括号都可以省略。- 参数类型可以省略。
- 如果只有一个参数,()也可以省略。
- 如果Lambda表达式只有一行,大括号、分号、return都可以省略,而且必须同时省略。
1 | Comparator<E>接口是一个函数式接口,只有一个抽象方法int compare(Integer o1, Integer o2),所以使用sort方法可以使用Lambda表达式简化: |
可以看到,使用Lambda可以不用关注接口名Comparator和抽象方法名compare,大大减少学习压力。











