Java语言是美国Sun公司在1995年推出的高级编程语言,主要应用于企业应用开发,如商城系统、物流系统、网银系统等。
JDK (Java Development Kit):指Java程序开发工具包,包含JRE和开发人员使用的一些工具。
JRE(Java Runtime Environment):指Java程序的运行时环境,包含JVM和运行时所需要的核心类库。
JVM(Java Virtual Machine):指Java虚拟机,所有的Java代码,都运行在Java虚拟机之上。
x1# 1. 下载安装包
2https://www.oracle.com/java/technologies/javase/jdk11-archive-downloads.html
3> jdk-11.0.16_linux-x64_bin.tar.gz
4
5# 2. 解压到指定目录
6tar -zxvf jdk-11.0.16_linux-x64_bin.tar.gz -C /usr/local
7
8# 3. 配置3个系统变量
9$ vim /etc/profile
10export JAVA_HOME=/usr/local/jdk-11.0.16
11export CLASSPATH=$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
12export PATH=$PATH:$JAVA_HOME/bin
13
14# 4. 加载系统变量
15$ source /etc/profile
16
17# 5. 验证
18java -version
19
新建HelloWorld.java
文件,输入以下代码:
61public class HelloWorld {
2 public static void main(String[] args) {
3 System.out.println("Hello World!");
4 }
5}
6
注意:
这里的文件名必须和类名一致,注意大小写。
使用javac
命令对源文件进行编译,如果编译成功,将会生成一个HelloWorld.class字节码文件。
11javac HelloWorld.java
使用java
命令进行运行,注意文件名不要加.class后缀。
11java HelloWorld
在Java中,提供了四类八种基本数据类型,分别是:
整型:从小到大又可区分为字节型(byte)
、短整型(short)
、整型(int)
、长整型(long)
四种,分别占用1、2、4、8个字节。
浮点型:有单精度浮点型(float)
和双精度浮点型(double)
两种,分别占用4和8个字节,默认的浮点型变量是double类型。
字符型:只有char
一种,其采用unicode方式编码,占用两个字节,取值范围为0-65535。
布尔型:布尔型变量(boolean)只有true
和false
两种值,占用一个字节,用来表示真或假。
引用数据类型包含基本数据类型之外的所有类型,如字符串、类、数组、接口、lamda表达式等。
211// 案例:定义不同类型的变量,并使用字面量进行赋值
2public class Variable {
3 public static void main(String[] args){
4 // 整型
5 byte b = 100; // 字节型
6 short s = 1000; // 短整型
7 int i = 123456; // 整型
8 long l = 12345678900L; // 长整型
9
10 // 浮点型
11 float f = 5.5F; // 单精度浮点型
12 double d = 8.5; // 双精度浮点型
13
14 // 布尔型
15 boolean bool = false;
16
17 // 字符型
18 char c = 'A';
19 }
20}
21
提示:
字面量除了上述各种基本类型的值外,还有
字符串
和null
值两种。变量必须初始化后才能使用,否则会出现编译错误。
参与的计算的数据,必须要保证数据类型的一致性,否则将发生类型的转换,转换可分为:
自动类型转换:将取值范围小的类型自动提升为取值范围大的类型。
强制类型转换:一般是将取值范围大的类型强制转换成取值范围小的类型,但可能会造成精度损失或数据溢出等。
101public static void main(String[] args) {
2 // 小类型->大类型,自动类型转换
3 long l = 100;
4 float n = 30L;
5 double d = 2.5F;
6
7 // 大类型->小类型,强制类型转换
8 int i1 = (int) 1.5; // 1,精度损失
9 int i2 = (int) 6000000000L; // 1705032704,发生了数据溢出
10}
特殊的,对于byte
、short
、char
类型,在赋值时,如果右边为不超过取值范围的常量,则编译器会自动补上强制类型转换,在参与运算时,至少会被提升为int类型。
141public static void main(String[] args) {
2 // 赋值时的常量优化
3 byte b1 = 30; // 不超过范围,自动补充强制类型转换
4 byte b2 = 128; // 编译错误,超过范围
5 byte b6 = b1 + 1; // 编译错误,右边不是常量
6
7 // 运算时提升为int
8 short s1 = 1;
9 short s2 = 2;
10 short s3 = s1 + s2; // 编译失败,因为运算结果为int,不能赋值给更小的类型
11 int i = s1 + s2; // 编译成功
12 s3 = (short) (s1 + s2); // 编译成功
13}
14
字符编码大致可分为Unicode编码和非Unicode编码,不同编码对应了不同的存储格式:
Unicode编码为全球约110万字符分配的的唯一数字编号,编号范围为ox0000~0x10FFFF
。
大部分常见的字符都在ox0000~0xFFFF之间,大部分常见的中文字符在ox4E00~ox9FFF
之间,如"马"的Unicode编码为U+9A6C。
Unicode编码只是给字符编号,但未说明其二进制形式的编号如何存储,存储方式主要有如下三种:
UTF-32:所有字符以 4 字节存储,非常浪费空间,使用较少。
UTF-16:小编号字符(U+0000~U+FFFF)使用两字节存储,大编号字符(U+10000~U+10FFFF)使用四字节存储,但依然浪费空间。
UTF-8:灵活采用1~4个字节进行存储,使用最为广泛。例如"马"的Unicode编号为U+9A6C,对应的二进制值是1001 101001 101100,格式为三字节格式,因此UTF-8编码为11101001 10101001 10101100,十六进制表示为0xE9A9AC。
编号在0x00~0x7F(0~127)之间的使用单字节存储,兼容ASCII码,格式为0xxxxxxx;
编号在0x80~0x7FF(128~2047)之间的使用双字节存储,格式为110xxxxx 10xxxxxx;
编号在0x800~0xFFFF(2048~65535)之间的使用三字节存储,格式为1110xxxx 10xxxxxx 10xxxxxx;
编号在0x10000~0x10FFFF(65536及以上)之间的使用四字节存储,格式为11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。
提示:
如果第一个字节存储二进制编号的最高位则称为"大端"字节序,如果存储最低位则称为"小端"字节序。
UTF-8-BOM:默认情况下,文件不会声明自身的编码格式,但UTF-8编码的文本文件,可以设置前三个字节为
0xEF0xBB0xBF
来表示当前文件为UTF-8文件,这三个字节被称为BOM(ByteOrder Mark)头,支持UTF-8-BOM格式的文件编辑器可自动识别。
ASCII:中文名称为“美国信息互换标准代码”,单字节编码,最高位固定为0,剩余7位可表示127个符号。其中用第32-126位表示可打印字符,第0-31和127位表示不可打印字符(一般用于控制目的)。ASCII码是最基本的编码,其它非Unicode编码和UTF-8编码都兼容它。
ISO-8859-1(Latin-1):单字节编码,256个字符,第0-127与ASCII码兼容,第128-159表示一些控制字符,160-255表示一些西欧字符。
Windows-1252:单字节编码,是ISO-8859-1的替代版本,将128-159位修改为了一些常用的可打印字符。注意:平时所说的ISO-8859-1编码实际上就是Windows-1252编码。
GB2312:双字节编码,如果两个字节最高位都为0则表示ASCII码,如果都为1则表示中文字符,高位字节范围是0xA1~0xF7
,低位字节范围是0xA1~0xFE
,包括约7000个汉字和一些罕用词和繁体字。
GBK:双字节编码,在GB2312的基础上发展而来,高位字节范围是0x81~0xFE
,低位字节范围是0x40~0x7E
和0x80~0xFE
,额外添加了14000多个中文字符(注意:低字节从0x40开始的,中文字符的低字节最高位有可能为0,因此解析二进制时如果碰到最高位为1的字符,则将连续的两个字节视为一个字符,下一个字符从第三个字节开始解析)。
GB18030:二/四字节编码,在GBK的基础上发展而来,额外添加了55000多个字符,包括了很多少数民族字符以及中日韩统一字符。它的二字节编码与GBK一致,四字节编码中,第一/三字节的值为0x81~0xFE,第二/四字节的值为0x30~0x39。在解析二进制时,如果判断第二个字节范围为0x30~0x39,则将连续的四个字节视为一个字符,下一个字符从第五个字节开始解析。
Big5:双字节编码,高位字节范围是0x81~OxFE,低位字节范围是0x40~0x7E和0xA1~0xFE,包括13000多个繁体字,广泛用于我国台湾地区和我国香港特别行政区等地。
字符乱码主要有两种原因:
使用了错误的编码进行解析:例如使用UTF-8编码存储的文件使用GBK格式解析查看,就可能出现乱码,这时候,只需要切换正确的编码查看格式就可以解决乱码问题了。
错误的解析和编码转换:一些程序为了方便统一处理,经常会将所有编码转换为同一种格式(最常用的为UTF-8),在转换的时候需要知道原来的编码是什么,但可能会搞错,一旦搞错,就会将二进制弄乱,这时候使用任何编码查看都不管用了。
在Java内部(内存中)进行字符处理时,采用的都是Unicode编码,具体编码格式是UTF-16BE。
进行字符处理时的基础类型是char
,其它的Character、String、StringBuilder都是基于char类型的。
char本质上是一个固定占用两个字节的无符号正整数,这个正整数对应于Unicode编号,用于表示那个Unicode编号对应的字符。
由于char固定占用两个字节,只能表示Unicode编号在65536以内的字符,而不能表示超出范围的字符,超出范围的字符得使用两个char。
51char c = 'A'; // 将'A'的Unicode编号(0x65)赋值给变量c
2char c = '马'; // 将'马'的Unicode编号(0x9a6c)赋值给变量c
3char c = 0x9a6c; // 将Unicode编号0x9a6c('马')赋值给变量c
4char c = 39532; // 将Unicode编号39532('马')赋值给变量c
5char c = '\u9a6c'; // 将Unicode编号\u9a6c('马')赋值给变量c
注意:
如果使用字面量
'马'
进行赋值,使用不同的编码格式打开文件可能看到不同的显示效果。推荐JAVA文件采用UTF-8格式,并采用UTF-8格式进行编译。
整数的除法运算,将会丢弃余数,例如1/2=0
。
负数的取余运算,结果的正负始终与前一个操作数相同,例如-3%2==-1
,3%-2==1
。
复合赋值运算符中隐含了一个强制类型转换,例如short s = 1; s = s + 1;
会报错,而s += 1;
却不会。
【重要】对于&和|运算符,如果两边为布尔型
,则执行不短路的逻辑运算,如果为int
型,则执行按位与和按位或。
301//if语句
2public static void main(String[] args) {
3 int score = 79;
4 if (score < 60) {
5 System.out.println("不及格");
6 } else if (score < 80) {
7 System.out.println("及格");
8 } else {
9 System.out.println("优秀");
10 }
11}
12
13// switch语句
14public static void main(String[] args) {
15 String level = "及格";
16 switch (level) {
17 case "不及格":
18 System.out.println("score<60分");
19 break;
20 case "及格":
21 System.out.println(" score>=60分且score<80分");
22 break;
23 case "优秀":
24 System.out.println("score>=80分");
25 default:
26 System.out.println("输入错误!");
27 break;
28 }
29}
30
注意:
switch语句支持的数据类型有
整型
、字符串
、枚举
。如果case语句的后面不写break语句,将出现穿透现象。
241// for循环
2public static void main(String[] args) {
3 for (int i = 0; i < 5; i++) {
4 System.out.println("i = " + i);
5 }
6}
7
8// while循环
9public static void main(String[] args) {
10 int i = 0;
11 while (i < 5) {
12 System.out.println("i = " + i);
13 i++;
14 }
15}
16
17// do-while循环
18public static void main(String[] args) {
19 int i = 0;
20 do {
21 System.out.println("i = " + i);
22 i++;
23 } while (i < 5);
24}
提示:
do-while循环至少会被执行一次,可以用do{}while(flase)来实现goto语句。
break可用于跳出本层循环或结束switch语句,continue可用于结束本次循环。
221// break
2public static void main(String[] args) {
3 for (int i = 1; i<=10; i++) {
4 // 打印完两次HelloWorld之后结束循环
5 if(i == 3){
6 break;
7 }
8 System.out.println("HelloWorld"+i);
9 }
10}
11
12// continue
13public static void main(String[] args) {
14 for (int i = 1; i <= 10; i++) {
15 // 不打印第三次HelloWorld
16 if(i == 3){
17 continue;
18 }
19 System.out.println("HelloWorld"+i);
20 }
21}
22
411public class ArrayTest {
2 public static void main(String[] args) {
3 // 1. 定义Integer数组 (int数组后面不好降序排序)
4 Integer[] arr = {2, 34, 35, 4, 657, 8, 69, 9};
5 Integer[] arr2 = {2, 34, 35, 4, 657, 8, 69, 9};
6
7 // 2. 打印数组地址值
8 System.out.println(arr); // [I@2ac1fdc4
9
10 // 2. toString
11 System.out.println(Arrays.toString(arr)); // [2, 34, 35, 4, 657, 8, 69, 9]
12
13 // 3. 排序、并行排序
14 Arrays.sort(arr); // 默认升序(按字典)
15 Arrays.sort(arr2, Collections.reverseOrder()); // 降序
16 Arrays.sort(arr, (o1, o2) -> o2 - o1); // 降序
17 Arrays.parallelSort(arr2); // 并行排序
18
19 // 4. 比较数组是否相等
20 // 数组元素逐个比较, 注意区分:arr.equals(arr2)比较的是地址
21 boolean isSame = Arrays.equals(arr, arr2);
22
23 // 5. 二分查找
24 // 查到了返回索引位置(>=0),未查找返回-插入点-1(<0)
25 int index = Arrays.binarySearch(arr, 3); // -2
26
27 // 6. 拷贝数组
28 Integer[] copyArr = Arrays.copyOf(arr, arr.length + 1); // 第二个参数为新数组长度
29 Integer[] copyArr1_3 = Arrays.copyOfRange(arr, 1, 3); // 拷贝索引1-3的两个元素
30
31 // 7. 填充元素
32 Arrays.fill(arr, 0); // arr -> [0, 0, 0, 0, 0, 0, 0, 0]
33
34 // 8. 转换为List(只读)
35 List<Integer> integerList = Arrays.asList(arr2);
36
37 // 9. 转化为Stream
38 Stream<Integer> integerStream = Arrays.stream(arr2);
39 }
40}
41
181public static void main(String[] args) {
2 // 二维数组
3 int[][] arr = new int[2][3];
4 for (int i = 0; i < arr.length; i++) {
5 for (int j = 0; j < arr[i].length; j++) {
6 arr[i][j] = i + j;
7 }
8 }
9
10 // 三维数组
11 int[][][] arr02 = new int[10][10][10];
12
13 // 多维数组本质上还是一维数组,只是一位数组的元素又是一个数组,并且这些子数组的长度可以不相同
14 int[][] arr03 = new int[2][];
15 arr03[0] = new int[3];
16 arr03[1] = new int[5];
17}
18
java.util.Arrays
类包含用来操作数组的各种方法,比如排序和搜索等。其所有方法均为静态方法,调用起来非常简单。
171public static void main(String[] args) {
2 // 定义Integer数组 (int数组后面不好降序排序)
3 Integer[] arr = {2, 34, 35, 4, 657, 8, 69, 9};
4
5 // 打印数组地址值
6 System.out.println(arr); // [I@2ac1fdc4
7
8 // 打印数组内容
9 System.out.println(Arrays.toString(arr)); // [2, 34, 35, 4, 657, 8, 69, 9]
10
11 // 排序
12 Arrays.sort(arr); // 默认升序
13 Arrays.sort(arr, (o1, o2) -> o2 - o1); // 降序
14
15 System.out.println("排序后:" + Arrays.toString(arr)); // 排序后:[657, 69, 35, 34, 9, 8, 4, 2]
16}
17
101// 定义
2public static void method01() {
3 System.out.println("method01......");
4}
5
6public static void main(String[] args) {
7 // 调用
8 method01();
9}
10
方法重载是指在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可。参数列表不同可以是参数个数不同、参数类型不同和参数的顺序不同三种形式。
41// 参数顺序不同
2public void open(double a,int b){}
3public void open(int a,double b){}
4
注意:方法重载与参数的名称以及返回值类型无关。
方法的最后一个参数可以定义为可变参数,同时支持数组和可变参数形式的传参。
181public static void main(String[] args) {
2 // 可变参数形式传参
3 int sum01 = sum(6, 7, 2, 12, 2121);
4
5 // 数组形式传参
6 int[] arr = {1, 4, 62, 431, 2};
7 int sum02 = sum(arr);
8}
9
10// 使用可变参数的方法
11public static int sum(int... arr) {
12 int sum = 0;
13 for (int a : arr) {
14 sum += a;
15 }
16 return sum;
17}
18
注意:
可变参数形式与数组形式的方法定义相互冲突,只能保留一个。
形参格式使用数组形式的方法则只能通过数组传参。
面向对象的三大基本特征:封装
、继承
、多态
。
类是一组相关属性和行为的集合,对象是一类事物的具体体现。
571// 定义一个类
2public class Student {
3 /* 成员变量 */
4 private String name;
5 private int age;
6
7 /* 无参数构造方法 */
8 public Student() {
9 }
10
11 /* 全参数构造方法 */
12 public Student(String name, int age) {
13 this.name = name;
14 this.age = age;
15 }
16
17 /* 成员方法 */
18 public void study() {
19 System.out.println("好好学习,天天向上");
20 }
21
22 /* getter/setter方法 */
23 public String getName() {
24 return name;
25 }
26 public void setName(String name) {
27 this.name = name;
28 }
29 public int getAge() {
30 return age;
31 }
32 public void setAge(int age) {
33 this.age = age;
34 }
35}
36
37
38// 使用类
39public class Test01_Student {
40 public static void main(String[] args) {
41 // 创建对象
42 Student student = new Student();
43
44 // 获取成员变量
45 System.out.println("姓名:" + student.getName()); //null
46 System.out.println("年龄:" + student.getAge()); //0
47 System.out.println("‐‐‐‐‐‐‐‐‐‐");
48
49 // 设置成员变量
50 student.setName("赵丽颖");
51 student.setAge(18);
52
53 // 调用成员方法
54 student.study(); // "好好学习,天天向上"
55 }
56}
57
注意:可以通过
this
关键字来显示指明需要访问成员变量,来区分同名的局部变量。
权限修饰符可以用来修改类、方法、属性等的访问权限,体现了面向对象的封装性。
public
:公有的,对所有人开放,无访问限制。
protected
:被保护的,对同包和其子类开放。
[default]
:默认值(缺省值),对同包开放。
private
:私有的,仅对所属类开放。
注意:
一般将类定义为
public
,此时要求文件名和类名保持一致;如果定义为[default]
,则仅有同包中才能访问该类。一般将成员变量定义为
private
,并提供相应的Getter/Setter方法和构造器。不能对父类的private方法进行重写。
static可以用来修饰变量、方法、代码块、内部类等,被修饰的内容保存在静态区(方法区的一部分)
,随着类的加载而加载,且只加载一次,加载后被所有对象共享,一般用类名进行调用。
441public class Student {
2 private Integer id;
3 private String name;
4 private static Integer stuCount; // 静态变量
5
6 static {
7 System.out.println("---静态代码块---");
8 stuCount = 0;
9 }
10
11 {
12 System.out.println("---构造代码块---");
13 this.id = null;
14 this.name = null;
15 }
16
17 public Student(String name) {
18 System.out.println("---构造函数---");
19 this.name = name;
20 this.id = ++stuCount; // 通过 stuCount 给学生分配学号
21 }
22
23 public void showStudent() {
24 {
25 System.out.println("---普通代码块---");
26 System.out.println("Student : id = " + id + ", name = " + name);
27 }
28 }
29
30 public static void showStuCount() {
31 System.out.println("---静态方法---");
32 System.out.println("stuCount:" + stuCount);
33 }
34
35 public static void main(String[] args) {
36 System.out.println("---Main---");
37
38 Student student = new Student("张三");
39 Student student2 = new Student("李四");
40 student.showStudent();
41 Student.showStuCount();
42 }
43}
44
注意:
静态属性和静态方法一般使用类名来调用,虽然也可以通过对象来调用,但不推荐。
静态方法和静态代码块只能访问类中的静态变量,不能直接访问普通成员变量或成员方法。
除静态代码块外,Java中还有构造代码块,它们的执行顺序为:静态代码块->Main函数->构造代码块->构造函数->普通代码块。
在Java1.7后,静态代码块不能存在于主类中,防止干扰main函数的执行。
关于静态内部类的使用请参考内部类章节。
final表示不可改变,可以用于修饰类
、方法
和变量
等,分别具有不同的含义。
161// 1) finnal修饰的类不能被继承
2final public class FinalClass {
3
4 // 2) final修饰的变量不能被重新赋值,必须在声明时或构造函数中初始化
5 final public Integer id;
6
7 public FinalClass(Integer id) {
8 this.id = id; // 初始化final变量
9 }
10
11 // 3) finnal修饰的方法不能被重写
12 final public void finalMethod() {
13
14 }
15}
16
注意:
被final修饰的静态变量一般称为常量,通常使用大写字母+下划线的形式命名。
必须保证类当中所有重载的构造方法,最终都会对final变量进行赋值。
继承指子类继承父类的特征和行为,使子类对象具有父类的实例域和方法,不仅复用了代码,而且使不同子类的对象能够被统一处理。
361public class Base {
2 String name; // 父类变量
3
4 public Base(String name) {
5 this.name = name;
6 }
7
8 public void parentShow() {
9 System.out.println("I am " + name + ".");
10 }
11}
12
13
14public class ChildA extends Base {
15 String name; // 子类同名变量
16
17 public ChildA(String parentName, String childName) {
18 // 调用父类构造
19 super(parentName);
20 this.name = childName;
21 }
22
23 public void childShow() {
24 // 使用super关键字调用父类同名变量
25 System.out.println("I am " + name + ", parent is " + super.name);
26 }
27}
28
29
30public class Test {
31 public static void main(String[] args) {
32 ChildA childA = new ChildA("小头爸爸", "大头儿子");
33 childA.parentShow(); // I am 小头爸爸.
34 childA.childShow(); // I am 大头儿子, parent is 小头爸爸
35 }
36}
注意:
Java只支持单继承,一个类只能有一个父类,并且最终都继承自
java.lang.Object
类。构造子类对象前必须先构造父类对象,如果父类没有无参构造,则必须在子类构造器的首行通过
super
显式调用有参构造。不建议在构造方法中调用非private方法,特别是可被子类重写的方法,这将带来不必要的麻烦。
在子类中,可以使用
super
关键字来显式调用父类属性或方法。子类初始化顺序为:基类静态代码块->子类静态代码块->基类实例代码块->基类构造方法->子类实例代码块->子类构造方法。
方法重写指在子类中覆盖父类的实例方法,以此来实现子类的特异性功能,其要求方法名、参数列表和返回值完全一致,一般使用@Override
注解标识。由于实例方法可能被重写,因此采用动态绑定方式,实际调用的方法取决于对象的动态类型。
371public class Base {
2 public Base(String name) {
3 this.name = name;
4 }
5
6 // 父类待重写方法
7 public void print(){
8 System.out.println("--- Base print() ---");
9 }
10}
11
12
13public class ChildA extends Base {
14 public ChildA(String parentName, String childName) {
15 super(parentName);
16 this.name = childName;
17 }
18
19 // 子类重写方法
20
21 public void print() {
22 System.out.println("--- ChildA print() ---");
23 }
24}
25
26
27public class Test {
28 public static void main(String[] args) {
29 ChildA childA = new ChildA("小头爸爸", "大头儿子");
30 Base base = childA;
31
32 // 动态绑定(取决于动态类型,即为.号右边的类型)
33 base.print(); // --- ChildA print() ---
34 childA.print(); // --- ChildA print() ---
35 }
36}
37
注意:
使用final修饰的类不能被继承,使用final修饰的方法不能被重写,可用于加强封装性。
使用向下转型时可以先用
instanceof
关键字进行判断是否是某个类的子类,避免ClassCastException。
子类还可以定义与父类重名的变量或静态方法,其采用静态绑定(编译期绑定)方式,实际调用取决于对象的静态类型。
441public class Base {
2 // 父类公有变量
3 public String name;
4
5 public Base(String name) {
6 this.name = name;
7 }
8
9 // 父类静态方法
10 public static void staticShow() {
11 System.out.println("--- Base staticShow() ---");
12 }
13}
14
15
16public class ChildA extends Base {
17 // 子类同名的公有变量
18 public String name;
19
20 public ChildA(String parentName, String childName) {
21 super(parentName);
22 this.name = childName;
23 }
24
25 // 子类同名的静态方法
26 public static void staticShow() {
27 System.out.println("--- ChildA staticShow() ---");
28 }
29}
30
31
32public class Test {
33 public static void main(String[] args) {
34 ChildA childA = new ChildA("小头爸爸", "大头儿子");
35 Base base = childA;
36
37 // 静态绑定(取决于静态类型,即为.号左边的类型)
38 System.out.println(base.name); // 小头爸爸
39 System.out.println(childA.name); // 大头儿子
40 Base.staticShow(); // --- Base staticShow() ---
41 ChildA.staticShow(); // --- ChildA staticShow() ---
42 }
43}
44
注意:
此处所说的重名指方法名(变量名)与参数列表皆相同,而非方法重载的情形(重载始终优先于重写)。
接口是Java提供的一种的引用类型,其中可以定义常量、抽象方法、默认方法和静态方法(JDK 8)以及私有方法(JDK 9)等。
221public interface InterfaceA {
2 // 常量
3 public static final int NUM_OF_MY_CLASS = 12; //必须进行初始化
4
5 // 抽象方法(使用 public abstract 修饰,可以省略,没有方法体,非抽象子类必须实现该方法)
6 void abstractMethod();
7
8 // 默认方法(使用 default 修饰,default不可省略,供子类调用或者子类选择性重写)
9 // JDK8之前一般通过抽象类实现
10 default void defaultMethod() {
11 }
12
13 // 静态方法(使用 static 修饰,通过接口直接调用)
14 // JDK之前,相关的静态方法往往定义在单独的类中,比如,Collection接口有一个对应的单独的类Collections
15 static void staticMethod() {
16 }
17
18 // 私有方法(JDK9)(使用 private 修饰,供接口中的其它方法调用)
19 private void privateMethod() {
20 }
21}
22
接口也可以继承其它接口,并且能够同时继承多个,如果父接口中有重名的方法,那么子接口中只需要重写一次。
181// 接口1
2public interface A1 {
3 void compare();
4}
5
6// 接口2
7public interface A2 {
8 void compare();
9}
10
11// 类B继承接口1和接口2
12public interface B extends A1, A2 {
13
14 default void compare() {
15
16 }
17}
18
注意:
接口中只可以定义
public static final
修饰的常量,且不可以定义任何变量。接口中没有静态代码块、构造方法,不能实例化对象。
一个类可以实现多个接口,一个接口也可以被多个类实现。
81// 类C实现接口1和接口2
2public class C implements A1, A2 {
3
4 public void compare() {
5
6 }
7}
8
类在继承基类的同时,也可以实现一个或多个接口,但要求extends关键字在implements之前。
31public class Child extends Base implements IChild {
2}
3
注意:
如果实现类不是抽象类,则必须实现接口的所有抽象方法。
子接口在重写默认方法时,default关键字可以保留,而子类重写默认方法时,不可以保留,因为类中不存在默认方法。
如果继承来的方法和接口的默认方法重名,则子类会优先使用继承来的方法,即继承优于实现。
111public static void main(String[] args) {
2 // 调用接口声明的方法
3 InterfaceAImpl interfaceA = new InterfaceAImpl();
4 interfaceA.abstractMethod();
5
6 // 判断是否继承某个接口
7 if (interfaceA instanceof InterfaceA) {
8 System.out.println("instanceof InterfaceA");
9 }
10}
11
继承至少有两个好处:一个是复用代码;另一个是利用多态和动态绑定统一处理多种不同子类的对象。使用组合替代继承,可以复用代码,但不能统一处理。使用接口替代继承,针对接口编程,可以实现统一处理不同类型的对象,但接口没有代码实现,无法复用代码。将组合和接口结合起来替代继承,就既可以统一处理,又可以复用代码了。
如下例,Child复用了Base的代码,又都实现了IAdd接口,这样,既复用代码,又可以统一处理,还不用担心破坏封装。
261public interface IAdd {
2 void add(int number);
3}
4
5
6public class Base implements IAdd {
7
8 public void add(int number) {
9 System.out.println("---Base.add()---");
10 }
11}
12
13
14public class Child implements IAdd {
15 private Base base; // 组合
16
17 public Child() {
18 base = new Base();
19 }
20
21
22 public void add(int number) {
23 base.add(number); // 复用
24 }
25}
26
抽象类指使用abstract
修饰的类,一般存在抽象方法。抽象方法指只有方法声明,但没有方法体的方法。
261// 抽象类
2public abstract class Shape {
3 // 抽象方法
4 public abstract void draw();
5}
6
7// 子类
8public class Circle extends Shape {
9 // 实现抽象方法
10
11 public void draw() {
12 System.out.println("---画圆---");
13 }
14}
15
16// 测试
17public class Test {
18 public static void main(String[] args) {
19 // 创建子类对象
20 Shape shape = new Circle();
21
22 // 调用子类方法
23 shape.draw();
24 }
25}
26
注意:
抽象类的子类,必须重写抽象父类中所有的抽象方法,除非该子类也是抽象类。
抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
抽象类和接口经常配合使用,接口定义能力,而抽象类提供默认实现,方便子类实现接口,如List接口对应AbstractList,Map接口对应AbstractMap等。它们之间的对比如下:
成员内部类指定义在其它类内部的类,如List返回的Iterator对象等,适用于与外部类关系密切且需要访问外部类实例的变量或方法的类。
341public class Outer {
2 private int a = 100;
3
4 // 成员内部类
5 public class Inner {
6 public void innerMethod() {
7 System.out.println("outer a " + a);
8
9 // 访问外部类变量或方法 Outer.this.xxx
10 Outer.this.action();
11 }
12 }
13
14 private void action() {
15 System.out.println("action");
16 }
17
18 public void test() {
19 // 在外部类中使用成员内部类
20 Inner inner = new Inner();
21 inner.innerMethod();
22 }
23}
24
25
26public class Test {
27 public static void main(String[] args) {
28 // 在其他类中使用成员内部类
29 Outer.Inner inner = new Outer().new Inner();
30 inner.innerMethod();
31 }
32}
33
34
注意:
如需创建成员内部类对象,则必须先创建外部类对象,成员内部类会保存外部类对象的引用。
内部类在编译后会生成独立的.class文件,类名用
$
进行分隔,如Body$Heart.class
。如果在内部类中访问了外部类的私有变量或方法,则在编译时会被替换为[default]权限的
access$0
系列方法。外部类只可以用public或[default]修饰,但内部类还额外支持private和protected修饰符。
接口也可以定义内部接口和内部抽象类等,了解即可。
静态内部类指定义在其它类内部的静态类,如LinkedList类内部的Node类等,适用于与外部类关系密切但不依赖于外部类实例的类。
251public class Outer {
2 private static int shared = 100;
3
4 // 静态内部类
5 public static class StaticInner {
6 public void innerMethod() {
7 System.out.println("inner " + shared);
8 }
9 }
10
11 public void test() {
12 // 在外部类使用静态内部类
13 StaticInner si = new StaticInner();
14 si.innerMethod();
15 }
16}
17
18public class Test {
19 public static void main(String[] args) {
20 // 在其他类使用静态内部类
21 Outer.StaticInner staticInner = new Outer.StaticInner();
22 staticInner.innerMethod();
23 }
24}
25
注意:
创建静态内部类对象时不依赖外部类对象,因此它只能访问外部类的静态变量和方法,不可以访问实例变量和方法。
静态内部类除了可以定义成员方法、成员变量、构造方法、静态final变量等,还额外支持静态变量和静态方法。
方法内部类指定义在方法内部的类,方法可以是成员方法或静态方法,适用于只在当前方法中使用该类的场景。
241ppublic class Outer {
2 private int a = 100;
3
4 public void test(final int param) {
5 final String str = "hello";
6
7 // 方法内部类
8 class Inner {
9 public void innerMethod() {
10 // 访问外部类变量或方法
11 System.out.println("outer a " + a);
12
13 // 访问方法参数或局部变量
14 System.out.println("param " + param);
15 System.out.println("local var " + str);
16 }
17 }
18
19 // 在方法内部使用方法内部类
20 Inner inner = new Inner();
21 inner.innerMethod();
22 }
23}
24
注意:
如需在方法内部类中访问方法参数或局部变量,则该变量必须是final的,或等效final的(因为该变量是通过构造参数直接传入)。
方法内部类不能写任何权限修饰符。
匿名内部类指定义在方法内部的匿名类,它没有类名和构造方法,且只能在定义类时创建对象,适用于接口回调的场景。
131public class Outer {
2 public void sortIgnoreCase(String[] strs) {
3 // 使用匿名内部类创建的对象
4 Arrays.sort(strs, new Comparator<String>() {
5
6 public int compare(String o1, String o2) {
7 return o1.compareToIgnoreCase(o2);
8 }
9 });
10 }
11}
12
13
注意:
匿名内部类可以定义实例变量、实例方法、初始化代码块、参数列表等,其中参数列表将会传递给父类的构造方法。
与方法内部类一样,匿名内部类也可以访问外部类的变量和方法,可以访问等效final的方法参数和局部变量。
枚举(enum)是一种特殊的类,它只有类中声明的有限个对象,对象名称一般大写。
431// 定义枚举
2public enum Size {
3 // 该枚举有三个对象
4 SMALL, MEDIUM, LARGE
5}
6
7// 使用枚举
8public class Test {
9 public static void main(String[] args) {
10 // 获取单个枚举对象
11 Size size = Size.SMALL; // 直接获取
12 Size size2 = Size.valueOf("SMALL"); // 使用字面值获取
13
14 // 枚举对象的字面值、序号
15 System.out.println(size.toString()); // SMALL,等同name()
16 System.out.println(size.name()); // SMALL
17 System.out.println(size.ordinal()); // 0
18
19 // 枚举对象进行比较
20 System.out.println(size == Size.SMALL); // true
21 System.out.println(size.equals(Size.SMALL)); // true
22 System.out.println(size.compareTo(Size.MEDIUM)); // -1 比较ordinal值
23
24 // 获取所有枚举对象 .values()
25 for (Size item : Size.values()) {
26 System.out.println(item);
27 }
28
29 // switch使用枚举
30 switch (size) {
31 case SMALL:
32 System.out.println("chosen small");
33 break;
34 case MEDIUM:
35 System.out.println("chosen medium");
36 break;
37 case LARGE:
38 System.out.println("chosen large");
39 break;
40 }
41 }
42}
43
枚举类可以定义自己的成员变量、成员方法以及继承接口等。
481// 接口
2public interface ExceptionMessage {
3 String getCode();
4 String getMessage();
5 ExceptionLevel getLevel();
6}
7
8// 继承接口的枚举
9public enum KfmsExceptionMessage implements ExceptionMessage {
10 // 枚举对象
11 ERR_NOT_SUPPORTED("-99", "暂不支持", ExceptionLevel.NORMAL),
12 ERR_DEFAULT("-1", "未知异常", ExceptionLevel.NORMAL),
13 ERR_RTMSG_OK("0", "业务程序运行正常", ExceptionLevel.NORMAL);
14
15 // 定义的变量
16 private String code;
17 private String message;
18 private ExceptionLevel level;
19
20 // 构造器,用于上述枚举对象的构造
21 KfmsExceptionMessage(String code, String message, ExceptionLevel level) {
22 this.code = code;
23 this.message = message;
24 this.level = level;
25 }
26
27 // 实现接口方法
28
29 public String getCode() {
30 return this.code;
31 }
32
33 public String getMessage() {
34 return this.message;
35 }
36
37 public ExceptionLevel getLevel() {
38 return this.level;
39 }
40
41 // 重写抽象类Enum<T>中的方法
42
43 public String toString() {
44 return this.message;
45 }
46
47}
48
注意:
枚举对象必须定义在类的前面,并且以分号结尾,再写其它代码。
不需要提供Setter方法,枚举对象不允许修改。
枚举本质上还是一个类,上述案例转换为普通类如下:
491// 枚举类 ==> final修饰的普通类,继承Enum<E extends Enum<E>>
2public final class KfmsExceptionMessage extends Enum<KfmsExceptionMessage> implements ExceptionMessage {
3 // 枚举对象 ==> public final修饰的静态对象
4 public static final KfmsExceptionMessage ERR_NOT_SUPPORTED = new KfmsExceptionMessage("ERR_NOT_SUPPORTED", 0, "-99", "暂不支持", ExceptionLevel.NORMAL);
5 public static final KfmsExceptionMessage ERR_DEFAULT = new KfmsExceptionMessage("ERR_DEFAULT", 1, "-1", "未知异常", ExceptionLevel.NORMAL);
6 public static final KfmsExceptionMessage ERR_RTMSG_OK = new KfmsExceptionMessage("ERR_RTMSG_OK", 2, "0", "业务程序运行正常", ExceptionLevel.NORMAL);
7
8 // VALUES
9 private static KfmsExceptionMessage[] VALUES = new KfmsExceptionMessage[]{ERR_NOT_SUPPORTED, ERR_DEFAULT, ERR_RTMSG_OK};
10
11 // 成员变量
12 private String code;
13 private String message;
14 private ExceptionLevel level;
15
16 // 构造方法私有,方式创建其它对象
17 private KfmsExceptionMessage(String name, int ordinal, String code, String message, ExceptionLevel level) {
18 super(name, ordinal);
19 this.code = code;
20 this.message = message;
21 this.level = level;
22 }
23
24 public static KfmsExceptionMessage[] values() {
25 KfmsExceptionMessage[] values = new KfmsExceptionMessage[VALUES.length];
26 System.arraycopy(VALUES, 0, values, 0, VALUES.length);
27 return values;
28 }
29 public static KfmsExceptionMessage valueOf(String name) {
30 return Enum.valueOf(KfmsExceptionMessage.class, name);
31 }
32
33 public String getCode() {
34 return this.code;
35 }
36
37 public String getMessage() {
38 return this.message;
39 }
40
41 public ExceptionLevel getLevel() {
42 return this.level;
43 }
44
45 public String toString() {
46 return this.message;
47 }
48}
49
Java中提供了四类八种基础类型,为了适应不同场合,又分别提供了对应的包装类。
基本类型与对应的包装类对象之间,来回转换的过程称为”装箱“与”拆箱“。从JDK1.5开始,该动作可以自动完成。
131// 装箱:基本类型 -> 包装类型
2Integer i = new Integer(4); // 使用构造函数
3Integer ii = Integer.valueOf(4); // 使用包装类中的valueOf方法
4Integer i = 4; // 自动装箱
5
6// 拆箱:包装类型 -> 基本类型
7int num = i.intValue();
8i = i + 5;
9
10// 自动装箱与拆箱
11Integer i = 4; // 相当于Integer i = Integer.valueOf(4);
12i = i + 5; // 等号右边:将i对象转成基本数值(自动拆箱),相当于i.intValue() + 5, 加法运算完成后,再次装箱,把基本数值转成对象。
13
161// 包装类 -> 字符串
2Integer i = 0;
3Boolean b = true;
4String str1 = i + ""; // "0"
5String str2 = String.valueOf(i); // "0" String.valueOf()兼容null值,null->"null"
6String str3 = i.toString(); // "0"
7String str4 = b.toString(); // "true"
8
9// 字符串 -> 包装类
10Boolean b = Boolean.parseBoolean("true"); // true 只有"true"转换为true,null和其它都是false
11Integer i = Integer.parseInt("666"); // 666 如果是小数或其它无法转换的数字字符串,则报错
12Double d = Double.parseDouble("666"); // 666.0
13
14// 字符串 -> Charset
15Character c = "abcd".charAt(0); // a 特殊的,Charset.parseCharset()方法不存在
16
与字符串常量池类似,Integer类内部维护了一个整型池,范围为-128~127,如使用自动装箱操作,则会自动取池中对象,可用==进行地址比较,否则需要用equals比较。
171public static void main(String[] args) {
2 // 始终创建新对象
3 Integer i1 = 0;
4 Integer i2 = new Integer(0);
5 System.out.println(i1 == i2); // false
6
7 // 自动装箱,复用池中对象
8 Integer i3 = 127;
9 Integer i4 = 127;
10 System.out.println(i3 == i4); // true
11
12 // 自动装箱,无池中对象可复用
13 Integer i5 = 128;
14 Integer i6 = 128;
15 System.out.println(i5 == i6); // false
16}
17
Java中使用java.lang.String
类代表字符串,创建字符串的方式如下:
141// 1. 通过字符串字面量构造
2String str = "ABCD"; //存入常量池
3
4// 2. 无参构造
5String str = new String(); //空字符串""
6
7// 3. 通过字符数组构造
8char chars[] = {'a', 'b', 'c'};
9String str2 = new String(chars);
10
11// 4. 通过字节数组构造
12byte bytes[] = { 97, 98, 99 };
13String str3 = new String(bytes); // 使用系统默认编码 ,其它可用"UTF-8"、"GBK"、"GB18030"等
14
String类在创建后不可修改,并且对字符串常量进行入池共享。
151// 1. 不可变性:字符串创建后不能被修改
2String s = "abc";
3s += "d"; // 尝试修改字符串,会创建新对象"abcd"
4
5
6// 2. 常量共享:通过字面量创建的String对象会被加入到“字符串常量池”中进行复用
7String name1 = new String("abc");
8String name2 = new String("abc"); // name1!= name2
9String s1 = "abc";
10String s2 = "abc"; // s2 == s1
11
12
13// 3. 基于字符数组char[]实现(JDK9及以后基于字节数组byte[])
14String str = "abc"; // String内部实际为 char data[] = {'a', 'b', 'c'};
15
注意:
通过字符串字面量构造也会创建String类对象,并且会自动入池,推荐使用。
String类与字节数组之间的转换依赖字符编码,如未指明则使用系统默认字符编码:
Charset.defaultCharset().name()
。
如果对字符串操作很频繁或者在循环中使用字符串,则推荐使用StringBuilder
类或其线程安全版本的StringBuffer
类来处理。
81public static void main(String[] args) {
2 // 基本使用
3 StringBuilder sb = new StringBuilder();
4 sb.append("Hello ");
5 sb.append("World!");
6 System.out.println(sb.toString()); // Hello World!
7}
8
注意:
StringBuilder内部采用非final修饰的字符数组,可以根据字符串的修改来进行动态扩容,而不会创建很多中间字符串对象。
如果不是在循环中使用,则编译器一般可以自动优化为StringBuilder操作,而在循环中,可能会创建多个StringBuilder对象。
在JDK1.8+版本,添加了更适合进行字符串拼接操作的类StringJoiner
,同时在String类中加了一个静态方法String.join()
。
141public static void main(String[] args) {
2 String[] strs = new String[]{"abc", "def", "xyz"};
3
4 // 使用 StringJoiner 拼接
5 StringJoiner sj = new StringJoiner("', '", "['", "']");
6 for (int i = 0; i < strs.length; i++) {
7 sj.add(strs[i]);
8 }
9 System.out.println(sj.toString()); // ['abc', 'def', 'xyz']
10
11 // 调用 String.join()封装方法(不支持前后缀)
12 System.out.println(String.join("|", strs)); // abc|def|xyz
13}
14
关于日期和时间,有一些基本概念,包括纪元时
、时刻
、时区
、年历
等。
纪元时(Epoch Time):一个特殊的时刻(零点),指格林尼治标准时间1970年1月1日0时0分0秒。
时刻:相对于纪元时的毫秒数(允许负数)。这个毫秒数在世界各地都相同,但是不同地区对其解读不一样(与时区和年历有关)。
时区:全球一共有24个时区,英国格林尼治是0时区(GMT+0),北京是东八区(GMT+8:00),也就是说格林尼治凌晨1点,北京是早上9点。
年历:描述了一年有多少月,每月有多少天,甚至一天有多少小时,在中文系统中,一般使用公历。
注意:时刻是一个绝对时间,但对时刻的解读,则是相对的,与时区和年历相关。
java.util.Date
是早期引入的日期API,主要用于表示时刻的概念,同时承担了年历的作用,但由于不支持国际化,许多方法被标记过时了,我们只需学习未过时的方法即可。
161public static void main(String[] args) {
2 // 当前时刻
3 Date date01 = new Date(); // Tue Nov 01 18:46:36 CST 2022
4
5 // 根据毫秒值获取时刻
6 Date date02 = new Date(0); // Thu Jan 01 08:00:00 CST 1970 注意:0时刻在东八区要+8小时
7
8 // 获取毫秒值
9 System.out.println(date01.getTime()); // 1667299596867
10
11 // 时刻之间进行比较
12 System.out.println(date01.compareTo(date02)); // 1 正数表示大于
13 System.out.println(date01.before(date02)); // false
14 System.out.println(date01.after(date02)); // true
15}
16
java.util.TimeZone(抽象类)
表示时区,默认时区由系统属性`user.timezone
属性决定。
111public static void main(String[] args) {
2 // 系统默认时区
3 TimeZone tz01 = TimeZone.getDefault(); // Asia/Shanghai
4
5 // 美国东部时区
6 TimeZone tz02 = TimeZone.getTimeZone("US/Eastern"); // US/Eastern
7
8 // GMT形式的时区
9 TimeZone tz03 = TimeZone.getTimeZone("GMT+08:00"); // GMT+08:00
10}
11
注意:
系统属性可通过System.getProperty("")获取,并在启动时通过java -Duser.timezone=Asia/Shanghai xxxx参数进行调整。
java.util.Locale
表示国家(或地区)和语言,中国内地的代码是CN,中国台湾地区的代码是TW,美国的代码是US,中文语言的代码是zh,英文语言的代码是en等。
181public static void main(String[] args) {
2 // 默认地区和语言
3 System.out.println(Locale.getDefault()); // zh_CN
4
5 // 内置的地区和语言常量
6 System.out.println(Locale.US); // en_US 美国英语
7 System.out.println(Locale.ENGLISH); // en 所有英语
8 System.out.println(Locale.TAIWAN); // zh_TW 中国台湾地区所用的中文
9 System.out.println(Locale.CHINESE); // zh 所有中文
10 System.out.println(Locale.SIMPLIFIED_CHINESE); // zh_CN 中国内地所用的中文
11
12 // 自定义地区和语言
13 System.out.println(new Locale("zh", "CN")); // zh_CN
14
15 // 获取所有可用地区和语言
16 System.out.println(Arrays.toString(Locale.getAvailableLocales())); //
17}
18
java.util.Calendar(抽象类)
表示与年月日相关的日历信息,可以对日期和时间进行设置和修改。
81public static void main(String[] args) {
2 // 默认Calendar实例
3 Calendar instance01 = Calendar.getInstance(); // java.util.GregorianCalendar[...]
4
5 // 根据TimeZone和Locale获取对应日历
6 Calendar instance02 = Calendar.getInstance(TimeZone.getTimeZone("GMT+08:00"), Locale.CHINESE);
7}
8
其内部有一个与Date中类似的长整型变量protected long time
,用于记录时刻(默认为当前时间),并且还有一个数组protected int fields[]
,表示日历中各个字段的值。这个数组的长度为17,能够表示的字段主要有:
字段 | 含义 |
---|---|
Calendar.MONTH | 月(0~11) |
Calendar.DAY_OF_MONTH | 当前月的第N日(1~xx) |
Calendar.HOUR_OF_DAY | 当前日的第N时(0~23) |
Calendar.MINUTE | 分钟(0~59) |
Calendar.SECOND | 秒(0~59) |
Calendar.MILLISECOND | 毫秒(0~999) |
Calendar.DAY_OF_WEEK | 当前周的第N天(1~7,1为周日) |
161public static void main(String[] args) {
2 // 获取默认日历实例
3 Calendar calendar = Calendar.getInstance();
4 System.out.println("time: " + calendar.getTime().getTime()); // time: 1667462212639
5
6 // 查看年月日等日历信息
7 System.out.println("year: " + calendar.get(Calendar.YEAR)); // year: 2022
8 System.out.println("month(0-11): " + calendar.get(Calendar.MONTH)); // month(0-11): 10
9 System.out.println("day: " + calendar.get(Calendar.DAY_OF_MONTH)); // day: 3
10 System.out.println("hour: " + calendar.get(Calendar.HOUR_OF_DAY)); // hour: 15
11 System.out.println("minute: " + calendar.get(Calendar.MINUTE)); // minute: 56
12 System.out.println("second: " + calendar.get(Calendar.SECOND)); // second: 52
13 System.out.println("millisecond: " + calendar.get(Calendar.MILLISECOND)); // millisecond: 0
14 System.out.println("day_of_week(周日为第1天): " + calendar.get(Calendar.DAY_OF_WEEK)); // day_of_week(周日为第1天): 5
15}
16
注意:
Calendar中定义了表示各个星期、各个月的静态变量,如Calendar.SUNDAY表示周日、Calendar.JULY表示7月。
Calendar支持根据Date或毫秒数设置时间,也支持根据年月日等日历字段设置时间。
121public static void main(String[] args) {
2 // 根据Date或毫秒数设置时间
3 Calendar calendar = Calendar.getInstance();
4 calendar.setTime(new Date());
5 calendar.setTimeInMillis(System.currentTimeMillis());
6
7 // 根据年月日等日历字段设置时间
8 calendar.set(2022, 11, 20); // 2022年11月20日
9 calendar.set(2022, 11, 20, 15, 48, 59); // 2022年11月20日15时48分59秒
10 calendar.set(Calendar.YEAR, 2021); // 2021年
11}
12
除了直接设置,也支持根据字段增加和减少时间,正数表示增加,负数表示减少,增减支持自动调整。
221public static void main(String[] args) {
2 // 设置为昨天下午2点15
3 Calendar calendar = Calendar.getInstance();
4 calendar.add(Calendar.DAY_OF_MONTH, -1); // 昨天
5 calendar.set(Calendar.HOUR_OF_DAY, 14);
6 calendar.set(Calendar.MINUTE, 15);
7 calendar.set(Calendar.SECOND, 0);
8 calendar.set(Calendar.MILLISECOND, 0);
9
10 // add方法支持自动调整
11 Calendar calendar = Calendar.getInstance();
12 calendar.set(Calendar.HOUR_OF_DAY, 13); // 13点
13 calendar.set(Calendar.MINUTE, 59); // 59分
14 calendar.add(Calendar.MINUTE, 3); // +3分,自动调整为14点02分
15
16 // roll方法不支持自动调整
17 calendar = Calendar.getInstance();
18 calendar.set(Calendar.HOUR_OF_DAY, 13);
19 calendar.set(Calendar.MINUTE, 59);
20 calendar.roll(Calendar.MINUTE, 3); // 13点02分
21}
22
91public static void main(String[] args) {
2 Calendar calendar = Calendar.getInstance();
3
4 // 转化为Date
5 Date date = calendar.getTime();
6
7 // 转换为毫秒数
8 long time =calendar.getTimeInMillis();
9}
101public static void main(String[] args) {
2 Calendar calendar = Calendar.getInstance();
3 Calendar otherCalendar = Calendar.getInstance();
4
5 // 与其它Calendar进行比较
6 System.out.println(calendar.equals(otherCalendar)); // false
7 System.out.println(calendar.compareTo(otherCalendar)); // -1
8 System.out.println(calendar.before(otherCalendar)); // true
9 System.out.println(calendar.after(otherCalendar)); // false
10}
java.text.DateFormat(抽象类)
用于Date与字符串之间的相互转换。我们一般用它的实现类SimpleDateFormat
,它接受一个pattern作为构造参数,pattern中的英文字符(a~z|A~Z)表示特殊含义,其他字符原样输出。部分特殊字符含义如下:
格式字符串 | 含义 | 格式字符串 | 含义 |
---|---|---|---|
yyyy | 年份 | mm | 分钟 |
MM | 月份 | ss | 秒 |
HH | 小时(24小时制) | E | 星期几 |
hh | 小时(12小时制) | a | 上午或下午(仅12小时制有效) |
特殊的,如果想原样输出英文字符,可以将其用单引号括起来。
241public static void main(String[] args) {
2 // 格式一
3 SimpleDateFormat simpleDateFormat01 = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
4 System.out.println(simpleDateFormat01.format(new Date())); // 2022年11月04日 17时35分59秒
5 try {
6 System.out.println(simpleDateFormat01.parse("2022年11月04日 17时35分59秒"));
7 } catch (ParseException e) {
8 e.printStackTrace();
9 }
10
11 // 格式二
12 SimpleDateFormat simpleDateFormat02 = new SimpleDateFormat("yyyy/MM/dd E hh:mm:ss a");
13 System.out.println(simpleDateFormat02.format(new Date())); // 2022/11/04 星期五 05:35:59 下午
14 try {
15 System.out.println(simpleDateFormat02.parse("2022/11/04 星期五 05:35:59 下午"));
16 } catch (ParseException e) {
17 e.printStackTrace();
18 }
19
20 // 原样输出英文字符、双引号等
21 SimpleDateFormat simpleDateFormat03 = new SimpleDateFormat("\"yyyy'year'MM'month'dd'day' HH'hour'mm'minute'ss'second'\"");
22 System.out.println(simpleDateFormat03.format(new Date())); // "2022year11month04day 17hour40minute58second"
23}
24
JDK8对日期和时间进行了增强,位于java.time
包下。
111public static void main(String[] args) {
2 // 当前时刻
3 Instant now01 = Instant.now(); // 2022-11-04T10:07:06.068Z
4
5 // 根据毫秒值获取时刻
6 Instant now02 = Instant.ofEpochMilli(System.currentTimeMillis());
7
8 // 获取毫秒数
9 long time = now02.toEpochMilli(); // 1667804917641
10}
11
111public static void main(String[] args) {
2 // 默认时区
3 ZoneId defaultZoneId = ZoneId.systemDefault(); // Asia/Shanghai
4
5 // 北京时区
6 ZoneId bjZone = ZoneId.of("GMT+08:00"); // Asia/Shanghai
7
8 // 时区偏移8小时
9 ZoneOffset zoneOffset = ZoneOffset.of("+08:00");
10}
11
291public static void main(String[] args) {
2 // 使用默认毫秒值与时区构造
3 LocalDateTime ldt01 = LocalDateTime.now();
4
5 // 指定毫秒值和时区构造
6 LocalDateTime ldt03 = LocalDateTime.ofEpochSecond(2030201403, 0, ZoneOffset.of("+08:00")); // 2034-05-03T00:50:03
7
8 // 直接指定日期时间
9 LocalDateTime ldt02 = LocalDateTime.of(2022, 7, 11, 20, 45, 5); // 2022年7月11日20时45分5秒
10
11 // 指定Instant时刻和时区构造
12 LocalDateTime ldt04 = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()); // 2022-11-07T15:42:33.035
13
14 // 获取年月日等日历信息
15 int year = ldt02.getYear(); // 2022
16 int monthValue = ldt02.getMonthValue(); // 7
17 int dayOfMonth = ldt02.getDayOfMonth(); // 11
18 int hour = ldt02.getHour(); // 20
19 int minute = ldt02.getMinute(); // 45
20 int second = ldt02.getSecond(); // 5
21 DayOfWeek dayOfWeek = ldt02.getDayOfWeek(); // MONDAY
22
23 // 转换为指定时区的Instant时刻
24 Instant instant = ldt02.toInstant(ZoneOffset.of("+08:00")); // 2022-11-04T10:20:42.158Z
25
26 // 转换为指定时区的毫秒值
27 long epochSecond = ldt02.toEpochSecond(ZoneOffset.of("+08:00")); // 1657543505
28}
29
注意:
DayOfWeek是一个枚举,有7个取值,从DayOfWeek.MONDAY到DayOfWeek.SUN-DAY。
LocalDateTime由两部分组成,一部分是日期(LocalDate),另一部分是时间(LocalTime),它们三者之间可以相互转换。
231public static void main(String[] args) {
2 // 当前日期
3 LocalDate ld01 = LocalDate.now();
4
5 // 指定日期
6 LocalDate ld02 = LocalDate.of(2017, 7, 11); // 2017年7月11日
7
8 // 当前时间
9 LocalTime lt01 = LocalTime.now();
10
11 // 指定时间
12 LocalTime lt02 = LocalTime.of(21, 10, 34); // 21时10分34秒
13
14 // LocalDateTime拆分为LocalDate和LocalTime
15 LocalDateTime ldt = LocalDateTime.of(2017, 7, 11, 20, 45, 5);
16 LocalDate ld = ldt.toLocalDate(); //2017-07-11
17 LocalTime lt = ldt.toLocalTime(); // 20:45:05
18
19 // LocalDate和LocalTime合并为LocalDateTime
20 LocalDateTime ldt01 = ld.atTime(21, 18, 39); // LocalDate + 时间
21 LocalDateTime ldt02 = lt.atDate(LocalDate.of(2016, 3, 24)); // LocalTime + 日期
22}
23
261public static void main(String[] args) {
2 // 使用默认毫秒值与时区构造
3 ZonedDateTime zdt01 = ZonedDateTime.now();
4
5 // 使用LocalDateTime+时区构造
6 ZonedDateTime zdt02 = ZonedDateTime.of(LocalDateTime.now(), ZoneId.systemDefault());
7
8 // 使用Instant+时区构造
9 ZonedDateTime zdt03 = ZonedDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()); // 方式一
10 ZonedDateTime zdt03_ = Instant.now().atZone(ZoneId.systemDefault()); // 方式二
11
12 // 指定日期时间+时区构造
13 ZonedDateTime zdt04 = ZonedDateTime.of(2022, 11, 7, 15, 59, 30, 0, ZoneId.systemDefault());
14
15 // 获取年月日等日历信息(与LocalDateTime类似,略......)
16 int year = zdt04.getYear(); // 2022
17
18 // 转换为LocalDateTime
19 LocalDateTime ldt = zdt04.toLocalDateTime();
20
21 // 转换为Instant
22 Instant instant = zdt04.toInstant();
23
24 // 转换为毫秒值
25 long epochSecond = zdt04.toEpochSecond();
26}
注意:
LocalDateTime只记录了年月日等相关信息,不记录时区信息,其它方法一般都要传递时区。
ZonedDateTime不仅记录了时刻,还记录了时区,ZonedDateTime ≈ LocalDateTime + 时区。
131public static void main(String[] args) {
2 // 构造DateTimeFormatter实例(线程安全)
3 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
4
5 // 格式化
6 String format = formatter.format(LocalDateTime.now());
7
8 // 解析
9 TemporalAccessor parse = formatter.parse("2016-08-18 14:20:45"); // 不常用
10 Instant instant = Instant.parse("2016-08-18 14:20:45"); // 解析为Instant
11 LocalDateTime localDateTime = LocalDateTime.parse("2016-08-18 14:20:45"); // 解析为LocalDateTime
12 ZonedDateTime zonedDateTime = ZonedDateTime.parse("2016-08-18 14:20:45"); // 解析为ZonedDateTime
13}
修改日期和时间有两种方式,一种是直接设置绝对值,另一种是在现有值的基础上进行相对增减操作。
301public static void main(String[] args) {
2 LocalDateTime ldt = LocalDateTime.now();
3
4 // 设置为“下午3点20分”
5 ldt = ldt.withHour(15).withMinute(20).withSecond(0).withNano(0); // 方式一:withXxx
6 ldt = ldt.toLocalDate().atTime(15, 20); // 方式二:使用之前介绍的API,先转换为LocalDate,再加上指定时间
7
8 // 加上“小时5分钟”
9 ldt = ldt.plusHours(3).plusMinutes(5); // plusXxx
10
11 // 今日0点
12 ldt = ldt.with(ChronoField.MILLI_OF_DAY, 0); // 方式一:with(ChronoField.Xxx, 数值)
13 ldt = LocalDateTime.of(LocalDate.now(), LocalTime.MIN); // 方式二
14 ldt = LocalDate.now().atTime(0, 0); // 方式三
15
16 // 下周二10点整
17 ldt = ldt.plusWeeks(1).with(ChronoField.DAY_OF_WEEK, 2).with(ChronoField.MILLI_OF_DAY, 0).withHour(10);
18
19 // 下一个周二10点整(针对今天是周一的特殊情形)
20 LocalDate ld = LocalDate.now();
21 if (!ld.getDayOfWeek().equals(DayOfWeek.MONDAY)) {
22 ld = ld.plusWeeks(1);
23 }
24 ldt = ld.with(ChronoField.DAY_OF_WEEK, 2).atTime(10, 0);
25
26 // 明天最后一刻
27 ldt = LocalDateTime.of(LocalDate.now().plusDays(1), LocalTime.MAX); // 方式一
28 ldt = LocalTime.MAX.atDate(LocalDate.now().plusDays(1)); // 方式二
29}
30
注意:
ChronoField是一个枚举,里面定义了很多表示日历的字段,MILLI_OF_DAY表示在一天中的毫秒数,值从0到(246060*1000) -1。
针对复杂的时间调整,JDK8专门定义了一个函数式接口TemporalAdjuster
,Instant、LocalDateTime和LocalDate等都实现了它。与此相关的还有TemporalAdjusters
类,里面提供了很多TemporalAdjuster的实现。
331// TemporalAdjuster接口
2public interface TemporalAdjuster {
3 Temporal adjustInto(Temporal temporal);
4}
5
6// TemporalAdjusters及测试
7public static void main(String[] args) {
8 LocalDateTime ldt = LocalDateTime.now();
9 LocalDate ld = LocalDate.now();
10
11 // 下一个周二10点整(针对今天是周一的特殊情形)
12 ldt = ld.with(TemporalAdjusters.next(DayOfWeek.TUESDAY)).atTime(10, 0);
13
14 // 本月最后一天的最后一刻
15 ldt = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()).atTime(LocalTime.MAX);// 方式一
16 long maxDayOfMonth = LocalDate.now().range(ChronoField.DAY_OF_MONTH).getMaximum();
17 ldt = LocalDate.now().withDayOfMonth((int) maxDayOfMonth).atTime(LocalTime.MAX); // 方式二
18
19 // 下个月第一个周一的下午5点整
20 ldt = LocalDate.now().plusMonths(1).with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)).atTime(17, 0);
21
22
23 /*
24 // 其它方法举例
25 public static TemporalAdjuster firstDayOfMonth()
26 public static TemporalAdjuster lastDayOfMonth()
27 public static TemporalAdjuster firstInMonth(DayOfWeek dayOfWeek)
28 public static TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek)
29 public static TemporalAdjuster previous(DayOfWeek dayOfWeek)
30 public static TemporalAdjuster nextOrSame(DayOfWeek dayOfWeek)
31 */
32}
33
JDK8中用Period
表示日期差,差值为N年N月N日;用Duration
表示时间差,单位可以为天(1天24小时)、时、分、秒、毫秒等。
121public static void main(String[] args) {
2 // 两个日期之间的年月天数
3 LocalDate ld1 = LocalDate.of(2016, 3, 24);
4 LocalDate ld2 = LocalDate.of(2017, 7, 12);
5 Period period = Period.between(ld1, ld2);
6 System.out.println(period.getYears() + "年" + period.getMonths() + "月" + period.getDays() + "天"); // 1年3月18天
7
8 // 两个时间之间的分钟数
9 Duration duration = Duration.between(LocalTime.of(9, 0), LocalTime.now());
10 System.out.println(duration.toDays() + "天|" + duration.toHours() + "时|" + duration.toMinutes() + "分|" + duration.toMillis() + "毫秒"); // 0天|7时|457分|27475454毫秒
11}
12
581// Date -> EpochMilli -> Instant
2public static Instant toInstant(Date date) {
3 long epochMilli = date.getTime();
4 return Instant.ofEpochMilli(epochMilli);
5}
6
7// Instant -> EpochMilli -> Date
8public static Date toDate(Instant instant) {
9 long epochMilli = instant.toEpochMilli();
10 return new Date(epochMilli);
11}
12
13// Date -> EpochMilli-> Instant -(defaultZoneId)-> LocalDateTime
14public static LocalDateTime toLocalDateTime(Date date) {
15 long epochMilli = date.getTime();
16 Instant instant = Instant.ofEpochMilli(epochMilli);
17 return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
18}
19
20// LocalDateTime -(defaultZoneId)-> ZonedDateTime -> instant -> EpochMilli -> Date
21public static Date toDate(LocalDateTime ldt) {
22 ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());
23 Instant instant = zdt.toInstant();
24 long epochMilli = instant.toEpochMilli();
25 return new Date(epochMilli);
26}
27
28// ZonedDateTime -> Calendar
29public static Calendar toCalendar(ZonedDateTime zdt) {
30 // ZonedDateTime -> instant -> EpochMilli
31 Instant instant = zdt.toInstant();
32 long epochMilli = instant.toEpochMilli();
33
34 // ZonedDateTime -> TimeZone
35 TimeZone tz = TimeZone.getTimeZone(zdt.getZone());
36
37 // TimeZone + EpochMilli -> Calendar
38 Calendar calendar = Calendar.getInstance(tz);
39 calendar.setTimeInMillis(epochMilli);
40
41 return calendar;
42}
43
44// Calendar -> ZonedDateTime
45public static ZonedDateTime toZonedDateTime(Calendar calendar) {
46 // Calendar -> EpochMilli-> Instant
47 long epochMilli = calendar.getTimeInMillis();
48 Instant instant = Instant.ofEpochMilli(epochMilli);
49
50 // Calendar -> TimeZone -> ZoneId
51 ZoneId zoneId = calendar.getTimeZone().toZoneId();
52
53 // Instant + ZoneId -> ZonedDateTime
54 ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, zoneId);
55
56 return zdt;
57}
58
java.lang.Object
类是Java语言中的根类,即所有类的父类,java.util.Objects
是与之配套的工具类。
方法名 | 说明 |
---|---|
protected Object clone() | 创建并返回对象的浅拷贝 |
boolean equals(Object obj) | 比较两个对象是否相等,默认按地址进行比较,可进行重写 |
protected void finalize() | 在对象被回收前由垃圾回收器调用 |
Class<?> getClass() | 获取对象的运行时对象的类 |
int hashCode() | 获取对象的 hash 值 |
void notify() | 唤醒在该对象上等待的某个线程 |
void notifyAll() | 唤醒在该对象上等待的所有线程 |
String toString() | 返回对象的字符串表示形式 |
void wait() | 让当前线程进入无限等待状态,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法 |
void wait(long timeout) | 让当前线程进入超时等待状态 |
void wait(long timeout, int nanos) | 让当前线程进入超时等待状态,支持纳秒级别控制(0-999999) |
281// Person默认继承Object,可对继承的方法进行重写
2public class Person {
3 private String name;
4 private int age;
5
6 // 重写equals
7
8 public boolean equals(Object o) {
9 // 如果对象地址一样,则认为相同
10 if (this == o)
11 return true;
12 // 如果参数为空,或者类型信息不一样,则认为不同
13 if (o == null || getClass() != o.getClass())
14 return false;
15
16 // 转换为当前类型
17 Person person = (Person) o;
18 // 要求基本类型相等,并且将引用类型交给java.util.Objects类的equals静态方法取用结果
19 return age == person.age && Objects.equals(name, person.name);
20 }
21
22 // 重写toString()
23
24 public String toString() {
25 return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
26 }
27}
28
注意:
重写equals的同时一般也需要重写
hashcode()
方法。对象比较推荐使用
Objects.equals(Object obj1, Object obj2)
方法,支持NULL值比较。
java.lang.System
类中提供了大量的静态方法,可以获取与系统相关的信息或进行系统级操作。java.lang.Runtime
描述的是运行时状态,在每一个JVM进程中都会提供唯一的一个Runtime类实例化对象,开发者可以通过Runtime类对象获取与JVM有关的运行时状态。
401public static void main(String[] args) {
2 // 输入流、输出流、错误流
3 // System.in // 输入流可以配合文本扫描器使用
4 System.out.println(System.in); // 输出流,内部调用了String.valueOf()进行输出
5 System.err.println("这是一些错误信息"); // 错误流
6
7 // 当前毫秒数
8 System.out.println(System.currentTimeMillis()); // 1667201631190
9
10 // 数组拷贝(高性能,支持源段和目标段重叠)
11 int[] arr = new int[]{1, 2, 3, 4, 5};
12 System.arraycopy(arr, 1, arr, 0, 3);
13 System.out.println(Arrays.toString(arr)); // [2, 3, 4, 4, 5]
14
15 // 获取内存、处理器相关信息
16 System.out.println(Runtime.getRuntime().availableProcessors()); // 16
17 System.out.println(Runtime.getRuntime().maxMemory()); // 7497842688
18 System.out.println(Runtime.getRuntime().totalMemory()); // 532152320
19 System.out.println(Runtime.getRuntime().freeMemory()); // 526854392
20
21 // 执行系统命令
22 try {
23 Process process = Runtime.getRuntime().exec("cmd /C mkdir java-test");
24 } catch (IOException e) {
25 e.printStackTrace();
26 }
27
28 // 关闭钩子
29 Runtime.getRuntime().addShutdownHook(new Thread() {
30
31 public void run() {
32 System.out.println("----hook----");
33 }
34 });
35
36 // 手动GC
37 System.gc();
38 Runtime.getRuntime().gc();
39}
40
注意:
System和Runtime都可以调用gc方法,但不建议在代码中手动干预,并且代码中的gc操作是可以通过JVM的运行参数来屏蔽的。
Random
类的实例用于生成伪随机数流。下面是一个生成范围1-n随机数的示例:
131public static void main(String[] args) {
2 // 创建对象
3 // Random r = new Random(); // 默认构造使用“真随机数”作为随机数种子
4 Random r = new Random(System.currentTimeMillis()); // 使用当前时间作为随机数种子
5
6 // 输出随机数
7 for (int i = 0; i < 10; i++) {
8 // [0,5)
9 System.out.print(r.nextInt(5) + " "); // 4 3 0 3 3 0 0 1 2 1
10 }
11
12}
13
注意:
如果随机数种子相同,那么它们将生成并返回相同的数字序列。
Random类是线程安全的,但如果并发太高,会产生竞争,这时候可以考虑
ThreadLocalRandom
类或Math.random()
方法。Java类库中还有一个随机类
SecureRandom
,可以产生安全性更高、随机性更强的随机数,用于安全加密等领域。
java.lang.Math
类包含了一些数学常量和基本数学运算,如初等指数、对数、平方根和三角函数等。
221public static void main(String[] args) {
2 // 绝对值
3 System.out.println(Math.abs(-5.1)); // 5.1
4
5 // 向上/向下取整
6 System.out.println(Math.ceil(1.2) + " " + Math.ceil(-1.2)); // 2.0 -1.0
7 System.out.println(Math.floor(1.2) + " " + Math.floor(-1.2)); // -1.0 -2.0
8
9 // 四舍五入
10 System.out.println(Math.round(0.4) + " " + Math.round(0.5)); // 0 1
11
12 // 随机
13 System.out.println(Math.random()); // [0,1) 0.28153917690592767
14
15 // 最大值/最小值
16 System.out.println(Math.max(1, 2)); // 2
17 System.out.println(Math.min(1, 2)); // 1
18
19 // 常量PI
20 System.out.println(Math.PI); // 3.141592653589793
21}
22
java.math.BigDecimal
用于对浮点数进行精确的运算,一般用于金额、利率计算等场景。
141public static void main(String[] args) {
2 // BigDecimal(int)/BigDecimal(long) 基于整数构建BigDecimal对象
3 BigDecimal bigDecimalByLong = new BigDecimal(18974865155L);
4 System.out.println(bigDecimalByLong); // 18974865155
5
6 // BigDecimal(double) 基于浮点数构建BigDecimal对象(极不推荐,不精确)
7 BigDecimal bigDecimalByDouble = new BigDecimal(1.2);
8 System.out.println(bigDecimalByDouble); // 1.1999999999999999555910790149937383830547332763671875
9
10 // BigDecimal(String) 基于数值字符串构建BigDecimal对象(推荐)
11 BigDecimal bigDecimalByString = new BigDecimal("1.2");
12 System.out.println(bigDecimalByString); // 1.2
13}
14
BigDecimal是对象类型,不能使用传统的+-*/
等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法,方法中的参数也必须是BigDecimal的对象。
211public static void main(String[] args) {
2 BigDecimal left = new BigDecimal("1.2");
3
4 // 加减乘除
5 System.out.println(left.add(new BigDecimal("1.3"))); // 2.5
6 System.out.println(left.subtract(new BigDecimal("1.3"))); // -0.1
7 System.out.println(left.multiply(new BigDecimal("1.3"))); // 1.56
8 // System.out.println(left.divide(new BigDecimal("1.3"))); // java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
9 System.out.println(left.divide(new BigDecimal("0.13"), 2, BigDecimal.ROUND_HALF_UP)); // 9.23 四舍五入保留2位小数
10
11 // 比较(compareTo忽略精度,只比较数值,equals则精度和数值都要求相同)
12 System.out.println(left.compareTo(new BigDecimal("1.3"))); // -1 小于时返回负数
13 System.out.println(left.compareTo(new BigDecimal("1.20"))); // 0 等于时返回0
14 System.out.println(left.equals(new BigDecimal("1.2"))); // true
15 System.out.println(left.equals(new BigDecimal("1.20"))); // false 精度不一致
16
17 // 转化为基本类型
18 System.out.println(new BigDecimal("1.23").toString()); // 1.23
19 System.out.println(new BigDecimal("1.23").longValue()); // 1
20 System.out.println(new BigDecimal("1.23").doubleValue()); // 1.23
21}
注意:
不推荐基于浮点数构建BigDecimal对象,因为双精度浮点型(double)的最大有效位为16位。
但如果必须使用,则可以先转换为字符串:
Double.toString(double)
,再基于数值字符串构建BigDecimal对象。如果进行除法运算的时候,结果不能整除(有余数),这个时候会报java.lang.ArithmeticException,需要设置精度和舍入模式。
如果需要处理的数为大整数,可以使用
java.math.BigInteger
来替代。
java.util.Scanner
类是一个可以使用正则表达式来解析基本类型和字符串的简单文本扫描器。
121public static void main(String[] args) {
2 // 定义扫描器
3 Scanner sc = new Scanner(System.in); // System.in表示从键盘扫描
4
5 // 阻塞等待键盘输入
6 System.out.print("请录入一个整数:");
7 int i = sc.nextInt(); // 读取一个整数
8
9 // 回显
10 System.out.println("你输入的整数是:" + i);
11}
12
java.util.Comparator
接口用于比较两个对象的大小,小于时返回负数,等于时返回0,大于时返回正数。
201// 比较器接口
2public interface Comparator<T> {
3 int compare(T o1, T o2);
4 boolean equals(Object obj);
5}
6
7// String内部有一个忽略大小写的比较器
8public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();
9
10// 包装类、String中一般都有compareTo方法
11Integer integer = new Integer(0);
12System.out.println(integer.compareTo(1)); // -1
13
14// 比较器反转
15Comparator<Object> objectComparator = Collections.reverseOrder(); // 倒序
16Comparator<String> stringComparator = Collections.reverseOrder(String.CASE_INSENSITIVE_ORDER); // 将String.CASE_INSENSITIVE_ORDER比较器反转
17
18// 构建比较器,然后反转,再引入次要比较器
19students.sort(Comparator.comparing(Student::getScore).reversed().thenComparing(Student::getName)); // 将学生列表按照分数倒序排(高分在前),分数一样的,按照名字进行排序
20
JDK1.7引入了AutoCloseAble
接口,配合try(获取资源){}
语法进行使用,这样获取的资源可以自动关闭,无需手动释放。
451// AutoCloseable接口
2public interface AutoCloseable {
3 void close() throws Exception;
4}
5
6// 实现AutoCloseable接口
7public class TransactionManager implements AutoCloseable {
8 private boolean rollback = true;
9
10 public TransactionManager() {
11 // 开启事务,并将事务状态存在线程变量中
12 TransManager.startTrans();
13 }
14
15 public void doSuccess() {
16 // 提交当前线程事务
17 TransManager.commitTrans();
18 rollback = false;
19 }
20
21
22 public void close() {
23 // 如果事务没有提交,则默认执行回滚
24 if (rollback) {
25 TransManager.rollbackTrans();
26 }
27 }
28}
29
30// 测试
31public static void main(String[] args) {
32 // 在try()中获取实现了AutoCloseable接口的资源
33 // try代码块执行完毕后自动调用其close()方法
34 try (TransactionManager transactionManager = new TransactionManager()) {
35 // 业务处理
36 System.out.println("业务处理中...");
37
38 // 手动提交事物
39 transactionManager.doSuccess();
40 } catch (Exception e) {
41 // 异常处理
42 e.printStackTrace();
43 }
44}
45
java.util.Optional
是Java 8引入的一个泛型容器类,内部只有一个类型为T的单一变量value,可能为null,也可能不为null。
Optional有什么用呢?它用于准确地传递程序的语义,它清楚地表明,其代表的值可能为null,程序员应该进行适当的处理。
91// 构建Optional
2public static<T> Optional<T> empty() // 构建一个空的Optional,value为null
3public static <T> Optional<T> of(T value) // 构建一个非空的Optional, 参数value不能为null
4public static <T> Optional<T> ofNullable(T value) // 构建一个Optional,参数value可以为null,也可以不为null
5
6// 使用Optional
7public boolean isPresent() // value不为null时返回true
8public T get() // 返回实际的值,如果为null,抛出异常NoSuchElementException
9public T orElse(T other) // 如果value不为null,返回value,否则返回other