Java基础

运行环境

jvm和跨平台

1.jvm(java虚拟机):java运行程序的假想计算机,主要用来运行java程序的
2.跨平台:java代码可以在不同的操作系统上运行(一次编写,到处运行)
 跨:跨越
 平台:操作系统 -> windows linux mac os  
     
3.关系:java程序想要在不同的操作系统上运行,实现跨平台,就需要安装不同版本的jvm

1698304141613.png

JDK和JRE

1.jdk:(Java Development Kit):java开发工具包,包含了jre
 javac 编译工具
 java 运行工具
 jdb  调试工具
 jhat 内存分析工具
…  
2.jre:(Java Runtime Environment):java运行环境,包含了jvm以及后面开发用到的核心类库
   
3.jdk和jre以及jvm的关系:
 jdk包含了jre,jre包含了jvm,所以我们只需要安装jdk即可

但是从jdk9开始jdk目录中就没有单独的jre目录了,因为jre作为一个运行时,里面不需要包含太多的东西浪费空间,降低运行效率,在jdk9的时候引用模块化的技术,让开发者能按照自己的应用创建一个最小的运行时(比如一个微服务的部署应用仅仅需要一个非常小的runtime,而不是像以前一样不管应用复杂还是简单,都需要一个近百兆的jre运行)这样提高了运行效率

jdk安装

1.下载:www.oracle.com
1698306463672.png

2.安装:双击 -> 选择安装路径(安装路径上不要有中文,不要有空格)

测试:
进入到jdk的bin路径下,打开对应dos命令窗口
输入javac(编译命令)和java(运行命令)

3.环境变量的配置

配置JAVA_HOME
1698308481783.png

Java第一个程序的开发

开发三步骤

1.编写:
 a.创建一个文本文档,将后缀名改成.java,变成一个java文件
 b.注意:我们需要将文件的后缀名显示出来  
 2.编译:
 a.命令:javac java文件名.java
 b.注意:javac会将java文件编译,生成一个.class文件(字节码文件),jvm运行只认class文件
 3.运行:
 a.命令:java class文件名(不需要带后缀名了)

编写HelloWorld

编写

1
2
3
4
5
public class Demo01HelloWorld{  
public static void main(String[] args){
System.out.println("HelloWorld");
}
}

编译:

1
javac java文件名.java

运行:

1
  java class文件名(不要带后缀名了)

注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.概述:对代码的解释说明
2.分类:
a.单行注释:
//注释内容

b.多行注释:
/*
注释内容
*/

c.文档注释:
/**
注释内容
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//单行注释 class后面的名字要和java文件名一致
public class Demo01HelloWorld{
/*
多行注释:
main是一个方法,是程序的入口,jvm运行程序要找main当入口
执行程序
*/
public static main(String[] args){
/**
文档注释:
下面的语句是输出语句
*/
System.out.println("HelloWorld");
}
}
1
2
3
4
5
1.文档注释作用:将来我们给别人一个开发好的类,如何让别人快速对我们写的代码了解呢?
我们的文档注释中的内容可以根据javadoc命令生成一个文档(API文档)
别人拿到这个文档,就能快速对此类以及类中实现的功能,进行快速了解

2.命令:javadoc -d 要生成的文件夹名字 -author -version 文件名.java

关键字

1
2
1.关键字:java提前定义好的,具有特殊含义的小写单词
2.怎么记:不用提前都背下来,关键字在高级记事本中颜色特殊,学到哪里记到哪里

1698317329001.png

字符编码

1
2
3
4
5
6
7
8
9
10
11
12
13
1.编码:保存数据的过程就是编码的过程
2.解码:读数据的过程就是解码的过程
3.注意:
a.编码和解码遵守的编码规范必须是一样的
b.常见的两个编码规范:
GBK:专门为我们中文所设计的编码
ANSI代表的是GBK

一个中文汉字在GBK中占2个字节

UTF-8:一个中文汉字在UTF-8中占3个字节

c.dos命令窗口默认编码:GBK

变量

概述

在代码的运行过程中,值可以被重新赋值改变。

声明和赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
a.数据类型 变量名 = 值(可以是字面量、变量、常量或表达式的结果);

b.数据类型 变量名;
变量名 = 值;

c.连续定义三个相同类型的变量
数据类型 变量名1,变量名2,变量名3;
变量名1 = 值;
变量名2 = 值;
变量名3 = 值;

比如:int i,j,k;
i = 10;
j = 20;
k = 30;

数据类型 变量名1 = 值,变量名2 = 值,变量名3 = 值;
比如: int i = 10,j = 20,k = 30;

数据类型

1.变量的数据类型:

  • 基本数据类型:4类8种
    整型:byte short int long
    浮点型:float double
    字符型:char
    布尔型:boolean

  • 引用数据类型: 类 数组 接口 枚举 注解

基本数据类型 关键字 内存占用 取值范围
字节型 byte 1个字节 -128 至 127 定义byte变量时超出范围,废了
短整型 short 2个字节 -32768 至 32767
整型 int(默认) 4个字节 -2^31 至 2^31-1 正负21个亿
-2147483648——2147483647
长整型 long 8个字节 -2^63^ 至 2^63^-1 19位数字
-9223372036854775808到9223372036854775807
单精度浮点数 float 4个字节 1.4013E-45 至 3.4028E+38
双精度浮点数 double(默认) 8个字节 4.9E-324 至 1.7977E+308
字符型 char 2个字节 0 至 2^16^-1
布尔类型 boolean 1个字节 true,false(可以做判断条件使用)
1
2
3
4
5
6
7
8
9
10
11
注意:
a.字符串不属于基本数据类型,属于引用数据类型,用String表示
String是一个类,只不过字符串在定义的时候可以和基本数据类型格式一样

floatdouble的区别:
a.float的小数位只有23位二进制,能表示的最大十进制为223次方(8388608),是7位数,所以float型代表的小数,小数位能表示7

b.double的小数位只有52位二进制,能表示的最大十进制为(4 503 599 627 370 496),是16位数,所以double型代表的小数,小数位能表示出16


切记:将来开发不要用float或者double直接参与运算,因为直接参与运算会有精度损失问题;在需要高精度计算的场合,可以使用Java的`BigDecimal`类,它提供了任意精度的小数运算。
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
public class Demo03Var{
public static void main(String[] args){
//byte
byte num1 = 100;
System.out.println(num1);

//short
short num2 = 1000;
num2 = 1001;
System.out.println(num2);

//int 整数的默认类型
int num3 = 10000;
num3 = 1;
System.out.println(num3);

//long -> 定义long型的变量后面加个L
long num4 = 10L;
System.out.println(num4);

//float -> 定义float型变量的时候后面加个F
float num5 = 2.5F;
System.out.println(num5);

//double -> 小数的默认类型
double num6 = 2.5;
System.out.println(num6);

//char
char num7 = 'A';
System.out.println(num7);

//boolean
boolean num8 = true;
boolean num9 = false;

/*
num9 = false
num8 = num9 -> 将num9的值赋值给num8 -> 相当于num8 = false
*/
num8 = num9;
System.out.println(num8);

//String -> 是一个引用数据类型,属于类的一种,但是定义和基本类型一致
String name = "金莲";
System.out.println(name);
}
}

精度损失

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class FloatPrecision {
public static void main(String[] args) {
double sum = 0.1 + 0.2;
System.out.println("The sum is: " + sum);
}
}
/**
The sum is: 0.30000000000000004

转换为二进制表示时,它们会变成无限循环小数:
- 0.1 = 0.0001100110011001100110011...(二进制)
- 0.2 = 0.00110011001100110011001100...(二进制)

这些无限循环小数不能在有限的二进制位数内精确表示,因此计算机必须对它们进行近似。


在不截断的情况下,理论上的计算应该是: 0.1+0.2=0.3

然而,由于0.1和0.2在二进制中的表示是近似的,所以实际存储在计算机中的值也是近似的。这意味着当你执行`0.1 + 0.2`的计算时,你实际上是在计算两个近似值的和。
*/

常量

1.概述:在代码的运行过程中,值不会发生改变的数据

2.常量的声明
在Java中声明常量有几种方式:

  • 使用final关键字:这是声明常量最常用的方法。当你在变量前加上final关键字时,意味着这个变量的值在初始化后就不能再被改变。
1
2
3
public class Constants {
public static final int MAX_USERS = 100;
}

在这个例子中,MAX_USERS是一个常量,它的值被设置为100,并且不能被修改。

  • 使用static关键字:静态变量是属于类的,而不是属于类的某个特定对象的。当你声明一个static final变量时,这个变量就成为了一个常量。
1
2
3
public class Constants {
public static final double PI = 3.14159;
}

在这个例子中,PI是一个常量,它的值被设置为3.14159。

  • 使用枚举类型:枚举(Enum)是一种特殊的类,它可以用来定义一组常量。
1
2
3
public enum Days {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}

在这个例子中,Days枚举定义了一周的七天作为常量。

  • 使用类名:有时候,我们也会通过类名来引用常量,这样可以避免使用static final
1
int maxUsers = Constants.MAX_USERS;

在这个例子中,我们通过Constants类名来引用MAX_USERS常量。

记住,常量应该总是大写,单词间用下划线分隔,这是Java中约定俗成的命名规范。

字面量

字面量(Literal)在编程中指的是直接在代码中表示固定值的符号或字符串。字面量是源代码的一部分,它表示一个固定的数据值,这个值在编译时就已经确定,并且在程序执行过程中不会改变。字面量可以用于初始化变量,也可以直接在表达式中使用。

以下是一些常见的字面量类型:

  1. 数值字面量:表示整数、浮点数、十六进制数等。

    • 整数:123-4560777(八进制), 0x1A(十六进制)
    • 浮点数:3.14-0.0011e10(科学记数法)
  2. 布尔字面量:表示逻辑值。

    • true 和 false
  3. 字符字面量:表示单个字符。

    • 'A''\n'(换行符), '\t'(制表符)
  4. 字符串字面量:表示文本字符串。

    • "Hello, World!""123""\"Quoted text\""
  5. 空值字面量:表示没有值或空引用。

    • null
  6. 复合字面量:在某些编程语言中,如JSON或数组。

    • JSON对象:{"name": "Kimi", "age": 30}
    • 数组:[1, 2, 3]

标识符

1.概述:给类,方法,变量取的名字
2.注意:
a.硬性规定(必须遵守)
标识符可以包含”英文字母”,”数字”,”$和_”
标识符不能以数字开头 int i1 = 100(正确) int 1i = 100(错误)
标识符不能是关键字 int static = 100(错误) int public = 100(错误)

b.软性建议(可遵守可不遵守,但是建议遵守)
给类取名字:遵循大驼峰式 -> 每个单词首字母大写
给方法和变量取名字:遵循小驼峰式 -> 从第二个单词开始往后首字母大写
见名知意

附:package取名字规范:
公司域名倒着写,例如com.example

数据类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.什么时候发生类型转换:
a.等号左右两边类型不一致
b.不同类型的数据做运算

2.分类:
a.自动类型转换
将取值范围小的数据类型赋值给取值范围大的数据类型 -> 小自动转大
取值范围小的数据类型和取值范围大的数据类型数据做运算 -> 小自动转大

b.强制类型转换
当将取值范围大的数据类型赋值给取值范围小的数据类型 -> 需要强转

3.基本类型中按照取值范围从小到大排序:
byte,short,char -> int -> long -> float -> double

1.自动类型转换

1
2
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
public class Demo09DataType{
public static void main(String[] args){
/*
等号右边是整数,整数默认类型为int
等号左边是long型的变量

将取值范围小的数据类型赋值给取值范围大的数据类型,发生了自动类型转换
*/
long num1 = 100;
System.out.println(num1);


int i = 10;
double b = 2.5;

/*
double = int+double
double = double+double
int自动提升为了double,发生了自动类型转换
*/
double sum = i+b;
System.out.println(sum);
}
}

2.强制类型转换

1
2
3
4
5
1.将取值范围大的数据类型赋值给取值范围小的数据类型
取值范围小的数据类型 变量名 = 取值范围大的数据类型 -> 需要强转

2.怎么强转:
取值范围小的数据类型 变量名 = (取值范围小的数据类型)取值范围大的数据类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Demo10DataType{
public static void main(String[] args){
/*
等号右边的数据是小数,小数默认类型为double
等号左边的变量是float型

将取值范围大的赋值给取值范围小 -> 报错,需要强转
*/
//float num1 = 2.5;
//float num1 = (float)2.5;
float num1 = 2.5F;
System.out.println(num1);

}
}

3.强转的注意事项

1
2
3
4
5
6
7
1.不要随意写成强转的格式,因为会有精度损失问题以及数据溢出现象,除非没有办法
2.byte,short定义的时候如果等号右边是整数常量,如果不超出byteshort的范围,不需要我们自己强转,jvm自动转型

byte,short如果等号右边有变量参与,byteshort自动提升为int,然后结果再次赋值给byte或者short的变量,需要我们自己手动强转


3.char类型数据如果参与运算,会自动提升为int型,如果char类型的字符提升为int型会去ASCII码表(美国标准交换代码)范围内去查询字符对应的int值,如果在ASCII码表范围内没有对应的int值,回去unicode码表(万国码)中找
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
public class Demo11DataType{
public static void main(String[] args){
//精度损失
int i = (int)2.9;
System.out.println(i);

/*
数据溢出
int型占内存4个字节,4个字节变成二进制是32位

100个亿: 10 0101 0100 0000 1011 1110 0100 0000 0000 -> 34位二进制

100个亿的二进制位比int型的二进制位多出来2位,此时干掉最前面的2位

101 0100 0000 1011 1110 0100 0000 0000

101 0100 0000 1011 1110 0100 0000 0000->1410065408
*/
byte k=(byte) 128;
System.out.println(k); //-128 (进制转换和补码)
// 强转为byte类型,取8位,10000000,这是原码,由于负数,原码和补码不一样,需要进行反码(01111111)+1,得到补码10000000。

int j = (int)10000000000L;
System.out.println(j);//1410065408

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

byte b = 10;
System.out.println(b);

b = (byte)(b+1);
System.out.println(b);

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

char c = '中';
System.out.println(c+0);//20013
}
}

进制的转换

十进制 二进制 八进制 十六进制
0 0 0 0
1 1 1 1
2 10 2 2
3 11 3 3
4 100 4 4
5 101 5 5
6 110 6 6
7 111 7 7
8 1000 10 8
9 1001 11 9
10 1010 12 a或A
11 1011 13 b或B
12 1100 14 c或C
13 1101 15 d或D
14 1110 16 e或E
15 1111 17 f或F
16 10000 20 10

十进制转成二进制

1
辗转相除法 -> 循环除以2,取余数

1698489992624.png

二进制转成十进制

1
8421规则

1698490196480.png

二进制转成八进制

1
将二进制数分开  (3位为一组)

1698490532286.png

二进制转成十六进制

1
将二进制数分组-> 4位为一组

1698490908576.png

位运算

符号 描述 运算规则
& 两个位都为1时,结果才为1
| 两个位都为0时,结果才为0
^ 异或 两个位相同为0,相异为1
~ 取反 0变1,1变0
<< 左移 各二进位全部左移若干位,高位丢弃,低位补0
>> 右移 各二进位全部右移若干位,高位补0或符号位补齐
>>> 无符号右移
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 1代表true   0代表false  

我们要知道计算机在存储数据的时候都是存储的数据的补码,计算也是用的数据的补码
但是我们最终看到的结果是原码换算出来的

原码 反码 补码

正数二进制最高位为0; 负数二进制最高位为1

如果是正数 原码 反码 补码 一致
比如:5的原码 反码 补码一致:
0000 0000 0000 0000 0000 0000 0000 0101 -> 因为是正数,二进制最高位为0

如果是负数,原码 反码 补码不一样了
反码是原码的基础上最高位不变,剩下的01互换
补码是在反码的基础上+1

比如:-9
原码: 1000 0000 0000 0000 0000 0000 0000 1001
反码: 1111 1111 1111 1111 1111 1111 1111 0110
补码: 1111 1111 1111 1111 1111 1111 1111 0111

左移:<<

运算规则:左移几位就相当于乘以2的几次方

注意: 当左移的位数n超过该数据类型的总位数时,相当于左移(n-总位数)位,溢出会回绕

1
2
2<<2   结果等于8
快速算法: 2*(22次方)

1698492698243.png

1
2
-2<<2  等于-8
快速算法: -2*(22次方)

1698493024906.png

1
2147483647<<2; 结果等于 -4,产生回绕

image.png

右移:>>

快速运算:类似于除以2的n次,如果不能整除,向下取整

1
2
3
9>>2  等于2

快速算法: 9除以(22次方)

1698493649466.png

1
2
3
4
-9>>2   等于-3

快速算法: -9除以(22次方)
负数右移,左边补1

1698493979532.png

无符号右移:>>>

运算规则:往右移动后,左边空出来的位直接补0,不管最高位是0还是1空出来的都拿0补

正数:和右移一样

9>>>2 等于2

负数:右边移出去几位,左边补几个0,结果变为正数

-9>>>2
结果为:1073741821 (正数原码和补码一样)   

笔试题: 8>>>32位 -> 相当于没有移动还是8

8>>>34位 -> 相当于往右移动2位

运算符

算数运算符

符号 说明
+ 加法
- 减法
* 乘法
/ 除法
如果符号前后都是整数,结果取整数部分
如果符号前后有一个为小数,结果就是正常小数
% 模,取余数部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Demo01Arithmetic {
public static void main(String[] args) {
int i = 10;
int j = 3;
int add = i+j;//推荐使用
System.out.println(add);//13
System.out.println(i+j);//13

int sub = i-j;
System.out.println(sub);//7

int mul = i*j;
System.out.println(mul);//30

int div = i/j;
System.out.println(div);//3

int mo = i%j;
System.out.println(mo);//1
}
}
1
2
3
4
+:
1.运算
2.字符串拼接:任何类型的数据遇到字符串都会变成字符串,此时+就不再是运算了,而是字符串拼接,将内容直接往后拼接

1
2
3
4
5
6
7
8
9
10
11
public class Demo02Arithmetic {
public static void main(String[] args) {
int i = 10;
int j = 3;
System.out.println(i+j+"");//13
System.out.println(i+j+""+1);//131
System.out.println(i+""+j);//103

System.out.println("i和j相加只和为:"+(i+j));
}
}

自增自减运算符(也算算数运算符的一种)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.格式:
变量++ -> 后自加
++变量 -> 前自加
变量-- -> 后自减
--变量 -> 前自减

自增和自减只变化1

2.使用:
a.单独使用: ++ -- 单独为一句,没有和其他的语句掺和使用
i++;

符号前在在后都是先运算

b.混合使用: ++ -- 和其他的语句掺和使用了(比如:输出语句,赋值语句)
符号在前:先运算,在使用运算后的值
符号在后:先使用原值,使用完毕之后,自身再运算

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
public class Demo03Arithmetic {
public static void main(String[] args) {
int i = 10;
//i++;
++i;
System.out.println("i = " + i); //11

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

int j = 100;
int result01 = ++j;
System.out.println("result01 = " + result01);//101
System.out.println(j);//101

System.out.println("==================");
int k = 10;
int result02 = k++;
System.out.println("result02 = " + result02); //10
System.out.println(k); //11

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

int z = 100;
System.out.println(z++); //100
System.out.println(z); //101

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

int c = 10;
c = c++;//这种写法是无效的,开发不会有这种写法
System.out.println(c);//10
System.out.println(c);//10

/*
int temp = c; // 保存当前的 c 值(10)
c = c + 1; // c 自增为 11
c = temp; // 将 temp 的值(10)重新赋值给 c
*/
}
}

赋值运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1.基本赋值运算符:
= -> 先看等号右边的,再将右边的数据赋值给等号左边的变量

2.复合赋值运算符:
+=:
int i = 10;
i+=2 -> i = i+2


-=
*=
/= : 取整数部分
%= : 取余数部分

3.注意:byte short 遇到复合赋值运算符,jvm会自动转型
1
2
3
4
5
6
7
public class Demo01Assignment {
public static void main(String[] args) {
int i = 10;
i+=2;//i = i+2
System.out.println(i);
}
}
1
2
3
4
5
6
7
8
public class Demo02Assignment {
public static void main(String[] args) {
byte b = 10;
//b = (byte)(b + 1);
b+=1;//b = b+1
System.out.println(b);
}
}

关系运算符(比较运算符)

1
2
1.结果:boolean型 -> 要么是true,要么是false
2.作用:做条件判断使用
符号 说明
== 如果符号前后相等为true;否则为false
> 如果符号前的数据大于符号后的数据为true,否则为false
< 如果符号前的数据小于符号后的数据为true,否则为false
>= 如果符号前的数据大于或者等于符号后的数据为true,否则为false
<= 如果符号前的数据小于或者等于符号后的数据为true,否则为false
!= 如果符号前后不相等为true;否则为false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Demo01Compare {
public static void main(String[] args) {
int i = 10;
int j = 20;

boolean result01 = i == j;
System.out.println("result01 = " + result01);//false
System.out.println(i>j);//false
System.out.println(i<j);//true
System.out.println(i>=j);//false
System.out.println(i<=j);//true
System.out.println(i!=j);//true

}
}

逻辑运算符

1
2
1.作用:连接多个boolean结果的
2.结果:boolean型结果
符号 说明
&&(与,并且) 有假则假,符号前后有一个结果为false,整体就是false
||(或者) 有真则真,符号前后有一个结果为true,整体就是true
!(非,取反) 不是true,就是false;不是false,就是true
^(异或) 符号前后结果一样为false;不一样为true
true^true -> false
true^false -> true
false^true -> true
false^false -> false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Demo01Logic {
public static void main(String[] args) {
int i = 10;
int j = 20;
int k = 10;

boolean result01 = (i>j) && (i==k);
System.out.println("result01 = " + result01);//false

boolean result02 = (i>j) || (i==k);
System.out.println("result02 = " + result02);//true

boolean result03 = (i>j) ^ (i==k);//false ^ true
System.out.println("result03 = " + result03);//true

boolean result04 = !(i>j) ;
System.out.println("result04 = " + result04);//true
}
}

符号 说明
& 1.单与,如果前后都是布尔型,有假则假,但是如果符号前为false,符号后的判断会继续执行
2.如果该符号前后都是数字,看做是位运算符
&& 1.双与,有假则假,但是有短路效果,如果符号前为false,符号后的判断就不会执行了
| 1.单或,如果前后都是布尔型,有真则真,但是如果符号前为true,符号后的判断会继续执行
2.如果该符号前后都是数字,看做是位运算符
|| 1.双或,有真则真,但是有短路效果,如果符号前为true,符号后的判断就不会执行了

三元运算符

1
2
3
4
5
1.格式:
boolean表达式?表达式1:表达式2

2.执行流程:
先判断,如果是true,就走?后面的表达式1,否则就走:后面的表达式2

练习1

1
需求:小明考完试了,判断小明的分数是否及格,返回结果
1
2
3
4
5
6
7
8
public class Demo01Ternary {
public static void main(String[] args) {
//定义一个变量,表示小明的分数
int score = 60;
String result = score>=60?"及格":"不及格";
System.out.println("result = " + result);
}
}

练习2

1
有两个老人,年龄分别为70  80  求出两个老人的最高年龄
1
2
3
4
5
6
7
8
9
public class Demo02Ternary {
public static void main(String[] args) {
int old1 = 70;
int old2 = 80;

int max = old1>old2?old1:old2;
System.out.println("max = " + max);
}
}

练习3

1
有三个老人,年龄分别为70  80  60  求出三个老人的最高年龄
1
2
3
4
5
6
7
8
9
10
11
12
public class Demo03Ternary {
public static void main(String[] args) {
int old1 = 70;
int old2 = 80;
int old3 = 60;

int temp = old1>old2?old1:old2;

int max = temp>old3?temp:old3;
System.out.println("max = " + max);
}
}

流程控制

流程控制语句是编程中的重要组成部分,用于控制程序执行的顺序和逻辑分支。通常,流程控制可以分为三大类:

  1. 顺序控制(Sequential Control):

    • 程序按照代码的先后顺序逐行执行,不发生跳转或分支。这是最基本的控制流。
  2. 选择控制(Selection Control):

    • 根据条件判断,选择不同的路径执行。这类控制流用于处理条件分支,包括:
      • if 语句:根据给定条件判断是否执行某个代码块,可以是单个条件,也可以有多个条件和分支,如 if-elseelse if 结构。
      • switch 语句:用于在多个分支中选择一个进行执行,通常用于处理多个离散值的情况(如整数、字符等)。它是一个多路分支结构。
      • if 和 switch 是同类的:它们都是选择控制结构,因为它们都根据某个条件来决定执行哪个分支,虽然语法不同,但它们的功能相似,都是根据条件做选择。
  3. 循环控制(Loop Control):

    • 根据条件重复执行某段代码,直到满足特定的条件为止。常见的循环控制结构包括:
      • for 循环:按固定次数或范围重复执行代码。
      • while 循环:根据条件重复执行代码,只要条件为真就继续循环。
      • do-while 循环:类似于 while 循环,但至少执行一次,因为条件是在循环体之后检查的。

键盘录入_Scanner

1
2
3
4
5
6
7
8
9
10
11
12
13
1.概述:是java定义好的一个类
2.作用:将数据通过键盘录入的形式放到代码中参与运行
3.位置:java.util
4.使用:
a.导包:通过导包找到要使用的类 -> 导包位置:类上
import java.util.Scanner -> 导入的是哪个包下的哪个类

b.创建对象
Scanner 变量名 = new Scanner(System.in);

c.调用方法,实现键盘录入
变量名.nextInt() 输入整数int型的
变量名.next() 输入字符串 String型的
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 class Demo01Scanner {
public static void main(String[] args) {
//创建对象
Scanner sc = new Scanner(System.in);
//录入int型整数
int data1 = sc.nextInt();
System.out.println("data1 = " + data1);

//录入String型字符串
String data2 = sc.next();
System.out.println("data2 = " + data2);
}
}

====================================================
public class Demo02Scanner {
public static void main(String[] args) {
//创建对象
Scanner sc = new Scanner(System.in);
//录入int型整数
int old1 = sc.nextInt();
int old2 = sc.nextInt();
int old3 = sc.nextInt();

int temp = old1>old2?old1:old2;
int max = temp>old3?temp:old3;
System.out.println(max);
}
}

1
2
变量名.next():录入字符串 -> 遇到空格和回车就结束录入了
变量名.nextLine():录入字符串 -> 遇到回车就结束录入了

Random随机数

1
2
3
4
5
6
7
8
9
1.概述:java自带的一个类
2.作用:可以在指定的范围内随机一个整数
3.位置:java.util
4.使用:
a.导包:import java.util.Random
b.创建对象:
Random 变量名 = new Random()
c.调用方法,生成随机数:
变量名.nextInt() -> 在int的取值范围内随机一个整数
1
2
3
4
5
6
7
8
public class Demo01Random {
public static void main(String[] args) {
//创建对象
Random rd = new Random();
int data = rd.nextInt();
System.out.println("data = " + data);
}
}
1
2
3
4
5
6
7
在指定范围内随机一个数:
nextInt(int bound) -> 在0-(bound-1)

a.nextInt(10) -> 0-9
b.在1-10之间随机一个数: nextInt(10)+1 -> (0-9)+1 -> 1-10
c.在1-100之间随机一个数:nextInt(100)+1 -> (0-99)+1 -> 1-100
d.在100-999之间随机一个数: nextInt(900)+100 -> (0-899)+100 -> 100-999
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Demo02Random {
public static void main(String[] args) {
//创建对象
Random rd = new Random();
//在1-100之间随机
int data1 = rd.nextInt(100)+1;
System.out.println("data1 = " + data1);

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

//在100-999之间随机一个数
int data2 = rd.nextInt(900)+100;
System.out.println("data2 = " + data2);
}
}

选择控制

switch(选择语句)

1
switch  if  循环必须要先掌握定义格式,然后掌握执行流程(带一个数进去,根据执行流程观察值的变化)
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
25
26
27
28
29
30
31
32
33
1.格式:
switch(变量){
case 常量值1:
执行语句1;
break;

case 常量值2:
执行语句2;
break;

case 常量值3:
执行语句3;
break;

case 常量值4:
执行语句4;
break;
...
default:
执行语句n;
break;
}

2.执行流程:
用变量接收的值和下面case后面的常量值匹配,匹配上哪个case就执行哪个case对应的执行语句
如果以上所有case都没有匹配上,就走default对应的执行语句n

3.break关键字:代表的是结束switch语句

4.注意:switch能匹配什么类型的数据:
byte short int char 枚举类型 String类型


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
public class Demo03Switch {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int month = sc.nextInt();
switch(month){
case 12:
case 1:
case 2:
System.out.println("冬季");
break;

case 3:
case 4:
case 5:
System.out.println("春季");
break;

case 6:
case 7:
case 8:
System.out.println("夏季");
break;

case 9:
case 10:
case 11:
System.out.println("秋季");
break;
default:
System.out.println("什么情况,你家有这个月份?");
}
}
}

case的穿透性

1
1.如果没有break,就会出现case的穿透性,程序就一直往下穿透执行,直到遇到了break或者switch代码执行完毕了,就停止了

分支语句

1.单分支
1
2
3
4
5
6
7
8
9
10
1.格式:
if(boolean表达式){
执行语句;
}

2.执行流程:
先走if后面的boolean表达式,如果是true,就走if后面大括号中的执行语句,否则就不走

3.注意:
if后面跟的是boolean表达式,只要是结果为boolean型的,都可以放在小括号中,哪怕直接写一个true或者false
2.双分支
1
2
3
4
5
6
7
8
9
1.格式:
if(boolean表达式){
执行语句1;
}else{
执行语句2;
}
2.执行流程:
a.先走if后面的boolean表达式,如果是true,就走if后面的执行语句1
b.否则就走else后面的执行语句2

奇偶数

1
2
3
4
5
6
7
8
9
10
11
public class Demo03IfElse {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int data = sc.nextInt();
if (data%2==0){
System.out.println("偶数");
}else{
System.out.println("奇数");
}
}
}

闰年

1
2
3
4
5
6
7
8
9
案例:从键盘输入年份,请输出该年的2月份的总天数。闰年2月份29天,平年28天。
闰年:
a.能被4整除,但是不能被100整除 year%4==0 && year%100!=0
b.或者能直接被400整除 year%400==0

步骤:
1.创建Scanner对象,调用nextInt键盘录入一个年份 year
2.判断(year%4==0 && year%100!=0) || (year%400==0)
3.如果条件成立,就输出闰年229天,否则输出平年228
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Demo06IfElse {
public static void main(String[] args) {
//1.创建Scanner对象,调用nextInt键盘录入一个年份 year
Scanner scanner = new Scanner(System.in);
int year = scanner.nextInt();
//2.判断(year%4==0 && year%100!=0) || (year%400==0)
if ((year%4==0 && year%100!=0) || (year%400==0)){
//3.如果条件成立,就输出闰年2月29天,否则输出平年2月28天
System.out.println("闰年2月29天");
}else{
System.out.println("平年2月28天");
}
}
}
3.多分支
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1.格式:
if(boolean表达式){
执行语句1
}else if(boolean表达式){
执行语句2
}else if(boolean表达式){
执行语句3
}...else{
执行语句n
}

2.执行流程:
if开始往下挨个判断,哪个if判断结果为true,就走哪个if对应的执行语句,如果以上所有的判断都是false,就走else对应的执行语句n

3.使用场景:2种情况以上的判断

switch和if的区别:debug

1.switch:会直接跳到相匹配的case

2.if:从上到下挨个判断 -> 实际开发主要用if做判断,灵活

循环控制

1
2
什么时候使用循环语句:
当我们发现一件事或者一段代码在反复执行,我们就可以考虑使用循环语句了

1.for循环

1
2
3
4
5
6
7
8
9
10
1.格式:
for(初始化变量;比较;步进表达式){
循环语句 -> 哪段代码循环执行,就将哪段代码放到此处
}

2.执行流程:
a.先走初始化变量
b.比较,如果是true,走循环语句,走步进表达式(初始化的变量的值进行变化)
c.再比较,如果还是true,继续走循环语句,走步进表达式
d.再比较,直到比较为false,循环结束了

2.while循环

1
2
3
4
5
6
7
8
9
10
11
12
1.格式:
初始化变量;
while(比较){
循环语句;
步进表达式
}

2.执行流程:
a.初始化变量
b.比较,如果是true,就走循环语句,走步进表达式
c.再比较,如果还是true,继续走循环语句,继续走步进表达式
d.再比较,直到比较为false,循环结束

3.do…while循环(了解)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1.格式:
初始化变量;
do{
循环语句;
步进表达式
}while(比较);

2.执行流程:
a.初始化变量
b.走循环语句
c.走步进表达式
d.判断,如果是true,继续循环,直到比较为false,循环结束

3.特点:
至少循环一次

4.循环控制关键字

1
2
3
4
5
6
1.break:
a.在switch中代表结束switch语句
b.在循环中代表结束循环

2.continue:
结束当前本次循环,直接进入下一次循环,直到条件为false为止
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo01BreakAndContinue {
public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
if (i==3){
//结束循环
//break;
//结束本次循环,进入下一次循环
continue;
}
System.out.println("我爱java"+i);
}
}
}

5.死循环

1
2
3
4
5
1.概述:
一直循环
2.什么条件下一直循环:
比较条件一直是true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Demo01Endless {
public static void main(String[] args) {
int count = 0;
for (int i = 0; i < 10;) {
count++;
System.out.println("我爱java"+count);
}


/* while(true){
count++;
System.out.println("我爱java"+count);
}*/


}
}

6.嵌套循环

1
2
3
1.概述:循环中还有循环
2.执行流程:
先执行外层循环,再进入内层循环,内层循环就一直循环,直到内层循环结束,外层循环进入下一次循环,直到外层循环都结束了,整体结束

九九乘法表

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {  
public static void main(String[] args) {
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++) {
System.out.print(i+"*"+j+"="+(i*j)+"\t");

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

}
}

猜数字小游戏

1
2
3
4
5
6
1.创建Scanner和Random对象
2.调用Random中的nextInt(100)+11-100之间随机一个数 rdNumber
3.调用Scanner中的nextInt()方法 键盘录入一个要猜的数 scNumber
4.如果scNumber大于rdNumber,证明猜大了
5.如果scNumber小于rdNumber,证明猜小了
6.如果scNumber等于rdNumber,证明猜中了
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
public class Demo01Exam {
public static void main(String[] args) {
//1.创建Scanner和Random对象
Scanner sc = new Scanner(System.in);
Random rd = new Random();
//2.调用Random中的nextInt(100)+1在1-100之间随机一个数 rdNumber
int rdNumber = rd.nextInt(100) + 1;
while(true){
//3.调用Scanner中的nextInt()方法 键盘录入一个要猜的数 scNumber
System.out.println("请您猜一个数:");
int scNumber = sc.nextInt();
//4.如果scNumber大于rdNumber,证明猜大了
if (scNumber>rdNumber){
System.out.println("对不起,您猜大了!");
}else if (scNumber<rdNumber){
//5.如果scNumber小于rdNumber,证明猜小了
System.out.println("对不起,您猜小了!");
}else{
//6.如果scNumber等于rdNumber,证明猜中了
System.out.println("恭喜您,猜中了!");
break;
}
}

}
}

数组

数组的定义

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
1.问题:想将一个数据保存起来,我们可以使用变量,但是变量一次只能存储一个数据,所以我们想能不能一次存多个数据

2.数组概述:是一个容器,数组本身属于引用数据类型

3.作用:一次存储多个数据

