JAVA后端全套

JavaSE

尚硅谷Java基础

image-20220427210812626

第一章 java基础

1.1 编程语言介绍

第一代语言  机器语言:指令以二进制代码形式存在。

第二代语言  汇编语言:使用助记符表示一条机器指令。

常用语言对比:

  • C语言:执行效率比较高,一般用来做底层驱动或者操作系统。
  • C++:游戏内核,应用程序。
  • C#:微软、支持的平台是windows,基于.netframework的。
  • Java:世界第一,toibe排行榜。
  • Python:脚本语言,灵活性极高。
  • html:静态网页,网站
  • CSS:美化网页
  • JS:动态网页
  • go语言:谷歌开发
  • Sql:数据库,数据是软件的根本

语言类型:

  • 编译型:一次编译,到处运行。

    执行效率较高,跨平台比较差

  • 解释型:一行一行解释,执行。

    执行效率较低,跨平台性较好。

  • 混合型,譬如Java。

1.2 Java简介

Java是一门程序开发语言. 1991年由sun公司的James Gosling和他的团队一起开发了一个叫Oak的语言. 在1995年更名为Java. 一直沿用至今。

 1991年 Green项目,开发语言最初命名为Oak (橡树)

 1994年,开发组意识到Oak 非常适合于互联网  1996年,发布JDK 1.0,约8.3万个网页应用Java技术来制作

 1997年,发布JDK 1.1,JavaOne会议召开,创当时全球同类会议规模之最  1998年,发布JDK 1.2,同年发布企业平台J2EE

 1999年,Java分成J2SE、J2EE和J2ME,JSP/Servlet技术诞生

 2004年,发布里程碑式版本:JDK 1.5,为突出此版本的重要性,更名为JDK 5.0

 2005年,J2SE -> JavaSE,J2EE -> JavaEE,J2ME -> JavaME

 2009年,Oracle公司收购SUN,交易价格74亿美元

 2011年,发布JDK 7.0

 2014年,发布JDK 8.0,是继JDK 5.0以来变化最大的版本

 2017年,发布JDK 9.0,最大限度实现模块化

 2018年3月,发布JDK 10.0,版本号也称为18.3

 2018年9月,发布JDK 11.0,版本号也称为18.9

特点:针对C++简化了很多

czQzvV.png

czQxg0.png

czQjCn.png

image-20220321160016813


诞生:java之父James Gosling团队在开发”Green”项目时,发现C缺少垃圾回收系统,还有可移植的安 全性、分布程序设计和多线程功能。最后,他们想要一种易于移植到各种设备上的平台。 Java确实是从C语言和C++语言继承了许多成份,甚至可以将Java看 成是类C语言发展和衍生的产物。比如Java语言的变量声明,操作符 形式,参数传递,流程控制等方面和C语言、C++语言完全相同。但同 时,Java是一个纯粹的面向对象的程序设计语言,它继承了C++语言 面向对象技术的核心。Java舍弃了C语言中容易引起错误的指针(以 引用取代)、运算符重载(operator overloading)、多重继承 (以接口取代)等特性,增加了垃圾回收器功能用于回收不再被引用 的对象所占据的内存空间。JDK1.5又引入了泛型编程(Generic Programming)、类型安全的枚举、不定长参数和自动装/拆箱。


Java特性:

  • Java语言是易学的。Java语言的语法与C语言和C++语言很接近,使得大多数程序员 很容易学习和使用Java。
  • Java语言是强制面向对象的。Java语言提供类、接口和继承等原语,为了简单起见, 只支持类之间的单继承,但支持接口之间的多继承,并支持类与接口之间的实现机制 (关键字为implements)。
  • Java语言是分布式的。Java语言支持Internet应用的开发,在基本的Java应用编 程接口中有一个网络应用编程接口(java net),它提供了用于网络应用编程的类 库,包括URL、URLConnection、Socket、ServerSocket等。Java的RMI(远程 方法激活)机制也是开发分布式应用的重要手段。
  • Java语言是健壮的。Java的强类型机制、异常处理、垃圾的自动收集等是Java程序 健壮性的重要保证。对指针的丢弃是Java的明智选择。
  • Java语言是安全的。Java通常被用在网络环境中,为此,Java提供了一个安全机 制以防恶意代码的攻击。如:安全防范机制(类ClassLoader),如分配不同的名字空间以防替代本地的同名类、字节代码检查。
  • Java语言是体系结构中立的。Java程序(后缀为java的文件)在Java平台上被 编译为体系结构中立的字节码格式(后缀为class的文件),然后可以在实现这个 Java平台的任何系统中运行
  • Java语言是解释型的。如前所述,Java程序在Java平台上被编译为字节码格式, 然后可以在实现这个Java平台的任何系统的解释器中运行。
  • Java是性能略高的。与那些解释型的高级脚本语言相比,Java的性能还是较优的。
  • Java语言是原生支持多线程的。在Java语言中,线程是一种特殊的对象,它必须 由Thread类或其子(孙)类来创建。

java语言特点:

  • 特点一:面向对象

    • 两个基本概念:类、对象
    • 三大特性:封装、继承、多态
  • 特点二:健壮性

    • 吸收了C/C++语言的优点,但去掉了其影响程序健壮性的部分(如指针、内存的申请与 释放等),提供了一个相对安全的内存管理和访问机制
  • 特点三:跨平台性

    • 跨平台性:通过Java语言编写的应用程序在不同的系统平台上都可以运行。“Write once , Run Anywhere”

    • 原理:只要在需要运行 java 应用程序的操作系统上,先安装一个Java虚拟机 (JVM Java Virtual Machine) 即可。由JVM来负责Java程序在该系统中的运行。

    • 因为有了JVM,同一个Java 程序在三个不同的操作系统中都可以执行。这 样就实现了Java 程序的跨平台性。

      image-20220321160747726


Java的两个核心机制:

  • Java虚拟机 (Java Virtal Machine)

    JVM是一个虚拟的计算机,具有指令集并使用不同的存储区域。负责执行指令,管理数据、内存、寄存器。

    对于不同的平台,有不同的虚拟机。

    只有某平台提供了对应的java虚拟机,java程序才可在此平台运行。

    Java虚拟机机制屏蔽了底层运行平台的差别,实现了“一次编译,到处运行”。

    image-20220321161004124
  • 垃圾收集机制 (Garbage Collection)

    不再使用的内存空间应回收—— 垃圾回收。

    • 在C/C++等语言中,由程序员负责回收无用内存。
    • Java 语言消除了程序员回收无用内存空间的责任:它提供一种系统级线程跟踪存储空 间的分配情况。并在JVM空闲时,检查并释放那些可被释放的存储空间。

    垃圾回收在Java程序运行过程中自动进行,程序员无法精确控制和干预。

    Java程序还会出现内存泄漏和内存溢出问题吗?会!


Java相关概念:

  • JVM(Java Virtual Machine):Java虚拟机,用来模拟统一的硬件平台环境,供Java程序运行的一个软件。
  • JRE(Java Runtime Environment):Java运行时环境,里面包含了JVM和一些Java运行时的类库。
  • JDK(Java Development Kit):Java开发工具包、包含JRE和Java开发工具包。
  • image-20220321161429668

常用DOS命令:

image-20220321155656154

1.3 第一个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
/*
对第一个java程序进行总结
1. java程序编写-编译-运行的过程
编写:我们将编写的java代码保存在以".java"结尾的源文件中
编译:使用javac.exe命令编译我们的java源文件。格式:javac 源文件名.java
运行:使用java.exe命令解释运行我们的字节码文件。 格式:java 类名

2.
在一个java源文件中可以声明多个class。但是,只能最多有一个类声明为public的。
而且要求声明为public的类的类名必须与源文件名相同。

3. 程序的入口是main()方法。格式是固定的。

4. 输出语句:
System.out.println():先输出数据,然后换行
System.out.print():只输出数据

5.每一行执行语句都以";"结束。

6.编译的过程:编译以后,会生成一个或多个字节码文件。字节码文件的文件名与java源文件中的类名相同。
C:\ProgramData\Oracle\Java\javapath
.;%JAVA_HOME%\dt.jar;%JAVA_HOME%\lib\tools.jar;

*/
public class Hello {
public static void main(String[] args) {//public static void main(String a[]) {//arguments:参数
System.out.print("Hello World!");
System.out.println();//换行
System.out.println("Hello World!");
}
}

class Person{

}

class Animal{

}

public class Hello:这一行表示创建一个类,类是Java程序运行的最小单元,必须要有类。

public static void main(String[] args):Java程序入口,一切从这里开始执行。

System.out.println("Hello world!!!"):让程序打印一句话,内容是引号中的内容。

注意:Java程序严格区分大小写,写错一个字母都不行。

1.4 Java注释

一、单行注释

1
// 单行注释,这一行执行时,会被JVM运行的时候过滤掉。

二、多行注释

1
2
3
4
/*
多行注释
一次注释多行代码
*/

三、文档注释

1
2
3
4
/**
* 文档注释
* 后期可以使用Javadoc命令生成api文档
*/

注释场景:

  • 核心的解决方案
  • 比较难懂的业务逻辑
  • 记录代码存在经历

1.6 变量

变量: 可以发生改变的量. 程序运行过程中产生的中间值。供后面的程序使用。

java中的变量:数据类型 变量名 = 值;

  • int 类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class type {
    public static void main(String[] args) {
    int a = 20, b = 20;
    int c = a + b;

    System.out.println(a);
    System.out.println(b);
    System.out.println("a + b = " + c);

    int d = a - b;
    System.out.println("a - b = " + d);

    int e = a * b;
    System.out.println("a * b = " + e);

    int f = a / b;
    System.out.println("a / b = " + f);
    }
    }

  • double型:小数,计算结果一定也是小数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class type {
    public static void main(String[] args) {
    double a = 20, b = 20;
    double c = a + b;

    System.out.println(a);
    System.out.println(b);
    System.out.println("a + b = " + c);

    double d = a - b;
    System.out.println("a - b = " + d);

    double e = a * b;
    System.out.println("a * b = " + e);

    double f = a / b;
    System.out.println("a / b = " + f);
    }
    }

  • String字符串:注意(String)的S必须大写。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class type {
    public static void main(String[] args) {
    String s1 = "为什么不在自信一点?";
    String s2 = "你快变成你自己最讨厌的样子了,zjl,你知道吗?";
    String s3 = "为什么你总是因为害怕失败而不敢去尝试?";

    String s4 = s3 + "你应该知道因为这样你失去了多少东西";
    System.out.println(s4);
    }
    }
  • bollean布尔值:非真即假。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class type {
    public static void main(String[] args) {
    boolean a = true;
    boolean b = false;

    System.out.println(a);
    System.out.println(b);

    boolean c = 1 > 2;
    boolean d = 2 > 1;
    System.out.println(c);

    int e = 10;
    System.out.println(e == 10);
    System.out.println(e != 10);
    }
    }

