计算机编程基础

人机交互方式

  1. 图形化界面(GUI),简单直观,使用者易于接受且容易上手。
  2. 命令行方式(CLI),需要有一个控制台,输入特定的指令,让计算机完成一些操作。

常用的一些命令行指令

  1. dir: 列出当前目录下的文件以及文件夹
  2. md: 创建目录(文件夹)
  3. rd: 删除目录(需要保证目录是空的)
  4. cd: 进入指定目录
  5. cd..:退回到上一级目录
  6. cd\: 退回到根目录
  7. del: 删除文件
  8. exit:退出dos命令行

del后面也可以跟文件夹名,作用是把该文件夹内所有文件都删除。

Java的两种核心机制

  1. Java虚拟机(JVM)

Ⅰ.JVM是一个虚拟的计算机,具有指令集并使用不同的存储区域。负责执行指令,管理数据、内存、寄存器。
Ⅱ.对于不同平台,有不同的虚拟机。
Ⅲ.只有某平台提供了对应的Java虚拟机,Java程序才能在此平台运行。
Ⅳ.Java虚拟机机制屏蔽了底层运行平台的差别,实现了“一次编译,到处运行”。

  1. 垃圾回收

Ⅰ.不再使用的内存空间应回收,在C/C++等语言中,由程序员负责回收无用内存。
Ⅱ.垃圾回收在Java程序运行过程中自动进行,程序员无法精确控制和干预。
Ⅲ.还是有可能出现内存泄露和内存溢出的问题。

JDK,JRE

  1. JDK(Java Decelopment Kit)Java开发工具包

JDK是提供给Java开发人员使用的,其中包含了Java的开发工具,也包括了JRE,所以安装了JDK就不用单独安装JRE了。

  1. JRE(Java Runtime Environment)Java运行环境

包括Java虚拟机和Java程序所需的核心类库等,如果想要运行一个开发好的Java程序,计算机只需要安装JRE即可

JDK与JRE的关系

了解Java

注释

  1. 单行注释
1
2
3
4
5
6
Class Hello{
public static void main(String args[]){
//我是单行注释
System.out.println("Hello, World!");
}
}
  1. 多行注释
1
2
3
4
5
6
7
8
9
Class Hello{
/*
我是多行注释,
main方法是程序的入口!
*/
public static void main(String args[]){
System.out.println("Hello, World!");
}
}
  1. 文档注释(Java特有)
1
2
3
4
5
6
7
8
9
10
/**
文档注释,可以被javadoc所解析,生成一套以网页文件形式体现的该程序的说明文档
@author yuhong
@version 1.0
*/
Class Hello{
public static void main(String args[]){
System.out.println("Hello, World!");
}
}

Java基本语法

关键字与保留字

  1. 用于定义数据类型的关键字
class interface enum byte
short int long float
double char boolean void
  1. 用于定义流程控制的关键字
if else switch case
default while do for
break continue return
  1. 用于定义访问权限修饰符的关键字
private protected public
  1. 用于定义类、函数、变量修饰符的关键字
abstract final static synchronized
  1. 用于定义类与类之间的关系
extends implements
  1. 用于定义建立实例及引用实例,判断实例的关键字
new this super instanceof
  1. 用于异常处理的关键字
try catch finally throw throws
  1. 用于包的关键字
package import
  1. 其他修饰符关键字
native strictfp transient volatile assert
  1. 用于定义数据类型值的字面值
true false null
  1. 保留字:现有Java版本尚未使用,但是以后的版本可能会作为关键字使用的,命名标识符时要避免使用这些保留字。
    goto、const

Java中级名称命名规范

  1. 包名:多单词组成时所有字母都小写:xxxyyyzzz

  2. 类名、接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz

  3. 变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母发泄:xxxYyyZzz

  4. 常量名:所有字母都大写。多单词组成时用下划线连接:XXX_YYY_ZZZ

Java的几种数据类型

数据类型

变量

  1. 整型:byte(1字节=8bit),short(2字节),int(4字节),long(8字节)
1
2
3
4
5
6
7
8
9
10
Class Hello{
public static void main(String args[]){
/*
定义long类型的数据时要在后面加上“L”或者“l”,
不加的话会默认转换为int类型的,但是超过了int的表示范围之后就必须加L
*/
long a = 151234L;
System.out.println("Hello, World!");
}
}
  1. 浮点型:float(4字节)、double(8字节)
1
2
3
4
5
6
7
8
9
10
Class Hello{
public static void main(String args[]){
/*
定义float类型的变量时要在后面加上“f”或者“F”
*/
double a = 15.1;
float f1 = 12.3f;
System.out.println("Hello, World!");
}
}
  1. 字符型:char(2字节)
1
2
3
4
5
6
7
8
9
10
11
12
13
Class Hello{
public static void main(String args[]){
/*
1.声明一个字符
2.转义字符
3.直接使用Unicode的值来表示
*/
char c1 = 'a';
char c2 = '\n';
char c3 = '\u0043';
System.out.println("Hello, World!");
}
}
  1. 布尔型:boolean(1字节)

自动类型提升和强制类型转换

  1. 自动类型提升

当容量小的数据类型的变量与容量大的数据类型的变量做运算时,结果自动提升为容量大的类型。
byte 、 short 、 char -> int -> long -> float -> double
特别的,当byte、char、short三种类型的变量做运算时(包括与自身相同的类型),结果为int型。
变量与整形、浮点型常量做运算时,整型常量默认为int型,浮点型常量默认为double型。

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
class VariableTest{
public static void main(String args[]){
byte b1 = 2;
int i1 = 129;
// 因为运算结果是int,所以用更小的byte去接收是会报错的
// byte b2 = b1 + i1;
int i2 = b1 + i1;
long l1 = b1 + i1;
System.out.println(i2); //131

// 那么使用浮点型变量是否可以接收整形呢
float f =b1 + i1;
System.out.println(f); //131.0

short s1 = 123;
double d1 = s1;
System.out.println(d1); //123.0

char c1 = 'a';
int i3 = 10;
int i4 = c1 + i3;
System.out.println(i4); //97

short s2 = 10;
// short s3 = c1 + s2; 报错
// char c2 = c1 + s2; 报错

byte b2 = 10;
// char c3 = c1 + b2; 报错
}
}
  1. 强制类型转换

即自动类型提升运算的逆运算
需要使用强转符:()
注意点:强制类型转换可能导致精度损失

1
2
3
4
5
6
7
8
class VariableTest{
public static void main(String args[]){
double d1 = 12.3;

int i1 = (int)d1;
System.out.println(i1); //12
}
}

String类型变量的使用

  1. String不是基本数据类型,属于引用数据类型

  2. 声明String类型变量时,使用一对双引号:””

  3. String可以和8种基本数据类型做运算,且运算只能是连接运算:+,运算的结果仍是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
public class StringTest{
public static void main(String args[]){
String s1 = "Hello World";
System.out.println(s1);
int number = 1001;
String numberStr = "学号:";
String info = numberStr + number; //+:连接运算
boolean b1 =true;
String info2 = info + b1;
System.out.println(info2);
System.out.println(info);

//练习1
char c = 'a';
int num = 10;
String str = "Hello";
System.out.println(c + num + str); //107Hello
System.out.println(c + str + num); //aHello10
System.out.println(c + (num + str)); //a10Hello
System.out.println((c + num) + str); //107Hello
System.out.println(str + num + c); //Hello10a

//练习2
//* *
System.out.println("* *");
System.out.println('*'+"\t"+'*');
}
}

运行结果:

运行结果

四种进制

  1. 二进制(binary):以0b或者0B开头
  2. 十进制(decimal)
  3. 八进制(octal):以数字0开头
  4. 十六进制(hex):以0x或者0X开头
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class System1 {

public static void main(String[] args) {
// TODO Auto-generated method stub
int num1 = 0b110;
int num2 = 110;
int num3 = 0127;
int num4 = 0x110A;

System.out.println("num1 = "+num1);
System.out.println("num2 = "+num2);
System.out.println("num3 = "+num3);
System.out.println("num4 = "+num4);

}

}

补充组原知识:原码、反码、补码

原码、反码、补码

运算符

算术运算符

运算符

取模运算的结果正负性与被模数保持一致

逻辑运算符

逻辑运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/*
* 逻辑运算符:
* & && | || ! ^
*
* 说明:
* 1.逻辑运算符操作的都是boolean型的变量
*
*
* */

package logic;

public class LogicTest {

public static void main(String[] args) {
// TODO Auto-generated method stub
// 区分逻辑& 与 &&
//相同点1:&与&&的运算结果相同
//相同点2:当符号昨天是true时,二者都会执行符号右边的运算
//不同点:当符号左边是false时,&会继续执行符号右边的运算。而&&不再执行
boolean b1 = true;
b1 = false;
int num1 = 10;
if(b1 & (num1++ > 0)) {
System.out.println("我现在在北京");
}
else {
System.out.println("我现在在南京");
}

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

boolean b2 = true;
b2 = false;
int num2 = 10;
if(b2 && (num2++ > 0)) { //短路与,因为b2已经是false了,不管&&后面是真是假,最后整体肯定为假,所以就不执行后面的语句了,所以num2++就没有执行,直接跳过了,即被短路了
System.out.println("我现在在北京");
}
else {
System.out.println("我现在在南京");
}

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


//区分|与||
//想同点1:|与||运算结果是下昂痛的
//相同点2:当符号左边是false时,二者都会执行符号右边的运算
//不同点:当符号左边是true时,|继续执行符号右边的运算,而||不再执行

boolean b3 =false;
b3 = true;
int num3 = 10;
if(b3 | (num3++ >0)) {
System.out.println("我现在在北京");
}
else {
System.out.println("我现在在南京");
}

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


boolean b4 =false;
b4 = true;
int num4 = 10;
if(b4 || (num4++ >0)) {
System.out.println("我现在在北京");
}
else {
System.out.println("我现在在南京");
}

System.out.println("num4 = "+ num4);
}


}

位运算符

位运算符

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.<<:在一定范围内,每向左移一位,相当于乘2,末尾补0
* >>:在一定范围内,每向右移一位,相当于除以2,开头是0就补0,开头是1就补1
* >>>:在一定范围内,每向右移一位,相当于除以2,开头补0
* */

package bit_operator;

public class Bit_operator {

public static void main(String[] args) {
// TODO Auto-generated method stub
int i =21;
//左移两位,就是乘2的2次方,在原本二进制后面加上了两个0
System.out.println("i << 2 :" + (i << 2));
//左移三位,就是乘2的3次方,在原本二进制后面加上了三个0
System.out.println("i << 3 :" + (i << 3));

//&是与运算,将每位上的数进行与操作:两者都为1,结果为1,其余都为0
//|是或运算,将每位上的数进行或操作:二者有一个为1,结果为1
//^是异或运算,将每位上的数进行异或操作:二者不一致的时候,结果为1,其余为0
int m = 12;
int n =5;
System.out.println("m & n :" + (m & n));
System.out.println("m | n :" + (m | n));
System.out.println("m ^ n :" + (m ^ n));
}

}

运算符的优先级

运算符的优先级

Scanner类的使用

具体的实现步骤:

1.导包:import java.util.Scanner;
2.Scanner的实例化
3.调用Scanner类的相关方法,来获取指定类型的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package scanner;

import java.util.Scanner;
public class Scanner1 {

public static void main(String[] args) {
// Scanner的实例化
Scanner scan = new Scanner(System.in);
//调用nextInt()方法,来获取键盘输入的int型变量
int num = scan.nextInt();
//获取其他变量的输入方法:
//String类型:next()
//Double类型:nextDouble()
//Boolean类型:nextBoolean()
//对于char类型的获取没有提供相关的方法,故可以使用字符串的方法来获取
System.out.println(num);
}

}

分支结构(if-else, switch-case)

注意点:switch中的表达式,只能是如下的六种数据类型之一:
byte, short, char, int, 枚举类型, String

循环结构(for, do while, while)

嵌套循环的应用:打印出100以内所有的质数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//100以内所有质数

package cycle1;
public class Cycle {

public static void main(String[] args) {
// TODO Auto-generated method stub
boolean isFlag = true; //用于记录i是否可以被除尽
for(int i=2;i<=100;i++) {
for(int j=2;j<i;j++) {
if(i%j==0) {
isFlag = false;
break;
}
}
if(isFlag==true) {
System.out.println(i);
}
isFlag = true; //重置isFlag的值,否则当isFlag为false时,上方的条件语句永远不会执行
}
}
}

对以上算法进行有优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args){
long start = System.currentTimeMillis(); //获取程序刚开始运行时的时间戳
boolean isFlag = true; //用于记录i是否可以被除尽
label:for(int i=2;i<=10000;i++) {
for(int j=2;j<=Math.sqrt(i);j++) { //优化二:开方,调用Math.sqrt方法,需加上等号,最大的一个质数是97
// 开方后是9点多,所以9就是它的一个倍数,不需要再试更大的数
// 根据时间戳的结果,在优化一的基础上又变为了17ms,又更进一步缩短了程序的运行时间
if(i%j==0) {
isFlag = false;
break label; //break默认是结束最近的一层循环,但也可以指明跳出指定的循环,比如跳出外层循环
//在要跳出的指定循环前加上label:然后在break后面加上label就可以跳出label指定的循环
}
}
if(isFlag==true) {
System.out.println(i);
}
isFlag = true; //重置isFlag的值,否则当isFlag为false时,上方的条件语句永远不会执行
}
long end = System.currentTimeMillis(); //获取程序运行结束时的时间戳
System.out.println(end-start); //两个时间戳相减即可得到程序运行的时间
}

小项目一-家庭收支管理系统

AccountController.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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package accountcon;
public class AccountController {
public static void main(String[] args) {
// TODO Auto-generated method stub
boolean isFlag = true;
//用于记录用户的收入和支出详情
String details = "";
//初始金额
int balance = 10000;
while(isFlag) {
System.out.println("----------家庭收支记账软件----------\n");
System.out.println(" 1.收支明细");
System.out.println(" 2.登记收入");
System.out.println(" 3.登记支出");
System.out.println(" 4.退 出\n");
System.out.println(" 请输入1-4来执行对应的功能");

//调用Utility文件中的方法,获取用户的选择
char selection = Utility.readMenuSelection();

switch(selection) {
//因为selection是char类型的,所以下面的1,2,3,4均需加上单引号
case '1' :
System.out.println("1.收支明细");
System.out.println("----------家庭收支记账软件----------\n");
System.out.println("收支\t账户金额\t收支金额\t说 明\n");
System.out.println(details);
System.out.println("-----------------------------------\n");
continue;
case '2' :
System.out.println("本次收入金额:");
int money = Utility.readNumber();
System.out.println("本次收入说明:");
String info = Utility.readString();

//处理余额balance
balance += money;

//处理details
details += "收入\t" + balance + "\t+" + money + "\t" + info + "\n";

System.out.println("--------------登记完成--------------\n");
continue;
case '3' :
System.out.println("本次支出金额:");
int moneyout = Utility.readNumber();
System.out.println("本次支出说明:");
String infoout = Utility.readString();

//处理balance
if(balance >= moneyout) {
balance -= moneyout;
}
else {
System.out.println("支付失败");
}

//处理deatils
details += "支出\t" + balance + "\t-" + moneyout + "\t" + infoout + "\n";

System.out.println("--------------登记完成--------------\\n");
continue;
case '4' :
System.out.println("确定要退出吗(Y/N)\n");
char isExit = Utility.readConfirmSelection();
if(isExit == 'Y') {
isFlag = false;
}
break;
}
}
}

}

Utility.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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package accountcon; 
import java.util.Scanner;
/**
Utility工具类:
将不同的功能封装为方法,就是可以直接通过调用方法使用它的功能,而无需考虑具体的功能实现细节。
*/
public class Utility {
private static Scanner scanner = new Scanner(System.in);
/**
用于界面菜单的选择。该方法读取键盘,如果用户键入’1’-’4’中的任意字符,则方法返回。返回值为用户键入字符。
*/
public static char readMenuSelection() {
char c;
for (; ; ) {
String str = readKeyBoard(1);
c = str.charAt(0);
if (c != '1' && c != '2' && c != '3' && c != '4') {
System.out.print("选择错误,请重新输入:");
} else break;
}
return c;
}
/**
用于收入和支出金额的输入。该方法从键盘读取一个不超过4位长度的整数,并将其作为方法的返回值。
*/
public static int readNumber() {
int n;
for (; ; ) {
String str = readKeyBoard(4);
try {
n = Integer.parseInt(str);
break;
} catch (NumberFormatException e) {
System.out.print("数字输入错误,请重新输入:");
}
}
return n;
}
/**
用于收入和支出说明的输入。该方法从键盘读取一个不超过8位长度的字符串,并将其作为方法的返回值。
*/
public static String readString() {
String str = readKeyBoard(8);
return str;
}

/**
用于确认选择的输入。该方法从键盘读取‘Y’或’N’,并将其作为方法的返回值。
*/
public static char readConfirmSelection() {
char c;
for (; ; ) {
String str = readKeyBoard(1).toUpperCase();
c = str.charAt(0);
if (c == 'Y' || c == 'N') {
break;
} else {
System.out.print("选择错误,请重新输入:");
}
}
return c;
}


private static String readKeyBoard(int limit) {
String line = "";

while (scanner.hasNext()) {
line = scanner.nextLine();
if (line.length() < 1 || line.length() > limit) {
System.out.print("输入长度(不大于" + limit + ")错误,请重新输入:");
continue;
}
break;
}

return line;
}
}

数组

数组属于引用数据类型,所以需要用new进行实例化,其元素可以是基本数据类型,也可以是引用数据类型。
创建数组对象会在内存中开辟一块连续的内存空间,而数组名引用的是这块连续空间的首地址。
数组的长度一旦确定,就不可以更改。

1
2
3
4
5
6
7
//1.动态初始化
int[] arr1 = new int[3];
//2.静态初始化
int[] arr2 = new int[]{1,2,3};
String arr3 = new String[3];
//3.在初始化时未赋值,每个类型都会有一个默认值,例如
//整形默认为0,char型默认为0,String默认为null,boolean默认为false,浮点型默认为0.0

二维数组

