avatar


3.最常用的Java自带的类

这一章,我们讨论最常用的Java自带的类。

我个人总结,使用频率最高的,Java自带的,至少有如下十一种:

  1. String
  2. StringBuilder
  3. ArrayList
  4. Arrays
  5. Math
  6. System
  7. Object
  8. Integer
  9. Data和Calendar
  10. java.time
  11. 异常类

我们一个一个讨论。

String

这个其实我们在第一章《1.基础语法》的时候就讨论过一点,当时我们讨论字符串的"+运算"。
现在,我们专门讨论一下这个String。

String的概述

​String类代表字符串,Java程序中的所有字符串文字都被实现为此类的实例。
也就是说,Java程序中所有的双引号字符串,都是String类的对象。
String类在java.lang包下,使用的时候不需要导包。

String的特点

  1. 字符串不可变,它们的值在创建后不能被更改。
  2. 虽然String的值是不可变的,但是它们可以被共享。
  3. 字符串效果上相当于字符数组char[],但是底层原理是字节数组byte[]

这几点或许都不好理解。
字符串不可变?

示例代码:

1
2
3
4
String s = "张曼玉";
System.out.println(s);
s = "林青霞";
System.out.println(s);

运行结果:

1
2
张曼玉
林青霞

这看着可变的啊。
还有可以被共享,这到底在说啥?
我们会在下文解释这几个特点。

String类的构造方法

String类有很多构造方法,我们讨论几种常用的构造方法。

方法名 说明
public String() 创建一个空白字符串对象,不含有任何内容。
public String(char[] chs) 根据字符数组的内容,来创建字符串对象。
public String(byte[] bys) 根据字节数组的内容,来创建字符串对象。
String s = "abc" 直接赋值的方式创建字符串对象。
  • 实际上,String s = "abc",不属于构造方法。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//public String():创建一个空白字符串对象,不含有任何内容
String s1 = new String();
System.out.println("s1:" + s1);

//public String(char[] chs):根据字符数组的内容,来创建字符串对象
char[] chs = {'a', 'b', 'c'};
String s2 = new String(chs);
System.out.println("s2:" + s2);

//public String(byte[] bys):根据字节数组的内容,来创建字符串对象
byte[] bys = {97, 98, 99};
String s3 = new String(bys);
System.out.println("s3:" + s3);

//String s = “abc”; 直接赋值的方式创建字符串对象,内容就是abc
String s4 = "abc";
System.out.println("s4:" + s4);

运行结果:

1
2
3
4
s1:
s2:abc
s3:abc
s4:abc

字符串的内存结构

在上文,我们讨论了字符串初始化的两种方法,两种方法实际上存在区别。

  1. 通过构造方法创建
    通过new创建的字符串对象,每一次new都会申请一个内存空间,虽然内容相同,但是地址不同。
  2. 直接赋值方式创建:
    以""方式给出的字符串,只要字符序列相同(顺序和大小写),无论在程序代码中出现几次,JVM都只会建立一个String对象,并在字符串池中维护。

我们举例子说明。
示例代码:

1
2
3
4
5
6
7
8
9
10
char[] chs = {'a','b','c'};
String s1 = new String(chs);
String s2 = new String(chs);
System.out.println(s1 == s2);

String s3 = "abc";
String s4 = "abc";
System.out.println(s3 == s4);

System.out.println(s1 == s3);

运行结果:

1
2
false
true
  • 都是abc,怎么一会儿相等,一会儿不相等?

这是因为,==号,对于基本数据类型,比较的是具体的值;对于引用数据类型,比较的是对象地址。
我们来看看具体的内存结构。

String内存

解释一下。

首先main方法进栈。
执行char[] chs = {'a','b','c'}
栈内存的main方法中有了char[] chs,堆内存有了new char[3]chs指向new char[3]

String s1 = new String(chs)
栈内存的main方法中有了String s1,堆内存有了new String(chs)s1指向new String(chs)

String s2 = new String(chs)
栈内存的main方法中有了String s2,堆内存有了new String(chs)s2指向new String(chs)
特别注意!这里的new String(chs)和上一步的new String(chs)不一样,是堆内存的两个不同的对象。

System.out.println(s1 == s2),输出false

String s3 = "abc"
栈内存的main方法中有了String s3,堆内存的常量池中有了一个内容"abc"s3指向堆内存中的常量池的空间。

String s4 = "abc"
栈内存的main方法中有了String s4,堆内存的常量池已经有了一个内容"abc"s4指向堆内存中的常量池的空间。注意,这时候s3s4指向的是同一个内存地址。

System.out.println(s3 == s4),输出true

System.out.println(s1 == s3),输出false

最后,main方法出栈。

通过分析内存结构,也解释了我们说的第二个特点,共享。
比如s3s4就共享了堆内存中常量池的同一个东西。

String拼接的内存结构

接下来,我们解释第一个特点,字符串不可变。

来看这个例子。
示例代码:

1
2
3
String s = "hello";
s += "world";
System.out.println(s);

运行结果:

1
helloworld

这看起来还是可变啊?
我们来看内存结构。

String的字符串拼接

解释一下。

首先main方法入栈。
执行String s = "hello"
栈内存的main方法中有了一个String s,堆内存的常量池中有了一个"hello",s指向堆内存的常量池中的hello。

执行s += "world"
这个我们在第一章《1.基础语法》讨论赋值运算符的时候讨论过。s += "world"等价于s = s + "world"
但是常量池中并没有"world",所以只能再新增一个常量"world"。
s + "world"s"hello"s + "world"的结果是"helloworld",常量池中并没有"helloworld",所以还需要新增一个"helloworld"
然后s指向常量池中的"helloworld"

这就解释了第一个特点,字符串不可变,即对象中的内容是不可变的。

通过上述过程,我们还发现,每次拼接,都会构建一个新的String对象。浪费内存,浪费时间。
解决办法是StringBuilder。我们在本章就会讨论。

字符串的比较