中文乱码:UTF-8 –> GBK

1.7 用户输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.Scanner;//导入Scanner包

public class type {
public static void main(String[] args) {
// 准备一个Scanner对象20
Scanner sc = new Scanner(System.in);

// 数据类型
// 获取数据
System.out.println("请输入你的年龄:");
int my_age = sc.nextInt();
System.out.println("录入年龄 " + my_age);

System.out.println("请输入你的余额:");
double my_money = sc.nextDouble();
System.out.println("录入余额 " + my_money);

// 获取字符串,以回车键为止。
System.out.println("请输入你的姓名:");
String my_name = sc.nextLine();
System.out.println("录入姓名" + my_name);

}
}

注意:不出意外,会自动跳过录入姓名项,因为和C语言类似,我们没有去清除Enter,会导致后续录入字符串读入Enter导致直接结束录入,要看效果,把前面的两个删除即可.

1.8 If语句

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
import java.util.Scanner;//导入Scanner包

public class type {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);

while (true) {
System.out.println("请输入你的考研成绩");
int score = sc.nextInt();
if (score > 500 || score < 0) {
System.out.println("请输入正确成绩(0~500)");
} else if (score < 315) {
System.out.println("请好好学习,明年再来!");
} else if (score >= 350 && score < 400) {
System.out.println("初始通过,计算机学院!");
} else if (score >= 400 && score < 420) {
System.out.println("初始通过,通信!");
} else if (score > 420) {
System.out.println("初始通过,专业任选!");
}

}
}
}

1.9 while循环(略)

1.10 作业

猜数字游戏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

//猜数游戏
import java.util.Scanner;//导入Scanner包
import java.util.Random;//导入Random包

public class type {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Random rm = new Random();

System.out.println("欢迎来到猜数游戏,你要猜的数在0~99之间。");
int n = rm.nextInt(100); // 生成0~99之间的随机数
int times = 0;
while (true) {
System.out.println("请输入你的猜的数字:");
int guess = sc.nextInt();
if (guess == n) {
times = times + 1;
System.out.println("答对了,猜的次数:" +times );
System.out.println("是否继续?(1/0)");
int answer = sc.nextInt();
if (answer == 0) {
System.exit(1);
} else {
System.out.println("欢迎来到猜数游戏,你要猜的数在0~99之间。");
n = rm.nextInt(100); // 生成0~99之间的随机数
times = 0;
}
} else if (guess > n) {
times = times + 1;
System.out.println("猜大了!");
} else if (guess < n) {
times = times + 1;
System.out.println("猜小了!");
}
}
}
}

1.11二进制转化(略)

1.12 编码

现代计算机编码类型:

  • ASCII码:最开始编码,只支持英文,8比特。
  • ANSI码:对ASCII的扩展,只是一套标准,不同的国家使用该标准对自己国家的文字进行编码。
  • GBK:中国ANSI编码,起名GB码,后期扩展成GBK,GBK兼容ASCII,16比特。
  • Unicode:由于各个国家的编码不相容,由国际标准化组织ISO和统一码联盟制作了Unicode,对各国文字进行统一编码,32比特。
  • UTF-8:由于编入很多国家文字,占用内存很大,网络传输占用带宽,于是产生了UTF-8,是可变长度的Unicode,全称:Unicode Transformation Format。对于不同的文字信息,UTF的长度不同,英文8比特,欧洲文字16比特,中文24比特。

1.13 Java基础数据类型

  • 基础数据类型:
    • 整数
      • byte:字节,1字节,范围-128~127
      • short短整数:2字节,-32768~32767
      • int整数:4字节
      • long长整数:8字节
    • 浮点数
      • float:单精度浮点数,精度低。
      • double:双精度浮点数,精度高。
    • 字符
      • char:字符类型,表示单个字符,2字节。
    • 布尔
      • boolean:布尔类型,两个取值(true,false)
  • 引用数据类型:除开基本数据类型以外,其他数据类型全是引用数据类型,最典型的:字符串String。
    • 字符串可以执行加法运算,表示字符串拼接。
    • 当出现非字符串和字符串进行相加的时候. 首先把非字符串自动转化成字符串然后再执行拼接操作。
  • 转义字符:具有特定含义的字符串。
    • \n:换行符
    • \t:水平制表符
    • \':'
    • \":\

1.14 基础数据类型之间的转化

一般是对数字进行转化,char和boolean类型一般不参与转化。

我们把数据类型进行排序, 按照能表示的数据量的范围进行排序:

  • byte->short-> int ->long->float->double
  • long排在float前面,因为整数是有数据量的范围的,而小数是没有的,

从小数据类型向大数据类型转化是直接转化的。

1
2
3
4
5
6
7
8
9
10
11
12
public class type {
public static void main(String[] args) {
byte a = 10;
int b = a;

System.out.println(b); //10

long c = b;

System.out.println(c); //10
}
}

大数据类型向小数据类型转化,需要强制类型转换

1
2
3
4
5
6
7
8
9
public class type {
public static void main(String[] args) {
long a = 3000000;
int b = (int) a;

System.out.println(b); // 10
}
}

1.15 基本数据类型之间的运算

  • 相同数据类型之间运算,得到的一定是这个数据类型。

    • int = int +int
  • 不同数据类型之间运算:首先把小的数据类型转化为大的数据类型·1,然后进行计算,得到的结果一定时大的数据类型。

    • long = long + int
  • 特殊的byte、short、char:执行算数运算时,首先会转化为int类型再进行计算,因为这样是安全的。

    int = int + char

    int = byte + byte

    1
    2
    3
    4
    5
    6
    7
    public class type {
    public static void main(String[] args) {
    char a = 'A';
    int b = 100;
    System.out.println(a+b); // 10
    }
    }

    输出:165;证明是这样的。

    • 相关报错:

      1
      2
      3
      short a = 1;
      short b = a + 1;
      System.out.println(b);

      此时第2行代码一定会报错。

      因为s1是short类型。而short类型计算的时候会自动转换成int进行计算。并且,所有的数字, 默认都可以看做是int类型, 默认的小数都可以看做是double类型,所以第二行计算的结果应该是int类型。把int类型的数据赋值给short类型的变量,一定会报错的,此处必须要进行强制类型转换

1.16 基本运算符

  • 算数运算符:+ - * /

  • 复制运算符:

    =

    +=

    -=

    *=

    /=

    %=

  • 比较运算符:> < >= <= != ==

    结果一定是boolean类型。

    如果是字符串比较,不能用 ==,要用equals

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class type {
    public static void main(String[] args) {
    String str1 = "我是憨批";
    String str2 = "我是憨批";
    System.out.println(str1 == str2);
    System.out.println(str1.equals(str2));

    String str3 = new String("我是憨批");
    String str4 = new String("我是憨批");
    System.out.println(str3 == str4);
    System.out.println(str3.equals(str4));
    }
    }

    输出:

    1
    2
    3
    4
    true
    true
    false
    true
  • 逻辑运算符:

    &:并且

    |:或者

    ! :取反

    &&:短路与

    ||:短路或

    |, || 或者的含义, 左右两端同时为假,结果才能是假。

    &, && 并且的含义. 左右两端同时为真,结果才能是真。

    短路:再执行并/或运算时,如果式子的前面已经得到了结果,那么式子的后半段就不再继续进行运算了,效率较高。(与:有假就停,或:有真就停)。

  • 一元运算

    a++;

    a–;

1.17 switch语句

switch变量必须是int或者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
public class type {
public static void main(String[] args) {
Scanner birth_year = new Scanner(System.in);
System.out.println("输入出生年份:");

int a = birth_year.nextInt();

switch (a) {
case 2001:
case 2013:
case 1989:
System.out.println("属蛇");
break;
case 2002:
case 2014:
case 1990:
case 1966:
case 1978:
System.out.println("属马");
break;
case 2000:
case 2012:
case 1988:
System.out.println("属龙");
break;
case 1999:
case 1987:
case 2011:
System.out.println("属兔");
break;
case 1998:
case 1986:
case 2010:
System.out.println("属虎");
break;
case 1997:
case 1985:
case 2009:
System.out.println("属牛");
break;
default:
System.out.println("暂时未录入");
break;
}
}
}

注意:break一定不要忘记,如果没有及时跳出switch,会发生case穿透现象。

case穿透:如果有一个case匹配成功,则后面的case不会继续判断,而是直接执行后续的所有case成立语句。

1.18 for循环(略)

死循环:

1
2
3
for(;;){
System.out.println("我是憨憨!");
}

1.19 do……while循环

无限循环:

1
2
3
do{
……
}while(true);

注意不要忘记while后面的 ;

1.20 break和continue

break:停止当前循环,用于while/for/do……while循环

contiune:停止当前这次循环,跳至循环体代码末尾。(循环过程中,某些数据不需要处理时,可以使用它跳过。)

1
2
3
4
5
6
for (int i = 0 ; i < 10; i++){
if(i == 7){
continue; // 我不喜欢7
}
System.out.println(i);
}

1.21 数组

声明时需要给出数据类型。

1
2
3
4
5
6
7
8
9
10
public class type {
public static void main(String[] args) {
String[] arr = new String[10];//创建了10个位置,可以装载10个字符串,当前是空的。
String[] game = {"LOL","DNF","云顶","当铺人生"};
String[] smallGames = new String[]{"张加林是憨批","张加林是傻逼","张加林是胆小鬼","张加林是废物"};

System.out.println(game[1]);
System.out.println(smallGames[2]);
}
}

数组越界、数组遍历,略

1.22 数组算法

程序 = 算法 + 数据结构

  • 质数:只能被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
    import java.util.Scanner;//导入Scanner包

    public class type {
    public static void main(String[] args) {
    Scanner object = new Scanner(System.in);
    boolean flag;

    while (true) {
    System.out.println("请输入一个数( 输入0退出):");
    int a = object.nextInt();
    if (a == 0) {
    break;
    }
    else {
    flag = false;
    for (int i = a / 2; i > 1; i--) {
    if (a % i == 0) {
    flag = true;
    System.out.println(a + "不是质数。");
    break;
    }
    }
    if(!flag)
    System.out.println(a + "是质数。");
    }
    }
    }
    }
  • 求数组最大值(略)

  • 计算数组平方和(略)

  • 七大经典排序算法

    • 冒泡排序(js写过,不太难,略)