4.特点:
a.既可以存储基本类型的数据,还能存储引用类型的数据
b.定长(定义数组时长度为多长,最多就能存多少个数据)

5.定义:
a.动态初始化:
数据类型[] 数组名 = new 数据类型[长度]
数据类型 数组名[] = new 数据类型[长度]

各部分解释:
等号左边的数据类型:规定了数组中只能存储什么类型的元素
[]:代表的是数组,一个[]代表一维数组,两个[][]代表二维数组
数组名:自己取的名字,遵循小驼峰
new:代表的是创建数组
等号右边的数据类型:要和等号左边的数据类型一致
[长度]:指定数组长度,规定了数组最多能存多少个数据

b.静态初始化
数据类型[] 数组名 = new 数据类型[]{元素1,元素2...} -> 不推荐使用
数据类型 数组名[] = new 数据类型[]{元素1,元素2...} -> 不推荐使用

c.简化的静态初始化:
数据类型[] 数组名 = {元素1,元素2...}-> 推荐使用


6.区别:
a.动态初始化:定义的时候只指定了长度,没有存具体的数据
当只知道长度,但不知道具体存啥数据时可以使用动态初始化


b.静态初始化:定义的时候就直接知道存啥了
7.数组的长度
格式:
数组名.length

注意:
length后面不要带小括号,因为length不是一个方法,而是数组中的一个属性
8.数组的索引
1.概述:元素在数组中存储的位置

2.特点:
a.索引唯一
b.索引都是从0开始的,最大索引是数组长度-1

3.注意:
我们将来操作元素,必须通过索引来操作
存数据,要指定索引
取数据,要指定索引
查数据,要指定索引
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Demo01Array {
public static void main(String[] args) {
//动态初始化
int[] arr1 = new int[3];
String[] arr2 = new String[3];

//静态初始化
int[] arr3 = new int[]{1,2,3,4,5};

//简化静态初始化
int[] arr4 = {1,2,3,4,5};

String[] arr5 = {"乾隆","和珅","纪晓岚"};
}
}

数组基本操作

遍历数组和获取元素

1
1.遍历:将元素从数组中一个一个的获取出来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Demo07Array {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6};
/*for (int i = 0; i < 6; i++){
System.out.println(arr[i]);
}*/

/* for (int i = 0; i < arr.length; i++){
System.out.println(arr[i]);
}*/

/*
快速遍历快捷键: 数组名.fori
*/
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
1.地址值:
数组在内存中的身份证号,唯一标识,我们可以通过这个唯一标识到内存中准确找到这个数,从而操作这个数组中的数据

2.注意:
a.直接输出数组名,会直接输出数组在内存中的地址值
b.如果数组中没有存元素,那么直接获取索引上对应的元素也是有值的,只不过不是我们存储的数据,而是数组中的元素默认值
整数: 0
小数: 0.0
字符: '\u0000' -> 空白字符 -> 对应的int值是0
引用(字符串): null
布尔: false

存储元素

1
2
1.格式:
数组名[索引值] = 值 -> 将等号右边的值放到数组指定的索引位置上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Demo02Array {
public static void main(String[] args) {
int[] arr = new int[3];
arr[0] = 100;//将100存到了arr这个数组的0索引上
arr[1] = 200;//将200存到了arr这个数组的1索引上
arr[2] = 300;//将300存到了arr这个数组的2索引上
//arr[3] = 1000;

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

String[] arr1 = new String[3];
arr1[0] = "东方不败";
arr1[1] = "岳不群";
arr1[2] = "林平之";
}
}

操作数组时两个常见的问题

1.数组索引越界异常_ArrayIndexOutOfBoundsException
1
2
1.原因:
操作的索引超出了数组索引范围了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Demo09Array {
public static void main(String[] args) {
int[] arr = new int[3];
arr[0] = 100;
arr[1] = 200;
arr[2] = 300;
//arr[3] = 400;//索引3超出了arr的索引范围

//arr[-1] = 1000;//索引3超出了arr的索引范围

for (int i = 0; i <= arr.length; i++) {
System.out.println(arr[i]);//索引3超出了arr的索引范围
}
}
}
2.空指针异常_NullPointerException
1
2
1.原因:
当一个对象为null时,再调用此对象中的其他成员
1
2
3
4
5
6
7
8
9
10
public class Demo10Array {
public static void main(String[] args) {

int[] arr = new int[3];
System.out.println(arr.length);//3
arr = null;
System.out.println(arr.length);//NullPointerException
}
}

内存图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1.内存:可以理解"内存条",任何程序,软件运行起来都会在内存中运行,占用内存,在java的世界中,将内存分为了5大块

2.分为哪5大块
栈(重点)(Stack)
主要运行方法,方法的运行都会去栈内存中运行,运行完毕之后,需要"弹栈",腾空间

堆(重点):(Heap)
new一次,都会在堆内存中开辟空间,并为此空间自动分配一个地址值
堆中的数据都是有默认值的
整数:0
小数:0.0
字符: '\u0000'
布尔:false
引用:null

方法区(重点)(Method Area)
代码的"预备区",记录了类的信息以及方法的信息

本地方法栈(了解):专门运行native方法(本地方法)
本地方法可以理解为对java功能的扩充
有很多功能java语言实现不了,所以就需要依靠本地方法完成

寄存器(了解) -> 跟CPU有关

两个数组指向同一片内存空间

1
2
arr2不是new出来的,是arr1直接赋值的,arr1在内存中保存的是地址值,给了arr2,那么arr2的地址值和arr1就是一样的
所以此时arr1和arr2指向了堆内存中的同一片空间(同一个地址值,同一个数组),此时改变一个数组中的元素会影响到另外一个数组

1677569271494.png

二维数组

1.二维数组的定义格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1.概述:数组中的套多个数组
2.定义格式
a.动态初始化
数据类型[][] 数组名 = new 数据类型[m][n]
数据类型 数组名[][] = new 数据类型[m][n]
数据类型[] 数组名[] = new 数据类型[m][n]

m:代表的是二维数组的长度
n:代表的是二维数组中每一个一维数组的长度

数据类型[][] 数组名 = new 数据类型[m][] -> 二维数组中的一维数组没有被创建

b.静态初始化
数据类型[][] 数组名 = new 数据类型[][]{{元素1,元素2...},{元素1,元素2...},{元素1,元素2...}}
数据类型 数组名[][] = new 数据类型[][]{{元素1,元素2...},{元素1,元素2...},{元素1,元素2...}}
数据类型[] 数组名[] = new 数据类型[][]{{元素1,元素2...},{元素1,元素2...},{元素1,元素2...}}

c.简化静态初始化:
数据类型[][] 数组名 = {{元素1,元素2...},{元素1,元素2...},{元素1,元素2...}}
数据类型 数组名[][] = {{元素1,元素2...},{元素1,元素2...},{元素1,元素2...}}
数据类型[] 数组名[] = {{元素1,元素2...},{元素1,元素2...},{元素1,元素2...}}

2.获取二维数组长度

1
2
3
4
1.格式:
数组名.length

2.获取每一个一维数组长度,需要先遍历二维数组,将每一个一维数组从二维数组中拿出来
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo02Array {
public static void main(String[] args) {
String[][] arr = {{"张三","李四"},{"王五","赵六","田七"},{"猪八","牛九"}};
System.out.println(arr.length);
for (int i = 0; i < arr.length; i++) {
/*
arr[i]代表的是每一个一维数组
*/
System.out.println(arr[i].length);
}
}
}

3.获取二维数组中的元素

1
2
3
4
5
1.格式:
数组名[i][j]

i:代表的是一维数组在二维数组中的索引位置
j:代表的是元素在一维数组中的索引位置
1
2
3
4
5
6
7
8
public class Demo03Array {
public static void main(String[] args) {
String[][] arr = {{"张三","李四"},{"王五","赵六","田七"},{"猪八","牛九"}};
System.out.println(arr[0][0]);
System.out.println(arr[2][0]);
System.out.println(arr[1][1]);
}
}

4.二维数组中存储元素

1
2
3
4
5
1.格式:
数组名[i][j] = 值

i:代表的是一维数组在二维数组中的索引位置
j:代表的是元素在一维数组中的索引位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Demo04Array {
public static void main(String[] args) {
String[][] arr = new String[2][2];
arr[0][0] = "张飞";
arr[0][1] = "李逵";
arr[1][0] = "刘备";
arr[1][1] = "宋江";


System.out.println(arr[0][0]);
System.out.println(arr[0][1]);
System.out.println(arr[1][0]);
System.out.println(arr[1][1]);
}
}

5.二维数组的遍历

1
2
1.先遍历二维数组,将每一个一维数组遍历出来
2.再遍历每一个一维数组,将元素获取出来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Demo05Array {
public static void main(String[] args) {
String[][] arr = new String[2][2];
arr[0][0] = "张飞";
arr[0][1] = "李逵";
arr[1][0] = "刘备";
arr[1][1] = "宋江";


//遍历二维数组
for (int i = 0; i < arr.length; i++) {
/*
arr[i]代表的每一个一维数组
*/
for (int j = 0; j < arr[i].length; j++) {
System.out.println(arr[i][j]);
}
}
}
}

6.二维数组内存图

1
2
3
4
5
6
7
8
9
10
11
public class Demo06Array {
public static void main(String[] args) {
int[][] arr1 = new int[3][];

arr1[1] = new int[]{1,2,3};

arr1[2] = new int[3];

arr1[2][1] = 100;
}
}

image-20210825144338705.png

方法

定义

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
1.问题描述:
之前所有的代码都在main方法中写,如果我们将来将所有功能的代码都放到main方法中,会显得main方法代码太多,太乱,太臃肿-> 不好维护

解决:将不同功能的代码放到不同的方法中,想执行某个功能,直接调用方法名就行了,对应的代码就自动执行起来了
将来维护的时候,直接找到对应的方法,就可以对其直接修改维护

2.方法:
拥有功能性代码的代码块
将不同的功能放在不同的方法中,给每个方法取个名字,直接调用方法名,对应的方法就执行起来了,好维护

3.通用定义格式:
修饰符 返回值类型 方法名(参数){
方法体
return 结果
}

各部分解释:
a.修饰符: public static
b.返回值类型:
该方法最终返回的结果的数据类型
比如: return 1 -> 方法上的返回值类型写int
return 2.5 -> 方法上的返回值类型写double
return "" -> 方法上的返回值类型写String
如果没有返回值,不要写具体的返回值类型了,要写void


c.方法名:见名知意(小驼峰式)
d.参数:进入到方法内部参与执行的数据(数据类型 变量名,数据类型 变量名)
e.方法体:实现该方法的具体代码
f.return 结果: 如果有返回值,可以利用return 将结果返回

分类

  • a.无参无返回值方法
  • b.有参无返回值方法
  • c.无参有返回值方法
  • d.有参有返回值方法

无参无返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.无参无返回值方法定义:
public static void 方法名(){
方法体 -> 实现此方法的具体代码

}

2.调用:直接调用
在其他方法中: 方法名()

3.注意事项:
a.void关键字代表无返回值,写了void,就不要在方法中写return 结果
b.方法不调用不执行, main方法是jvm调用的
c.方法之间不能互相嵌套,方法之间是平级关系
d.方法的执行顺序只和调用顺序有关
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo02Method {
public static void main(String[] args) {
sum();
System.out.println("哈哈哈哈哈");
}
public static void sum(){
int a = 10;
int b = 20;
int sum = a+b;
System.out.println("sum = " + sum);
}
}

有参数无返回值

1
2
3
4
5
6
7
1.格式:
public static void 方法名(数据类型 变量名){
方法体
}

2.调用:
直接调用:方法名(具体的值) -> 调用的时候要给参数赋值
1
2
3
4
5
6
7
8
9
10
11
public class Demo03Method {
public static void main(String[] args) {
sum(10,20);
}

public static void sum(int a,int b){
int sum = a+b;
System.out.println("sum = " + sum);
}
}

无参数有返回值

1
2
3
4
5
6
7
8
9
10
1.格式:
public static 返回值类型 方法名(){
方法体
return 结果
}

2.调用: 返回值返回给了谁? 哪里调用返回给哪里
a.打印调用:sout(方法名()) -> 不推荐使用
b.赋值调用:调用完之后用一个变量接收返回值结果 -> 极力推荐
数据类型 变量名 = 方法名()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Demo04Method {
public static void main(String[] args) {
//打印调用 -> 涛哥不推荐
System.out.println(sum());

//赋值调用-> 极力推荐
int result = sum();
System.out.println("result = " + result);

}

public static int sum(){
int a = 10;
int b = 20;
int sum = a+b;
return sum;
//return a+b;
}
}

有参数有返回值

1
2
3
4
5
6
7
8
9
10
11
12
1.格式:
public static 返回值类型 方法名(参数){
方法体
return 结果
}

2.调用:
a.打印调用:
sout(方法名(具体的值))

b.赋值调用(极力推荐)
数据类型 变量名 = 方法名(具体的值)
1
2
3
4
5
6
7
8
9
10
11
public class Demo05Method {
public static void main(String[] args) {
int sum = sum(10, 20);
System.out.println("sum = " + sum);
}
public static int sum(int a,int b){
int sum = a+b;
return sum;
}
}

形式参数和实际参数区别

1
2
1.形式参数(形参):在定义方法的时候形式上定义的参数,此参数还没有值
2.实际参数(实参):在调用方法的时候给形参赋予的具体的值

1695708876216.png

参数和返回值使用的时机

1
2
3
4
5
1.参数:
当想将方法A的数据传递到方法B时,那么定义方法B时就需要带个参数,在方法A中调用方法B时,可以将方法A中的数据通过参数传递到方法B中

2.返回值:
调用方法A时,想要方法A的结果,去参与其他的操作,那么方法A就需要将自己的结果返回

1695711097363.png

1.controller接收的请求参数需要一层一层传递到service层,service层需要将请求参数再传递到dao层,此时service的方法以及dao的方法都需要参数,去接收

2.dao层的结果需要传递给service,service再传递给controller层,此时dao层和service方法需要返回值

方法注意事项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1.方法不调用不执行
2.方法的执行顺序只和调用顺序有关
3.方法之间不能互相嵌套

4.void不能和[return 结果]共存,但是void能和[return]共存
a.void:代表没有返回值
b.return 结果:就代表有返回值了
先将结果返回,然后结束方法

c.return:仅仅代表结束方法,不代表有返回值

5.一个方法中不能连续写多个return(也就是说一个方法不能都多个返回值)

6.调用方法的时候要看看下面有没有这个方法,没有的方法直接调用会报错
7.引用数据类型做方法参数传递,传递的是地址值
8.返回值为引用数据类型时,返回的是地址值

方法的重载(Overload)

在Java中,方法的重载(Overloading) 指的是在同一个类中,可以定义多个方法名相同但参数不同的方法。即使方法名相同,只要参数的类型、个数或顺序不同,Java编译器会根据调用时传递的参数来选择合适的方法执行。这种机制称为方法的重载。

方法重载的规则:

  1. 参数个数不同:方法名相同,参数的个数不同。
  2. 参数类型不同:方法名相同,参数的类型不同。
  3. 参数顺序不同:方法名相同,但参数的类型顺序不同。

方法重载的注意点:

  • 方法的返回类型是否相同不影响重载。
  • 方法的修饰符(如 publicstatic)不影响重载。
  • 方法抛出的异常不会影响重载。
    1
    需求:定义三个方法,分别求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
public class Demo1OverLoad {
public static void main(String[] args) {
sum(10,20);
sum(10,20,30);
sum(10,20,30,40);
}

//两个整数相加
public static void sum(int a,int b){
System.out.println(a+b);
}

//三个整数相加
public static void sum(int a,int b,int c){
System.out.println(a+b+c);
}

//四个整数相加
public static double sum(double a,double b,double c,double d){
System.out.println(a+b+c+d);
return a+b+c+d;
}
}

方法重载(Overloading) vs 方法重写(Overriding)的区别

特性 方法重载(Overloading) 方法重写(Overriding)
定义位置 同一个类中 父类与子类之间
方法名 相同 相同
参数列表 不同(参数的个数、类型、顺序) 必须相同
返回类型 可以不同 必须相同
访问修饰符 可以不同 子类不能比父类更严格
抛出异常 可以不同 子类抛出的异常不能比父类的更宽泛
与继承的关系 无关 与继承有关
静态/非静态方法 静态和非静态方法都可以重载 只能重写非静态方法
调用时机 编译时决定调用哪个方法 运行时决定调用哪个方法

面向对象

介绍

面向对象编程(Object-Oriented Programming, OOP)是一种编程范式,它通过“对象”和“”来组织程序,使得代码更加模块化、可重用、可扩展和易于维护

面向对象编程的三大特性:

  1. 封装(Encapsulation): 封装是指将数据和操作这些数据的方法绑定在一起,限制外部对这些数据的直接访问,只允许通过特定的方法访问。这种方式能够保护对象的内部状态,防止外部代码意外或恶意地修改对象的属性。封装还使代码的修改和维护更加容易,因为只需调整特定类内部的实现,而不必修改其他依赖该类的代码。

  2. 继承(Inheritance): 继承允许一个类从另一个类中继承属性和方法,从而实现代码的复用。通过继承,一个子类可以拥有父类的所有特性,并可以在此基础上扩展或修改这些特性。继承的主要作用是减少代码冗余,并让代码结构更加清晰。父类通常定义通用的属性和行为,而子类则实现更加具体的特性。

  3. 多态(Polymorphism): 多态是指同一个方法或操作在不同的对象上可能有不同的行为。它允许对象在不同的上下文中以不同的方式执行操作。多态可以通过方法重写(子类对父类方法的重新定义)或接口实现,使得同一方法在不同的类中表现出不同的行为。多态有助于提高代码的灵活性和可扩展性。

面向对象编程入门使用

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
public class Demo01Object {
public static void main(String[] args) {
/*
我们想在Demo01Object类中使用Scanner类中的next方法实现录入字符串
那么我们就需要使用面向对象思想编程

对象:Scanner-> new出来的
对象实现好的功能:next()

我们只需要知道找来Scanner这个对象,就可以调用这个对象中实现好的next方法
至于next方法怎么实现的,我们不需要关心
*/
Scanner sc = new Scanner(System.in);
String data = sc.next();
System.out.println("data = " + data);

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

/*
我们想在Demo01Object类中使用Random类中的nextInt方法实现随机整数
那么我们就需要使用面向对象思想编程

对象:Random -> new出来的
对象实现好的功能:nextInt()

我们只需要知道找来Random这个对象,就可以调用Random中的nextInt方法
至于nextInt怎么实现的,我们不需要关心
*/
Random rd = new Random();
int data2 = rd.nextInt();
System.out.println("data2 = " + data2);

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

int[] arr = {1,2,3,4};//[1,2,3,4]
/* System.out.print("[");
for (int i = 0; i < arr.length; i++) {
if (i== arr.length-1){
System.out.print(arr[i]+"]");
}else{
System.out.print(arr[i]+",");
}
}*/

/*
Arrays就是我们找来的对象
toStrig就是此对象中实现好的功能
我们只需要调用,怎么实现我们不关心
*/
System.out.println(Arrays.toString(arr));

}
}

类(Entity实体类)_class

1
2
实体类:是一类事物的抽象表示形式
世间万物的分类:比如: 人类 狗类 猫类 鼠标类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
组成部分:
1.属性(成员变量):这一类事物有啥
a.定义位置:类中方法外
b.作用范围:作用于当前类
c.定义格式: 数据类型 变量名
d.默认值:
整数:0
小数:0.0
字符:'\u0000'
引用(字符串):null
布尔:false


2.行为(成员方法):这一类事物都能干啥
只需要将之前的方法中的static干掉,其他的都一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Person {
//属性-> 成员变量
String name;
int age;

//行为 -> 成员方法
public void eat(){
System.out.println("人要干饭");
}

public void drink(){
System.out.println("人要喝水");
}
}

对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1.概述:一类事物的具体体现
2.使用:
a.导包: import 包名.类名
如果两个类在同一个包下,想要使用对方的成员不需要导包
如果两个类不在同一个包下,想要使用对方的成员需要导包

特殊包:java.lang -> 使用lang包下的类不需要导包 -> String

友情提示:在学四种权限修饰符之前,尽量让两个类在同一个包下

b.创建对象:想要使用哪个类中的成员,就new哪个类的对象
类名 对象名 = new 类名() -> 比如: Person person = new Person()

c.调用成员(成员变量,成员方法) -> 想要使用哪个类中的成员,就用哪个类的对象去点哪个成员
对象名.成员变量名 = 值
对象名.方法名() -> 调用的是无参无返回值方法
对象名.方法名(实参) -> 调用的是有参无返回值方法
数据类型 变量名 = 对象名.方法名() -> 调用的是无参有返回值方法
数据类型 变量名 = 对象名.方法名(实参) -> 调用的是有参有返回值方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Demo01Person {
public static void main(String[] args) {
Person person = new Person();
System.out.println(person.name);
System.out.println(person.age);
person.name = "金莲";
person.age = 26;
System.out.println(person.name);
System.out.println(person.age);

person.eat();
person.drink();

}
}

匿名对象的使用

1
2
3
4
5
6
7
8
9
1.int i = 10
a.int:是数据类型
b.i:变量名
c.等号右边的10:真正的数据

2.Person p = new Person()
a.等号左边的Person:对象的类型,好比是int
b.p:对象名
c.等号右边的new Person():真正的数据,是一个Person对象,将这个对象真正创建出来了
1
2
3
4
5
6
7
1.所谓的匿名对象:其实就是没有等号左边的部分,只有等号右边的部分(对象)
2.使用:
new 对象().成员