再回到上一个例子。
==号的作用:对于基本数据类型,比较的是具体的值;对于引用数据类型,比较的是对象地址。
那么,如果我们一定要比较String的具体的值是否相等。怎么办?
equals

1
public boolean equals(String s)

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//构造方法的方式得到对象
char[] chs = {'a', 'b', 'c'};
String s1 = new String(chs);
String s2 = new String(chs);

//直接赋值的方式得到对象
String s3 = "abc";
String s4 = "abc";

//比较字符串对象地址是否相同
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s3 == s4);
System.out.println("--------");

//比较字符串内容是否相同
System.out.println(s1.equals(s2));
System.out.println(s1.equals(s3));
System.out.println(s3.equals(s4));

运行结果:

1
2
3
4
5
6
7
false
false
true
--------
true
true
true

equals源码分析

关于字符串的equals方法,有一个特点,并没有利用哈希值。
关于什么是哈希值,我们会在《4.集合》讨论Set的时候,做更详细的讨论。
现在,我们只需要记住,equals没有利用哈希值。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}

String的常用方法

除了我们讨论过的equals,还有几个常用方法。我们在这里列举一下。

方法名 说明
public boolean equals(Object object) 比较字符串的内容,严格区分大小写。
public char charAt(int index) 返回指定索引处的char值。
public int length() 返回此字符串的长度。
substring(int beginIndex) 这截取的字符串是从索引beginIndex开始的,到整个字符串的末尾,例如:字符串String s = “abcdef”;调用s.substring(2)表示从字符串的索引2开始截取到整个字符串结束,截取的字符串为cdef。
substring(int beginIndex, int endIndex) 截取的字符串从beginIndex开始,到字符串索引的endIndex - 1结束,即截取的字符串不包括endIndex这个索引对应的字符,所以endIndex的最大值为整个字符串的长度,所以使用这个方法的时候需要特别注意容易发生字符串截取越界的问题。

StringBuilder

在上文,我们讨论字符串的拼接的时候,说String每次拼接,都会构建一个新的String对象。浪费内存,浪费时间。解决办法是StringBuilder。

StringBuilder的概述

StringBuilder是一个可变的字符串类,这里的可变指的是StringBuilder对象中的内容是可变的。这就是StringBuilder和String的主要区别。

StringBuilder的构造方法

方法名 说明
public StringBuilder() 创建一个空白可变字符串对象,不含有任何内容。
public StringBuilder(String str) 根据字符串的内容,来创建可变字符串对象。

示例代码:

1
2
3
4
5
6
7
8
9
//public StringBuilder():创建一个空白可变字符串对象,不含有任何内容
StringBuilder sb1 = new StringBuilder();
System.out.println("sb1:" + sb1);
System.out.println("sb1.length():" + sb1.length());

//public StringBuilder(String str):根据字符串的内容,来创建可变字符串对象
StringBuilder sb2 = new StringBuilder("hello");
System.out.println("sb2:" + sb2);
System.out.println("sb2.length():" + sb2.length());

运行结果:

1
2
3
4
sb1:
sb1.length():0
sb2:hello
sb2.length():5

StringBuilder的添加和反转方法

添加和反转方法:

方法名 说明
public StringBuilder append(任意类型) 添加数据,并返回对象本身
public StringBuilder reverse() 相反的字符序列,并返回对象本身

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
StringBuilder sb1 = new StringBuilder();
System.out.println("sb1:" + sb1);
//public StringBuilder append(任意类型):添加数据,并返回对象本身
StringBuilder sb2 = sb1.append("hello");

System.out.println("sb1:" + sb1);
System.out.println("sb2:" + sb2);
System.out.println(sb1 == sb2);

StringBuilder sb3 = new StringBuilder();
//链式编程
sb3.append("hello").append("world").append("java").append(100);
System.out.println("sb3:" + sb3);

//public StringBuilder reverse():返回相反的字符序列
StringBuilder sb4 = sb3.reverse();
System.out.println("sb3:" + sb3);
System.out.println("sb4:" + sb4);
System.out.println(sb3 == sb4);

运行结果:

1
2
3
4
5
6
7
8
sb1:
sb1:hello
sb2:hello
true
sb3:helloworldjava100
sb3:001avajdlrowolleh
sb4:001avajdlrowolleh
true

StringBuilder和String相互转换

通过上面的例子,我们发现StringBuilder的功能比String强大,所以有时候我们想利用StringBuilder的一些功能,那就需要转成StringBuilder类型的;然后在利用完成之后,又想再转回String。
这就涉及到了StringBuilder和String相互转换。

StringBuilder转换为String:

1
public String toString()
  • 通过toString()就可以实现把StringBuilder转换为String

String转换为StringBuilder:

1
public StringBuilder(String s)
  • 通过构造方法就可以实现把String转换为StringBuilder

StringBuilder的常用方法

小结一下,StringBuilder的常用方法:

方法名 说明
public StringBuilder append (任意类型) 添加数据,并返回对象本身。
public StringBuilder reverse() 相反的字符序列,并返回对象本身。
public int length() 返回长度,实际存储值。
public String toString() 通过toString()就可以实现把StringBuilder转换为String

ArrayList

这不是我们第一次讨论ArrayList,在《算法入门经典(Java与Python描述):2.数组、链表》中,我们也有讨论ArrayList,当时我们还专门基于数组实现了一个ArrayList。这次,我们主要讨论在Java中的ArrayList。

ArrayList的概述

ArrayList是集合的一种。
集合是指一种存储空间可变的存储模型,存储的数据容量可以发生改变。
(关于集合,我们在之后的章节中会有更详细的讨论。)

ArrayList的特点:底层是数组实现的,长度可以变化。

ArrayList的构造方法

方法名 说明
public ArrayList() 创建一个空的集合对象
  • 除了这种,还有其他构造方法。但是不常用。

示例代码:

1
ArrayList<String> array = new ArrayList<String>();