1.23 Scanner类应用

从键盘获取不同类型的变量:需要使用Scanner类。

实现步骤:

  • 导包:import java.util.Scanner
  • Scanner实例化:Scanner scan = new Scanner(System in);
  • 调用Scanner类的相关方法(next() / nextXxx()),来获取指定类型的变量。

注意: 需要根据相应的方法,来输入指定类型的值。如果输入的数据类型与要求的类型不匹配时,会报异常:InputMisMatchException 导致程序终止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//1.导包:import java.util.Scanner;
import java.util.Scanner;

class ScannerTest{

public static void main(String[] args){
//2.Scanner的实例化
Scanner scan = new Scanner(System.in);

//3.调用Scanner类的相关方法
System.out.println("请输入你的姓名:");
String name = scan.next();
System.out.println(name);

System.out.println("请输入你的芳龄:");
int age = scan.nextInt();
System.out.println(age);

System.out.println("请输入你的体重:");
double weight = scan.nextDouble();
System.out.println(weight);

System.out.println("你是否相中我了呢?(true/false)");
boolean isLove = scan.nextBoolean();
System.out.println(isLove);

//对于char型的获取,Scanner没有提供相关的方法。只能获取一个字符串
System.out.println("请输入你的性别:(男/女)");
String gender = scan.next();//"男"
char genderChar = gender.charAt(0);//获取索引为0位置上的字符
System.out.println(genderChar);
}
}

第二章 面向对象

第三章 异常处理

throw与throws的区别:

  • throw:生成一个异常对象,并抛出,使用在方法内部<->自动抛出异常对象。
  • throws:处理异常的方式使用在方法声明处的末尾<–>try...catch...finally

第四章 多线程

第五章 java常用类

5.1 字符串相关类

String特性

  • String类:代表字符串。Java程序中所有字符串字面值(如:”abc”)为此类的实例实现。

  • String是一个final类,代表不可变的字符序列,不能被继承。

    1
    2
    3
    4
    5
    6
    7
    public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence{
    // The value is used for character storage.
    private final char value[];

    private int hash; // Default 0
    }

    String对象的字符内容是存储在一个字符数组value[]中的。

  • 内存:

    image-20220427224311689

String对象的创建以及存储方式

1
2
3
4
5
6
7
8
9
10
11
12
String str = "hello";

// this.value = new char[0]
String s1 = new String();

// this.value = original.value;
String s2 = new String(String original);

// this.value = Arrays.copyOf(value, value.lenth)
String s3 = new String(char [] a);

String s4 = new String(char[] a, int startIndex, int count);
image-20220427225143549

所以要分清楚到底是在常量池中,还是在堆空间中即可。

image-20220427225528675 image-20220427225659491 image-20220427225716975 image-20220427230008699

答案:True,画图分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class StringTest {
String str = new String("good");
char[] ch = { 't', 'e', 's', 't' };
public void change(String str, char ch[]) {
str = "test ok";
ch[0] = 'b';
}
public static void main(String[] args) {
StringTest ex = new StringTest();
ex.change(ex.str, ex.ch);
System.out.print(ex.str + " and "); // good
System.out.println(ex.ch); // best
/*
由于值传递机制,但是String类有不可变性
*/
}
}

字符串拼接

image-20220427231157459

结论:

  • 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。

  • 只要其中有一个是变量,结果就在堆中

  • 如果拼接的结果调用intern()方法,返回值就在常量池中

    1
    String s8 = (s1 + s2).intern();
    1
    2
    final String s = "Asuka";
    String s1 = s + "Ayanami"; // 存储在常量池中,因为都是常量

JVM中涉及字符串的内存结构

一般用Hot Spot

一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,保存所有引用类型的真实信息,以便执行器执行,堆内存分为:

  • 新生区
  • 养老区
  • 永久存储区(方法区): 被加载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放1此区域所占用的内存。但是一般不认为永久存储区堆的一部分。
    • JDK8中字符串常量池在方法区中。

常用API

  • int length():返回字符串的长度: return value.length

  • char charAt(int index): 返回某索引处的字符return value[index]

  • boolean isEmpty():判断是否是空字符串:return value.length == 0

  • String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写

  • String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写

  • String trim():返回字符串的副本,忽略前导空白和尾部空白

  • boolean equals(Object obj):比较字符串的内容是否相同

  • boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大 小写

  • String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”

    结果赋值给变量后,返回值会被保存在

  • int compareTo(String anotherString):比较两个字符串的大小

  • String substring(int beginIndex):返回一个新的字符串,它是此字符串的从 beginIndex开始截取到最后的一个子字符串。

  • String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字 符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。

  • boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束

  • boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始

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

  • boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列 时,返回 true

  • int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引

  • int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出 现处的索引,从指定的索引开始

  • int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引

  • int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后 一次出现处的索引,从指定的索引开始反向搜索 注:indexOf和lastIndexOf方法如果未找到都是返回-1

  • String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。

  • String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。

  • String replaceAll(String regex, String replacement) : 使 用 给 定 的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。

  • String replaceFirst(String regex, String replacement) : 使 用 给 定 的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。

  • boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。

  • String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。

  • String[] split(String regex, int limit) :根据匹配给定的正则表达式来拆分此 字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。

String类型转换

  • String与基本数据类、包装类

    • 字符串-->基本数据类型、包装类

      调用包装类的静态方法:包装类型.parseXxx(str)

    • 基本数据类型、包装类-->字符串

      调用String的重载的String.valueOf(xxx)方法

  • String与字符串数组转换

    • 字符数组-->字符串

      String 类的构造器:String(char[])String(char[],int offset,int length)分别用字符数组中的全部字符和部分字符创建字符串对象。

    • 字符串-->字符数组

      public char[] toCharArray():将字符串中的全部字符存放在一个字符数组中的方法。

      public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin):提供了将指定索引范围内的字符串存放到数组中的方法。

  • 字符串与字节数组转换

    • 字节数组-->字符串:解码

      String(byte[]):通过使用平台的默认字符集解码指定的 byte 数组,构 造一个新的 String。

      String(byte[],int offset,int length) :用指定的字节数组的一部分, 即从数组起始位置offset开始取length个字节构造一个字符串对象。

    • 字符串-->字节数组:编码

      public byte[] getBytes():使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。

      public byte[] getBytes(String charsetName) :使用指定的字符集将 此 String 编码到 byte 序列,并将结果存储到新的 byte 数组。

UTF-8中一个汉字占用三个字节。

GBK中一个汉字占用两个字节。

乱码:编码方式和解码方式不同。

StringBuffer类

  • java.lang.StringBuffer代表可变的字符序列,JDK1.0中声明,可以对字符 串内容进行增删,此时不会产生新的对象。
  • 很多方法与String相同。
  • 作为参数传递时,方法内部可以改变值。
1
2
3
4
5
6
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence{
char[] value; // value没有final关键字,value可以不断扩容
int count; // 记录有效字符个数
}
  • StringBuffer类不同于String,其对象必须使用构造器生成。

    有三个构造器:

    • StringBuffer():初始容量为16的字符串缓冲区
    • StringBuffer(int size):构造指定容量的字符串缓冲区
    • StringBuffer(String str):将内容初始化为指定字符串内容
image-20220428011030895

空参构造初始化容量

image-20220428011053002

字符串参数构造初始化容量

image-20220428011122282

底层扩容,扩容后如果不够,就直接把总长度作为新容量

image-20220428011632304 image-20220428011658926

length返回机制

  • 常用方法:

    StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接

    StringBuffer delete(int start,int end):删除指定位置的内容

    StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str

    StringBuffer insert(int offset, xxx):在指定位置插入xxx

    StringBuffer reverse():把当前字符序列逆转

    • 如上这些方法支持方法链操作,方法链的原理:
    image-20220428014909180

    ​ 例如:a.append('').append('').delete(1,2).insert(3,'A')

    public int indexOf(String str)

    public String substring(int start,int end)

    public int length()

    public char charAt(int n )

    public void setCharAt(int n ,char ch)

StringBuilder类

StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列,而且 提供相关功能的方法也一样。

image-20220428010442474

空参构造初始化容量

image-20220428010537974

字符串参数构造初始化容量

image-20220428010343982

底层扩容

  • String、StringBuilder、StringBuffer三者之间的区别?

​ 答:

​ String:不可变的字符序列。

​ StringBuffer:可变的字符序列;线程安全的,效率低。

​ StringBuilder:可变的字符序列,JDK5.0新增;线程不安全,效率高。

​ 三者底层都是使用char[]存储。

​ 效率:StringBuilder > StringBuffer > Stirng

  • 源码分析

    1
    2
    3
    String str = new String(); // value = new char[0];
    String str1 = new String("abc"); // value = new char[]{'a', 'b', 'c'}

  • 需要频繁修改字符序列,就不要用String,考虑StringBuffer和StringBuffer

  • 开发中,尽量用

    1
    2
    3
    4
    5
    public synchronized void ensureCapacity(int minimumCapacity) {
    if (minimumCapacity > value.length) {
    expandCapacity(minimumCapacity);
    }
    }

    构造器先初始化容量,避免扩容的巨大开销。

5.2 JDK 8之前的日期时间API

image-20220428015704795

java.lang.System类

System类提供的public static long currentTimeMillis()用来返回当前时 间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。

此方法适于计算时间差。

计算世界时间的主要标准有:

  • UTC(Coordinated Universal Time)

  • GMT(Greenwich Mean Time)

  • CST(Central Standard Time

java.util.Date类

表示特定的瞬间,精确到毫秒。

构造器:

  • Date():使用无参构造器创建的对象可以获取本地时间。
  • Date(long date)

常用方法:

  • getTime():返回1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象 表示的毫秒数。

  • toString():把此 Date 对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat),zzz是时间标准。

    其他很多方法已经过时

java.sql.Date:对应数据库中的日期类型的变量,跟数据库交互时使用。

ublic class Date extends java.util.Date

1
java.sql.Date date = new java.sql.Date(35235325345L);

两种Date类型相互转换,不能直接强制类型转换,因为子类强转为父类是不能赋值给一个父类变量的

1
2
3
Date date = new Date();
java.sql.Date dateSql = (java.sql.Date) date; // Error
java.sql.Date dateSql = new java.sql.Date(date.getTime()); // Ok