对于二维数组,可以简单理解为arr1作为另一个数组arr2的数组元素而存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//1.动态初始化
int[][] arr = new int arr[11][3];
int[] arr1[] = new int arr1[11][3];
int arr2[][] = new int arr2[11][3];
//2.静态初始化
int [][] arr3 = new int arr3[][]{{1,2,3},{2,3,6},{7,8,9}};
//3.遍历二维数组
for(int i=0;i<arr.lenght;i++){
for(int j=0;j<arr[i].length;j++){
System.out.println(arr[i][j]+" ");
}
}
//4.二维数组的初始化默认值
System.out.println(arr);//是一个地址值
//如果是动态初始化,且内部元素未定义长度,则会返回null
System.out.println(arr[1]);//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 YangHui {
public static void main(String[] args) {
//1.声明并初始化二维数组
int[][] arr = new int[10][];
for(int i=1;i<=arr.length;i++) {
arr[i-1]=new int[i];
}

//2.二维数组赋值
for(int i=0;i<arr.length;i++) {
arr[i][0]=1;
arr[i][i]=1;
for(int j=0;j<=i;j++) {
if(i>1&&j>0&&j<i) {
arr[i][j]=arr[i-1][j-1]+arr[i-1][j];
}
System.out.print(arr[i][j]+" ");
}
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
37
38
package com.geo.exer2;

/**
* @author yuxiaohong
* @package com.geo.exer2
* @date 2023/7/14 16:20
* @description 二分查找
*/
public class BinarySearchTest {
public static void main(String[] args) {
int[] arr = new int[]{2, 4, 5, 8, 12, 15, 19, 26, 37, 49, 51, 66, 89, 100};
// 查找目标数
int target = 5;
// 默认的首索引
int head = 0;
// 默认的尾索引
int end = arr.length - 1;
// 是否找到了目标数
boolean flag = false;
while (head <= end) {
// 记录中间数
int middle = (head + end) / 2;
if (arr[middle] == target) {
flag = true;
System.out.println("找到了" + target + ",在第" + (middle + 1) + "位上");
break;
} else if (arr[middle] < target) {
head = middle + 1;
} else if (arr[middle] > target) {
end = middle - 1;
}
}
if (!flag) {
System.out.println("该数组中不存在" + target);
}
}
}

数组的一些工具类

ArraysUtils.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
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
package com.geo.ArrayUtils;

import java.util.Arrays;

/**
* @author yuxiaohong
* @package com.geo.ArrayUtils
* @date 2023/7/17 22:21
* @description 数组的一些常用工具类
*/
public class ArrayUtils {
public static void main(String[] args) {
// 1.boolean equals(int[] a,int[] b):比较两个数组元素是否依次相等
int[] arr1 = new int[]{1, 2, 3, 4, 5};
int[] arr2 = new int[]{1, 2, 3, 4, 5};
System.out.println(arr1 == arr2); // false,因为数组中存的是地址值,所以两个数组就算元素都一致,但是使用双等号去依旧为false。

boolean isEquals = Arrays.equals(arr1, arr2);
System.out.println(isEquals);

// 2.String toString(int[] a):输出数组元素信息
System.out.println(arr1); //[I@16b98e56,直接打印数组会将数组存放的地址值打印出来
System.out.println(Arrays.toString(arr1)); //[1, 2, 3, 4, 5],会直接打印出数组元素来,不需要我们进行遍历

// 3.void fill(int[] a,int val):将指定值填充到数组当中
Arrays.fill(arr1, 10);
System.out.println(Arrays.toString(arr1)); //[10, 10, 10, 10, 10]将数组中全部的元素都替换为10

// 4.void sort(int[] a):使用快速排序算法实现的排序
int[] arr3 = new int[]{1, 6, 5, 3, 9, 11, 54, 43, 99};
Arrays.sort(arr3);
System.out.println(Arrays.toString(arr3)); //[1, 3, 5, 6, 9, 11, 43, 54, 99],可见成功进行了排序操作

// 5.int binarySearch(int[] a,int k):二分查找,使用前提,当前的数组必须是有序的
int index = Arrays.binarySearch(arr3, 5);
System.out.println(index); // 2,正确找到并返回指定数字的下标,如果返回的是一个负数,则说明没有找到指定的数字


/**数组的使用中,常见的异常小结:
*
* 数组角标越界的异常:ArrayIndexOutOfBoundsException
* 空指针的异常:NullPointerException
*
* */


// 空指针异常:
// 情况1:
// int[] arr4 = new int[10];
// arr4 = null;
// System.out.println(arr4[0]);

// 情况2:
int arr5[][] = new int[3][];
System.out.println(arr5[0][1]);
}

}

面向对象编程

学习面向对象内容的三条主线

Java类及类的成员:(重点)属性、方法、构造器;(熟悉)代码块、内部类
面向对象的特征:封装、继承、多态、(抽象)
其他关键字的使用:this、super、package、import、static、final、interface、abstract等

面向过程的程序设计思想

关注的焦点是过程:过程就是操作数据的步骤,如果某个过程的实现代码重复出现,那么就可以把这个过程抽取为一个函数,这样就可以大大简化代码,便于维护。
典型的语言:C语言
代码结构:以函数为组织单位
是一种“执行者思维”,适合解决简单问题。扩展能力差、后期维护难度较大。

面向对象的程序设计思想

关注的焦点是类:在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,用类来表示。
典型的语言:Java、C#、C++、Python、Ruby和PHP等
代码结构:以“类”为组织单位,每种事物都具备自己的属性和方法。
是一种“设计者思维”,适合解决复杂问题。代码扩展性强、可维护性高。

类和对象的概述

类(Class)和对象(Object)是面向对象的核心概念。

1.什么是类:具有相同特征额事物的抽象描述,是抽象的、概念上的定义。
2.什么是对象:实际存在的该类事物 每个个体,是具体的,因而也被成为实例(instance)。

类的声明和使用

设计类,实际上就是设计类的成员。
Class Person{

}

类的内部成员一、二

成员之一:属性、成员变量、field(字段、域)
成员之二:(成员)方法、函数、method

类的实例化剖析,内存解析

1.栈(Stack):方法内定义的变量,存储在栈中。
2.堆(Heap):new出来的结构(数组的实体、对象的实体)。包括对象中的属性。
3.方法区(method area):存放类的模板。

局部变量和成员变量

不同点:
1.声明的位置不同
属性:声明在类内,方法外的变量
局部变量:声明方法、构造器内部的变量
2.在内存中分配的位置不同
属性:随着对象的创建,存储在堆空间当中
局部变量:存储在栈空间中
3.是否可以使用权限修饰符进行修饰
属性:Public、Private、缺省、Protected
局部变量:不能使用任何的权限修饰符进行修饰
4.是否有默认初始化值
属性:都有默认初始化值
局部变量:都没有默认初始化值

对象数组的使用

StudentTest.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
28
29
package com.Learning04.ObjArr;

/**
* @author yuxiaohong
* @package com.Learning04.ObjArr
* @date 2023/7/18 18:43
* @description 对象数组的使用
*/
public class StudentTest {
public static void main(String[] args) {

Student[] student = new Student[20];
for (int i = 0; i < student.length; i++) {
student[i] = new Student();
student[i].number = i + 1;
student[i].state = (int) (Math.random() * 6 + 1);
student[i].score = (int) (Math.random() * 101);
}

// 需求1,打印出三年级(state=3)的学生的信息
for (int i = 0; i < student.length; i++) {
if (student[i].state == 3) {
Student stu = student[i];
System.out.println(stu.number);
}
}
}
}

Student.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.Learning04.ObjArr;

/**
* @author yuxiaohong
* @package com.Learning04.ObjArr
* @date 2023/7/18 18:43
* @description 学生类
*/
public class Student {
int number;
int state;
int score;

}

方法的重载

概念及特点

  1. 在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可。
  2. 参数列表不同,意味着参数个数或参数类型的不同。
  3. 满足这样特征的多个方法,彼此之间构成方法的重载。
  4. 方法的重载与形参的名、权限修饰符、返回值类型都没有关系。
  5. 在同一个类中不允许定义两个相同的方法

overload.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
28
29
30
31
32
33
34
35
36
package com.Learning05.method_more;

/**
* @author yuxiaohong
* @package com.Learning05.method_more
* @date 2023/7/18 22:07
* @description 重载
*/
public class overload {
public static void main(String[] args) {
overload over = new overload();
// 根据参数类型和个数去匹配对应的方法
over.add(1, "33");
}

public void add(int i, int j) {

}

public void add(int i, int j, int k) {

}

public void add(String s1, String s2) {

}

public void add(int i, String s) {

}

public void add(String s, int i) {

}
}

可变个数形参的方法

  1. 使用场景:可变参数使用类型,在调用方法时,可能会出现形参的类型是确定的,但是参数的个数不确定。此时就可以使用可变个数形参。
  2. 格式(参数类型 … 参数名)
  3. 可变个数形参的方法在调用时,针对于可变的形参赋的实参个数可以为0个、1个或多个。
    可变个数形参的方法与同一个类中,同名的方法之间可以构成重载,但是优先级最低。
    特例:可变个数形参的方法与同一个类中,同名的参数为数组的方法不构成重载,会报错。
  4. 可变个数形参必须声明在参数列表的最后。

递归

递归实际上就是在函数体内部再次调用自己,效率不高,能使用循环迭代的尽量使用循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.Learning05.recursion.exer1;

/**
* @author yuxiaohong
* @package com.Learning05.recursion.exer1
* @date 2023/7/20 17:15
* @description
*/
public class RecursionExer1 {
// 练习1:
// 已知一个数列,f(20)=1,f(21)=4,f(n+2)=2*f(n+1)+f(n)
// 已知n是大于0的整数,求f(10)的值

public static void main(String[] args) {
RecursionExer1 res = new RecursionExer1();
System.out.println(res.f(10));
}

public int f(int n) {
if (n == 20) {
return 1;
} else if (n == 21) {
return 4;
} else {
return f(n + 2) - 2 * f(n + 1);
}
}
}

面向对象的特征之一:封装性

为什么需要封装性?

高内聚:类的内部数据操作细节自己完成,不允许外部干涉。
低耦合:仅暴露少量的方法给外部使用,尽量方便外部调用。

通俗的说就是把该暴露的暴露出来,该隐藏的隐藏起来。

如何实现数据封装?

使用权限修饰符:private、缺省、protected、public

四种权限修饰符的权限如下所示:

其中,类只能使用public、缺省修饰
类的内部成员可以使用四种修饰符修饰

封装性的体现:

  1. 私有化(private)类的属性,提供公共(public)的get和set方法,对此属性进行获取或修改。
  2. 将类中不需要对外暴露的方法,设置为private。
  3. 在单例模式中构造器声明为private,避免在类的外部创建实例。

构造器

1
2
// 当我们在new一个类的时候,其实就用到了构造器
person p1 = new Person() //<=这个Person()其实就是一个构造器

构造器的作用

  1. 搭配new关键字,创建类的对象
  2. 在创建对象的同时,可以给对象的相关属性进行赋值

构造器的使用说明

  1. 构造器的声明格式:权限修饰符 类名(形参列表) {}
  2. 创建类以后,在没有显式提供任何构造器的情况下,系统会默认提供一个空参的构造器,且构造器的权限与类的权限相同
  3. 一旦类中显示声明了构造器,则系统不再提供默认的空参的构造器
  4. 一个类中可以声明多个构造器,彼此之间构成重载

Person.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
28
29
30
31
32
package com.Learning02.memory;

/**
* @author yuxiaohong
* @package com.Learning02.memory
* @date 2023/7/18 14:27
* @description Person类
*/
public class Person {

String name;
int age;
char gender;

public void eat() {
System.out.println("吃饭");
}

public void sleep() {
System.out.println("睡觉");
}

public void interest(String hobby) {
System.out.println("爱好:" + hobby);
}

// 构造器,带有参数可以在被实例化之后自动进行赋值等操作
public Person(String s) {
name = s;
}
}

PersonTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.Learning02.memory;

/**
* @author yuxiaohong
* @package com.Learning02.memory
* @date 2023/7/18 14:27
* @description Person测试类
*/
public class PersonTest {
public static void main(String[] args) {

// 创建对象\类的实例化
Person p1 = new Person("小杰");
p1.age = 23;
p1.gender = '男';

System.out.println("姓名:" + p1.name + "年龄:" + p1.age + "性别:" + p1.gender);
p1.eat();
p1.sleep();
p1.interest("打游戏");
}
}

实例变量赋值过程

在类的属性中,可以怎样实现赋值?

  1. 默认赋值
  2. 显式赋值
  3. 构造器赋值
  4. 通过“对象.方法”赋值
  5. 通过“对象.属性”赋值

执行的先后顺序?
1->2->3->4/5

以上操作在执行次数上的限制
只能执行一次:1、2、3
可以多次执行:4、5

JavaBean

JavaBean的理解:是一种由Java语言编写的可复用组件

所谓JaveBean,是指符合下列标准的java类:

  1. 类是公共的
  2. 有一个无参的公共构造器
  3. 有属性,且有对应的get和set方法

JavaBeanTest.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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.Learning06.JavaBean;

import java.sql.Date;

/**
* @author yuxiaohong
* @package com.Learning06.JavaBean
* @date 2023/7/25 18:13
* @description javabean测试
*/
public class customer {
// 无参的构造器
public customer() {

}

private int id;
private String name;
private Date birth;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

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

public Date getBirth() {
return birth;
}

public void setBirth(Date birth) {
this.birth = birth;
}
}

UML类图

UML(Unified Modeling Language,统一建模语言),用来描述软件模型和架构的图形化语言

this关键字的使用

  1. 引出问题:当我们在声明一个属性对应的set方法时,通过形参给对应的属性赋值,如果形参名和属性名同名了,那么该如何在方法内部区分这两个变量呢?

解决方案:使用this。具体来讲,使用this修饰的是属性,没有用this修饰的是形参。

  1. this可以调用的结构:成员变量、方法、构造器

  2. 对this的一个理解:当对象(在方法中调用时),或当前正在创建的对象(在构造器中调用时)

  3. this调用构造器

格式:this(形参列表)

我们可以在类的构造器中,调用当前类中指定的其他构造器

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
package com.Learning06.JavaBean;

import java.sql.Date;

/**
* @author yuxiaohong
* @package com.Learning06.JavaBean
* @date 2023/7/25 18:13
* @description javabean测试
*/
public class customer {
// 无参的构造器
public customer() {

}

public customer(String name) {
// 这行代码就代表调用了上方的无参构造器
this();
}

public customer(String name, int id) {
// 这行代码代表调用了上方的构造器
this(name);
this.id = id;
}

private int id;
private String name;
}

电商项目练习(增删改查)

  1. 首先创建好一个客户类,用于构造客户,即JavaBean。

在其中,定义好私有属性之后,使用alt+insert创建get和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
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
package com.Project01.mangement;

/**
* @author yuxiaohong
* @package com.Project01.mangement
* @date 2023/7/27 16:26
* @description 客户类
*/
public class customer {
private String name;
private char gender;
private int age;
private String phone;
private String email;

public customer() {
}

public customer(String name, char gender, int age, String phone, String email) {
this.name = name;
this.gender = gender;
this.age = age;
this.phone = phone;
this.email = email;
}

public String getName() {
return name;
}

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

public char getGender() {
return gender;
}

public void setGender(char gender) {
this.gender = gender;
}

public int getAge() {
return age;
}

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

public String getPhone() {
return phone;
}

public void setPhone(String phone) {
this.phone = phone;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}
}

  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
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
package com.Project01.mangement;

/**
* @author yuxiaohong
* @package com.Project01.mangement
* @date 2023/7/27 16:31
* @description 是客户类的管理模块,内部使用数组管理一组Customer对象
*/
public class CustomerList {
private Customer[] customers;// 用于保存客户对象的数组
private int total = 0;// 用于记录用户的数量

// 构造器,用来初始化customers数组
public CustomerList(int totalCustomer) { // 参数就是,客户的数量,作为数组的长度
customers = new Customer[totalCustomer];
}

// 用途:将参数customer添加组中最后一个客户对象记录之后,
public boolean addCustomer(Customer customer) {
// 如果客户的数量比数组的长度小,说明可以放入数组
if (total < customers.length) {
customers[total] = customer;
total++;
return true;
}
// 超过数组的长度,则无法再添加入数组当中,返回false
return false;
}

// 用参数customer替换数组中index指定的对象
public boolean replaceCustomer(int index, Customer customer) {
if (index >= 0 && index < total) {
customers[index] = customer;
return true;
}
return false;
}

// 从数组中删除参数index指定的客户对象的记录
public boolean deleteCustomer(int index) {
if (index >= 0 && index < total) {
for (int i = index; i < total - 1; i++) {
customers[i] = customers[i + 1];
}
customers[total - 1] = null; // 因为移动完之后最后一位还指向原来最后一位客户,所以需要将其指向null
total--; // 客户数量减一
return true;
}
return false;
}

// 用途:返回数组中所有记录的客户对象,数组的长度与对象的个数相等
public Customer[] getAllCustomers() {
Customer[] customers1 = new Customer[total];
for (int i = 0; i < total; i++) {
customers1[i] = customers[i];
}
return customers1;
}

// 用途:返回参数index指定索引位置的客户对象记录
public Customer getCustomer(int index) {
if (index >= 0 && index < total) {
return customers[index];
}
return null;
}

// 获取客户列表中,客户的数量
public int getTotal() {
return total;
}
}

  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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package com.Project01.mangement;

/**
* @author yuxiaohong
* @package com.Project01.mangement
* @date 2023/7/27 17:41
* @description 负责菜单的显示和处理用户操作
*/
public class CustomerView {
CustomerList customerList = new CustomerList(10);

// 进入主界面的方法
public void enterMainMenu() {
boolean isFlag = true;
while (isFlag) {
// 打印界面
System.out.println("\n-----------------客户信息管理软件-----------------\n");
System.out.println(" 1 添 加 客 户");
System.out.println(" 2 修 改 客 户");
System.out.println(" 3 删 除 客 户");
System.out.println(" 4 客 户 列 表");
System.out.println(" 5 退 出\n");
System.out.print(" 请选择(1-5):");

char key = CMUtility.readMenuSelection();
switch (key) {
case '1':
addNewCustomer();
break;
case '2':
modifyCustomer();
break;
case '3':
deleteCustomer();
break;
case '4':
listAllCustomers();
break;
case '5':
// 当用户输入数字5的时候退出循环
System.out.println("确认是否退出(Y/N):");
char isExit = CMUtility.readConfirmSelection();
if (isExit == 'Y') {
isFlag = false;
}

break;
}

}

}

public static void main(String[] args) {
CustomerView customerView = new CustomerView();
customerView.enterMainMenu();
}


/**
* 添加客户的操作
*
* @Description
* @author shkstart
* @date 2019年1月19日上午11:35:35
*/
private void addNewCustomer() {
// System.out.println("添加客户的操作");
System.out.println("---------------------添加客户---------------------");
System.out.print("姓名:");
String name = CMUtility.readString(10);
System.out.print("性别:");
char gender = CMUtility.readChar();
System.out.print("年龄:");
int age = CMUtility.readInt();
System.out.print("电话:");
String phone = CMUtility.readString(13);
System.out.print("邮箱:");
String email = CMUtility.readString(30);

// 将上述数据封装到对象中
Customer customer = new Customer(name, gender, age, phone, email);

boolean isSuccess = customerList.addCustomer(customer);
if (isSuccess) {
System.out.println("---------------------添加完成---------------------");
} else {
System.out.println("-------------------客户目录已满,添加失败---------------");
}
}

/**
* 修改客户的操作
*
* @Description
* @author shkstart
* @date 2019年1月19日上午11:35:35
*/
private void modifyCustomer() {
// System.out.println("修改客户的操作");

System.out.println("---------------------修改客户---------------------");
Customer cust;
int number;
for (; ; ) {
System.out.print("请选择待修改客户编号(-1退出):");
number = CMUtility.readInt();

if (number == -1) {
return;
}

cust = customerList.getCustomer(number - 1);
if (cust == null) {
System.out.println("无法找到指定客户!");
} else {// 找到了相应编号的客户
break;

}

}
// 修改客户信息
System.out.print("姓名(" + cust.getName() + "):");
String name = CMUtility.readString(10, cust.getName());
System.out.print("性别(" + cust.getGender() + "):");
char gender = CMUtility.readChar(cust.getGender());
System.out.print("年龄(" + cust.getAge() + "):");
int age = CMUtility.readInt(cust.getAge());
System.out.print("电话(" + cust.getPhone() + "):");
String phone = CMUtility.readString(13, cust.getPhone());
System.out.print("邮箱(" + cust.getEmail() + "):");
String email = CMUtility.readString(30, cust.getEmail());

Customer newCust = new Customer(name, gender, age, phone, email);

boolean isRepalaced = customerList.replaceCustomer(number - 1, newCust);
if (isRepalaced) {
System.out.println("---------------------修改完成---------------------");
} else {
System.out.println("---------------------修改失败---------------------");
}
}

/**
* 删除客户的操作
*
* @Description
* @author shkstart
* @date 2019年1月19日上午11:35:35
*/
private void deleteCustomer() {
// System.out.println("删除客户的操作");
System.out.println("---------------------删除客户---------------------");
int number;
for (; ; ) {
System.out.print("请选择待删除客户编号(-1退出):");
number = CMUtility.readInt();

if (number == -1) {
return;
}

Customer customer = customerList.getCustomer(number - 1);
if (customer == null) {
System.out.println("无法找到指定客户!");
} else {
break;
}
}

// 找到了指定的客户
System.out.print("确认是否删除(Y/N):");
char isDelete = CMUtility.readConfirmSelection();
if (isDelete == 'Y') {
boolean deleteSuccess = customerList.deleteCustomer(number - 1);
if (deleteSuccess) {
System.out.println("---------------------删除完成---------------------");
} else {
System.out.println("---------------------删除失败---------------------");
}
} else {
return;
}
}

/**
* 显示客户列表的操作
*
* @Description
* @author shkstart
* @date 2019年1月19日上午11:35:35
*/
private void listAllCustomers() {
// System.out.println("显示客户列表的操作");
System.out.println("---------------------------客户列表---------------------------\n");

int total = customerList.getTotal();
if (total == 0) {
System.out.println("没有客户记录!");

} else {
System.out.println("编号\t姓名\t性别\t年龄\t电话\t\t邮箱");
Customer[] custs = customerList.getAllCustomers();
for (int i = 0; i < custs.length; i++) {
Customer cust = custs[i];
System.out.println((i + 1) + "\t" + cust.getName() +
"\t" + cust.getGender() + "\t" + cust.getAge() +
"\t" + cust.getPhone() + "\t" + cust.getEmail());
}
}


System.out.println("-------------------------客户列表完成-------------------------");
}
}

  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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package com.Project01.mangement;

/**
* @author yuxiaohong
* @package com.Project01.mangement
* @date 2023/7/27 16:33
* @description 用于输入处理的工具类
*/

import java.util.Scanner;

/**
* CMUtility工具类:
* 将不同的功能封装为方法,就是可以直接通过调用方法使用它的功能,而无需考虑具体的功能实现细节。
*/
public class CMUtility {
private static Scanner scanner = new Scanner(System.in);

/**
* 用于界面菜单的选择。该方法读取键盘,如果用户键入’1’-’5’中的任意字符,则方法返回。返回值为用户键入字符。
*/
public static char readMenuSelection() {
char c;
for (; ; ) {
String str = readKeyBoard(1, false);
c = str.charAt(0);
if (c != '1' && c != '2' &&
c != '3' && c != '4' && c != '5') {
System.out.print("选择错误,请重新输入:");
} else break;
}
return c;
}

/**
* 从键盘读取一个字符,并将其作为方法的返回值。
*/
public static char readChar() {
String str = readKeyBoard(1, false);
return str.charAt(0);
}

/**
* 从键盘读取一个字符,并将其作为方法的返回值。
* 如果用户不输入字符而直接回车,方法将以defaultValue 作为返回值。
*/
public static char readChar(char defaultValue) {
String str = readKeyBoard(1, true);
return (str.length() == 0) ? defaultValue : str.charAt(0);
}

/**
* 从键盘读取一个长度不超过2位的整数,并将其作为方法的返回值。
*/
public static int readInt() {
int n;
for (; ; ) {
String str = readKeyBoard(2, false);
try {
n = Integer.parseInt(str);
break;
} catch (NumberFormatException e) {
System.out.print("数字输入错误,请重新输入:");
}
}
return n;
}

/**
* 从键盘读取一个长度不超过2位的整数,并将其作为方法的返回值。
* 如果用户不输入字符而直接回车,方法将以defaultValue 作为返回值。
*/
public static int readInt(int defaultValue) {
int n;
for (; ; ) {
String str = readKeyBoard(2, true);
if (str.equals("")) {
return defaultValue;
}

try {
n = Integer.parseInt(str);
break;
} catch (NumberFormatException e) {
System.out.print("数字输入错误,请重新输入:");
}
}
return n;
}

/**
* 从键盘读取一个长度不超过limit的字符串,并将其作为方法的返回值。
*/
public static String readString(int limit) {
return readKeyBoard(limit, false);
}

/**
* 从键盘读取一个长度不超过limit的字符串,并将其作为方法的返回值。
* 如果用户不输入字符而直接回车,方法将以defaultValue 作为返回值。
*/
public static String readString(int limit, String defaultValue) {
String str = readKeyBoard(limit, true);
return str.equals("") ? defaultValue : str;
}

/**
* 用于确认选择的输入。该方法从键盘读取‘Y’或’N’,并将其作为方法的返回值。
*/
public static char readConfirmSelection() {
char c;
for (; ; ) {
String str = readKeyBoard(1, false).toUpperCase();
c = str.charAt(0);
if (c == 'Y' || c == 'N') {
break;
} else {
System.out.print("选择错误,请重新输入:");
}
}
return c;
}

private static String readKeyBoard(int limit, boolean blankReturn) {
String line = "";

while (scanner.hasNextLine()) {
line = scanner.nextLine();
if (line.length() == 0) {
if (blankReturn) return line;
else continue;
}

if (line.length() < 1 || line.length() > limit) {
System.out.print("输入长度(不大于" + limit + ")错误,请重新输入:");
continue;
}
break;
}

return line;
}
}

面向对象的特征之二:继承性

如上图所示,Student类继承了父类Person的所有属性和方法,并增加了一个属性school。Person中的属性和方法,Student都可以使用。

继承的优点

  1. 继承的出现减少了代码冗余,提高了代码的复用性。
  2. 继承的出现,更有利用功能的扩展。
  3. 继承的出现让类与类之间产生了is-a的关系,为多态的使用提供了前提。
    注意:不要仅仅为了获取其他类中的功能而去继承!

继承的语法

class A{

}

class B extends A{

}

继承中的基本概念

类A:父类、superClass、超类、基类
类B:子类、subClass、派生类

有了继承之后

  1. 子类可以获取到父类所有的属性和方法。
  2. 但是,由于封装性的影响,可能子类不能直接调用父类中声明的属性或者方法(私有)。
  3. 同时,子类还可以扩展自己特有的属性和方法。
  4. 默认的父类:Java中声明的类,如果没有显式的声明其父类时,则默认继承于java.lang.Object
  5. 一个父类可以有多个子类。
  6. 一个子类只能有一个父类。
  7. Java支持多层继承机制,即一个类可以同时是父类与子类。

重写

为什么需要方法的重写?

子类在继承父类之后,就获取到了父类中声明的所有的方法。但是,父类中的方法可能不太适合子类,换句话说,子类需要对继承过来的方法进行覆盖、重写的操作。

具体规则

  1. 父类被重写的方法与子类被重写的方法的方法名和形参列表必须相同。
  2. 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符。
  3. 子类不能重写父类中声明为private权限修饰的方法。
  4. 当返回值是void和基本数据类型,子类的返回值类型必须和父类相同。
  5. 当返回值是引用数据类型,子类要与父类保持一致,或者是被重写方法的返回值类型的子类。

super关键字的使用

为什么要使用super

eg:子类继承父类之后,对父类的方法进行了重写,那么在子类中,是否还可以对父类被重写额方法进行调用?
eg:子类继承父类之后,发现子类和父类中定义了同名的属性,是否可以在子类中区分?

解决办法:使用super关键字即可,理解为父类的。

super可以调用的结构:属性、方法、构造器

使用规范

  1. 子类继承父类时,不会继承父类的构造器,只能通过super(“形参列表”)的方式调用父类指定的构造器。
  2. 规定super必须声明在构造器的首行,同时,this和super只能二选一。
  3. 如果在子类的构造器中首行既没有调用this()也没有调用super(),则此构造器默认调用super(),即调用父类的空参构造器。

面向对象的特征之三:多态性

如何理解多态性?

理解为一个事物的多种形态

Java中多态性的体现

父类的引用指向子类的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.Learning09.polymorphism;

/**
* @author yuxiaohong
* @package com.Learning09.polymorphism
* @date 2023/7/28 17:37
* @description 多态性测试
*/
public class PersonTest {
public static void main(String[] args) {
// 多态性之前的使用场景
Person person = new Person();
Man man = new Man();

// 多态性:
Person person1 = new Man();
}
}

多态性的使用前提

  1. 要有类的继承关系
  2. 要有方法的重写

多态的适用性

适用于方法,不适用属性

多态的好处与弊端

  1. 好处
    极大地减少了代码的冗余,不需要定义多个重载的方法。

  2. 弊端
    在多态的场景下,创建了子类的对象,也加载了子类特有的属性和方法。但是由于声明了父类的引用,导致我们没有办法直接调用子类特有的属性和方法。

apply.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
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
package com.Learning09.polymorphism.apply;

/**
* @author yuxiaohong
* @package com.Learning09.polymorphism.apply
* @date 2023/7/31 14:40
* @description 多态的实际应用
*/
public class AnimalTest {

public static void main(String[] args) {
AnimalTest animalTest = new AnimalTest();
animalTest.adopt(new Dog()); // Animal animal = new Dog()
animalTest.adopt(new Cat()); // Animal animal = new Cat()
}

// 有了多态性,我们就不必单独为每一个类声明对应的方法,使代码更加简洁
public void adopt(Animal animal) {
animal.eat();
animal.jump();
}
}

class Animal {
public void eat() {
System.out.println("动物吃饭");
}

public void jump() {
System.out.println("动物跳跃");
}
}

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

public void jump() {
System.out.println("狗跳");
}

public void watchDoor() {
System.out.println("狗能看家");
}
}

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

public void jump() {
System.out.println("猫跳");
}

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

子类的强制转型

强制转型的目的:可以访问到子类特有的属性或者方法。

如下的代码片段中使用instanceof进行了判断

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
package com.Learning09.polymorphism;

/**
* @author yuxiaohong
* @package com.Learning09.polymorphism
* @date 2023/7/31 16:16
* @description
*/
public class PersonTest1 {
public static void main(String[] args) {
Person person = new Man();
// 动态无法调用子类特有的方法
// person.earnMoney();

// 在强制转型之前先使用instanceof进行判断,避免出现类型转换异常
if (person instanceof Man) {
// 向下转型,使用强转符
Man man = (Man) person;
man.earnMoney();
System.out.println(man.isSmoke);
}

}
}

Object类

如何理解跟父类

  1. 类java.lang.Object是类层次结构的根类,即所有它的类的父类。每个类都是用Object作为超类。
  2. 任何一个Java类(除Object类)都直接或间接的继承与Object类
  3. Object类称为java类的根父类
  4. Object类中声明的结构(属性、方法等)具有通用性
    Object类中没有声明属性
    Object类提供了一个空参的构造器

常用方法

重点方法:equals() \ toString()
了解方法:clone() \ finalize()

equals()方法的使用

  1. 适用情况:任何引用数据类型都可以使用
  2. 子类使用说明:
    如果自定义的类没有重写Object类中equals()方法的情况下,调用的就是Object类中的equals(),比较两个对象的引用地址是否相同。(或比较两个对象是否指向了堆空间中的同一个对象实体)

对于像String、File、Date这些包装类等,它们都重写了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
package com.Learging10.object.equals;

import java.util.Objects;

/**
* @author yuxiaohong
* @package com.Learging10.object.equals
* @date 2023/8/2 10:58
* @description
*/
public class UserTest {
public static void main(String[] args) {
User user = new User("小红", 23);
User user1 = new User("小红", 23);
System.out.println(user.equals(user1)); // false,判断user和user1的地址值是否相同
}
}

class User {
String name;
int age;

public User() {
}

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

重写equals()

@Override
public boolean equals(Object obj) {
// 如果地址一样,内容也肯定一样,直接返回true
if (this == obj) {
return true;
}
if (obj instanceof User) {
User user = (User) obj;
if (user.age == this.age && user.name.equals(this.name)) {
return true;
}
}
return false;
}
}

但是在实际开发当中,可以使用效率更高的方法,使用IDEA自动生成equals()方法

  1. 按下alt+ins键,然后点击equals()和hashcode()

  1. 下一步

  1. 下一步

经过IDEA自动生成的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
package com.Learging10.object.equals;

import java.util.Objects;

/**
* @author yuxiaohong
* @package com.Learging10.object.equals
* @date 2023/8/2 10:58
* @description
*/
public class UserTest {
public static void main(String[] args) {
User user = new User("小红", 23);
User user1 = new User("小红", 23);
System.out.println(user.equals(user1)); // false,判断user和user1的地址值是否相同
}
}

class User {
String name;
int age;

public User() {
}

public User(String name, int age) {
this.name = name;
this.age = age;
}
// // IDEA自动生成
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age && Objects.equals(name, user.name);
}
}

equals和==的区别

  1. ==:运算符
    适用范围:基本数据类型、引用数据类型
    (1)基本数据类型:判断数据值是否相等
    (2)引用数据类型:判断两个对象的地址值是否相等

  2. equals()方法
    适用范围:引用数据类型
    不重写equals()方法的话默认比较两个对象的地址值是否相等
    一般我们在自定义的类中会将equals()重写为比较数值是否相同的方法

toString()方法的使用

1.Object类中toString()的定义:

1
2
3
4
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
//com.Learging10.object.tostring.User@7ef20235
}

根据源码可以分析出:返回的是一个类名+地址

2.开发中的使用场景:

平时我们在使用System.out.println()这个方法的时候就调用了toString()方法

3.子类使用说明
自定义的类:在没有重写Object类下的toString()的情况下,默认返回的是当前对象的地址值
String、File、Date等包装类:都重写了toString()方法,在调用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
package com.Learging10.object.tostring;

/**
* @author yuxiaohong
* @package com.Learging10.object.tostring
* @date 2023/8/2 13:53
* @description
*/
public class toStringTest {
public static void main(String[] args) {
User user = new User("小红", 23);
System.out.println(user.toString()); //com.Learging10.object.tostring.User@7ef20235
String s1 = new String("666");
System.out.println(s1.toString()); //666
}
}