注意看,这里有一个<>,里面写着String。这个含义是说这个ArrayList的元素的数据类型是String。
除了String,还可以是其他各种各样的引用类型,比如ArryList<Student>,表示每一个元素的数据类型都是Student。
这就是泛型的使用,用于约束集合中存储元素的数据类型。但是需要注意的是,基本数据类型不可以。
(关于泛型,我们在下一章《4.集合》做更多的讨论。)

ArrayList的成员方法

方法名 说明
public boolean remove(Object o) 删除指定的元素,返回删除是否成功。
public E remove(int index) 删除指定索引处的元素,返回被删除的元素。
public E set(int index,E element) 修改指定索引处的元素,返回被修改的元素。
public E get(int index) 返回指定索引处的元素。
public int size() 返回集合中的元素的个数。
public boolean add(E e) 将指定的元素追加到此集合的末尾。
public void add(int index,E element) 在此集合中的指定位置插入指定的元素。

示例代码:

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

import java.util.ArrayList;

public class ArrayListDemo {
public static void main(String[] args) {
// 创建集合
ArrayList<String> array = new ArrayList<String>();
// JDK 7 及之后的新特性
// ArrayList<String> array = new ArrayList<String>();

// 添加元素
array.add("kakawanyifan");
array.add("hello");
array.add("world");
array.add("java");

// public boolean remove(Object o):删除指定的元素,返回删除是否成功
System.out.println(array.remove("world"));
System.out.println(array.remove("javaee"));

// public E remove(int index):删除指定索引处的元素,返回被删除的元素
System.out.println(array.remove(1));

// IndexOutOfBoundsException
// System.out.println(array.remove(5));

// public E set(int index,E element):修改指定索引处的元素,返回被修改的元素
System.out.println(array.set(1,"javaee"));

// IndexOutOfBoundsException
// System.out.println(array.set(3,"javaee"));

//public E get(int index):返回指定索引处的元素
System.out.println(array.get(0));
System.out.println(array.get(1));
// IndexOutOfBoundsException
// System.out.println(array.get(3));

//public int size():返回集合中的元素的个数
System.out.println(array.size());

//输出集合
System.out.println("array:" + array);
}
}

运行结果:

1
2
3
4
5
6
7
8
true
false
hello
java
kakawanyifan
javaee
2
array:[kakawanyifan, javaee]

关于ArrayList,还有一个非常常见的场景,遍历。这个其实很简单,sizegetset配合起来,就可以遍历了。

Arrays

Arrays的常用方法

除了ArrayList,还有一个是Arrays。
Arrays的常用方法

方法名 说明
public static String toString(int[] a) 返回指定数组的内容的字符串表示形式。
public static void sort(int[] a) 按照数字顺序排列指定的数组。

这个我们在《算法入门经典(Java与Python描述):5.排序》中有大量使用。就不举例了。

工具类的设计思想

Array这个类,和我们之前的类的最大不同在于。之前的类,一般都需要先实例化,然后再调用。
Array这个类,不需要。直接用类名就可以,这是一个典型的工具类。
我们可以看看Array这个类的源代码,其中有两点值得我们学习。

一、构造方法用private,防止外界创建对象。
构造方法用private,防止外界创建对象。

二、成员方法用public static,为了使用类名访问成员方法。
成员方法用public static,目的是为了使用类名访问成员方法。

这两点也是我们在设计工具类时候,非常建议的准则。

  1. 构造方法用private,防止外界创建对象。
  2. 成员方法用public static,为了使用类名访问成员方法。

这就是我们在上一章《2.面向对象》所说的,构造方法不一定是public。

Math

Math类概述:Math包含执行基本数字运算的方法
Math中方法的调用方式:方法都是静态的,通过类名.进行调用

Math类的常用方法:

方法名 说明
public static int abs(int a) 返回参数的绝对值。
public static double ceil(double a) 返回大于或等于参数的最小double值,等于一个整数。
public static double floor(double a) 返回小于或等于参数的最大double值,等于一个整数。
public static int round(float a) 按照四舍五入返回最接近参数的int。
public static int max(int a,int b) 返回两个int值中的较大值。
public static int min(int a,int b) 返回两个int值中的较小值。
public static double pow (double a,double b) 返回a的b次幂的值。
public static double random() 返回值为double的正值,[0.0,1.0)。

Math也是一个典型的工具类,同样我们可以来看看Math的设计。
构造方法:
Math的构造方法

成员方法:
Math的成员方法

同样符合我们上述关于工具类的设计准则。

  1. 构造方法用private
  2. 成员方法用public static

System

System类的常用方法:

方法名 说明
public static void exit(int status) 终止当前运行的 Java 虚拟机,非零表示异常终止
public static long currentTimeMillis() 返回当前时间(以毫秒为单位)
  • 注意,虽然返回值的时间单位是毫秒,但是该值的粒度取决于底层操作系统。可能有些操作系统以几十毫秒为测量时间。
  • 是当前和1970年1月1日0时0分0秒0毫秒之间的毫秒数。(如果考虑时差,就不是0时,比如在中国是8时。)

示例代码:

1
2
3
4
5
6
7
8
// 获取开始的时间节点
long start = System.currentTimeMillis();
for (int i = 1; i <= 10000; i++) {
System.out.println(i);
}
// 获取代码运行结束后的时间节点
long end = System.currentTimeMillis();
System.out.println("共耗时:" + (end - start) + "毫秒");

运行结果:

1
2
3
4
5
6
7
8
9
10
1
2
3

【部分运行结果略】

9998
9999
10000
共耗时:49毫秒

Object

Object,或许在我们写的代码中不算最常见,但这是所有的类的根,每个类都直接或间接继承自Object。所有的对象包括数组都实现了这个类的方法。

包括我们设计的类,也继承自Object。而我们设计的类,通常需要重写的方法有:

  1. hashCode
  2. equals
  3. toString

我们在下一章《4.集合》就会看到例子。

Integer

基本数据类型的包装类