java.text.SimpleDateFormat类

  • Date类的API不易于国际化,大部分被废弃了。java.text.SimpleDateFormat 类是一个不与语言环境有关的方式来格式化和解析日期的具体类。
  • 它允许进行格式化:日期-->文本;解析:文本-->日期
  • 格式化:
    • SimpleDateFormat() :默认的模式和语言环境创建对象
    • public SimpleDateFormat(String pattern):该构造方法可以用参数pattern 指定的格式创建一个对象,该对象调用:
    • public String format(Date date):方法格式化时间对象date
  • 解析:
    • public Date parse(String source):从给定字符串的开始解析文本,以生成 一个日期。
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
/*
output:
java.text.SimpleDateFormat@b5341f2a
22-5-2 下午10:31
Mon May 02 22:31:00 CST 2022
2022年05月02日 星期一 22:31:12
Fri Aug 08 08:08:08 CST 2008
* */
@Test
public void test2() throws ParseException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
System.out.println(simpleDateFormat);

/* 默认格式化 */
Date date = new Date();
String formatDate = simpleDateFormat.format(date);
System.out.println(formatDate);
Date parse = simpleDateFormat.parse(formatDate);
System.out.println(parse);

/* 指定格式化 */
SimpleDateFormat simpleDateFormat1 = new SimpleDateFormat("yyyy年MM月dd日 EEE HH:mm:ss");
System.out.println(simpleDateFormat1.format(date));

try{
// 实例化一个指定的格式化对象
Date date2 = simpleDateFormat1.parse("2008年08月08日 星期一 08:08:08");
System.out.println(date2);
}catch(ParseException e){
e.printStackTrace();
}
}

java.util.Calender(日历)类

  • Calendar是一个抽象基类,主要用于完成日期字段之间相互操作的功能。

  • 获取Calendar实例的方法

    • 使用Calendar.getInstance()方法
    • 调用它的子类GregorianCalendar的构造器
  • 一个Calendar的实例是系统时间的抽象表示,通过get(int field)方法来取得想要的时间信息。比如:YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY、MINUTE、SECOND

    • public void set(int field, int value)
    • public void add(int field, int amount)
    • puclic final Date getTime()
    • public final void setTime(Date date)
  • 注意:

    • 获取月份时:一月是0,二月是1,以此类推,12月是11
    • 获取星期时:周日是1,周二是2……周六是7
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
/*
output:
Mon May 02 22:50:42 CST 2022
Sat Jun 04 09:03:55 CST 1977
当前时间日设置为8后,时间是:Wed Jun 08 09:03:55 CST 1977
当前时间+2小时后,时间是:Wed Jun 08 11:03:55 CST 1977
当时间减2个月,时间是:Fri Apr 08 11:03:55 CST 1977
* */
@Test
public void test3(){
Calendar calendar = Calendar.getInstance(); // 单例模式

// 从一个Calendar对象获取Date对象
Date date = calendar.getTime(); // new Date(getTimeInMillis());
System.out.println(date);
// 使用给定的Date设置此Calendar的时间
date = new Date(234234235235L);
System.out.println(date);

calendar.setTime(date); // setTimeInMillis(date.getTime());
calendar.set(Calendar.DAY_OF_MONTH,8);
System.out.println("当前时间日设置为8后,时间是:" + calendar.getTime());
calendar.add(Calendar.HOUR, 2);
System.out.println("当前时间+2小时后,时间是:" + calendar.getTime());
calendar.add(Calendar.MONTH, -2);
System.out.println("当时间减2个月,时间是:" + calendar.getTime());
}

5.3 JDK 8中新日期时间API

新API出现背景:

  • 可变性:像日期和时间这样的类应该是不可变的
  • 偏移性:Date中的年份是从1900年开始的,而月份都从0开始
  • 格式化:格式化只对Date有用,Calendar则不行

新时间API:

  • java.time:包含值对象的基础包
  • java.time.chrono:提供对不同的日历系统的访问
  • java.time.format:格式化和解析时间和日期
  • java.time.temporal:包括底层框架和扩展特性
  • java.time.zone:包含时区支持的类

说明:大多数开发者只会用到基础包和format包,也可能会用到temporal包。因此,尽 管有68个新的公开类型,大多数开发者,大概将只会用到其中的三分之一。

LocalDate、LocalTime、LocalDateTime

  • LocalDate、LocalTime、LocalDateTime类是其中较为重要的几个类,它们的实例都是不可变对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。 它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区 相关的信息。

    • LocalDate代表IOS格式(yyyy-MM-dd)的日期,可以存储生日、纪念日等日期。

    • LocalTime表示一个时间,而不是日期。

    • LocalDateTime是用来表示日期和时间的,这是一个最常用的类之一

      ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示 法,也就是公历。

  • 实例化
  • 获取日期
  • 修改日期
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
/*
* outputs:
* 2022-05-10
16:53:45.703
2022-05-10T16:53:45.703
2022-05-11
12:14:59
2022-05-09T12:07:08
11
131
WEDNESDAY
MAY
2022
5
2022-05-01
2022-10-27
0200-05-11
2022-12-11
2022-05-23
2022-08-03
2023-05-11
2034-05-11
* */
@Test
public void test4(){
/* 实例化 */
// now()
LocalDate now = LocalDate.now();
LocalTime now1 = LocalTime.now();
LocalDateTime now2 = LocalDateTime.now();
System.out.println(now);
System.out.println(now1);
System.out.println(now2);

// of()
LocalDate time = LocalDate.of(2022, 5, 11);
LocalTime time1 = LocalTime.of(12, 14, 59);
LocalDateTime time2 = LocalDateTime.of(2022, 5, 9, 12, 7, 8);
System.out.println(time);
System.out.println(time1);
System.out.println(time2);
System.out.println(time.getDayOfMonth());
System.out.println(time.getDayOfYear());
System.out.println(time.getDayOfWeek());
System.out.println(time.getMonth());
System.out.println(time.getYear());
System.out.println(time.getMonthValue());

// 修改对象
System.out.println(time.withDayOfMonth(1));
System.out.println(time.withDayOfYear(300));
System.out.println(time.withYear(200));
System.out.println(time.withMonth(12));

// 加减,只演示加
System.out.println(time.plusDays(12));
System.out.println(time.plusWeeks(12));
System.out.println(time.plusMonths(12));
System.out.println(time.plusYears(12));
}

瞬时:Instant

  • Instant:时间线上的一个瞬时点。 这可能被用来记录应用程序中的事件时间戳。
  • 在处理时间和日期的时候,我们通常会想到年,月,日,时,分,秒。然而,这只是时间的一个模型,是面向人类的。第二种通用模型是面向机器的,或者说是连 续的。在此模型中,时间线中的一个点表示为一个很大的数,这有利于计算机 处理。在UNIX中,这个数从1970年开始,以秒为的单位;同样的,在Java中, 也是从1970年开始,但以毫秒为单位。
  • java.time包通过值类型Instant提供机器视图,不提供处理人类意义上的时间 单位。Instant表示时间线上的一点,而不需要任何上下文信息,例如,时区。 概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC)开始的秒 数。因为java.time包是基于纳秒计算的,所以Instant的精度可以达到纳秒级。
  • (1 ns = 10-9 s) 1秒 = 1000毫秒 =10^6微秒=10^9纳秒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* outputs:
* 2022-05-10T09:07:34.911Z
2022-05-10T17:07:34.911+08:00
1652173654911
2033-05-18T03:33:19.999Z
* */
@Test
public void testInstant(){
// now:获取本初子午线对应的标准时间
Instant now = Instant.now();
System.out.println(now);
// 设置时区偏移
System.out.println(now.atOffset(ZoneOffset.ofHours(8)));
// 获取时间戳:1970-01-01 00:00:00当当前的毫秒数
System.out.println(now.toEpochMilli());
//
System.out.println(Instant.ofEpochMilli(1999999999999L));
}

格式化与解析日期或时间

java.time.format.DateTimeFormatter类:该类提供了三种格式化方法:

  • 预定义的标准格式。如: ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
  • 本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)
  • 自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)

用的时候一般自定义。

image-20220510174435655
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
/*
* outputs:
2022-05-10T17:48:16.746
{},ISO resolved to 2022-05-10T17:17:08.054
22-5-10 下午5:48
2022-5-10 17:48:16
2022年5月10日 下午05时48分16秒
22-5-10
2022-5-10
2022年5月10日
2022-05-10 05:48:16自定义
*/
@Test
public void testDateTimeFormatter(){
// 方法一:预定义的格式化标准
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
String formatRes1 = formatter.format(LocalDateTime.now());
System.out.println(formatRes1);

TemporalAccessor afterParse = formatter.parse("2022-05-10T17:17:08.054");
System.out.println(afterParse);

// 方法二:本地相关的格式 ofLocalizedDateTime(FormatStyle.?)
DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
System.out.println(formatter1.format(LocalDateTime.now()));
System.out.println(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).format(LocalDateTime.now()));
System.out.println(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG).format(LocalDateTime.now()));
// ofLocalizedDate(FormatStyle.?)
System.out.println(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).format(LocalDate.now()));
System.out.println(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).format(LocalDate.now()));
System.out.println(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).format(LocalDate.now()));

// 方法三:自定义格式化
DateTimeFormatter customFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
System.out.println(customFormatter.format(LocalDateTime.now()) + "自定义");
}

其他API

  • ZoneId:该类中包含了所有的时区信息,一个时区的ID,如 Europe/Paris
  • ZonedDateTime:一个在ISO-8601日历系统时区的日期时间,如 2007-12- 03T10:15:30+01:00 Europe/Paris。
    • 其中每个时区都对应着ID,地区ID都为“{区域}/{城市}”的格式,例如:Asia/Shanghai等
  • Clock:使用时区提供对当前即时、日期和时间的访问的时钟。
  • 持续时间:Duration,用于计算两个“时间”间隔
  • 日期间隔:Period,用于计算两个“日期”间隔。
  • TemporalAdjuster : 时间校正器。有时我们可能需要获取例如:将日期调整到“下一个工作日”等操作。
  • TemporalAdjusters : 该类通过静态方法 (firstDayOfXxx()/lastDayOfXxx()/nextXxx())提供了大量的常用TemporalAdjuster的实现。
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
LocalTime localTime = LocalTime.now();
LocalTime localTime1 = LocalTime.of(15, 23, 32);
//between():静态方法,返回Duration对象,表示两个时间的间隔
Duration duration = Duration.between(localTime1, localTime);
System.out.println(duration);
System.out.println(duration.getSeconds());
System.out.println(duration.getNano());
LocalDateTime localDateTime = LocalDateTime.of(2016, 6, 12, 15, 23, 32);
LocalDateTime localDateTime1 = LocalDateTime.of(2017, 6, 12, 15, 23, 32);
Duration duration1 = Duration.between(localDateTime1, localDateTime);
System.out.println(duration1.toDays());