3.注意:
a.如果我们只想单纯的调用一个方法,让方法执行,我们可以考虑使用匿名对象
b.但是如果涉及到赋值,千万不要用匿名对象
1
2
3
4
5
6
public class Person {
String name;
public void eat(){
System.out.println("人要吃饭");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Demo01Person {
public static void main(String[] args) {
//原始方式
Person p = new Person();
p.name = "金莲";
System.out.println(p.name);
p.eat();

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

//匿名对象
new Person().eat();

new Person().name = "大郎";
System.out.println(new Person().name);//null
}
}

两个对象指向同一片空间内存图

1699614259259.png

phone2是phone1给的,phone1在内存中保存的是地址值,此时phone1和phone2地址值是一样的了,操作的是同一片空间的数据,所以改变一个对象的数据会影响到另外一个对象

封装

介绍

将一个物品封装起来,外界不能直接使用了,提高了物品的安全性

1
2
3
隐藏对象内部的复杂性,只对外提供公开,公共的接口,便于外界调用,从而提高了系统的可扩展性,可维护性,安全性,通俗来说,把该隐藏的隐藏起来(细节),把该暴露的暴露出来(对外提供的供别人使用的接口),这就是封装思想 

我们只需要调用这个接口(功能)即可,此接口背后封装起来的细节就开始执行了,但是我们不需要关注细节,只关注公共的接口怎么调用

成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1.问题:
定义成员变量,只要是new出来对象,就可以随便调用,随便赋值,哪怕是不合理的值我们也挡不住,怎么办?
将属性封装起来(隐藏细节)

a.关键字:private(私有化的) -> 被private修饰的成员只能在本类中使用,在别的类中使用不了

b.注意:
将代码放到一个方法中,也是封装的体现
一个成员被private修饰也是封装的体现,只不过private最具代表性

c.private的使用:
修饰成员变量:private 数据类型 变量名
修饰方法:将public改成private,其他的都一样

2.问题:属性被私有化了,外界直接调用不了了,那么此时属性就不能直接赋值取值了,所以需要提供公共的接口
get/set方法

set方法:为属性赋值
get方法:获取属性值
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 Person {
private String name;
private int age;

//为name提供get/set方法
public void setName(String xingMing) {
name = xingMing;
}

public String getName() {
return name;
}

//为age提供get/set方法
public void setAge(int nianLing) {
if (nianLing < 0 || nianLing > 150) {
System.out.println("你脑子是不是秀逗啦!岁数不合理");
} else {
age = nianLing;
}
}

public int getAge() {
return age;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test01 {
public static void main(String[] args) {
Person person = new Person();
//person.name = "悟空";
//person.age = -18;

//System.out.println(person.name);
//System.out.println(person.age);

person.setName("悟空");
person.setAge(18);

String name = person.getName();
int age = person.getAge();
System.out.println(name+"..."+age);
}
}

小结:

用private将属性封装起来,外界不能直接调用,保护了属性

对外提供一套公共的接口(set/get方法),让外界通过公共的接口间接使用封装起来的属性

成员变量和局部变量的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1.定义位置不同(重点)
a.成员变量:类中方法外
b.局部变量:定义在方法之中或者参数位置

2.初始化值不同(重点)
a.成员变量:有默认值的,所以不用先手动赋值,就可以直接使用
b.局部变量:是没有默认值的,所以需要先手动赋值,再使用

3.作用范围不同(重点)
a.成员变量:作用于整个类
b.局部变量:只作用于自己所在的方法,其他方法使用不了

4.内存位置不同(了解)
a.成员变量:在堆中,跟着对象走
b.局部变量:在栈中,跟着方法走

5.生命周期不同(了解)
a.成员变量:随着对象的创建而产生,随着对象的消失而消失
b.局部变量:随着方法的调用而产生,随着方法的调用完毕而消失
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Person {
String name;//成员变量
public void eat(){
int i = 10;//局部变量
System.out.println(i);

System.out.println(name);//成员变量不用手动赋值可以直接使用,因为有默认值
}

public void drink(){
int j;
//System.out.println(j);//局部变量没有默认值,所以需要手动赋值再使用
System.out.println(name);

//System.out.println(i);//i是eat方法的局部变量,在drink中使用不了
}
}

关键字this

1
2
3
4
5
6
7
1.如果成员变量和局部变量重名时,我们遵循"就近原则",先访问局部变量
2.this概述:代表的是当前对象
3.作用:this可以区分重名的成员变量和局部变量
this点出来的一定是成员的变量

4.this代表当前对象,那么具体代表哪个对象呢?
哪个对象调用的this所在的方法,this就代表哪个对象
1
2
3
4
5
6
7
8
9
10
11
12
public class Person {
String name;

/*
哪个对象调用的this所在的方法,this就代表哪个对象
*/
public void speak(String name){
System.out.println(this+"........");
System.out.println(this.name+"您好,我是"+name);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test01 {
public static void main(String[] args) {
Person person = new Person();
System.out.println(person+"=========");
person.name = "沉香";
person.speak("刘彦昌");

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

Person person2 = new Person();
System.out.println(person2+"+++++");
person2.name = "奥特曼";
person2.speak("奥特曼之父");
}
}

构造方法

1
2
3
4
5
6
7
8
9
10
11
1.概述:是一种特殊的方法,方法名和类名一致并且能初始化对象的方法。没有返回值,且不用写void
2.分类:
a.无参构造:没有参数
b.有参构造:有参数,参数是为指定的属性赋值
c.满参构造:给所有属性赋值

以上构造咱们不用记那么详细,我们就记有参和无参构造就可以了

3.特点:
a.方法名和类名一致
b.没有返回值,连void都没有
空参构造
1
2
3
4
5
6
7
8
9
10
11
1.格式:
public 类名(){

}
2.作用:
new对象使用

3.特点:
每个类中默认都有一个无参构造,不写也有,jvm会自动提供

4.使用:一new对象就是在调用构造方法
有参构造
1
2
3
4
5
6
7
8
9
10
11
1.格式:
public 类名(形参){
为属性赋值
}

2.作用:
a.new对象
b.为属性赋值

3.特点:
jvm不会自动提供有参构造,但是将有参构造手写出来,jvm将不再提供无参构造,所以建议有参,无参的构造都手写上去
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
public class Person {
private String name;
private int age;

//无参构造
public Person(){
System.out.println("我是无参构造");
}

//有参构造
public Person(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;
}
}

1
2
3
4
5
6
7
8
9
public class Test01 {
public static void main(String[] args) {
Person person = new Person();

Person person2 = new Person("涛哥", 18);
System.out.println(person2.getName()+"..."+person2.getAge());

}
}

标准JavaBean

JavaBean 是 Java语言编写类的一种标准规范。符合JavaBean` 的类,要求:

(1)类必须是具体的(非抽象 abstract)和公共的,public class 类名

(2)并且具有无参数的构造方法,有参构造

(3)成员变量私有化,并提供用来操作成员变量的setget 方法。
也称bean类,和entity实体类都可归为pojo类
JavaBean和 Entity Bean 的区别:
JavaBean 可以包含业务逻辑代码,并且不一定与数据表对应。

1
2
3
4
5
com.example.controller -> 专门放和页面打交道的类(表现层)
com.example.service -> 专门放业务处理的类 (业务层)
com.example.dao -> 专门放和数据库打交道的类(持久层)
com.example.pojo -> 专门放javabean类
com.example.utils -> 专门放工具类

dao全称 data access object,即数据连接层,也叫持久层

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
public class Person {
private String name;
private int age;

//无参构造
public Person(){

}

//有参构造
public Person(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;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test01 {
public static void main(String[] args) {
Person person = new Person();
person.setName("金莲");
person.setAge(26);
System.out.println(person.getName()+"..."+person.getAge());

Person person2 = new Person("涛哥", 18);
System.out.println(person2.getName()+"..."+person2.getAge());

}
}

编写符合JavaBean 规范的类,以学生类为例,标准代码如下:

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 class Student {
private int sid;
private String sname;

public Student() {
}

public Student(int sid, String sname) {
this.sid = sid;
this.sname = sname;
}

public int getSid() {
return sid;
}

public void setSid(int sid) {
this.sid = sid;
}

public String getSname() {
return sname;
}

public void setSname(String sname) {
this.sname = sname;
}
}

快速生成标准javabean通用快捷键:alt+insert

小结:

1.知道private的作用嘛?私有的,别的类不能直接调用

2.知道空参构造作用嘛?new对象

3.知道有参构造作用嘛? new对象 为属性赋值

4.知道set方法作用嘛? 为属性赋值

5.知道get方法作用嘛? 获取属性值

6.知道this的作用嘛? 区分重名的成员变量和局部变量

7.知道快捷键生成标准javabean嘛? alt+insert

JavaBean怎么来的

1
2
3
4
5
1.将来的javabean都是和数据库的表相关联(这种情况,也可称作实体类)
a.类名 -> 表名
b.属性名 -> 列名
c.对象 -> 表中每一行数据
d.属性值 -> 表中单元格中的数据

1699943030780.png

1699944355126.png

将页面填写的数据获取到,封装到javabean中,一层一层传递到dao层,然后将javabean中的属性值获取出来放到表中保存 -> 相等于是一个添加功能

1699946356124.png

将所有的数据查询出来,封装成一个一个的javabean对象,然后将封装好的javabean对象放到一个容器中,将此容器返回 给页面,在页面上遍历展示

工具类util

关键static

static的介绍以及基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1.概述:static是一个静态关键字
2.使用:
a.修饰一个成员变量:
static 数据类型 变量名

b.修饰一个方法:
修饰符 static 返回值类型 方法名(形参){
方法体
return 结果
}

3.调用静态成员:
类名直接调用(不用new对象)

4.静态成员特点:
a.静态成员属于类成员,不属于对象成员(非静态的成员属于对象成员)
b.静态成员会随着类的加载而加载
c.静态成员优先于非静态成员存在在内存中
d.凡是根据静态成员所在的类创建出来的对象,都可以共享这个静态成员
1
2
3
4
5
6
public class Student {
String name;
int age;
static String classRoom;
}

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 Test01 {
public static void main(String[] args) {
//先给静态成员赋个值
Student.classRoom = "222";

Student s1 = new Student();
s1.name = "郭靖";
s1.age = 28;
//s1.classRoom = "111";

System.out.println(s1.name+","+s1.age+","+Student.classRoom);

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

Student s2 = new Student();
s2.name = "黄蓉";
s2.age = 26;
//s2.classRoom = "111";

System.out.println(s2.name+","+s2.age+","+Student.classRoom);
}
}

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
1.在静态方法中能直接访问非静态成员嘛?  不能
想要调用的话:new对象调用

2.在非静态方法中能直接访问静态成员嘛? 能
a.同类:
直接调用
类名调用

b.不同类:
类名调用


3.在静态方法中能直接访问静态成员嘛?能
a.同类:
直接调用
类名调用

b.不同类:
类名调用

4.在非静态方法中能直接访问非静态成员嘛?能
a.同类:
直接调用
new对象调用

b.不同类:
new对象调用

问题1:既然static成员那么好使(类名直接调用),那么我们在实际开发中,能不能将所有的成员都定义成静态的呢?
不能

原因:由于静态成员会随着类的加载而加载,如果将所有的成员都变成静态的,那么类一加载,静态成员都会进内存,会大量占用内存空间

问题2:那么静态成员都啥时候定义呢?
一般情况下,我们在抽取工具类的时候可以将工具类中的所有成员都定义成静态的

问题3:啥时候定义工具类?
比如我们在写代码的过程中,发现有的功能在反复实现,代码一样,功能一样,此时就可以抽取出来,形成工具类

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 ArraysUtils {
/*
构造方法用private修饰

工具类中的成员都是静态的,静态成员都是类名调用,不需要new对象
所以工具类中的构造方法都是用private修饰

如果构造方法被private修饰,那么在别的类中,就不能利用构造方法new对象
*/
private ArraysUtils(){

}


//定义一个方法,实现获取int数组最大值
public static int getMax(int[] arr){
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (max<arr[i]){
max = arr[i];
}
}

return max;
}
}

1
2
3
4
5
6
7
8
public class Test01 {
public static void main(String[] args) {
int[] arr = {5,3,4,6,7,54,8};
int max = ArraysUtils.getMax(arr);
System.out.println("max = " + max);
}
}

成员方法

可变参数
1
2
3
4
5
1.需求:
定义一个方法,实现n个整数相加

2.分析:
方法参数位置,只明确了参数的类型,但是不明确参数个数,此时就可以定义成可变参数
1
2
3
4
5
6
1.定义格式:
数据类型...变量名

2.注意:
a.可变参数的本质是一个数组
b.参数位置不能连续写多个可变参数,而且当可变参数和其他普通参数一起使用时,可变参数需要放到参数列表最后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Demo01Var {
public static void main(String[] args) {
sum(1,2,3,4,5);
sum1(1,1,2,3,4);
}

public static void sum(int...arr){
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum+=arr[i];
}
System.out.println(sum);
}

public static void sum1(int i,int...arr){
//只能有一个可变参数,和普通参数一起使用时,需放在最后

}

}

字符串拼接
需求:n个字符串进行拼接,每一个字符串之间使用某字符进行分隔,如果没有传入字符串,那么返回空字符串””

1
比如:concat("-","张三丰","张翠山","张无忌") -> 返回 -> 张三丰-张翠山-张无忌
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Demo03Var {
public static void main(String[] args) {
String result = concat("-", "张三丰", "张翠山", "张无忌");
System.out.println("result = " + result);
}

public static String concat(String regex, String... s) {
String str = "";
for (int i = 0; i < s.length; i++) {
if (i == s.length - 1) {
str += s[i];
} else {
str += s[i] + regex;
}
}

return str;
}
}

基本数据类型做方法参数传递
1
2
3
基本类型做方法参数传递,传递的是值,不是变量本身
方法运行:压栈
方法运行完毕:弹栈 -> 释放栈内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Demo01Param {
public static void main(String[] args) {
int a = 10;
int b = 20;
method(a,b);
System.out.println(a);//10
System.out.println(b);//20
}
public static void method(int a,int b){
a+=10;
b+=20;
System.out.println(a);//20
System.out.println(b);//40
}
}

引用数据类型做方法参数传递
1
引用数据类型做方法参数传递时,传递的是地址值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Demo02Param {
public static void main(String[] args) {
int[] arr = {10,20};
method(arr);
System.out.println(arr[0]);//20
System.out.println(arr[1]);//40
}

public static void method(int[] arr){
arr[0]+=10;
arr[1]+=20;
System.out.println(arr[0]);//20
System.out.println(arr[1]);//40
}
}
命令行参数(了解)

通过命令行给main方法的形参传递的实参称为命令行参数

1
2
3
4
5
6
7
8
public class TestCommandParam{
//形参:String[] args
public static void main(String[] args){
for(int i=0; i<args.length; i++){
System.out.println("第" + (i+1) + "个参数的值是:" + args[i]);
}
}
}

运行命令:

1
java TestCommandParam
1
java TestCommandParam 1 2 3
1
java TestCommandParam hello world

快速生成方法

1
2
3
4
5
6
1.初学者要求先定义,再调用;不是初学者,就可以先调用,再定义方法
a.快捷键:alt+回车

2.快速将一段代码抽取到一个方法中:
a.选中要抽取的方法
b.按ctrl+alt+m

debug调试

1
2
3
4
5
6
7
8
9
1.概述:调试代码的一种手段

2.作用:
a.能清楚的看到每个变量在代码执行过程中的变化
b.找错

3.使用:
a.在想要开始debug的那一行左边点击一下,出现红色小圆点(断点)
b.右键-> 点击debug

继承

介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
1.父类怎么形成的:我们的定义了多个类,发现这些类中有很多重复性的代码,我们就定义了一个父类,将相同的代码抽取出来放到父类中,其他的类直接继承这个父类,就可以直接使用父类中的内容了

2.怎么去继承: extends
子类 extends 父类

3.注意:
a.子类可以继承父类中私有和非私有成员,但是不能使用父类中私有成员

b.构造方法不能继承

4.继承怎么学:
a.继承不要从是否"拥有"方面来学习
要从是否能"使用"方面来学习

继承如何使用

1
2
3
4
1.定义一个父类,在其中定义重复性的代码
2.定义一个子类继承父类 -> extends
子类 extends 父类
3.创建子类对象,直接使用父类中非私有成员
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Employee {
String name;
int age;

public void work(){
System.out.println("工作");
}

private void eat(){
System.out.println("员工要干饭");
}
}

1
2
3
4
public class Teacher extends Employee{

}

1
2
3
public class Manager extends Employee{
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test01 {
public static void main(String[] args) {
Teacher teacher = new Teacher();
teacher.name = "涛哥";
teacher.age = 18;
System.out.println(teacher.name+"..."+teacher.age);
teacher.work();
//teacher.eat();子类继承父类之后不能使用父类私有成员,只能使用父类非私有成员

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

Manager manager = new Manager();
manager.name = "金莲";
manager.age = 18;
System.out.println(manager.name+"..."+manager.age);
manager.work();
}
}

继承的特点

1
2
3
4
5
6
7
8
9
10
11
12
1.继承只支持单继承,不能多继承
public class A extends B,C{} -> 错误
2.继承支持多层继承
public class A extends B{}
public class B extends C{}
3.一个父类可以有多个子类
public class A extends C{}
public class B extends C{}

4.构造方法不能继承,也不能重写
私有方法可以继承,但是不能被重写
静态方法可以继承,但是不能被重写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Fu {
public void method01(){
System.out.println("method01方法");
}

private void method02(){
System.out.println("method02方法");
}

public static void method03(){
System.out.println("method03方法");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
public class Zi extends Fu{
@Override
public void method01(){
System.out.println("重写的method01方法");
}

/* @Override
private void method02(){
System.out.println("method02方法");
}*/
}

1
2
3
4
5
6
7
public class Test01 {
public static void main(String[] args) {
Zi zi = new Zi();
zi.method03();
Zi.method03();
}
}

继承中,成员变量和成员方法的访问特点

子类和父类中的成员变量不重名:
1
2
3
4
public class Fu {
int numFu = 100;
}

1
2
3
public class Zi extends Fu{
int numZi = 10;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test01 {
public static void main(String[] args) {
//创建父类对象
Fu fu = new Fu();
System.out.println(fu.numFu);//父类中的numFu
//System.out.println(fu.numZi);//不能直接调用子类特有的成员

System.out.println("=================");
//创建子类对象
Zi zi = new Zi();
System.out.println(zi.numZi);
System.out.println(zi.numFu);//继承了父类,可以使用父类中非私有成员
}
}

子类和父类中的成员变量重名
1
2
3
4
5
public class Fu {
int numFu = 100;

int num = 10000;
}
1
2
3
4
5
6
public class Zi extends Fu{
int numZi = 10;

int num = 1000;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test01 {
public static void main(String[] args) {
//创建父类对象
Fu fu = new Fu();
System.out.println(fu.numFu);//父类中的numFu
//System.out.println(fu.numZi);//不能直接调用子类特有的成员
System.out.println(fu.num);//父类的

System.out.println("=================");
//创建子类对象
Zi zi = new Zi();
System.out.println(zi.numZi);
System.out.println(zi.numFu);//继承了父类,可以使用父类中非私有成员
System.out.println(zi.num);//子类的
}
}

总结:继承前提下,成员变量访问特点口诀:

​ 看等号左边是谁,先调用谁中的成员,子类没有,找父类

子类和父类中的成员方法重名
1
2
3
4
5
6
7
8
9
public class Fu {
public void methodFu(){
System.out.println("我是父类中的methodFu");
}

public void method(){
System.out.println("我是父类中的method方法");
}
}
1
2
3
4
5
6
7
8
9
public class Zi extends Fu{
public void methodZi(){
System.out.println("我是子类中的methodZi方法");
}

public void method(){
System.out.println("我是子类中的method方法");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Test01 {
public static void main(String[] args) {
Fu fu = new Fu();
fu.methodFu();
// fu.methodZi(); 不能直接调用子类特有的方法
fu.method();//父类中的method方法

System.out.println("=====================");
Zi zi = new Zi();
zi.methodZi();
zi.methodFu();//继承父类之后,能调用父类非私有成员
zi.method();//子类中的method方法

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

Fu fu1 = new Zi();
fu1.method();//调用的是子类中的method方法
}
}

成员方法:看new的是谁,先调用谁中的方法,子类没有,找父类

继承中,成员变量访问特点:看等号左边是谁,先调用谁中的成员变量

​ 成员方法访问特点:看new的是谁,先调用谁中的方法

方法的重写

1
2
3
4
5
1.概述:子类中有一个和父类方法名以及参数列表相同的方法
2.前提:继承
3.访问:看new的是谁,先调用谁中的,如果new的是子类,调用调用子类重写的方法,子类没有,找父类
4.检测是否为重写方法:在该方法上写
@Override
1
2
3
4
5
6
7
8
public class Fu {
public void methodFu(){
System.out.println("我是父类中的methodFu方法");
}
public void method(){
System.out.println("我是父类中的method方法");
}
}
1
2
3
4
5
6
7
8
9
public class Zi extends Fu{
public void methodZi(){
System.out.println("我是子类中的methodZi方法");
}

@Override
public void method(){
System.out.println("我是子类中的method方法");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test01 {
public static void main(String[] args) {
Fu fu = new Fu();
fu.methodFu();//自己的methodFu方法
fu.method();//new的是父类对象,那么调用的就是父类中的method

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

Zi zi = new Zi();
zi.methodZi();
zi.methodFu();
zi.method();//子类中的method方法
}
}

注意事项
1
2
3
4
5
1.子类重写父类方法之后,权限必须要保证大于等于父类权限(权限指的是访问权限)
public -> protected -> 默认 -> private
2.子类方法重写父类方法,方法名和参数列表要一样
3.私有方法不能被重写,构造方法不能被重写,静态方法不能被重写
4.子类重写父类方法之后,返回值类型应该是父类方法返回值类型的子类类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Fu {
public void methodFu(){
System.out.println("我是父类中的methodFu方法");
}
public void method(){
System.out.println("我是父类中的method方法");
}

void method01(){

}

/* public static void method02(){

}*/

public Fu method03(){
return null;
}
}
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 class Zi extends Fu{
public void methodZi(){
System.out.println("我是子类中的methodZi方法");
}

@Override
public void method(){
System.out.println("我是子类中的method方法");
}

@Override
public void method01(){

}

/* public static void method02(){

}*/

@Override
public Zi method03(){
return null;
}
}
使用场景
1
1.使用场景:功能升级改造,子类需要对父类中已经实现好的功能进行重新改造
1
2
3
4
5
6
7
8
9
10
11
12
13
public class OldPhone {
public void call(){
System.out.println("打电话");
}

public void message(){
System.out.println("发短信");
}

public void show(){
System.out.println("显示手机号");
}
}
1
2
3
4
5
6
7
public class NewPhone extends OldPhone{
public void show(){
System.out.println("显示手机号");
System.out.println("显示归属地");
}
}

1
2
3
4
5
6
7
8
9
10

public class Test01 {
public static void main(String[] args) {
NewPhone newPhone = new NewPhone();
newPhone.call();
newPhone.message();
newPhone.show();
}
}

继承中构造方法的特点

1
2
3
4
1.注意:new子类对象时,会先初始化父类(先走父类无参构造方法)
2.原因:
每个构造方法的第一行,默认都会有一个super(),不写jvm自动提供一个
super()代表的是父类无参构造
1
2
3
4
5
6
public class Fu {
public Fu(){
System.out.println("我是父类中的无参构造");
}
}

1
2
3
4
5
6
7
8
9
10
11
public class Zi extends Fu{
public Zi(){
//super();
System.out.println("我是子类中的无参构造");
}

public Zi(int i){
//super();
System.out.println("我是子类中的有参构造");
}
}
1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
Zi zi = new Zi();
System.out.println("===========");
Zi zi1 = new Zi(10);
}
}

super的具体使用

1
2
3
4
5
6
7
8
9
10
11
12
1.概述:代表的是父类引用
2.作用:可以调用父类中的成员
3.使用:
a.调用父类构造方法-> 在子类中的构造中写
super() -> 调用父类无参构造
super(实参) -> 调用父类有参构造

b.调用父类成员变量:
super.成员变量名

c.调用父类成员方法:
super.成员方法名(实参)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Fu {
int num = 10;
public Fu(){
System.out.println("我是父类中的无参构造");
}

public Fu(int data){
System.out.println("我是父类中的有参构造");
}

public void method(){
System.out.println("我是父类中的method方法");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Zi extends Fu{
int num = 100;
public Zi(){
super();//调用父类中的无参构造
System.out.println("我是子类中的无参构造");
}

public Zi(int num){
super(10);//调用父类的有参构造
System.out.println("我是子类中的有参构造");
}

public void method(){
super.method();//调用父类的method方法
System.out.println("我是子类中的method方法");
System.out.println(num);//子类自己的
System.out.println(super.num);//调用父类的num
}
}

1
2
3
4
5
6
7
8
9
10
11
public class Test01 {
public static void main(String[] args) {
Zi zi = new Zi();
System.out.println("============");
Zi zi1 = new Zi(10);
System.out.println("============");
Zi zi2 = new Zi();
zi2.method();

}
}

this的具体使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.this概述:代表的是当前对象(哪个对象调用的this所在的方法,this就代表哪个对象)
2.作用:
a.区分重名的成员变量和局部变量
b.调用当前对象中的成员
3.使用:
a.调用当前对象的构造:在构造中写
this():调用当前对象的无参构造
this(实参):调用当前对象的有参构造
b.调用当前对象的成员变量:
this.成员变量名
c.调用当前对象的成员方法:
this.成员方法名(实参)
4.注意:
不管是super还是this,只要在构造中使用,都必须在第一行,所以二者不能同时手写出来
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
public class Person {
int num = 10;
public Person(){
//this(10);
System.out.println("我是Person中的无参构造");
}

public Person(int data){
//super();super和this不能同时再构造中出现
this();
System.out.println("我是Person中的有参构造");
}

public void method(){
int num = 20;
System.out.println(num);//20
System.out.println(this.num);//10
this.method01();
System.out.println("我是Person类中的method方法");
}

public void method01(){
System.out.println("我是Person类中的method01方法");
}
}

1
2
3
4
5
6
7
8
9
10
public class Test01 {
public static void main(String[] args) {
Person person = new Person();
System.out.println("========");
Person person1 = new Person(10);
System.out.println("========");
Person person2 = new Person();
person2.method();
}
}

抽象类

抽象的介绍
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   1.抽象类怎么来的?
抽取共性方法,放到父类中,发现方法没法实现,因为每个子类对此方法的实现方式细节不一样
此时方法体说不清道不明,可以定义成抽象方法
抽象方法所在的类一定是抽象类


2.关键字: abstract

3.抽象方法:
修饰符 abstract 返回值类型 方法名(参数);

4.抽象类:
public abstract class 类名{}

5.注意:
a.抽象方法所在的类一定是抽象类
b.抽象类中不一定非得有抽象方法
c.子类继承父类之后,需要重写父类
中所有的抽象方法,不然编译报错
d.抽象类不能new对象,只能通过new子类对象调动重写方法

6.可以将抽象类看成是一类事物的标准,要求只要是属于这一类的,都必须要拥有抽象类中的方法,必须要给我实现,怎么证明拥有了,怎么证明实现了呢?-> 重写
至于这个方法怎么实现,就看子类重写之后怎么写方法体了
1
2
3
4
public abstract class Animal {
public abstract void eat();
public abstract void drink();
}
1
2
3
4
5
6
7
8
9
10
11
public class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗啃骨头");
}

@Override
public void drink() {
System.out.println("狗喝水");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}

@Override
public void drink() {
System.out.println("猫喝水");
}
}

1
2
3
4
5
6
7
8
9
10
11
public class Test01 {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat();
dog.drink();
System.out.println("===================");
Cat cat = new Cat();
cat.eat();
cat.drink();
}
}
抽象的注意事项
1
2
3
4
5
1.抽象类不能直接new对象,只能创建非抽象子类的对象
2.抽象类中不一定非得有抽象方法,但是抽象方法所在的类一定抽象类
3.抽象类的子类,必须重写父类中的所有抽象方法,否则,编译报错,除非该子类也是抽象类
4.抽象类中可以有成员变量,构造,成员方法
5.抽象类中可以有构造方法,是供子类创建对象时,初始化父类属性使用的
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 abstract class Employee {
private String name;
private int age;

public Employee() {
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

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

public abstract void work();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Teacher extends Employee{
public Teacher() {
}

public Teacher(String name, int age) {
super(name, age);
}

@Override
public void work() {
System.out.println("涛哥在讲java");
}
}
1
2
3
4
5
6
7
public class Test01 {
public static void main(String[] args) {
Teacher t1 = new Teacher("涛哥", 18);
System.out.println(t1.getName()+"..."+t1.getAge());
}
}

接口

接口的定义以及使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1.接口:是一个引用数据类型,是一种标准,规则
2.关键字:
a.interface 接口
public interface 接口名{}

b.implements 实现
实现类 implements 接口名{}

3.接口中可以定义的成员:

a.jdk7以及之前:
抽象方法: public abstract -> 即使不写public abstract,默认也有
成员变量:public static final 数据类型 变量名 = 值-> 即使不写public static final,默认也有
final是最终的,被final修饰的变量不能二次赋值,所以我们一般将final修饰的变量视为常量

b.jdk8:
默认方法:public default 返回值类型 方法名(形参){}
静态方法:public static 返回值类型 方法名(形参){}

c.jdk9开始:
私有方法:
private的方法
1
2
3
4
5
6
7
8
9
1.定义接口:
public interface 接口名{}
2.实现:
public class 实现类类名 implements 接口名{}
3.使用:
a.实现类实现接口
b.重写接口中的抽象方法
c.创建实现类对象(接口不能直接new对象)
d.调用重写的方法
1
2
3
4
5
public interface USB {
public abstract void open();
public abstract void close();
}

1
2
3
4
5
6
7
8
9
10
11
12
public class Mouse implements USB{
@Override
public void open() {
System.out.println("鼠标打开");
}

@Override
public void close() {
System.out.println("鼠标关闭");
}
}

1
2
3
4
5
6
7
8
public class Test01 {
public static void main(String[] args) {
Mouse mouse = new Mouse();
mouse.open();
mouse.close();
}
}

成员方法

抽象方法

1
2
3
4
5
6
7
8
1.定义格式:
public abstract 返回值类型 方法名(形参);
2.注意:
不写public abstract 默认也有
3.使用:
a.定义实现类,实现接口
b.重写抽象方法
c.创建实现类对象,调用重写的方法
1
2
3
4
public interface USB {
public abstract void open();
String close();
}
1
2
3
4
5
6
7
8
9
10
11
public class Mouse implements USB{
@Override
public void open() {
System.out.println("鼠标打开");
}

@Override
public String close() {
return "鼠标关闭";
}
}
1
2
3
4
5
6
7
8
9
public class Test01 {
public static void main(String[] args) {
Mouse mouse = new Mouse();
mouse.open();
String result = mouse.close();
System.out.println("result = " + result);
}
}

默认方法

1
2
3
4
5
6
7
8
9
1.格式:
public default 返回值类型 方法名(形参){
方法体
return 结果
}
2.使用:
a.定义实现类,实现接口
b.默认方法可重写,可不重写
c.创建实现类对象,调用默认方法
1
2
3
4
5
6
7
public interface USB {
//默认方法
public default void methodDef(){
System.out.println("我是默认方法");
}
}

1
2
3
4
5
6
7
public class Mouse implements USB {
@Override
public void methodDef(){
System.out.println("我是重写接口中的默认方法");
}
}

1
2
3
4
5
6
public class Test01 {
public static void main(String[] args) {
Mouse mouse = new Mouse();
mouse.methodDef();
}
}

静态方法

1
2
3
4
5
6
7
8
9
1.定义格式:
public static 返回值类型 方法名(形参){
方法体
return 结果
}

2.使用:
接口名直接调用

1
2
3
4
5
6
7
8
9
10
11
public interface USB {
//默认方法
public default void methodDef(){
System.out.println("我是默认方法");
}

//静态方法
public static void methodSta(){
System.out.println("我是接口中的静态方法");
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Test01 {
public static void main(String[] args) {
Mouse mouse = new Mouse();
mouse.methodDef();

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

USB.methodSta();
}
}

默认方法和静态方法 -> 可以作为临时加的一个小功能来使用

成员变量
1
2
3
4
5
6
7
8
9
10
11
1.格式:
public static final 数据类型 变量名 = 值
2.相关知识点:final
final代表最终的,被它修饰的变量,不能二次赋值,可以视为常量
3.特点:
不写public static final 默认也有
4.使用:
接口名直接调用
5.注意:
a.被static final修饰的成员变量需要手动赋值
b.习惯上我们会将static final修饰的成员变量名大写
1
2
3
4
public interface USB {
public static final int NUM1 = 100;
int NUM2 = 200;//不写public static final 默认也有
}
1
2
3
4
5
6
public class Test01 {
public static void main(String[] args) {
System.out.println(USB.NUM1);
System.out.println(USB.NUM2);
}
}
接口的特点
1
2
3
4
5
6
7
8
9
1.接口可以多继承 -> 一个接口可以继承多个接口
public interface InterfaceA extends InterfaceB,InterfaceC{}
2.接口可以多实现 -> 一个实现类可以实现一个或者多个接口
public class InterfaceImpl implements InterfaceA,InterfaceB{}
3.一个子类可以继承一个父类的同时实现一个或者多个接口
public class Zi extends Fu implements InterfaceA,InterfaceB{}

4.注意:
继承也好,实现接口也罢,只要是父类中或者接口的抽象方法,子类或者实现类都要重写

当一个类实现多个接口时,如果接口中的抽象方法有重名且参数一样的,只需要重写一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface InterfaceA {
public abstract void method();
}

public interface InterfaceB {
public abstract void method();
}

public class InterfaceImpl implements InterfaceA,InterfaceB{
@Override
public void method() {
System.out.println("重写的method方法");
}
}

当一个类实现多个接口时,如果多个接口中默认方法有重名的,且参数一样的,必须重写一次默认方法

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
public interface InterfaceA {
public abstract void method();

public default void methodDef(){
System.out.println("我是接口A中的默认方法");
}
}

public interface InterfaceB {
public abstract void method();

/* public default void methodDef(){
System.out.println("我是接口B中的默认方法");
}*/
public default void methodDef(int a) {
System.out.println("我是接口B中的默认方法");
}
}

public class InterfaceImpl implements InterfaceA,InterfaceB{
@Override
public void method() {
System.out.println("重写的method方法");
}

/* @Override
public void methodDef() {
System.out.println("重写后的默认方法");
}*/
}


public class Test01 {
public static void main(String[] args) {
InterfaceImpl anInterface = new InterfaceImpl();
anInterface.methodDef();
anInterface.methodDef(10);
}
}

接口和抽象类的区别

1
2
3
4
5
6
7
8
9
相同点:
a.都位于继承体系的顶端,用于被其他类实现或者继承
b.都不能new
c.都包含抽象方法,其子类或者实现类都必须重写这些抽象方法

不同点:
a.抽象类:一般作为父类使用,可以有成员变量,构造,成员方法,抽象方法等
b.接口:成员单一,一般抽取接口,抽取的都是方法,视为功能的大集合
c.类不能多继承,但是接口可以

多态

多态的介绍

1
2
3
4
5
6
7
1.前提:
a.必须有子父类继承或者接口实现关系
b.必须有方法的重写(没有重写,多态没有意义),多态主要拓展的是重写方法作用
c.new对象:父类引用指向子类对象
Fu fu = new Zi() -> 理解为大类型接收了一个小类型的数据 ->比如 double b = 10
2.注意:
多态下不能直接调用子类特有功能

多态的基本使用

1
2
3
4
public abstract class Animal {
public abstract void eat();
}

1
2
3
4
5
6
7
8
9
10
11
public class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗啃骨头");
}

//特有方法
public void lookDoor(){
System.out.println("狗会看门");
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}

//特有方法
public void catchMouse(){
System.out.println("猫会捉老鼠");
}
}
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 class Test01 {
public static void main(String[] args) {
//原始方式
Dog dog = new Dog();
dog.eat();//重写的
dog.lookDoor();//特有的

Cat cat = new Cat();
cat.eat();//重写的
cat.catchMouse();//特有的

System.out.println("==================");
//多态形式new对象
Animal animal = new Dog();//相当于double b = 10
animal.eat();//重写的 animal接收的是dog对象,所以调用的是dog中的eat
// animal.lookDoor(); 多态前提下,不能直接调用子类特有成员

Animal animal1 = new Cat();
animal1.eat();//cat重写的


}
}

多态的条件下成员的访问特点

成员变量
1
2
3
public class Fu {
int num = 1000;
}
1
2
3
public class Zi extends Fu{
int num = 100;
}
1
2
3
4
5
6
public class Test01 {
public static void main(String[] args) {
Fu fu = new Zi();
System.out.println(fu.num);
}
}
1
看等号左边是谁,先调用谁中的成员变量
成员方法
1
2
3
4
5
6
7
public class Fu {
int num = 1000;
public void method(){
System.out.println("我是父类中的method方法");
}
}

1
2
3
4
5
6
7
8
public class Zi extends Fu{
int num = 100;

public void method(){
System.out.println("我是子类中的method方法");
}
}

1
2
3
4
5
6
7
8
public class Test01 {
public static void main(String[] args) {
Fu fu = new Zi();
System.out.println(fu.num);//父类中的num
fu.method();//子类中重写的method方法
}
}

1
new的是谁,先调用谁中的成员方法,子类没有,找父类

多态的好处(为什么学多态)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1.问题描述:
如果使用原始方式new对象(等号左右两边一样),既能调用重写的,还能调用继承的,还能调用自己特有的成员
但是多态方式new对象,只能调用重写的,不能直接调用子类特有的成员,那为啥还要用多态呢?

2.多态方式和原始方式new对象的优缺点:
原始方式:
a.优点:既能调用重写的,还能调用父类非私有的,还能调用自己特有的
b.缺点:扩展性差

多态方式:
a.优点:扩展性强
b.缺点:不能直接调用子类特有功能

Fu fu = new Zi()
double b = 10;
b = 100L;

1
2
3
4
public abstract class Animal {
public abstract void eat();
}

1
2
3
4
5
6
7
8
9
10
11
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗啃骨头");
}

//特有方法
public void lookDoor(){
System.out.println("狗会看门");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}

//特有方法
public void catchMouse(){
System.out.println("猫会捉老鼠");
}
}

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
public class Test01 {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat();//重写的
dog.lookDoor();//特有的

//dog = new Cat();
System.out.println("=============");
method(dog);

Cat cat = new Cat();
method(cat);

/* houzi houzi = new houzi();
method(houzi);

bird bird = new bird();
method(bird);*/
}

public static void method(Dog dog){
dog.eat();
dog.lookDoor();
}

public static void method(Cat cat){
cat.eat();
cat.catchMouse();
}

/* public static void method(houzi houzi){
cat.eat();
cat.catchMouse();
}*/
}

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 class Test02 {
public static void main(String[] args) {
/*
double b = 10;
b = 100L;
*/
Animal animal = new Dog();
animal.eat();

animal = new Cat();
animal.eat();
System.out.println("=================");

Dog dog = new Dog();
method(dog);

Cat cat = new Cat();
method(cat);

}

/*
形参传递父类类型,调用此方法父类类型可以接收任意它的子类对象
传递哪个子类对象,就指向哪个子类对象,就调用哪个子类对象重写的方法
*/
public static void method(Animal animal){//Animal animal = dog Animal animal = cat
animal.eat();
}
}

形参传递父类类型,调用此方法父类类型可以接收任意它的子类对象
传递哪个子类对象,就指向哪个子类对象,就调用哪个子类对象重写的方法

多态中的转型

向上转型
1
2
1.父类引用指向子类对象
好比是: double b = 1;
向下转型
1
2
3
4
5
6
1.向下转型:好比强转,将大类型强制转成小类型
2.表现方式:
父类类型 对象名1 = new 子类对象() -> 向上转型 -> double b = 1
子类类型 对象名2 = (子类类型)对象名1 -> 向下转型 -> int i = (int)b

3.想要调用子类特有功能,我们就需要向下转型
1
2
3
public abstract class Animal {
public abstract void eat();
}
1
2
3
4
5
6
7
8
9
10
11
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}

//特有方法
public void catchMouse(){
System.out.println("猫会捉老鼠");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗啃骨头");
}

//特有方法
public void lookDoor(){
System.out.println("狗会看门");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test01 {
public static void main(String[] args) {
//多态new对象 向上转型
Animal animal = new Dog();
animal.eat();//dog重写的
//animal.lookDoor();//多态不能调用子类特有功能

//向下转型
Dog dog = (Dog) animal;
dog.eat();
dog.lookDoor();
}
}
转型可能会出现的问题
1
2
3
4
5
6
7
8
1.如果等号左右两边类型不一致,会出现类型转换异常(ClassCastException)
2.解决:
在向下转型之前,先判断类型
3.怎么判断类型: instanceof
判断结果是boolean

4.使用:
对象名 instanceof 类型 -> 判断的是关键字前面的对象是否符合关键字后面的类型
1
2
3
public abstract class Animal {
public abstract void eat();
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗啃骨头");
}

//特有方法
public void lookDoor(){
System.out.println("狗会看门");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}

//特有方法
public void catchMouse(){
System.out.println("猫会捉老鼠");
}
}

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
public class Test01 {
public static void main(String[] args) {
Dog dog = new Dog();
method(dog);

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

Cat cat = new Cat();
method(cat);
}

public static void method(Animal animal){//animal = dog animal = cat
/* animal.eat();
*//*
这里会出现类型转换异常(ClassCastException)
原因:当调用method,传递Cat对象时,animal代表的就是cat对象
此时我们将代表cat对象的animal强转成了dog
此时等号左右两边类型不一致了,所以出现了类型转换异常
*//*
Dog dog = (Dog) animal;
dog.lookDoor();*/

if (animal instanceof Dog){
Dog dog = (Dog) animal;
dog.eat();
dog.lookDoor();
}

if (animal instanceof Cat){
Cat cat = (Cat) animal;
cat.eat();
cat.catchMouse();
}
}
}
综合练习
1
2
3
4
5
6
7
定义笔记本类,具备开机,关机和使用USB设备的功能。具体是什么USB设备,笔记本并不关心,只要符合USB规格的设备都可以。鼠标和键盘要想能在电脑上使用,那么鼠标和键盘也必须遵守USB规范,不然鼠标和键盘的生产出来无法使用;
进行描述笔记本类,实现笔记本使用USB鼠标、USB键盘

- USB接口,包含开启功能、关闭功能
- 笔记本类,包含运行功能、关机功能、使用USB设备功能
- 鼠标类,要符合USB接口
- 键盘类,要符合USB接口
1
2
3
4
public interface USB {
public abstract void open();
public abstract void close();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Mouse implements USB{
@Override
public void open() {
System.out.println("鼠标开启");
}

@Override
public void close() {
System.out.println("鼠标关闭");
}

//特有方法
public void click(){
System.out.println("来呀,快点我");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class KeyBoard implements USB{
@Override
public void open() {
System.out.println("键盘开启");
}

@Override
public void close() {
System.out.println("键盘关闭");
}

//特有功能
public void input(){
System.out.println("来呀,敲我呀!");
}
}
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
public class NoteBook {
//开机
public void start(){
System.out.println("开机");
}

//使用USB
/*
USB usb = mouse 多态
USB usb = keyBoard 多态
*/
public void useUSB(USB usb){
if (usb instanceof Mouse){
Mouse mouse = (Mouse) usb;
mouse.open();
mouse.click();
mouse.close();
}else{
KeyBoard keyBoard = (KeyBoard) usb;
keyBoard.open();
keyBoard.input();
keyBoard.close();
}
//usb.open();
//usb.close();
}

//关机
public void stop(){
System.out.println("关机");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test01 {
public static void main(String[] args) {
NoteBook noteBook = new NoteBook();
Mouse mouse = new Mouse();
noteBook.start();
noteBook.useUSB(mouse);
noteBook.stop();

System.out.println("===========");
KeyBoard keyBoard = new KeyBoard();
noteBook.start();
noteBook.useUSB(keyBoard);
noteBook.stop();
}
}

权限修饰符

概述

在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限,

  • public:公共的,最高权限,被public修饰的成员,在哪里都能访问

  • protected:受保护的

  • default::默认的 注意 不写权限修饰符就是默认权限,不能直接把default写出来

  • private:私有的,只能在自己的类中直接访问

    我们只需要知道一个成员被这四个权限修饰符修饰在4种情况下能不能访问就行了

不同权限的访问能力

public protected default(空的) private
同类 yes yes yes yes
同包不同类 yes yes yes no
不同包子父类 yes yes no no
不同包非子父类 yes no no no

public具有最大权限,private有最小权限

编写代码时,如果没有特殊的考虑,建议这样使用权限:

1
2
3
1.属性:用private -> 封装思想
2.成员方法public -> 便于调用
3.构造public -> 便于new对象

final关键字

1
2
3
4
5
6
7
8
9
1.概述:最终的
2.使用:
a.修饰一个对象
b.修饰一个类
c.修饰一个成员变量
d.修饰一个方法
e.修饰一个局部变量

3.怎么学final:只需要知道被final修饰之后特点是啥即可

final修饰对象

1
2
3
4
1.格式:
final 数据类型 对象名 = new 对象();
2.特点:
final修饰的对象,地址值不能改变,但是对象中的属性值可以改变
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 class Person {
private String name;
private int age;

public Person() {
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test02 {
public static void main(String[] args) {
//Person p1 = new Person("金莲",26);
//System.out.println(p1);//地址值
//p1 = new Person("涛哥",18);
//System.out.println(p1);//地址值

final Person p1 = new Person("金莲",26);
System.out.println(p1);//地址值
//p1 = new Person("涛哥",18);
//System.out.println(p1);//地址值

p1.setName("大郎");
p1.setAge(30);
System.out.println(p1.getName()+"..."+p1.getAge());
}
}

final修饰类

1
2
3
4
1.格式:
public final class 类名{}
2.特点:
final修饰的类不能被继承
1
2
public final class Animal {
}
1
2
public class Dog /*extends Animal*/{
}
final修饰成员变量
1
2
3
4
5
1.格式:
final 数据类型 变量名 = 值
2.特点:
a.需要手动赋值
b.不能二次赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Student {
final String name = "广坤";

public Student() {
}

//有参构造现在属于二次赋值了
/*public Student(String name) {
this.name = name;
}*/

public String getName() {
return name;
}
//set方法现在属于二次赋值了
/*public void setName(String name) {
this.name = name;
}*/
}

final修饰方法

1
2
3
4
5
6
7
8
9
10
1.格式:
修饰符 final 返回值类型 方法名(形参){
方法体
return 结果
}
2.特点:
final修饰的方法,不能被重写

3.注意:
finalabstract不能同时修饰一个方法
1
2
3
4
5
6
7
public abstract class Animal {
public final void eat(){
System.out.println("动物要干饭");
}

//public abstract final void drink();
}
1
2
3
4
5
6
7
public class Dog extends Animal{
/* public void eat(){
System.out.println("狗啃骨头");
}*/

}

final修饰局部变量

1
2
3
4
1.格式:
final 数据类型 变量名 = 值
2.特点:
final修饰的变量不能二次赋值
1
2
3
4
5
6
7
8
9
10
11
public class Test01 {
public static void main(String[] args) {
final int i = 10;
//i = 20; 被final修饰的变量不能二次赋值

final int j;
j = 100;
//j = 200;

}
}

代码块

构造代码块

1
2
3
4
5
6
1.格式:
{
代码
}

2.执行特点:优先于构造方法执行,每new一次,就会执行一次
1
2
3
4
5
6
7
8
9
10
public class Person {
public Person(){
System.out.println("我是无参构造方法");
}

//构造代码块
{
System.out.println("我是构造代码块");
}
}
1
2
3
4
5
6
public class Test01 {
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person();
}
}

静态代码块

1
2
3
4
5
6
7
1.格式:
static{
代码
}

2.执行特点:
静态代码块优先于构造代码块和构造方法执行的,而且只执行一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Person {
public Person(){
System.out.println("我是无参构造方法");
}

//构造代码块
{
System.out.println("我是构造代码块");
}

//静态代码块
static{
System.out.println("我是静态代码块");
}
}
1
2
3
4
5
6
public class Test01 {
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person();
}
}

静态代码块使用场景

1
如果想让一些数据最先初始化,而且只需要初始化一次,就可以将这些数据放到静态代码块中

1703494409653.png

内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1.什么时候使用内部类:
当一个事物的内部,还有一个部分需要完整的结构去描述,而这个内部的完整结构又只为外部事物提供服务,那么整个内部的完成结构最好使用内部类

比如:人类都有心脏,人类本身需要用属性,行为去描述,那么人类内部的心脏也需要心脏特殊的属性和行为去描述,此时心脏就可以定义成内部类,人类中的一个心脏类

2.在java中允许一个类的定义位于另外一个类内部,前者就称之为内部类,后者称之为外部类
class A{
class B{

}
}

类A就是类B的外部类
类B就是类A的内部类

3.分类:
成员内部类(静态,非静态)
局部内部类
匿名内部类(重点)

成员内部类

静态成员内部类(设计模式)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1.格式:直接在定义内部类的时候加上static关键字
public class A{
static class B{

}
}

2.注意:
a.内部类可以定义属性,方法,构造等
b.静态内部类可以被final或者abstract修饰
final修饰之后,不能被继承
abstract修饰之后,不能new
c.静态内部类不能调用外部的非静态成员
d.内部类还可以被四种权限修饰符修饰

3.调用静态内部类成员:
外部类.内部类 对象名 = new 外部类.内部类()
1
2
3
4
5
6
7
8
9
10
11
public class Person {
public void eat(){
System.out.println("人要干饭");
}

static class Heart{
public void jump(){
System.out.println("心脏哐哐哐跳");
}
}
}
1
2
3
4
5
6
7
8
public class Test01 {
public static void main(String[] args) {
// 外部类.内部类 对象名 = new 外部类.内部类()
Person.Heart heart = new Person.Heart();
heart.jump();
}
}

非静态成员内部类
1
2
3
4
5
6
7
8
格式:
public class A{
class B{

}
}
调用非静态内部类成员:
外部类.内部类 对象名 = new 外部类().new 内部类()
1
2
3
4
5
6
7
8
9
10
11
public class Person {
public void eat(){
System.out.println("人要干饭");
}

class Heart{
public void jump(){
System.out.println("心脏哐哐哐跳");
}
}
}
1
2
3
4
5
6
7
public class Test01 {
public static void main(String[] args) {
// 外部类.内部类 对象名 = new 外部类().new 内部类()
Person.Heart heart = new Person(). new Heart();
heart.jump();
}
}

外部类的成员变量和内部类的成员变量以及内部类的局部变量重名时,怎么区分?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Student {
String name = "金莲";
class Heart{
String name = "大郎";
public void display(String name){
System.out.println(name);//内部类的局部变量
System.out.println(this.name);//内部类的成员变量
System.out.println(Student.this.name);//外部类的成员变量
}
}
}


public class Test02 {
public static void main(String[] args) {
Student.Heart heart = new Student().new Heart();
heart.display("涛哥");
}
}

局部内部类

局部内部类基本操作
1
1.可以定义在方法中,代码块中,构造中
1
2
3
4
5
6
7
8
9
10
11
12
public class Person {
public void eat(){
class Heart{
public void jump(){
System.out.println("心脏哐哐哐的跳");
}
}

new Heart().jump();
}
}

1
2
3
4
5
6
7
public class Test01 {
public static void main(String[] args) {
Person person = new Person();
person.eat();
}
}

局部内部类实际操作

接口类型作为方法参数传递和返回

1.接口作为方法参数,传递实参时,传递的是实现类对象

2.接口作为返回值类型返回,实际返回的是实现类对象

1
2
3
public interface USB {
public abstract void open();
}
1
2
3
4
5
6
public class Mouse implements USB{
@Override
public void open() {
System.out.println("鼠标打开");
}
}
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 Test01 {
public static void main(String[] args) {
Mouse mouse = new Mouse();
method(mouse);
System.out.println("================");

USB usb = method01();//USB usb = new Mouse();
usb.open();
}

/*
接口作为方法参数,传递实参时,传递的是实现类对象
*/
public static void method(USB usb){//USB usb = mouse -> 多态
usb.open();
}

/*
接口作为返回值类型返回,实际返回的是实现类对象
*/
public static USB method01(){
//Mouse mouse = new Mouse();
//return mouse;
return new Mouse();
}
}

抽象类作为方法参数和返回值

1.抽象类作为方法参数传递,传递实参时,传递的是其子类对象

2.抽象类作为方法返回值类型返回时,实际返回的是其子类对象

1
2
3
public abstract class Animal {
public abstract void eat();
}
1
2
3
4
5
6
public class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗啃骨头");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test02 {
public static void main(String[] args) {
Dog dog = new Dog();
method01(dog);
System.out.println("=================");
Animal animal = method02();//Animal animal = new Dog()
animal.eat();
}

public static void method01(Animal animal){//Animal animal = dog
animal.eat();
}

public static Animal method02(){
return new Dog();
}
}

普通类做方法参数和返回值

普通类作为方法参数传递,传递的是对象

普通类作为方法返回值返回,返回的是对象

1
2
3
4
5
6
public class Person {
public void eat(){
System.out.println("人要干饭");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test03 {
public static void main(String[] args) {
Person person = new Person();
method01(person);
System.out.println("==================");
Person person1 = method02();//Person person1 = new Person()
person1.eat();
}
public static void method01(Person person){
person.eat();
}

public static Person method02(){
return new Person();
}
}

局部内部类实际操作

1
2
3
4
public interface USB {
void open();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test01 {
public static void main(String[] args) {
USB usb = method();//USB usb = new Mouse()
usb.open();
}

public static USB method(){
//局部内部类
class Mouse implements USB{

@Override
public void open() {
System.out.println("鼠标打开");
}
}

return new Mouse();
}
}

匿名内部类(重点)

所谓的匿名内部类,可以理解为没有显式声明出类名的内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1.问题描述:我们如果想实现接口,简单使用一次抽象方法,我们就需要创建一个实现类,实现这个接口,重写抽象方法,还要new实现类对象,所以我们在想如果就单纯的想使用一次接口中的方法,我们能不能不这么麻烦呢?可以
a.创建实现类,实现接口
b.重写方法
c.创建实现类对象
d.调用方法

2.如果就想单纯的使用一下接口中的方法,我们就没必要经过以上四步了,我们可以四合一

3.匿名内部类怎么学:就按照一种格式来学,这一种格式就代表了实现类对象或者子类对象

4.格式:
new 接口/抽象类(){
重写方法
}.重写的方法();

=================================

类名 对象名 = new 接口/抽象类(){
重写方法
}
对象名.重写的方法();
1
2
3
4
public interface USB {
void open();
void close();
}
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
public class Test01 {
public static void main(String[] args) {
new USB(){

@Override
public void open() {
System.out.println("usb打开了");
}

@Override
public void close() {
System.out.println("usb关闭了");
}
}.open();

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

USB usb = new USB() {
@Override
public void open() {
System.out.println("USB打开了");
}

@Override
public void close() {
System.out.println("USB关闭了");
}
};
usb.open();
usb.close();
}
}

1.什么时候使用匿名内部类:

​ 当简单调用一次接口中的方法,我们就可以使用匿名内部类

2.将一种格式代表实现类对象或者子类对象来看待,来学习

3.匿名内部类会编译生成的,咱们不要管,我们只需要利用咱们讲的格式去new对象,调用重写的方法即可

匿名内部类复杂用法_当参数传递
1
2
3
public interface USB {
void open();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test01 {
public static void main(String[] args) {
method01(new USB() {
@Override
public void open() {
System.out.println("usb打开了");
}
});
}
public static void method01(USB usb){
usb.open();
}
}
匿名内部类复杂用法_当返回值返回
1
2
3
4
public interface USB {
void open();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test02 {
public static void main(String[] args) {
USB usb = method01();
usb.open();

}

public static USB method01(){
return new USB() {
@Override
public void open() {
System.out.println("USB打开了");
}
};
}
}

异常处理

异常介绍

1
1.概述:代码出现了不正常的现象;在java中,异常都是一个一个的类                

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 Demo01Exception {
public static void main(String[] args) throws ParseException {
//错误Error -> StackOverflowError
//method();

//运行时期异常 -> ArrayIndexOutOfBoundsException
int[] arr1 = new int[3];
//System.out.println(arr1[4]);

/*
编译时期异常:
注意看:编译时期异常是我们代码写错了嘛?不是,当我们调用方法的时候
该方法底层给我们抛了一个编译时期异常,所以导致我们一调用此方法
一编译,就爆红了
当我们一旦触发了这个异常,jvm就会将异常信息打印到控制台上,给程序员们看
*/
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = "2000-10-10 10:10:10";
Date date = sdf.parse(time);
System.out.println(date);
}

public static void method(){
method();
}
}

异常出现的过程

创建异常对象(了解)

创建异常对象,只是为了后面学习如何处理异常,其他的暂时没有啥意义

1
2
1.关键字:throw
2.格式: throw new 异常
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo03Exception {
public static void main(String[] args) {
String s = "a.tx1t";
method(s);
}
public static void method(String s){
if (!s.endsWith(".txt")){
//故意创建异常对象,用throw说明此处有异常
throw new NullPointerException();
}
System.out.println("我要执行了");
}
}

异常处理方式(重点)

异常处理方式一_throws

1
2
3
4
1.格式:在方法参数和方法体之间位置上写
throws 异常
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
public class Demo04Exception {
public static void main(String[] args)throws FileNotFoundException {
String s = "a.txt1";
add(s);//添加功能
delete();//删除功能
update();//修改功能
find();//查询功能
}


private static void add(String s)throws FileNotFoundException {
if (!s.endsWith(".txt")) {
//故意创建异常
throw new FileNotFoundException("文件找不到");
}
System.out.println("我要执行了");
}

private static void find() {
System.out.println("查询功能");
}

private static void update() {
System.out.println("修改功能");
}

private static void delete() {
System.out.println("删除功能");
}
}

异常处理方式一_throws多个异常
1
2
3
4
5
1.格式:throws 异常1,异常2

2.注意:
如果throws的多个异常之间有子父类继承关系,我们可以直接throws父类异常
如果不知道多个异常之间是否有子父类继承关系,我们可以直接throws Exception
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
public class Demo05Exception {
public static void main(String[] args)throws /*FileNotFoundException,*//*IOException*/Exception {
String s = null;
add(s);//添加功能
delete();//删除功能
update();//修改功能
find();//查询功能
}


private static void add(String s)throws /*FileNotFoundException,*//*IOException*/Exception {
if (s==null){
//故意造异常
throw new IOException("IO异常");
}
if (!s.endsWith(".txt")) {
//故意创建异常
throw new FileNotFoundException("文件找不到");
}
System.out.println("我要执行了");
}

private static void find() {
System.out.println("查询功能");
}

private static void update() {
System.out.println("修改功能");
}

private static void delete() {
System.out.println("删除功能");
}

}

异常处理方式二_try…catch

1
2
3
4
5
6
1.格式:
try{
可能出现异常的代码
}catch(异常 对象名){
处理异常的代码-> 将来开发会将异常信息保存到日志文件中
}
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
public class Demo06Exception {
public static void main(String[] args){
String s = "a.txt1";
try{
//int[] arr = null;
//System.out.println(arr.length);//NullPointerException
add(s);//添加功能
}catch (FileNotFoundException e){
System.out.println(e);
}

delete();//删除功能
update();//修改功能
find();//查询功能
}


private static void add(String s)throws FileNotFoundException {
if (!s.endsWith(".txt")) {
//故意创建异常
throw new FileNotFoundException("文件找不到");
}
System.out.println("我要执行了");
}

private static void find() {
System.out.println("查询功能");
}

private static void update() {
System.out.println("修改功能");
}

private static void delete() {
System.out.println("删除功能");
}

}

异常处理方式二_多个catch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1.格式:
try{
可能出现异常的代码
}catch(异常 对象名){
处理异常的代码-> 将来开发会将异常信息保存到日志文件中
}catch(异常 对象名){
处理异常的代码-> 将来开发会将异常信息保存到日志文件中
}catch(异常 对象名){
处理异常的代码-> 将来开发会将异常信息保存到日志文件中
}catch(异常 对象名){
处理异常的代码-> 将来开发会将异常信息保存到日志文件中
}...

2.注意:
如果catch的多个异常之间有子父类继承关系,我们可以直接catch父类异常
如果不知道多个异常之间是否有子父类继承关系,我们也可以直接catch Exception
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
public class Demo07Exception {
public static void main(String[] args) {
String s = null;
/* try {
add(s);//添加功能
}catch (FileNotFoundException e){
System.out.println(e);
}catch (IOException e){
System.out.println(e);
}*/

/*try {
add(s);//添加功能
}catch (IOException e){
System.out.println(e);
}*/

try {
add(s);//添加功能
}catch (Exception e){
e.printStackTrace();//将详细的异常信息打印到控制台上
}
delete();//删除功能
update();//修改功能
find();//查询功能
}


private static void add(String s) throws FileNotFoundException, IOException {
if (s == null) {
//故意造异常
throw new IOException("IO异常");
}
if (!s.endsWith(".txt")) {
//故意创建异常
throw new FileNotFoundException("文件找不到");
}
System.out.println("我要执行了");
}

private static void find() {
System.out.println("查询功能");
}

private static void update() {
System.out.println("修改功能");
}

private static void delete() {
System.out.println("删除功能");
}

}

finally关键字
1
2
3
4
5
6
7
8
9
10
1.概述:代表的是不管是否触发了异常,都会执行的代码块
特殊情况:如果之前执行了System.exit(0)终止当前正在执行的java虚拟机
2.使用:都是配合try...catch使用
try{
可能出现异常的代码
}catch(异常 对象名){
处理异常的代码-> 将来开发会将异常信息保存到日志文件中
}finally{
不管是否有异常,都会执行的代码
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Demo08Exception {
public static void main(String[] args){
String s = "a.txt";
try {
add(s);//添加功能
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
System.out.println("我必须滴执行");
}
}


private static void add(String s)throws FileNotFoundException {
if (!s.endsWith(".txt")) {
//故意创建异常
throw new FileNotFoundException("文件找不到");
}
System.out.println("我要执行了");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Demo09Exception {
public static void main(String[] args) {
int result = method();
System.out.println(result);
}

public static int method() {
try {
String s = null;
System.out.println(s.length());//空指针异常
return 2;
} catch (Exception e) {
return 1;
} finally {
System.out.println("我一定要执行");
//return 3;
}
}
}

finally的使用场景:

1.关闭资源

2.原因:对象如果没有用了,GC(垃圾回收器)回收,用来回收堆内存中的垃圾,释放内存,但是有一些对象GC回收不了,比如:连接对象(Connection),IO流对象,Socket对象,这些对象GC回收不了,就需要我们自己手动回收,手动关闭

​ 将来不能回收的对象new完之后,后续操作不管是否操作成功,是否有异常,我们都需要手动关闭,此时我们就可以将关闭资源的代码放到finally中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Test {
public static void main(String[] args) {
FileWriter fw = null;
try {
fw = new FileWriter("day13_exception_object\\1.txt");
fw.write("哈哈哈");//假如这里写失败或者写成功了
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
if (fw!=null){
try {
fw.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

}
}
}
抛异常时注意的事项
1
2
3
4
1.如果父类中的方法抛了异常,那么子类重写之后要不要抛?
可抛可不抛
2.如果父类中的方法没有抛异常,那么子类重写之后要不要抛?
不要抛
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Demo10Exception {
public static void main(String[] args) {

}
class A{
public void method()/*throws Exception*/{

}
}

class B extends A{
@Override
public void method()/*throws Exception*/{

}
}
}

try_catch和throws的使用时机

1
2
1.如果处理异常之后,还想让后续的代码正常执行,我们使用try...catch
2.如果方法之间是递进关系(调用),我们可以先throws,但是到了最后需要用try...catch做一个统一的异常处理

自定义异常

1
1.需求:键盘录入一个用户名,实现登录功能,如果登录失败,抛出LoginUserException
1
2
3
4
5
6
7
8
9
public class LoginUserException extends Exception{
public LoginUserException() {
}

public LoginUserException(String message) {
super(message);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Demo11Exception {
public static void main(String[] args) throws LoginUserException {
//1.定义一个用户名,代表已经注册的用户
String username = "root";
//2.创建Scanner对象,录入用户名
Scanner sc = new Scanner(System.in);
System.out.println("请您输入要登录的用户名:");
String name = sc.next();
//3.判断用户名是否和已经存在的用户名一致
if (name.equals(username)){
System.out.println("登录成功了");
}else{
throw new LoginUserException("登录失败了,用户名或者密码有问题");
}
}
}

1.定义一个类

2.继承 Exception 但不继承 RuntimeException:是编译时异常,必须显式处理(选择上述其中一种处理方式)。

如果你希望调用者必须显式处理登录异常(例如,通过 try-catch 处理或在方法声明中使用 throws 抛出异常),那么你应该继承 Exception,使其成为一个编译时异常。登录失败时,调用者可能会想要执行一些逻辑,例如重新提示用户输入正确的用户名和密码,或者记录登录失败的日志等。这意味着调用者应该明确知道这个异常,并做出相应的处理,而不是直接忽略它。

3.继承 RuntimeException:是运行时异常,不需要显式处理。

打印异常信息的三个方法

1
2
3
4
Throwable类中的方法:
String toString() :输出异常类型和设置的异常信息
String getMessage(): 输出设置的异常信息
void printStackTrace():打印异常信息是最全的:包括异常类型,信息,以及出现的行数等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Demo11Exception {
public static void main(String[] args) {
//1.定义一个用户名,代表已经注册的用户
String username = "root";
//2.创建Scanner对象,录入用户名
Scanner sc = new Scanner(System.in);
System.out.println("请您输入要登录的用户名:");
String name = sc.next();
//3.判断用户名是否和已经存在的用户名一致
if (name.equals(username)) {
System.out.println("登录成功了");
} else {
try {
throw new LoginUserException("登录失败了,用户名或者密码有问题");
}catch (Exception e){
//System.out.println(e.toString());
//System.out.println(e.getMessage());
e.printStackTrace();
}
}
}
}

Object类

1
1.概述:所有类的根类(父类),所有的类都会直接或者间接继承Object类

Object中的toString

1
2
3
4
5
6
7
8
9
10
11
1.Object中的toString方法:返回该对象的字符串表示形式
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

2.注意:
a.如果没有重写Object中的toString方法,直接输出对象名会默认调用Object中的toString方法,直接输出地址值
b.如果重写了Object中的toString方法,再输出地址值,重写没有意义,所以重写完toString之后,应该返回对象的内容

3.总结:
如果直接输出对象名不想输出地址值,就重写Object中的toString方法
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
public class Person {
private String name;
private int age;

public Person() {
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

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

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test01 {
public static void main(String[] args) {
Person p1 = new Person("金莲", 26);
System.out.println(p1);//com.example.b_object.Person@4eec7777
System.out.println(p1.toString());//com.example.b_object.Person@4eec7777

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

ArrayList<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
System.out.println(list);//[张三, 李四, 王五]
System.out.println(list.toString());//[张三, 李四, 王五]
}
}

快速生成toString

alt+insert -> 选择toString -> 直接下一步

Object中的equals

1
2
3
4
5
6
7
8
9
10
11
1.概述:比较两个对象的地址值是否相等
public boolean equals(Object obj) {
return (this == obj);
}

== 针对于基本数据类型来说,比较的是值
== 针对于引用数据类型来说,比较的是地址值

2.注意:
a.如果没有重写Object中的equals方法,那么就会调用Object中的equals方法,比较对象的地址值
b.如果重写了Object中的equals方法,那么就会调用重写后的equals方法,应该比较对象的内容
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 Person {
private String name;
private int age;

public Person() {
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

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

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

/*
问题1:obj直接调用name和age调用不了,因为Object接收了Person类型的对象
属于多态,多态前提下不能直接调用子类特有内容
解决问题1:向下转型

问题2:如果传递的不是Person类型,就会出现类型转换异常
解决问题2:先判断类型,如果是Person类型,再强转成Person

问题3:如果传递null呢?,就不用判断类型了,直接给false

问题4:如果传递自己呢?就不用判断非空了,也不同判断类型了,直接给true
*/
/* public boolean equals(Object obj){
if (this==obj){
return true;
}

if (obj==null){
return false;
}

if (obj instanceof Person){
Person p = (Person) obj;
return this.name.equals(p.name)&&this.age==p.age;
}
return false;

}*/

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}

}
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 Test02 {
public static void main(String[] args) {
Person p1 = new Person("金莲", 26);
Person p2 = new Person("金莲", 26);
System.out.println(p1==p2);//false
System.out.println(p1.equals(p2));//false & true
System.out.println("==============");


ArrayList<String> list = new ArrayList<>();
System.out.println(p1.equals(list));

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

System.out.println(p1.equals(null));

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

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

String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1.equals(s2));//true
}
}

小结:

1.如果直接输出对象名不想输出地址值,重写toString方法

2.如果想比较两个对象的内容,就重写一下equals方法

3.怎么重写:alt+insert -> 选toString 或者equals and hashcode -> 啥也不要管 -> 一路下一步即可

Object中的clone方法

1
2
3
4
1.作用:复制一个属性值一样的新对象
2.使用:
需要被克隆的对象实现Cloneable
重写clone方法
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
public class Person implements Cloneable{
private String name;
private int age;

public Person() {
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

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

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

/*
问题1:obj直接调用name和age调用不了,因为Object接收了Person类型的对象
属于多态,多态前提下不能直接调用子类特有内容
解决问题1:向下转型

问题2:如果传递的不是Person类型,就会出现类型转换异常
解决问题2:先判断类型,如果是Person类型,再强转成Person

问题3:如果传递null呢?,就不用判断类型了,直接给false

问题4:如果传递自己呢?就不用判断非空了,也不同判断类型了,直接给true
*/
/* public boolean equals(Object obj){
if (this==obj){
return true;
}

if (obj==null){
return false;
}

if (obj instanceof Person){
Person p = (Person) obj;
return this.name.equals(p.name)&&this.age==p.age;
}
return false;

}*/

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Test03 {
public static void main(String[] args) throws CloneNotSupportedException {
Person p2 = new Person("涛哥", 16);
Object o = p2.clone();
Person p3 = (Person) o;//克隆了一个新对象

System.out.println(p2==p3);//比较地址值 false
System.out.println(p2.equals(p3));//true
}
}

对象比较大小

java.lang.Comparable

我们知道基本数据类型的数据(除boolean类型外)需要比较大小的话,之间使用比较运算符即可,但是引用数据类型是不能直接使用比较运算符来比较大小的。那么,如何解决这个问题呢?

Java给所有引用数据类型的大小比较,指定了一个标准接口,就是java.lang.Comparable接口:

1
2
3
4
5
package java.lang;

public interface Comparable{
int compareTo(Object obj);
}

那么我们想要使得我们某个类的对象可以比较大小,怎么做呢?步骤:

第一步:哪个类的对象要比较大小,哪个类就实现java.lang.Comparable接口,并重写方法

  • 方法体就是你要如何比较当前对象和指定的另一个对象的大小

第二步:对象比较大小时,通过对象调用compareTo方法,根据方法的返回值决定谁大谁小。

  • this对象(调用compareTo方法的对象)减 指定对象(传入compareTo()的参数对象)大于0,返回正整数
  • this对象(调用compareTo方法的对象)减 指定对象(传入compareTo()的参数对象)小于0 返回负整数
  • this对象(调用compareTo方法的对象)减 指定对象(传入compareTo()的参数对象)等于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
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
public class Student implements Comparable{
private String name;
private int score;

public Student() {

}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getScore() {
return score;
}

public void setScore(int score) {
this.score = score;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}

/*
this:代表students[i]
o:代表students[i+1]

如果students[i].getScore()-students[i+1].getScore()>0
证明数组中的前面一个对象比后面一个对象的分数高
*/
@Override
public int compareTo(Object o) {
Student s = (Student) o;
return this.getScore()- s.getScore();
}
}

测试类

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 class Test01 {
public static void main(String[] args) {
//创建一个数组
Student[] students = new Student[3];
Student s1 = new Student("张三", 100);
Student s2 = new Student("李四", 60);
Student s3 = new Student("王五", 80);
students[0] = s1;
students[1] = s2;
students[2] = s3;

for (int j = 0; j<students.length-1;j++){
for (int i = 0;i<students.length-1-j;i++){
//如果students[i]比students[i+1]大,就排序换位置
if (students[i].compareTo(students[i+1])>0){
Student temp = students[i];
students[i] = students[i+1];
students[i+1] = temp;
}
}
}

//遍历
for (int i = 0; i < students.length; i++) {
System.out.println(students[i]);
}
}
}

java.util.Comparator

思考:

(1)如果一个类,没有实现Comparable接口,而这个类你又不方便修改(例如:一些第三方的类,你只有.class文件,没有源文件),那么这样类的对象也要比较大小怎么办?

(2)如果一个类,实现了Comparable接口,也指定了两个对象的比较大小的规则,但是此时此刻我不想按照它预定义的方法比较大小,但是我又不能随意修改,因为会影响其他地方的使用,怎么办?

JDK在设计类库之初,也考虑到这种情况了,所以又增加了一个java.util.Comparator接口。

1
2
3
4
5
package java.util;

public interface Comparator{
int compare(Object o1,Object o2);
}

那么我们想要比较某个类的两个对象的大小,怎么做呢?步骤:

第一步:编写一个类,我们称之为比较器类型,实现java.util.Comparator接口,并重写方法

  • 方法体就是你要如何指定的两个对象的大小

第二步:比较大小时,通过比较器类型的对象调用compare()方法,将要比较大小的两个对象作为compare方法的实参传入,根据方法的返回值决定谁大谁小。

  • o1对象减o2大于0返回正整数
  • o1对象减o2小于0返回负整数
  • o1对象减o2等于0返回零
1
2
3
4
5
6
7
8
9
10
11
12
public class StudentScoreComparator implements Comparator<Student> {  
/*
如果o1的分数大于o2的分数-> compare方法返回正整数
如果o1的分数小于o2的分数-> compare方法返回负整数
如果o1的分数等于o2的分数-> compare方法返回0
*/
@Override
public int compare(Student o1, Student o2) {
return o1.getScore()-o2.getScore();
}
}

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
public class Test01 {
public static void main(String[] args) {
//创建一个数组
Student[] students = new Student[3];
Student s1 = new Student("张三", 100);
Student s2 = new Student("李四", 60);
Student s3 = new Student("王五", 80);
students[0] = s1;
students[1] = s2;
students[2] = s3;

StudentScoreComparator c = new StudentScoreComparator();

for (int j = 0; j<students.length-1;j++){
for (int i = 0;i<students.length-1-j;i++){
//如果students[i]比students[i+1]大,就排序换位置
if (c.compare(students[i],students[i+1])>0){
Student temp = students[i];
students[i] = students[i+1];
students[i+1] = temp;
}
}
}

//Arrays.sort(students,new StudentScoreComparator());
//遍历
for (int i = 0; i < students.length; i++) {
System.out.println(students[i]);
}
}
}

多线程

多线程基本了解

线程和进程

1
2
3
4
5
进程:在内存中执行的应用程序
线程:是进程中最小的执行单元
线程作用:负责当前进程中程序的运行.一个进程中至少有一个线程,一个进程还可以有多个线程,这样的应用程序就称之为多线程程序

简单理解:一个功能就需要一条线程取去执行

1.使用场景: 软件中的耗时操作 -> 拷贝大文件, 加载大量的资源

​ 所有的聊天软件

​ 所有的后台服务器

​ 一个线程可以干一件事,我们就可以同时做多件事了,提高了CPU的利用率

并发和并行

1
2
3
4
5
6
7
并行:在同一个时刻,有多个执行在多个CPU上(同时)执行(好比是多个人做不同的事儿)
比如:多个厨师在炒多个菜
并发:在同一个时刻,有多个指令在单个CPU上(交替)执行
比如:一个厨师在炒多个菜
细节:
1.之前CPU是单核,但是在执行多个程序的时候好像是在同时执行,原因是CPU在多个线程之间做高速切换
2.现在咱们的CPU都是多核多线程的了,比如24线程,那么CPU可以同时运行4个线程,此时不同切换,但是如果多了,CPU就要切换了,所以现在CPU在执行程序的时候并发和并行都存在

CPU调度

1
2
1.分时调度:值的是让所有的线程轮流获取CPU使用权,并且平均分配每个线程占用CPU的时间片
2.抢占式调度:多个线程轮流抢占CPU使用权,哪个线程先抢到了,哪个线程先执行,一般都是优先级高的先抢到CPU使用权的几率大,我们java程序就是抢占式调度

主线程介绍

1
主线程:CPU和内存之间开辟的专门为main方法服务的线程

创建线程的方式(重点)

extends Thread

入门使用
1
2
3
4
1.定义一个类,继承Thread
2.重写run方法,在run方法中设置线程任务(所谓的线程任务指的是此线程要干的具体的事儿,具体执行的代码)
3.创建自定义线程类的对象
4.调用Thread中的start方法,开启线程,jvm自动调用run方法
1
2
3
4
5
6
7
8
9
10
11
12
public class Test01 {
public static void main(String[] args) {
//创建线程对象
MyThread t1 = new MyThread();
//调用start方法,开启线程,jvm自动调用run方法
t1.start();

for (int i = 0; i < 10; i++) {
System.out.println("main线程..........执行了"+i);
}
}
}
1
2
3
4
5
6
7
8
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("MyThread...执行了"+i);
}
}
}
多线程在内存中的运行原理

1
注意:同一个线程对象不能连续调用多次start,如果想要再次调用start,那么咱们就new一个新的线程对象
Thread类中的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void start() -> 开启线程,jvm自动调用run方法
void run() -> 设置线程任务,这个run方法是Thread重写的接口Runnable中的run方法
String getName() -> 获取线程名字
void setName(String name) -> 给线程设置名字
static Thread currentThread() -> 获取正在执行的线程对象(此方法在哪个线程中使用,获取的就是哪个线程对象)
static void sleep(long millis)->线程睡眠,超时后自动醒来继续执行,传递的是毫秒值

void setPriority(int newPriority) -> 设置线程优先级,优先级越高的线程,抢到CPU使用权的几率越大,但是不是每次都先抢到

int getPriority() -> 获取线程优先级

void setDaemon(boolean on) -> 设置为守护线程,当非守护线程执行完毕,守护线程就要结束,但是守护线程也不是立马结束,当非守护线程结束之后,系统会告诉守护线程人家结束了,你也结束吧,在告知的过程中,守护线程会执行,只不过执行到半路就结束了

static void yield() -> 礼让线程,让当前线程让出CPU使用权

void join() -> 插入线程或者叫做插队线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//线程睡眠
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"...执行了"+i);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test01 {
public static void main(String[] args) throws InterruptedException {
//创建线程对象
MyThread t1 = new MyThread();

//给线程设置名字
t1.setName("金莲");

//调用start方法,开启线程,jvm自动调用run方法
t1.start();

for (int i = 0; i < 10; i++) {
Thread.sleep(1000L);
System.out.println(Thread.currentThread().getName()+"线程..........执行了"+i);
}
}
}

问题:为啥在重写的run方法中有异常只能try,不能throws

原因:继承的Thread中的run方法没有抛异常,所以在子类中重写完run方法之后就不能抛,只能try

线程优先级
1
2
3
4
5
6
7
8
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"执行了......"+i);
}
}
}
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
public class Test01 {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
t1.setName("金莲");

MyThread1 t2 = new MyThread1();
t2.setName("阿庆");

/*
获取两个线程的优先级
MIN_PRIORITY = 1 最小优先级 1
NORM_PRIORITY = 5 默认优先级 5
MAX_PRIORITY = 10 最大优先级 10
*/
//System.out.println(t1.getPriority());
//System.out.println(t2.getPriority());

//设置优先级
t1.setPriority(1);
t2.setPriority(10);

t1.start();
t2.start();
}
}

守护线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test01 {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
t1.setName("金莲");

MyThread2 t2 = new MyThread2();
t2.setName("阿庆");

//将t2设置成守护线程
t2.setDaemon(true);

t1.start();
t2.start();
}
}

1
2
3
4
5
6
7
8
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"执行了......"+i);
}
}
}
1
2
3
4
5
6
7
8
9
public class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"执行了..."+i);
}
}
}

礼让线程
1
2
3
场景说明:如果两个线程一起执行,可能会执行一会儿线程A,再执行一会线程B,或者可能线程A执行完毕了,线程B在执行
那么我们能不能让两个线程尽可能的平衡一点 -> 尽量让两个线程交替执行
注意:只是尽可能的平衡,不是绝对的你来我往,有可能线程A线程执行,然后礼让了,但是回头A又抢到CPU使用权了
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test01 {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
t1.setName("金莲");

MyThread1 t2 = new MyThread1();
t2.setName("阿庆");


t1.start();
t2.start();
}
}
1
2
3
4
5
6
7
8
9
10
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"执行了......"+i);
Thread.yield();
}
}
}

插入线程
1
2
3
4
5
6
7
8
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"执行了......"+i);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test01 {
public static void main(String[] args) throws InterruptedException {
MyThread1 t1 = new MyThread1();
t1.setName("金莲");
t1.start();

/*
表示把t1插入到当前线程之前,t1要插到main线程之前,所以当前线程就是main线程
*/
t1.join();

for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"执行了......"+i);
}
}
}

实现Runnable接口

1
2
3
4
1.创建类,实现Runnable接口
2.重写run方法,设置线程任务
3.利用Thread类的构造方法:Thread(Runnable target),创建Thread对象(线程对象),将自定义的类当参数传递到Thread构造中 -> 这一步是让我们自己定义的类成为一个真正的线程类对象
4.调用Thread中的start方法,开启线程,jvm自动调用run方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test01 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();

/*
Thread(Runnable target)
*/
Thread t1 = new Thread(myRunnable);
//调用Thread中的start方法,开启线程
t1.start();

for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"...执行了"+i);
}
}
}
1
2
3
4
5
6
7
8
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"...执行了"+i);
}
}
}
与继承Thread方式区别
1
2
1.继承Thread:继承只支持单继承,有继承的局限性
2.实现Runnable:没有继承的局限性, MyThread extends Fu implements Runnable
匿名内部类创建多线程

严格意义上来说,匿名内部类方式不属于创建多线程方式其中之一,因为匿名内部类形式建立在实现Runnable接口的基础上完成的

1
2
3
4
5
6
7
8
9
匿名内部类回顾: 
1.new 接口/抽象类(){
重写方法
}.重写的方法();

2.接口名/类名 对象名 = new 接口/抽象类(){
重写方法
}
对象名.重写的方法();
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 Test02 {
public static void main(String[] args) {
/*
Thread(Runnable r)
Thread(Runnable target, String name) :name指的是给线程设置名字
*/

new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"...执行了"+i);
}
}
},"阿庆").start();

new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"...执行了"+i);
}
}
},"金莲").start();
}
}

Callable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.概述:Callable<V>是一个接口,类似于Runnable
2.方法:
V call() -> 设置线程任务的,类似于run方法
3.call方法和run方法的区别:
a.相同点:都是设置线程任务的
b.不同点:
call方法有返回值,而且有异常可以throws
run方法没有返回值,而且有异常不可以throws

4.<V>
a.<V>叫做泛型
b.泛型:用于指定我们操作什么类型的数据,<>中只能写引用数据类型,如果泛型不写,默认是Object类型数据
c.实现Callable接口时,指定泛型是什么类型的,重写的call方法返回值就是什么类型的

5.获取call方法返回值: FutureTask<V>
a. FutureTask<V> 实现了一个接口: Future <V>
b. FutureTask<V>中有一个方法:
V get() -> 获取call方法的返回值
1
2
3
4
5
6
7
public class MyCallable implements Callable<String> {

@Override
public String call() throws Exception {
return "涛哥和金莲...的故事";
}
}
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) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
/*
FutureTask(Callable<V> callable)
*/
FutureTask<String> futureTask = new FutureTask<>(myCallable);

//创建Thread对象-> Thread(Runnable target)
Thread t1 = new Thread(futureTask);
t1.start();

//调用get方法获取call方法返回值
System.out.println(futureTask.get());
}
}

线程池

1
1.问题:之前来一个线程任务,就需要创建一个线程对象去执行,用完还要销毁线程对象,如果线程任务多了,就需要频繁创建线程对象和销毁线程对象,这样会耗费内存资源,所以我们就想线程对象能不能循环利用,用的时候直接拿线程对象,用完还回去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1.如何创建线程池对象:用具类-> Executors
2.获取线程池对象:Executors中的静态方法:
static ExecutorService newFixedThreadPool(int nThreads)
a.参数:指定线程池中最多创建的线程对象条数
b.返回值ExecutorService 是线程池,用来管理线程对象

3.执行线程任务: ExecutorService中的方法
Future<?> submit(Runnable task) 提交一个Runnable任务用于执行
Future<T> submit(Callable<T> task) 提交一个Callable任务用于执行

4.submit方法的返回值:Future接口
用于接收run方法或者call方法返回值的,但是run方法没有返回值,所以可以不用Future接收,执行call方法需要用Future接收

Future中有一个方法:V get() 用于获取call方法返回值

5. ExecutorService中的方法:
void shutdown() 启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务
1
2
3
4
5
6
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"...执行了");
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Test01 {
public static void main(String[] args) {
//创建线程池对象
ExecutorService es = Executors.newFixedThreadPool(2);
es.submit(new MyRunnable());
es.submit(new MyRunnable());
es.submit(new MyRunnable());

//es.shutdown();//关闭线程池对象
}
}
1
2
3
4
5
6
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 1;
}
}
1
2
3
4
5
6
7
8
public class Test02 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService es = Executors.newFixedThreadPool(2);
Future<Integer> future = es.submit(new MyCallable());
System.out.println(future.get());
}
}

线程安全

1
什么时候发生:多个线程同时访问和修改共享资源导致的数据不一致问题

线程安全问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyTicket implements Runnable{
//定义100张票
int ticket = 100;

@Override
public void run() {
while(ticket>0){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
}
}

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 Test01 {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();

Thread t1 = new Thread(myTicket, "赵四");
Thread t2 = new Thread(myTicket, "刘能");
Thread t3 = new Thread(myTicket, "广坤");

t1.start();
t2.start();
t3.start();
/** 导致买到了同一张票

赵四买了第100张票
刘能买了第100张票
刘能买了第98张票
刘能买了第97张票
刘能买了第96张票
广坤买了第100张票
广坤买了第94张票
*/
}
}

synchronized同步锁

1
2
3
4
5
6
7
1.格式:
synchronized(任意对象){
线程可能出现不安全的代码
}
2.任意对象:就是我们的锁对象
3.执行:
一个线程拿到锁之后,会进入到同步代码块中执行,在此期间,其他线程拿不到锁,就进不去同步代码块,需要在同步代码块外面等待排队,需要等着执行的线程执行完毕,出了同步代码块,相当于释放锁了,等待的线程才能抢到锁,才能进入到同步代码块中执行
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
public class MyTicket implements Runnable{
//定义100张票
int ticket = 100;

//任意new一个对象
Object obj = new Object();

@Override
public void run() {
while(ticket>0){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (obj){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}

}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test01 {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();

Thread t1 = new Thread(myTicket, "赵四");
Thread t2 = new Thread(myTicket, "刘能");
Thread t3 = new Thread(myTicket, "广坤");

t1.start();
t2.start();
t3.start();
}
}
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
public class MyTicket implements Runnable{
//定义100张票
int ticket = 100;

@Override
public void run() {
while(true){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//method01();
method02();
}
}

/* public synchronized void method01(){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}*/
public void method02(){
synchronized(this){
System.out.println(this+"..........");
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}

}

}

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
public class MyTicket implements Runnable{
//定义100张票
static int ticket = 100;

@Override
public void run() {
while(true){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//method01();
method02();
}
}

/*public static synchronized void method01(){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}*/
public static void method02(){
synchronized(MyTicket.class){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}

}
}

Lock锁

1
2
3
4
5
1.概述:Lock是一个接口
2.实现类:ReentrantLock
3.方法:
lock() 获取锁
unlock() 释放锁
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 class MyTicket implements Runnable {
//定义100张票
int ticket = 100;

//创建Lock对象
Lock lock = new ReentrantLock();

@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);

//获取锁
lock.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
ticket--;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
//释放锁
lock.unlock();
}

}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test01 {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();

Thread t1 = new Thread(myTicket, "赵四");
Thread t2 = new Thread(myTicket, "刘能");
Thread t3 = new Thread(myTicket, "广坤");

t1.start();
t2.start();
t3.start();
}
}

1
2
synchronized:不管是同步代码块还是同步方法,都会在结束一对{}之后,释放锁对象
Lock:是通过两个方法控制需要被同步的代码,更灵活

不加锁不会产生线程安全的情况

在并发编程中,加锁是为了避免多个线程同时访问和修改共享资源导致的数据不一致问题。然而,在某些情况下,即使不加锁,也可能不会发生线程安全问题。以下是几种常见的情况:

共享的数据是只读的

如果多个线程只读取共享数据而不进行写操作,那么不存在数据竞争的风险,不会出现线程安全问题。只读操作不会引起数据不一致。

示例:

1
2
3
4
5
6
7
8
9
class SharedResource {
private final int sharedValue = 42;

public int getSharedValue() {
return sharedValue;
}
}

// 多个线程只调用 getSharedValue() 方法,没有写操作,因此没有线程安全问题

线程内使用局部变量

局部变量是线程私有的,每个线程都有自己的一份副本,其他线程无法访问和修改。因此,局部变量的操作是线程安全的,不需要加锁。

示例:

1
2
3
4
5
6
7
8
class Worker implements Runnable {
@Override
public void run() {
int localVariable = 0; // 每个线程都有自己独立的 localVariable
localVariable++;
System.out.println(localVariable);
}
}

线程独享的对象

如果某个对象只在一个线程内部使用,而其他线程无法访问或修改该对象,那么这个对象的操作也是线程安全的。即使这个对象涉及修改操作,也不会出现并发问题。

示例:

1
2
3
4
5
6
7
8
class Worker implements Runnable {
@Override
public void run() {
List<String> threadLocalList = new ArrayList<>(); // 每个线程都有自己独立的 ArrayList
threadLocalList.add("Hello");
System.out.println(threadLocalList);
}
}

无状态操作

无状态操作,即不会依赖于共享的可变数据,也不会产生副作用。在这种情况下,多个线程并行执行也不会产生线程安全问题。

示例:

1
2
3
4
5
class Calculator {
public int add(int a, int b) {
return a + b; // 纯粹的计算操作,无状态
}
}

原子操作

某些操作本身是原子的(不可分割的),即使在多线程环境下,也能保证执行过程中的完整性和一致性。例如,Java 中的 AtomicIntegerAtomicBoolean 等类,它们内部通过原子操作实现了线程安全。

示例:

1
2
3
4
5
AtomicInteger counter = new AtomicInteger(0);

public void increment() {
counter.incrementAndGet(); // 这个方法是原子的,线程安全
}

不可变对象

不可变对象在创建之后,无法被修改,所有对不可变对象的操作都是线程安全的。例如,Java 中的 StringInteger 是不可变对象。多个线程可以安全地共享这些对象,而不会出现线程安全问题。

示例:

1
2
3
4
5
6
7
8
class Worker implements Runnable {
private final String sharedString = "immutable";

@Override
public void run() {
System.out.println(sharedString); // 不可变对象,线程安全
}
}

ThreadLocal 变量

ThreadLocal 提供了每个线程独立的一份数据副本,每个线程都可以独立访问和修改它的副本,而不会与其他线程产生冲突。

示例:

1
2
3
4
5
6
7
public class ThreadLocalExample {
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);

public void increment() {
threadLocal.set(threadLocal.get() + 1); // 每个线程都有自己的 ThreadLocal 变量副本
}
}

死锁(了解)

死锁介绍(锁嵌套就有可能产生死锁)

1
指的是两个或者两个以上的线程在执行的过程中由于竞争同步锁而产生的一种阻塞现象;如果没有外力的作用,他们将无法继续执行下去,这种情况称之为死锁

1
2
3
根据上图所示:线程1正在持有锁1,但是线程1必须再拿到锁2,才能继续执行
而线程2正在持有锁2,但是线程2需要再拿到锁1,才能继续执行
此时两个线程处于互相等待的状态,就是死锁,在程序中的死锁将出现在同步代码块的嵌套中

死锁的分析

代码实现

1
2
3
4
public class LockA {
public static LockA lockA = new LockA();
}

1
2
3
public class LockB {
public static LockB lockB = new LockB();
}
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 DieLock implements Runnable{
private boolean flag;

public DieLock(boolean flag) {
this.flag = flag;
}

@Override
public void run() {
if (flag){
synchronized (LockA.lockA){
System.out.println("if...lockA");
synchronized (LockB.lockB){
System.out.println("if...lockB");
}
}
}else{
synchronized (LockB.lockB){
System.out.println("else...lockB");
synchronized (LockA.lockA){
System.out.println("else...lockA");
}
}
}
}
}

1
2
3
4
5
6
7
8
9
10
11
public class Test01 {
public static void main(String[] args) {
DieLock dieLock1 = new DieLock(true);
DieLock dieLock2 = new DieLock(false);

new Thread(dieLock1).start();
new Thread(dieLock2).start();
}
}


只需要知道死锁出现的原因即可(锁嵌套),以后尽量避免锁嵌套

线程状态

线程状态介绍

1
2
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State这个枚举中给出了六种线程状态:
这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析。
线程状态 导致状态发生条件
NEW(新建) 线程刚被创建,但是并未启动。还没调用start方法。
Runnable(可运行) 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
Blocked(锁阻塞) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待) 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
Timed Waiting(计时等待) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Terminated(被终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。或者调用过时方法stop()

线程状态图

等待唤醒机制

等待唤醒案例分析(线程之间的通信)

1
要求:一个线程生产,一个线程消费,不能连续生产,不能连续消费 -> 等待唤醒机制(生产者,消费者)(线程之间的通信)
方法 说明
void wait() 线程等待,等待的过程中线程会释放锁,需要被其他线程调用notify方法将其唤醒,重新抢锁执行
void notify() 线程唤醒,一次唤醒一个等待线程;如果有多条线程等待,则随机唤醒一条等待线程
void notifyAll() 唤醒所有等待线程

wait和notify方法需要锁对象调用,所以需要用到同步代码块中,而且必须是同一个锁对象

等待唤醒案例实现

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
/*
count和flag可以定义成包装类
但是要记得给count和flag手动赋值
不然对于本案例来说,容易出现空指针异常
*/
public class BaoZiPu {
//代表包子的count
private int count;
//代表是否有包子的flag
private boolean flag;

public BaoZiPu() {
}

public BaoZiPu(int count, boolean flag) {
this.count = count;
this.flag = flag;
}

/*
getCount 改造成消费包子方法
直接输出count
*/
public void getCount() {
System.out.println("消费了..............第"+count+"个包子");
}

/*
setCount 改造成生产包子
count++
*/
public void setCount() {
count++;
System.out.println("生产了...第"+count+"个包子");
}

public boolean isFlag() {
return flag;
}

public void setFlag(boolean flag) {
this.flag = flag;
}
}

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
public class Product implements Runnable{
private BaoZiPu baoZiPu;

public Product(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}

@Override
public void run() {
while(true){

try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

synchronized (baoZiPu){
//1.判断flag是否为true,如果是true,证明有包子,生产线程等待
if (baoZiPu.isFlag()==true){
try {
baoZiPu.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

//2.如果flag为false,证明没有包子,开始生产
baoZiPu.setCount();
//3.改变flag状态,为true,证明生产完了,有包子了
baoZiPu.setFlag(true);
//4.唤醒消费线程
baoZiPu.notify();
}
}
}
}

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
public class Consumer implements Runnable{
private BaoZiPu baoZiPu;

public Consumer(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}

@Override
public void run() {
while(true){

try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

synchronized (baoZiPu){
//1.判断flag是否为false,如果是false,证明没有包子,消费线程等待
if (baoZiPu.isFlag()==false){
try {
baoZiPu.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

//2.如果flag为true,证明有包子,开始消费
baoZiPu.getCount();
//3.改变flag状态,为false,证明消费完了,没 有包子了
baoZiPu.setFlag(false);
//4.唤醒生产线程
baoZiPu.notify();
}
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test01 {
public static void main(String[] args) {
BaoZiPu baoZiPu = new BaoZiPu();

Product product = new Product(baoZiPu);
Consumer consumer = new Consumer(baoZiPu);

Thread t1 = new Thread(product);
Thread t2 = new Thread(consumer);

t1.start();
t2.start();
}
}

用同步方法改造等待唤醒案例

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
/*
count和flag可以定义成包装类
但是要记得给count和flag手动赋值
不然对于本案例来说,容易出现空指针异常
*/
public class BaoZiPu {
//代表包子的count
private int count;
//代表是否有包子的flag
private boolean flag;

public BaoZiPu() {
}

public BaoZiPu(int count, boolean flag) {
this.count = count;
this.flag = flag;
}

/*
getCount 改造成消费包子方法
直接输出count
*/
public synchronized void getCount() {
//1.判断flag是否为false,如果是false,证明没有包子,消费线程等待
if (this.flag == false) {
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

//2.如果flag为true,证明有包子,开始消费
System.out.println("消费了..............第" + count + "个包子");

//3.改变flag状态,为false,证明消费完了,没 有包子了
this.flag = false;
//4.唤醒生产线程
this.notify();
}

/*
setCount 改造成生产包子
count++
*/
public synchronized void setCount() {
//1.判断flag是否为true,如果是true,证明有包子,生产线程等待
if (this.flag == true) {
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

//2.如果flag为false,证明没有包子,开始生产
count++;
System.out.println("生产了...第" + count + "个包子");
//3.改变flag状态,为true,证明生产完了,有包子了
this.flag = true;
//4.唤醒消费线程
this.notify();
}

public boolean isFlag() {
return flag;
}

public void setFlag(boolean flag) {
this.flag = flag;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Product implements Runnable{
private BaoZiPu baoZiPu;

public Product(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}

@Override
public void run() {
while(true){

try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
baoZiPu.setCount();
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Consumer implements Runnable{
private BaoZiPu baoZiPu;

public Consumer(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}

@Override
public void run() {
while(true){

try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

baoZiPu.getCount();
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test01 {
public static void main(String[] args) {
BaoZiPu baoZiPu = new BaoZiPu();

Product product = new Product(baoZiPu);
Consumer consumer = new Consumer(baoZiPu);

Thread t1 = new Thread(product);
Thread t2 = new Thread(consumer);

t1.start();
t2.start();
}
}

多等待多唤醒

解决多生产多消费问题(if改为while,将notify改为notifyAll)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test01 {
public static void main(String[] args) {
BaoZiPu baoZiPu = new BaoZiPu();

Product product = new Product(baoZiPu);
Consumer consumer = new Consumer(baoZiPu);

new Thread(product).start();
new Thread(product).start();
new Thread(product).start();

new Thread(consumer).start();
new Thread(consumer).start();
new Thread(consumer).start();
}
}

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
/*
count和flag可以定义成包装类
但是要记得给count和flag手动赋值
不然对于本案例来说,容易出现空指针异常
*/
public class BaoZiPu {
//代表包子的count
private int count;
//代表是否有包子的flag
private boolean flag;

public BaoZiPu() {
}

public BaoZiPu(int count, boolean flag) {
this.count = count;
this.flag = flag;
}

/*
getCount 改造成消费包子方法
直接输出count
*/
public synchronized void getCount() {
//1.判断flag是否为false,如果是false,证明没有包子,消费线程等待
while (this.flag == false) {
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

//2.如果flag为true,证明有包子,开始消费
System.out.println("消费了..............第" + count + "个包子");

//3.改变flag状态,为false,证明消费完了,没 有包子了
this.flag = false;
//4.唤醒所有等待线程
this.notifyAll();
}

/*
setCount 改造成生产包子
count++
*/
public synchronized void setCount() {
//1.判断flag是否为true,如果是true,证明有包子,生产线程等待
while (this.flag == true) {
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

//2.如果flag为false,证明没有包子,开始生产
count++;
System.out.println("生产了...第" + count + "个包子");
//3.改变flag状态,为true,证明生产完了,有包子了
this.flag = true;
//4.唤醒所有等待线程
this.notifyAll();
}

public boolean isFlag() {
return flag;
}

public void setFlag(boolean flag) {
this.flag = flag;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Product implements Runnable{
private BaoZiPu baoZiPu;

public Product(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}

@Override
public void run() {
while(true){

try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
baoZiPu.setCount();
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Consumer implements Runnable{
private BaoZiPu baoZiPu;

public Consumer(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}

@Override
public void run() {
while(true){

try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

baoZiPu.getCount();
}
}
}

集合

1
2
3
4
5
6
7
8
9
10
11
12
13
1.之前我们学了保存数据的有:变量,数组,但是数组定长,所以如果添加一个数据或者删除一个数据,数组并不好使,需要创建新数组,所以接下来我们学一个长度可变的容器,集合

2.集合的特点
a.只能存储引用数据类型的数据
b.长度可变
c.集合中有大量的方法,方便我们操作

3.分类:
a.单列集合:一个元素就一个组成部分:
list.add("张三")
b.双列集合:一个元素有两部分构成: key 和 value
map.put("涛哥","金莲") -> key,value叫做键值对

单列集合Collection接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1.概述:单列集合的顶级接口
2.使用:
a.创建:
Collection<E> 对象名 = new 实现类对象<E>()
b.<E>:泛型,决定了集合中能存储什么类型的数据,可以统一元素类型
泛型中只能写引用数据类型,如果不写,默认Object类型,此时什么类型的数据都可以存储了
<int> 不行
<Integer> 行
<Person> 行

c.泛型细节:
我们等号前面的泛型必须写,等号后面的泛型可以不写,jvm会根据前面的泛型推导出后面的泛型是啥

3.常用方法:
boolean add(E e) : 将给定的元素添加到当前集合中(我们一般调add时,不用boolean接收,因为add一定会成功)
boolean addAll(Collection<? extends E> c) :将另一个集合元素添加到当前集合中 (集合合并)
void clear():清除集合中所有的元素
boolean contains(Object o) :判断当前集合中是否包含指定的元素
boolean isEmpty() : 判断当前集合中是否有元素->判断集合是否为空
boolean remove(Object o):将指定的元素从集合中删除
int size() :返回集合中的元素个数。
Object[] toArray(): 把集合中的元素,存储到数组中
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
public class Demo01Collection {
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
//boolean add(E e) : 将给定的元素添加到当前集合中(我们一般调add时,不用boolean接收,因为add一定会成功)
collection.add("萧炎");
collection.add("萧薰儿");
collection.add("彩鳞");
collection.add("小医仙");
collection.add("云韵");
collection.add("涛哥");
System.out.println(collection);
//boolean addAll(Collection<? extends E> c) :将另一个集合元素添加到当前集合中 (集合合并)
Collection<String> collection1 = new ArrayList<>();
collection1.add("张无忌");
collection1.add("小昭");
collection1.add("赵敏");
collection1.add("周芷若");
collection1.addAll(collection);
System.out.println(collection1);

//void clear():清除集合中所有的元素
collection1.clear();
System.out.println(collection1);
//boolean contains(Object o) :判断当前集合中是否包含指定的元素
boolean result01 = collection.contains("涛哥");
System.out.println("result01 = " + result01);
//boolean isEmpty() : 判断当前集合中是否有元素->判断集合是否为空
System.out.println(collection1.isEmpty());
//boolean remove(Object o):将指定的元素从集合中删除
collection.remove("涛哥");
System.out.println(collection);
//int size() :返回集合中的元素个数。
System.out.println(collection.size());
//Object[] toArray(): 把集合中的元素,存储到数组中
Object[] arr = collection.toArray();
System.out.println(Arrays.toString(arr));
}
}

迭代器

迭代器基本使用

1
2
3
4
5
6
7
1.概述:Iterator接口
2.主要作用:遍历集合
3.获取:Collection中的方法:
Iterator<E> iterator()
4.方法:
boolean hasNext() -> 判断集合中有没有下一个元素
E next() ->获取下一个元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Demo01Iterator {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("楚雨荨");
list.add("慕容云海");
list.add("端木磊");
list.add("上官瑞谦");
list.add("叶烁");
//获取迭代器对象
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String element = iterator.next();
System.out.println(element);
}
}
}

注意:next方法在获取的时候不要连续使用多次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Demo02Iterator {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("楚雨荨");
list.add("慕容云海");
list.add("端木磊");
list.add("上官瑞谦");
list.add("叶烁");
//获取迭代器对象
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String element = iterator.next();
System.out.println(element);
//String element2 = iterator.next();
//System.out.println(element2);
}
}
}

NoSuchElementException:没有可操作的元素异常

迭代器迭代过程

1
2
int cursor; //下一个元素索引位置
int lastRet = -1;//上一个元素索引位置

迭代器底层原理

1
2
3
4
1.获取Iterator的时候怎么获取的:
Iterator iterator = list.iterator()
我们知道Iterator是一个接口,等号右边一定是它的实现类对象
问题:Iterator接收的到底是哪个实现类对象呢? -> ArrayList中的内部类Itr对象

注意:只有ArrayList使用迭代器的时候Iterator接口才会指向Itr,其他的集合使用迭代器Iterator就指向的不是Itr了

1
2
HashSet<String> set = new HashSet<>();
Iterator<String> iterator1 = set.iterator();

并发修改异常

1
需求:定义一个集合,存储 唐僧,孙悟空,猪八戒,沙僧,遍历集合,如果遍历到猪八戒,往集合中添加一个白龙马
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Demo03Iterator {
public static void main(String[] args) {
//需求:定义一个集合,存储 唐僧,孙悟空,猪八戒,沙僧,遍历集合,如果遍历到猪八戒,往集合中添加一个白龙马
ArrayList<String> list = new ArrayList<>();
list.add("唐僧");
list.add("孙悟空");
list.add("猪八戒");
list.add("沙僧");

Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String element = iterator.next();
if ("猪八戒".equals(element)){
list.add("白龙马");
}
}
System.out.println(list);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
String element = iterator.next();

private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such

/*
modCount: 实际操作次数
expectedModCount:预期操作次数
*/
int expectedModCount = modCount;

@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
}

final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
1
结论:当预期操作次数和实际操作次数不相等了,会出现"并发修改异常"
1
我们干了什么事儿,让实际操作次数和预期操作次数不相等了
1
2
3
4
5
6
7
list.add("白龙马")
====================================
public boolean add(E e) {
modCount++;//实际操作次数+1
}
====================================
最终结论:我们调用了add方法,而add方法底层只给modCount++,但是再次调用next方法的时候,并没有给修改后的modCount重新赋值给expectedModCount,导致next方法底层的判断判断出实际操作次数和预期操作次数不相等了,所以抛出了"并发修改异常"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 public class Demo03Iterator {
public static void main(String[] args) {
/*需求:定义一个集合,存储 唐僧,孙悟空,猪八戒,沙僧,遍历集合,如果遍历到猪八戒,往集合中添加一个白龙马*/
ArrayList<String> list = new ArrayList<>();
list.add("唐僧");
list.add("孙悟空");
list.add("猪八戒");
list.add("沙僧");

//Iterator<String> iterator = list.iterator();
ListIterator<String> listIterator = list.listIterator();
while(listIterator.hasNext()){
String element = listIterator.next();
if ("猪八戒".equals(element)){
listIterator.add("白龙马");
}
}
System.out.println(list);
}
}

使用迭代器迭代集合的过程中,不要随意修改集合长度,容易出现并发修改异常

数据结构

1
数据结构是一种具有一定逻辑关系,在计算机中应用某种存储结构,并且封装了相应操作的数据元素集合。它包含三方面的内容,逻辑关系、存储关系及操作。
1
随着应用程序变得越来越复杂和数据越来越丰富,几百万、几十亿甚至几百亿的数据就会出现,而对这么大对数据进行搜索、插入或者排序等的操作就越来越慢,数据结构就是用来解决这些问题的。

数据的逻辑结构指反映数据元素之间的逻辑关系,而与他们在计算机中的存储位置无关:

  • 集合(数学中集合的概念):数据结构中的元素之间除了“同属一个集合” 的相互关系外,别无其他关系;
  • 线性结构:数据结构中的元素存在一对一的相互关系;
  • 树形结构:数据结构中的元素存在一对多的相互关系;
  • 图形结构:数据结构中的元素存在多对多的相互关系。

1
2
3
1.特点:
先进后出
2.好比:手枪压子弹

队列

1
2
1.特点:先进先出
2.好比:过安检

数组

1
2
3
4
5
6
7
1.特点:查询快,增删慢
2.查询快:因为有索引,我们可以直接通过索引操作元素
增删慢:因为数组定长
a.添加元素:创建新数组,将老数组中的元素复制到新数组中去,在最后添加新元素;要是从中间插入就更麻烦了
插入完新元素,后面的元素都要往后移动
b.删除元素:创建新数组,将老数组中的元素复制到新数组中去,被删除的元素就不复制了;如果要是从之间删除
被删除的元素后面的元素都要往前移动

链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1.在集合中涉及到了两种链表
2.单向链表
a.节点:一个节点分为两部分
第一部分:数据域(存数据)
第二部分:指针域(保存下一个节点地址)
b.特点:前面节点记录后面节点的地址,但是后面节点地址不记录前面节点地址

3.双向链表:
a.节点:一个节点分为三部分
第一部分:指针域(保存上一个节点地址)
第二部分:数据域(保存的数据)
第三部分:指针域(保存下一个节点地址)
b.特点:
前面节点记录后面节点地址,后面节点也记录前面节点地址

4.链表结构特点:查询慢,增删快
单向链表
1
2
3
4
a.节点:一个节点分为两部分
第一部分:数据域(存数据)
第二部分:指针域(保存下一个节点地址)
b.特点:前面节点记录后面节点的地址,但是后面节点地址不记录前面节点地址

双向链表
1
2
3
4
5
6
a.节点:一个节点分为三部分
第一部分:指针域(保存上一个节点地址)
第二部分:数据域(保存的数据)
第三部分:指针域(保存下一个节点地址)
b.特点:
前面节点记录后面节点地址,后面节点也记录前面节点地址

List接口

1
2
3
1.概述:是Collection接口的子接口
2.常见的实现类:
ArrayList LinkedList Vector

ArrayList集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1.概述:ArrayList是List接口的实现类
2.特点:
a.元素有序-> 按照什么顺序存的,就按照什么顺序取
b.元素可重复
c.有索引-> 可以利用索引去操作元素
d.线程不安全

3.数据结构:数组
4.常用方法:
boolean add(E e) -> 将元素添加到集合中->尾部(add方法一定能添加成功的,所以我们不用boolean接收返回值)
void add(int index, E element) ->在指定索引位置上添加元素
boolean remove(Object o) ->删除指定的元素,删除成功为true,失败为false
E remove(int index) -> 删除指定索引位置上的元素,返回的是被删除的那个元素
E set(int index, E element) -> 将指定索引位置上的元素,修改成后面的element元素
E get(int index) -> 根据索引获取元素
int size() -> 获取集合元素个数
ArrayList集合使用
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
public class Demo01ArrayList {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
//boolean add(E e) -> 将元素添加到集合中->尾部(add方法一定能添加成功的,所以我们不用boolean接收返回值)
list.add("铁胆火车侠");
list.add("喜洋洋");
list.add("火影忍者");
list.add("灌篮高手");
list.add("网球王子");
System.out.println(list);
//void add(int index, E element) ->在指定索引位置上添加元素
list.add(2,"涛哥");
System.out.println(list);
//boolean remove(Object o) ->删除指定的元素,删除成功为true,失败为false
list.remove("涛哥");
System.out.println(list);
//E remove(int index) -> 删除指定索引位置上的元素,返回的是被删除的那个元素
String element = list.remove(0);
System.out.println(element);
System.out.println(list);
//E set(int index, E element) -> 将指定索引位置上的元素,修改成后面的element元素
String element2 = list.set(0, "金莲");
System.out.println(element2);
System.out.println(list);
//E get(int index) -> 根据索引获取元素
System.out.println(list.get(0));
//int size() -> 获取集合元素个数
System.out.println(list.size());
}
}

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
public class Demo02ArrayList {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("铁胆火车侠");
list.add("喜洋洋");
list.add("火影忍者");
list.add("灌篮高手");
list.add("网球王子");

Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}

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

for (int i = 0;i<list.size();i++){
System.out.println(list.get(i));
}

System.out.println("=====================");
/*
遍历带有索引集合的快捷键
集合名.fori
*/
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}

}
}

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 class Demo03ArrayList {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(2);
System.out.println(list);

/*
需求:删除2
remove(Object o) -> 直接删除指定元素
remove(int index) -> 删除指定索引位置上的元素

如果remove中直接传递整数,默认调用按照指定索引删除元素的remove
但是此时list中没有2索引,所以越界

解决:我们可以将2包装成包装类,变成包装类之后,其父类就是Object了,


*/
//list.remove(2);
list.remove(Integer.valueOf(2));
System.out.println(list);
}
}

底层源码分析
1
2
3
4
5
6
7
8
9
10
1.ArrayList构造方法:
a.ArrayList() 构造一个初始容量为十的空列表
b.ArrayList(int initialCapacity) 构造具有指定初始容量的空列表

2.ArrayList源码总结:
a.不是一new底层就会创建初始容量为10的空列表,而是第一次add的时候才会创建初始化容量为10的空列表
b.ArrayList底层是数组,那么为啥还说集合长度可变呢?
ArrayList底层会自动扩容-> Arrays.copyOf
c.扩容多少倍?
1.5
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
ArrayList() 构造一个初始容量为十的空列表
=========================================
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
Object[] elementData; ->ArrayList底层的那个数组

public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

=========================================
list.add("a");

public boolean add(E e) {
modCount++;
add(e, elementData, size);// e->要存的元素 elementData->集合数组,长度开始为0,size->0
return true;
}

private void add(E e->元素, Object[] elementData->集合数组, int s->0) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}

private Object[] grow() {
return grow(size + 1);
}

private Object[] grow(int minCapacity->1) {
int oldCapacity = elementData.length;//0
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY->10, minCapacity->1)];
}
}
==========================================
假设ArrayList中存了第11个元素,会自动扩容-> Arrays.copyOf

private Object[] grow(int minCapacity) {//11
int oldCapacity = elementData.length;//10
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity(15) = ArraysSupport.newLength(oldCapacity->10,
minCapacity - oldCapacity->1, /* minimum growth */
oldCapacity >> 1 ->5 /* preferred growth */);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}


public static int newLength(int oldLength->10, int minGrowth->1, int prefGrowth->5) {
// preconditions not checked because of inlining
// assert oldLength >= 0
// assert minGrowth > 0

int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // 15
if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
return prefLength;
} else {
// put code cold in a separate method
return hugeLength(oldLength, minGrowth);
}
}
1
ArrayList(int initialCapacity) 构造具有指定初始容量的空列表 
1
2
3
4
5
6
7
8
9
10
11
12
ArrayList<String> list = new ArrayList<>(5);
==============================================
public ArrayList(int initialCapacity->5) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];//直接创建长度为5的数组
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
1
2
3
4
5
6
7
ArrayList<String>  list = new  ArrayList<String>() -> 现在我们想用都是new

但是将来开发不会想使用就new集合,都是调用一个方法,查询出很多数据来,此方法返回一个集合,自动将查询出来的数据放到集合中,我们想在页面上展示数据,遍历集合

而且将来调用方法,返回的集合类型,一般都是接口类型

List<泛型> list = 对象.查询方法()

LinkedList集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1.概述:LinkedList是List接口的实现类
2.特点:
a.元素有序
b.元素可重复
c.有索引 -> 这里说的有索引仅仅指的是有操作索引的方法,不代表本质上具有索引
d.线程不安全

3.数据结构:双向链表

4.方法:有大量直接操作首尾元素的方法
- public void addFirst(E e):将指定元素插入此列表的开头。
- public void addLast(E e):将指定元素添加到此列表的结尾。
- public E getFirst():返回此列表的第一个元素。
- public E getLast():返回此列表的最后一个元素。
- public E removeFirst():移除并返回此列表的第一个元素。
- public E removeLast():移除并返回此列表的最后一个元素。
- public E pop():从此列表所表示的堆栈处弹出一个元素。
- public void push(E e):将元素推入此列表所表示的堆栈。
- public boolean isEmpty():如果列表没有元素,则返回true
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
public class Demo05LinkedList {
public static void main(String[] args) {
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("吕布");
linkedList.add("刘备");
linkedList.add("关羽");
linkedList.add("张飞");
linkedList.add("貂蝉");
System.out.println(linkedList);

linkedList.addFirst("孙尚香");
System.out.println(linkedList);

linkedList.addLast("董卓");
System.out.println(linkedList);

System.out.println(linkedList.getFirst());
System.out.println(linkedList.getLast());

linkedList.removeFirst();
System.out.println(linkedList);

linkedList.removeLast();
System.out.println(linkedList);

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

Iterator<String> iterator = linkedList.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}

System.out.println("=======================");
for (int i = 0; i < linkedList.size(); i++) {
System.out.println(linkedList.get(i));
}
}
}

1
2
public E pop():从此列表所表示的堆栈处弹出一个元素。
public void push(E e):将元素推入此列表所表示的堆栈。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Demo06LinkedList {
public static void main(String[] args) {
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("吕布");
linkedList.add("刘备");
linkedList.add("关羽");
linkedList.add("张飞");
linkedList.add("貂蝉");

//public E pop():从此列表所表示的堆栈处弹出一个元素。
linkedList.pop();
System.out.println(linkedList);
//public void push(E e):将元素推入此列表所表示的堆栈。
linkedList.push("涛哥");
System.out.println(linkedList);
}
}

LinkedList底层成员解释说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1.LinkedList底层成员
transient int size = 0; 元素个数
transient Node<E> first; 第一个节点对象
transient Node<E> last; 最后一个节点对象

2.Node代表的是节点对象
private static class Node<E> {
E item;//节点上的元素
Node<E> next;//记录着下一个节点地址
Node<E> prev;//记录着上一个节点地址

Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
LinkedList中add方法源码分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
LinkedList<String> list = new LinkedList<>();
list.add("a");
list.add("b");

void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}

LinkedList中get方法源码分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}

Node<E> node(int index) {
// assert isElementIndex(index);

if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
1
index < (size >> 1)采用二分思想,先将index与长度size的一半比较,如果index<size/2,就只从位置0往后遍历到位置index处,而如果index>size/2,就只从位置size往前遍历到位置index处。这样可以减少一部分不必要的遍历
增强for
1
2
3
4
5
6
7
8
1.作用:
遍历集合或者数组
2.格式:
for(元素类型 变量名:要遍历的集合名或者数组名){
变量名就是代表的每一个元素
}

3.快捷键:集合名或者数组名.for
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Demo01ForEach {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");
for (String s : list) {
System.out.println(s);
}

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

int[] arr = {1,2,3,4,5};
for (int i : arr) {
System.out.println(i);
}
}
}

注意

1
2
1.增强for遍历集合时,底层实现原理为迭代器
2.增强for遍历数组时,底层实现原理为普通for

所以不管是用迭代器还是使用增强for,在遍历集合的过程中都不要随意修改集合长度,否则会出现并发修改异常

Collections集合工具类

1
2
3
4
5
6
7
8
9
10
11
12
1.概述:集合工具类
2.特点:
a.构造私有
b.方法都是静态的

3.使用:类名直接调用

4.方法:
static <T> boolean addAll(Collection<? super T> c, T... elements)->批量添加元素
static void shuffle(List<?> list) ->将集合中的元素顺序打乱
static <T> void sort(List<T> list) ->将集合中的元素按照默认规则排序
static <T> void sort(List<T> list, Comparator<? super T> c)->将集合中的元素按照指定规则排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Demo01Collections {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
//static <T> boolean addAll(Collection<? super T> c, T... elements)->批量添加元素
Collections.addAll(list,"张三","李四","王五","赵六","田七","朱八");
System.out.println(list);
//static void shuffle(List<?> list) ->将集合中的元素顺序打乱
Collections.shuffle(list);
System.out.println(list);
//static <T> void sort(List<T> list) ->将集合中的元素按照默认规则排序-> ASCII码表
ArrayList<String> list1 = new ArrayList<>();
list1.add("c.举头望明月");
list1.add("a.床前明月光");
list1.add("d.低头思故乡");
list1.add("b.疑是地上霜");
Collections.sort(list1);
System.out.println(list1);
}
}

1
2
3
4
5
6
7
1.方法:static <T> void sort(List<T> list, Comparator<? super T> c)->将集合中的元素按照指定规则排序

2.Comparator比较器
a.方法:
int compare(T o1,T o2)
o1-o2 -> 升序
o2-o1 -> 降序
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 Person {
private String name;
private Integer age;

public Person() {
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

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

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Demo02Collections {
public static void main(String[] args) {
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("柳岩",18));
list.add(new Person("涛哥",16));
list.add(new Person("金莲",20));

Collections.sort(list, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge()-o2.getAge();
}
});

System.out.println(list);
}
}

1
2
1.接口:Comparable接口
2.方法: int compareTo(T o) -> this-o (升序) o-this(降序)
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
public class Student implements Comparable<Student>{
private String name;
private Integer score;

public Student() {
}

public Student(String name, Integer score) {
this.name = name;
this.score = score;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getScore() {
return score;
}

public void setScore(Integer score) {
this.score = score;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}

@Override
public int compareTo(Student o) {
return this.getScore()-o.getScore();
}
}

1
2
3
4
5
6
7
8
9
10
11
public class Demo03Collections {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<>();
list.add(new Student("涛哥",100));
list.add(new Student("柳岩",150));
list.add(new Student("三上",80));
Collections.sort(list);
System.out.println(list);
}
}

1
2
Arrays中的静态方法:
static <T> List<T> asList(T...a) -> 直接指定元素,转存到list集合中
1
2
3
4
5
6
public class Demo04Collections {
public static void main(String[] args) {
List<String> list = Arrays.asList("张三", "李四", "王五");
System.out.println(list);
}
}

泛型

1
2
3
4
5
6
1.泛型:<>
2.作用:
统一数据类型,防止将来的数据转换异常
3.注意:
a.泛型中的类型必须是引用类型
b.如果泛型不写,默认类型为Object

为什么要使用泛型

1
2
3
4
1.从使用层面上来说:
统一数据类型,防止将来的数据类型转换异常
2.从定义层面上来看:
定义带泛型的类,方法等,将来使用的时候给泛型确定什么类型,泛型就会变成什么类型,凡是涉及到泛型的都会变成确定的类型,代码更灵活
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Demo01Genericity {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("1");
list.add(1);
list.add("abc");
list.add(2.5);
list.add(true);

//获取元素中为String类型的字符串长度
for (Object o : list) {
String s = (String) o;
System.out.println(s.length());//ClassCastException

}
}
}

含有泛型的类

1
2
3
4
5
6
7
1.定义:
public class 类名<E>{

}

2.什么时候确定类型
new对象的时候确定类型
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 class MyArrayList <E>{
//定义一个数组,充当ArrayList底层的数组,长度直接规定为10
Object[] obj = new Object[10];
//定义size,代表集合元素个数
int size;

/**
* 定义一个add方法,参数类型需要和泛型类型保持一致
*
* 数据类型为E 变量名随便取
*/
public boolean add(E e){
obj[size] = e;
size++;
return true;
}

/**
* 定义一个get方法,根据索引获取元素
*/
public E get(int index){
return (E) obj[index];
}

@Override
public String toString() {
return Arrays.toString(obj);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Demo02Genericity {
public static void main(String[] args) {
MyArrayList<String> list1 = new MyArrayList<>();
list1.add("aaa");
list1.add("bbb");
System.out.println(list1);//直接输出对象名,默认调用toString

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

MyArrayList<Integer> list2 = new MyArrayList<>();
list2.add(1);
list2.add(2);
Integer element = list2.get(0);
System.out.println(element);
System.out.println(list2);
}
}

含有泛型的方法

1
2
3
4
5
1.格式:
修饰符 <E> 返回值类型 方法名(E e)

2.什么时候确定类型
调用的时候确定类型
1
2
3
4
5
6
7
8
9
public class ListUtils {
//定义一个静态方法addAll,添加多个集合的元素
public static <E> void addAll(ArrayList<E> list,E...e){
for (E element : e) {
list.add(element);
}
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Demo03Genericity {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
ListUtils.addAll(list1,"a","b","c");
System.out.println(list1);

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

ArrayList<Integer> list2 = new ArrayList<>();
ListUtils.addAll(list2,1,2,3,4,5);
System.out.println(list2);
}
}

含有泛型的接口

1
2
3
4
5
6
7
1.格式:
public interface 接口名<E>{

}
2.什么时候确定类型:
a.在实现类的时候还没有确定类型,只能在new实现类的时候确定类型了 ->比如 ArrayList
b.在实现类的时候直接确定类型了 -> 比如Scanner
1
2
3
public interface MyList <E>{
public boolean add(E e);
}
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 class MyArrayList1<E> implements MyList<E>{
//定义一个数组,充当ArrayList底层的数组,长度直接规定为10
Object[] obj = new Object[10];
//定义size,代表集合元素个数
int size;

/**
* 定义一个add方法,参数类型需要和泛型类型保持一致
*
* 数据类型为E 变量名随便取
*/
public boolean add(E e){
obj[size] = e;
size++;
return true;
}

/**
* 定义一个get方法,根据索引获取元素
*/
public E get(int index){
return (E) obj[index];
}

@Override
public String toString() {
return Arrays.toString(obj);
}
}

1
2
3
4
5
6
7
8
9
public class Demo04Genericity {
public static void main(String[] args) {
MyArrayList1<String> list1 = new MyArrayList1<>();
list1.add("张三");
list1.add("李四");
System.out.println(list1.get(0));

}
}
1
2
3
4
5
6
7
8
9
10
11
public interface MyIterator <E>{
E next();
}

public class MyScanner implements MyIterator<String>{
@Override
public String next() {
return "涛哥和金莲的故事";
}
}

1
2
3
4
5
6
7
8
public class Demo05Genericity {
public static void main(String[] args) {
MyScanner myScanner = new MyScanner();
String result = myScanner.next();
System.out.println("result = " + result);
}
}

泛型的高级使用(限定类型)

泛型通配符 ?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Demo01Genericity {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
list1.add("张三");
list1.add("李四");

ArrayList<Integer> list2 = new ArrayList<>();
list2.add(1);
list2.add(2);

method(list1);
method(list2);
}

public static void method(ArrayList<?> list){
for (Object o : list) {
System.out.println(o);
}
}

}
泛型的上限下限
1
2
3
4
5
6
7
1.作用:可以规定泛型的范围
2.上限:
a.格式:<? extends 类型>
b.含义:?只能接收extends后面的本类类型以及子类类型
3.下限:
a.格式:<? super 类型>
b.含义:?只能接收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
33
34
35
/**
* Integer -> Number -> Object
* String -> Object
*/
public class Demo02Genericity {
public static void main(String[] args) {
ArrayList<Integer> list1 = new ArrayList<>();
ArrayList<String> list2 = new ArrayList<>();
ArrayList<Number> list3 = new ArrayList<>();
ArrayList<Object> list4 = new ArrayList<>();

get1(list1);
//get1(list2);错误
get1(list3);
//get1(list4);错误

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

//get2(list1);错误
//get2(list2);错误
get2(list3);
get2(list4);
}

//上限 ?只能接收extends后面的本类类型以及子类类型
public static void get1(Collection<? extends Number> collection){

}

//下限 ?只能接收super后面的本类类型以及父类类型
public static void get2(Collection<? super Number> collection){

}
}

应用场景:

1.如果我们在定义类,方法,接口的时候,如果类型不确定,我们可以考虑定义含有泛型的类,方法,接口

2.如果类型不确定,但是能知道以后只能传递某个类的继承体系中的子类或者父类,就可以使用泛型的通配符

红黑树(了解)

1
2
3
4
5
集合加入红黑树的目的:提高查询效率
HashSet集合:
数据结构:哈希表
jdk8之前:哈希表 = 数组+链表
jdk8之后:哈希表 = 数组+链表+红黑树 ->为的是查询效率

1
2
3
4
5
6
7
8
9
1. 每一个节点或是红色的,或者是黑色的

2. 根节点必须是黑色

3. 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的

4. 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连 的情况)

5. 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点

https://www.cs.usfca.edu/~galles/visualization/RedBlack

Set集合

1
Set接口并没有对Collection接口进行功能上的扩充,而且所有的Set集合底层都是依靠Map实现

Set集合介绍

1
2
Set和Map密切相关的
Map的遍历需要先变成单列集合,只能变成set集合

HashSet集合的介绍和使用

1
2
3
4
5
6
7
8
9
10
11
1.概述:HashSet是Set接口的实现类
2.特点:
a.元素唯一
b.元素无序
c.无索引
d.线程不安全
3.数据结构:哈希表
a.jdk8之前:哈希表 = 数组+链表
b.jdk8之后:哈希表 = 数组+链表+红黑树
加入红黑树目的:查询快
4.方法:和Collection一样

TreeSet

1
2
3
4
5
6
7
8
1.概述:TreeSet是Set的实现类
2.特点:
a.对元素进行排序
b.无索引
c.不能存null
d.线程不安全
e.元素唯一
3.数据结构:红黑树
1
2
3
构造:
TreeSet() -> 构造一个新的空 set,该 set 根据其元素的自然顺序进行排序 -> ASCII
TreeSet(Comparator<? super E> comparator)构造一个新的空 TreeSet,它根据指定比较器进行排序
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
public class Person {
private String name;
private Integer age;

public Person() {
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

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

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name) && Objects.equals(age, person.age);
}

@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
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 Demo01TreeSet {
public static void main(String[] args) {
TreeSet<String> set1 = new TreeSet<>();
set1.add("c.白毛浮绿水");
set1.add("a.鹅鹅鹅");
set1.add("b.曲项向天歌");
set1.add("d.红掌拨清波");
System.out.println(set1);

System.out.println("=====================");
TreeSet<Person> set2 = new TreeSet<>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge()-o2.getAge();
}
});
set2.add(new Person("柳岩",18));
set2.add(new Person("涛哥",12));
set2.add(new Person("三上",20));
System.out.println(set2);
}
}

Map集合

Map的介绍

1
2
3
1.概述:是双列集合的顶级接口
2.元素特点:
元素都是由key(键),value(值)组成 -> 键值对

HashMap的介绍和使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.概述:HashMap是Map的实现类
2.特点:
a.key唯一,value可重复 -> 如果key重复了,会发生value覆盖
b.无序
c.无索引
d.线程不安全
e.可以存nullnull
3.数据结构:
哈希表
4.方法:
V put(K key, V value) -> 添加元素,返回的是
V remove(Object key) ->根据key删除键值对,返回的是被删除的value
V get(Object key) -> 根据key获取value
boolean containsKey(Object key) -> 判断集合中是否包含指定的key
Collection<V> values() -> 获取集合中所有的value,转存到Collection集合中

Set<K> keySet()->将Map中的key获取出来,转存到Set集合中
Set<Map.Entry<K,V>> entrySet()->获取Map集合中的键值对,转存到Set集合中
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
public class Demo01HashMap {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
//V put(K key, V value) -> 添加元素,返回的是被覆盖的value
String value1 = map.put("猪八", "嫦娥");
System.out.println(value1);
String value2 = map.put("猪八", "高翠兰");
System.out.println(value2);
System.out.println(map);

map.put("后裔","嫦娥");
map.put("二郎神","嫦娥");
map.put("唐僧","女儿国国王");
map.put("涛哥","金莲");
map.put(null,null);
System.out.println(map);

//V remove(Object key) ->根据key删除键值对,返回的是被删除的value
String value3 = map.remove("涛哥");
System.out.println(value3);
System.out.println(map);
//V get(Object key) -> 根据key获取value
System.out.println(map.get("唐僧"));
//boolean containsKey(Object key) -> 判断集合中是否包含指定的key
System.out.println(map.containsKey("二郎神"));
//Collection<V> values() -> 获取集合中所有的value,转存到Collection集合中
Collection<String> collection = map.values();
System.out.println(collection);
}
}

1
2
3
4
5
6
7
8
9
10
1.概述:LinkedHashMap extends HashMap
2.特点:
a.key唯一,value可重复 -> 如果key重复了,会发生value覆盖
b.有序
c.无索引
d.线程不安全
e.可以存nullnull
3.数据结构:
哈希表+双向链表
4.使用:和HashMap一样
1
2
3
4
5
6
7
8
9
10
11
public class Demo02LinkedHashMap {
public static void main(String[] args) {
LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put("八戒","嫦娥");
map.put("涛哥","金莲");
map.put("涛哥","三上");
map.put("唐僧","女儿国国王");
System.out.println(map);
}
}

HashMap的两种遍历方式
1
Set<K> keySet()->将Map中的key获取出来,转存到Set集合中  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Demo03HashMap {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
map.put("猪八", "嫦娥");
map.put("猪八", "高翠兰");
map.put("后裔","嫦娥");
map.put("二郎神","嫦娥");
map.put("唐僧","女儿国国王");
map.put("涛哥","金莲");

Set<String> set = map.keySet();//获取所有的key,保存到set集合中
for (String key : set) {
//根据key获取value
System.out.println(key+".."+map.get(key));
}

}
}

1
Set<Map.Entry<K,V>> entrySet()->获取Map集合中的键值对,转存到Set集合中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Demo04HashMap {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
map.put("猪八", "嫦娥");
map.put("猪八", "高翠兰");
map.put("后裔","嫦娥");
map.put("二郎神","嫦娥");
map.put("唐僧","女儿国国王");
map.put("涛哥","金莲");

/*
Set集合中保存的都是"结婚证"-> Map.Entry
我们需要将"结婚证"从set集合中遍历出来
*/
Set<Map.Entry<String, String>> set = map.entrySet();
for (Map.Entry<String, String> entry : set) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"..."+value);
}
}
}
Map存储自定义对象时如何去重复
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
public class Person {
private String name;
private Integer age;

public Person() {
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

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

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name) && Objects.equals(age, person.age);
}

@Override
public int hashCode() {
return Objects.hash(name, age);
}
}

1
2
3
4
5
6
7
8
9
public class Demo05HashMap {
public static void main(String[] args) {
HashMap<Person, String> map = new HashMap<>();
map.put(new Person("涛哥",18),"河北省");
map.put(new Person("三上",26),"日本");
map.put(new Person("涛哥",18),"北京市");
System.out.println(map);
}
}
1
2
如果key为自定义类型,去重复的话,重写hashCode和equals方法,去重复过程和set一样一样的
因为set集合的元素到了底层都是保存到了map的key位置上
哈希表结构存储过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1.HashMap底层数据数据结构:哈希表
2.jdk7:哈希表 = 数组+链表
jdk8:哈希表 = 数组+链表+红黑树
3.
先算哈希值,此哈希值在HashMap底层经过了特殊的计算得出
如果哈希值不一样,直接存
如果哈希值一样,再去比较内容,如果内容不一样,也存
如果哈希值一样,内容也一样,直接去重复(后面的value将前面的value覆盖)

哈希值一样,内容不一样->哈希冲突(哈希碰撞)
4.要知道的点:
a.在不指定长度时,哈希表中的数组默认长度为16,HashMap创建出来,一开始没有创建长度为16的数组
b.什么时候创建的长度为16的数组呢?在第一次put的时候,底层会创建长度为16的数组
c.哈希表中有一个数据加[加载因子]->默认为0.75(加载因子)->代表当元素存储到百分之75的时候要扩容了->2
d.如果对个元素出现了哈希值一样,内容不一样时,就会在同一个索引上以链表的形式存储,当链表长度达到8并且当前数组长度>=64时,链表就会改成使用红黑树存储
如果后续删除元素,那么在同一个索引位置上的元素个数小于6,红黑树会变回链表
e.加入红黑树目的:查询快
1
2
3
4
5
6
外面笔试时可能会问到的变量
default_initial_capacity:HashMap默认容量 16
default_load_factor:HashMap默认加载因子 0.75f
threshold:扩容的临界值 等于 容量*0.75 = 12 第一次扩容
treeify_threshold:链表长度默认值,转为红黑树:8
min_treeify_capacity:链表被树化时最小的数组容量:64

1.问题:哈希表中有数组的存在,但是为啥说没有索引呢?

​ 哈希表中虽然有数组,但是set和map却没有索引,因为存数据的时候有可能在同一个索引下形成链表,如果2索引上有一条链表,那么我们要是按照索引2获取,咱们获取哪个元素呢?所以就取消了按照索引操作的机制

2.问题:为啥说HashMap是无序的,LinkedHashMap是有序的呢?

​ 原因:HashMap底层哈希表为单向链表

​ LinkedHashMap底层在哈希表的基础上加了一条双向链表

HashMap无参数构造方法的分析
1
2
3
4
5
//HashMap中的静态成员变量
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

解析:使用无参数构造方法创建HashMap对象,将加载因子设置为默认的加载因子,loadFactor=0.75F。

HashMap有参数构造方法分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
HashMap(int initialCapacity, float loadFactor) ->创建Map集合的时候指定底层数组长度以及加载因子

public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);//10
}

解析:带有参数构造方法,传递哈希表的初始化容量和加载因子

  • 如果initialCapacity(初始化容量)小于0,直接抛出异常。
  • 如果initialCapacity大于最大容器,initialCapacity直接等于最大容器
    • MAXIMUM_CAPACITY = 1 << 30 是最大容量 (1073741824)
  • 如果loadFactor(加载因子)小于等于0,直接抛出异常
  • tableSizeFor(initialCapacity)方法计算哈希表的初始化容量。
    • 注意:哈希表是进行计算得出的容量,而初始化容量不直接等于我们传递的参数。
tableSizeFor方法分析
1
2
3
4
5
6
7
8
9
10
11
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

8 4 2 1规则->无论指定了多少容量,最终经过tableSizeFor这个方法计算之后,都会遵循8421规则去初始化列表容量为了存取高效,尽量较少碰撞

解析:该方法对我们传递的初始化容量进行位移运算,位移的结果是 8 4 2 1 码

  • 例如传递2,结果还是2,传递的是4,结果还是4。
  • 例如传递3,结果是4,传递5,结果是8,传递20,结果是32。
Node 内部类分析

哈希表是采用数组+链表的实现方法,HashMap中的内部类Node非常重要,证明HashSet是一个单向链表

1
2
3
4
5
6
7
8
9
10
11
 static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}

解析:内部类Node中具有4个成员变量

  • hash,对象的哈希值
  • key,作为键的对象
  • value,作为值得对象(讲解Set集合,不牵扯值得问题)
  • next,下一个节点对象
存储元素的put方法源码
1
2
3
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

解析:put方法中调研putVal方法,putVal方法中调用hash方法。

  • hash(key)方法:传递要存储的元素,获取对象的哈希值
  • putVal方法,传递对象哈希值和要存储的对象key
putVal方法源码
1
2
3
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;

解析:方法中进行Node对象数组的判断,如果数组是null或者长度等于0,那么就会调研resize()方法进行数组的扩容。

resize方法的扩容计算
1
2
3
4
5
6
7
8
9
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}

解析:计算结果,新的数组容量=原始数组容量<<1,也就是乘以2。

确定元素存储的索引
1
2
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);

解析:i = (数组长度 - 1) & 对象的哈希值,会得到一个索引,然后在此索引下tab[i],创建链表对象。

不同哈希值的对象,也是有可能存储在同一个数组索引下。

1
2
3
4
5
6
7
8
9
其中resize()扩容的方法,默认是16
tab[i] = newNode(hash, key, value, null);->将元素放在数组中 i就是索引

i = (n - 1) & hash
0000 0000 0000 0000 0000 0000 0000 1111->15
& 0&0=0 0&1=0 1&1=1
0000 0000 0000 0001 0111 1000 0110 0011->96355
--------------------------------------------------------
0000 0000 0000 0000 0000 0000 0000 0011->3
1
2
3
4
5
     0000 0000 0000 0000 0000 0000 0000 1111->15
& 0&0=0 0&1=0 1&1=1
0000 0000 0001 0001 1111 1111 0001 0010->1179410
--------------------------------------------------------
0000 0000 0000 0000 0000 0000 0000 0010->2
遇到重复哈希值的对象
1
2
3
4
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;

解析:如果对象的哈希值相同,对象的equals方法返回true,判断为一个对象,进行覆盖操作。

1
2
3
4
5
6
7
8
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}

解析:如果对象哈希值相同,但是对象的equals方法返回false,将对此链表进行遍历,当链表没有下一个节点的时候,创建下一个节点存储对象.

TreeMap

1
2
3
4
5
6
7
8
1.概述:TreeMap是Map的实现类
2.特点:
a.对key进行排序
b.无索引
c.key唯一
d.线程不安全
e.不能存null
3.数据结构:红黑树
1
2
3
构造:
TreeMap() -> 使用键的自然顺序构造一个新的、空的树映射 -> ASCII
TreeMap(Comparator<? super E> comparator)构造一个新的、空的树映射,该映射根据给定比较器进行排序
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
public class Person {
private String name;
private Integer age;

public Person() {
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

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

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name) && Objects.equals(age, person.age);
}

@Override
public int hashCode() {
return Objects.hash(name, age);
}
}

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
public class Demo02TreeMap {
public static void main(String[] args) {
TreeMap<String, String> map1 = new TreeMap<>();
map1.put("c","白毛浮绿水");
map1.put("a","鹅鹅鹅");
map1.put("b","曲项向天歌");
map1.put("d","红掌拨清波");
System.out.println(map1);

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

TreeMap<Person, String> map2 = new TreeMap<>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge()-o2.getAge();
}
});

map2.put(new Person("柳岩",18),"北京");
map2.put(new Person("涛哥",12),"北京");
map2.put(new Person("三上",20),"北京");
System.out.println(map2);

}
}

Hashtable集合

1
2
3
4
5
6
7
8
1.概述:Hashtable是Map的实现类
2.特点:
a.key唯一,value可重复
b.无序
c.无索引
d.线程安全
e.不能存储null键,null
3.数据结构:哈希表
1
2
3
4
5
6
7
8
9
10
11
public class Demo01Hashtable {
public static void main(String[] args) {
Hashtable<String, String> table = new Hashtable<>();
table.put("迪丽热巴","马尔扎哈");
table.put("古力娜扎","涛哥思密达");
table.put("古力娜扎","雷霆嘎巴");
table.put("玛卡巴卡","哈哈哈哈");
//table.put(null,null);
System.out.println(table);
}
}

HashMap和Hashtable区别:

相同点:元素无序,无索引,key唯一

不同点:HashMap线程不安全,Hashtable线程安全

​ HashMap可以存储null键null值;Hashtable不能

Vector集合

1
2
3
4
5
6
7
8
9
10
11
1.概述:Vector是List接口的实现类
2.特点:
a.元素有序
b.有索引
c.元素可重复
d.线程安全
3.数据结构:数组

4.源码分析:
a.如果用空参构造创建对象,数组初始容量为10,如果超出范围,自动扩容,2
b.如果用有参构造创建对象,如果超出了范围,自动扩容,扩的是老数组长度+指定的容量增强
1
2
3
4
5
6
7
8
9
10
public class Demo02Vector {
public static void main(String[] args) {
Vector<String> vector = new Vector<>();
vector.add("张三");
vector.add("李四");
for (String s : vector) {
System.out.println(s);
}
}
}

Vector底层源码分析

1
2
Vector() 构造一个空向量,使其内部数据数组的大小为 10,其标准容量增量为零
Vector(int initialCapacity, int capacityIncrement)使用指定的初始容量和容量增量构造一个空的向量
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
Vector<String> vector = new Vector<>();
public Vector() {
this(10);
}
public Vector(int initialCapacity->10) {
this(initialCapacity, 0);
}
public Vector(int initialCapacity->10, int capacityIncrement->0) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];//长度为0的数组
this.capacityIncrement = capacityIncrement;//0
}
=====================================================
vector.add("李四");-> 假设李四是第11个元素
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
private void ensureCapacityHelper(int minCapacity->11) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity->11);
}
private void grow(int minCapacity->11) {
// overflow-conscious code
int oldCapacity = elementData.length;//10
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);//10+10=20
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
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
Vector<String> vector = new Vector<>(10,5);
public Vector(int initialCapacity->10, int capacityIncrement->5) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;//5
}

======================================================
vector.add("李四");-> 假设李四是第11个元素
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}

private void ensureCapacityHelper(int minCapacity->11) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

private void grow(int minCapacity->11) {
// overflow-conscious code
int oldCapacity = elementData.length;//10
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}

Properties集合(属性集)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.概述:Properties 继承自 Hashtable
2.特点:
a.key唯一,value可重复
b.无序
c.无索引
d.线程安全
e.不能存null键,null
f.Properties的key和value类型默认为String
3.数据结构:哈希表
4.特有方法:
Object setProperty(String key, String value) -> 存键值对
String getProperty(String key) ->根据key获取value的
Set<String> stringPropertyNames() -> 获取所有的key,保存到set集合中,相当于keySet方法
void load(InputStream inStream) -> 将流中的数据加载到Properties集合中(IO部分讲)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Demo01Properties {
public static void main(String[] args) {
Properties properties = new Properties();
//Object setProperty(String key, String value) -> 存键值对
properties.setProperty("username","root");
properties.setProperty("password","1234");
System.out.println(properties);
//String getProperty(String key) ->根据key获取value的
System.out.println(properties.getProperty("username"));
//Set<String> stringPropertyNames() -> 获取所有的key,保存到set集合中,相当于keySet方法
Set<String> set = properties.stringPropertyNames();
for (String key : set) {
System.out.println(properties.getProperty(key));
}
}
}

IO流

File类

介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
计算机常识:
1.以.jpg结尾的一定是图片吗?
不一定,有可能是文件夹

2.什么是文本文档?
用记事本打开,人能看懂的文件 -> txt html css

3.E:\Idea\io\1.jpg 中的1.jpg的父路径是谁?
E:\Idea\io

4.分隔符:
a.路径名称分隔符:
windows: \
linux: /
b.路径分隔符:一个路径和其他路径之间的分隔符
;
1
2
3
4
1.概述:文件和目录(文件夹)路径名的抽象表示   
2.简单理解:
我们在创建File对象的时候,需要传递一个路径,这个路径定为到哪个文件或者文件夹上,我们的File就代表哪个对象
File file = new File("E:\Idea\io\1.jpg")

File的静态成员

1
2
static String pathSeparator:与系统有关的路径分隔符,为了方便,它被表示为一个字符串。
static String separator:与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。
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 class Demo01File {
public static void main(String[] args) {
//file01();
file02();
}

/*
将来写代码如何正确编写一个路径用java代码
*/
private static void file02() {
String path1 = "E:\\Idea\\io";
System.out.println(path1);
System.out.println("==================");

//要求代码写完,一次编写,到处运行
String path2 = "E:"+File.separator+"Idea"+File.separator+"io";
System.out.println(path2);
}

private static void file01() {
//static String pathSeparator:与系统有关的路径分隔符,为了方便,它被表示为一个字符串。
String pathSeparator = File.pathSeparator;
System.out.println("pathSeparator = " + pathSeparator); // ;
//static String separator:与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。
String separator = File.separator;
System.out.println("separator = " + separator); // \
}
}

File的构造方法

1
2
3
4
5
6
7
8
File(String parent, String child) 根据所填写的路径创建File对象
parent:父路径
child:子路径
File(File parent, String child) 根据所填写的路径创建File对象
parent:父路径,是一个File对象
child:子路径
File(String pathname) 根据所填写的路径创建File对象
pathname:直接指定路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Demo02File {
public static void main(String[] args) {
//File(String parent, String child) 根据所填写的路径创建File对象
//parent:父路径
//child:子路径
File file1 = new File("E:\\Idea\\io", "1.jpg");
System.out.println("file1 = " + file1);
//File(File parent, String child) 根据所填写的路径创建File对象
//parent:父路径,是一个File对象
//child:子路径
File parent = new File("E:\\Idea\\io");
File file2 = new File(parent, "1.jpg");
System.out.println("file2 = " + file2);
//File(String pathname) 根据所填写的路径创建File对象
//pathname:直接指定路径
File file3 = new File("E:\\Idea\\io\\1.jpg");
System.out.println("file3 = " + file3);
}
}

细节:

我们创建File对象的时候,传递的路径可以是不存在的,但是传递不存在的路径,你调用某些方法时,Java 才会实际去检查该路径的存在性或尝试操作它,导致错误或异常。

File的获取方法

1
2
3
4
String getAbsolutePath() -> 获取File的绝对路径->带盘符的路径
String getPath() ->获取的是封装路径->new File对象的时候写的啥路径,获取的就是啥路径
String getName() -> 获取的是文件或者文件夹名称
long length() -> 获取的是文件的长度 -> 文件的字节数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static void file01() {
//String getAbsolutePath() -> 获取File的绝对路径->带盘符的路径
File file1 = new File("1.txt");
System.out.println("file1.getAbsolutePath() = " + file1.getAbsolutePath());
//String getPath() ->获取的是封装路径->new File对象的时候写的啥路径,获取的就是啥路径
File file2 = new File("io\\1.txt");
System.out.println("file2.getPath() = " + file2.getPath());
//String getName() -> 获取的是文件或者文件夹名称
File file3 = new File("E:\\Idea\\io\\1.jpg");
System.out.println("file3.getName() = " + file3.getName());
//long length() -> 获取的是文件的长度 -> 文件的字节数
File file4 = new File("E:\\Idea\\io\\1.txt");
System.out.println("file4.length() = " + file4.length());
}

File的创建方法

1
2
3
4
5
6
7
boolean createNewFile()  -> 创建文件
如果要创建的文件之前有,创建失败,返回false
如果要创建的文件之前没有,创建成功,返回true

boolean mkdirs() -> 创建文件夹(目录)既可以创建多级文件夹,还可以创建单级文件夹
如果要创建的文件夹之前有,创建失败,返回false
如果要创建的文件夹之前没有,创建成功,返回true
1
2
3
4
5
6
7
8
9
10
11
12
13
private static void file02() throws IOException {
/*boolean createNewFile() -> 创建文件
如果要创建的文件之前有,创建失败,返回false
如果要创建的文件之前没有,创建成功,返回true*/
File file1 = new File("E:\\Idea\\io\\1.txt");
System.out.println("file1.createNewFile() = " + file1.createNewFile());

/*boolean mkdirs() -> 创建文件夹(目录)既可以创建多级文件夹,还可以创建单级文件夹
如果要创建的文件夹之前有,创建失败,返回false
如果要创建的文件夹之前没有,创建成功,返回true*/
File file2 = new File("E:\\Idea\\io\\haha\\heihei\\hehe");
System.out.println("file2.mkdirs() = " + file2.mkdirs());
}

File类的删除方法

1
2
3
4
5
boolean delete()->删除文件或者文件夹

注意:
1.如果删除文件,不走回收站
2.如果删除文件夹,必须是空文件夹,而且也不走回收站
1
2
3
4
5
6
private static void file03() {
//boolean delete()->删除文件或者文件夹
//File file1 = new File("E:\\Idea\\io\\1.txt");
File file1 = new File("E:\\Idea\\io\\haha");
System.out.println("file1.delete() = " + file1.delete());
}

File类的判断方法

1
2
3
boolean isDirectory() -> 判断是否为文件夹 
boolean isFile() -> 判断是否为文件
boolean exists() -> 判断文件或者文件夹是否存在
1
2
3
4
5
6
7
8
9
private static void file04() {
File file = new File("E:\\Idea\\io\\1.txt");
// boolean isDirectory() -> 判断是否为文件夹
System.out.println("file.isDirectory() = " + file.isDirectory());
// boolean isFile() -> 判断是否为文件
System.out.println("file.isFile() = " + file.isFile());
// boolean exists() -> 判断文件或者文件夹是否存在
System.out.println("file.exists() = " + file.exists());
}

File的遍历方法

1
2
3
4
5
String[] list() -> 遍历指定的文件夹,返回的是String数组 
File[] listFiles()-> 遍历指定的文件夹,返回的是File数组 ->这个推荐使用

细节:listFiles方法底层还是list方法
调用list方法,遍历文件夹,返回一个Stirng数组,遍历数组,将数组中的内容一个一个封装到File对象中,然后再将File对象放到File数组中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static void file05() {
File file = new File("E:\\Idea\\io\\aa");
//String[] list() -> 遍历指定的文件夹,返回的是String数组
String[] list = file.list();
for (String s : list) {
System.out.println(s);
}
//File[] listFiles()-> 遍历指定的文件夹,返回的是File数组 ->这个推荐使用
System.out.println("==============");
File[] files = file.listFiles();
for (File file1 : files) {
System.out.println(file1);
}
}

字节流

IO流介绍以及输入输出以及流向的介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.单词:
Output:输出
Input:输入

write:写数据
read:读数据

2.IO流:
将一个设备上的数据传输到另外一个设备上,称之为IO流技术

3.为啥要学IO流呢?
之前学了一个集合以及数组,可以保存数据,但是这两个都是临时存储(代码运行完毕,集合和数组会从内存中消失,从而数据就不存在了),所以集合和数组达不到永久保存的目的,我们希望咱们的数据永久保存起来,所以我们就可以将数据保存到硬盘上,此时我们就可以随时想拿到硬盘上的数据就随时拿

而且我们将来传输数据,必然要用到输入,输出动作

IO流的流向

1
2
3
4
5
6
7
输入流:将数据从硬盘上读到内存中  Input
输出流:从内存出发,将数据写到硬盘上 Output


要是从电脑和电脑之间做数据传输,就是相对的
发数据一方 : 输出
接数据一方 : 输入

IO流分类

1
2
3
4
5
6
7
字节流:万能流,一切皆字节
字节输出流: OutputStream 抽象类
字节输入流: InputStream 抽象类

字符流:专门操作文本文档
字符输出流:Writer 抽象类
字符输入流:Reader 抽象类

OutputStream中子类[FileOutputStream]的介绍以及方法的简单介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1.概述:字节输出流,OutputStream 是一个抽象类
子类: FileOutputStream
2.作用:往硬盘上写数据

3.构造:
FileOutputStream(File file)
FileOutputStream(String name)

4.特点:
a.指定的文件如果没有,输出流会自动创建
b.每执行一次,默认都会创建一个新的文件,覆盖老文件

5.方法:
void write(int b) 一次写一个字节
void write(byte[] b) 一次写一个字节数组
void write(byte[] b, int off, int len) 一次写一个字节数组一部分
b:写的数组
off:从数组的哪个索引开始写
len:写多少个
void close() -> 关闭资源
1
2
3
4
5
6
7
8
  /*
void write(int b) 一次写一个字节
*/
private static void method01() throws IOException {
FileOutputStream fos = new FileOutputStream("module21\\1.txt");
fos.write(97);
fos.close();
}
1
2
3
4
5
6
7
8
9
/*
void write(byte[] b) 一次写一个字节数组
*/
private static void method02()throws IOException {
FileOutputStream fos = new FileOutputStream("module21\\1.txt");
byte[] bytes = {97,98,99,100,101,102,103};
fos.write(bytes);
fos.close();
}
1
2
3
4
5
6
7
8
9
10
11
12
  /*
void write(byte[] b, int off, int len) 一次写一个字节数组一部分
b:写的数组
off:从数组的哪个索引开始写
len:写多少个
*/
private static void method03()throws IOException {
FileOutputStream fos = new FileOutputStream("module21\\1.txt");
byte[] bytes = {97,98,99,100,101,102,103};
fos.write(bytes,0,2);
fos.close();
}
1
2
3
4
5
6
7
8
9
   /*
void write(byte[] b) 一次写一个字节数组
*/
private static void method04()throws IOException {
FileOutputStream fos = new FileOutputStream("module21\\1.txt");
//byte[] bytes = "abc".getBytes();
fos.write("abcde".getBytes());
fos.close();
}

1
2
3
4
5
6
7
8
1.字节流的续写追加:
FileOutputStream(String name, boolean append)
append:true -> 会实现续写追加,文件不覆盖了

2.换行:
a.windows: \r\n -> 占2个字节 \n
b.linux: \n
c.mac os : \r
1
2
3
4
5
6
7
8
private static void method05()throws IOException {
FileOutputStream fos = new FileOutputStream("module21\\1.txt",true);
fos.write("床前明月光\r\n".getBytes());
fos.write("疑是地上霜\n".getBytes());
fos.write("举头望明月\n".getBytes());
fos.write("低头思故乡\n".getBytes());
fos.close();
}

InputStream子类[FileInputStream]的介绍以及方法的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.概述:字节输入流 InputStream ,是一个抽象类
子类:FileInputStream

2.作用:读数据,将数据从硬盘上读到内存中来

3.构造:
FileInputStream(File file)
FileInputStream(String path)

4.方法:
int read() 一次读一个字节,返回的是读取的字节
int read(byte[] b) 一次读取一个字节数组,返回的是读取的字节个数
int read(byte[] b, int off, int len) 一次读取一个字节数组一部分,返回的是读取的字节个数
void close() 关闭资源

一次读取一个字节

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
 /*
int read() 一次读一个字节,返回的是读取的字节
*/
private static void method01()throws Exception {
FileInputStream fis = new FileInputStream("module21\\1.txt");
//int data1 = fis.read();
//System.out.println(data1);

//int data2 = fis.read();
//System.out.println(data2);

//int data3 = fis.read();
//System.out.println(data3);

//int data4 = fis.read();
//System.out.println(data4);// -1

//int data5 = fis.read();
//System.out.println(data5);//-1

System.out.println("=================");
//定义一个变量,接收读取到的字节
int len;
while((len = fis.read())!=-1){
System.out.println((char)len);
}

fis.close();
}

问题1:一个流对象,读完之后,就不要再读了;除非重新new一个新的对象

问题2:流关闭之后,流对象不能继续使用了

1
2
3
4
5
Exception in thread "main" java.io.IOException: Stream Closed
at java.base/java.io.FileInputStream.read0(Native Method)
at java.base/java.io.FileInputStream.read(FileInputStream.java:228)
at com.atguigu.c_input.Demo01FileInputStream.method01(Demo01FileInputStream.java:38)
at com.atguigu.c_input.Demo01FileInputStream.main(Demo01FileInputStream.java:7)

读取-1的问题

1
2
3
每个文件末尾都会有一个"结束标记",这个"结束标记"我们看不见,摸不着

而read()方法规定,如果读到了文件的结束标记,方法直接返回-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
34
/*
int read(byte[] b) 一次读取一个字节数组,返回的是读取的字节个数
*/
private static void method02()throws Exception {
FileInputStream fis = new FileInputStream("module21\\1.txt");
/*
创建一个数组:byte[]
1.创建的数组相当于一个临时存储区域,我们要读取的内容会临时保存到数组中
然后我们再从数组中将数据获取

2.数组长度定为多少,每次读取多少个,一般情况下数组长度定为1024或者1024的倍数
如果剩下的字节不够数组长度了,那么就最后有多少读多少
*/
byte[] bytes = new byte[2];
//int len1 = fis.read(bytes);
/*System.out.println(len1);//2
System.out.println(new String(bytes,0,len1));
System.out.println("===============");
int len2 = fis.read(bytes);
System.out.println(len2);//2
System.out.println(new String(bytes,0,len2));
System.out.println("===============");
int len3 = fis.read(bytes);
System.out.println(len3);//1
System.out.println(new String(bytes,0,len3));*/

//定义一个变量len,接收读取的字节个数
int len;
while((len = fis.read(bytes))!=-1){
//System.out.println(new String(bytes,0,len));
System.out.println(new String(bytes));
}
fis.close();
}

字节流实现图片复制分析

字节流实现图片复制代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Demo01CopyFile {
public static void main(String[] args)throws Exception {
//1.创建FileInputStream
FileInputStream fis = new FileInputStream("E:\\Idea\\io\\24.jpg");
//2.创建一个FileOutputStream,将读取的图片写到指定的位置
FileOutputStream fos = new FileOutputStream("E:\\Idea\\io\\大姐.jpg");
//3.定义一个数组
byte[] bytes = new byte[1024];
//4.边读边写
int len;
while((len = fis.read(bytes))!=-1){
fos.write(bytes,0,len);//读多少个,写多少个
}
//5.关流 先开后关
fos.close();
fis.close();
}
}

字符流

字节流读取中文的问题

1
2
3
4
5
6
7
8
9
10
11
1.注意:
字节流是万能流,更侧重于文件复制,但是尽量不要边读边看

2.原因:
UTF-8:一个汉字占3个字节
GBK:一个中文占2个字节

如果按照字节读取,每次读取的字节没有构成一个汉字的字节数,就直接输出,汉字是显示不了的

3.解决:
将文本文档中的内容,按照字符去操作

话说回来:

1.按照字符去操作编码也要一致,如果不一致,照样会乱码

2.按照字节流去操作即使编码一致,边读边看,也有可能乱码

FileReader的介绍以及使用

1
字符流专门操作文本文档的,但是复制操作不要用字符流,要用字节流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.概述:字符输入流 -> Reader -> 是一个抽象类
子类:FileReader
2.作用:将文本文档中的内容读取到内存中来
3.构造:
FileReader(File file)
FileReader(String path)
4.方法:
int read() -> 一次读取一个字符,返回的是读取字符对应的int
int read(char[] cbuf) -> 一次读取一个字符数组,返回的是读取个数
int read(char[] cbuf, int off, int len) -> 一次读取一个字符数组一部分,返回的是读取个数
cbuf:读取的数组
off:从数组的哪个索引开始读
len:读多少个
void close() -> 关闭资源
1
2
3
4
5
6
7
8
private static void method01() throws IOException {
FileReader fr = new FileReader("module21\\1.txt");
int len;
while((len = fr.read())!=-1){
System.out.println((char) len);
}
fr.close();
}
1
2
3
4
5
6
7
8
9
private static void method02()throws Exception {
FileReader fr = new FileReader("module21\\1.txt");
char[] chars = new char[2];
int len;
while((len = fr.read(chars))!=-1){
System.out.println(new String(chars,0,len));
}
fr.close();
}

FileWriter的介绍以及使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1.概述:字符输出流 -> Writer -> 抽象类
子类:FileWriter
2.作用:将数据写到文件中
3.构造:
FileWriter(File file)
FileWriter(String fileName)
FileWriter(String fileName, boolean append) -> 追加,续写
4.方法:
void write(int c) -> 一次写一个字符
void write(char[] cbuf) 一次写一个字符数组
void write(char[] cbuf, int off, int len) 一次写一个字符数组一部分
void write(String str) 直接写一个字符串
void flush() :将缓冲区中的数据刷到文件中
void close() 关流

5.注意:FileWriterr底层自带一个缓冲区,我们写的数据会先保存到缓冲区中,所以我们需要将缓冲区中的数据刷到文件中
1
2
3
4
5
6
7
8
9
10
11
public class Demo01FileWriter {
public static void main(String[] args)throws Exception {
FileWriter fw = new FileWriter("module21\\2.txt");
fw.write("千山鸟飞绝\r\n");
fw.write("万径人踪灭\r\n");
fw.write("孤舟蓑笠翁\r\n");
fw.write("独钓寒江雪\r\n");
//fw.flush();
fw.close();
}
}

FileWriter的刷新功能和关闭功能

1
2
flush():将缓冲区中的数据刷到文件中,后续流对象还能继续使用
close():先刷新后关闭,后续流对象不能使用了

IO异常处理的方式

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 Demo01Exception {
public static void main(String[] args) {
FileWriter fw = null;
try{
fw = new FileWriter("module21\\3.txt");
fw.write("你好");

}catch (Exception e){
e.printStackTrace();
}finally {
//如果fw不为null,证明new出来了所以需要close;相反不需要close
if (fw!=null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}

}
}
}

字节缓冲流

1
2
3
4
5
6
7
8
9
10
11
12
13
1.为啥要学字节缓冲流
之前所写的FileOutputStream,FileInputStream,FileReader,FileWriter这都叫做基本类,其中FileInputStream和FileOutputStream的读写方法都是本地方法(方法声明上带native),本地方法是和系统以及硬盘打交道的,也就是说这两个对象的读和写都是在硬盘之间进行读写的,效率不高;缓冲流中底层带一个长度为8192的数组(缓冲区),此时读和写都是在内存中完成的(在缓冲区之间完成),内存中的读写效率非常高

使用之前需要将基本流包装成缓冲流,其实就new对象时,传递基本流

2.字节缓冲流
a.BufferedOutputStream:字节缓冲输出流
构造:BufferedOutputStream(OutputStream out)
使用:和FileOutputStream一样

b.BufferedInputStream:字节缓冲输入流
构造:BufferedInputStream(InputStream in)
使用:和FileInputStream一样
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
 public class Demo01BufferedInputStream_OutputStream {
public static void main(String[] args)throws Exception {
//method01();
method02();
}

//使用字节缓冲流复制文件
private static void method02()throws Exception {
long start = System.currentTimeMillis();

FileInputStream fis = new FileInputStream("E:\\Idea\\io\\1.avi");
FileOutputStream fos = new FileOutputStream("E:\\Idea\\io\\2.avi");

BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
//边读编写
int len;
while((len = bis.read())!=-1){
bos.write(len);
}

long end = System.currentTimeMillis();

System.out.println(end-start);

bos.close();
bis.close();
}

//用基本流复制文件
private static void method01()throws Exception {
long start = System.currentTimeMillis();

FileInputStream fis = new FileInputStream("E:\\Idea\\io\\1.avi");
FileOutputStream fos = new FileOutputStream("E:\\Idea\\io\\2.avi");
//边读编写
int len;
while((len = fis.read())!=-1){
fos.write(len);
}

long end = System.currentTimeMillis();

System.out.println(end-start);

fos.close();
fis.close();
}
}

细节:

问题1:使用缓冲流的时候,为啥只需要关闭缓冲流,不用单独关闭基本流呢?

​ 原因:缓冲流的close方法底层会自动关闭基本流/?

问题2:缓冲流底层有数组(缓冲区),都是在内存之间进行读写,那么缓冲流读写的过程是啥样的呢?

​ 注意:先依靠基本流将数据读出来,然后交给缓冲流,由于缓冲流缓冲区是8192,所以每次读取8192个字节放到缓冲区中,然后再将输入流缓冲区中的数据交给输出流缓冲区,然后再利用基本流将数据写到硬盘上

​ 那么在操作代码时len是干啥的呢?其实主要是在两个缓冲区中倒腾数据,将输入流缓冲区中的数据读到,然后写到输出流缓冲区中,等待输出流缓冲区满了,再依靠基本流写到硬盘上;如果输入流缓冲区中的数据读不到了,重新从硬盘上读8192个字节,进入到输入流缓冲区中,继续利用len在两个缓冲区中来回倒腾数据

字符缓冲流

1
我们知道,字符流的基本流底层是有缓冲区的,所以在效率这一块效果不是特别明显,但是不代表不重要,因为我们应该主要学字符缓冲流的两个特有方法

字符缓冲输出流_BufferedWriter

1
2
3
4
5
6
1.构造:
BufferedWriter(Writer w)
2.方法:
用起来和FileWriter一样
3.特有方法:
newLine() 换行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Demo02BufferedWriter {
public static void main(String[] args)throws Exception {
BufferedWriter bw = new BufferedWriter(new FileWriter("module22\\1.txt",true));
bw.write("床前明月光");
bw.newLine();
bw.write("疑是地上霜");
bw.newLine();
bw.write("举头望明月");
bw.newLine();
bw.write("低头思故乡");
bw.newLine();
bw.close();
}
}

字符缓冲输入流_BufferedReader

1
2
3
4
5
6
1.构造:
BufferedReader(Reader r)
2.方法:
用法和FileReader一样
3.特有方法:
String readLine()-> 一次读一行,如果读到结束标记,返回的是null
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
public class Demo03BufferedReader {
public static void main(String[] args)throws Exception {
BufferedReader br = new BufferedReader(new FileReader("module22\\1.txt"));
/*String s = br.readLine();
System.out.println(s);

String s1 = br.readLine();
System.out.println(s1);

String s2 = br.readLine();
System.out.println(s2);

String s3 = br.readLine();
System.out.println(s3);

String s4 = br.readLine();
System.out.println(s4);*/

//定义一个字符串,接收读取的内容
String line = null;
while((line = br.readLine())!=null){
System.out.println(line);
}
br.close();
}
}

转换流

字符编码

计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。[按照某种规则,将字符存储到计算机中,称为编码] 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本f符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。

  • 字符编码Character Encoding : 就是一套自然语言的字符与二进制数之间的对应规则。

字符集

  • **字符集 Charset**:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。

计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。

可见,当指定了编码,它所对应的字符集自然就指定了,所以编码才是我们最终要关心的。

  • ASCII字符集
    • ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
    • 基本的ASCII字符集,使用7位(bits)表示一个字符,共128字符。ASCII的扩展字符集使用8位(bits)表示一个字符,共256字符,方便支持欧洲常用字符。
  • ISO-8859-1字符集
    • 拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰、丹麦、德语、意大利语、西班牙语等。
    • ISO-8859-1使用单字节编码,兼容ASCII编码。
  • GBxxx字符集
    • GB就是国标的意思,是为了显示中文而设计的一套字符集。
    • GB2312:简体中文码表。一个小于127的字符的意义与原来相同。但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127号以下的那些就叫”半角”字符了。
    • GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。
    • GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。
  • Unicode字符集
    • Unicode编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。
    • 它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF-32。最为常用的UTF-8编码。
    • UTF-8编码,可以用来表示Unicode标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。所以,我们开发Web应用,也要使用UTF-8编码。它使用一至四个字节为每个字符编码,编码规则:
      1. 128个US-ASCII字符,只需一个字节编码。
      2. 拉丁文等字符,需要二个字节编码。
      3. 大部分常用字(含中文),使用三个字节编码。
      4. 其他极少使用的Unicode辅助字符,使用四字节编码。
1
2
3
1.想要不乱码,编码和解码遵循的规则(字符编码)要一致 -> 想要不乱码,最重要的是先知道这个字符按照什么编码去存的
2.UTF-8中一个汉字占3个字节
GBK中一个汉字占2个字节

转换流_InputStreamReader

1
2
3
1.字节流读取中文在编码一致的情况,也不要边读边看,因为如果字节读不准,读不全,输出的内容有可能会出现乱码
2.所以,我们学了字符流,字符流读取文本文档中的内容如果编码一致,就不会出现乱码问题了
3.但是如果编码不一致,字符流读取文本文档中的内容也有可能出现乱码
1
2
3
4
5
6
7
8
1.概述:是字节流通向字符流的桥梁 -> 读数据
2.构造:
InputStreamReader(InputStream in,String charsetName)
charsetName:指定编码,不区分大小写
3.作用:
可以直接指定编码,按照指定的编码去读内容
4.用法:
基本用法和FileReader一样
1
2
3
4
5
6
7
8
9
public class Demo01InputStreamReader {
public static void main(String[] args)throws Exception {
InputStreamReader isr =
new InputStreamReader(new FileInputStream("E:\\Idea\\io\\1.txt"),"gbk");
int data = isr.read();
System.out.println((char)data);
isr.close();
}
}

转换流_OutputStreamWriter

1
2
3
4
5
6
7
8
9
1.概述:是字符流通向字节流的桥梁
2.构造:
OutputStreamWriter(OutputStream out,String charsetName)

3.作用:
按照指定的编码规则去存数据

4.用法:
和FileWriter一样
1
2
3
4
5
6
7
8
public class Demo02OutputStreamWriter {
public static void main(String[] args)throws Exception {
OutputStreamWriter osw =
new OutputStreamWriter(new FileOutputStream("E:\\Idea\\io\\1.txt"),"gbk");
osw.write("你好");
osw.close();
}
}

序列化流

介绍

1
2
3
4
5
6
7
8
1.作用:读写对象
2.两个对象:
a.ObjectOutputStream -> 序列化流 -> 写对象
b.ObjectInputStream -> 反序列化流 -> 读对象
3.注意:
我们将对象序列化到文件中,我们打开文件看不懂,这就对了,很多时候,我们操作的数据不能随便让别人看懂,不然别人就随意改动了,我们只需要将这些看不懂的内容成功读回来即可

应用场景:比如玩儿游戏会对英雄存档,那么退出的时候英雄变成对象,将人物的属性变成对象的成员变量值,然后存到文件中,再次打开游戏,直接从文件中将这些人物对象读回来,将对象以及对象中的属性还原

序列化流_ObjectOutputStream

1
2
3
4
5
6
7
1.作用:写对象
2.构造:
ObjectOutputStream(OutputStream out)
3.方法:
writeObject(Object obj) -> 写对象
4.注意:
想要将对象序列化到文件中,被序列化的对象需要实现Serializable接口
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 Person implements Serializable {
private String name;
private Integer age;

public Person() {
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

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

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

1
2
3
4
5
6
7
8
//序列化
private static void write()throws Exception {
ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("module22\\person.txt"));
Person p1 = new Person("涛哥", 12);
oos.writeObject(p1);
oos.close();
}

反序列化_ObjectInputStream

1
2
3
4
5
1.作用:读对象
2.构造:
ObjectInputStream(InputStream in)
3.方法:
Object readObject()
1
2
3
4
5
6
7
private static void read()throws Exception {
ObjectInputStream ois =
new ObjectInputStream(new FileInputStream("module22\\person.txt"));
Person person = (Person) ois.readObject();
System.out.println(person);
ois.close();
}

不想被序列化操作(了解)

1
transient

反序列化时出现的问题以及分析以及解决

1
2
3
问题描述:
序列化之后,修改源码,修改完之后没有重新序列化,直接反序列化了,就会出现了序列号冲突问题:
InvalidClassException

1
2
解决:将序列号定死,后面不管怎么修改源码,序列号都是这一个
在被序列化的对象中加上一个public static final long 的变量,并为其赋值
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
public class Person implements Serializable {
public static final long serialVersionUID = 42L;
private String name;
public Integer age;

public Person() {
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

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

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

将一个对象实现一个序列化接口,我们将来才能让这个对象变成二进制,在网络上传输

经验问题

1
1.问题:循环读取的次数和存储对象的个数不对应,就会出现EOFException
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
public class Demo02Serializable {
public static void main(String[] args) throws Exception {
//write();
read();
}

//反序列化流
private static void read() throws Exception {
ObjectInputStream ois =
new ObjectInputStream(new FileInputStream("module22\\person.txt"));
/* for (int i = 0; i < 4; i++) {
Person person = (Person) ois.readObject();
System.out.println(person);
}*/
//将集合反序列化出来即可
ArrayList<Person> list = (ArrayList<Person>) ois.readObject();
for (Person person : list) {
System.out.println(person);
}
ois.close();
}

//序列化
private static void write() throws Exception {
ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("module22\\person.txt"));
//创建一个集合,存储多个Person对象
ArrayList<Person> list = new ArrayList<>();
Person p1 = new Person("涛哥", 12);
Person p2 = new Person("三上", 30);
Person p3 = new Person("金莲", 24);
//将对象存储到集合中
list.add(p1);
list.add(p2);
list.add(p3);
oos.writeObject(list);
oos.close();
}
}

打印流_PrintStream(了解)

PrintStream打印流基本使用

1
2
3
4
5
1.构造:
PrintStream(String fileName)
2.方法:
a.println(): 原样输出,自带换行效果
b.print(): 原样输出,不带换行效果
1
2
3
4
5
6
7
8
9
10
public class Demo01PrintStream {
public static void main(String[] args)throws Exception{
PrintStream ps = new PrintStream("module22\\printstream.txt");
ps.println("涛哥是一个大帅哥");
ps.println("涛哥是一个小鲜肉");
ps.println("涛哥和金莲不为人知的故事");
ps.close();
}
}

1
2
3
4
5
6
7
改变流向:
1.什么叫做改变流向:
System.out.println()-> 本身是输出到控制台上
改变流向:可以让输出语句从控制台上输出改变成往指定文件中输出

2.方法:System中的方法:
static void setOut(PrintStream out) -> 改变流向 ->让输出语句从控制台输出转移到指定文件中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Demo02PrintStream {
public static void main(String[] args)throws Exception{
PrintStream ps = new PrintStream("module22\\log.txt");

//改变流向
System.setOut(ps);

System.out.println("这个错误是今天下午2点出现的");
System.out.println("这个错误是文件意外到达结尾异常");
System.out.println("出现的原因是循环反序列化次数不对");
ps.close();
}
}

使用场景:

可以将输出的内容以及详细信息放到日志文件中,永久保存

以后我们希望将输出的内容永久保存,但是输出语句会将结果输出到控制台上,控制台是临时显示,如果有新的程序运行,新程序的运行结果会覆盖之前的结果,这样无法达到永久保存,到时候我们想看看之前的运行结果信息就看不到了,所以我们需要将输出的结果保存到日志文件中,就可以使用setOut改变流向

PrintStream打印流完成续写

1
PrintStream(OutputStream out)  -> 可以依靠OutputStream的续写功能完成打印流续写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Demo03PrintStream {
public static void main(String[] args)throws Exception{
PrintStream ps = new PrintStream(new FileOutputStream("module22\\log.txt",true));

//改变流向
System.setOut(ps);

System.out.println("这个错误是今天下午2点出现的");
System.out.println("这个错误是文件意外到达结尾异常");
System.out.println("出现的原因是循环反序列化次数不对");
ps.close();
}
}

Properties集合

Properties结合IO流使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
回顾:
1.概述: Properties extends Hashtable
2.特点:
a.无序,无索引
b.key唯一,value可重复
c.线程安全
d.key和value默认类型都是String

3.特有方法:
setProperty(String key,String value) 存键值对
getProperty(String key) -> 根据key获取value
stringPropertyNames()-> 获取所有的key存放到set集合中
load(InputStream in) -> 将流中的数据加载到Properties集合中
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo01Properties {
public static void main(String[] args) {
Properties properties = new Properties();
properties.setProperty("username","root");
properties.setProperty("password","1234");

Set<String> set = properties.stringPropertyNames();
for (String key : set) {
System.out.println(properties.getProperty(key));
}
}
}

1
2
3
4
使用场景:配合配置文件使用

注意:
将来我们不能将很多的硬数据放到源码中,比如用户名和密码这些数据,因为将来我们有可能换用户名或者密码,如果一换,我们就需要去源码中修改,将来我们的类和类之间都有联系,有可能牵一发动全身,所以我们需要将这些数据提取出来,放到文件中,改的时候直接去文件中改,源码不需要改动
1
2
3
4
5
6
7
8
创建配置文件:
1.在模块下右键 -> file -> 取名为xxx.properties
2.在xxx.properties文件中写配置数据
a.key和value都是key=value形式
b.key和value都是String的,但是不要加双引号
c.每个键值对写完之后,需要换行再写下一对
d.键值对之间最好不要有空格(空格可以有,但是不建议写)
e.键值对中建议不要使用中文(中文可以有,但是直接读取会乱码,需要转换流转码)
1
2
jdbc.username=root
jdbc.password=1234

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo02Properties {
public static void main(String[] args)throws Exception {
Properties properties = new Properties();
FileInputStream fis = new FileInputStream("module22\\jdbc.properties");
properties.load(fis);

Set<String> set = properties.stringPropertyNames();
for (String key : set) {
System.out.println(key+"..."+properties.getProperty(key));
}
}
}

Commons-io工具包

介绍

1
2
3
IO技术开发中,代码量很大,而且代码的重复率较高。如果我们要遍历目录,拷贝目录就需要使用方法的递归调用,也增大了程序的复杂度。

Apache软件基金会,开发了IO技术的工具类`commonsIO`,大大简化IO开发。

添加第三方jar包

1
2
3
4
5
6
7
8
9
10
1.Apache软件基金会属于第三方(Oracle公司是第一方,我们自己是第二方,其他的都是第三方),我们使用第三方开发出来的工具,都需要添加第三方提供给我们的jar包

2.jar包:本身是一个压缩包,里面转的都是class文件,我们想使用jar包中的工具类,就需要将相应的jar包解压到我们的当前项目下

3.怎么引入jar包
a.在当前模块下创建文件夹,取名为lib或者libs
b.将准备好的jar包,放到此文件夹下
c.对着jar包,右键 -> add as library (如果我们想将lib下所有的jar包一起解压,我们就直接对着lib文件夹右键)
d.level可以选择module,此时上面叫做name的输入框会变成空的,不用管
e.直接点ok

工具包的使用

1
2
3
IOUtils类
- 静态方法:IOUtils.copy(InputStream in,OutputStream out)传递字节流,实现文件复制。
- 静态方法:IOUtils.closeQuietly(任意流对象)悄悄的释放资源,自动处理close()方法抛出的异常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Demo01IOUtils {
public static void main(String[] args) /*throws Exception*/{
//- 静态方法:IOUtils.copy(InputStream in,OutputStream out)传递字节流,实现文件复制。
//IOUtils.copy(new FileInputStream("E:\\Idea\\io\\8.jpg"),new FileOutputStream("E:\\Idea\\io\\孝敏.jpg"));
//- 静态方法:IOUtils.closeQuietly(任意流对象)悄悄的释放资源,自动处理close()方法抛出的异常。
FileWriter fw = null;
try{
fw = new FileWriter("module22\\commons.txt");
fw.write("你好");
}catch (Exception e){
e.printStackTrace();
}finally {
if (fw!=null){
IOUtils.closeQuietly(fw);
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
FileUtils类

- 静态方法:FileUtils.copyDirectoryToDirectory(File src,File dest);
传递File类型的目录,进行整个目录的复制,自动进行递归遍历。

参数:
src:要复制的文件夹路径
dest:要将文件夹粘贴到哪里去

- 静态方法:writeStringToFile(File file,String str)写字符串到文本文件中。
- 静态方法:String readFileToString(File file)读取文本文件,返回字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Demo02FileUtils {
public static void main(String[] args)throws Exception {
/* - 静态方法:FileUtils.copyDirectoryToDirectory(File src,File dest);
传递File类型的目录,进行整个目录的复制,自动进行递归遍历。

参数:
src:要复制的文件夹路径
dest:要将文件夹粘贴到哪里去*/
//FileUtils.copyDirectoryToDirectory(new File("E:\\Idea\\io\\aa"),new File("E:\\Idea\\io\\cc"));

//- 静态方法:writeStringToFile(File file,String str)写字符串到文本文件中。
//FileUtils.writeStringToFile(new File("module22\\commons.txt"),"haha");
//- 静态方法:String readFileToString(File file)读取文本文件,返回字符串。
String s = FileUtils.readFileToString(new File("module22\\commons.txt"));
System.out.println(s);
}
}

如何快速记忆IO流

网络编程

1
2
3
概述:在网络通信协议下,不同计算机上运行的程序,进行数据传输
比如:通信,视频通话,网游,邮件等
只要是计算机之间通过网络进行数据传输,就有网络编程的存在

软件结构

  • C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、红蜘蛛、飞秋等软件。

B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有IE、谷歌、火狐等。

两种架构各有优势,但是无论哪种架构,都离不开网络的支持。网络编程,就是在一定的协议下,实现两台计算机的通信的程序。

服务器概念

1
2
1.概述:安装了服务器软件的计算机
2.后面马上要见到的服务器软件:tomcat

网络通信协议:两台计算机在做数据交互时要遵守的规则,协议会对数据的格式,速率等进行规定,只有都遵守了这个协议,才能完成数据交互

两台计算机想完成数据交互,需要遵守网络通信协议

通信三要素

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
[IP地址]:计算机的唯一标识,用于两台计算机之间的连接

a.概述:指互联网协议地址(Internet Protocol Address),俗称IP
计算机的唯一标识
b.作用:可用于计算机和计算机之间的连接
c.IPV4
32位的二进制数,通常被分为4个字节,表示成a.b.c.d 的形式,例如192.168.65.100 。其中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个。
IPV6
为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789->号称能给地球上的每一粒沙子分配一个IP地址

d.查看ip的命令:ipconfig
测试是否能连接其他计算机的命令:ping ip地址

e:特殊的网址:代表的是本机地址,到了哪里都不会变,代表自己
127.0.0.1 -> 固定不变
localhost

localhost(主机名,写的是服务器的IP):端口号/应用名称/资源

localhost:8080/应用名称/index.html


[协议]
TCP:面向连接协议
需要先确认连接,才能进行数据交互
三次握手:
- 第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
- 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
- 第三次握手,客户端再次向服务器端发送确认信息,确认连接。

好处:数据安全,能给数据的传输提供一个安全的传输环境
坏处:效率低

UDP:面向无连接协议
好处:效率高
坏处:传输的数据不安全,容易丢失数据包

[端口号]
每一个应用程序的唯一标识

用两个字节表示的整数,它的取值范围是0~65535。其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。

TCP协议中的三次握手和四次挥手

三次握手过程概述

TCP 三次握手是指在建立连接时,客户端和服务器通过三次交换信息来确保连接的建立。其过程分为以下三步:

1.第一步:客户端发送 SYN
  • 客户端 → 服务器:客户端向服务器发送一个带有 SYN(同步)标志位的数据包,表示客户端请求与服务器建立连接。
  • 该数据包的序列号(Sequence Number)通常会设置为一个初始值 seq = x。此时,客户端进入 SYN_SENT 状态,等待服务器的确认。

目的:

  • 客户端通知服务器自己希望建立连接。
  • 客户端选择了一个初始序列号(x),表示接下来的数据传输将从这个序列号开始。
2.第二步:服务器回应 SYN-ACK
  • 服务器 → 客户端:服务器收到客户端的 SYN 包后,表示同意建立连接,向客户端发送一个带有 SYNACK(确认)标志位的数据包。服务器的 SYN 包的序列号为 seq = y,并且还确认了客户端的序列号 ack = x + 1,表示已经接收到客户端的 SYN 包。
  • 此时,服务器进入 SYN_RCVD 状态,等待客户端的确认。

目的:

  • 服务器同意建立连接,并向客户端发送确认,告诉客户端其同步序列号(y)。
  • 同时,确认客户端的请求序列号(x + 1),表明服务器已经准备好接受数据。
3.第三步:客户端确认 ACK
  • 客户端 → 服务器:客户端收到服务器的 SYN-ACK 包后,发送一个带 ACK 标志的数据包,确认收到了服务器的 SYN 包,并将 ack = y + 1,表示确认服务器的序列号。此时客户端的序列号是 x + 1,客户端进入 ESTABLISHED 状态。
  • 客户端向服务器确认了 seq = x + 1,并且服务器的 ack = y + 1,表示客户端已经准备好接收数据。

目的:

  • 客户端确认服务器的响应包,并通知服务器连接建立成功。
  • 此时,双方的连接已经建立,客户端和服务器都进入 ESTABLISHED 状态,可以开始数据传输。

总结:

  • 第一次握手:客户端发送 SYN 包,请求连接,选择初始序列号(seq = x)。
  • 第二次握手:服务器回应 SYN-ACK 包,确认客户端的请求,选择自己的初始序列号(seq = y)并确认客户端的序列号(ack = x + 1)。
  • 第三次握手:客户端确认收到服务器的 SYN-ACK 包,并确认序列号(ack = y + 1),连接建立成功,双方可以开始数据传输。

三次握手的整个过程确保了双方都能可靠地建立连接并且能够无误地同步初始序列号,避免了连接中数据的混乱和丢失。

四次挥手的过程

四次挥手过程是客户端和服务器在断开连接时逐步完成的,确保双方都能够有序地关闭连接,且数据传输无遗漏。

第一次挥手:客户端发送 FIN 包
  • 客户端 → 服务器:客户端向服务器发送一个 FIN(Finish)包,表示客户端已经没有数据要发送了,准备关闭连接。这个包通常会携带客户端的 序列号 seq = x
  • 客户端进入 FIN_WAIT_1 状态,等待服务器的确认。

目的

  • 客户端通知服务器,自己希望断开连接,并且没有数据要发送了。
第二次挥手:服务器回应 ACK 包
  • 服务器 → 客户端:服务器收到客户端的 FIN 包后,确认客户端的断开请求,发送一个 ACK(确认)包,确认号为 ack = x + 1,表示已经接收到客户端的断开请求。
  • 服务器进入 CLOSE_WAIT 状态,表示服务器已经准备好关闭连接,但仍可以继续发送数据。

目的

  • 服务器确认客户端的 FIN 包,表示已经准备好接收客户端的断开请求,但还可以继续发送数据。
第三次挥手:服务器发送 FIN 包
  • 服务器 → 客户端:当服务器没有数据要发送时,它会向客户端发送一个 FIN 包,表示自己也准备关闭连接。这个包的 序列号seq = y,并且客户端需要确认这个 FIN 包。
  • 服务器进入 LAST_ACK 状态,等待客户端的最后确认。

目的

  • 服务器告诉客户端,服务器也没有数据要发送,准备关闭连接。
第四次挥手:客户端确认 ACK 包
  • 客户端 → 服务器:客户端收到服务器的 FIN 包后,发送一个 ACK 包,确认号为 ack = y + 1,表示已经接收到服务器的断开请求,连接关闭的过程完成。
  • 客户端进入 TIME_WAIT 状态,等待足够的时间以确保服务器收到最后的 ACK 包后,才会完全关闭连接。客户端最终进入 CLOSED 状态。

目的

  • 客户端确认服务器的 FIN 包,表示连接关闭过程已完成。客户端处于 TIME_WAIT 状态,以确保服务器收到最后的 ACK。

UDP协议编程

1
2
1.DatagramSocket -> 好比寄快递找的快递公司
2.DatagramPacket -> 好比快递公司打包

客户端(发送端)

1
2
3
4
5
6
7
8
9
1.创建DatagramSocket对象(快递公司)
a.空参:端口号从可用的端口号中随机一个使用
b.有参:自己指定
2.创建DatagramPacket对象,将数据进行打包
a.要发送的数据-> byte[]
b.指定接收端的IP
c.指定接收端的端口号
3.发送数据
4.释放资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Send {
public static void main(String[] args) throws Exception{
/*1.创建DatagramSocket对象(快递公司)
a.空参:端口号从可用的端口号中随机一个使用
b.有参:自己指定*/
DatagramSocket socket = new DatagramSocket();
/* 2.创建DatagramPacket对象,将数据进行打包
a.要发送的数据-> byte[]
b.指定接收端的IP
c.指定接收端的端口号*/
byte[] bytes = "你好呀".getBytes();
InetAddress ip = InetAddress.getByName("127.0.0.1");
int port = 6666;
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, ip, port);
//3.发送数据
socket.send(dp);
//4.释放资源
socket.close();
}
}

直接执行发现,发送端在没有接收端的情况下,不会报错,因为UDP协议是面向无连接协议,不管有没有接收端,照发不误

服务端(接收端)

1
2
3
4
1.创建DatagramSocket对象,指定服务端的端口号
2.接收数据包
3.解析数据包
4.释放资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Receive {
public static void main(String[] args) throws Exception{
//1.创建DatagramSocket对象,指定服务端的端口号
DatagramSocket socket = new DatagramSocket(6666);
//2.接收数据包
byte[] bytes = new byte[1024];//用于保存接收过来的数据
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
socket.receive(dp);
//3.解析数据包
byte[] data = dp.getData();//接收的数据
int len = dp.getLength();//从数据包中获取多少个数据
InetAddress address = dp.getAddress();//获取发送端的主机
int port = dp.getPort();//发送端的端口号
System.out.println(new String(data,0,len));
System.out.println(address+"..."+port);
//4.释放资源
socket.close();
}
}

TCP协议编程

编写客户端

1
2
3
4
1.创建Socket对象,指明服务端的ip以及端口号
2.调用socket中的getOutputStream,往服务端发送请求
3.调用socket中的getInputStream,读取服务端响应回来的数据
4.关流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Client {
public static void main(String[] args)throws Exception {
//1.创建Socket对象,指明服务端的ip以及端口号
Socket socket = new Socket("127.0.0.1", 6666);
//2.调用socket中的getOutputStream,往服务端发送请求
OutputStream os = socket.getOutputStream();
os.write("我想下载一个小电影".getBytes());
//3.调用socket中的getInputStream,读取服务端响应回来的数据
InputStream is = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new String(bytes,0,len));
//4.关流
is.close();
os.close();
socket.close();
}
}

编写服务端

1
2
3
4
5
1.创建ServerSocket对象,设置端口号
2.调用ServerSocket中的accept方法,等待客户端连接,返回Socket对象
3.调用socket中的getInputStream,用于读取客户端发送过来的数据
4.调用socket中的getOutputStream,用于给客户端响应数据
5.关闭资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Server {
public static void main(String[] args)throws Exception {
//1.创建ServerSocket对象,设置端口号
ServerSocket ss = new ServerSocket(6666);
//2.调用ServerSocket中的accept方法,等待客户端连接,返回Socket对象
Socket socket = ss.accept();
//3.调用socket中的getInputStream,用于读取客户端发送过来的数据
InputStream is = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new String(bytes,0,len));
//4.调用socket中的getOutputStream,用于给客户端响应数据
OutputStream os = socket.getOutputStream();
os.write("给你一个小电影".getBytes());
//5.关闭资源
os.close();
is.close();
socket.close();
ss.close();
}
}

实战:文件上传

文件上传客户端以及服务端实现

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
public class Client {
public static void main(String[] args)throws Exception {
//1.创建Socket对象
Socket socket = new Socket("127.0.0.1", 6666);
//2.创建FileInputStream,用于读取本地上的图片
FileInputStream fis = new FileInputStream("E:\\Idea\\io\\24.jpg");
//3.调用getOutputStream,用于将读取过来的图片写给服务端
OutputStream os = socket.getOutputStream();
//4.边读边写
byte[] bytes = new byte[1024];
int len;
while((len = fis.read(bytes))!=-1){
os.write(bytes,0,len);
}

//给服务端写一个结束标记
socket.shutdownOutput();
System.out.println("======以下代码是读取响应的结果======");

//5.调用getInputStream,读取响应结果
InputStream is = socket.getInputStream();
byte[] bytes1 = new byte[1024];
int len1 = is.read(bytes1);
System.out.println(new String(bytes1,0,len1));

//6.关流
is.close();
os.close();
fis.close();
socket.close();
}
}

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
public class Server {
public static void main(String[] args)throws Exception {
//1.创建ServerSocket对象
ServerSocket ss = new ServerSocket(6666);
//2.调用accept方法等待客户端的连接
Socket socket = ss.accept();
//3.调用socket中的getInputStream,读取客户端发送过来的图片
InputStream is = socket.getInputStream();

/*
UUID调用randomUUID(),再调用toString,将其转成String
*/
String s = UUID.randomUUID().toString();
String name = s + System.currentTimeMillis();

//4.创建FileOutputStream,将读取过来的图片写到硬盘上
FileOutputStream fos = new FileOutputStream("E:\\Idea\\io\\upload\\"+name+".jpg");
//5.边读边写
byte[] bytes = new byte[1024];
int len;
while((len = is.read(bytes))!=-1){
fos.write(bytes,0,len);
}

System.out.println("======以下代码是给客户端的响应结果======");

//6.调用socket中的getOutputStream,给客户端响应结果
OutputStream os = socket.getOutputStream();
os.write("上传成功".getBytes());
//7.关流
os.close();
fos.close();
is.close();
socket.close();
ss.close();
}
}

1
2
3
4
5
6
> public class Demo01UUID {
> public static void main(String[] args) {
> String string = UUID.randomUUID().toString();//生成一个十六进制的随机数
> System.out.println("string = " + string);
> }
> }

文件上传服务端实现(多线程)

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
public class ServerThread {
public static void main(String[] args) throws Exception {
//1.创建ServerSocket对象
ServerSocket ss = new ServerSocket(6666);

while (true) {

//2.调用accept方法等待客户端的连接
//accept阻塞,有新客户端连接才会创建一个新的线程。
Socket socket = ss.accept();

new Thread(new Runnable() {
@Override
public void run() {
InputStream is = null;
FileOutputStream fos = null;
OutputStream os = null;
try {
//3.调用socket中的getInputStream,读取客户端发送过来的图片
is = socket.getInputStream();

/*
UUID调用randomUUID(),再调用toString,将其转成String
*/
String s = UUID.randomUUID().toString();
String name = s + System.currentTimeMillis();

//4.创建FileOutputStream,将读取过来的图片写到硬盘上
fos = new FileOutputStream("E:\\Idea\\io\\upload\\" + name + ".jpg");
//5.边读边写
byte[] bytes = new byte[1024];
int len;
while ((len = is.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}

System.out.println("======以下代码是给客户端的响应结果======");

//6.调用socket中的getOutputStream,给客户端响应结果
os = socket.getOutputStream();
os.write("上传成功".getBytes());

} catch (Exception e) {
e.printStackTrace();
}finally {
//7.关流
CloseUtils.closeQ(socket,fos,is,os);
}

}
}).start();

}

}
}

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
public class CloseUtils {
private CloseUtils(){

}
public static void closeQ(Socket socket, FileOutputStream fos, InputStream is, OutputStream os){
if (os!=null){
try {
os.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

if (fos!= null){
try {
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

if (is!=null){
try {
is.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

if (socket!=null){
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}


文件上传线程池版

虽然代码实现了多线程,但没有使用线程池来限制并发数,这可能会导致以下问题:

  • 过多线程可能导致系统资源耗尽:每次有新的客户端连接,服务器都会创建一个新的线程。如果客户端的连接非常频繁,服务器可能会创建过多的线程,消耗大量的系统资源(如内存和 CPU)。如果并发量过大,可能会导致系统资源耗尽,甚至使服务器崩溃。

对有潮汐效应的业务,还可以通过**ScheduledExecutorService**动态调整线程池的大小。

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
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;

public class ServerThreadWithPool {
private static final int PORT = 6666;
private static final int THREAD_POOL_SIZE = 10; // 最大线程数

public static void main(String[] args) throws Exception {
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);

// 1.创建ServerSocket对象
ServerSocket ss = new ServerSocket(PORT);

while (true) {
// 2.调用accept方法等待客户端的连接
Socket socket = ss.accept();

// 3. 使用线程池处理请求
executorService.submit(new Runnable() {
@Override
public void run() {
InputStream is = null;
FileOutputStream fos = null;
OutputStream os = null;
try {
// 3.1 获取输入流,读取客户端发送过来的图片
is = socket.getInputStream();
String s = UUID.randomUUID().toString();
String name = s + System.currentTimeMillis();

// 4. 创建文件输出流,将读取到的图片保存到硬盘
fos = new FileOutputStream("E:\\Idea\\io\\upload\\" + name + ".jpg");
byte[] bytes = new byte[1024];
int len;
while ((len = is.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}

// 5. 响应客户端上传成功
os = socket.getOutputStream();
os.write("上传成功".getBytes());
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6. 关闭流
CloseUtils.closeQ(socket, fos, is, os);
}
}
});
}
}
}

反射

类的加载

类的加载时机

1
2
3
4
5
1.new对象
2.new子类对象(new子类对象先初始化父类)
3.执行main方法
4.调用静态成员
5.反射,创建Class对象

类加载器(了解)_ClassLoader

1
类加载器咱们基于jdk8讲解
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
1.概述:
在jvm中,负责将本地上的class文件加载到内存的对象_ClassLoader
2.分类:
BootStrapClassLoader:根类加载器->C语言写的,我们是获取不到的
也称之为引导类加载器,负责Java的核心类加载的
比如:System,String等
jre/lib/rt.jar下的类都是核心类
ExtClassLoader:扩展类加载器
负责jre的扩展目录中的jar包的加载
在jdk中jre的lib目录下的ext目录
AppClassLoader:系统类加载器
负责在jvm启动时加载来自java命令的class文件(自定义类),以及classPath环境变量所指定的jar包(第三方jar包)

不同的类加载器负责加载不同的类

3.三者的关系(从类加载机制层面):AppClassLoader的父类加载器是ExtClassLoader
ExtClassLoader的父类加载器是BootStrapClassLoader

但是:他们从代码级别上来看,没有子父类继承关系->他们都有一个共同的父类->ClassLoader

4.获取类加载器对象:getClassLoader()是Class对象中的方法
类名.class.getClassLoader()

5.获取类加载器对象对应的父类加载器
ClassLoader类中的方法:ClassLoader
getParent()->没啥用

6.双亲委派(全盘负责委托机制)

a.Person类中有一个String
Person本身是AppClassLoader加载
String是BootStrapClassLoader加载
b.加载顺序:
Person本身是App加载,按道理来说String也是App加载
但是App加载String的时候,先问一问Ext,说:Ext你加载这个String吗?
Ext说:我不加载,我负责加载的是扩展类,但是app你别着急,我问问我爹去->boot
Ext说:boot,你加载String吗?
boot说:正好我加载核心类,行吧,我加载吧!

7.类加载器的cache(缓存)机制(扩展):一个类加载到内存之后,缓存中也会保存一份儿,后面如果再使用此类,如果缓存中保存了这个类,就直接返回他,如果没有才加载这个类.下一次如果有其他类在使用的时候就不会重新加载了,直接去缓存中拿,保证了类在内存中的唯一性

8.所以:类加载器的双亲委派和缓存机制共同造就了加载类的特点:保证了类在内存中的唯一性
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
public class Demo01ClassLoader {
public static void main(String[] args) {
app();
//ext();
//boot();
}

/**
* 负责加载核心类
* rt.jar包中的类
*
* BootStrapClassLoader是C语言编写,我们获取不到
*/
private static void boot() {
ClassLoader classLoader = String.class.getClassLoader();
System.out.println("classLoader = " + classLoader);
}


/**
* 负责加载扩展类
*/
private static void ext() {
ClassLoader classLoader = DNSNameService.class.getClassLoader();
System.out.println("classLoader = " + classLoader);
}

/**
* 负责加载自定义类以及第三方jar中的类
*/
private static void app() {
ClassLoader classLoader = Demo01ClassLoader.class.getClassLoader();
System.out.println("classLoader = " + classLoader);

ClassLoader classLoader1 = FileUtils.class.getClassLoader();
System.out.println("classLoader1 = " + classLoader1);

ClassLoader parent = classLoader1.getParent();
System.out.println("parent = " + parent);

//ClassLoader parent1 = parent.getParent();
//System.out.println("parent1 = " + parent1);
}
}

介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1.反射概述:解剖class对象的一个技术
2.问题:能解剖class对象的啥呢?
a.解剖出成员变量 : 赋值
b.解剖出成员方法: 调用
c.解剖出构造方法: new对象

3.用反射的好处:让代码变的更通用,更灵活

4.怎么学反射:
a.将反射看成是一套API来学
b.通过案例,体会反射的好处,用反射写出来的代码有多牛掰

5.问题:玩反射,最开始的一步是干啥?
获取Class对象

6.class对象:class文件对应的对象
class类:描述class对象的类叫做class类

反射之获取Class对象

1
2
3
4
5
6
7
1.方式1:调用Object中的getClass方法:
Class <?> getClass()
2.方式2:不管是基本类型还是引用类型,jvm都为其提供了一个静态成员:class

3.方式3:Class类中的静态方法:
static Class<?> forName(String className)
className:传递的是类的全限定名(包名.类名)
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
public class Person {
private String name;
private Integer age;

public Person() {
}

//私有构造
private Person(String name){
this.name = name;
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

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

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

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
public class Demo01GetClass {
@Test
public void get1()throws Exception{
/*
1.方式1:调用Object中的getClass方法:
Class <?> getClass()
*/
Person person = new Person();
Class<? extends Person> aClass1 = person.getClass();
System.out.println("aClass1 = " + aClass1);

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

//2.方式2:不管是基本类型还是引用类型,jvm都为其提供了一个静态成员:class
Class<Person> aClass2 = Person.class;
System.out.println("aClass2 = " + aClass2);

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

/*
static Class<?> forName(String className)
className:传递的是类的全限定名(包名.类名)
*/
Class<?> aClass3 = Class.forName("com.example.c_reflect.Person");
System.out.println("aClass3 = " + aClass3);

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

System.out.println(aClass1==aClass2);
}
}

写类的全限定名小技巧:

1.如何快速写类的全限定名:

​ a.直接复制粘贴

b.直接写类名 -> 回车选中

2.如何检测类的全限定名写对了

​ 按ctrl不放,鼠标点击此类名,能跳到对应的类中,就证明写对了

三种获取Class对象的方式最通用的一种

1
2
3
4
1. 方式3:Class类中的静态方法:
static Class<?> forName(String className)
className:传递的是类的全限定名(包名.类名)
2.原因:参数为String形式,可以和properties文件结合使用
1
className=com.example.c_reflect.Student
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Demo02GetClass {
public static void main(String[] args)throws Exception {
Properties properties = new Properties();
FileInputStream in = new FileInputStream("module25\\pro.properties");
properties.load(in);

String className = properties.getProperty("className");
System.out.println(className);

Class<?> aClass = Class.forName(className);
System.out.println("aClass = " + aClass);

}
}

反射成员变量

获取所有属性

1
2
3
4
Class类中的方法:

1.Field[] getFields() -> 获取所有public的属性
2.Field[] getDeclaredFields() -> 获取所有属性,包括priavte的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 1.Field[] getFields() -> 获取所有public的属性
* 2.Field[] getDeclaredFields() -> 获取所有属性,包括priavte的
*/
private static void method01() {
Class<Student> studentClass = Student.class;
Field[] fields = studentClass.getFields();
for (Field field : fields) {
System.out.println(field);
}

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

Field[] df = studentClass.getDeclaredFields();
for (Field field : df) {
System.out.println(field);
}
}

获取指定属性

1
2
3
4
5
6
7
8
9
10
11
12
13
Class类中的方法:

1.Field getField(String name) -> 获取指定public的属性
2.Field getDeclaredField(String name) -> 获取指定属性,包括priavte的

3.Field类中的方法:
void set(Object obj,Object value) -> 为属性赋值,相当于javabean中的set方法
obj:对象
value:赋予的值

Object get(Object obj) -> 获取属性值
obj:对象
void setAccessible(boolean flag) -> 解除私有权限
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
 /**
* Field getField(String name) -> 获取指定public的属性
*/
private static void method02()throws Exception {
Class<Student> studentClass = Student.class;
Student student = studentClass.newInstance();
Field age = studentClass.getField("age");
//调用set方法为属性赋值
age.set(student,10);
//调用get方法获取属性值 -> 相当于javabean中的get方法
Object o = age.get(student);
System.out.println("o = " + o);
}

/**
* Field getDeclaredField(String name) -> 获取指定属性,包括priavte的
*/
private static void method03()throws Exception {
Class<Student> studentClass = Student.class;
Student student = studentClass.newInstance();
Field name = studentClass.getDeclaredField("name");

//解除私有权限
name.setAccessible(true);

//调用set方法为属性赋值
name.set(student,"柳岩");
//调用get方法获取属性值 -> 相当于javabean中的get方法
Object o = name.get(student);
System.out.println("o = " + o);
}

获取Class对象中的构造方法

获取所有构造方法

1
2
3
1.Class类中的方法:
Constructor<?>[] getConstructors() -> 获取所有public的构造
Constructor<?>[] getDeclaredConstructors()获取所有构造方法,包括private
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Demo03GetConstructor {
public static void main(String[] args) {
//获取Class对象
Class<Person> aClass = Person.class;
//获取所有public的构造
Constructor<?>[] constructors = aClass.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}

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

//获取所有构造方法,包括private
Constructor<?>[] dc = aClass.getDeclaredConstructors();
for (Constructor<?> constructor : dc) {
System.out.println(constructor);
}
}
}

获取指定构造

1
2
3
4
5
6
7
8
9
10
11
12
13
1.Class类中的方法:
Constructor<T> getConstructor(Class<?>... parameterTypes)->获取指定的public的构造
Constructor<T> getDeclaredConstructor(class<?>... parameterTypes) -> 获取指定构造,包括private
parameterTypes:可变参数,可以传递0个或者多个参数
a.如果获取的是空参构造:参数不用写
b.如果获取的是有参构造:参数写参数类型的class对象

2.Constructor类中的方法:
T newInstance(Object...initargs) -> 创建对象
initargs:传递的是构造方法的实参
a.如果根据无参构造new对象,initargs不写了
b.如果根据有参构造new对象,initargs传递实参
3.void setAccessible(boolean flag) -> 解除私有权限

`

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 class Demo06GetConstructor {
public static void main(String[] args)throws Exception {
//获取Class对象
Class<Person> aClass = Person.class;

Constructor<Person> constructor = aClass.getConstructor(String.class, Integer.class);
System.out.println("constructor = " + constructor);

//创建对象-> 好比是Person person = new Person("三上",26)
Person person = constructor.newInstance("三上", 26);

//好比是直接输出Person对象,直接调用toString
System.out.println(person);

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

Constructor<Person> dc = aClass.getDeclaredConstructor(String.class);
dc.setAccessible(true);//解除私有权限->暴力反射

Person person = dc.newInstance("三上");
System.out.println(person);
}
}

反射方法

利用反射获取所有成员方法

1
2
3
1.Class类中方法:
Method[] getMethods() -> 获取所有public的方法,包括父类中的public方法
Method[] getDeclaredMethods() -> 获取所有的成员方法,包括private
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 /**
* 获取所有方法
*/
private static void method01() {
Class<Person> aClass = Person.class;
Method[] methods = aClass.getMethods();
for (Method method : methods) {
System.out.println(method);
}

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

Method[] dm = aClass.getDeclaredMethods();
for (Method method : dm) {
System.out.println(method);
}
}

获取指定方法

1
2
3
4
5
6
7
8
9
10
11
12
13
1.Class类中的方法:
Method getMethod(String name, Class<?>... parameterTypes) 获取指定的public的成员方法
Method getDeclaredMethod(String name, 类<?>... parameterTypes)-> 获取执行成员方法,包括private
name:传递方法名
parameterTypes:方法参数类型的class对象

2.调用方法:Method对象中的方法:
Object invoke(Object obj, Object... args) -> 执行方法
obj:根据构造new出来的对象
args:方法实参 -> 如果有参数,直接传递实参;否则不用传

返回值:Object -> 接收被执行方法的返回值,如果方法没有返回值,不用接收了
3.解除私有权限:void setAccessible(boolean flag)
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
  /**
* 获取指定方法
*/
private static void method02()throws Exception {
Class<Person> aClass = Person.class;

//创建对象
Person person = aClass.newInstance();

Method setName = aClass.getMethod("setName", String.class);
/*
相当于person.setName("柳岩")
*/
setName.invoke(person,"柳岩");

System.out.println(person);//好比调用toString方法

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

Method getName = aClass.getMethod("getName");

//好比是person.getName()
Object o = getName.invoke(person);
System.out.println(o);

Method eatMethod = aClass.getDeclaredMethod("eat");
eatMethod.setAccessible(true);
method.invoke(person);
}

注解

注解的介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.引用数据类型:
类 数组 接口 枚举 注解

1.jdk1.5版本的新特性->一个引用数据类型
和类,接口,枚举是同一个层次的

引用数据类型:类 数组 接口 枚举 注解
2.作用:
说明:对代码进行说明,生成doc文档(API文档)
检查:检查代码是否符合条件 @Override(会用) @FunctionalInterface
分析:对代码进行分析,起到了代替配置文件的作用(会用)
3.JDK中的注解:
@Override -> 检测此方法是否为重写方法
jdk1.5版本,支持父类的方法重写
jdk1.6版本,支持接口的方法重写
@Deprecated -> 方法已经过时,不推荐使用
调用方法的时候,方法上会有横线,但是能用
@SuppressWarnings->消除警告 @SuppressWarnings("all")

1
2
3
4
5
6
public class Person {
@Deprecated
public void eat(){
System.out.println("人要吃饭");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
@SuppressWarnings("all")
public class Test01 {
public static void main(String[] args) {
Person person = new Person();
person.eat();

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

ArrayList list = new ArrayList();
list.add(1);
}
}

自定义注解

定义格式

1
大家需要知道的是,咱们这里说的注解属性,其实本质上是抽象方法,但是我们按照属性来理解,好理解,因为到时候使用注解的时候,需要用=为其赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
1.定义:
public @interface 注解名{

}

2.定义属性:增强注解的作用
数据类型 属性名() -> 此属性没有默认值,需要在使用注解的时候为其赋值
数据类型 属性名() default 值 -> 此属性有默认值,如果有需要,还可以二次赋值

3.注解中能定义什么类型的属性呢?
a.8种基本类型
b.String类型,class类型,枚举类型,注解类型
c.以及以上类型的一维数组
1
2
3
4
5
6
7
8
9
10
public @interface Book {
//书名
String bookName();
//作者
String[] author();
//价格
int price();
//数量
int count() default 10;
}

设置元注解

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
1.概述:元注解就是管理注解的注解
2.从哪些方面管理呢?
a.控制注解的使用位置
控制注解是否能在类上使用
控制注解是否能在构造上使用
控制注解是否能在方法上使用等

b.控制注解的生命周期(加载位置)
控制注解是否能在源码中出现
控制注解是否能在class文件中出现
控制注解是否能在内存中出现

3.怎么使用:
a.@Target:控制注解的使用位置
属性:ElementType[] value();
ElementType是一个枚举,里面的成员可以类名直接调用
ElementType中的成员:
TYPE:控制注解能使用在类上
FIELD:控制注解能使用在属性上
METHOD:控制注解能使用在方法上
PARAMETER:控制注解能使用在参数上
CONSTRUCTOR:控制注解能使用在构造上
LOCAL_VARIABLE:控制注解能使用在局部变量上

b.@Retention:控制注解的生命周期(加载位置)
属性:RetentionPolicy value();
RetentionPolicy是一个枚举,里面的成员可以类名直接调用
RetentionPolicy中的成员:
SOURCE:控制注解能在源码中出现 -> 默认
CLASS:控制注解能在class文件中出现
RUNTIME:控制注解能在内存中出现
1
2
3
4
5
6
7
8
9
10
11
12
13
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
//书名
String bookName();
//作者
String[] author();
//价格
int price();
//数量
int count() default 10;
}

不设置元注解,解析的时候获取不到值。

注解的使用

添加注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1.注解的使用:
说白了就是为注解中的属性赋值
2.使用位置上:
在类上使用,方法上使用,成员变量上使用,局部变量上使用,参数位置使用等
3.使用格式:
a.@注解名(属性名 = 值,属性名 = 值...)
b.如果属性中有数组:
@注解名(属性名 = {元素1,元素2...})

注解注意事项:
1.空注解可以直接使用->空注解就是注解中没有任何的属性
2.不同的位置可以使用一样的注解,但是同样的位置不能使用一样的注解
3.使用注解时,如果此注解中有属性,注解中的属性一定要赋值,如果有多个属性,用,隔开
如果注解中的属性有数组,使用{}
4.如果注解中的属性值有默认值,那么我们不必要写,也不用重新赋值,反之必须写上
5.如果注解中只有一个属性,并且属性名叫value,那么使用注解的时候,属性名不用写,直接写值
(包括单个类型,还包括数组)
1
2
3
public @interface Book1 {
String value();
}
1
2
3
4
5
6
7
8
9
@Book(bookName = "金瓶梅", author = {"涛哥", "金莲"}, price = 10, count = 20)
@Book1("水浒野传")
public class BookShelf {
@Book(bookName = "金瓶梅", author = {"涛哥", "金莲"}, price = 10, count = 20)
public void method() {

}
}

注解解析->AnnotatedElement接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
注解的解析:说白了就是将注解中的属性值获取出来

1.注解解析涉及到的接口:AnnotatedElement接口
实现类: AccessibleObject, Class, Constructor, Executable, Field, Method, Package, Parameter

2.解析思路:先判断指定位置上有没有使用指定的注解,如果有,获取指定的注解,获取注解中的属性值
a.boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)->判断指定位置上有没有指定的注解

比如:判断BookShelf上有没有Book注解
Class bookShelf = BookShelf.class
bookShelf.isAnnotationPresent(Book.class)

b.getAnnotation(Class<T> annotationClass) ->获取指定的注解,返回值类型为获取的注解类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test01 {
public static void main(String[] args) {
//1.获取BookShelf的class对象
Class<BookShelf> bookShelfClass = BookShelf.class;
//2.判断bookShelf上有没有Book注解
boolean b = bookShelfClass.isAnnotationPresent(Book.class);
//3.判断,如果b为true,就获取
if (b){
Book book = bookShelfClass.getAnnotation(Book.class);
System.out.println(book.bookName());
System.out.println(Arrays.toString(book.author()));
System.out.println(book.price());
System.out.println(book.count());
}
}
}

Junit单元测试

Junit介绍

1
2
3
1.概述:Junit是一个单元测试框架,可以代替main方法去执行其他的方法
2.作用:可以单独执行一个方法,测试该方法是否能跑通
3.注意:Junit是第三方工具,所以使用之前需要导入jar包

Junit的基本使用(重点)

1
2
3
4
5
6
7
8
9
10
1.导入Junit的jar包
2.定义一个方法,在方法上写注解: @Test
3.执行方法:
a.点击该方法左边的绿色按钮,点击run执行 -> 单独执行一个指定的方法
b.如果想要执行所有带@Test的方法,点击类名左边绿色按钮,点击run执行 -> 执行当前类中所有带@Test的方法
4.注意事项:
a.@Test不能修饰static方法
b.@Test不能修饰带参数的方法
c.@Test不能修饰带返回值的方法

1
2
3
4
5
6
7
8
9
10
11
12
public class Demo01Junit {
@Test
public void add(){
System.out.println("我是@Test执行的add方法");
}

@Test
public void delete(){
System.out.println("我是@Test执行的delete方法");
}
}

Junit其他注解

1
2
3
4
@Before:在@Test之前执行,有多少个@Test执行,@Before就执行多少次->都是用作初始化一些数据
@After:在@Test之后执行,有多少个@Test执行,@After就执行多少次-> 都是用作释放资源使用
@BeforeClass:在@Test之前执行,只执行一次,可以修饰静态方法
@AfterClass:@Test之后执行,只执行一次,可以修饰静态方法
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
public class Demo02Junit {
@Test
public void add(){

System.out.println("我是@Test执行的add方法");
}

@Test
public void delete(){

System.out.println("我是@Test执行的delete方法");
}


@Before
public void methodBefore(){
System.out.println("我是@Before执行的方法");
}

@After
public void methodAfter(){
System.out.println("我是@After执行的方法");
}

@BeforeClass
public static void methodBeforeClass(){
System.out.println("我是@BeforeClass执行的方法");
}


@AfterClass
public static void methodAfterClass(){
System.out.println("我是@AfterClass执行的方法");
}

/**
* 我是@BeforeClass执行的方法
* 我是@Before执行的方法
* 我是@Test执行的delete方法
* 我是@After执行的方法
* 我是@Before执行的方法
* 我是@Test执行的add方法
* 我是@After执行的方法
* 我是@AfterClass执行的方法
* */

}

枚举

枚举介绍

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.概述:五大引用数据类型:
类 数组 接口 注解 枚举

2.定义:
public enum 枚举类名{

}
所有的枚举类父类都是Enum

3.定义枚举值:
a.枚举值特点:都是static final,但是定义的时候不要写出来,写出来报错
写完所有的枚举值之后,最后加个;
枚举值名字要大写 -> 开发习惯

b.使用:类名直接调用

c.注意:每一个枚举值都是当前枚举类的对象

4.问题:枚举类中的枚举值都是什么类型的?
本类类型

5.枚举类中其他成员:构造
在枚举类中定义的构造,默认都是private

6.枚举类的使用场景:
表示对象的状态
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 enum State {
//State WEIFUKUAN = new State()
//State WEIFUKUAN = new State("未付款")
WEIFUKUAN("未付款"),
//State YIFUKUAN = new State()
//State YIFUKUAN = new State("已付款")
YIFUKUAN("已付款"),
//State WEIFAHUO = new State()
//State WEIFAHUO = new State("未发货")
WEIFAHUO("未发货"),
//State YIFAHUO = new State()
//State YIFAHUO = new State("已发货")
YIFAHUO("已发货");

private String name;

private State() {

}

State(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

1
2
3
4
5
6
7
8
9
10
public class Test01 {
public static void main(String[] args) {
State weifahuo = State.WEIFAHUO;
System.out.println(weifahuo);//默认调用toString

State yifukuan = State.YIFUKUAN;
System.out.println(yifukuan.getName());

}
}

枚举的方法_Enum

方法名 说明
String toString() 返回枚举值的名字
values() 返回所有与的枚举值
valueOf(String str) 将一个字符串转成枚举类型
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
public class Test01 {
public static void main(String[] args) {
State weifahuo = State.WEIFAHUO;
System.out.println(weifahuo);//默认调用toString

State yifukuan = State.YIFUKUAN;
System.out.println(yifukuan.getName());

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

String string = State.WEIFUKUAN.toString();
System.out.println("string = " + string);

System.out.println("===================");
State[] values = State.values();
for (State value : values) {
System.out.println(value);
}

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

State yifahuo = State.valueOf("YIFAHUO");
System.out.println("yifahuo = " + yifahuo);
}
}

正则表达式

正则表达式的概念及演示

1
2
3
4
5
6
1.概述:正则表达式是一个具有特殊规则的字符串
2.作用:校验
比如:校验手机号,身份证号,密码,用户名,邮箱等
3.String中有一个校验正则的方法:
boolean matches(String regex) 校验字符串是否符合指定的regex的规则
4.比如:校验QQ号(不能以0开头,必须都是数字,必须是5-15位的)

正则表达式生成网址:

1
https://www.sojson.com/regex/generate
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 Demo01Regex {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String data = scanner.next();
//boolean result01 = method01(data);
//System.out.println("result01 = " + result01);
boolean result02 = method02(data);
System.out.println("result02 = " + result02);
}

private static boolean method02(String data) {
boolean result = data.matches("[1-9][0-9]{4,14}");
return result;
}

private static boolean method01(String data) {
//不能是0开头的
if (data.startsWith("0")) {
return false;
}

//必须都是数字
char[] chars = data.toCharArray();
for (char aChar : chars) {
if (aChar < '0' || aChar > '9') {
return false;
}
}

//必须是5-15位
if (data.length()<5 || data.length()>15){
return false;
}

return true;
}
}

正则表达式-字符类

1
2
3
4
5
6
7
8
9
10
java.util.regex.Pattern:正则表达式的编译表示形式。
正则表达式-字符类:[]表示一个区间,范围可以自己定义
语法示例:
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之间的任意一个字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//字符类
private static void method01() {
//1.验证字符串是否以h开头,d结尾,中间是aeiou的某一个字符
boolean result01 = "had".matches("[h][aeiou][d]");
System.out.println("result01 = " + result01);

//2.验证字符串是否以h开头,d结尾,中间不是aeiou的某个字符
boolean result02 = "hyd".matches("[h][^aeiou][d]");
System.out.println("result02 = " + result02);

//3.验证字符串是否是开头a-z的任意一个小写字母,后面跟ad
boolean result03 = "had".matches("[a-z][a][d]");
System.out.println("result03 = " + result03);
}

正则表达式-逻辑运算符

1
2
3
4
正则表达式-逻辑运算符
语法示例:
1. &&:并且
2. | :或者
1
2
3
4
5
6
7
8
9
10
11
12
/*
逻辑运算符
*/
private static void method02() {
//1.要求字符串是小写字母并且字符不能以[aeiou]开头,后面跟ad
boolean result01 = "yad".matches("[[a-z]&&[^aeiou]][a][d]");
System.out.println("result01 = " + result01);

//2.要求字符是aeiou的某一个字符开头,后面跟ad
boolean result02 = "had".matches("[a|e|i|o|u][a][d]");
System.out.println("result02 = " + result02);
}

正则表达式-预定义字符

1
2
3
4
5
6
7
8
9
正则表达式-预定义字符
语法示例:
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]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//预定义字符
private static void method03() {
//1.验证字符串是否是三位数字
//boolean result01 = "111".matches("[0-9][0-9][0-9]");
boolean result01 = "111".matches("\\d\\d\\d");
System.out.println("result01 = " + result01);

//2.验证手机号: 1开头 第二位3 5 8 剩下的都是0-9的数字
boolean result02 = "13838381438".matches("[1][358]\\d\\d\\d\\d\\d\\d\\d\\d\\d");
System.out.println("result02 = " + result02);

//3.验证字符串是否以h开头,d结尾,中间是任意一个字符
boolean result03 = "had".matches("[h].[d]");
System.out.println("result03 = " + result03);
}

正则表达式-数量词

1
2
3
4
5
6
7
8
正则表达式-数量词
语法示例:x代表字符
1. X? : x出现的数量为 0次或1
2. X* : x出现的数量为 0次到多次 任意次
3. X+ : x出现的数量为 1次或多次 X>=1
4. X{n} : x出现的数量为 恰好n次 X=n次
5. X{n,} : x出现的数量为 至少n次 X>=n次 x{3,}
6. X{n,m}: x出现的数量为 n到m次(n和m都是包含的) n<=X<=m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//数量词
private static void method04() {
//1.验证字符串是否是三位数字
boolean result01 = "111".matches("\\d{3}");
System.out.println("result01 = " + result01);
//2.验证手机号: 1开头 第二位3 5 8 剩下的都是0-9的数字
boolean result02 = "13838381438".matches("[1][358]\\d{9}");
System.out.println("result02 = " + result02);

//3.验证qq号: 不能是0开头,都是数字,长度为5-15
boolean result03 = "111111".matches("[1-9][0-9]{4,14}");
System.out.println("result03 = " + result03);

}

正则表达式-分组括号( )

1
正则表达式-分组括号( )  (abc)
1
2
3
4
5
6
//分组括号
private static void method05() {
//校验abc可以出现任意次
boolean result = "abcabc".matches("(abc)*");
System.out.println("result = " + result);
}

String类中和正则表达式相关的方法

1
2
3
4
String类中和正则表达式相关的方法
boolean matches(String regex) 判断字符串是否匹配给定的正则表达式。
String[] split(String regex) 根据给定正则表达式的匹配拆分此字符串。
String replaceAll(String regex, String replacement)把满足正则表达式的字符串,替换为新的字符
1
2
3
4
5
6
7
8
9
private static void method06() {
//String[] split(String regex) 根据给定正则表达式的匹配拆分此字符串。
String s1 = "abc hahah hehe hdhshsh";
String[] arr1 = s1.split(" +");
System.out.println(Arrays.toString(arr1));
//String replaceAll(String regex, String replacement)把满足正则表达式的字符串,替换为新的字符
String s2 = s1.replaceAll(" +", "z");
System.out.println("s2 = " + s2);
}