在第一章《1.基础语法》,我们讨论过基本数据类型,七种数值型的,再加上一种布尔型的,一共八种。

  1. byte
  2. short
  3. int
  4. long
  5. float
  6. double
  7. char
  8. boolean

这八种基本数据类型,都有各自对应的包装类。即将基本数据类型封装成了引用数据类型,其好处在于可以在对象中定义更多的功能方法操作该数据。

基本数据类型 包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

这里我们主要讨论一下Integer类,其他类都差不多。

Integer的构造方法

Integer,包装基本数据类型int。

Integer类构造方法:

方法名 说明
public Integer(int value) 根据 int 值创建 Integer 对象(过时)
public Integer(String s) 根据 String 值创建 Integer 对象(过时)
public static Integer valueOf(int i) 返回表示指定的 int 值的 Integer 实例
public static Integer valueOf(String s) 返回一个保存指定值的 Integer 对象 String

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// public Integer(int value):根据 int 值创建 Integer 对象(过时)
Integer i1 = new Integer(100);
System.out.println(i1);

// public Integer(String s):根据 String 值创建 Integer 对象(过时)
Integer i2 = new Integer("100");
// Integer i2 = new Integer("abc"); //NumberFormatException
System.out.println(i2);
System.out.println("--------");

// public static Integer valueOf(int i):返回表示指定的 int 值的 Integer 实例
Integer i3 = Integer.valueOf(100);
System.out.println(i3);

// public static Integer valueOf(String s):返回一个保存指定值的Integer对象 String
Integer i4 = Integer.valueOf("100");
System.out.println(i4);

运行结果:

1
2
3
4
5
100
100
--------
100
100

int和String类型的相互转换

上述构造方法在int和String类型的相互转换中常用。

int转换为String

方式一:直接在数字后加一个空字符串。
方式二:通过String类静态方法valueOf()。

示例代码:

1
2
3
4
5
6
7
8
9
//int --- String
int number = 100;
//方式1
String s1 = number + "";
System.out.println(s1);
//方式2
//public static String valueOf(int i)
String s2 = String.valueOf(number);
System.out.println(s2);

运行结果:

1
2
100
100
  • + "",可以将int转换成字符串,这个我们在第一章《1.基础语法》讨论过。在"+"操作中,如果出现了字符串,+就是字符串连接运算符,否则就是算术运算符。

String转换为int

方式一:先将字符串数字转成Integer,再调用valueOf()方法
方式二:通过Integer静态方法parseInt()进行转换

示例代码:

1
2
3
4
5
6
7
8
9
10
11
//String --- int
String s = "100";
//方式1:String --- Integer --- int
Integer i = Integer.valueOf(s);
//public int intValue()
int x = i.intValue();
System.out.println(x);
//方式2
//public static int parseInt(String s)
int y = Integer.parseInt(s);
System.out.println(y);

运行结果:

1
2
100
100

自动拆箱和自动装箱

自动装箱:自动的把基本数据类型转换为对应的包装类类型。
自动拆箱:自动的把包装类类型转换为对应的基本数据类型。

示例代码:

1
2
3
4
// 自动装箱
1. Integer a = 100;
// 自动拆箱
2. int b = a;
  • 自动装箱,相当于Java编译器替我们执行了Integer.valueOf(XXX);
  • 自动拆箱,相当于Java编译器替我们执行了Integer.intValue(XXX);

Integer的比较

最后,我们来看看一个比较有意思的现象。其实这个现象,我们不是第一次讨论。在《算法入门经典(Java与Python描述):7.哈希表》这一章,讨论Java中的哈希表的时候,我们也讨论过。
来看例子,示例代码:

1
2
3
4
5
6
7
8
Integer a = 100;
Integer b = 100;
Integer c = 200;
Integer d = 200;
// 打印true
System.out.println(a==b);
// 打印false
System.out.println(c==d);

运行结果:

1
2
true
false

100=100100=100,这个没有任何问题。但是难道200200200\neq 200
问题出在Integer.valueOf(),看看这个到底做了什么。
Integer.valueOf()

[128,127][-128,127]之间使用了缓存,这么做的原因是认为该区间会被经常使用到,为了提高效率,所以利用缓存,防止每次自动装箱都创建一次对象实例。
但是呢!需要注意的是,其他的包装类,没有这个"坑"。因为其他的包装类,例如double、float,不存在常用数字,不适合缓存。

Data和Calendar

Data和Calendar都是时间日期类。

Date的构造方法

Date代表了一个特定的时间,精确到毫秒。

Date类构造方法

方法名 说明
public Date() 分配一个 Date对象,并初始化,以便它代表它被分配的时间,精确到毫秒。
public Date(long date) 分配一个 Date对象,并将其初始化为表示从标准基准时间起指定的毫秒数。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.Date;

public class M {
public static void main(String[] args) {
//public Date():分配一个 Date对象,并初始化,以便它代表它被分配的时间,精确到毫秒
Date d1 = new Date();
System.out.println(d1);

//public Date(long date):分配一个 Date对象,并将其初始化为表示从标准基准时间起指定的毫秒数
long date = 1000*60*60;
Date d2 = new Date(date);
System.out.println(d2);

// Thu Jan 01 08:00:00 CST 1970
System.out.println(new Date(0));
}
}

运行结果:

1
2
3
Sun Aug 29 22:16:44 CST 2021
Thu Jan 01 09:00:00 CST 1970
Thu Jan 01 08:00:00 CST 1970

Date的常用方法

方法名 说明
public long getTime() 获取的是日期对象从1970年1月1日 00:00:00到现在的毫秒值。
public void setTime(long time) 设置时间,给的是毫秒值。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Date;

public class M {
public static void main(String[] args) {
//创建日期对象
Date d = new Date();

//public long getTime():获取的是日期对象从1970年1月1日 00:00:00到现在的毫秒值
System.out.println(d.getTime());
System.out.println(d.getTime() * 1.0 / 1000 / 60 / 60 / 24 / 365 + 1970 + "年");

//public void setTime(long time):设置时间,给的是毫秒值
long time = System.currentTimeMillis();
d.setTime(time);

System.out.println(d);
}
}