//Period:用于计算两个“日期”间隔,以年、月、日衡量
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = LocalDate.of(2028, 3, 18);
Period period = Period.between(localDate, localDate1);
System.out.println(period);
System.out.println(period.getYears());
System.out.println(period.getMonths());
System.out.println(period.getDays());
Period period1 = period.withYears(2);
System.out.println(period1);

// TemporalAdjuster:时间校正器
// 获取当前日期的下一个周日是哪天?
TemporalAdjuster temporalAdjuster = TemporalAdjusters.next(DayOfWeek.SUNDAY);
LocalDateTime localDateTime = LocalDateTime.now().with(temporalAdjuster);
System.out.println("下一个周日是:" + localDateTime);
// 获取下一个工作日是哪天?
LocalDate localDate = LocalDate.now().with(new TemporalAdjuster() {
@Override
public Temporal adjustInto(Temporal temporal) {
LocalDate date = (LocalDate) temporal;
if (date.getDayOfWeek().equals(DayOfWeek.FRIDAY)) {
return date.plusDays(3);
} else if (date.getDayOfWeek().equals(DayOfWeek.SATURDAY)) {
return date.plusDays(2);
} else {
return date.plusDays(1);
}
}
});
System.out.println("下一个工作日是:" + localDate);


参考:与传统日期处理的转换

image-20220510180310135

5.4 Java比较器

对象数组的排序问题,就涉及到对象之间的比较问题。

对象排序方式:

  • 自然排序:java.lang.Comparable
  • 定制排序:java.util.Comparator

Comparable接口

  • Comparable接口强行对实现它的每个类的对象进行整体排序。这种排序被称 为类的自然排序。
  • 实现 Comparable 的类必须实现 compareTo(Object obj)方法,两个对象即通过 compareTo(Object obj)方法的返回值来比较大小。如果当前对象this大于形参对象obj,则返回正整数,如果当前对象this小于形参对象obj,则返回负整数,如果当前对象this等于形参对象obj,则返回零。
  • 实现Comparable接口的对象列表(和数组)可以通过 Collections.sortArrays.sort进行自动排序。实现此接口的对象可以用作有序映射中的键或有 序集合中的元素,无需指定比较器。
  • 对于类 C 的每一个 e1 和 e2 来说,当且仅当 e1.compareTo(e2) == 0e1.equals(e2)具有相同的boolean值时,类 C 的自然排序才叫做与 equals一致。建议(虽然不是必需的)最好使自然排序与equals一致。
  • 对于自定义类,如果需要排序,我们可以自定义类实现Comparable接口,重写Comparable(obj)方法,在方法中指明如何排序。

案例:Comparable 的典型实现:(默认都是从小到大排列的)

  • String:按照字符串中字符的Unicode值进行比较
  • Character:按照字符的Unicode值来进行比较
  • 数值类型对应的包装类以及BigInteger、BigDecimal:按照它们对应的数值 大小进行比较
  • Boolean:true 对应的包装类实例大于 false 对应的包装类实例
  • Date、Time等:后面的日期时间比前面的日期时间大
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
public void testComparable(){
Goods[] all = new Goods[4];
all[0] = new Goods("《红楼梦》", 100.0);
all[1] = new Goods("《西游记》", 80.0);
all[2] = new Goods("《三国演义》", 140.0);
all[3] = new Goods("《水浒传》", 120.0);

Arrays.sort(all);
System.out.println(Arrays.toString(all));
}
class Goods implements Comparable<Goods> {
private String name;
private double price;
//按照价格,比较商品的大小
@Override
public int compareTo(Goods good) {
if(good != null) {
if (this.price > good.price) {
return 1;
} else if (this.price < good.price) {
return -1;
}
return 0;
}
// return Double.compare(this.price, good.price)
throw new RuntimeException("比较对象为空!");
}

public Goods(String name, Double price){
this.name = name;
this.price = price;
}

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 "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}

Comparator接口