class User {
String name;
int age;

public User() {
}

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

4.开发中使用说明:

习惯上:开发中对于自定义方法的类在调用toString()时,也希望显示其对象的实体内容,这时候就需要重写toString()方法了。

手动重写toString()以及IDEA生成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
37
38
39
40
41
42
43
package com.Learging10.object.tostring;

/**
* @author yuxiaohong
* @package com.Learging10.object.tostring
* @date 2023/8/2 13:53
* @description
*/
public class toStringTest {
public static void main(String[] args) {
User user = new User("小红", 23);
System.out.println(user.toString()); // com.Learging10.object.tostring.User@7ef20235
String s1 = new String("666");
System.out.println(s1.toString()); // 666
}
}

class User {
String name;
int age;

public User() {
}

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

// 手动重写toString()方法
// public String toString() {
// return "User{ name = " + this.name + ", age = " + this.age + "}";
// }

// IDEA自动生成toString()重写方法
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

static关键词的使用

1.static:静态的
2.static用来修饰的结构:属性、方法、代码块、内部类
3.对比静态变量和实例变量
(1)个数
静态变量:在内存空间中只有一份,被类的多个对象所共享
实例变量:类的每一个实例都保存着一份实例变量
(2)内存位置
静态变量:jdk6及之前存放在方法区,jdk7及之后存放在堆空间中
实例变量:存放在对空间的对象实体中
(3)加载时机
静态变量:随着类的加载而加载,由于类只会加载一次,所以静态变量也只有一份
实例变量:随着对象的创建而加载
(4)调用者
静态变量:可以被类直接调用,也可以使用对象调用
实例变量:只能使用对象进行调用
(5)消亡时机
静态变量:随着类的卸载而消亡
实例变量:随着对象的消亡而消亡

4.static修饰方法:
(1)随着类的加载而加载
(2)可以通过“类.静态方法”的方式,直接调用静态方法
(3)静态方法内可以调用静态的属性或静态的方法,不可以调用非静态的结构
(4)在类的非静态方法中,可以调用当前类中的静态结构或非静态结构
(5)static修饰的方法内,不能使用this和super

单例(Singleton)设计模式

何为单例模式?

所谓类的单例设计模式,就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类提供一个取得其对象实例的方法。

实现思路

如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能用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
package com.Learning12.singleton;

/**
* @author yuxiaohong
* @package com.Learning12.singleton
* @date 2023/8/4 10:44
* @description 单例模式,饿汉式
*/
public class BankTest {
public static void main(String[] args) {
Bank bank = Bank.getBank();
}
}

class Bank {
// 类的构造器私有化,避免类的外部创建对象
private Bank() {

}

// 在类的内部创建当前类的实例
private static Bank bank = new Bank();

// 使用get方法获取当前类的实例,必须声明为static
public static Bank getBank() {
return bank;
}
}

懒汉式

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
package com.Learning12.singleton;

/**
* @author yuxiaohong
* @package com.Learning12.singleton
* @date 2023/8/4 10:51
* @description 单例模式,懒汉式,延迟加载。调用的时候再创建
*/
public class GirlFriendTest {
}

class GirlFriend {
// 类的构造器私有化
private GirlFriend() {

}

private static GirlFriend girlFriend = null;

public static GirlFriend getGirlFriend() {
if (girlFriend == null) {
girlFriend = new GirlFriend();
}
return girlFriend;
}
}

对比两种模式的优缺点

1.特点
饿汉式:“立即加载”,随着类的加载而记载,当前的唯一实例就创建了
懒汉式:“延迟记载”,在需要使用的时候进行创建

2.优点
饿汉式:写法简单,由于内存中较早加载,使用更方便、更快。线程安全。
懒汉式:在需要的时候进行创建,节省内存空间

3.缺点
饿汉式:内存中占用时间较长
懒汉式:线程不安全

单例模式的一些应用场景

1.Windows中的任务管理器和回收站
2.手机上的App应用
3.数据库连接池的设计一般也是采用单例模式

类的成员之四:代码块

1.代码块的作用:
用来初始化类或对象的信息(即初始化类或者对象的成员变量)

2.代码块的修饰
只能使用static进行修饰,或者没有修饰词

3.代码块的分类
静态代码块:使用static进行修饰
非静态代码块:没有使用static进行修饰

4.具体使用
静态代码块的使用:随着类的加载而执行;由于类的加载只会执行一次,进而静态代码块的执行也只有一次。
用来初始化类的信息
静态代码块的内部只能调用静态的结构(即静态的属性、方法),不能调用非静态的结构
非静态代码块的使用:随着对象的创建而执行,每创建当前类的一个实例,就会执行一次非静态代码块。
用来初始化对象的信息
非静态代码块的内部既能调用静态的结构(即静态的属性、方法),也能调用非静态的结构

final关键字的使用

1.final的理解:最终的

2.final可以修饰的结构:类、方法、变量

3.具体说明:

(1)final修饰类:表示此类不能被继承,即将来也不再需要进行额外的功能扩展
(2)final修饰方法:表示此方法不能被重写
(3)final修饰变量:既可以修饰成员变量也可以修饰局部变量;此时的“变量”其实就变成了常量
final修饰成员变量有哪些位置可以给成员变量赋值
显式赋值
代码块中赋值
构造器中赋值
final修饰局部变量:一旦赋值就不能更改,且在调用局部变量之前一定要进行赋值

4.final和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
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
package com.Learning16._abstract;

/**
* @author yuxiaohong
* @package com.Learning16._abstract
* @date 2023/8/4 18:55
* @description 抽象类测试类
*/
public class AbstractTest {
public static void main(String[] args) {
Student student = new Student();
student.eat();
}
}

// 抽象类
abstract class Person {
String name;
int age;

public Person() {
}

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

// 抽象方法,大括号都不能保留
public abstract void eat();

public void sleep() {
System.out.println("人睡觉");
}
}

class Student extends Person {
String school;

public Student() {
}

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

@Override
public void eat() {
System.out.println("学生吃饭");
}

@Override
public void sleep() {
System.out.println("学生睡觉");
}
}

1.abstract的概念:抽象的
2.abstract可以修饰:类、方法
3.具体的使用
3.1abstract修饰类:
此类称为抽象类;不可以实例化;
抽象类是包含构造器的,因为子类对象实例化时,需要直接或者间接的调用到父类的构造器;
抽象类中可以不包含抽象方法;抽象方法所在的类一定是抽象类
3.2abstract修饰方法:
此方法即为抽象方法;
抽象方法只有方法的声明没有方法体;
抽象方法其功能是确定的(通过方法的声明即可确定)
子类必须重写父类中所有的抽象方法之后才可以实例化,否则此子类仍然是一个抽象类;
4.abstract不能修饰哪些结构:属性、构造器、代码块;
5.abstract不能与哪些关键字同时使用:不能修饰私有方法、静态方法、final的方法、final的类;
原因:私有方法不能被重写;避免静态方法使用类进行调用;final修饰的方法不能被重写;final修饰的类不能喝有子类;

接口

1.接口的理解:接口的本质是契约、标准、规范,就像法律一样,制定好之后都要遵守。
2.定义接口的关键字:interface
3.接口内部结构的说明:
可以声明:属性:必须使用public、static、final修饰;方法:JDK8之前只能声明抽象方法,修饰为public abstract;JDK8:声明静态方法、默认方法;JDK9:可以声明私有方法
不可以声明:构造器、代码块等
4.接口与类的关系:实现关系
5.格式:class A implements B,C{}
6.满足此关系之后,说明:
类可以实现多个接口。
类针对于接口的多实现,一定程度上弥补了类的单继承的局限性。
类必须将实现的接口中的所有的抽象方法都重写,方可实例化,否则此实现类必须声明为抽象类。
7.接口与接口之间的关系:继承关系,且可以多继承。
8.接口的多态性:接口名 变量名 = new 实现类对象
9.区分抽象类和接口
共性:都可以有抽象方法;都不能实例化;
不同:抽象类一定有构造器,接口没有构造器;类与类之间是继承关系,接口与接口之间是多继承关系,类与接口之间是实现关系;

接口使用方法

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

/**
* @author yuxiaohong
* @package com.Learning17._interface.apply
* @date 2023/8/8 18:31
* @description
*/
public class USBTest {
public static void main(String[] args) {

Computer computer = new Computer();
// 创建接口实现类的对象
Printer printer = new Printer();
computer.transferData(printer);
// 创建接口实现类的匿名对象
computer.transferData(new Camera());
// 创建接口匿名实现类的对象
USB usb = new USB() {
@Override
public void start() {
System.out.println("U盘开始工作了");
}

@Override
public void stop() {
System.out.println("U盘结束工作了");
}
};
computer.transferData(usb);

// 创建接口匿名实现类的匿名对象
computer.transferData(new USB() {
@Override
public void start() {
System.out.println("匿名实现类的匿名对象开始工作了");
}

@Override
public void stop() {
System.out.println("匿名实现类的匿名对象结束工作了");
}
});

}
}

interface USB {
public abstract void start();

void stop();
}

class Computer {
public void transferData(USB usb) { // USB usb = new Printer() 接口的多态性体现
System.out.println("设备连接成功...");
usb.start();
System.out.println("数据传输的细节操作");
usb.stop();
}
}

class Printer implements USB {
@Override
public void start() {
System.out.println("打印机开始工作");
}

@Override
public void stop() {
System.out.println("打印机结束工作");
}
}

class Camera implements USB {
@Override
public void start() {
System.out.println("照相机开始工作");
}

@Override
public void stop() {
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
package com.Learning17._interface.jdk8;

/**
* @author yuxiaohong
* @package com.Learning17._interface.jdk8
* @date 2023/8/9 11:51
* @description
*/
public interface CompareA {

// jdk8中新定义的静态方法
public static void method() {
System.out.println("接口中的静态方法,只能接口自己调用,实现类无法调用");
}

// jdk8中新定义的接口默认方法
public default void method1() {
System.out.println("接口中的默认方法,实现类可以进行重写");
}

// jdk9中新定义的接口私有方法
private void method2() {
System.out.println("供接口自己使用");
}
}

类的成员之五:内部类

1.什么是内部类:将一个类A定义在另一个类B里面,类A就称为内部类,类B则称为外部类
2.为什么要声明内部类:当一个事物A的内部,还有一个部分需要一个完整的结构B进行描述,而这个内部的完整结构B又只为外部事物A提供服务,不在其他地方单独使用,那么整个内部的完整结构最好使用内部类。
总体来说,遵循高内聚低耦合的面向对象开发原则。
3.内部类使用举例:
Thread类内部声明State类,用于表示线程的生命周期。
HashMap类中,声明了一个Node类,用来表示封装的key和value
4.内部类的分类:(参考变量的分类)
成员内部类:直接声明在外部类的里面;使用static称为静态的成员内部类、反之是非静态的成员内部类
局部内部类:声明在方法内、构造器内、代码块内;匿名的局部内部类、非匿名的局部内部类

5.关于成员内部类的理解:
从类的角度看:内部可以声明属性、方法、构造器、代码块、内部类等结构;此内部类可以声明父类、可以实现接口;可以使用final修饰,表示不能有子类;可以使用abstract进行修饰。

从外部类的成员的角度看:在内部可以调用外部类的结构,如:属性、方法等;除了使用public、缺省权限修饰之外,还可以使用private和protected进行修饰;可以使用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
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
package com.Learning18.inner;

/**
* @author yuxiaohong
* @package com.Learning18.inner
* @date 2023/8/9 13:49
* @description
*/
public class OuterClass {
public static void main(String[] args) {
// 1.创建Person静态成员内部类的实例
Person.Dog dog = new Person.Dog();
dog.eat();

// 创建Person的非静态的成员内部类的实例
Person person = new Person();
Person.Bird bird = person.new Bird();
bird.eat();
bird.show("jack");
bird.show1();
}
}

// 外部类
class Person {

String name = "Tom";

public void eat() {
System.out.println("人吃饭");
}


// 静态的成员内部类
static class Dog {
public void eat() {
System.out.println("狗可以啃骨头");
}
}

// 非静态的成员内部类
class Bird {
String name = "Jerry";

public void eat() {
System.out.println("鸟吃虫子");
}

public void show(String name) {
System.out.println("形参中的name" + name);
System.out.println("Bird类中的name" + this.name);
System.out.println("Person类中的name" + Person.this.name);
}

public void show1() {
eat();
this.eat();
// 调用外部类的eat方法
Person.this.eat();
}
}

public void method() {
// 方法中的局部内部类
class InnerClass1 {

}
}

public Person() {
// 构造器中的局部内部类
class InnerClass2 {

}
}
}

局部内部类的使用

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
package com.Learning18.inner;

/**
* @author yuxiaohong
* @package com.Learning18.inner
* @date 2023/8/14 18:37
* @description 局部内部类的使用
*/
public class OuterClassTest1 {
public void method1() {
// 局部内部类
class A {
// 可以声明属性、方法等

}
}

// 开发中的场景
public Comparable getInstance() {
// 提供了实现了Comparable接口的实例
// 方式1:提供了接口的实现类的匿名对象
// class MyComparable implements Comparable {
// @Override
// public int compareTo(Object o) {
// return 0;
// }
// }
// return new MyComparable();

// 方式2:提供了接口的匿名实现类的实现对象
return new Comparable() {
@Override
public int compareTo(Object o) {
return 0;
}
};
}
}

枚举类

枚举类的理解

枚举类型本质上也是一种类,只不过这个类的对象是有限的、固定的几个、不能让用户随意创建。

开发中的建议

1.如果针对某个类,其实例个数是确定的。则推荐将此类声明为枚举类。
2.如果枚举类的实例只有一个,则可以看做单例的实现方式。

JDK5.0之前与之后如何定义枚举类

JDK5.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
package com.Learning19._enum;

/**
* @author yuxiaohong
* @package com.Learning19._enum
* @date 2023/8/15 18:28
* @description
*/
public class SeasonTest {
public static void main(String[] args) {
System.out.println(Season.autumn);
}
}

// JDK5.0之前的枚举类实现方式
class Season {

// 声明当前类的对象的实例变量
private final String seasonName;
private final String seasonDesc;
// 因为枚举类的实例个数是有限的,所以要私有化类的构造器,防止无限构造实例对象

private Season(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}

public String getSeasonName() {
return seasonName;
}

public String getSeasonDesc() {
return seasonDesc;
}

// 创建类的实例
public static final Season spring = new Season("春天", "春暖花开");
public static final Season summer = new Season("夏天", "夏日炎炎");
public static final Season autumn = new Season("秋天", "秋高气爽");
public static final Season winter = new Season("冬天", "白雪皑皑");

@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}

JDK5.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
package com.Learning19._enum;

/**
* @author yuxiaohong
* @package com.Learning19._enum
* @date 2023/8/15 18:47
* @description
*/
public class SeasonTest1 {
}

enum Season1 {
// 必须在枚举类的开头声明多个对象,对象之间必须使用逗号隔开
Season1("春天", "春暖花开"),
summer1("夏天", "夏日炎炎"),
autumn1("秋天", "秋高气爽"),
winter1("冬天", "白雪皑皑");
// 声明当前类的对象的实例变量
private final String seasonName;
private final String seasonDesc;
// 因为枚举类的实例个数是有限的,所以要私有化类的构造器,防止无限构造实例对象

private Season1(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}

public String getSeasonName() {
return seasonName;
}

public String getSeasonDesc() {
return seasonDesc;
}
@Override
public String toString() {
return "Season1{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}

Enum中的常用方法

1.使用enum关键字定义的枚举类,默认父类是java.lang.Enum类,不能再显式定义其父类,会报错。
2.熟悉Enum类中常用的方法:
String toString():默认返回的是常量名(对象名),可以手动重写该方法。
static 枚举类型[] values():返回枚举类型的对象数组。该方法可以很方便的遍历所有的枚举值,是一个静态方法。
static 枚举类型 valueof(String name),可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举

3.枚举类实现接口的操作:
情况1:枚举类实现接口,在枚举类中重写接口的抽象方法。当通过不同的枚举类对象调用此方法时,执行的是同一个方法。
情况2:让枚举类的一个对象,去重写接口中的抽象方法。当不同的枚举类对象调用此方法时,执行的是各自重写的方法。

如下具体代码所示

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

/**
* @author yuxiaohong
* @package com.Learning19._enum
* @date 2023/8/15 18:47
* @description
*/
public class SeasonTest1 {
public static void main(String[] args) {
// 测试方法
// 1.toString()方法,打印当前对象名
System.out.println(Season1.Spring);

// 2.name()方法,打印当前对象名,用于在toString()方法被重写的情况下
System.out.println(Season1.Spring.name());

// 3.values()方法,把当前枚举类中所有的对象都放到数组当中
Season1[] values = Season1.values();

// 4.valueOf(String objName),用于获取与传入字符串名字相同的对象
String objName = "winter";
Season1 season1 = Season1.valueOf(objName);
System.out.println(season1);

// 5.ordinal(),获取当前对象所在枚举类中的次序(按枚举类中声明的顺序),从0开始
System.out.println(Season1.Spring.ordinal());

// 通过枚举类的对象调用重写接口中的方法
Season1.Spring.show();
}
}

enum Season1 implements Info {
// 必须在枚举类的开头声明多个对象,对象之间必须使用逗号隔开
// 在各个对象中实现接口的重写方法
Spring("春天", "春暖花开") {
@Override
public void show() {
System.out.println("这个季节是" + getSeasonName());
}
},
summer("夏天", "夏日炎炎") {
@Override
public void show() {
System.out.println("这个季节是" + getSeasonName());
}
},
autumn("秋天", "秋高气爽") {
@Override
public void show() {
System.out.println("这个季节是" + getSeasonName());
}
},
winter("冬天", "白雪皑皑") {
@Override
public void show() {
System.out.println("这个季节是" + getSeasonName());
}
};
// 声明当前类的对象的实例变量
private final String seasonName;
private final String seasonDesc;
// 因为枚举类的实例个数是有限的,所以要私有化类的构造器,防止无限构造实例对象

private Season1(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}

public String getSeasonName() {
return seasonName;
}

public String getSeasonDesc() {
return seasonDesc;
}

// 在枚举类中进行重写接口中的方法
// @Override
// public void show() {
// System.out.println("这是一个季节");
// }
}

interface Info {
void show();
}

Annotation注解

什么是注解

注解(Annotation)是JDK5.0引入的,以“@注解名”在代码中存在。例如:
@Override ->重写
@Deprecated ->已弃用
@SuppressWarnings(value=”unchecked”) ->抑制编译器警告

注解与注释

对于单行注释和多行注释,是给程序员看的
注解则是可以被编译器或其它程序读取的。程序还可以根据注解的不同,作出相应的处理。

常见的Annotation作用

@author:表明开发该模块的作者,多个作者之间使用,分割
@version:表明该类模块的版本
@see:参考转向,也就是相关主题
@since:从哪个版本开始增加的
@param:对方法中参数的说明,如果没有参数就不能写
@return:对方法返回值的说明,如果方法的返回值类型是void就不能写
@exception:对方法可能抛出的异常进行说明,如果方法没有用throws显式抛出异常就不能写

自定义注解

参考@SuppressWarnings为参照,进行定义即可。

元注解

对现有的注解进行解释说明的注解。
1.@Target:用于描述注解的使用范围
可以通过枚举类型ElementType的10个常量对象来指定
TYPE、METHOD、CONSTRUCTOR、PACKAGE…
2.@Retention:用于描述注解的生命周期
可以通过枚举类型RetentionPolicy的3个常量对象来指定
SOURCE(源代码)、CLASS(字节码)、RUNTIME(运行时)
唯有RUNTIME阶段才能被反射读取到
3.@Documented:表明这个注解应该被javadoc工具记录
4.@Inherited:允许子类继承父类中的注解

单元测试

下载好junit和hamcrest-core这两个jar包之后导入到项目库当中,然后使用注解@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
package com.Learning20.annotation.junit;

import org.junit.Test;

/**
* @author yuxiaohong
* @package com.Learning20.annotation.junit
* @date 2023/8/16 10:48
* @description
*/
public class JUnitTest {
int number = 10;

@Test
public void test() {
System.out.println("这是JUnit的单元测试");
}

@Test
public void test2() {
System.out.println("number = " + number);
}
}

要想能正确的编写单元测试方法,需要满足

所在的类必须是public的,非抽象的,包含唯一的无参构造器
@Test标记的方法本身必须是public的,非抽象的,非静态的,void无返回值的,无参数的

默认情况下,单元测试方法中使用Scanner会失效,解决方法

在IDEA上方的帮助中选择“编辑自定义虚拟机选项”,然后在最后一行加上
-Deditable.java.test.console=true
就可以让Scanner恢复正常功能

定义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
package com.Learning21.wrapper;

/**
* @author yuxiaohong
* @package com.Learning21.wrapper
* @date 2023/8/16 18:20
* @description
*/
public class WrapperTest {
public static void main(String[] args) {
MyInteger myInteger = new MyInteger(33);
System.out.println(myInteger);
}
}

class MyInteger {
int value;

public MyInteger() {
}

public MyInteger(int value) {
this.value = value;
}

public int getValue() {
return value;
}

public void setValue(int value) {
this.value = value;
}

@Override
public String toString() {
return "MyInteger{" +
"value=" + value +
'}';
}
}

掌握基本数据类型和以后用数据类型之间的转换

为什么需要转换

一方面,在有一些场景下,需要使用到基本数据类型对用的包装类对象,此时就需要将基本数据类型的变量转换为包装类的对象。
比如:ArrayList的add(Object obj);Object类的equals(Object obj)
对于包装类来讲:既然使用的是对象,对象是不能进行加减乘除等运算的。为了能够进行运算就需要将包装类对象转换为基本数据类型对象。

jdk5.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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package com.Learning21.wrapper;

import com.Learning14.block.BlockTest;
import org.junit.Test;

/**
* @author yuxiaohong
* @package com.Learning21.wrapper
* @date 2023/8/16 18:57
* @description 包装类和基本数据类型之间的转换
*/
public class WrapperTest2 {
// 基本数据类型--->包装类,1.使用包装类的构造器 2.建议使用valueOf(xxx)
@Test
public void test1() {
int i1 = 10;
Integer i = new Integer(i1);
System.out.println(i.toString());

float f1 = 12.3F;
Float ff1 = new Float(f1);
System.out.println(ff1.toString());

boolean b1 = true;
Boolean bb1 = new Boolean(b1);
System.out.println(bb1.toString());

// 推荐使用
Integer i2 = Integer.valueOf(i1);
Boolean b = Boolean.valueOf(b1);
Float v = Float.valueOf(f1);
System.out.println(i2.toString() + b.toString() + v.toString());
}

// 包装类--->基本数据类型,调用包装类的xxxValue()实现
@Test
public void test2() {
Integer i = new Integer(11);
int i1 = i.intValue();
System.out.println(++i1);

Float f = new Float(12.3F);
float f1 = f.floatValue();
System.out.println(++f1);

Boolean b = new Boolean(true);
boolean b1 = b.booleanValue();
System.out.println(b1 + "123");
}

// jdk5新特性:自动装箱、自动拆箱
@Test
public void test3() {
// 自动装箱
int i1 = 10;
Integer ii1 = i1;
System.out.println(ii1.toString());

Boolean bb1 = true;
Float f1 = 12.3F;

// 自动拆箱
int ii2 = ii1;
System.out.println(ii2);
}
}

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
package com.Learning21.wrapper;

import org.junit.Test;

/**
* @author yuxiaohong
* @package com.Learning21.wrapper
* @date 2023/8/17 19:02
* @description String与包装类、基本数据类型之间的转换
*/
public class WrapperTest1 {
// 基本数据类型、包装类转换为String类型:调用String的静态方法valueOf()
@Test
public void test() {
int i1 = 10;
String s = String.valueOf(i1);
System.out.println(s); //"10",控制台打印的是没有引号的,实际上是有的

boolean b1 = true;
String s1 = String.valueOf(b1);
System.out.println(s1);

Float f1 = 12.3f;
String s2 = String.valueOf(f1);
System.out.println(s2);
}

@Test
public void test1() {
// String类型转换为基本数据类型、包装类:调用包装类的静态方法parseXxx()
String s1 = "123";
int i1 = Integer.parseInt(s1);
System.out.println(i1);
}
}

异常

什么是程序的异常

在使用计算机语言进行项目开发的过程中,即使把代码写的尽善尽美,在系统运行的过程当中仍然会遇到一些问题,比如:客户输入数据的格式不对,读取文件是否存在,网络是否保持通畅等等。

异常:指的是程序在执行过程中,出现的非正常情况,如果不处理最终会导致JVM的非正常停止。

异常的抛出机制

Java中把不同的异常用不同的类表示,一旦发生某种异常,就创建该异常类的对象,并且抛出(throw)。然后程序员可以捕获(catch)到这个异常对象,并处理。如果没有捕获这个异常对象,那么这个异常对象将导致程序终止。

编译时异常和运行时异常

编译时期异常(即checked异常、受检异常):在代码编译阶段,编译器就能明确警示当前代码可能发生的xx异常。
运行时期异常(即runtime异常、unchecked异常、非受检异常):在代码编译阶段,编译器完全不做任何检查,无论该异常是否发生,编译器都不给出任何提示,只有等代码运行起来并且发生了异常,编译器才会发现。

异常的体系结构

java.lang.Throwable:异常体系的根父类:
java.lang.Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。一般不编写针对性的代码进行处理。
java.lang.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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package com.Learning01.throwable;

import org.junit.Test;
import org.w3c.dom.ls.LSOutput;

import java.io.File;
import java.io.FileInputStream;
import java.util.Date;
import java.util.Scanner;

/**
* @author yuxiaohong
* @package com.Learning01.throwable
* @date 2023/8/18 18:04
* @description
*/
public class ExceptionTest {

// java.lang.ArrayIndexOutOfBoundsException
@Test
public void test() {
int arr[] = new int[10];
System.out.println(arr[10]);
}

// java.lang.NullPointerException
@Test
public void test1() {
// String str = "hello";
// str = null;
// System.out.println(str.toString());

// int arr[] = new int[10];
// arr = null;
// System.out.println(arr[0]);

int[][] arr1 = new int[10][];
System.out.println(arr1[0][0]);
}

// java.lang.ClassCastException
@Test
public void test2() {
Object obj = new String();
Date date = (Date) obj;
}

// java.lang.NumberFormatException
@Test
public void test3() {
String str = "123";
str = "abc";
int i = Integer.parseInt(str);
System.out.println(i);
}

// java.util.InputMismatchException,输入了除数字之外的其他类型
@Test
public void test4() {
Scanner scanner = new Scanner(System.in);
int num = scanner.nextInt();
System.out.println(num);
}

// java.lang.ArithmeticException,算数异常
@Test
public void test5() {
int num = 10;
System.out.println(num / 0);
}
// 以上是运行时异常

// java.lang.ClassNotFoundException
@Test
public void test6() {
Class clz = Class.forName("java.lang.String");
}

// java.io.FileNotFoundException
@Test
public void test7() {
File file = new File("hello.txt");
FileInputStream fileInputStream = new FileInputStream(file);
}
}

异常的处理

在编写程序时,经常要在可能出现的错误的地方加上检测的代码,如运算x/y的时候,要检测分母是否为0,数据为空,输入的不是数据而是字符等。因此要采用异常处理机制。

Java异常处理

Java采用额异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅、并易于维护。

方式一:try-catch-finally

过程1:抛:程序在执行的过程中, 一旦出现异常,就会出现在异常的代码处,生成对应异常类的对象,并将此对象抛出。
一旦抛出,此程序就不执行其后的代码。

过程2:抓:针对过程1中抛出的异常对象进行捕获处理。
一旦将出现的异常处理掉,代码就可以继续执行。

基本结构

try{
可能产生异常的代码
}
catch(异常类型1 e){
当产生异常类型1异常时的处理措施
}
catch(异常类型2 e){
当产生异常类型2异常时的处理措施
}
finally{
无论是否发生异常,都无条件执行的语句
}

使用细节

如果声明了多个catch结构,不同的异常类型不存在父子类的关系,谁声明在上,或者在下都无所谓。
但如果多个类型满足父子类的关系,则必须将子类的结构声明在父类的上方。否则报错,因为先执行父类的异常处理,就不会执行子类异常的处理了

catch中异常处理的方式

1.自己编写输出的语句
2.printStackTrace():打印异常的详细信息
3.getMessage():获取发生异常的原因

开发体会

对于运行时异常:开发中,通常不进行显式处理,一旦在程序运行中出现按照提示进行修改代码即可。
对于编译时异常:一定要进行处理,否则编译不通过。

处理异常代码

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
package com.Learning01.throwable;

import org.junit.Test;
import org.w3c.dom.ls.LSOutput;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Date;
import java.util.InputMismatchException;
import java.util.Scanner;

/**
* @author yuxiaohong
* @package com.Learning01.throwable
* @date 2023/8/18 18:04
* @description
*/
public class ExceptionTest {

// java.util.InputMismatchException,输入了除数字之外的其他类型
@Test
public void test4() {
try {
Scanner scanner = new Scanner(System.in);
int num = scanner.nextInt();
System.out.println(num);
} catch (InputMismatchException e) {
// System.out.println("出现了InputMismatchException的异常");
e.printStackTrace();
}
System.out.println("异常处理结束,代码继续执行");

}

// 以上是运行时异常,以下是编译时异常

// java.io.FileNotFoundException
@Test
public void test7() {
try {
File file = new File("hello.txt");
FileInputStream fileInputStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
System.out.println("读取数据结束");
}
}

finally的使用

1.关于finally的理解:

将一定要执行的代码声明在finally结构中。
无论try中或catch中是否存在仍未被处理的异常或者存在return语句,finally中的代码都会被执行。

唯一的例外就是使用System.exit(0)终止当前运行的虚拟机。

finally语句和catch语句是可选的,但是finally不能单独使用,try也不能单独使用。

2.什么样的代码需要写在finally当中

在开发中,有一些资源:输入流、输出流、数据库连接、Socket连接等资源,在使用完之后必须显式的进行关闭操作,否则GC不会自动回收这些资源,进而导致内存泄露。
必须将这些操作声明在finally当中。

方式二:throws+异常类型

1.格式:在方法的声明处使用throws+异常类型1,异常类型2,…

2.基本结构:
public void test() throws 异常类型1,异常类型2,..{
可能产生异常的代码
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.learning03._throws;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

/**
* @author yuxiaohong
* @package com.learning03._throws
* @date 2023/8/22 10:20
* @description
*/
public class ThrowTest {

public static void main(String[] args) {
method2();
}

public static void method2() {
try {
method1();
} catch (FileNotFoundException e) {
e.printStackTrace();
}

}

public static void method1() throws FileNotFoundException {
File file = new File("hello.txt");
FileInputStream fileInputStream = new FileInputStream(file);
System.out.println("读取数据结束");
}
}

3.是否真正处理了异常?

从编译是否能通过的角度看,看成是给出了异常的解决方案,继续向上抛出。
但是throws的方式仅是将可能出现的异常抛给了此方法的调用者。调用者仍然需要考虑如何处理相关异常。
从这个角度来看,throws不算真正意义上处理了异常。

4.方法重写的要求(针对于编译型异常)

子类重写的方法抛出的异常类型可以与被重写的方法抛出的异常类型相同,或者是父类被重写的方法抛出的异常类型的子类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.learning03._throws;

import java.io.FileNotFoundException;
import java.io.IOException;

/**
* @author yuxiaohong
* @package com.learning03._throws
* @date 2023/8/22 10:45
* @description
*/
public class OverrideTest {
public static void main(String[] args) {
Father f = new Son();
try {
f.method1();
} catch (IOException e) {
e.printStackTrace();
}
}
}

class Father {
public void method1() throws IOException {

}
}

class Son extends Father {
@Override
public void method1() throws FileNotFoundException {
}
}

5.开发中,如何选择处理两种异常方式:

涉及到资源的调用(流、数据库连接、网络连接等),使用try-catch-finally处理,防止发生内存泄漏。
如果父类被重写的方法没有throws异常类型,子类重写的方法中出现异常,只能使用try-catch-finally。
开发中,方法a中依次调用了b,c,d等方法,方法b,c,d是递进关系。此时如果方法b,c,d中有异常,通常选择使用throws,然后在a中使用try-catch-finally处理。

手动抛出异常(throw)

为什么需要手动抛出异常对象

在实际开发中,如果出现不满足具体问题的情况就有必要手动抛出一个指定类型的异常对象。

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
package com.learning04._throw;

/**
* @author yuxiaohong
* @package com.learning04._throw
* @date 2023/8/22 11:23
* @description
*/
public class ThrowTest {
public static void main(String[] args) {
Student student = new Student();
try {
student.regist(-12);
} catch (RuntimeException e) {
e.printStackTrace();
}
System.out.println("程序结束");
}
}

class Student {
int id;

public void regist(int id) {
if (id > 0) {
System.out.println(this.id = id);
} else {
// System.out.println("id不能为负");
// 手动抛出异常类的对象
throw new RuntimeException("id不能为负");
}

}
}

自定义异常类

1.继承于现有的异常体系,通常继承于RuntimeException\Exception
2.提供几个重载的构造器
3.提供一个全局常量,声明为:static final long serialVersionUID

多线程

相关概念

1.程序、进程与线程

程序(program):为完成特定任务,用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

进程(process):程序的一次执行过程,或是正在内存中运行的应用程序,如:运行中的QQ,运行中的音乐播放器。
每个进程都有一个独立的内存空间,系统运行一个程序即是一个进程从创建、运行到消亡的过程。(声明周期)
程序是静态的,进程是动态的。
进程作为操作系统调度和分配资源的最小单位(亦是系统运行程序的基本单位),系统在运行时会为每个进程分配不同的内存区域。
现代的操作系统,大多数是支持多进程的,支持同时运行多个程序。

线程(thread):进程可进一步细分为线程,是程序内部的一条执行路径。一个进程中至少有一个线程。
一个进程同一时间若并行多个线程,就是支持多线程的。
线程作为CPU调度和执行的最小单位。
一个进程中的多个线程共享相同的内存单元,它们从同一个堆中分配对象,可以访问相同的变量和对象。这就使得线程间的通信更简便、高效。但多个线程操作共享的系统资源可能带来安全隐患。
不同进程之间不共享内存。
进程之间的数据交换和成本很高。

线程调度

分时调度:所有线程轮流使用CPU的使用权,并且平均分配每个线程占用CPU的时间

抢占式调度:让优先级高的线程以较大概率优先使用CPU、如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

多线程程序的优点

1.提高应用程序的响应,对图形化界面更有意义,可增强用户体验。
2.提高计算机系统的CPU利用率。
3.改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。

并行与并发

并行(parallel):指两个或多个事件在同一时刻发生(同时发生)。指在同一时刻,有多条指令在多个CPU上同时执行。

并发(concurrency):指两个或多个事件在同一时间段内发生。即在一段时间内,有多条指令在单个CPU上快速轮换、交替执行,使得在宏观上具有多个进程同时执行的效果。

创建和启动线程

Java语言的JVM实例允许程序运行多个线程,使用java.lang.Thread类代表线程,所有线程的对象都必须是Thread类或其子类的实例。
Thread类的特性:每个线程都是通过某个特定Thread对象的run()方法来完成操作的,因此把run()方法体称为线程执行体;通过该Thread对象的start()方法来启动这个线程,而非直接调用run();要想实现多线程,必须在主线程中创建新的线程对象。

线程的创建方式一:继承Thread类

1.创建一个继承于Thread类的子类
2.重写Thread类的run()方法 —>将此线程要执行的操作声明在此方法体中
3.创建当前Thread的子类的对象
4.通过对象调用start():启动线程;调用当前线程的run方法

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
package com.learning01.create.exer1;

/**
* @author yuxiaohong
* @package com.learning01.create.exer1
* @date 2023/8/28 15:33
* @description
*/
public class PrintNumberTest {
public static void main(String[] args) {
// EvenNumberPrint t1 = new EvenNumberPrint();
// OddNumberTest t2 = new OddNumberTest();
//
// t1.start();
// t2.start();

// 方式2:创建Thread类的匿名子类的匿名对象
new Thread() {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();

new Thread() {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();

}
}

// 打印偶数
class EvenNumberPrint extends Thread {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}

class OddNumberTest extends Thread {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}

线程的创建方式二:实现Runnable接口

1.创建一个实现Runnable接口的类
2.实现接口中的run()方法 —>将此线程要执行的操作声明在此方法体中
3.创建当前实现类的对象
4.将此对象作为参数传递到Thread构造器中,创建Thread类的实例
5.Thread类的实例调用start():启动线程;调用当前线程的run方法

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
package com.learning01.create.runnable;

/**
* @author yuxiaohong
* @package com.learning01.create.runnable
* @date 2023/8/28 16:31
* @description
*/
public class EvenNumberTest {
public static void main(String[] args) {
// 3.创建当前实现类的对象
EvenNumberPrint p = new EvenNumberPrint();
// 4.将此对象作为参数传递给Thread对象
new Thread(p).start();

// main方法对应的主线程执行的操作
for (int i = 1; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}

// 1.创建一个实现Runnable接口的类
class EvenNumberPrint implements Runnable {
// 2.实现接口中的run()
@Override
public void run() {
for (int i = 1; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}

对比两种方式

共同点:1.启动线程,使用的都是Thread类中定义的start();2.创建的线程对象,都是Thread类或其子类的实例。
不同点:一个是类的继承,一个是接口的实现。
建议:建议使用实现Runnable接口的方式,好处是实现的方式避免了类的单继承的局限性,更适合来处理有共享数据的问题。

线程的常用结构

1.线程中的构造器

public Thread():分配一个新的线程对象
public Thread(String name):分配一个指定名字的新的线程对象
public Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
public Thread(Runnable target, String name):分配一个带有指定目标新的线程对象并指定名字

2.线程中的常用方法

start():1.启动线程;2.调用线程的run();
run():将线程要执行的操作声明在run()方法中
currentThread():获取当前执行代码对应的线程
getName():获取线程名
setName():设置线程名
sleep():静态方法,让线程睡眠指定的毫秒数
yield():静态方法,一旦执行此方法,就释放CPU的执行权
join():在线程a中通过线程b调用join()方法,意味着线程a进入阻塞状态,直到线程b结束,线程a才结束阻塞状态继续执行
isAlive():判断当前线程是否存活

过时方法:

stop():强行结束一个线程的执行,直接进入死亡状态
void suspend() / void resume():暂停/恢复线程,可能造成死锁,不建议使用

3.线程的优先级

getPriority():获取线程的优先级
setPriority():设置线程的优先级,范围[1,10]

Thread类内部声明的三个常量
MAX_PRIOTITY(10):最高优先级
MIN_PRIOTITY(1):最低优先级
NORM_PRIOTITY(5):普通优先级,默认情况下main线程具有普通优先级

多线程的生命周期

JDK1.5之前:五种状态

线程的声明周期有五种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)

JDK1.5及之后

线程的安全问题与同步机制

当我们使用多个线程访问同一资源(可以是同一个变量、同一个文件、同一条记录等等)的时候,若多线程只有读的操作,那么不会有线程安全的问题。但是如果多个线程对同一个资源有读和写的操作,就容易出现线程安全的问题。

线程不安全的方法,使用实现Runnable接口的方式

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
package com.learning03.threadsafe.notsafe;

/**
* @author yuxiaohong
* @package com.learning03.threadsafe.notsafe
* @date 2023/8/30 16:38
* @description
*/
public class WindowTest {
public static void main(String[] args) {
SaleTicket s = new SaleTicket();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);


t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");

t1.start();
t2.start();
t3.start();
}
}

class SaleTicket implements Runnable {
int ticket = 100;


@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}

线程不安全的方法,使用继承Thread类的方式

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
package com.learning03.threadsafe.notsafe;

/**
* @author yuxiaohong
* @package com.learning03.threadsafe.notsafe
* @date 2023/8/30 16:56
* @description
*/
public class WindowTest1 {
public static void main(String[] args) {
SaleTicket1 s1 = new SaleTicket1();
SaleTicket1 s2 = new SaleTicket1();
SaleTicket1 s3 = new SaleTicket1();

s1.setName("窗口1");
s2.setName("窗口2");
s3.setName("窗口3");


s1.start();
s2.start();
s3.start();
}
}

class SaleTicket1 extends Thread {
int ticket = 100;

@Override
public void run() {

while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}


1.多线程卖票,出现的问题:出现了重票、错票
2.什么原因导致的?

线程1要操作ticket的过程中尚未结束的情况下,其他的线程也参与进来对ticket进行操作,导致线程出现安全问题。

3.如何解决?

必须保证一个线程在操作ticket的过程中,其他线程必须等待,直到当前线程操作ticket结束以后其他线程才可以继续进行操作ticket。

4.Java是如何解决线程的安全问题的?

方法一:同步代码块

synchronized(同步监视器){
需要被同步的代码
}

说明:需要被同步的代码即为操作共享数据的代码。
共享数据:即多个线程都需要操作的数据。比如ticket。
同步监视器:俗称锁,哪个线程获取了锁,哪个线程就能执行被同步的代码。可以使用任意一个类的对象充当,但是,多个线程必须共用同一个同步监视器。

注意:在实现Runnable接口的方式中,同步监视器可以考虑使用this;而在继承Thread类的方式中,同步监视器可以使用类名.class。

线程安全的,实现Runnable接口的方式

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
package com.learning03.threadsafe.runnablesafe;

/**
* @author yuxiaohong
* @package com.learning03.threadsafe.notsafe
* @date 2023/8/30 16:38
* @description
*/
public class WindowTest {
public static void main(String[] args) {
SaleTicket s = new SaleTicket();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);


t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");

t1.start();
t2.start();
t3.start();
}
}

class SaleTicket implements Runnable {
int ticket = 100;

Object obj = new Object();

@Override
public void run() {
while (true) {
synchronized (obj) { // obj是唯一的吗? yes
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}

线程安全的,继承Thread类的方式

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
package com.learning03.threadsafe.threadsafe;

/**
* @author yuxiaohong
* @package com.learning03.threadsafe.notsafe
* @date 2023/8/30 16:56
* @description
*/
public class WindowTest1 {
public static void main(String[] args) {
SaleTicket1 s1 = new SaleTicket1();
SaleTicket1 s2 = new SaleTicket1();
SaleTicket1 s3 = new SaleTicket1();

s1.setName("窗口1");
s2.setName("窗口2");
s3.setName("窗口3");


s1.start();
s2.start();
s3.start();
}
}

class SaleTicket1 extends Thread {
static int ticket = 100;

@Override
public void run() {
// 使用ctrl+alt+t选择包围方式
synchronized (SaleTicket1.class) { // 结构:Class alz = SaleTicket1.class,是唯一的。
while (true) {
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}

方法二:同步方法

如果操作共享数据的代码完整声明在了一个方法中,那么就可以将此方法声明为同步方法即可。
非静态的同步方法,默认的同步监视器是this
静态的同步方法,默认同步监视器默认是当前类本身

synchronized的好处:解决了线程的安全问题
弊端:在操作共享数据时,多线程其实是串行执行的,性能较低。

使用实现Runnable接口的方式使用同步方法保证线程的安全性

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
package com.learning03.threadsafe.runnablesafe;

/**
* @author yuxiaohong
* @package com.learning03.threadsafe.runnablesafe
* @date 2023/8/31 10:07
* @description 使用同步方法的方式解决线程同步安全的问题
*/
public class WindowTest1 {
public static void main(String[] args) {
SaleTicket1 s = new SaleTicket1();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);


t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");

t1.start();
t2.start();
t3.start();
}
}

class SaleTicket1 implements Runnable {
int ticket = 100;

Boolean isFlag = true;

@Override
public void run() {
while (isFlag) {
show();
}
}

public synchronized void show() { // 此时的同步监视器是this
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
} else {
isFlag = false;
}
}
}

使用继承Thread类的方式,使用同步方法实现线程安全

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
package com.learning03.threadsafe.threadsafe;

/**
* @author yuxiaohong
* @package com.learning03.threadsafe.threadsafe
* @date 2023/8/31 10:19
* @description 使用同步方法解决继承Thread类中的线程安全问题
*/
public class WindowTest2 {
public static void main(String[] args) {
SaleTicket2 s1 = new SaleTicket2();
SaleTicket2 s2 = new SaleTicket2();
SaleTicket2 s3 = new SaleTicket2();

s1.setName("窗口1");
s2.setName("窗口2");
s3.setName("窗口3");

s1.start();
s2.start();
s3.start();
}
}

class SaleTicket2 extends Thread {
static int ticket = 100;
static Boolean isFlag = true;

@Override
public void run() {

while (isFlag) {
show();
}
}

public void show() {
synchronized (SaleTicket2.class) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
} else {
isFlag = false;
}
}

}
}

单例之懒汉式的线程安全问题

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

/**
* @author yuxiaohong
* @package com.learning05.threadsafemore.singleton
* @date 2023/8/31 22:58
* @description 实现线程安全的懒汉式
*/
public class BankTest {
static Bank b1 = null;
static Bank b2 = null;

public static void main(String[] args) {
Thread t1 = new Thread() {
@Override
public void run() {
b1 = Bank.getInstance();
}
};

Thread t2 = new Thread() {
@Override
public void run() {
b2 = Bank.getInstance();
}
};

t1.start();
t2.start();

try {
t1.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

try {
t2.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

System.out.println(b1);
System.out.println(b2); // 可以看到两个地址值是相同的
System.out.println(b1 == b2); // true
}
}

class Bank {
private Bank() {

}

private static Bank instance = null;

// 实现线程安全的方式1
// public static synchronized Bank getInstance() {
// try {
// Thread.sleep(1000); // 当睡眠时间够长,就会导致单例模式失效
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// if (instance == null) {
//
// instance = new Bank();
// }
// return instance;
// }

// 实现线程安全的方式二
public static Bank getInstance() {
synchronized (Bank.class) {
try {
Thread.sleep(1000); // 当睡眠时间够长,就会导致单例模式失效
} catch (InterruptedException e) {
e.printStackTrace();
}
if (instance == null) {

instance = new Bank();
}
return instance;
}
}
}

线程同步机制带来的问题:死锁

1.如何看待死锁?

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
编写程序时,要避免出现死锁。

2.诱发死锁的原因

互斥条件、占用且等待、不可抢占、循环等待

3.解决死锁

死锁一旦出现,基本上很难进行人为干预,只能尽量规避。可以考虑打破上面的诱发条件。
针对条件一:互斥条件基本上无法打破,因为线程需要互斥条件解决安全问题。
针对条件二:可以考虑一次性申请所有资源,这样就不存在等待的问题。
针对条件三:占用部分资源的进程在进一步申请其他资源时,如果申请不到就主动释放自身已经占用的资源。
针对条件四:可以将资源改为线性顺序,申请资源时,先申请序号较小的,这样避免循环等待问题。

Lock锁的使用

步骤:
1.创建lock的实例,需要确保多个线程共用一个Lock,需要将这个对象声明为static
2.执行lock方法,锁定对共享资源的调用
3.调用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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.learning05.threadsafemore.singleton.lock;

import java.util.concurrent.locks.ReentrantLock;

/**
* @author yuxiaohong
* @package com.learning05.threadsafemore.singleton.lock
* @date 2023/9/5 22:19
* @description
*/
public class LockTest {
public static void main(String[] args) {
SaleTicket1 s1 = new SaleTicket1();
SaleTicket1 s2 = new SaleTicket1();
SaleTicket1 s3 = new SaleTicket1();

s1.setName("窗口1");
s2.setName("窗口2");
s3.setName("窗口3");

s1.start();
s2.start();
s3.start();
}
}

class SaleTicket1 extends Thread {
static int ticket = 100;

// 1.创建lock的实例,需要确保多个线程共用一个Lock,需要将这个对象声明为static
private static ReentrantLock lock = new ReentrantLock();

@Override
public void run() {

while (true) {
try {
// 2.执行lock方法,锁定对共享资源的调用
lock.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
} else {
break;
}
} finally { // 为了确保unlock一定会被执行,所以要放在finally中
// 3.调用unlock,释放对共享数据的锁定
lock.unlock();
}
}
}
}

Synchronized与Lock的对比

Synchronized不管是同步代码块还是同步方法,都需要在结束一对{}之后,释放对同步监视器的调用。
Lock是通过两个方法来控制需要同步的代码,相比较而言更灵活一些。
Lock作为接口提供了多种实现类,适合更多的、更复杂的场景、效率也会更高一些。

线程之间的通信

为什么需要处理线程之间的通信?

当我们需要多个线程来完成同一件任务,并且我们希望他们可以有规律的执行,那么多线程之间需要一些通信机制,可以协调它们的工作,以此实现多线程共同操作一份数据。

涉及到三个方法的使用

wait():线程一旦执行此方法,就进入等待状态。同时会释放对同步监视器的调用。wait()方法需要进行抛异常的操作。
notify():一旦执行此方法,就会唤醒被wait的线程中优先级最高的那个。
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。

案例1:使用两个线程交替打印1-100之间的数

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
package com.learning06.communication;

/**
* @author yuxiaohong
* @package com.learning06.communication
* @date 2023/9/6 10:33
* @description 使用两个线程打印1-100,两个线程交替打印
*/
public class PrintNumberTest {
public static void main(String[] args) {
PrintNumber p1 = new PrintNumber();
Thread t1 = new Thread(p1, "线程一");
Thread t2 = new Thread(p1, "线程二");

t1.start();
t2.start();
}
}

class PrintNumber implements Runnable {

private static int number = 1;

@Override
public void run() {
while (true) {
synchronized (this) {

try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}

// 一旦执行此方法,就会唤醒被wait的线程中优先级最高的那个。
notify();

if (number <= 100) {
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;

try {
// 线程一旦执行此方法,就会进入等待状态,同时会释放对同步监视器的调用
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}

三个注意点

此三个方法的使用,必须是在同步代码块或者同步方法中。
Lock需要配合Condition配合线程之间的通信。
此三个方法的调用者必须是同步代码块或是同步方法中的同步监视器。否则会报错。

wait()和sleep()的区别

相同点:
两个方法一旦执行都会导致当前线程进入阻塞的状态

不同点:
声明的位置:wait()声明在Object类中,sleep()声明在Thread类中;
使用的场景不同:wait()只能使用在同步代码块或同步方法中,sleep()可以使用在任意位置;
使用在同步代码块或同步方法中:wait()会释放同步监视器,sleep()不会释放同步监视器;
结束阻塞的方式:wait():到达指定时间自动结束阻塞或通过被notify()唤醒结束阻塞,sleep():到达时间自动结束阻塞。

案例2:生产者和消费者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package com.learning06.communication;

/**
* @author yuxiaohong
* @package com.learning06.communication
* @date 2023/9/6 11:24
* @description 生产者&消费者
* 生产者(Producer)将产品交给店员(Clerk),而消费者(Consumer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果
* 生产者试图生产更多产品,店员会叫生产者停一下,如果店员中有空位放产品了,再通知生产者继续生产,如果店中没有产品了,店员会告诉消费者
* 等一下,如果点钟有产品了再通知消费者取走产品。
* <p>
* 分析:是否是多线程问题:是,生产者、消费者
* 是否有共享数据:有,产品
* 是否有线程安全问题:有
* 是否需要处理线程安全问题:需要
* 是否存在线程之间的通信:存在
*/
public class ProducerConsumerTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);

producer.setName("生产者1");
consumer.setName("消费者1");

producer.start();
consumer.start();
}
}

class Clerk {
private static int ProductNumber = 0;

// 增加产品数量的方法
public synchronized void addProduct() {
if (ProductNumber >= 20) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
ProductNumber++;
System.out.println(Thread.currentThread().getName() + "生产者生产了第" + ProductNumber + "个产品");

notifyAll();
}
}

// 减少产品数量的方法
public synchronized void minusProduct() {
if (ProductNumber <= 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName() + "消费者消费了第" + ProductNumber + "个产品");
ProductNumber--;

notifyAll();
}
}
}

class Producer extends Thread {
private Clerk clerk;

public Producer(Clerk clerk) {
this.clerk = clerk;
}

@Override
public void run() {
while (true) {
System.out.println("生产者开始生产产品");
// 每50毫秒生产一个产品
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.addProduct();
}
}
}

class Consumer extends Thread {
private Clerk clerk;

public Consumer(Clerk clerk) {
this.clerk = clerk;
}

@Override
public void run() {
while (true) {
System.out.println("消费者开始消费产品");
// 每50毫秒生产一个产品
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.minusProduct();
}
}
}

新增两种创建线程的方式

创建多线程的方式之三:实现Callable(jdk5.0新增)

与Runnable作对比:
优点:call()方法可以有返回值;call()方法可以使用throws的方式处理异常,更灵活;Callable使用了泛型参数,可以指明具体的返回值类型,更灵活。
缺点:如果在主线程中需要获取分线程call()的返回值,则此时的主线程是阻塞状态的。

创建多线程的方式之四:使用线程池

现有问题:如果并发的线程有很多,并且每一个线程都是执行一个时间很短的任务就结束了,这样频繁创建、销毁线程就会大大降低系统的效率。

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁销毁创建、实现重复利用。

此方法的好处:提高了程序执行的效率;提高了资源的复用率;可以设置相关的参数,对线程池中的线程的使用进行管理。

常用类与基础API

String类

  1. 类的声明:

public final class String
implements java.io.Serializable, Comparable, CharSequence,
Constable, ConstantDesc

final:表示String是不可被继承的。
Serializable:是可序列化的接口,凡是实现此接口的对象都可以通过网络或本地流进行数据的传输。
Comparable:凡是实现此接口的类,其对象都可以比较大小。
CharSequence:实现字符序列的接口。

  1. 内部声明的属性

jdk8中:
private final char[] value;

存储字符串数据的容器。
final:指明此value数组一旦初始化,其地址就不可变。

jdk9及之后:
private final byte[] value;
替换成byte是为了节省内存空间。

  1. 字符串常量的存储位置

字符串常量都存储在字符串常量池(StringTable)中
字符串常量池不允许存放两个相同的字符串常量
字符串常量池在不同的jdk版本中,存放位置不同
jdk7之前存放在方法区
jdk7及之后存放在堆空间

  1. String的不可变性的理解
    (1)当字符串变量重新赋值时,需要重新指定一个字符串常量的位置进行赋值,不能在原有的地方修改。
    (2)当对现有的字符串进行拼接操作时,需要重新开辟空间保存拼接之后的字符串,不能在原有的地方修改。
    (3)当调用字符串的replace()方法替换某个现有的字符时,仍需要重新开辟空间保存新的字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package com.learning01.string;

import org.junit.Test;

/**
* @author yuxiaohong
* @package com.learning01.string
* @date 2023/9/12 9:57
* @description
*/
public class StringDemo {
@Test
public void test1() {
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true
}

@Test
public void test2() {
String s1 = "hello";
String s2 = "hello";
s2 = "h1";
System.out.println(s1); // hello
System.out.println(s2); // h1
}

@Test
public void test3() {
String s1 = "hello";
String s2 = "hello";
String s3 = s2.replace('l', 'w');
System.out.println(s1); // hello
System.out.println(s2); // hello
System.out.println(s3); // hewwo
}
}

5. String实例化的两种方式

String s1 = "hello";
String s2 = new String("hello");

其中第二种方式创建了几个对象? 两个
一个是堆空间中new的对象。另外一个是在字符串常量池中生成的字面量。

6. String的连接操作
情况1:常量 + 常量:结果仍然存储在字符串常量池中。
情况2:常量 + 变量 或 变量 + 变量:会通过new的方式创建新的字符串,返回堆空间中字符串对象的地址。
情况3:调用intern()方法,返回的是字符串常量池中字面量的地址。
情况4:concat():不管是常量调用此方法,还是变量调用此方法,同样不管参数是常量还是变量,总之,调用完之后都返回一个新new的对象。

``` java
package com.learning01.string;

import org.junit.Test;

/**
* @author yuxiaohong
* @package com.learning01.string
* @date 2023/9/14 9:34
* @description
*/
public class StringDemo1 {
@Test
public void test() {
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
String s4 = new String("hello");

System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s3 == s4); // false
}

@Test
public void test1() {
String s1 = "hello";
String s2 = "world";
String s3 = "helloworld";
String s4 = "hello" + "world";

// 通过字节码文件发现调用了StringBuilder的toString()方法--->new String(),所以和上面的字符串不是同一个地址
String s5 = s1 + "world";
String s6 = "hello" + s2;
String s7 = s1 + s2;

System.out.println(s3 == s4); // true
System.out.println(s3 == s5); // false
System.out.println(s3 == s6); // false
System.out.println(s3 == s7); // false
System.out.println(s5 == s6); // false
System.out.println(s3 == s7); // false

// intern():返回的是字符串常量池中字面量的地址
String s8 = s5.intern();
System.out.println(s3 == s8); // true
}

@Test
public void test2() {
final String s1 = "hello";
String s2 = "world";
String s3 = "helloworld";
String s4 = "hello" + "world";

// 通过字节码文件发现调用了StringBuilder的toString()方法--->new String(),所以和上面的字符串不是同一个地址
String s5 = s1 + "world";
String s6 = "hello" + s2;
String s7 = s1 + s2;

System.out.println(s3 == s5); // true,因为加了final之后就变成常量了,所以不会再去new新的对象了
}

@Test
public void test3() {
String s1 = "hello";
String s2 = "world";
String s3 = s1.concat(s2);
String s4 = "hello".concat("world");
String s5 = s1.concat("world");

System.out.println(s3 == s4); // false
System.out.println(s3 == s5); // false
System.out.println(s4 == s5); // false
}
}

  1. String的构造器

    public String():初始化新创建的String对象,以使其表示空字符序列

    public String(String original):初始化一个新创建’String’对象,使其表示一个与参数相同的字符序列;换句话说

    public String(char[] value):通过当前参数中的字符数组来构造新的String

    public String(char[] value, int offset, int count):通过字符数组的一部分来构造新的String

    public String(byte[] bytes):通过使用平台的默认字符集解码当前参数中的字节数组来构造新的String

    public String(byty[] bytes, String charsetName):通过使用指定的字符集解码当前参数中的字节数组来构造新的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
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.learning01.string;

import org.junit.Test;

import java.io.UnsupportedEncodingException;

/**
* @author yuxiaohong
* @package com.learning01.string
* @date 2023/9/18 9:30
* @description
*/
public class StringMethodTest {
@Test
public void test1() {
String s1 = new String();
String s2 = new String("");

String s3 = new String(new char[]{'a', 'b', 'c'});
System.out.println(s3);
}

// String与char []进行转换
@Test
public void test2() {
String str = "hello";
// String ---> char[],调用String的toCharArray()
char[] charArray = str.toCharArray();
System.out.println(charArray);

// char[] ---> String,调用String的构造器
String str1 = new String((charArray));
System.out.println(str1);
}

// String与byte[]之间的转换
/*
* 在UTF-8中,一个汉字占3个字节,一个字母占1个字节
* 在GBK中,一个汉字占2个字节,一个字母占1个字节
*
* utf-8或gbk都向下兼容了ascii码
*
* 编码与解码:
*编码:String --->字节或字节数组
*解码:字节或字节数组 ---> String
* 要求:解码时使用的字符集必须要和编码时使用的字符集保持一致,不一致就会导致乱码
* */
@Test
public void test3() throws UnsupportedEncodingException {
String str = new String("hello");

// String ---> byte[]
byte[] bytes = str.getBytes(); // 使用默认的字符集
for (int i = 0; i < bytes.length; i++) {
System.out.println(bytes[i]);
}
// getBytes(String charsetName)使用指定的字符集
// 会报异常
byte[] arr1 = str.getBytes("gbk");
for (int i = 0; i < arr1.length; i++) {
System.out.println(arr1[i]);
}
// byte[] ---> String
String s = new String(arr1);
System.out.println(s);
}


}

  1. String的常用方法

boolean isEmpaty():字符串是否为空
int length():返回字符串的长度
String concat(xx):拼接
boolean equals(Object obj):比较字符串是否相等,区分大小写
boolean equalsIgnoreCase(Object obj):比较字符串是否相等,不区分大小写
int compareTo(String other):比较字符串的大小,区分大小写,按照Unicode编码值比较大小
int compareToIgnore(String other):比较字符串的大小,不区分大小写
String toLowerCase():将字符串中的大写字母转换为小写字母
String toUpperCase():将字符串中的小写字母转换为大写字母
String trim():去掉字符串前后的空白符
public String intern():结果在常量池中共享

boolean contains(xx):是否包含xx
int indexOf(xx):从前往后找当前字符串当中的xx,如果有,则返回第一次出现的下标,如果没有,则返回-1
int indexOf(String str, int fromIndex):返回指定字符串在此字符串中第一次出现处的下标,从指定索引开始
int lastIndexOf(xx):从后往前找当前字符串当中的xx,如果有,则返回最后一次出现的下标,如果没有,则返回-1
int lastIndexOf(String str, int fromIndex):返回指定字符串在此字符串中最后一次出现的下标,如果没有,则返回-1

String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取,一直取到最后
String substring(int beginIndex, int endIndex):返回一个新的字符串,从此字符串的beginIndex开始截取,一直到endIndex(不包括)

char charAt(index):返回[index]位置的字符
char[] toCharArray():将此字符串转换为一个新的字符数组并返回
static String valueOf(char[] data):返回指定数组中表示给字符序列的String
static String valueOf(char[] data, int offset, int count):返回指定数组中表示该字符序列的String
static String copyValueOf(char[] data):返回指定数组中表示该字符序列的String
static String copyValueOf(char[] data, int offset, int count):返回指定数组中表示该字符序列的String

boolean startsWith(xx):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子串是否以指定的前缀开始
boolean endWith(xx):测试此字符串是否以指定的后缀结束

String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用newChar替换此字符串中所有的oldChar得到的,不支持正则
String replace(CharSequence target, CharSequence replacement):使用指定的字面值序列替代此字符串所有匹配字面值目标序列的子字符串
String replace(String regex, String replacement):使用给定的replacement替换此字符串中所有匹配给定的正则表达式的子字符串
String replaceFirst(String regex, String replacement):使用给定的replacement替换此字符串中所有匹配给定的正则表达式的第一个子字符串

StringBuffer和StringBuilder

  1. 这三个类有什么区别?

String:不可变的字符序列;
StringBuffer:可变的字符序列;JDK1.0声明,线程安全的,效率低;
StringBuilder:可变的字符序列;JDK5.0声明,线程不安全,效率高;

  1. StringBuffer和StringBuilder的可变性分析

针对于String:
String s1 = new String(); //char[] value = new char[0];
String s2 = new String(“abc”); //char[] value = new char[3]{‘a’,’b’,’c’};

针对于StringBuilder:
内部的属性有:
char[] value; //用来存储字符序列
int count; //实际存储的字符的个数

StringBuilder sBuilder = new StringBuilder(); //char[] value = new Char[16];
StringBuilder sBuilder = new StringBuilder(“abc”); //char[] value = new Char[16+”abc”.length];

不断添加新的元素,一旦count超过value.length时,就需要扩容,默认扩容为原有容量的2倍+2,并将原有value数组中的元素复制到新的数组当中。

  1. 源码启示

如果开发中需要频繁的针对于字符串进行增删改查操作,建议使用StringBuffer或StringBuilder。

如果开发中,不涉及到线程安全问题,建议使用StringBuilder替换StringBuffer。

如果开发中大体确定要操作的字符的个数,建议使用带int capacity参数的构造器,可以避免底层多次扩容操作,性能更高。

  1. StringBuffer和StringBuilder中常用的方法

StringBuffer append(xx):提供了很多的append()方法,用于进行字符串追加的方式拼接
StringBuffer delete(int start, int end):删除[start,end)之间的字符
StringBuffer deleteCharAt(int index):删除[index]位置字符
StringBuffer replace(int start, int end, String str):替换[start,end)范围的字符序列
void setCharAt(int index, char c):替换[index]位置字符
char charAt(int index):查找指定index位置上的字符
StringBuffer insert(int index, xx):在[index]位置上插入xx
int length():返回存储字符数据的长度
StringBuffer reverse():反转

  1. 对比三者的执行效率

StringBuilder > StringBuffer > String

日期时间的API使用

JDK8之前的API

  1. System类的currentTimeMillis()

获取当前时间对应的毫秒数,long类型,时间戳
当前时间与1970年1月1日0时0分0秒之间的毫秒数
常用来计算时间差

  1. 两个Date类
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
package com.learning03.date.before8;

import org.junit.Test;

import java.util.Date;

/**
* @author yuxiaohong
* @package com.learning03.date.before8
* @date 2023/9/19 9:29
* @description
*/
public class DateTimeTest {
@Test
public void test() {
Date date1 = new Date(); // 创建一个基于当前系统时间的Date的实例
System.out.println(date1.toString());

long time = date1.getTime(); // 获取当前时间的时间戳
System.out.println("对应的毫秒数为:" + time);

Date date2 = new Date(1675087138561L); // 创建一个基于指定时间戳的Date的对象
System.out.println(date2.toString()); // 可以打印出年月日时分秒
}

@Test
public void test1() {
// 因为在同一个类中已经使用了java.util.Date,所以这里需要写全类名
java.sql.Date date = new java.sql.Date(1675087138561L);
System.out.println(date.toString()); // 只能打印出年月日
}
}

  1. SimpleDateFormat类

java.text.SimpleDateFormat类是一个不与语言环境有关的方式来格式化和解析日期的具体类。

格式化:将日期转换成指定格式的字符串
解析:将字符串转换为一个日期

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
@Test
public void test2() throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat();
// 格式化:日期 ---> 字符串
Date date = new Date();
String format = sdf.format(date);
System.out.println(format);

// 解析:字符串 ---> 日期
Date parse = sdf.parse("2023/9/19 09:53");
System.out.println(parse);
}

@Test
public void test3() throws ParseException {
// 以指定格式格式化日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyyy.MMMMM.dd GGG hh:mm aaa");
Date date = new Date();
String format = sdf.format(date);
System.out.println(format);

// 解析
Date parse = sdf.parse("02023.九月.19 公元 10:01 上午");
System.out.println(parse);
}
  1. java.util.Calendar(日历)

Date类中大部分的API被废弃了,替换为Calendar
Calendar是一个抽象类,主要用于完成日期字段之间相互操作的功能
获取Calendar实例的方法:由于Calendar是一个抽象类,所以需要通过Calendar的静态方法getInstance()即可获取

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
@Test
public void test4() {
Calendar instance = Calendar.getInstance();
System.out.println(instance.getClass());

// get(int field)
System.out.println(instance.get(Calendar.DAY_OF_MONTH)); // 19
System.out.println(instance.get(Calendar.DAY_OF_WEEK)); // 3

// set(int field, xx)
instance.set(Calendar.DAY_OF_MONTH, 23);
System.out.println(instance.get(Calendar.DAY_OF_MONTH)); // 23

// add(int field, xx)
instance.add(Calendar.DAY_OF_MONTH, 3); // 加三天,可以为负数
System.out.println(instance.get(Calendar.DAY_OF_MONTH)); // 26

// getTime():Calendar ---> Date
Date date = instance.getTime();
System.out.println(date);

// setTime():使用指定的Date重置为Calender
Date date1 = new Date();
instance.setTime(date1);
System.out.println(instance.get(Calendar.DAY_OF_MONTH));
}

JDK8新增的日期API

可变性:像日期时间这样的类应该是不可变的
偏移性:Date中的年份是从1900年开始的,而月份都从0开始
格式化:格式化只对Date有用,Calendar则不行
此外,它们的线程都是不安全的;不能处理闰秒等

本地日期时间LocalDate、LocalTime、LocalDateTime

实例化:now()/of()
方法:get、with、plus、minus等等

瞬时:Instant

实例化:now()/ofEpochMilli()
方法:toEpochMilli()

DateTimeFormatter —> 类似于SimpleDateFormat
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
package com.learning03.date.jdk8;

import org.junit.Test;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;

/**
* @author yuxiaohong
* @package com.learning03.date.jdk8
* @date 2023/9/21 9:53
* @description
*/
public class DateTimeTest {
// jdk8中的api,LocalDate, LocalTime, LocalDateTime
@Test
public void test1() {
// now():获取当前日期和时间对应的实例
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDate); // 2023-09-21
System.out.println(localTime); // 10:26:25.292232800
System.out.println(localDateTime); // 2023-09-21T10:26:25.292232800

// of():获取指定日期时间对应的实例
LocalDate localDate1 = LocalDate.of(2021, 5, 3);
LocalDateTime localDateTime1 = LocalDateTime.of(2022, 12, 5, 11, 23, 45);
System.out.println(localDate1); // 2021-05-03
System.out.println(localDateTime1); // 2022-12-05T11:23:45

// get相关的操作
LocalDate localDate2 = LocalDate.now();
System.out.println(localDate2.getDayOfMonth()); // 21

LocalDate localDate3 = localDate2.withDayOfMonth(15);
// 体现了新日期api的不可变性,更改日期之后会返回一个新的对象,之前的日期本身不会更改
System.out.println(localDate2); // 2023-09-21
System.out.println(localDate3); // 2023-09-15

}

// Instant()
@Test
public void test2() {
Instant now = Instant.now();
System.out.println(now); // 2023-09-21T02:39:50.395129800Z得到的是格林尼治标准时间,与北京相差八小时
// 通过时区偏移量得到正确的本地时间
OffsetDateTime offsetDateTime = now.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime); // 2023-09-21T10:41:28.807036+08:00

Instant instant = Instant.ofEpochMilli(24123123312L);
System.out.println(instant);
}

// DateTimeFormatter的使用
@Test
public void test3() {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
LocalDateTime localDateTime = LocalDateTime.now();

// 格式化:日期时间 --> 字符串
String format = dateTimeFormatter.format(localDateTime);
System.out.println(format);

// 解析:字符串 --> 日期时间
TemporalAccessor parse = dateTimeFormatter.parse("2023-09-21 10:49:44");
System.out.println(parse);
}
}

使用Comparable接口实现自然排序

实现对象的排序,可以考虑两种方法:自然排序、定制排序

Product类,里面必须实现Comparable接口并且实现对应的抽象方法

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
package com.learning04.compare;

public class Product implements Comparable {
private String name;
private double price;

/*当前的类需要实现Comparable中的抽象方法
在此方法中指明如何判断当前类对象的大小
比如按照价格的高低进行排序
*/
@Override
public int compareTo(Object o) {
if (o == this) {
return 0;
}
if (o instanceof Product) {
Product p = (Product) o;
return Double.compare(this.price, p.price);
}
// 手动抛出异常类的对象
throw new RuntimeException("类型不匹配");
}

public Product(String name, double price) {
this.name = name;
this.price = price;
}

public Product() {
}

public String getName() {
return name;
}

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

public double getPrice() {
return price;
}

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

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

方式一:采用实现Comparable接口的方式

实现步骤:

  1. 具体的类A实现Comparable接口。
  2. 重写Comparable接口中的compareTo(Object obj)方法,在此方法中指明比较类A的对象的大小的标准。
  3. 创建类A的多个实例,进行大小的比较、排序。
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
package com.learning04.compare.comparable;

import com.learning04.compare.Product;
import org.junit.Test;

import java.util.Arrays;

public class ComparableTest {
@Test
public void Test1() {
String[] arr = new String[]{"Tom", "Jerry", "Tony", "Rose", "Jack", "Lucy"};
// 默认从小到大排序
Arrays.sort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}

@Test
public void Test2() {
Product[] arr = new Product[5];
arr[0] = new Product("HuaWeiMate60Pro", 6999);
arr[1] = new Product("Xiaomi13Pro", 4999);
arr[2] = new Product("VivoX90", 5999);
arr[3] = new Product("iPhone14ProMax", 9999);
arr[4] = new Product("HonorMagic4", 6299);

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

}
}

方式二:实现Comparator接口的方式

实现步骤:

  1. 创建了一个实现了Comparator接口的实现类。
  2. 实现类要求重写Comparator接口中的抽象方法compare(Object o1, Object o2),在此方法中指明要比较大小的对象的大小关系。
  3. 创建此实现类的对象,并将此对象传入到Arrays.sort()的参数位置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package com.learning04.compare.comparator;

import com.learning04.compare.Product;
import org.junit.Test;

import java.nio.DoubleBuffer;
import java.util.Arrays;
import java.util.Comparator;

/**
* @author yuxiaohong
* @package com.learning04.compare.comparator
* @date 2023/9/25 9:40
* @description
*/
public class ComparatorTest {
@Test
public void test1() {
Product[] arr = new Product[5];
arr[0] = new Product("HuaWeiMate60Pro", 6999);
arr[1] = new Product("Xiaomi13Pro", 4999);
arr[2] = new Product("VivoX90", 5999);
arr[3] = new Product("IPhone14ProMax", 9999);
arr[4] = new Product("HonorMagic4", 6299);

// 创建一个实现了comparator接口的实现类对象
Comparator comparator = new Comparator() {
// 如何判断两个对象的大小,其标准就是此方法的方法体要编写的逻辑
// 如:按照价格从低到高进行排序
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Product && o2 instanceof Product) {
Product p1 = (Product) o1;
Product p2 = (Product) o2;

return Double.compare(p1.getPrice(), p2.getPrice());
}
throw new RuntimeException("类型不匹配");
}
};
// 将数组arr按照comparator进行排序
Arrays.sort(arr, comparator);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}

// 按名字进行排序
Comparator comparator1 = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Product && o2 instanceof Product) {
Product p1 = (Product) o1;
Product p2 = (Product) o2;

// 实现降序排列只需要在return后加个负号即可
return p1.getName().compareTo(p2.getName());
}
throw new RuntimeException("类型不匹配");
}
};

Arrays.sort(arr, comparator1);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}

// 将该数组降序排列
@Test
public void test2() {
String[] arr = new String[]{"Tom", "Jerry", "Tony", "Rose", "Jack", "Lucy"};
Arrays.sort(arr, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
if (o1 instanceof String && o2 instanceof String) {
String s1 = (String) o1;
String s2 = (String) o2;
return -s1.compareTo(s2);
}
throw new RuntimeException("类型不匹配");
}
});

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

对比两种方式:

角度一:
自然排序:单一的、唯一的;
定制排序:灵活的、多样的;

角度二:
自然排序:一劳永逸的;
定制排序:临时的;

角度三:
自然排序:对应的接口是Comparable,对应的方法是compareTo(Object obj);
定制排序:对应的接口是Comparator,对应的方法是compare(Object obj1, Object obj2);

其他常用类的使用

  1. System类

    属性:out、in、err
    方法:currentTimeMillis()、gc()、exit(status)、getProperty(String property)

  2. Runtime类

    对应着Java进程的内存使用的运行时环境,是单例的。

  3. Math类

    凡是与数学有关的操作,都可以找到相关的方法。
    ceil、floor、round、pow、sqrt、random…

  4. BigInteger类和BigDecimal类

    BigInteger可以表示不可变的任意精度的整数,主要用于表示long类型都存不下的长度的数值。
    BigDecimal可以表示任意精度的浮点数,要求的数字精度比较高,常用来科学计算或工程计算。

集合框架

数组的特点、弊端

  1. 内存层面需要针对于多个数据进行存储。此时,可以考虑的容器有:数组、集合类

  2. 数组存储多个数据方面的特点:

    数组一旦初始化,其长度就是确定的。
    数组中的多个元素是紧密排列的、有序的、可重复的。
    数组一旦初始化完成,其元素类型就是确定的。不是此类型的元素,就不能添加到此数组中。
    数组元素的类型既可以是基本数据类型,也可以是引用数据类型。

  3. 数组存储多个数据方面的弊端:

    数组一旦初始化,长度就不可变了。
    数组中存储数据特点的单一性。对于无序的、不可重复的场景的多个数据就无能为力了。
    数组中可用的方法、属性都极少。具体的需求都需要自己组织相关的代码逻辑。
    针对于数组中元素的删除、插入操作,性能较差。

Java集合框架体系

  1. java.util.Collection:存储一个一个的数据
    子接口:
    List:存储有序的、可重复的数据(动态数组)
    ArrayList(主要实现类)、LinkedList、Vector
    Set:存储无序的、不可重复的数据
    HashSet、LinkedHashSet、TreeSet

  2. java.util.Map:存储一对一对的数据(key-value键值对)
    HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

Collection接口

  1. 添加
    add(Object obj):添加元素对象到当前集合当中;如果添加的是一个集合的话,那么不管添加的集合中有多少个元素,添加到当前集合当中之后,算做一个整体,总长度只加一。
    addAll(Collection other):添加other集合中的所有元素对象到当前集合中。
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
package com.learning01.collection;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Collection;

/**
* @author yuxiaohong
* @package com.learning01.collection
* @date 2023/9/27 18:56
* @description 测试Collection中方法的使用
*/
public class CollectionTest {
@Test
public void test1() {
Collection coll = new ArrayList();
coll.add("AA");
coll.add(123); // 自动装箱
coll.add("扫黄大队");
coll.add(new Object());
coll.add(new Person("Tom", 33));
System.out.println(coll);


Collection coll1 = new ArrayList();
coll1.addAll(coll);
System.out.println(coll1);
System.out.println(coll.size());

coll1.add(coll);
System.out.println(coll1);
System.out.println(coll1.size());
}
}

运行截图:

int size():获取当前集合中实际存储的元素的个数
boolean isEmpty():判断当前集合是否为空集合
boolean contains(Object obj):判断当前集合是否存在一个与obj对象equals返回true的元素
boolean containsAll(Collection coll):判断coll集合中的元素是否都在当前集合中存在
boolean equals(Object obj):判断当前集合与obj是否相等

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test2() {
Collection collection = new ArrayList();
collection.add("Aa");
// isEmpty()
System.out.println(collection.isEmpty());
// contains()
System.out.println(collection.contains("Aa"));
// containsAll(Collection other),判断集合other中的元素是否在当前集合都存在
Collection collection1 = new ArrayList();
collection1.add("cc");
System.out.println(collection.containsAll(collection1));
}
  1. 删除

    void clear():清空集合
    boolean remove(Object obj):从当前集合当中删除第一个找到的与obj对象equals返回true的元素
    boolean removeAll(Collection coll):从当前集合中删除两个集合中不同的元素,仅保留两个集合相同的元素

  2. 集合与数组的相互转换

    集合 —> 数组:toArray()
    数组 —> 集合:Arrays.asList(T…)

    1
    2
    3
    4
    5
    6
    @Test
    public void test3() {
    String arr[] = new String[]{"AA", "BB", "CC"};
    List<String> list = Arrays.asList(arr);
    System.out.println(list); //[AA,BB,CC]
    }
  3. 向Collection中添加元素的要求

    要求元素所属的类一定要重写equals()
    因为Collection中的相关方法(contains、remove等)在使用时,要调用元素所在类的equals()

  4. Iterator迭代器

    (1). 迭代器的作用:用来遍历集合元素
    (2). 如何获取迭代器对象:Iterator iterator = collection.iterator();
    (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
    @Test
    public void test1() {
    Collection collection = new ArrayList();
    collection.add("AA");
    collection.add("BB");
    collection.add("CC");

    // 获取迭代器对象
    Iterator iterator = collection.iterator();
    // 方式一:
    System.out.println(iterator.next());
    System.out.println(iterator.next());
    System.out.println(iterator.next());
    // 如果超出了集合中元素的个数,会报异常
    // System.out.println(iterator.next());

    // 方式二:采用循环
    for (int i = 0; i < collection.size(); i++) {
    System.out.println(iterator.next());
    }

    // 方式三:hasNext()
    while (iterator.hasNext()) {
    System.out.println(iterator.next());
    }
    }
  5. 增强for循环(foreach)jdk5.0新特性

    (1). 作用:用来遍历数组、集合
    (2). 格式:for(要遍历的集合或数组元素的类型 临时变量名 : 要遍历的集合或数组名){针对临时变量的一些操作}
    (3). 说明:针对于集合来讲,foreach底层仍然使用的是迭代器;该循环执行过程中,是将集合或数组的元素依次赋值给临时变量,循环体内对临时变量进行修改,不会对原有数组或集合元素造成影响。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Test
    public void test1() {
    Collection collection = new ArrayList();
    collection.add("AA");
    collection.add("BB");
    collection.add("CC");

    for (Object obj : collection) {
    System.out.println(obj);
    }
    }

    @Test
    public void test2() {
    int[] arr = new int[]{1, 2, 3};
    for (int a : arr) {
    System.out.println(a);
    }
    }

Collection的子接口List

  1. List接口中存储数据的特点

    用于存储有序的、可以重复的数据。 —> 使用List替代数组,“动态”数组

  2. List中的常用方法

    (1). Collection中声明的15个方法
    (2). 因为List是有序的,进而增加了一些索引操作的方法
    插入元素:
    void add(int index, Object ele):在index位置插入ele元素
    boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
    获取元素:
    Object get(int index):获取指定index位置的元素
    List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
    获取元素索引:
    int indexOf(Object obj):返回obj在集合中首次出现的位置
    int lastIndexOf(Object obj):返回obj在当前集合最后一次出现的位置
    删除和替换元素:
    Object remove(int index):移除指定index位置的元素,并返回此元素
    Object set(int index, Object ele):设置指定index位置的元素为ele

  3. List及其实现类的特点
    java.util.Collection:存储一个一个的数据
    子接口:List:存储有序的、可重复的数据(“动态”数组)
    ArrayList:List的主要实现类;线程不安全的,效率高;底层使用Object[]数组存储;在添加数据和查找数据时,效率较高,在删除和插入数据时,效率较低
    LinkedList:底层使用双向链表的方式进行存储;在删除和插入数据时,效率较高,在添加数据和查找数据时,效率较低;在对集合中的数据频繁进行删除、插入操作时,建议使用此类
    Vector:List的古老实现类;线程安全的,效率低,底层使用Object[]数组存储。

  4. 练习:
    题目要求:案例:键盘录入学生信息,保存到集合List当中。

     1.定义学生类,属性姓名、年龄,提供必要的getter、setter方法,构造器,toString(),equals()方法。
     2.使用ArrayList集合,保存录入多个学生对象。
     3.循环录入方式:1:继续录入 2:结束录入。
     4.录入结束后,用foreach遍历集合。
    

    代码1: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
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
package com.learning03.list.exer1;

import java.util.Objects;

/**
* @author yuxiaohong
* @package com.learning03.list.exer1
* @date 2023/10/6 14:55
* @description
*/
public class Student {
private String name;
private int age;

public Student() {
}

public Student(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 boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}

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

代码2:实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.learning03.list.exer1;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Scanner;

/**
* @author yuxiaohong
* @package com.learning03.list.exer1
* @date 2023/10/6 14:58
* @description
*/
public class StudentTest {
public static void main(String[] args) {
List list = new ArrayList();
System.out.println("请输入数字以进行操作:1.继续录入 2.结束录入");
Scanner scanner = new Scanner(System.in);
while (scanner.nextInt() == 1) {
System.out.println("继续录入...");
System.out.println("请输入学生姓名:");
String name = scanner.next();
System.out.println("请输入学生年龄:");
int age = scanner.nextInt();
list.add(new Student(name, age));
System.out.println("请输入数字以进行操作:1.继续录入 2.结束录入");
}
System.out.println("结束录入。");
System.out.println("开始遍历数据...");
for (Object o : list) {
System.out.println(o);
}
scanner.close();
}
}

Collection的子接口Set

  1. Set及其实现类特点
    java.util.Collection:存储一个一个的数据
    子接口:Set:存储无序的、不可重复的数据
    HashSet:主要实现类;底层使用的是HashMap,即使用数组+单向链表+红黑树的结构进行存储。
    LinkedHashSet:HashSet的子类,在HashSet的基础上,又添加了一组双向链表,用于记录添加元素的先后顺序。就可以根据添加元素的顺序实现遍历操作。便于频繁的查询操作。
    TreeSet:底层使用红黑树存储。可以按照添加的元素指定的属性的大小顺序进行遍历。

  2. 开发中的使用频率及场景:
    较List、Map来说,Set使用的频率比较少。
    用来过滤重复数据

  3. Set中常用方法:Collection中声明的15个抽象方法,没有新增的方法。

  4. Set中无序性、不可重复性的理解(以HashSet及其子类为例说明)

    无序性:不等于随机性;与添加的元素位置有关,不像ArrayList一样是一次紧密排列的。 -
    不可重复性:添加到Set中的元素是不能相同的。
    比较的标准:需要判断hashCode()得到的哈希值以及equals()得到的boolean型的结果。
    哈希值相同且equals()返回true,则认为元素是相同的。

  5. 添加到HashSet/LinkedHashSet中元素的要求:
    要求元素所在的类要重写两个方法:equals()和hashCode()
    同时,要求equals()和hashCode()要保持一致性,只需要在IDEA中自动生成就可以保证一致性。

  6. TreeSet的使用:

    1. 底层的数据结构:红黑树
    2. 添加数据后的特点:可以按照添加的元素的指定的属性的大小顺序进行遍历。
    3. 向TreeSet中添加元素的要求:
      1. 添加到TreeSet中的元素必须是同一个类型的对象,否则会报错。
    4. 判断数据是否相同的标准:
      1. 不再是考虑hashCode()和equals()方法了,也就意味着添加到TreeSet中的元素所在的类不需要重写这两个方法了。
      2. 比较元素大小的或比较元素是否相等的标准就是考虑自然排序或定制排序中compareTo()和compare()的返回值;如果返回值为0,则认为两个对象是相等的。

SetTest

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
    package com.learning04.set;

import org.junit.Test;

import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;

/**
* @author yuxiaohong
* @package com.learning04.set
* @date 2023/10/6 15:57
* @description
*/
public class SetTest {
@Test
public void test1() {
Set set = new HashSet();
set.add("aa");
set.add("bb");
set.add("cc");
set.add("dd");
set.add(new Person("小红", 23));

Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}

System.out.println(set.contains(new Person("小红", 23)));
}

@Test
public void Test2() {
Set set = new LinkedHashSet();
set.add("aa");
set.add("bb");
set.add("cc");
set.add("dd");
set.add(new Person("小红", 23));

Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}

TreeSetTest

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
package com.learning04.set;

import org.junit.Test;

import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

public class TreeSetTest {
@Test
public void Test1() {
// 自然排序
TreeSet treeSet = new TreeSet();
treeSet.add("AA");
treeSet.add("BB");
treeSet.add("CC");
// 会报错
// treeSet.add(123);

Iterator iterator = treeSet.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}

@Test
public void Test2() {
// 自然排序
TreeSet treeSet = new TreeSet();
treeSet.add(new User("小杰", 23));
treeSet.add(new User("小红", 23));
treeSet.add(new User("小民", 23));

Iterator iterator = treeSet.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}

@Test
public void Test3() {
// 定制排序
Comparator comparator = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof User && o2 instanceof User) {
User user1 = (User) o1;
User user2 = (User) o2;

int value = user1.getName().compareTo(user2.getName());
if (value != 0) {
return value;
}
return -(user1.getAge() - user2.getAge());
}
throw new RuntimeException("类型不匹配");
}
};
TreeSet treeSet = new TreeSet(comparator);
treeSet.add(new User("小杰", 23));
treeSet.add(new User("小红", 22));

for (Object o : treeSet) {
System.out.println(o);
}
}
}

Map接口

  1. java.util.Map:存储一对一对的数据(key-value键值对)
    HashMap:主要实现类;线程不安全的;效率高;可以添加null的key和value值;底层使用了数组+单向链表+红黑树结构存储
    LinkedHashMap:是HashMap的子类;在HashMap数据结构基础上增加了一对双向链表,用于记录添加的元素的先后顺序;
    进而在遍历元素时就可以按照添加的顺序显示,对于频繁的遍历操作,建议使用此类。
    TreeMap:底层使用红黑树存储;可以按照添加的元素的指定的属性的大小进行遍历,需要考虑使用自然排序或定制排序
    Hashtable:古老实现类;线程安全的;效率低;不可以添加null的key和value值;底层使用了数组+单向链表结构存储
    properties:存储的键值对都是String类型的,常用来处理属性文件

  2. HashMap的特点:
    HashMap中元素的key彼此之间是不可重复的、无序的。所有的key就构成一个Set集合;key所在的类要重写hashCode()和equals()
    HashMap中的所有的value彼此之间是可重复的、无序的。所有的value构成了一个Collection集合;value所在的类要重写equals()
    HashMap中的一个key-value,就构成了一个entry
    HashMap中的所有的entry是不可重复的、无序的。所有的entry构成了一个Set集合

  3. Map中的常用方法:

    添加、修改操作:
    Object put(Object kry, Object value):将指定的key-value添加到(修改)当前map对象当中。
    void putAll(Map m):将m中所有的key-value存放到当前map中。
    删除操作:
    Object remove(Object key):移除指定key的key-value对,并返回value。
    void clear():清空当前map中的所有数据。
    元素查询的操作:
    Object get(Object key):获取指定key对应的value。
    boolean containsKey(Object key):是否包含指定的key。
    boolean containsValue(Object value):是否包含指定的value。
    int size():返回map中key-value对个数。
    boolean isEmpty():判断当前map是否为空。
    boolean equals(Object obj):判断当前map和参数对象obj是否相等。
    元视图操作方法:
    Set keySet():返回所有key构成的set集合。
    Collection values():返回所有value构成的Collection集合。
    Set entrySet():返回所有key-value对构成的set集合。

  4. TreeMap的使用:

    底层使用红黑树进行存储,可以按照添加的key-value中的key元素的指定的属性大小顺序进行遍历,需要考虑使用自然排序、定制排序。
    要求:向TreeMap中添加的key必须是同一个类型的

  5. Hashtable与Properties的使用

    Properties是Hashtable的一个子类,key和value都是String类型的,常用来处理属性文件。

TreeMap测试类

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
package com.learning05.map;

import org.junit.Test;

import java.util.Comparator;
import java.util.TreeMap;

public class TreeMapTest {
@Test
public void Test1() {
TreeMap map = new TreeMap();
map.put("CC", 89);
map.put("BB", 77);
map.put("AA", 84);
map.put("DD", 62);

for (Object o : map.entrySet()) {
System.out.println(o);
}
}

@Test
public void Test2() {
TreeMap map = new TreeMap<>();
map.put(new User("小杰", 23), 63);
map.put(new User("小红", 22), 62);
map.put(new User("效民", 21), 61);

for (Object o : map.keySet()) {
System.out.println(o);
}
}

// 定制排序
@Test
public void Test3() {
Comparator comparator = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof User && o2 instanceof User) {
User u1 = (User) o1;
User u2 = (User) o2;

int value = u1.getName().compareTo(u2.getName());
if (value != 0) {
return value;
}
return u1.getAge() - u2.getAge();
}
throw new RuntimeException("类型不匹配");
}
};

TreeMap map = new TreeMap(comparator);
map.put(new User("小杰", 23), 63);
map.put(new User("小红", 22), 62);
map.put(new User("效民", 21), 61);

for (Object o : map.keySet()) {
System.out.println(o);
}
}
}

Properties测试类

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
package com.learning05.map;

import org.junit.Test;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

public class PropertiesTest {
@Test
public void Test1() throws IOException { //因为涉及到流的操作,为了确保流能关闭,建议使用try-catch方式
// 方式1:数据和代码耦合度高,如果修改的话,需要重新编译代码、打包发布,繁琐
// String name = "Tom";
// String password = "abc";

// 操作数据...


/* 方式2:将数据封装到具体的配置文件当中,在程序中读取配置文件的信息。
实现了数据和代码的解耦,省去了编译和打包的时间。*/

File file = new File("info.properties");
System.out.println(file.getAbsoluteFile());


FileInputStream fileInputStream = new FileInputStream(file);
Properties properties = new Properties();
properties.load(fileInputStream); //加载流中的文件中的数据

// 读取数据
String name = properties.getProperty("name");
String pwd = properties.getProperty("password");

System.out.println(name);
System.out.println(pwd);
}
}

Collections工具类

  1. Collections概述:
    Collections是一个操作Set、List、Map等集合的工具类。

  2. 常用方法:

    排序操作:
    reverse(List):反转List中元素的顺序
    shuffle(List):对List集合元素进行随机排序
    sort(List):根据元素的自然顺序对指定List集合元素按升序排序
    sort(List, Comparator):根据Comparator指定的顺序对List中集合元素进行排序
    swap(List list, int i, int j):将list集合当中i处元素和j处元素进行交换
    查找操作:
    Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
    Object max(Collection, Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素(程序认为排完序之后在最右侧的为最大值)
    Object min(Collection):根据元素的自然顺序,返回给定集合中的最小元素
    Object min(Collection, Comparator):根据Comparator指定的顺序,返回给定集合中的最小元素(程序认为排完序之后在最左侧的为最小值)
    int binarySearch(List list, T key):在List集合中查找某个元素的下标,但是List的元素必须是T或T的子类对象,而且必须是可比较大小的,即支持自然排序的。而且集合事先也必须是有序的,否则结果不确定。
    int binarySearch(List list, T key, Comparator c):在List集合中查找某个元素的下标,但是List的元素必须是T或T的子类对象,而且必须是可比较大小的,即支持自然排序的。而且集合事先按照比较器c进行排序过的,否则结果不确定。
    复制、替换:
    void copy(List dest, List src):将src中的内容复制到dest中
    boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换List对象的所有旧值
    提供了多个unmodifiableXxx()方法,该方法返回指定Xxx的不可修改的视图。
    添加:
    boolean addAll(Collection c, T… elements):将所有指定元素添加到指定的collection中
    同步:
    Collections类中提供了多个synchronizedXxx()方法,该方法可以使指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。

  3. 区分Collection和Collections

    Collection:用于存储一个一个元素的接口,又分为List和Set等子接口。
    Collections:用于操作集合框架当中的一个工具类,此时的集合框架包括了Set、List、Map。

Collections工具类测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package com.learning06.collections;

import org.junit.Test;

import java.util.*;

/**
* @author yuxiaohong
* @package com.learning06.collections
* @date 2023/10/23 11:00
* @description
*/
public class CollectionsTest {
/*
* 排序
* */
@Test
public void test1() {
List<Integer> list = Arrays.asList(45, 12, 33, 87, 56);
// reverse(),将集合中的内容进行翻转
Collections.reverse(list);
System.out.println(list);

// shuffle(),将集合中的内容随机排序
Collections.shuffle(list);
System.out.println(list);

// sort(),根据元素的自然排序对指定集合进行升序排序
Collections.sort(list);
System.out.println(list);

// 根据Comparator指定的顺序进行排序
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if (o1 instanceof Integer && o2 instanceof Integer) {
Integer i1 = (Integer) o1;
Integer i2 = (Integer) o2;
return -(i1.intValue() - i2.intValue());
}
throw new RuntimeException("类型不匹配");
}
});
System.out.println(list);

// swap():将集合中的i处和j处的元素互换
Collections.swap(list, 0, 1);
System.out.println(list);
}

/*
* 查找
* */
@Test
public void test2() {
List<Integer> list = Arrays.asList(45, 12, 33, 87, 56);
// max(),根据元素的自然顺序,返回给定集合当中的最大元素(排序之后最右边的元素)
Integer max = Collections.max(list);
System.out.println(max);
// max(),根据Comparator指定的顺序,返回给定集合当中的最大元素
// 实际上下方这个Comparator排序之后,最右方的元素是最小值
Integer max1 = Collections.max(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if (o1 instanceof Integer && o2 instanceof Integer) {
Integer i1 = (Integer) o1;
Integer i2 = (Integer) o2;
return -(i1.intValue() - i2.intValue());
}
throw new RuntimeException("类型不匹配");
}
});
System.out.println(max1);

// min(),返回集合中自然排序最小的元素
Integer min = Collections.min(list);
System.out.println(min);

// min(),根据Comparator指定的顺序,返回最小值(集合中最左边的元素)

// binarySearch(List list,T key),在集合中查找某个元素的下标,但是List的元素必须是T或者T的子类
int i = Collections.binarySearch(list, 23);
System.out.println(i);
// binarySearch(List list, T key, Comparator)

// frequency(Collection collection, Object o),返回指定集合当中指定元素的出现次数
int frequency = Collections.frequency(list, 55);
System.out.println(frequency);

}

/*
* 复制、替换
* */
@Test
public void test3() {
List<Integer> list = Arrays.asList(45, 12, 33, 87, 56);
// 错误的写法,会报异常
// List dest = new ArrayList();
// 正确的写法
List<Object> dest = Arrays.asList(new Object[list.size()]);
// copy(),将一个集合中的内容复制到另一个集合当中
Collections.copy(dest, list);
System.out.println(dest);

// replaceAll(),使用新值替换list中的所有旧值
}

@Test
public void test4() {
// 提供了多个unmodifiableXxx()方法,该方法返回指定Xxx不可修改的视图
// list可以写入数据
List list = new ArrayList();
list.add(34);
list.add(22);
list.add(12);

// 返回的集合只能读不能写
List list1 = Collections.unmodifiableList(list);
list1.add(38);
}

/*
* 添加、同步
* */
@Test
public void test5() {
// Collections.addAll(),将所有的指定元素添加到Collection中

// Collections类中提供了多个SynchronizedXxx()方法
List list = new ArrayList();
// 此时,返回的list1就是线程安全的
List list1 = Collections.synchronizedList(list);

}
}


泛型

  1. 什么是泛型:
    所谓泛型,就是允许在定义类、接口时通过一个“标识”表示类中某个“属性的类型”或者是某个方法的“返回值或参数类型”。这个类型参数将在使用时(继承或实现这个接口、创建对象或者调用方法时)确定(即传入实际的类型参数,也称为类型实参)。

  2. 在集合中使用泛型之前可能存在的问题
    问题1:类型不安全,因为add()的参数是Object类型,意味着任何类型的对象都可以添加成功。
    问题2:需要使用强转操作,繁琐。还有可能导致ClassCastException异常。

  3. 在集合、比较器中使用泛型(重点)

  4. 使用说明

    集合框架在声明接口和其实现类时,使用泛型(jdk5.0),在实例化集合对象时,如果没有使用泛型,则认为操作的是Object类型的数据/
    如果使用了泛型,则需要指明泛型的具体类型。一旦指明了泛型的具体类型,则在集合的相关方法中,凡是使用类的泛型的位置,都替换为具体的泛型类型。

CollectionMapTest泛型测试类

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

import org.junit.Test;

import java.util.*;

/**
* @author yuxiaohong
* @package com.learning01.use
* @date 2023/10/23 18:28
* @description
*/
public class CollectionMapTest {

// 体会使用泛型之前的场景
@Test
public void test1() {
// 体会使用泛型之前的场景
List arrayList = new ArrayList();
arrayList.add(123);
arrayList.add(13);
arrayList.add(12);
// 问题1:类型不安全,参数是Object类型,意味着任意的对象都可以放入该集合。
arrayList.add("AA");

// 问题2:需要强转操作,繁琐。还有可能导致ClassCastException异常。
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()) {
Integer i = (Integer) iterator.next();
int score = i;

System.out.println(score);
}
}

// 在集合中使用泛型的例子
@Test
public void test2() {
List<Integer> list = new ArrayList<Integer>();
// 编译报错,保证类型的一致性
// list.add("AA");

Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
// 因为添加的都是Integer类型,避免了强转操作
Integer i = iterator.next();
}
}

// 泛型在Map中使用的例子
@Test
public void test3() {
Map<String, Integer> map = new HashMap<String, Integer>();

// jdk7的新特性:类型推断,不需要在右方写类型
Map<String, Integer> map1 = new HashMap<>();

map1.put("tom", 33);
map1.put("Jerry", 63);
map1.put("Rose", 52);
// Set<Map.Entry<String, Integer>> entries = map1.entrySet();
// Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();

//jdk10的新特性
var entrySet = map1.entrySet();
var iterator = entrySet.iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> next = iterator.next();
String key = next.getKey();
Integer value = next.getValue();
System.out.println(key + "--->" + value);
}

}
}

泛型练习

题目要求:
1.定义一个Employee类。
该类包含:private成员变量name,age,birthday,其中birthday为MyDate类型。
并为每一个属性定义定义getter和setter方法

2.MyDate类包含:
private成员变量包含year,month,day;并为每一个属性定义getter和setter方法

3.创建该类的5个对象,并把这些对象放入TreeSet集合当中(需要使用泛型定义)

4.分别按照以下两种方式对集合中的元素进行排序,并遍历输出

使Employee实现Comparable接口,并按name排序
创建TreeSet时传入Comparator对象,按生日日期的先后排序

Employee类

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
package com.learning01.use.exer;

import java.util.Comparator;
import java.util.EnumMap;

/**
* @author yuxiaohong
* @package com.learning01.use.exer
* @date 2023/10/24 9:16
* @description
*/
public class Employee implements Comparable<Employee> {
private String name;
private int age;
private MyDate birthday;

public Employee() {
}

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


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 MyDate getBirthday() {
return birthday;
}

public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}

@Override
public int compareTo(Employee o) {
// 按照name从低到高进行排序
return this.name.compareTo(o.getName());
}

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

MyDate类

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
package com.learning01.use.exer;

/**
* @author yuxiaohong
* @package com.learning01.use.exer
* @date 2023/10/24 9:17
* @description
*/
public class MyDate {
private int year;
private int month;
private int day;

public MyDate() {
}

public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}

public int getYear() {
return year;
}

public void setYear(int year) {
this.year = year;
}

public int getMonth() {
return month;
}

public void setMonth(int month) {
this.month = month;
}

public int getDay() {
return day;
}

public void setDay(int day) {
this.day = day;
}

@Override
public String toString() {
return "MyDate{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
}

Employee测试类

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

import org.junit.Test;

import java.util.Comparator;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

/**
* @author yuxiaohong
* @package com.learning01.use.exer
* @date 2023/10/24 10:59
* @description
*/
public class EmployeeTest {
@Test
public void test1() {
Employee e1 = new Employee("小红", 23, new MyDate(2000, 2, 8));
Employee e2 = new Employee("小杰", 23, new MyDate(2000, 4, 30));
Employee e3 = new Employee("效民", 22, new MyDate(2001, 8, 22));
Employee e4 = new Employee("佳诚", 23, new MyDate(2000, 6, 20));
Employee e5 = new Employee("小菜", 22, new MyDate(2001, 3, 23));

Set<Employee> set = new TreeSet<>();

set.add(e1);
set.add(e2);
set.add(e3);
set.add(e4);
set.add(e5);
Iterator<Employee> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}

@Test
public void test2() {
Comparator<Employee> comparator = new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
int year = o1.getBirthday().getYear() - o2.getBirthday().getYear();
if (year != 0) {
return year;
}
int month = o1.getBirthday().getMonth() - o2.getBirthday().getMonth();
if (month != 0) {
return month;
}
int day = o1.getBirthday().getDay() - o2.getBirthday().getDay();
if (day != 0) {
return day;
}
return 0;
}
};
Set<Employee> set = new TreeSet<>(comparator);
Employee e1 = new Employee("小红", 23, new MyDate(2000, 2, 8));
Employee e2 = new Employee("小杰", 23, new MyDate(2000, 4, 30));
Employee e3 = new Employee("效民", 22, new MyDate(2001, 8, 22));
Employee e4 = new Employee("佳诚", 23, new MyDate(2000, 6, 20));
Employee e5 = new Employee("小菜", 22, new MyDate(2001, 3, 23));
set.add(e1);
set.add(e2);
set.add(e3);
set.add(e4);
set.add(e5);
Iterator<Employee> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}

自定义泛型

  1. 自定义泛型类\接口

    1. 格式:
      class A{

      }

      interface B<T1, T2>{

      }

    2. 使用说明:

      1. 我们在声明完自定义泛型类之后,可以在类的内部(比如:属性、方法、构造器中)使用类的泛型。
      2. 我们在创建自定义泛型类的对象时,可以指明泛型参数类型。一旦指明,内部凡是使用类的泛型参数的地方,都具体化为指定的泛型类型。
      3. 如果在创建自定义泛型类的对象时,没有指明泛型参数类型,那么泛型将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。
      4. 泛型的指定中必须使用引用数据类型,不能使用基本数据类型,此时只能用包装类进行替换。
      5. 除创建泛型类对象之外,子类继承泛型类、实现泛型类接口时,也可以确定泛型结构中的泛型参数。
      6. 如果我们在给泛型类对象提供子类时,子类也不确定泛型类的类型,则可以继续使用泛型参数。
      7. 我们还可以在现有的父类的泛型参数的基础上,新增泛型参数。
    3. 注意点:

      1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如<E1,E2,E3>。
      2. JDK7.0开始,泛型的简化操作:ArrayList flist = new ArrayyList();
      3. 如果泛型结构是一个接口或者抽象类,则不可创建泛型类的对象。
      4. 不能使用new E[]。但是可以E[] elements = (E[])new Object[capacity];
      5. 在类\接口上声明的泛型,在本类或者本接口中即代表某种类型,但不可以在静态方法中使用类的泛型。
      6. 异常类是不能带泛型的。
  2. 自定义泛型方法

    1. 问题:在泛型类的方法中,使用了类的泛型参数。那么此方法是泛型方法吗
    2. 格式:权限修饰符 返回值类型 方法名 (形参列表){ //通常在形参列表或返回值类型的位置会出现泛型参数T

    }
    3. 举例:
    public method(E e){}
    4. 说明:

    1. 声明泛型方法时,一定要添加泛型参数
    2. 泛型参数在方法调用时,指明其具体类型。
    3. 泛型方法可以根据需要声明为static。
    4. 泛型方法所属的类是否是一个泛型类,都可以。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.learning02.selfdefine;

/**
* @author yuxiaohong
* @package com.learning02.selfdefine
* @date 2023/10/31 9:30
* @description
*/
public class Order<T> {
// 声明了类的泛型参数之后,就可以在类的内部使用此泛型参数
T t;
int orderId;

public Order() {
}

public Order(T t, int orderId) {
this.t = t;
this.orderId = orderId;
}

public T getT() {
return t;
}

public void setT(T t) {
this.t = t;
}

public int getOrderId() {
return orderId;
}

public void setOrderId(int orderId) {
this.orderId = orderId;
}

@Override
public String
toString() {
return "Order{" +
"t=" + t +
", orderId=" + orderId +
'}';
}
}

泛型在继承上的体现

  1. 类SuperA是类A的父类,则G< SuperA >和G< A >的关系:两个是并列的类,并没有任何的子父类的关系。
    比如:ArrayList< Object > 和 ArrayList< String >没有子父类的关系。
  2. 类SuperA是类A的父类或接口,SuperA< G > 和 A< G >的关系是:有继承或实现的关系。
    比如:List< String > 和 ArrayList< 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
package com.learning03.more;

import org.junit.Test;

import java.io.ObjectStreamException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
* @author yuxiaohong
* @package com.learning03.more
* @date 2023/11/6 18:07
* @description
*/
public class GenericTest {
@Test
public void test1() {
Object obj = null;
String str = null;
obj = str; // 给予继承性的多态的使用


}


@Test
public void test2() {
ArrayList<Object> list1 = null;
ArrayList<String> list2 = null;
/*
不可以
list1 = list2;
*/
}

@Test
public void test4() {
List<String> list1 = null;
ArrayList<String> list2 = null;
list1 = list2;
}
}

通配符的使用

  1. 通配符:?
  2. 使用说明:举例 ArrayList G可以看作是G类型的父类,即可以将G的对象赋值给G<?>类型的引用(或变量)。
  3. 读写数据的特点:
    允许读取数据,读取值的类型是Object类型;不允许写入数据(特例:null)。
  4. 有限制条件的通配符:
    List<? extends A>:可以将List
    或List赋值给List<? extends A>,其中B类是A类的子类。
    List<? super A>:可以将List
    或List赋值给List<? super A>,其中B类是A类的父类。

数据结构与集合源码

数据结构

  1. 概念:数据结构,就是一种程序设计优化的方法论,研究数据的“逻辑结构”和“物理结构”,以及他们之间的关系,并对这种结构定义相应的运算,目的是加快执行速度,减少内存占用空间。

  2. 数据结构的研究对象:

    1. 研究对象一:
      1. 集合结构:数据结构中的元素之间除了“同属于一个集合”的相互关系之外,别无其他关系。集合元素之间没有逻辑关系。
      2. 线性结构:数据结构中的元素存在一对一的相互关系。比如:排队,结构中必须存在唯一的首元素和唯一的尾元素。体现为:一维数组、链表、栈、队列。
      3. 树形结构:数据结构中的元素存在一对多的相互关系。比如:家谱、文件系统、组织架构。
      4. 图形结构:数据结构中的元素存在多对多的相互关系。比如:铁路网、地铁图。
    2. 研究对象二:数据的存储结构(或物理结构)
      1. 顺序结构
      2. 链式结构
      3. 索引结构
      4. 散列结构
      5. 开发中,更习惯如下方式理解存储结构:
        1. 线性表(一对一的关系):一维数组、单向链表、双向链表、栈、队列。
        2. 树(一对多的关系):二叉树、B+树。
        3. 图(多对多)
        4. 哈希表:HashMap、HashSet
    3. 研究对象三:运算结构
      1. 分配资源、建立结构、释放资源
      2. 插入和删除
      3. 获取和遍历
      4. 修改和排序
  3. 常见存储结构之:数组
    略…

  4. 常见存储结构之:链表
    链表中的基本单位是:节点(Node)

    1. 单向链表
      class Node{
      Objcet data;
      Node next;
      public Node(Object data, Node data){
      this.data = data;
      }
      }

      创建对象:
      Node node1 = new Node(“AA”);
      Node node2 = new Node(“BB”);
      node1.next = node2;

    2. 双向链表
      class Node{
      Node prev;
      Object data;
      Node next;
      public Node(Node prev, Object data, Node next){
      this.prev = prev;
      this.data = data;
      this.next = next;
      }
      }

      创建对象:
      Node node1 = new Node(null, “AA”, null);
      Node node1 = new Node(node1, “BB”, null);
      Node node1 = new Node(node2, “CC”, null);

      node1.next = node2;
      node2.next = node3;

  5. 常见存储结构之:二叉树
    class TreeNode{
    TreeNode left;
    Object data;
    TreeNode right;

    public TreeNode(Object data){
    this.data = data;
    }

    public TreeNode(TreeNode left, Object data, TreeNode right){
    this.left = left;
    this.data = data;
    this.right = right;
    }
    }

    创建对象:
    TreeNode node1 = new TreeNode(null, “AA”, null);
    TreeNode leftNode = new TreeNode(null, “BB”, null);
    TreeNode rightNode = new TreeNode(null, “CC”, null);
    node1.left = leftNode;
    node1.right = rightNode;



    class TreeNode{
    TreeNode parent;
    TreeNode left;
    Object data;
    TreeNode right;

    public TreeNode(Object data){
    this.data = data;
    }

    public TreeNode(TreeNode left, Object data, TreeNode right){
    this.parent = parent;
    this.left = left;
    this.data = data;
    this.right = right;
    }
    }

    创建对象:
    TreeNode node1 = new TreeNode(null, null, “AA”, null);
    TreeNode leftNode = new TreeNode(node1, null, “BB”, null);
    TreeNode rightNode = new TreeNode(node1, null, “CC”, null);
    node1.left = leftNode;
    node1.right = rightNode;

  6. 常见存储结构之:栈(stack、先进先出、FILO)

    1. 属于抽象数据结构(ADT)

    2. 可以使用数组或链表来构建
      class Stack{
      Object[] values;
      int size; //记录存储的元素的个数

      public Stack(int length){
      values = new Object[length];
      }

      //入栈
      public void push(Object ele){
      if(size >= values.length){
      throw new RuntimeException(“栈空间已满,入栈失败”);
      }

      values[size] = ele;
      size++;
      }

      //出栈
      public Object pop(){
      if(size <= 0){
      throw new RuntimeException(“栈空间已空,出栈失败”);
      }

      return values[–size];
      }

    }

  7. 常见存储结构之:队列(queue、先进先出、FIFO)

    1. 属于抽象数据类型(ADT)

    2. 可以使用数组或链表来构建
      class Queue{
      Object[] values;
      int size;//记录存储的元素的个数

      public Queue(int length){
      values = new Object[length];
      }

      public void add(Object ele){
      if(size >= values.length){
      throw new RuntimeException(“队列已满,添加失败”);
      }

      values[size] = ele;
      size++;
      }

      public Object get(){
      if(size <= 0){
      throw nnew RuntimeException(“队列已空,获取失败”);
      }

      Object obj = values[0];

      //数据前移
      for(int i = 0;i < size-1;i++){
      values[i] = values[i+1];
      }

      //最后一个元素置空
      values[size-1] = null;
      size–;
      return obj;
      }
      }

File类与IO流

File类

  1. File类的理解:
    1. File类位于java.io包下,其他涉及到的相关流也声明在java.io包下。
    2. File类的一个对象,对应于操作系统下的一个文件或一个文件目录。
    3. File类中声明了新建、删除、获取名称、重命名等方法,并没有涉及到内容的读写操作,要想实现文件内容的读写,就必须要使用io流。
    4. File类的对象,通常是作为io流操作文件的端点出现的。
      1. 代码层面,将File类的对象作为参数传递到io流相关类的构造器中。
  2. 内部API的使用:
    1. 构造器:
      1. public File(String pathname):以pathname为路径创建File对象,可以是绝对路径也可以是相对路径。
      2. public File(String parent, String child):以parent为父路径,child为子路径创建File对象。
      3. public File(File parent, String child):根据一个File父对象和子文件路径创建File对象。
    2. 方法:
      1. 获取文件和目录基本信息:
        1. public String getName():获取名称
        2. public String getPath():获取路径
        3. public String getAbsolutePath():获取绝对路径
        4. public File getAbsoluteFile():获取绝对路径表示的文件
        5. public String getParent():获取上层文件目录路径,若无返回null
        6. public long length():返回文件长度(即:字节数),不能返回目录的长度
        7. public long lastModified():获取最后一次修改时间,时间戳
      2. 列出目录的下一级
        1. public String[] list():返回一个String类型的数组,表示该File目录中所有的子文件或者目录
        2. public File[] listFiles():返回一个File数组,表示该File目录中的所有子文件或目录
      3. File类的重命名功能
        1. public boolean renameTo(File dest):把文件重命名为指定的文件路径
      4. 判断功能的方法
        1. public boolean exists():此File表示的文件或目录是否实际存在
        2. public boolean isDirectory():此File类表示的是否为目录
        3. public boolean isFile():此File类表示的是否为文件
        4. public boolean canRead():判断是否可读
        5. public boolean canWrite():判断是否可写
        6. public boolean isHidden():判断是否隐藏
      5. 创建、删除功能
        1. public boolean createNewFile():创建文件、若文件已经存在,则不创建,返回false
        2. public boolean mkdir():创建文件目录,如果文件目录已经存在,则不创建。如果此文件目录的上层目录不存在,也不创建
        3. public boolean mkdirs():创建文件目录,如果上层文件目录不存在,则一并创建
        4. public boolean delete():删除文件或文件夹
          1. 删除注意事项:
            1. Java中的删除不走回收站
            2. 要删除一个文件目录,请注意该文件目录不能包含文件或文件目录
  3. 概念:
    1. 绝对路径:包括盘符在内的文件或文件目录的完整路径。
    2. 相对路径:相对于某一个文件目录来讲的相对位置。
      1. 在IDEA当中,如果使用单元测试方法,相当于当前module来讲。
      2. 如果使用main()方法,相当于当前project来讲。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package com.learning01.file;

import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;

/**
* @author yuxiaohong
* @package com.learning01.file
* @date 2023/11/10 18:01
* @description
*/
public class FileTest1 {
@Test
public void test1() {
File file1 = new File("hello.txt");
System.out.println(file1.getName());
System.out.println(file1.getPath());
System.out.println(file1.getAbsolutePath());
System.out.println(file1.getAbsoluteFile());
System.out.println(file1.getParent());
System.out.println(file1.length());
System.out.println(file1.lastModified());

/* hello.txt
hello.txt
D:\IDEA_Workspace\JavaSE\_13_io\hello.txt
D:\IDEA_Workspace\JavaSE\_13_io\hello.txt
null
0
0
*/
}

@Test
public void test3() {
File file = new File("D:\\Spring6.0.0");
System.out.println(Arrays.toString(file.list()));
System.out.println(Arrays.toString(file.listFiles()));

/* [commons-logging, spring-framework-6.0.0-RC2]
[D:\Spring6.0.0\commons-logging, D:\Spring6.0.0\spring-framework-6.0.0-RC2]*/
}

@Test
public void test4() {
File file1 = new File("hello.txt");

}

@Test
public void test5() throws IOException {
File file = new File("d:/hello.txt");
// 测试文件的创建和删除
if (!file.exists()) {
boolean isSucceed = file.createNewFile();
if (isSucceed) {
System.out.println("创建成功");
}
} else {
System.out.println("此文件已存在");
}
}

@Test
public void test6() throws IOException {
File file = new File("d:/io");
if (!file.exists()) {
boolean newFile = file.createNewFile();
if (newFile) {
System.out.println("文件目录创建成功");
}
} else {
System.out.println("文件目录已存在");
}
}
}

IO流

IO流原理

  1. Java程序中,对于数据的输入/输出操作以“流(stream)”的方式进行,可以看做是一种数据的流动。
  2. IO流中的I/O是Input和Output的缩写,I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件、网络通讯等等。
    1. 输入Input:读取外部数据(磁盘、光盘中存储的数据)到程序(内存)当中。
    2. 输出Output:将程序(内存)中的数据输出到磁盘、光盘等设备当中。
  3. 流的分类:
    1. java.io包下提供了各种“流”类和接口,用以处理不同种类的数据,并通过“标准”的方法输入和输出数据。
    2. 按数据的流向不同分为:输入流和输出流
      1. 输入流:把数据从其他设备读取到内存中的流:以InputStream、Reader结尾
      2. 输出流:把数据从内存写出到其他设备上的流:以OutputStream、Writer结尾
    3. 按操作数据单位不同分为:字节流(8bit)和字符流(16bit)
      1. 字节流:以字节为单位,读写数据中的流:以InputStream、OutputStream结尾
      2. 字符流:以字符为单位,读写数据中的流:以Reader、Writer结尾
    4. 根据IO流的角色不同分为:节点流和处理流
      1. 节点流:直接从数据源或目的地读写数据
      2. 处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能
  4. 流的API
    1. Java中的IO流涉及40多个类,实际上非常规则,都是从如下4个抽象基类派生的。
    2. 由这四个类派生出来的子类名称都是以其父类名作为子类名后缀

FileReader和FileWriter的使用

  1. 执行步骤
    1. 第一步:创建读取或写出的File类的对象
    2. 第二步:创建输入流或输出流
    3. 第三步:具体的写入或写出的过程
      1. 读入:read(char[] cbuffer)
      2. 写出:write(String str) / write(char[] cbuffer, 0, len)
    4. 第四步:关闭流资源,避免内存泄漏
  2. 注意点:
    1. 因为涉及到流资源的关闭操作,所以出现异常的话,需要使用try-catch-finally的方式来处理异常
    2. 对于输入流来讲,要求File类的对象对应的物理磁盘上的文件必须存在。否则,回报FileNotFoundException
    3. 对于输出流来讲,File的对象对应的物理磁盘上的文件可以不存在
    4. 如果此文件不存在,则在输出的过程中,会自动创建该文件,并写出数据到此文件当中
    5. 如果此文件存在,如果使用FileWriter(File file)或FileWriter(File file, false)会对现有文件中的数据覆盖;如果使用FileWriter(File file, 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package com.learning02.filestream;

import org.junit.Test;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/**
* @author yuxiaohong
* @package com.learning01.filestream
* @date 2023/11/13 11:32
* @description
*/
public class FileReaderWriterTest {
/**
* @Author: yuxiaohong
* @Description: 读取hello.txt中的内容,显示在控制台上
*/
@Test
public void test1() throws IOException {
// 1.创建一个File类的对象,对应着hello.txt文件
File file = new File("hello.txt");

// 2.创建输入型的字符流,用于读取数据
FileReader fileReader = new FileReader(file);

// 3.读取数据,并显示在控制台
// 方式1
// int data = fileReader.read();
// while (data != -1) {
// System.out.println((char) data);
// data = fileReader.read();
// }

// 方式2
int data;
while ((data = fileReader.read()) != -1) {
System.out.println((char) data);
}

// 4.流资源的关闭操作,必须要关闭,否则会导致内存泄漏
fileReader.close();
}

/**
* @Author: yuxiaohong
* @Description: 使用try-catch finally的方式处理异常,确保流一定被关闭
*/
@Test
public void test2() {
FileReader fileReader = null;
try {
// 1.创建一个File类的对象,对应着hello.txt文件
File file = new File("hello.txt");

// 2.创建输入型的字符流,用于读取数据
fileReader = new FileReader(file);

// 3.读取数据,并显示在控制台
int data;
while ((data = fileReader.read()) != -1) {
System.out.println((char) data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4.流资源的关闭操作,必须要关闭,否则会导致内存泄漏
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

/*
* 对test进行优化,每次读取多个字符,存放到字符数组中,减少磁盘的交互次数,提高效率
* */
@Test
public void Test3() {
FileReader fileReader = null;
try {
// 1.创建一个File类的对象,对应着hello.txt文件
File file = new File("hello.txt");

// 2.创建输入型的字符流,用于读取数据
fileReader = new FileReader(file);

// 3.读取数据,并显示在控制台
char[] cbuffer = new char[5];
int len;
while ((len = fileReader.read(cbuffer)) != -1) {
//遍历数组
for (int i = 0; i < len; i++) {
System.out.print(cbuffer[i]);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4.流资源的关闭操作,必须要关闭,否则会导致内存泄漏
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

//将内存中的数据写出到指定的文件
@Test
public void Test4() throws IOException {
FileWriter fileWriter = null;
try {
//1.创建File类的对象,指明要写出的文件的名称
File file = new File("info.txt");
//2.创建输出流
fileWriter = new FileWriter(file);
//3.写出具体的过程
fileWriter.write("avc");
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭资源
try {
if (fileWriter != null) {
fileWriter.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

//需求:复制一份hello.txt文件,命名为hello_copy.txt
@Test
public void Test5() {
FileReader fr = null;
FileWriter fw = null;
try {
//1.创建File类对象
File src = new File("hello.txt");
File dest = new File("hello_copy.txt");
//2.创建输入流和输出流
fr = new FileReader(src);
fw = new FileWriter(dest);
//3.数据的读入和写出的过程
char[] cbuffer = new char[5];
int len; //记录每次读入到cbuffer中的字符的个数
while ((len = fr.read(cbuffer)) != -1) {
//该方法参数:字符序列、起始下标、长度
fw.write(cbuffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭流资源
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}

}
}

FileInputStream \ FileOutputStream的使用

  1. 执行步骤
    1. 第一步:创建读取或写出的File类的对象
    2. 第二步:创建输入流或输出流
    3. 第三步:具体的读入或写出的过程
      1. 读入:read(byte[] buffer)
      2. 写出:write(byte[] buffer, 0, len)
    4. 第四步:关闭流资源,避免内存泄露
  2. 注意点:
    1. 因为涉及到流资源的关闭操作,所以出现异常的话,需要使用try-catch-finally的方式来处理异常
    2. 对于输入流来讲,要求File类的对象对应的物理磁盘上的文件必须存在。否则,回报FileNotFoundException
    3. 对于输出流来讲,File的对象对应的物理磁盘上的文件可以不存在
      1. 如果此文件不存在,则在输出的过程中,会自动创建该文件,并写出数据到此文件当中
      2. FileInputStream(File file)或FileOutputStream(File file, false)会对现有文件中的数据覆盖;如果使用FileWriter(File file, true),则会将现有的数据追加到末尾。
    4. 对于字符流,只能用来操作文本文件,不能用来处理非文本文件。
    5. 对于字节流,通常用来处理非文本文件。但是涉及到文本文件的复制操作,也可以使用字节流。
    6. 说明:
      1. 文本文件:.txt、.java、.c、.cpp、.py等
      2. 非文本文件:.doc、.xls、.jpg、.png、.pdf、.mp3等
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
package com.learning02.filestream;

import org.junit.Test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileStreamTest {
@Test
public void Test1() {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//1.创建相关的File类的对象
File srcFile = new File("player.jpg");
File destFile = new File("player_copy.jpg");
//2.创建相关的字节流
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
//3.数据的读入和写出
byte[] buffer = new byte[1024]; //1kb
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//4.关闭流资源
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

处理流之一:缓冲流

  1. 基础IO流的框架
    1. BufferedInputStream
    2. BufferedOutputStream
    3. BufferedReader
    4. BufferedWriter
  2. 缓冲流的作用:提升文件读写的效率,文件比较大的时候推荐使用这个方法
  3. 4个缓冲流所使用的方法
    1. 处理非文本文件的字节流:
      1. BufferedInputStream:read(byte[] buffer)
      2. BufferedOutputStream:write(byte[] buffer, 0, len)、flush()
    2. 处理文本文件的字符流:
      1. BufferedReader:read(char[] cbuffer) / String readLine()
      2. BufferedWriter:write(char[] cbuffer, 0, len) / write(String)、flush()
    3. 实现的步骤
      1. 第一步:创建File对象、流的对象(包括文件流、缓冲流)
      2. 第二步:使用缓冲流实现读取数据或写出数据的过程
        1. 读取:int read(char[] cbuf/byte[] buffer):每次将数据读入到cbuf/buffer数组中,并返回读入到数组中的数据
        2. 写出:void write(String str)/write(char[] cbuf):将str或cbuf写入到文件中;void write(byte[] buffer)将byte[]写出到文件中
      3. 第三步:关闭资源

BufferedStream

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
package com.learning03.buffered;

import org.junit.Test;

import java.io.*;

public class BufferedStreamTest {
//需求;使用BufferedInputStream和BufferedOutputStream复制一个图片
@Test
public void Test1() throws IOException {

//1.创建相关的File类的对象
File srcFile = new File("player.jpg");
File destFile = new File("player_copy1.jpg");
//2.创建相关的字节流
FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream fos = new FileOutputStream(destFile);

BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
//3.数据的读入和写出
byte[] buffer = new byte[1024]; //1kb
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}

//4.关闭流资源
//外层的流的关闭,由于外层流的关闭也会自动对内层的流进行关闭的操作,所以可以省略内层流的关闭。
bos.close();
bis.close();

}
}

BufferedReaderWriter

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
package com.learning03.buffered;

import org.junit.Test;

import java.io.*;

//测试BufferedReader和BufferedWriter的使用
public class BufferedReaderWriterTest {
//使用BufferedReader将会hello.txt中的内容显示在控制台上
@Test
public void Test1() throws IOException {
File file = new File("hello.txt");
BufferedReader br = new BufferedReader(new FileReader(file));

//读取的过程
//方式1:read(char[] cBuffer)
/* char[] cBuffer = new char[1024];
int len;
while ((len = br.read(cBuffer)) != -1) {
for (int i = 0; i < len; i++) {
System.out.println(cBuffer[i]);
}
}*/

//方式2:readLine(),每次读取一行文本中的数据,返回的字符串不包含换行符。
String data;
while ((data = br.readLine()) != null) {
System.out.println(data + "\n");
}
br.close();
}

//使用BufferedReader和BufferedWriter实现文本文件的复制
@Test
public void Test2() throws IOException {
//1.造文件、造流
File file = new File("hello.txt");
File file1 = new File("hello_copy1.txt");

BufferedReader br = new BufferedReader(new FileReader(file));
BufferedWriter bw = new BufferedWriter(new FileWriter(file1));

//2.文件的读写操作
String data;
while ((data = br.readLine()) != null) {
bw.write(data);
bw.newLine(); //换行操作
bw.flush(); //刷新,将缓存中的数据全部下厨到磁盘文件中
}
System.out.println("复制成功");

//3.关闭资源,如果没有关闭资源,则必须使用flush方法,将数据全部写出
bw.close();
br.close();
}
}

处理流之二:转换流

  1. 复习
    1. 字符编码:字符、字符串、字符数组—>字节、字节数组(人们看懂的->人们看不懂的)
    2. 字符解码:(看不懂的->看懂的)
  2. 如果希望程序在读取文本文件时,不出现乱码,需要注意什么
    1. 解码时使用的字符集必须与当初编码时使用的字符集相同。
    2. 解码集必须和编码集兼容。比如:文件编码是GBK,解码时使用UTF-8。如果文件中只有英文字母,此文件不会出现乱码,但是中文就会乱码了。
  3. 作用:实现字节与字符之间的转换
  4. API:
    1. InputStreamReader:将一个输入型的字节流转换为输入型的字符流
    2. OutputStreamWriter:将一个输出型的字符流转换为输出型的字节流
  5. 关于字符集的理解
    1. 存储在文件中的字符:
      1. ascii:主要用来存储英文字符、数字、标点符号等,每个字符占用一个字节,向下兼容ascii。
      2. iso8859-1:用与欧洲地区的使用。每个字符占用一个字节,向下兼容ascii。
      3. gbk:用来存储包括中文、英文字符、标点符号等字符,每个字符占用两个字节,向下兼容ascii。
      4. utf-8:可以用来存储世界范围内主要语言的所有字符,使用1-4个不等的字节表示一个字符。中文字符采用三个字节存储。
    2. 在内存中的字符:
      1. 一个字符(char)占用两个字节,在内存中使用的字符集称为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
package com.learning04.inputstreamreader;

import org.junit.Test;

import java.io.*;

public class InputStreamReaderTest {
@Test
public void Test1() throws IOException {
// 1.创建File对象
File file = new File("dbcp_utf-8.txt");
// 2.创建流对象
FileInputStream fis = new FileInputStream(file);
InputStreamReader isr = new InputStreamReader(fis);
// 3.读入操作
char[] cBuffer = new char[1024];
int len;
while ((len = isr.read(cBuffer)) != -1) {
String str = new String(cBuffer, 0, len);
System.out.println(str);
}

// 关闭资源
isr.close();
}
}

处理流之三:对象流

  1. 数据流及其使用(了解):
    1. DataOutputStream:可以将内存中的基本数据类型的变量、String类型的变量写出到具体文件当中。
    2. DataInputStream:将文件中保存的数据还原为内存中的基本数据类型的变量、String类型的变量。
  2. 对象流及其作用:
    1. API:
      1. ObjectInputStream:
      2. ObjectOutputStream:
    2. 作用:
      1. 可以读写基本数据类型的变量、引用数据类型的变量。
  3. 对象的序列化机制是什么:
    1. 对象的序列化机制允许把内存中的Java对象转换称平台无关的二进制流,从而允许把这种二进制流持久的保持在磁盘上,或通过网络将这种二进制流传输到另一个网络节点,当其他程序获取了这种二进制流,就可以恢复成原来的Java对象。
  4. 如下两个过程使用的流:
    1. 序列化过程:使用ObjectOutputStream流实现。将内存中的Java对象保存在文件中或通过网络传输出去。
    2. 反序列化过程:使用ObjectInputStream流实现。将文件中的数据或网络传输过来的数据还原成内存中的Java对象。
  5. 自定类要想实现序列化机制,需要满足:
    1. 自定义类需要实现接口:Serializable
    2. 要求自定类声明一个全局常量:static final long serialVersionUID = 42234234L;
    3. 要求自定义类的各个属性也必须是可序列化的
      1. 对于基本数据类型的属性:默认就是可以序列化的
      2. 对于引用数据类型的属性:要求实现Serializable接口
  6. 注意点:
    1. 如果不声明全局常量,系统会自动生成一个针对当前类的serialVersionUID ,如果修改此类的话,就会导致serialVersionUID修改,进而导致反序列化时,出现InvalidClassException。
    2. 类中的属性如果声明为transient或static,则不会实现序列化。

ObjectInputOutputStreamTest.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
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
package com.learning05.objectstream;

import org.junit.Test;

import java.io.*;

/**
* @author yuxiaohong
* @package com.learning05.objectstream
* @date 2023/11/21 14:04
* @description
*/
public class ObjectInputOutputStreamTest {
@Test
public void test1() throws IOException {
// 1.创建文件以及对象输出流
File file = new File("Object.txt");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
// 2.写出数据即为序列化的过程
oos.writeUTF("三三四四");
oos.flush();
oos.writeObject("123456");
oos.flush();
oos.close();

}

@Test
public void test2() throws IOException, ClassNotFoundException {
// 1.创建文件以及对象输入流
File file = new File("Object.txt");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));

// 2.读入数据即反序列化的过程
String s = ois.readUTF();
System.out.println(s);

String str = (String) ois.readObject();
System.out.println(str);
ois.close();
}

// 演示自定义类的对象序列化和反序列化过程
@Test
public void test3() throws IOException {
// 1.创建文件以及对象输出流
File file = new File("Object1.dat");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
// 2.写出数据即为序列化的过程
Person p1 = new Person("Tom", 12);
oos.writeObject(p1);
oos.flush();
oos.close();
}

@Test
public void test4() throws IOException, ClassNotFoundException {
// 1.创建文件以及对象输入流
File file = new File("Object1.dat");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));

// 2.读入数据即反序列化的过程

Person person = (Person) ois.readObject();
System.out.println(person);
ois.close();
}
}

Person.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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.learning05.objectstream;

import java.io.Serializable;

/**
* @author yuxiaohong
* @package com.learning05.objectstream
* @date 2023/11/21 14:44
* @description
*/
public class Person implements Serializable { // 标识接口,没有任何需要重写的方法,表示对象是可以序列化的
private static final long serialVersionUID = 42234234L;
String name;
int age;

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

public Person() {
}

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. 标准输入、输出流
    1. System.in:标准的输入流,默认从键盘键入
    2. System.out:标准的输出流,默认从显示器输出
    3. 通过调用如下的方法,修改输入流和输出流的位置
      1. setIn(inputStream is)
      2. setOut(printStream ps)

网络编程

InetAddress相关方法

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
package com.geo.learning01.net;

import java.net.InetAddress;
import java.net.UnknownHostException;

/**
* @author yuxiaohong
* @package com.geo.learning01.net
* @date 2023/11/21 17:36
* @description
*/
public class InetAddressTest {
public static void main(String[] args) {
// 1.实例化
try {
// ip
InetAddress inet1 = InetAddress.getByName("192.168.23.32");
System.out.println(inet1); // /192.168.23.32

// 域名,打印之后是解析过的ip地址
InetAddress inet2 = InetAddress.getByName("www.baidu.com"); // www.baidu.com/39.156.66.14
System.out.println(inet2);

// 获取本地的IP地址
System.out.println(InetAddress.getLocalHost()); // DESKTOP-TQKP7AK/192.168.0.125


// 两个常用的方法
System.out.println(inet2.getHostName());
System.out.println(inet2.getHostAddress());
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
}

运行截图:

TCP

Socket,一个简单的TCP/IP通信程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package com.geo.learning02.tcpudp;

import org.junit.Test;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

/**
* @author yuxiaohong
* @package com.geo.learning02.tcpudp
* @date 2023/11/21 18:39
* @description
*/
public class TCPTest1 {
// 客户端
@Test
public void client() {
Socket socket = null;
OutputStream os = null;
try {
// 创建一个Socket
// 声明ip地址
InetAddress inetAddress = InetAddress.getByName("192.168.21.107");
// 声明端口号
int port = 8989;
socket = new Socket(inetAddress, port);
// 发送数据
os = socket.getOutputStream();
os.write("我是客户端。".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭Socket
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (os != null) {
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}

}

// 服务端
@Test
public void server() throws IOException {
ServerSocket serverSocket = null;
Socket socket = null; // 阻塞式的方法
InputStream is = null;
try {
// 创建一个server的Socket
int port = 8989;
serverSocket = new ServerSocket(port);
// 调用accept(),接受客户端的Socket
socket = serverSocket.accept();
System.out.println("服务器端已开启。。。");
// 接收数据
is = socket.getInputStream();
byte[] buffer = new byte[1024];
int len;
ByteArrayOutputStream baos = new ByteArrayOutputStream(); // 内部维护了一个byte[]
while ((len = is.read(buffer)) != -1) {
// 防止出现接收byte数组的长度不是3的倍数或长度不足而导致的乱码,
// String str = new String(buffer, 0, len);
// System.out.println(str);

baos.write(buffer, 0, len);
System.out.println(baos);
System.out.println("收到了来自:" + socket.getInetAddress().getHostAddress() + "的信息。");
}
System.out.println("数据接受完毕。。。");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭Socket
if (socket != null) {
socket.close();
}
if (serverSocket != null) {
serverSocket.close();
}
if (is != null) {
is.close();
}
}
}
}

运行截图:

稍微复杂一点的TCP/IP通信:客户端给服务端发送一张图片,服务端接收到之后保存在本地

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package com.geo.learning02.tcpudp;

import org.junit.Test;

import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

/**
* @author yuxiaohong
* @package com.geo.learning02.tcpudp
* @date 2023/11/22 15:56
* @description 用户端给服务器端发送文件,服务器端将文件保存在本地
*/
public class TCPTest2 {
@Test
public void client() {
Socket socket = null;
FileInputStream fis = null;
OutputStream os = null;
try {
// 1.创建Socket
InetAddress address = InetAddress.getByName("192.168.0.125");
int port = 9090;
socket = new Socket(address, port);

// 2.创建File的实例和流的实例
File file1 = new File("pic.jpg");
fis = new FileInputStream(file1);
os = socket.getOutputStream();
// 3.通过Socket获取输出流
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4.关闭Socket和相关的流
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

@Test
public void server() {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
FileOutputStream fos = null;
try {
// 1.创建ServerSocket
int port = 9090;
serverSocket = new ServerSocket(port);
// 2.接收来自客户端的socket.accept()
socket = serverSocket.accept();
// 3.通过Socket获取输入流
is = socket.getInputStream();
// 4.创建File类的实例和输出流的实例
File file = new File("pic_copy.jpg");
fos = new FileOutputStream(file);
// 5.读写过程
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
System.out.println("数据接收完毕");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 6.关闭相关的Socket的流
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (serverSocket != null) {
serverSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

运行截图:

TCP/IP测试三:客户端发送文件到服务端,服务端保存到本地,并给客户端发送消息说文件发送成功

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
package com.geo.learning02.tcpudp;

import org.junit.Test;

import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author yuxiaohong
* @package com.geo.learning02.tcpudp
* @date 2023/11/22 16:26
* @description 客户端发送文件到服务端,服务端保存到本地,并给客户端发送消息说文件发送成功
*/
public class TCPTest3 {
@Test
public void client() throws IOException {

InetAddress address = InetAddress.getByName("192.168.0.125");
int port = 8891;
Socket socket = new Socket(address, port);
File file = new File("aaa.txt");
FileInputStream fis = new FileInputStream(file);
OutputStream os = socket.getOutputStream();

byte[] buffer = new byte[1024];
int len;
while ((len = fis.read()) != -1) {
os.write(buffer, 0, len);
}
System.out.println("客户端发送文件成功。");
// 关闭客户端的输出
socket.shutdownOutput();

// 接收来自服务器端的数据
InputStream is = socket.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer1 = new byte[1024];
int len1;
while ((len1 = is.read(buffer1)) != -1) {
baos.write(buffer1, 0, len1);
}
System.out.println(baos);
baos.close();
is.close();
os.close();
fis.close();
socket.close();
}

@Test
public void server() throws IOException {
int port = 8891;
ServerSocket serverSocket = new ServerSocket(port);
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
File file = new File("aaa_copy.txt");
FileOutputStream fos = new FileOutputStream(file);

byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
System.out.println("服务端接收文件成功,并保存在本地。");

OutputStream os = socket.getOutputStream();
os.write("服务端已接收到文件。".getBytes());
os.close();
fos.close();
is.close();
socket.close();
serverSocket.close();
}
}

UDP

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
package com.geo.learning02.tcpudp;

import org.junit.Test;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
* @author yuxiaohong
* @package com.geo.learning02.tcpudp
* @date 2023/11/22 17:43
* @description
*/
public class UDPTest {
@Test
public void sender() throws Exception {
// 1.创建DatagramSocket实例
DatagramSocket ds = new DatagramSocket();
// 2.将目的地的ip和端口号都封装在数据报DatagramPacket中(64kb以内)
InetAddress address = InetAddress.getByName("192.168.0.125");
int port = 9090;
byte[] bytes = "我是发送端".getBytes("utf-8");
DatagramPacket dp = new DatagramPacket(bytes, 0, bytes.length, address, port);

ds.send(dp);
ds.close();
}

@Test
public void receiver() throws Exception {
// 1.创建DatagramSocket实例
DatagramSocket ds = new DatagramSocket(9090);
// 2.创建一个数据报的对象用于接收发送端发送过来的数据
byte[] buffer = new byte[64];
DatagramPacket dp = new DatagramPacket(buffer, 0, buffer.length);
// 3.接收数据报
ds.receive(dp);
// 4.打印数据
String str = new String(dp.getData(), 0, dp.getLength());
System.out.println(str);
ds.close();
}
}

URL

  1. 作用:一个具体的url就对应着互联网上的某一个资源。
  2. URL格式:应用层协议-ip地址-端口号-资源地址-参数列表
  3. URL类的实例化及常用方法
    1. public String getProtocol():获取该URL的协议名
    2. public String getHost():获取该URL的主机名
    3. public String getPort():获取该URL的端口号
    4. public String getPath():获取该URL的文件路径
    5. public String getFile():获取该URL的文件名
    6. public String getQuery():获取该URL的查询名
  4. 下载指定的URL的资源到本地
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
package com.geo.learning03.url;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

/**
* @author yuxiaohong
* @package com.geo.learning03.url
* @date 2023/11/22 18:04
* @description
*/
public class URLTest {
public static void main(String[] args) {
FileOutputStream fos = null;
InputStream is = null;
HttpURLConnection httpURLConnection = null;
try {
// 1.获取URL实例
URL url = new URL("https://www.bilibili.com/video/BV1PY411e7J6?p=186&vd_source=b13a0c249f074a81f967680e2c948d52");
try {
// 2.建立与服务器端之间的连接
httpURLConnection = (HttpURLConnection) url.openConnection();
// 3.获取输出、输入流
is = httpURLConnection.getInputStream();
File file = new File("aaa.jpg");
fos = new FileOutputStream(file);
// 4.读写数据
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}

} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
fos.close();
}
if (is != null) {
is.close();
}
if (httpURLConnection != null) {
httpURLConnection.disconnect();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

反射

反射的概念

  1. 反射出现的背景:
    Java程序中,所有的对象都有两种类型:编译时类型和运行时类型,而很多时候对象的编译时类型和运行时类型不一致(多态)。
    例如:某些变量或形参的声明类型是Object类型,但是程序却需要调用该对象运行时类型的方法,该方法不是object中的方法,那么如何解决呢?
    解决这个问题,有两种方案:
    方案1:在编译和运行时都完全知道类型的具体信息,在这种情况下,我们可以直接先使用 instanceof 运算符进行判断,再利用强制类型转换符将其转换成运行时类型的变量即可。
    方案2:编译时根本无法预知该对象和类的真实信息,程序只能依靠 运行时信息 来发现该对象和类的真实信息这就必须使用反射。
  2. 反射概述:
    1. Reflection(反射)是被视为动态语言的关键,反射机制允许程序在运行期间借助Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
    2. 加载完类之后,在堆内存的方法区就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子可以看到类的结构,所以形象的成为反射。

ReflectionTest.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
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
package com.geo.learning01.example;

import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
* @author yuxiaohong
* @package com.geo.learning01.example
* @date 2023/11/22 18:35
* @description
*/
public class ReflectionTest {

// 之前使用的创建对象的方法
// @Test
// public void test() {
// Person tom = new Person("Tom", 23);
// System.out.println(tom.age);
// tom.show();
// }

// 使用反射实现
@Test
public void test2() throws Exception {
// 1.创建Person类的实例
Class<Person> person = Person.class;
Person person1 = person.newInstance();
System.out.println(person1);

// 2.调用属性
Field ageField = person.getField("age");
ageField.set(person1, 10);
System.out.println(ageField.get(person1));

// 3.调用方法
Method showMethod = person.getMethod("show");
showMethod.invoke(person1);
}

/*出了Person类之后*/
@Test
public void test3() throws Exception {
// 1.调用私有的构造器创建Person的实例
Class clazz = Person.class;
Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class, int.class);
// 因为构造器是私有的,所以会报没有访问权限的错误,通过下面这行代码解决
declaredConstructor.setAccessible(true);
Person p1 = (Person) declaredConstructor.newInstance("Tom", 12);

// 2.调用私有的属性
Field declaredField = clazz.getDeclaredField("name");
declaredField.setAccessible(true);
declaredField.set(p1, "jerry");

// 3.调用私有的方法

Method showName = clazz.getDeclaredMethod("showName", String.class);
showName.setAccessible(true);
String info = (String) showName.invoke(p1, "李晓杰");
System.out.println(info);
}
}

Person.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
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
package com.geo.learning01.example;

/**
* @author yuxiaohong
* @package com.geo.learning01.example
* @date 2023/11/23 16:54
* @description
*/
public class Person {
public String name;
public int age;

public Person() {
}

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

public String getName() {
return name;
}

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

public int getAge() {
return age;
}

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

public void show() {
System.out.println("展示");
}

private String showName(String name) {
return "我的名字是" + name;
}

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

通过使用反射前后的例子对比:

  1. 面向对象中创建对象,调用指定结构(属性、方法)等功能,可以不使用反射,也可以使用反射。有什么区别?
    1. 不使用反射,需要考虑封装性,出了Person类之后就不能调用Person类中的私有结构;
    2. 使用反射,我们可以调用运行时类中任意的构造器、属性、方法,包括私有的构造器、属性和方法。
  2. 以前创建对象并调用方法的方式,与现在通过反射创建对象并调用方法的方式对比的话,哪种用的多?场景是什么?
    1. 从我们作为开发者的角度:开发中主要完成业务代码,对于相关的对象和方法的调用都是确定的,所以使用非反射的方式更多。
    2. 因为反射体现了动态性,可以在运行时动态的获取对象所属的类,动态的调用相关的方法。所以在设计框架的时候,会大量的使用反射。
  3. 单例模式的饿汉式和懒汉式中,私有化类的构造器了,通过反射可以创建多个类的对象吗?
    1. 可以。

Java反射机制研究及应用

  1. Java反射机制提供的功能:
    1. 在运行时判断任意一个对象所属的类
    2. 在运行时构造任意一个类的对象
    3. 在运行时判断任意一个类所具有的成员变量和方法
    4. 在运行时获取泛型信息
    5. 在运行时调用任意一个对象的成员变量和方法
    6. 在运行时处理注解
    7. 生成动态代理

反射相关的主要API

  1. java.lang.Class:代表一个类
  2. java.lang.reflect.Meethod:代表类的方法
  3. java.lang.reflect.Field:代表类的成员变量
  4. java.lang.reflect.Constructor:代表类的构造器

反射的优缺点

  1. 优点:
    1. 提高了Java程序的灵活性和扩展性,降低了耦合性,提高自适应能力。
    2. 允许程序创建和控制任何类的对象,无需提前硬编码目标类。
  2. 缺点:
    1. 反射的性能较低。
    2. 反射会模糊程序的内部逻辑,可读性很差。

Class类

Class类的理解

  1. 针对编写好的.java源文件进行编译(使用javac.exe),会生成一个或多个.class字节码文件。接着使用java.exe对指定的.class文件进行解释运行。这个解释运行的过程中,需要将.class字节码文件加载到内存中。加载到内存中的.class文件对应的结构即为Class的一个实例。
    1. 运行时类在内存中会缓存起来,在整个执行期间,只会加载一次。
  2. 体会:Class看作是反射的源头。
  3. 获取Class实例的几种方式:
    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
    package com.geo.learning02._class;

    import org.junit.Test;

    public class ClassTest {
    @Test
    public void Test1() throws ClassNotFoundException {
    //1.调用运行时类的静态属性
    Class<User> userClass = User.class;
    System.out.println(userClass);

    //2.调用运行时类的对象的getClass()
    User u1 = new User();
    Class user2 = u1.getClass();

    //3.调用Class的静态方法forName(String className)
    String className = "com.geo.learning02._class.User";
    Class<?> aClass = Class.forName(className);
    System.out.println(userClass == user2); //true
    System.out.println(userClass == aClass); //true

    //4.使用类的加载器
    Class<?> aClass1 = ClassLoader.getSystemClassLoader().loadClass("com.geo.learning02._class.User");
    System.out.println(aClass1 == userClass); //true
    }
    }
  4. Class的实例都可以指向哪些结构呢?(所有java类型)
    1. class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
    2. interface:接口
    3. []:数组
    4. enum:枚举
    5. annotation:注解@interface
    6. primitive type:基本数据类型
    7. void
  5. 类的加载过程
    1. 过程一:类的装载(loading)
      1. 将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成
    2. 过程二:链接(linking)
      1. 验证(Verify):确保加载的类信息符合JVM规范,例如:以cafebabe开头,没有安全方面的问题。
      2. 准备(Prepare):证实为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法去中进行分配。
      3. 解析(Resolve):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
    3. 过程三:初始化(initialization)
      1. 执行类构造器< clinit>()方法的过程。
      2. 类构造器< clinit>()方法是由编译期自动收集类中所有类变量赋值动作和静态代码块中的语句合并产生的。
  6. 关于类的加载器
    1. 作用:负责类的加载,并对应于一个Class实例。
    2. 分类:
      1. BootstrapClassLoader:引导类加载器、启动类加载器
        1. 使用C/C++语言编写的,不能通过Java代码获取其实例
        2. 负责加载Java的核心库
      2. 继承于ClassLoader的类加载器
        1. ExtensionClassLoader:扩展类加载器
          1. 负责加载从java.ext.dirs系统属性所指定的目录中加载类库
        2. SystemClassLoader\ApplicationClassLoader:系统类加载器、应用程序类加载器
          1. 自定义的类默认使用的类的加载器
        3. 用户自定义类的加载器
    3. 以上的类的加载器是否存在继承关系?
      1. 无继承关系。

ClassLoaderTest.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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.geo.learning02._class;

import org.junit.Test;

import java.io.*;
import java.util.Properties;

/**
* @author yuxiaohong
* @package com.geo.learning02._class
* @date 2023/11/28 18:43
* @description
*/
public class ClassLoaderTest {
/**
* @Author: yuxiaohong
* @Description: 通过ClassLoader加载指定的配置文件
* @Params:
* @Return
*/
@Test
public void test1() throws IOException {
Properties prop = new Properties();
// 读取的文件的默认路径为当前module下的src文件夹下
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("info1.properties");
prop.load(is);
String name = prop.getProperty("name");
String password = prop.getProperty("password");
System.out.println(name + ":" + password);
}

/**
* @Author: yuxiaohong
* @Description: Properties:处理属性文件
* @Params:
* @Return
*/
@Test
public void test2() throws IOException {
Properties prop = new Properties();
// 读取文件的默认路径为当前module下
FileInputStream fis = new FileInputStream(new File("info.properties"));
prop.load(fis);
String name = prop.getProperty("name");
String password = prop.getProperty("password");
System.out.println(name + ":" + password);
}
}

反射的应用

应用一:创建运行时类的对象

  1. 如何实现
    1. 通过Class的实例调用newInstatce()方法即可。
  2. 要想创建对象成功,需要满足:
    1. 条件1:要求运行时类中必须提供一个空参构造器。
    2. 条件2:要求提供的空参构造器的权限足够。
  3. 在JDK9中标记为过时,替换成什么结构
    1. 调用Constructor.newInstance()

应用二:获取运行时类的内部结构

  1. 获取运行时类的内部结构1:所有属性、所有方法、所有构造器

FieldTest.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
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
package com.atguigu.java2;

import com.atguigu.java1.Person;
import org.junit.Test;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/**
* 获取当前运行时类的属性结构
*
* @author shkstart
* @create 2019 下午 3:23
*/
public class FieldTest {

@Test
public void test1(){

Class clazz = Person.class;

//获取属性结构
//getFields():获取当前运行时类及其父类中声明为public访问权限的属性
Field[] fields = clazz.getFields();
for(Field f : fields){
System.out.println(f);
}
System.out.println();

//getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)
Field[] declaredFields = clazz.getDeclaredFields();
for(Field f : declaredFields){
System.out.println(f);
}
}

//权限修饰符 数据类型 变量名
@Test
public void test2(){
Class clazz = Person.class;
Field[] declaredFields = clazz.getDeclaredFields();
for(Field f : declaredFields){
//1.权限修饰符
int modifier = f.getModifiers();
System.out.print(Modifier.toString(modifier) + "\t");

//2.数据类型
Class type = f.getType();
System.out.print(type.getName() + "\t");

//3.变量名
String fName = f.getName();
System.out.print(fName);

System.out.println();
}
}
}

MethodTest.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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package com.atguigu.java2;

import com.atguigu.java1.Person;
import org.junit.Test;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
* 获取运行时类的方法结构
*
* @author shkstart
* @create 2019 下午 3:37
*/
public class MethodTest {

@Test
public void test1(){

Class clazz = Person.class;

//getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
Method[] methods = clazz.getMethods();
for(Method m : methods){
System.out.println(m);
}
System.out.println();
//getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method m : declaredMethods){
System.out.println(m);
}
}

/*
@Xxxx
权限修饰符 返回值类型 方法名(参数类型1 形参名1,...) throws XxxException{}
*/
@Test
public void test2(){
Class clazz = Person.class;
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method m : declaredMethods){
//1.获取方法声明的注解
Annotation[] annos = m.getAnnotations();
for(Annotation a : annos){
System.out.println(a);
}

//2.权限修饰符
System.out.print(Modifier.toString(m.getModifiers()) + "\t");

//3.返回值类型
System.out.print(m.getReturnType().getName() + "\t");

//4.方法名
System.out.print(m.getName());
System.out.print("(");
//5.形参列表
Class[] parameterTypes = m.getParameterTypes();
if(!(parameterTypes == null && parameterTypes.length == 0)){
for(int i = 0;i < parameterTypes.length;i++){

if(i == parameterTypes.length - 1){
System.out.print(parameterTypes[i].getName() + " args_" + i);
break;
}

System.out.print(parameterTypes[i].getName() + " args_" + i + ",");
}
}

System.out.print(")");

//6.抛出的异常
Class[] exceptionTypes = m.getExceptionTypes();
if(exceptionTypes.length > 0){
System.out.print("throws ");
for(int i = 0;i < exceptionTypes.length;i++){
if(i == exceptionTypes.length - 1){
System.out.print(exceptionTypes[i].getName());
break;
}

System.out.print(exceptionTypes[i].getName() + ",");
}
}
System.out.println();
}
}
}

Reflection.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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package com.atguigu.java2;

import com.atguigu.java1.Person;
import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
* 调用运行时类中指定的结构:属性、方法、构造器
*
* @author shkstart
* @create 2019 下午 4:46
*/
public class ReflectionTest {

/*

不需要掌握
*/
@Test
public void testField() throws Exception {
Class clazz = Person.class;

//创建运行时类的对象
Person p = (Person) clazz.newInstance();


//获取指定的属性:要求运行时类中属性声明为public
//通常不采用此方法
Field id = clazz.getField("id");

/*
设置当前属性的值

set():参数1:指明设置哪个对象的属性 参数2:将此属性值设置为多少
*/

id.set(p,1001);

/*
获取当前属性的值
get():参数1:获取哪个对象的当前属性值
*/
int pId = (int) id.get(p);
System.out.println(pId);


}
/*
如何操作运行时类中的指定的属性 -- 需要掌握
*/
@Test
public void testField1() throws Exception {
Class clazz = Person.class;

//创建运行时类的对象
Person p = (Person) clazz.newInstance();

//1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
Field name = clazz.getDeclaredField("name");

//2.保证当前属性是可访问的
name.setAccessible(true);
//3.获取、设置指定对象的此属性值
name.set(p,"Tom");

System.out.println(name.get(p));
}

/*
如何操作运行时类中的指定的方法 -- 需要掌握
*/
@Test
public void testMethod() throws Exception {

Class clazz = Person.class;

//创建运行时类的对象
Person p = (Person) clazz.newInstance();

/*
1.获取指定的某个方法
getDeclaredMethod():参数1 :指明获取的方法的名称 参数2:指明获取的方法的形参列表
*/
Method show = clazz.getDeclaredMethod("show", String.class);
//2.保证当前方法是可访问的
show.setAccessible(true);

/*
3. 调用方法的invoke():参数1:方法的调用者 参数2:给方法形参赋值的实参
invoke()的返回值即为对应类中调用的方法的返回值。
*/
Object returnValue = show.invoke(p,"CHN"); //String nation = p.show("CHN");
System.out.println(returnValue);

System.out.println("*************如何调用静态方法*****************");

// private static void showDesc()

Method showDesc = clazz.getDeclaredMethod("showDesc");
showDesc.setAccessible(true);
//如果调用的运行时类中的方法没有返回值,则此invoke()返回null
// Object returnVal = showDesc.invoke(null);
Object returnVal = showDesc.invoke(Person.class);
System.out.println(returnVal);//null

}

/*
如何调用运行时类中的指定的构造器
*/
@Test
public void testConstructor() throws Exception {
Class clazz = Person.class;

//private Person(String name)
/*
1.获取指定的构造器
getDeclaredConstructor():参数:指明构造器的参数列表
*/

Constructor constructor = clazz.getDeclaredConstructor(String.class);

//2.保证此构造器是可访问的
constructor.setAccessible(true);

//3.调用此构造器创建运行时类的对象
Person per = (Person) constructor.newInstance("Tom");
System.out.println(per);

}

}

  1. 获取运行时类的内部结构2:父类、接口、包、带泛型的父类、父类的泛型等。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    package com.atguigu.java2;

    import com.atguigu.java1.Person;
    import org.junit.Test;

    import java.lang.annotation.Annotation;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;

    /**
    * @author shkstart
    * @create 2019 下午 4:19
    */
    public class OtherTest {

    /*
    获取构造器结构

    */
    @Test
    public void test1(){

    Class clazz = Person.class;
    //getConstructors():获取当前运行时类中声明为public的构造器
    Constructor[] constructors = clazz.getConstructors();
    for(Constructor c : constructors){
    System.out.println(c);
    }

    System.out.println();
    //getDeclaredConstructors():获取当前运行时类中声明的所有的构造器
    Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
    for(Constructor c : declaredConstructors){
    System.out.println(c);
    }

    }

    /*
    获取运行时类的父类

    */
    @Test
    public void test2(){
    Class clazz = Person.class;

    Class superclass = clazz.getSuperclass();
    System.out.println(superclass);
    }

    /*
    获取运行时类的带泛型的父类

    */
    @Test
    public void test3(){
    Class clazz = Person.class;

    Type genericSuperclass = clazz.getGenericSuperclass();
    System.out.println(genericSuperclass);
    }

    /*
    获取运行时类的带泛型的父类的泛型


    代码:逻辑性代码 vs 功能性代码
    */
    @Test
    public void test4(){
    Class clazz = Person.class;

    Type genericSuperclass = clazz.getGenericSuperclass();
    ParameterizedType paramType = (ParameterizedType) genericSuperclass;
    //获取泛型类型
    Type[] actualTypeArguments = paramType.getActualTypeArguments();
    // System.out.println(actualTypeArguments[0].getTypeName());
    System.out.println(((Class)actualTypeArguments[0]).getName());
    }

    /*
    获取运行时类实现的接口
    */
    @Test
    public void test5(){
    Class clazz = Person.class;

    Class[] interfaces = clazz.getInterfaces();
    for(Class c : interfaces){
    System.out.println(c);
    }

    System.out.println();
    //获取运行时类的父类实现的接口
    Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
    for(Class c : interfaces1){
    System.out.println(c);
    }

    }
    /*
    获取运行时类所在的包

    */
    @Test
    public void test6(){
    Class clazz = Person.class;

    Package pack = clazz.getPackage();
    System.out.println(pack);
    }

    /*
    获取运行时类声明的注解

    */
    @Test
    public void test7(){
    Class clazz = Person.class;

    Annotation[] annotations = clazz.getAnnotations();
    for(Annotation annos : annotations){
    System.out.println(annos);
    }
    }

    }

应用三:调用指定的结构:指定的属性、方法、构造器

  1. 调用指定的属性的步骤
    1. 通过Class实例调用getDeclaredField(String fieldName),获取运行时类指定名的属性
    2. setAccessible(true),确保此属性是可访问的
    3. 通过Field类的实例调用get(Object obj)(获取的操作)或set(Object obj, Object value)(设置的操作)进行操作
  2. 调用指定的方法的步骤
    1. 通过Class实例调用getDeclaredMethod(String methodName, Class …args),获取指定的方法
    2. setAccessible(true),确保此方法是可访问的
    3. 通过Method实例调用invoke(Object obj, Object …args),即为对Method对应的方法进行调用,invoke()的返回值即为Method对应的方法的返回值,如果对应的方法返回值类型为void,则invoke的返回值为null。
  3. 调用指定的构造器步骤
    1. 通过Class实例嗲敖勇getDeclaredConstructor(Class …args),获取指定参数类型的构造器
    2. setAccessible(true),确保此构造器是可访问的
    3. 通过Constructor实例调用newInstance(Object …args),返回一个运行时类的实例

反射的动态性体现

代码结构

Apple.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.geo.learning03.reflectapply.exer;

/**
* @author yuxiaohong
* @package com.geo.learning03.reflectapply.exer
* @date 2023/11/29 18:46
* @description
*/
public class Apple implements Fruit {
@Override
public void squeeze() {
System.out.println("榨一杯苹果汁。");
}
}

Fruit.interface

1
2
3
4
5
6
7
8
9
10
11
12
package com.geo.learning03.reflectapply.exer;

/**
* @author yuxiaohong
* @package com.geo.learning03.reflectapply.exer
* @date 2023/11/29 18:46
* @description
*/
public interface Fruit {
void squeeze();
}

Juicer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.geo.learning03.reflectapply.exer;

/**
* @author yuxiaohong
* @package com.geo.learning03.reflectapply.exer
* @date 2023/11/29 18:48
* @description
*/
public class Juicer {
public void run(Fruit f) {
f.squeeze();
}
}

FruitTeest.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
28
29
30
31
32
33
34
35
36
37
38
package com.geo.learning03.reflectapply.exer;

import org.junit.Test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Properties;

/**
* @author yuxiaohong
* @package com.geo.learning03.reflectapply.exer
* @date 2023/11/29 18:49
* @description
*/
public class FruitTest {
@Test
public void test1() throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 1.读取配置文件中的信息
Properties prop = new Properties();
File file = new File("src/config.properties");
FileInputStream fis = new FileInputStream(file);
prop.load(fis);
String property = prop.getProperty("fruitName");

// 2.通过反射创建指定全类名对应的类的实例
Class aClass = Class.forName(property);
Constructor con = aClass.getDeclaredConstructor();
con.setAccessible(true);
Fruit f = (Fruit) con.newInstance();
// 3.通过榨汁机的对象调用run()
Juicer juicer = new Juicer();
juicer.run(f);
}
}

JDK8-17新特性

JDK8的新特性-Lambda表达式

  1. Lambda表达式的使用举例
    1. (o1, o2) -> Integer.compare(o1, o2);
  2. Lambda表达式的格式举例
    1. Lambda形参列表->Lambda体
  3. Lambda表达式的格式
    1. ->:lambda操作符或箭头操作符
    2. ->的左边:lambda形参列表,对应着要重写的接口中的抽象方法的形参列表。
    3. ->的右边:lambda体,对应着接口的实现类要重写的方法的方法体。
  4. Lambda表达式的本质
    1. 一方面,lambda表达式作为接口的实现类的对象。
    2. 另一方面,lambda表达式就是一个匿名函数。
  5. 函数式接口
    1. 什么是函数式接口?为什么需要函数式接口?
      1. 如果接口中只声明有一个抽象方法,则此接口就成为函数式接口。
      2. 因为只有给函数式接口提供实现类的对象时,我们才可以使用lambda表达式。
    2. api中函数式接口所在的包
      1. 声明在java.util.function包下。
    3. 4个基本的函数式接口
      1. 消费型接口:Consumer< T> void accept(T t);
      2. 供给型接口:Supplier< T> T get();
      3. 函数型接口:Function< T, R> R apply(T t);
      4. 判断型接口:Predicate< T> boolean test(T t);
    4. Lambda表达式的语法规则总结:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      package com.geo.learning01.lambda;

      import org.junit.Test;

      import java.util.Comparator;
      import java.util.function.Consumer;

      public class LambdaTest1 {
      // 语法格式一:无参,无返回值
      @Test
      public void Test1() {
      Runnable r1 = new Runnable() {
      @Override
      public void run() {
      System.out.println("123");
      }
      };

      r1.run();
      System.out.println("******************");


      Runnable r2 = () -> {
      System.out.println("123456");
      };

      r2.run();
      }

      // 语法格式二:Lambda需要一个参数,但没有返回值
      @Test
      public void Test2() {
      Consumer<String> con = new Consumer<String>() {
      @Override
      public void accept(String s) {
      System.out.println(s);
      }
      };

      con.accept("123456");
      System.out.println("****************");

      Consumer<String> con1 = (String s) -> {
      System.out.println(s);
      };
      con1.accept("54163415");
      }

      // 语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
      @Test
      public void Test3() {
      Consumer<String> con1 = (String s) -> {
      System.out.println(s);
      };
      con1.accept("zxcv");

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

      Consumer<String> con2 = (s) -> {
      System.out.println(s);
      };
      con2.accept("bbb");
      }

      // 语法格式四:Lambda若只需要一个参数时,参数的小括号可以省略
      @Test
      public void Test4() {
      Consumer<String> con1 = s -> {
      System.out.println(s);
      };
      con1.accept("asdasd");
      }

      // 语法格式五:Lambda需要两个或以上的参数,多条执行语句,并且可以有返回值
      @Test
      public void test5() {
      Comparator<Integer> com1 = new Comparator<Integer>() {
      @Override
      public int compare(Integer o1, Integer o2) {
      System.out.println(o1);
      System.out.println(o2);
      return o1.compareTo(o2);
      }
      };
      System.out.println(com1.compare(22, 33));

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

      Comparator<Integer> com2 = (o1, o2) -> {
      System.out.println(o1);
      System.out.println(o2);
      return o1.compareTo(o2);
      };

      System.out.println(com2.compare(22, 33));
      }

      // 语法格式六:当Lambda体只有一条语句时,return与大括号若有,则都可以省略
      @Test
      public void test6() {
      Comparator<Integer> com1 = new Comparator<Integer>() {
      @Override
      public int compare(Integer o1, Integer o2) {

      return o1.compareTo(o2);
      }
      };

      System.out.println("*************************");
      Comparator<Integer> com2 = (o1, o2) -> o1.compareTo(o2);
      System.out.println(com2.compare(32, 26));
      }
      }

方法引用、构造器引用、数组引用

方法引用

  1. 举例:
    1. Integer :: compare;
  2. 方法引用的理解:
    1. 可以看做是基于lambda表达式的进一步刻画。
    2. 当需要提供一个函数式接口的实例时,我们可以使用lambda表达式提供此实例。
      1. 当满足一定的条件的情况下,我们还可以使用方法引用或构造器引用去替换lambda表达式。
  3. 方法引用的本质:
    1. 方法引用作为了函数式接口的实例。
  4. 格式:
    1. 情况1:对象::实例方法
      1. 要求:函数式接口中的抽象方法a与内部实现时调用的某个对象的某个方法b的形参列表和返回值类型都相同,此时可以考虑使用方法b实现对方法a的替换、覆盖。此替换和覆盖就是方法引用,且方法a和方法b是非静态方法,所以需要对象进行调用。
    2. 情况2:类::静态方法
      1. 要求:函数式接口中的抽象方法a与内部实现时调用的某个类的某个静态方法b的形参列表和返回值类型都相同,此时可以考虑使用方法b实现对方法a的替换、覆盖。此替换和覆盖就是方法引用,且方法b是静态方法,所以需要类进行调用。
    3. 情况3:类::实例方法
      1. 要求:函数式接口中的抽象方法a与其内部实现时调用的对象的某个方法b的返回值类型相同。同时,抽象方法a中有n个参数,方法b中有n-1个参数,且抽象方法a的第一个参数作为方法b的调用者,且抽象方法a的后n-1个参数与方法b的后n-1个参数类型相同,则可以考虑使用方法b对方法a进行替换或覆盖。

具体代码展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package com.geo.learning02.reference;

import com.geo.learning02.reference.data.Person;
import org.junit.Test;

import java.util.Comparator;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
* @author yuxiaohong
* @package com.geo.learning02.reference
* @date 2023/12/1 12:19
* @description
*/
public class MethodRefTest {
// 情况1:对象::实例方法
@Test
public void test1() {
// 正常调用
Consumer<String> con1 = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con1.accept("hello");
System.out.println("*************************");

// lambda表达式
Consumer<String> con2 = s -> System.out.println(s);

// 方法引用
Consumer<String> con3 = System.out::println;
}

@Test
public void test2() {
Person person = new Person("tom", 23);
// 正常调用
Supplier<String> sup1 = new Supplier<String>() {
@Override
public String get() {
return person.getName();
}
};
System.out.println(sup1.get());

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

// lambda表达式
Supplier<String> sup2 = () -> person.getName();
System.out.println(sup2.get());

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

// 方法引用
Supplier<String> sup3 = person::getName;
System.out.println(sup3.get());
}

// 情况2:类::静态方法
@Test
public void test3() {
Comparator<Integer> com1 = new Comparator<Integer>() {
// 1.正常调用
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};

System.out.println(com1.compare(23, 25));
System.out.println("***************************");

// 2.使用lambda表达式
Comparator<Integer> com2 = (o1, o2) -> o1.compareTo(o2);
System.out.println(com2.compare(22, 21));
System.out.println("***************************");
// 3.使用方法引用
Comparator<Integer> com3 = Integer::compare;
System.out.println(com3.compare(0, 3));
}

// Function中的R apply()
@Test
public void test4() {

// 1.正常调用
Function<Double, Long> fun1 = new Function<Double, Long>() {
@Override
public Long apply(Double aDouble) {
return Math.round(aDouble);
}
};
// 2.使用lambda表达式
Function<Double, Long> fun2 = aDouble -> Math.round(aDouble);

// 3.使用方法引用
Function<Double, Long> fun3 = Math::round;

}

// 情况3:类::实例方法
@Test
public void test5() {
// 1.
Comparator<String> com1 = new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
};

// 2.lambda
Comparator<String> com2 = (o1, o2) -> o1.compareTo(o2);

// 3.方法引用
Comparator<String> com3 = String::compareTo;
System.out.println(com3.compare("abc", "abc"));
}

@Test
public void test6() {
BiPredicate<String, String> bipre1 = new BiPredicate<String, String>() {
@Override
public boolean test(String s1, String s2) {
return s1.equals(s2);
}
};

// 2.
BiPredicate<String, String> bipre2 = (s1, s2) -> s1.equals(s2);

// 3.方法引用
BiPredicate<String, String> bipre3 = String::equals;
}

@Test
public void test7() {
Person person = new Person("xiaojie", 23);
Function<Person, String> fun1 = new Function<Person, String>() {
@Override
public String apply(Person person) {
return person.getName();
}
};
fun1.apply(new Person());

// 2.lambda
Function<Person, String> fun2 = person1 -> person1.getName();
fun2.apply(person);

// 3.方法引用
Function<Person, String> fun3 = Person::getName;
System.out.println(fun3.apply(person));

}
}

构造器引用

  1. 构造器引用
    1. 格式:类名::new
    2. 说明:
      1. 调用了类名对应的类中的某一个确定的构造器。
      2. 具体调用的类中的构造器取决于函数式接口的抽象方法的形参列表。
  2. 数组引用
    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
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      package com.geo.learning02.reference;

      import com.geo.learning02.reference.data.Person;
      import org.junit.Test;

      import java.util.function.BiFunction;
      import java.util.function.Function;
      import java.util.function.Supplier;

      /**
      * @author yuxiaohong
      * @package com.geo.learning02.reference
      * @date 2023/12/1 17:02
      * @description
      */
      public class ConstructorRefTest {
      Person person = new Person("xiaohong", 24);

      @Test
      public void test1() {
      // 1.
      Supplier<Person> sup1 = new Supplier<Person>() {
      @Override
      public Person get() {
      return new Person();
      }
      };
      sup1.get();

      // 2.lambda

      Supplier<Person> sup2 = () -> new Person();
      sup2.get();

      // 3.构造器引用
      Supplier<Person> sup3 = Person::new;
      }

      @Test
      public void test2() {
      // 1.正常调用
      Function<Integer, Person> fun1 = new Function<Integer, Person>() {
      @Override
      public Person apply(Integer id) {
      return new Person(id);
      }
      };

      // 2.构造器引用
      Function<Integer, Person> fun2 = Person::new;
      }

      @Test
      public void test3() {
      BiFunction<String, Integer, Person> bi1 = new BiFunction<String, Integer, Person>() {
      @Override
      public Person apply(String name, Integer age) {
      return new Person(name, age);
      }
      };
      System.out.println(bi1.apply("li", 23));

      // 2.构造器引用
      BiFunction<String, Integer, Person> bi2 = Person::new;
      System.out.println(bi2.apply("er", 15));
      }

      // 数组引用
      @Test
      public void test4() {
      Function<Integer, Person[]> fun1 = new Function<Integer, Person[]>() {
      @Override
      public Person[] apply(Integer length) {
      return new Person[length];
      }
      };

      // 2.数组引用
      Function<Integer, Person[]> fun2 = Person[]::new;
      fun2.apply(10);
      }
      }

StreamAPI

  1. StreamAPI和集合框架的区别
    1. StereamAPI关注的是多个数据的计算(排序、查找、过滤、映射、遍历等),面向CPU。
    2. 集合关注的是数据的存储,面向内存。
  2. 使用说明
    1. Stream自己不会存储元素。
    2. Stream不会改变源对象。相反,它们会返回一个持有结果的新Streamm
    3. Stream操作是延迟执行的。这意味着它们会等到需要结果的时候才执行,一旦终止操作,就执行中间操作链,并产生结果。
    4. Stream一旦执行了终止操作,就不能再调用其他中间操作或终止操作了。
  3. Stream的执行流程
    1. Stream的实例化
    2. 一系列的中间操作
    3. 执行终止操作

Stream创建的三种方式

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
package com.geo.learning03.streamapi;

import com.geo.learning03.streamapi.data.Employee;
import com.geo.learning03.streamapi.data.EmployeeData;
import org.junit.Test;

import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
* @author yuxiaohong
* @package com.geo.learning03.streamapi
* @date 2023/12/1 18:17
* @description
*/
public class StreamAPITest {
// 1.创建Stream方式一:通过集合
@Test
public void test1() {
List<Employee> list = EmployeeData.getEmployees();
// default Stream<E> stream(),返回一个顺序留
Stream<Employee> stream = list.stream();
// default Stream<E> parallelStream(),返回一个并行流
Stream<Employee> employeeStream = list.parallelStream();
System.out.println(stream);
System.out.println(employeeStream);
}

// 2.创建Stream方式二:通过数组
@Test
public void test2() {
// 调用Arrays类的static<T> Stream<T> stream(T[] array):返回一个流
Integer[] arr = new Integer[]{1, 2, 3, 4, 5};
Stream<Integer> stream = Arrays.stream(arr);
int[] arr1 = new int[]{1, 2, 3, 4, 5};
IntStream stream1 = Arrays.stream(arr1);
}

// 3.创建Stream方式三:通过Stream的of()
@Test
public void test3() {
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
}
}

.. ... ...