运行结果:

1
2
3
1630246762658
2021.694785726091年
Sun Aug 29 22:19:22 CST 2021

SimpleDateFormat

那么,如果我们想输出那种更常见的表示的日期格式怎么办?
比如,下面这种。

1
2021年08月29日 22:42:12

SimpleDateFormat

SimpleDateFormat类构造方法

方法名 说明
public SimpleDateFormat() 构造一个SimpleDateFormat,使用默认模式和日期格式。
public SimpleDateFormat(String pattern) 构造一个SimpleDateFormat使用给定的模式和默认的日期格式。

SimpleDateFormat类的常用方法

方法名 说明
public final String format(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
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class M {
public static void main(String[] args) throws ParseException {
//格式化:从 Date 到 String
Date d = new Date();
// SimpleDateFormat sdf = new SimpleDateFormat();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String s = sdf.format(d);
System.out.println(s);
System.out.println("--------");

//从 String 到 Date
String ss = "2048-08-09 11:11:11";
//ParseException
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date dd = sdf2.parse(ss);
System.out.println(dd);
}
}

运行结果:

1
2
3
2021年05月10日 15:19:43
--------
Sun Aug 09 11:11:11 CST 2048

关于时间日期格式字符串有很多,但我们只需要记住几个常用的。

格式 释义 举例
yyyy 2019
MM 02
dd 18
HH 小时(24小时制) 13
mm 分钟 53
ss 42
SSS 毫秒 629

Calendar

Calendar的getInstance,返回一个表示当前时间的Calendar对象。

示例代码:

1
Calendar rightNow = Calendar.getInstance();

Calendar类常用方法

方法名 说明
public int get(int field) 返回给定日历字段的值
public abstract void add(int field, int amount) 根据日历的规则,将指定的时间量添加或减去给定的日历字段。
public final void set(int year,int month,int date) 设置当前日历的年月日。

示例代码:

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

public class M {
public static void main(String[] args) {
//获取日历类对象
Calendar c = Calendar.getInstance();

//public int get(int field):返回给定日历字段的值
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH) + 1;
int date = c.get(Calendar.DATE);
System.out.println(year + "年" + month + "月" + date + "日");

//public abstract void add(int field, int amount):根据日历的规则,将指定的时间量添加或减去给定的日历字段
//需求1:3年前的今天
c = Calendar.getInstance();
c.add(Calendar.YEAR,-3);
year = c.get(Calendar.YEAR);
month = c.get(Calendar.MONTH) + 1;
date = c.get(Calendar.DATE);
System.out.println(year + "年" + month + "月" + date + "日");

//需求2:10年后的10天前
c = Calendar.getInstance();
c.add(Calendar.YEAR,10);
c.add(Calendar.DATE,-10);
year = c.get(Calendar.YEAR);
month = c.get(Calendar.MONTH) + 1;
date = c.get(Calendar.DATE);
System.out.println(year + "年" + month + "月" + date + "日");

//public final void set(int year,int month,int date):设置当前日历的年月日
c = Calendar.getInstance();
c.set(2050,10,10);
year = c.get(Calendar.YEAR);
month = c.get(Calendar.MONTH) + 1;
date = c.get(Calendar.DATE);
System.out.println(year + "年" + month + "月" + date + "日");

}
}

运行结果:

1
2
3
4
2021年5月10日
2018年5月10日
2031年4月30日
2050年11月10日

案例需求:获取任意一年的二月有多少天

示例代码:

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

public class M {
public static void main(String[] args) {
//键盘录入任意的年份
Scanner sc = new Scanner(System.in);
System.out.println("请输入年:");
int year = sc.nextInt();

//设置日历对象的年、月、日
Calendar c = Calendar.getInstance();
c.set(year, 2, 1);

//3月1日往前推一天,就是2月的最后一天
c.add(Calendar.DATE, -1);

//获取这一天输出即可
int date = c.get(Calendar.DATE);
System.out.println(year + "年的2月份有" + date + "天");
}
}

运行结果:

1
2
3
请输入年:
1989
1989年的2月份有28天

java.time

我们讨论了DataCalendar,但其实我都不建议大家使用,推荐java.time。只是可能有一些旧项目,一时半会儿改不了,所以讨论了一下DataCalendar

DateCalendar,更准确来说是java.util.Datejava.util.Calendar,这两个堪称糟糕设计的典范,使用起来极其不方便。其实我本科毕业后,第一份工作是写C#,在见过了C#中畅快无比的DateTime后,这两个一度让我对整个Java产生了深深的厌恶。
但是Java强大之处在与拥有丰富的生态,比如有一个joda-time,这是一个第三方开源的时间日期类。

https://github.com/JodaOrg/joda-time

在JDK8中,已经被整合进了Java中。在JDK7之前的版本中,则需要我们手工引入包。

接下来,我们就来讨论java.time

常用类的概述和功能介绍

java.time,注意都是小写,这其实是一个包,这个包中常用的类有七种:

  1. Instant类
    Instant类对时间轴上的单一瞬时点建模,可以用于记录应用程序中的事件时间戳,之后学习的类型转换中,均可以使用Instant类作为中间类完成转换
  2. Duration类
    Duration类表示秒或纳秒时间间隔,适合处理较短的时间,需要更高的精确性。
  3. Period类
    Period类表示一段时间的年、月、日。
  4. LocalDate类
    LocalDate是一个不可变的日期时间对象,表示日期,通常被视为年月日。
  5. LocalTime类
    LocalTime是一个不可变的日期时间对象,代表一个时间,通常被看作是小时-秒,时间表示为纳秒精度。
  6. LocalDateTime类
    LocalDateTime类是一个不可变的日期时间对象,代表日期时间,通常被视为年-月-日=时-分-秒。
  7. ZonedDateTime类
    ZonedDateTime是具有时区的日期时间的不可变表示,此类存储所有日期和时间字段,精度为纳秒,时区为区域偏移量,用于处理模糊的本地日期时间。