  • 当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码, 或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那 么可以考虑使用Comparator的对象来排序,强行对多个对象进行整体排序的比较。
  • 重写compare(Object o1,Object o2)方法,比较o1和o2的大小:如果方法返 回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示 o1小于o2。
  • 可以将Comparator传递给 sort 方法(如 Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制。
  • 还可以使用 Comparator来控制某些数据结构(如有序 set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。

5.5 System类

  • System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于java.lang包。

  • 由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实 例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便 的进行调用。

  • 成员变量

    • System类内部包含in、out和err三个成员变量,分别代表标准输入流 (键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。
  • 成员方法

    • native long currentTimeMillis(): 该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。

    • void exit(int status): 该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。

    • void gc(): 该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。

    • String getProperty(String key): 该方法的作用是获得系统中属性名为key的属性对应的值。系统中常见 的属性名以及属性的作用如下表所示:

      image-20220510215746873

5.6 Math类

image-20220510210303239

5.7 BigInteger与BigDecimal

BigInteger

  • Integer类作为int的包装类,能存储的最大整型值为2^31-1,Long类也是有限的, 最大为2^63-1。如果要表示再大的整数,不管是基本数据类型还是他们的包装类 都无能为力,更不用说进行运算了。

  • java.math包的BigInteger可以表示不可变的任意精度的整数。BigInteger 提供所有 Java 的基本整数操作符的对应物,并提供 java.lang.Math的所有相关方法。 另外,BigInteger还提供以下运算:模算术、GCD 计算、质数测试、素数生成、 位操作以及一些其他操作。

  • 构造器

    • BigInteger(String val):根据字符串构建BigInteger对象
  • 常用方法

    • public BigInteger abs():返回此 BigInteger 的绝对值的 BigInteger。
    • BigInteger add(BigInteger val) :返回其值为 (this + val) 的 BigInteger
    • BigInteger subtract(BigInteger val):返回其值为 (this - val) 的 BigInteger
    • BigInteger multiply(BigInteger val):返回其值为 (this * val) 的 BigInteger
    • BigInteger divide(BigInteger val) :返回其值为 (this / val) 的 BigInteger。整数 相除只保留整数部分。
    • BigInteger remainder(BigInteger val) :返回其值为 (this % val) 的 BigInteger。
    • BigInteger[] divideAndRemainder(BigInteger val):返回包含 (this / val) 后跟 (this % val) 的两个 BigInteger 的数组。
    • BigInteger pow(int exponent) :返回其值为 (this^exponent) 的 BigInteger。

BigDecimal

  • 一般的Float类和Double类可以用来做科学计算或工程计算,但在商业计算中, 要求数字精度比较高,故用到java.math.BigDecimal类。
  • BigDecimal类支持不可变的、任意精度的有符号十进制定点数。
  • 构造器
    • public BigDecimal(double val)
    • public BigDecimal(String val)
  • 常用方法
    • public BigDecimal add(BigDecimal augend)
    • public BigDecimal subtract(BigDecimal subtrahend)
    • public BigDecimal multiply(BigDecimal multiplicand)
    • public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
1
2
3
4
5
6
7
8
9
public void testBigInteger() {
BigInteger bi = new BigInteger("12433241123");
BigDecimal bd = new BigDecimal("12435.351");
BigDecimal bd2 = new BigDecimal("11");
System.out.println(bi);
// System.out.println(bd.divide(bd2)); // 可能报错,因为没有指定存储小数点位数
System.out.println(bd.divide(bd2, BigDecimal.ROUND_HALF_UP));
System.out.println(bd.divide(bd2, 15, BigDecimal.ROUND_HALF_UP));
}

第七章 枚举类&注解

7.1 枚举类的使用

  • 类的对象只有有限个,确定的。

  • 当需要定义一组常量时,强烈建议使用枚举类。

  • 枚举类的实现:

    • JDK1.5 之前需要自定义枚举类
    • JDK1.5新增的enum关键字用于定义枚举类
  • 若枚举只有一个对象, 则可以作为一种单例模式的实现方式。

  • 枚举类的属性

    • 枚举类对象的属性不应允许被改动, 所以应该使用 private final 修饰
    • 枚举类的使用 private final 修饰的属性应该在构造器中为其赋值
    • 若枚举类显示的定义了带参数的构造器,则在列出枚举值时也必须对应的传入参数。
  • 自定义枚举类:

    • 私有化类的构造器,保证不能在类的外部创建其对象
    • 在类的内部创建枚举类的实例。声明为:public static final
    • 对象如果有实例变量,应该声明为private final,并在构造器中初始化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
Season{SEASONNAME='春天', SEASONDESC='春暖花开'}
Season{SEASONNAME='夏天', SEASONDESC='夏日炎炎'}
Season{SEASONNAME='秋天', SEASONDESC='秋高气爽'}
Season{SEASONNAME='冬天', SEASONDESC='白雪皑皑'}
*/
@Test
public void costumeEnumTest(){
//
System.out.println(Season.SPRING);
System.out.println(Season.SUMMER);
System.out.println(Season.AUTUMN);
System.out.println(Season.WINTER);
}
class Season{
private final String SEASONNAME; // 季节名称
private final String SEASONDESC; // 季节描述

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

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 + '\'' +
'}';
}
}
  • 使用enum定义枚举类
    • 使用 enum 定义的枚举类默认继承了 java.lang.Enum类,因此不能再继承其他类。
    • 枚举类的构造器只能使用 private 权限修饰符。
    • 枚举类的所有实例必须在枚举类中显式列出(, 分隔 ; 结尾)。列出的 实例系统会自动添加 public static final 修饰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
enum SeasonEnum {
// 提供当前枚举类的对象,多个对象之间用,隔开,末尾对象用;结束
SPRING("春天","春风又绿江南岸"),
SUMMER("夏天","映日荷花别样红"),
AUTUMN("秋天","秋水共长天一色"),
WINTER("冬天","窗含西岭千秋雪");

private final String seasonName;
private final String seasonDesc;

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

public String getSeasonName(){
return seasonName;
}

public String getSeasonDesc(){
return seasonDesc;
}
}
/* ............... */
/*
* output:
父类:class java.lang.Enum
AUTUMN
[SPRING, SUMMER, AUTUMN, WINTER]
[NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED]
WINTER
* */
@Test
public void EnumTest(){
System.out.println("父类:" + SeasonEnum.class.getSuperclass());
System.out.println(SeasonEnum.AUTUMN);
System.out.println(Arrays.toString(SeasonEnum.values()));
System.out.println(Arrays.toString(Thread.State.values()));

// 根据提供的objName,返回枚举类中对象名是objName的对象。
System.out.println(SeasonEnum.valueOf("WINTER"));
// System.out.println(SeasonEnum.valueOf("WINTER2")); // 每个objName的枚举类,抛异常
}
  • Enum类的主要方法:

    • values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值
    • valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符 串必须是枚举类对象的“名字”。如不是,会有运行时异常: IllegalArgumentException
    • toString():返回当前枚举类对象常量的名称
  • Enum枚举类实现接口:

    • 和普通 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
interface Info{
void show();
}
enum SeasonEnum implements Info{
// 提供当前枚举类的对象,多个对象之间用,隔开,末尾对象用;结束
SPRING("春天","春风又绿江南岸"){
@Override
public void show(){
System.out.println("春天");
}
},
SUMMER("夏天","映日荷花别样红"){
@Override
public void show(){
System.out.println("夏天");
}
},
AUTUMN("秋天","秋水共长天一色"){
@Override
public void show(){
System.out.println("秋天");
}
},
WINTER("冬天","窗含西岭千秋雪"){
@Override
public void show(){
System.out.println("冬天");
}
};

private final String seasonName;
private final String seasonDesc;

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

public String getSeasonName(){
return seasonName;
}

public String getSeasonDesc(){
return seasonDesc;
}


// @Override
// public void show() {
// System.out.println("This is a season");
// }
}

switch遍历类型:byte / short / char / int / String / 枚举类对象

7.2 注解(Annotation)

7.2.1 注解概述

  • 从 JDK 5.0 开始, Java 增加了对元数据(MetaData) 的支持, 也就是 Annotation(注解)

  • Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation, 程序员 可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。

  • Annotation 可以像修饰符一样被使用, 可用于修饰包,类, 构造器, 方法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在Annotation“name=value” 对中。

  • 在JavaSE中,注解的使用目的比较简单,例如标记过时的功能, 忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如 用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。

  • 未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以后也是基于注解的,现在的 Struts2有一部分也是基于注解的了,注解是一种趋势,一定程度上可以说:框架 = 注解 + 反射 + 设计模式

7.2.2 常见Annotation示例

使用 Annotation 时要在其前面增加 @ 符号, 并把该 Annotation 当成 一个修饰符使用。用于修饰它支持的程序元素。

  • 示例一:生成文档相关的注解

    • @author 标明开发该类模块的作者,多个作者之间使用,分割
    • @version 标明该类模块的版本
    • @see 参考转向,也就是相关主题
    • @since 从哪个版本开始增加的
    • @param 对方法中某参数的说明,如果没有参数就不能写
    • @return 对方法返回值的说明,如果方法的返回值类型是void就不能写
    • @exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写 其中
      • @param @return 和 @exception 这三个标记都是只用于方法的。
      • @param的格式要求:@param 形参名 形参类型 形参说明
      • @return 的格式要求:@return 返回值类型 返回值说明
      • @exception的格式要求:@exception 异常类型 异常说明
      • @param和@exception可以并列多个
  • 示例二:在编译时进行格式检查(JDK内置的三个基本注解)

    • @Override: 限定重写父类方法, 该注解只能用于方法
    • @Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择,抑制多个警告,传递一个数组:@SuppressWarinings({"unused", "rawtypes"})
    • @SuppressWarnings: 抑制编译器警告
  • 示例三:跟踪代码依赖性,实现替代配置文件功能

    • Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署。

      image-20220516153041276

    • spring框架中关于“事务”的管理

      image-20220516153151999

7.2.3 自定义注解

  • 定义新的 Annotation 类型使用 @interface关键字
  • 自定义注解自动继承了java.lang.annotation.Annotation接口
  • Annotation的成员变量在 Annotation 定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、 以上所有类型的数组。
  • 可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始 值可使用 default 关键字
  • 如果只有一个参数成员,建议使用参数名为value
  • 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认 值。格式是“参数名 = 参数值” ,如果只有一个参数成员,且名称为value, 可以省略“value=”
  • 没有成员定义的 Annotation 称为标记;包含成员变量的 Annotation 称为元数 据 Annotation

注意:自定义注解必须配上注解的信息处理流程(反射)才有意义。

7.2.4 元注解

  • JDK 的元 Annotation 用于修饰其他 Annotation 定义

  • JDK5.0提供了4个标准的meta-annotation类型,分别是:

    • Retention
    • Target
    • Documented
    • Inherited
  • 元数据的理解: String name = “atguigu”;

  • @Retention: 只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 的生命周期, @Rentention包含一个 RetentionPolicy 类型的成员变量, 使用 @Rentention 时必须为该 value 成员变量指定值:

    • RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释
    • RetentionPolicy.CLASS:在class文件中有效(即class保留),当运行 Java 程序时, JVM 不会保留注解。 这是默认值。
    • RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行 Java 程序时, JVM 会保留注释。程序可以通过反射获取该注释。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public enum RetentionPolicy{
    SOURCE,
    CLASS,
    RUNTIME
    }

    @Retention(RetentionPolicy.SOURCE)
    @interface MyAnnotation1{ }
    @Retention(RetentionPolicy.RUNTIME)
    @interface MyAnnotation2{ }
  • @Target: 用于修饰 Annotation 定义, 用于指定被修饰的 Annotation 能用于修饰哪些程序元素。 @Target 也包含一个名为 value 的成员变量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public enum ElementType {
    TYPE,
    FIELD,
    METHOD,
    PARAMETER,
    CONSTRUCTOR,
    LOCAL_VARIABLE,
    ANNOTATION_TYPE,
    PACKAGE,
    TYPE_PARAMETER,
    TYPE_USE
    }
    image-20220516161438444
  • @Documented: 用于指定被该元 Annotation 修饰的 Annotation 类将被 javadoc 工具提取成文档。默认情况下,javadoc是不包括注解的。

    • 定义为Documented的注解必须设置Retention值为RUNTIME
  • @Inherited: 被它修饰的 Annotation 将具有继承性。如果某个类使用了被@Inherited修饰的 Annotation, 则其子类将自动具有该注解。

    • 比如:如果把标有@Inherited注解的自定义的注解标注在类级别上,子类则可以继承父类类级别的注解
    • 实际应用中,使用较少
    • 通过反射获取注解信息:Annotation[] annotations = Student.class.getAnnotation;

7.2.5 反射获取注解信息

  • JDK 5.0 在 java.lang.reflect 包下新增了 AnnotatedElement 接口, 该接口代表程序中可以接受注解的程序元素。

  • 当一个 Annotation 类型被定义为运行时 Annotation 后, 该注解才是运行时可见, 当 class 文件被载入时保存在 class 文件中的 Annotation 才会被虚拟机读取

  • 程序可以调用 AnnotatedElement对象的如下方法来访问 Annotation 信息:

    getAnnotation(Class<T> annotationClass)

    getAnnotations()

    getDeclaredAnnotations()

    isAnnotationPresent(Class <? extends Annotation> annotationClass)

7.2.6 JDK8注解新特性

Java 8对注解处理提供了两点改进:可重复的注解及可用于类型的注解。此外, 反射也得到了加强,在Java8中能够得到方法参数的名称。这会简化标注在方法参数上的注解。

jdk8之前:声明一个成员变量为要重复的注解的数组。

image-20220516163242345

  • 在MyAnnotation上声明@repeatable,成员值为Annotations.class
  • MyAnnotation的Target和Rentation和MyAnnotations相同

类型注解:

  • JDK1.8之后,关于元注解@Target的参数类型ElementType枚举值多了两个: TYPE_PARAMETER,TYPE_USE。
  • 在Java 8之前,注解只能是在声明的地方所使用,Java8开始,注解可以应用在任何地方。
    • ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)。
    • ElementType.TYPE_USE表示该注解能写在使用类型的任何语句中。
1
2
3
4
5
6
7
8
public class TestTypeDefine<@TypeDefine() U> {
private U u;
public <@TypeDefine() T> void test(T t){
}
}
@Target({ElementType.TYPE_PARAMETER})
@interface TypeDefine{
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@MyAnnotation
public class AnnotationTest<U> {
@MyAnnotation
private String name;
public static void main(String[] args) {
AnnotationTest<@MyAnnotation String> t = null;
int a = (@MyAnnotation int) 2L;
@MyAnnotation
int b = 10;
}
public static <@MyAnnotation T> void method(T t) {
}
public static void test(@MyAnnotation String arg) throws @MyAnnotation Exception {
}
}
@Target(ElementType.TYPE_USE)
@interface MyAnnotation {
}

第八章 java集合

8.1 概述

一方面, 面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象 的操作,就要对对象进行存储。另一方面,使用Array存储(内存层面,不涉及持久化)对象方面具有一些弊 端,而Java 集合就像一种容器,可以动态地把多个对象的引用放入容器中。

  • 数组在内存存储方面的特点:
    • 数组初始化以后,长度就确定了。
    • 数组声明的类型,就决定了进行元素初始化时的类型
  • 数组在存储数据方面的弊端:
    • 数组初始化以后,长度就不可变了,不便于扩展
    • 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。 同时无法直接获取存储元素的个数(.length只是数组长度,不是放了多少个元素进去)
    • 数组存储的数据是有序的、可以重复的。—->存储数据的特点单一
  • Java 集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组

使用场景:

image-20220516170306984

Java 集合可分为 Collection 和 Map 两种体系:

  • Collection接口:单列数据,定义了存取一组对象的方法的集合
    • List:元素有序、可重复的集合–>动态数组
    • Set:元素无序、不可重复的集合
  • Map接口:双列数据,保存具有映射关系“key-value对”的集合
image-20220516190807090

集合API位于java.util包内。

image-20220516191054234

8.2 Collection接口

概述:

  • Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。

  • JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List) 实现。

  • 在 JDK 5 之前,Java 集合会丢失容器中所有对象的数据类型,把所有对象都当成 Object 类型处理;从 JDK 5.0 增加了泛型以后,Java 集合可以记住容器中对象的数据类型。

Collection接口方法:

  • 添加:
    • add(Object obj)
    • addAll(Collection coll)
  • 获取有效元素的个数:int size()
  • 清空集合:void clear()
  • 是否是空集合:boolean isEmpty()
  • 是否包含某个元素:
    • boolean contains(Object obj):是通过元素的equals方法来判断是否 是同一个对象
    • boolean containsAll(Collection c):也是调用元素的equals方法来比 较的。拿两个集合的元素挨个比较。
  • 删除
    • boolean remove(Object obj) :通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素
    • boolean removeAll(Collection coll):取当前集合的差集
  • 取两个集合的交集:
    • boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c
  • 集合是否相等:boolean equals(Object obj)
  • 转成对象数组:Object[] toArray()
  • 获取集合对象的哈希值hashCode()
  • 遍历
    • iterator():返回迭代器对象,用于集合遍历
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
/*
* output:
[1, abc, Ayanami, Asuka]
4
[1, abc, Ayanami, Asuka, Rei, Mari]
6
false
true
true
true
* */
@Test
public void test1(){
Collection<Object> collection = new ArrayList<>(); // 向上转型
collection.add(1);
collection.add("abc");
collection.add("Ayanami");
// collection.add(new String("Ayanami"));
collection.add("Asuka");
System.out.println(collection);

System.out.println(collection.size());

Collection<Object> collection1 = new ArrayList<>(); // 向上转型
collection1.add("Rei");
collection1.add("Mari");
collection.addAll(collection1);
System.out.println(collection);
System.out.println(collection.size());
System.out.println(collection.isEmpty()); // 底层通过判断size == 0实现

/* contains():
* 判断是否包含某个对象
* 底层使用equals()实现--String重写了equals,比的是值!
* 集合中也没有基本数据类型,用的都是包装类,包装类的toString都进行了重写
* 向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals方法(否则默认调用Object的equals())
* */
System.out.println(collection.contains("Ayanami")); // true
System.out.println(collection.contains(new String("Ayanami"))); // true

/*
* containsAll(Collection coll1):判断形参coll1中所有元素是否都存在于当前集合中
* */
Collection colleciton2 = Arrays.asList("Mari","Ayanami");
System.out.println(collection.containsAll(colleciton2));
}


/*
* output:
[1, abc, Ayanami, Asuka]
[abc, Ayanami]
[abc, Ayanami]
[]
true
* */
@Test
public void test2(){
Collection<Object> collection = new ArrayList<>(); // 向上转型
collection.add(1);
collection.add("abc");
collection.add("Ayanami");
collection.add("Asuka");
System.out.println(collection);

/*
* retainAll:
* 返回当前集合与另一个集合所包含的相同值--类比交集
* 会改变调用该方法的集合对象
* */
Collection collection1 = Arrays.asList("Ayanami", "abc");
collection.retainAll(collection1);
System.out.println(collection);

/*
* remove:
* 从当前集合删除一个数据元素,返回true / false,表示否移除成功
* */
collection.remove(1);
System.out.println(collection);

/*
* removeAll(Collection coll1):
* 从当前集合删除coll1中的所含有的所有元素,返回true / false,表示否移除成功
* 类比差集
* */

collection.removeAll(collection1);
System.out.println(collection);

/*
* equals:
* 判断两个集合是否相等
* 注意:对于ArrayList而言,它是有序的!顺序不一样也会返回false
* */
Collection collection2 = Arrays.asList();
System.out.println(collection.equals(collection));
}

/*
* output:
[1, abc, Ayanami, Asuka]
371050659
[1, abc, Ayanami, Asuka]
[AA, BB, CC]
1
* */
@Test
public void test3(){
Collection<Object> collection = new ArrayList<>(); // 向上转型
collection.add(1);
collection.add("abc");
collection.add("Ayanami");
collection.add("Asuka");
System.out.println(collection);
/*
* hashCode:返回当前对象的哈希值
* */
System.out.println(collection.hashCode());

/*
* toArray: 将集合转换为数组
* */
System.out.println(Arrays.toString(collection.toArray()));

/*
* Arrays.asList:数组转换为集合
* */
List<String> strings = Arrays.asList(new String[]{"AA", "BB", "CC"});
System.out.println(strings);

/*
* 这样并不能得到数组!!!
* 可行的方法:
* Arrays.asList(new Integer[]{123, 456})
* Arrays.asList(123, 456)
* */
List ints = Arrays.asList(new int[]{123, 456});
System.out.println(ints.size());

/*
* Iterator(): 返回Iterator接口实例,用于遍历集合元素
* */
}

8.3 Iterator迭代器接口

  • Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
  • GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元 素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。类似于“公交车上的售票员”、“火车上的乘务员”、“空姐”。
  • Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了 Iterator接口的对象。
  • Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建 Iterator 对象,则必须有一个被迭代的集合。
  • 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。

方法:

  • hasNext():如果迭代对象还有元素没有遍历到,返回true。

    在调用it.next()方法之前必须要调用it.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常。

  • next():返回下一次迭代遍历到的元素。

  • remove():从基础集合中移除此迭代器返回的最后一个元素(可选操作)。每次调用next时只能调用此方法一次。

    • 注意:
      • Iterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的remove方法,不是集合对象的remove方法。
      • 如果还未调用next()或在上一次调用next方法之后已经调用了remove方法, 再调用remove都会报IllegalStateException
image-20220516225735628
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
/*
* 集合元素的遍历操作:使用迭代器iterator接口
* hasNext() + next()
* output:
1
abc
Ayanami
false
* */
@Test
public void iteratorTest(){
Collection<Object> collection = new ArrayList<>(); // 向上转型
collection.add(1);
collection.add("abc");
collection.add("Ayanami");
collection.add(false);

Iterator iterator = collection.iterator();
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// iterator.remove();
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next()); // 抛出异常:java.util.NoSuchElementException

/*解决:hasNext()*/
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
  • 使用 foreach循环遍历集合元素

    • Java 5.0 提供了 foreach 循环迭代访问 Collection和数组。

    • 遍历操作不需获取Collection或数组的长度,无需使用索引访问元素。

    • 遍历集合的底层调用Iterator完成操作。

    • foreach还可以用来遍历数组。

    • foreach中不要用remove删除集合元素,因为这里的remove调用的会是集合的remove,而不是iterator的remove

      • 如果要进行remove操作,可以调用迭代器的 remove 方法而不是集合类的 remove 方法。因为如果列表在任何时间从结构上修改创建迭代器之后,以任何方式除非通过迭代器自身remove/add方法,迭代器都将抛出一个ConcurrentModificationException,这就是单线程状态下产生的 fail-fast 机制。
    • 易错:

      1
      2
      3
      4
      5
      6
      7
      8
      String[] strArr = new String[]{"A", "B", "C"};
      for(int index = 0;index<strArr.length;index++){
      strArr[i] = "Y"; // 会改变原数组
      }

      for(String str:strArr){
      str = "X"; // 无法改变原数组,因为值传递原则
      }
      1
      2
      3
      4
      5
      String a = new String("A");
      String x = a; // 直接指向常量池的值 + b
      x = "CC";
      System.out.println(a == x); // false
      System.out.println(a); // A

8.4 Collection子接口之一:List接口

8.5 Collection子接口之二:Set接口

8.6 Map接口

8.7 Collections工具

第九章 泛型

第十章 IO流

第十一章 网络编程

第十二章 java反射机制

第十三章 java8新特性

第十四章 java9&java10&java11新特性

Redis

1
2
3
4
5
6
在安装目录下cmd执行
卸载服务:redis-server --service-uninstall

开启服务:redis-server --service-start

停止服务:redis-server --service-stop

JDBC

1
2
3
4
5
6
7
8
9
10
11
12
/* JDBC,同一套java代码操作不同的关系型数据库。
* 导入对应的驱动jar包即可使用
*
* 步骤:
* 导入jar包
* 注册驱动
* 获取连接
* 定义sql
* 执行sql
* 处理返回结果
* 释放资源~
*/

数据库连接池,初始连接数,最大连接数,这样做是空间换时间,避免了反复创建、销毁连接的代价。

分页查询:

1
2
3
4
5
6
7
8
# LIMIT关键字
# 参数1:开始的索引
# 参数2:查询的条目数
select * from table LIMIT 0,5

# 页面传递参数:当前页码,每页显示条数
# 参数1 =(当前页码 - 1* 每页显示条数
# 参数2 = 每页显示条数

image-20220328214731128

image-20220328214843533 image-20220328215415972

image-20220328215643282

TomCat

java编写的web容器,将web项目放到webapps目录下启动即可。

cmd日志乱码\bin\logging.properties

1
2
3
java.util.logging.ConsoleHandler.encoding = UTF-8

java.util.logging.ConsoleHandler.encoding = GBK
image-20220324003101994

image-20220324003152909

http默认端口号80。

Tomcat中,.war包在webapps下会自动解压缩。

image-20220324004036354

image-20220324004308140

image-20220324004348980

image-20220324004629518

image-20220324004800145

image-20220324004811111

image-20220324005729630

image-20220324005930592

Servlet

Servlet执行流程

Servlet对象是由Tomcat web服务器创建的,service方法也是服务器调用的。

服务器为什么知道Servlet中一定有service方法?

因为我们自定义的Servlet,必须实现Servlet接口,并复写其方法,而Servlet接口中有service方法。

Servlet生命周期

  • 加载和实例化:默认情况下,当Servlet第一词被访问时,由容器创建Servlet对象。

    • @WebServlet(urlPatterns = "/demo", loadOnStartUp = 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

      负整数:第一次被访问时创建Servlet对象

      0或正整数:服务器启动时创建Servlet对象,数字越小,优先级越高。

      * 初始化:在Servlet实例化后,容器将调用Servlet的init()方法初始化这个对象,完成一些如加载配置文件、创建连接等初始化工作。**该方法只调用一次。**
      * 请求和处理:每次请求Servlet时,Servlet容器都会调用Servlet实例的service()方法对请求进行处理。
      * 服务终止:在destroy方法调用后,容器会调用Servlet实例的destory方法完成资源的释放。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java垃圾回收器所回收。

      ## Servlet方法简介

      * init()

      * service()

      * destroy()

      * getServletInfo()

      * getServletConfig()

      * ```java
      private ServletConfig config;

      public void init(ServletConfig config) throws ServletException{
      this.config = config;
      System.out.println("init()……");
      }

      public ServletConfig getServletConfig(){
      return config;
      }
image-20220325143258082

Servlet体系结构

引入:实现Servlet接口,需要复写5个方法,很麻烦。

image-20220325143509183

image-20220325144338344

urlPattern配置

image-20220325151645325 image-20220325151616618

当一个路径同时满足精确匹配和目录匹配,那么精确匹配的优先级高于目录匹配。

/*的优先级高于/

千万不要/*.do,这样是错误的。

image-20220325152300158

所以不要去配置//*,以免访问不了静态资源。

  • 优先级:精确路径 -> 目录路径 -> 扩展名路径 -> /* ->/

xml配置方式编写Servlet

image-20220325152731402

Request

获取请求数据

image-20220325153913356

image-20220325154800135

image-20220325154736683

字符输入流:文本

字节输入流:文件 / 图片等

获取请求参数

image-20220325155019694

image-20220325155335569

image-20220325155701318 image-20220325155721015 image-20220325155734784

doPost()中使用this.doGet()复用代码

请求参数中文乱码

image-20220325163256263

一个汉字3个字节

image-20220325162158371

cmd -> calc使用计算器

image-20220325162630786

乱码原因:tomcat进行url解码,默认字符集为ISO-8859-1

解决方法:大致就是先编码,将数据转换为字节数组,然后解码。

image-20220325162922862

简化:

1
username = new String(username.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);

这种方法GET和POST请求方法通用。

image-20220325163238720

请求转发

image-20220325163740579

Response

image-20220325163830580

设置响应数据功能介绍

image-20220325164026319

重定向

image-20220325164638741

image-20220325164659840

资源路径问题

image-20220325165022341

image-20220325164949182

响应字符数据

image-20220325165615442

image-20220325165632134

响应字节数据

image-20220325165857578 image-20220325165919552 image-20220325165945455

使用工具类读取流。

Servlet编写优化

image-20220328212642996

image-20220328212735155

image-20220328212745879

会话跟踪技术

请求间共享数据,用户身份认证。

image-20220328153115674

image-20220328153625941

image-20220328154736887

对于cookie不能存储中文:

1
2
3
4
5
6
7
8
9
10
11
12
String value = "张加林";
value = URLEncoder.encode(value, "UTF-8");

// URL编码
Cookie cookie = new Cookie("username", value);
……………………
// URL解码
Cookie[] cookies = request.getCookies();
for(cookies: cookie){
String username = cookie.getValue();
System.out.println(URL.decode(username, "UTF-8"));
}

session

数据存储在客户端并不是很安全,会被篡改,会被截获。

image-20220328155036330

session是基于cookie实现的,每一次会话都有对应的session,不同的会话session不同,通过cookie识别服务器是否有对应的session。

image-20220328160359151

image-20220328160927685

image-20220328161532323

登录注册

image-20220328163357181

验证码生成使用CheckCodeUtil.java工具类

image-20220328165004036

Filter

image-20220328165512540

image-20220328165727542

image-20220328170033842

放行前对request数据进行处理,方向后对response数据进行处理

image-20220328170425930

image-20220328170515658

注解配置的Filter,优先级按照过滤器类名(字符串)的自然排序。

image-20220328171655671

Listener

image-20220328171724471

image-20220328171848473

Maven基础

黑马

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 编译生成target目录
mvn compile

# 清理target目录
mvn clean

# 打包jar
mvn package

# 测试
mvn test

# 将当前项目安装到本地仓库
mvn install

生命周期:同一生命周期内,执行后边的命令,前面的所有命令也会自动执行。

  • clean

    pre-clean -> clean -> post-clean

  • default:核心工作,例如编译、测试、打包、安装

    compile -> test -> package -> install

  • site

    pre-site -> site -> post-site

IDEA内置有内置maven,但是最好自己配置,因为默认C盘。

Maven左边

  • groupId:定义当前Maven项目隶属于组织名称(通常为域名反写)

  • artifactId:定义当前Maven项目名称(通常为模块名称)

  • version:定义当前项目版本号

1
2
3
4
<artifactId>junit</artifactId>
<groupId>junit</groupId>
<version>1.0.1</version>
<scope>t</scope>

依赖管理中的scope配置项:生效范围,默认compiler

MyBatis

简单使用

优秀的持久层框架,用于简化JDBC开发。

持久层:负责将数据保存到数据库的那一层代码。

JavaEE三层架构:表现层(页面展示)、业务层(逻辑处理)、持久层(数据持久化)

Mybatis免除了几乎所有JBDC代码以及设置参数和获取结果集的工作。

MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

查询数据

  • 创建表,添加数据
  • 创建模块,导入坐标
  • 编写Mybatis核心配置文件 -> 替换连接信息,解决硬编码问题
  • 编写sql映射文件 -> 统一管理sql,解决硬编码问题
  • 编码
    • 定义POJO类
    • 加载核心配置文件,获取SqlSessionFactory对象
    • 获取SqlSession对象,执行SQL语句
    • 释放资源

Mapper代理方式

  • 定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录下
  • 设置SQL映射文件的namespace属性为Mapper接口全限定名。
  • 在Mapper接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致。

注意:如果Mapper接口名称和SQL映射文件一致,并在同一目录下,则可以用包扫描的方式简化mybatis-config.xml中的SQL映射文件的加载。

1
2
3
4
5
6
7
<mappers>
<!--加载sql映射文件-->
<!--<mapper resource="com/ycy/mapper/EvaluateMapper.xml"/>-->

<!--Mapper代理开发:自动加载所有的xml映射文件-->
<package name="com.ycy.mapper"/>
</mappers>

Myabtis核心配置文件

  • environments:配置数据库连接环境信息。可以配置多个environment,通过default属性切换不同的environment。

  • typeAliases:配置

    • 注意:配置各个xml 标签时,需要遵循先后顺序。

      image-20220323214438346
    1
    2
    3
    4
    5
    6
    7
    8
    <typeAliases>
    <typeAlias alias="Author" type="domain.blog.Author"/>
    <typeAlias alias="Blog" type="domain.blog.Blog"/>
    <typeAlias alias="Comment" type="domain.blog.Comment"/>
    <typeAlias alias="Post" type="domain.blog.Post"/>
    <typeAlias alias="Section" type="domain.blog.Section"/>
    <typeAlias alias="Tag" type="domain.blog.Tag"/>
    </typeAliases>
    1
    2
    3
    <typeAliases>
    <package name="domain.blog"/>
    </typeAliases>

    MybatisX插件

案例摘录

  • 数据库表的字段名称和实体类的属性名称不一致时,则不能自动封装数据(数据库中column-stu_score,实体类:stuScore),解决:

    • 起别名:在xml的sql中给stu_socre起别名

      对不一样的列名起别名,让实体类和别名一致即可

      1
      select stu_score as stuScore from stu;

      但是每次都写别名会很麻烦,所以可以封装sql片段。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      <sql id="stu_column">
      stu_score as stuScore
      </sql>

      <select id="selectAllFromStu" resultType="Stu">
      select
      <include refid="stu_column" />
      from stu;
      </select>

      但是不灵活

    • ResultMap

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <resultMap id="stuResultMap" type="stu">
      <!--
      resultMap的id属性:唯一标识
      type:映射的类型,支持typeAlias别名

      内部id标签:完成逐渐字段映射
      内部result标签:完成一般字段映射
      -->
      <result column="stu_score" property="StuScore" />
      </resultMap>

      <select id="selectAllFromStu" resultMap="stuResultMap">
      select * from stu;
      </select>
      • 定义<resultMap>
      • <select>标签中,使用resultMap替换resultType属性
  • 参数占位符:

    • #{}:会将其替换为?,为了防止SQL注入
    • ${}:拼sql,会存在sql注入问题
  • 特殊符号处理,例如<在xml中表示标签开始。

    • 转义字符:&lt;

    • CDATA区:

      1
      <![CDATA[ < ]]>
  • 传参接口传参给sql:

    • @params("name") int name
    • 封装为object
    • 封装为Map
  • 动态sql

    1
    <if test=""></if>

    但是存在and问题,解决:

    • 添加恒等式:1 = 1,然后每个条件sql中都使用and(如果是or,用1 = 0
    • mybatis中,使用<where>替换where 关键字,包裹条件sql区域即可
  • 单条件动态条件查询

    使用choose(when, otherwise),类似于switch语句

    image-20220323231518575

    默认查询可以使用1 = 1 ,或者使用where标签

  • 提交事务

    image-20220323232300592

    image-20220323232323392

  • 插入数据后返回id

    image-20220323232610646

  • 修改动态字段

    image-20220323233153935

    使用set标签,规避掉可能的,多余问题。

  • 批量删除

    image-20220323233732654

    mybatis会将数组参数,封装为Map集合。

    • 默认:array = 数组,这里就需要配置collection = "array"

    • 或者使用@Param注解来改变map集合的默认key名称

    • 千万不要不写注解直接写名称

      image-20220323234125972

    separator属性用来设置可能需要的分隔符

    image-20220323234520215

    open:开始时拼接符

    close:结束时拼接符

  • MyBatis参数传递:paramNameResolver

    多个参数:封装为Map集合

    1
    2
    3
    4
    map.put("arg0" , 参数值)
    map.put("param0" , 参数值)
    map.put("arg1" , 参数值)
    map.put("param1" , 参数值)

    @param注解:替换Map集合中默认的arg键名。

    image-20220324000103534 image-20220324000215703
  • image-20220324000255380

    参数传递的是Map,所以username = ${user.getUsername}

  • image-20220324000602924

  • 提取工具类

    image-20220325210554280

    而且反复创建sqlSessionFactory是十分占用资源的,因为使用了数据库连接池,所以都会创建一个连接池。

    但是注意:不要把SqlSession也封装了,因为它代表一个数据库连接,抽取后,所有功能都公用一个连接,这样很不合理,因为这样不能管理事务,而且会让多个用户、多个功能之间相互影响。

    image-20220325211315129

Spring

黑马SSM

1.spring简介

Spring:分层的JavaEE/SE应用全栈轻量级开源框架,以IoC(控制反转)和AOP(面向切面编程)为内核。

提供了展现层SpringMVC和持久层Spring JDBCTemplate以及业务层事务管理等众多的企业级应用技术,还能整合开源的众多第三方框架和类库,逐渐成为使用最多的Java EE 企业应用开发框架。

发展历程:略

Spring优势:

  • 方便解耦,简化开发
  • AOP编程支持
  • 声明式事务的支持
  • 方便测试
  • 方便集成各种优秀框架
  • 降低Java EE API使用难度
  • java源码是经典学习案例

Spring体系结构

image-20220322192643343

2.Spring快速入门

开发步骤:

SpringMVC

image-20220325170846578image-20220325170442080