需要注意的是java.time中的所有的类均没有公共构造方法,也就是说没办法通过new的方式直接创建,需要采用工厂方法加以实例化。
(关于什么是工厂方法,我们在以后的章节中会详细讨论,现在我们暂时理解需要通过成员方法创建实例。)

now方法

now方法可以根据当前日期或时间创建实例。

示例代码:

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

import java.time.*;

public class TimeNow {
public static void main(String[] args) {
// Instant
Instant instant = Instant.now();
System.out.println(instant);

// LocalDate
LocalDate localDate = LocalDate.now();
System.out.println(localDate);

// LocalTime
LocalTime localTime = LocalTime.now();
System.out.println(localTime);

// LocalDateTime
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);

// ZonedDateTime
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime);
}
}

运行结果:

1
2
3
4
5
2021-08-29T19:59:32.002Z
2021-08-30
03:59:32.074
2021-08-30T03:59:32.074
2021-08-30T03:59:32.075+08:00[Asia/Shanghai]

DurationPeriod没有now方法,这个我们根据这两个类的用途就知道。
除了上述的五个类外,还有几个类也有now方法。

  1. Year类,表示年
  2. YearMonth类,表示年月
  3. MonthDay类,表示月日

示例代码:

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

import java.time.*;

public class TimeNow {
public static void main(String[] args) {
// Year
Year year = Year.now();
System.out.println(year);

// yearMonth
YearMonth yearMonth = YearMonth.now();
System.out.println(yearMonth);

// monthDay
MonthDay monthDay = MonthDay.now();
System.out.println(monthDay);
}
}

运行结果:

1
2
3
2021
2021-08
--08-30

of方法

of方法可以根据给定的参数,指定任意时间节点,生成对应的日期/时间对象,基本上每个基本类都有of方法用于生成的对应的对象,而且重载形式多种,可以根据不同的参数生成对应的数据。

示例代码:

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

import java.time.*;

public class TimeOf {
public static void main(String[] args) {
LocalDate localDate = LocalDate.of(2021, Month.FEBRUARY,1);
System.out.println(localDate);
localDate = LocalDate.of(2021,2,1);
System.out.println(localDate);

LocalTime localTime = LocalTime.of(3,3);
System.out.println(localTime);
localTime = LocalTime.of(3,3,3);
System.out.println(localTime);
localTime = LocalTime.of(3,3,3,3);
System.out.println(localTime);

LocalDateTime localDateTime = LocalDateTime.of(2021,Month.FEBRUARY,1,3,3,3,3);
System.out.println(localDateTime);

localDateTime = LocalDateTime.of(localDate,localTime);
System.out.println(localDateTime);
}
}

运行结果:

1
2
3
4
5
6
7
2021-02-01
2021-02-01
03:03
03:03:03
03:03:03.000000003
2021-02-01T03:03:03.000000003
2021-02-01T03:03:03.000000003
  • 注意,对于月份,2月就写传入2;但是对于Calendar,却需要传入1。因为Calendar中的月份从0开始,但是对于年份和日期又没这个讲究。所以说Calendar使用不方便。
  • 推荐采用Month枚举的形式传入,防止记错。

日期比较

前后关系比较

三种常见比较场景

  1. 是否是同一天
  2. 是在之后
  3. 还是在之前

示例代码:

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

import java.time.LocalDate;
import java.time.Month;


public class equalsDemo {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
LocalDate date = LocalDate.of(2020, Month.FEBRUARY, 2);
System.out.println(today.equals(date));
System.out.println(today.isAfter(date));
System.out.println(today.isBefore(date));
}
}

运行结果:

1
2
3
false
true
false

检查周期性事件

示例代码:

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

import java.time.LocalDate;
import java.time.MonthDay;


public class birthdayDemo {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
LocalDate dateOfBirth = LocalDate.of(2010, 01, 14);
MonthDay birthday = MonthDay.of(dateOfBirth.getMonth(), dateOfBirth.getDayOfMonth());
MonthDay currentMonthDay = MonthDay.from(today);

if(currentMonthDay.equals(birthday)){
System.out.println("Many Many happy returns of the day !!");
}else{
System.out.println("Sorry, today is not your birthday");
}
}
}

运行结果:

1
Sorry, today is not your birthday

时区信息的获取

在上文的ZonedDateTime中,我们发现这个对象不仅有时间日期,还有偏移量+时区。那么时区如何在Java中获取呢?
通过提供的一个类ZonedId的getAvailableZoneIds方法可以获取到一个Set集合,集合中封装了600个时区。

示例代码:

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

import java.time.ZoneId;
import java.util.Set;

public class TimeZone {
public static void main(String[] args) {
// 所有支持的时区
Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
System.out.println(availableZoneIds);

// 当前默认时区
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId);
}
}

运行结果:

1
2
[Asia/Aden, America/Cuiaba, Etc/GMT+9, Etc/GMT+8, Africa/Nairobi, America/Marigot,  【部分运行结果略】
Asia/Shanghai

关于时区的讨论,推荐一篇文章。
https://www.jianshu.com/p/17d53272f9cb

plus和minus的使用

想要修改某个日期/时间对象的现有实例时,我们可以使用plus和minus来完成操作。
比如:

  • LocalDate plusDay(long days) 增加天数
  • LocalDate plusWeeks(long weeks) 增加周数
  • LocallDate plusMonths(long months) 增加月数
  • LocalDate plusYears(long years) 增加年数

minus方法与以上类似。

示例代码:

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

import java.time.LocalDate;

public class TimePlus {
public static void main(String[] args) {
LocalDate localDate = LocalDate.of(2020,2,2);
System.out.println(localDate);
System.out.println(localDate.plusDays(1));
System.out.println(localDate.plusWeeks(1));
System.out.println(localDate.plusMonths(1));
System.out.println(localDate.plusYears(1));
}
}

运行结果:

1
2
3
4
5
2020-02-02
2020-02-03
2020-02-09
2020-03-02
2021-02-02

除了LocalDate,LocalTime,LocalDateTime也都有相关的plus和minus方法。

java.time中的类都是不可变的,一旦创建LocalDate,LocalTime,LocalDateTime相关的对象,就无法再修改了。所以上述我们的操作,其内存结构和String拼接或重新赋值的内存结构比较相似。

TemporalAdjuster 调节器

TemporalAdjuster,日期调节器。
例如:

  • TemporalAdjusters.firstDayOfMonth()
  • TemporalAdjusters.firstDayOfNextMonth()
  • TemporalAdjusters.firstDayOfYear()
  • TemporalAdjusters.lastDayOfYear()
  • TemporalAdjusters.lastDayOfMonth()

示例代码:

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

import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;

public class TimeTemporalAdjuster {
public static void main(String[] args) {
LocalDate now = LocalDate.of(2020,2,2);
System.out.println(now);

LocalDate firstDayOfMonth = now.with(TemporalAdjusters.firstDayOfMonth());
System.out.println(firstDayOfMonth);

LocalDate firstDayOfNextMonth = now.with(TemporalAdjusters.firstDayOfNextMonth());
System.out.println(firstDayOfNextMonth);

LocalDate firstDayOfYear = now.with(TemporalAdjusters.firstDayOfYear());
System.out.println(firstDayOfYear);

LocalDate lastDayOfYear = now.with(TemporalAdjusters.lastDayOfYear());
System.out.println(lastDayOfYear);

LocalDate lastDayOfMonth = now.with(TemporalAdjusters.lastDayOfMonth());
System.out.println(lastDayOfMonth);
}
}

运行结果:

1
2
3
4
5
6
2020-02-02
2020-02-01
2020-03-01
2020-01-01
2020-12-31
2020-02-29

此外,我们还可以指定上一周的周几,或者下一周的周几。
示例代码:

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

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;

public class TimeTemporalAdjuster {
public static void main(String[] args) {
LocalDate now = LocalDate.of(2020,2,2);
System.out.println(now);

LocalDate nextDayOfWeekFriday = now.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
System.out.println(nextDayOfWeekFriday);

LocalDate previousDayOfWeekSunday = now.with(TemporalAdjusters.previous(DayOfWeek.SUNDAY));
System.out.println(previousDayOfWeekSunday);
}
}

运行结果:

1
2
3
2020-02-02
2020-02-07
2020-01-26

自定义TemporalAdjuster调节器

如果要自定义日期时间的更改逻辑,可以通过实现TemporalAdjuster类接口的方式来完成。

例如,每个月15号发工资,如果15号是周六周日,就提前发。
示例代码:

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

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAdjusters;

public class PayDayAdjuster implements TemporalAdjuster {
@Override
public Temporal adjustInto(Temporal temporal) {
//1.将temporal转换为子类对象LocalDate,from方法可以将任何时态对象转换为LocalDate
LocalDate payDay = LocalDate.from(temporal);
//2.判断当前封装的时间中的日期是不是当月15日,如果不是,则更改为15日.
int day;
if (payDay.getDayOfMonth() != 15) {
day = 15;
} else {
day = payDay.getDayOfMonth();
}
LocalDate realPayDay = payDay.withDayOfMonth(day);
//3.判断realPayDay对象中封装的星期数是不是周六或者是周日,如果是周末或者是周日则更改为周五.
if (realPayDay.getDayOfWeek() == DayOfWeek.SUNDAY || realPayDay.getDayOfWeek() == DayOfWeek.SATURDAY) {
//说明发薪日是周末,则更改为周五.
realPayDay = realPayDay.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
}
return realPayDay;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import java.time.LocalDate;

public class TimeTemporalAdjuster {
public static void main(String[] args) {
LocalDate payDay = LocalDate.of(2020,2,2);
System.out.println(payDay);

payDay = payDay.from(new PayDayAdjuster().adjustInto(payDay));
System.out.println(payDay);
}
}

运行结果:

1
2
2020-02-02
2020-02-14

Date和Calendar转为java.time

利用Date的toInstant(),先转为Instant,之后的转化都基于Instant。对于Calendar,一样的思路。

从Date转的例子。
示例代码:

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

import java.time.*;
import java.util.Date;

public class TimeConvert {
public static void main(String[] args) {
Date d = new Date();

// Date 转 Instant
Instant instant = d.toInstant();
System.out.println(instant);

// 转 LocalDateTime
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
System.out.println(localDateTime);

// 转 LocalDate
LocalDate localDate = localDateTime.toLocalDate();
System.out.println(localDate);

// 转 LocalTime
LocalTime localTime = localDateTime.toLocalTime();
System.out.println(localTime);
}
}

运行结果:

1
2
3
4
2021-08-29T21:26:21.858Z
2021-08-30T05:26:21.858
2021-08-30
05:26:21.858

从Calendar转的例子。
示例代码:

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

import java.time.*;
import java.util.Calendar;

public class TimeConvert {
public static void main(String[] args) {
Calendar c = Calendar.getInstance();

// Date 转 Instant
Instant instant = c.toInstant();
System.out.println(instant);

// 转 LocalDateTime
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
System.out.println(localDateTime);

// 转 LocalDate
LocalDate localDate = localDateTime.toLocalDate();
System.out.println(localDate);

// 转 LocalTime
LocalTime localTime = localDateTime.toLocalTime();
System.out.println(localTime);
}
}

运行结果:

1
2
3
4
2021-08-29T21:30:18.992Z
2021-08-30T05:30:18.992
2021-08-30
05:30:18.992

format和parse方法

对于format,利用DateTimeFormatter就可以了,有一些预设的格式,如果对于预设的格式都不满意,还可以利用ofPattern自定义格式。

示例代码:

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

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

public class TimeFormat {
public static void main(String[] args) {
// 转 LocalDateTime
LocalDateTime localDateTime = LocalDateTime.now();
String s = localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
System.out.println(s);
s = localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE);
System.out.println(s);
s = localDateTime.format(DateTimeFormatter.ISO_LOCAL_TIME);
System.out.println(s);
s = localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(s);
}
}

运行结果:

1
2
3
4
2021-08-30T05:36:20.346
2021-08-30
05:36:20.346
2021-08-30 05:36:20

对于parse,与format非常类似,我们不赘述。
示例代码:

1
2
3
4
5
6
7
8
9
10
11
package com.kakawanyifan;

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

public class TimeParse {
public static void main(String[] args) {
LocalDateTime localDateTime = LocalDateTime.parse("2020-02-02 20:20:02",DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(localDateTime);
}
}

运行结果:

1
2020-02-02T20:20:02

异常类

异常体系

异常就是程序出现了不正常的情况。
异常的体系结构:
异常的体系结构

JVM默认处理异常的方式

如果程序出现了问题,我们没有做任何处理,最终JVM会做默认的处理,处理方式有如下两个步骤:

  1. 把异常的名称,错误原因及异常出现的位置等信息输出在了控制台
  2. 程序停止执行

try-catch方式处理异常

格式:

1
2
3
4
5
try {
可能出现异常的代码;
} catch(异常类名 变量名) {
异常的处理代码;
}

执行流程:

  1. 程序从 try 里面的代码开始执行
  2. 出现异常,就会跳转到对应的 catch 里面去执行
  3. 执行完毕之后,程序还可以继续往下执行

示例代码:

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

public class ExceptionDemo {
public static void main(String[] args) {
System.out.println("开始");
method();
System.out.println("结束");
}

public static void method() {
try {
int[] arr = {1, 2, 3};
System.out.println(arr[3]);
System.out.println("这里能够访问到吗");
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
}
}
}

运行结果:

1
2
3
4
5
开始
结束
java.lang.ArrayIndexOutOfBoundsException: 3
at com.kakawanyifan.ExceptionDemo.method(ExceptionDemo.java:13)
at com.kakawanyifan.ExceptionDemo.main(ExceptionDemo.java:6)
  • 《5.IO流》中,我们还会讨论try-catch-finally这种结构。

Throwable成员方法

方法名 说明
public String getMessage() 返回此 throwable 的详细消息字符串
public String toString() 返回此可抛出的简短描述
public void printStackTrace() 把异常的错误信息输出在控制台

示例代码:

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

public class ExceptionDemo {
public static void main(String[] args) {
System.out.println("开始");
method();
System.out.println("结束");
}

public static void method() {
try {
int[] arr = {1, 2, 3};
System.out.println(arr[3]);
System.out.println("这里能够访问到吗");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println(e.toString());
System.out.println("------");
System.out.println(e.getMessage());
System.out.println("------");
e.printStackTrace();
}
}
}

运行结果:

1
2
3
4
5
6
7
8
9
开始
java.lang.ArrayIndexOutOfBoundsException: 3
------
3
------
结束
java.lang.ArrayIndexOutOfBoundsException: 3
at com.kakawanyifan.ExceptionDemo.method(ExceptionDemo.java:13)
at com.kakawanyifan.ExceptionDemo.main(ExceptionDemo.java:6)

编译时异常和运行时异常的区别

编译时异常:都是Exception类及其子类,必须显示处理,否则程序就会发生错误,无法通过编译。
运行时异常:都是RuntimeException类及其子类,无需显示处理,也可以和编译时异常一样处理。

throws方式处理异常

格式:

1
2
3
public void 方法() throws 异常类名 {

}

示例代码:

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

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ExceptionDemo {
public static void main(String[] args) {
System.out.println("开始");
method();
try {
method2();
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println("结束");
}

//编译时异常
public static void method2() throws ParseException {
String s = "2048-08-09";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date d = sdf.parse(s);
System.out.println(d);
}

//运行时异常
public static void method() throws ArrayIndexOutOfBoundsException {
int[] arr = {1, 2, 3};
System.out.println(arr[3]);
}
}

运行结果:

1
2
3
4
5
开始
开始
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
at com.kakawanyifan.ExceptionDemo.method(ExceptionDemo.java:30)
at com.kakawanyifan.ExceptionDemo.main(ExceptionDemo.java:10)

throws和throw的区别

throws:

  • 用在方法声明后面,跟的是异常类名。
  • 表示抛出异常,由该方法的调用者来处理。
  • 表示出现异常的一种可能性,并不一定会发生这些异常。

throw:

  • 用在方法体内,跟的是异常对象名。
  • 表示抛出异常,由方法体内的语句处理。
  • 执行throw一定抛出了某种异常

自定义异常

自定义异常类

示例代码:

1
2
3
4
5
6
7
8
9
package com.kakawanyifan;

public class ScoreException extends Exception{
public ScoreException() {}

public ScoreException(String message) {
super(message);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
package com.kakawanyifan;

public class Teacher {
public void checkScore(int score) throws ScoreException {
if(score<0 || score>100) {
// throw new ScoreException();
throw new ScoreException("你给的分数有误,分数应该在0-100之间");
} else {
System.out.println("成绩正常");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.kakawanyifan;

import java.util.Scanner;

public class ExceptionDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入分数:");

int score = sc.nextInt();

Teacher t = new Teacher();
try {
t.checkScore(score);
} catch (ScoreException e) {
e.printStackTrace();
}
}
}

运行结果:

1
2
3
4
5
请输入分数:
-1
com.kakawanyifan.ScoreException: 你给的分数有误,分数应该在0-100之间
at com.kakawanyifan.Teacher.checkScore(Teacher.java:7)
at com.kakawanyifan.ExceptionDemo.main(ExceptionDemo.java:14)
文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/10803
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

评论区