- 程序在执行的过程中出现非正常的情况,最终导致 JVM 非正常停止。异常在 Java 中以类的形式存在,每一个异常类都可以创建对象,在产生异常的时候就是创建了一个异常类的对象,然后将异常对象抛出。
public class Main {
public static void main(String[] args) {
NullPointerException exception = new NullPointerException("空指针异常");
System.out.println(exception);//java.lang.NullPointerException: 空指针异常
}
}
异常继承结构图
编译时异常和运行时异常都发生在运行阶段
异常的分类
- 大类参考上面异常继承结构图
RuntimeException
- 错误的强制类型转换
- 空指针异常
- 数组越界异常
- 这种异常是可以通过程序检查来避免的
- 除了 RuntimeException,其余的所有异常都是检查型异常,在编译时期就要对这种异常进行处理,否则编译器就会报错。对这种异常处理有两种方式,一种是使用 throws 异常将异常抛出,抛给调用者,然后调用者也可以选择抛出或者直接处理这个异常,这种方式如果在某一个地方发生了异常,那么程序将终止,不会在继续向下执行。另外一种方式就是使用 try...catch...finally 来处理这个异常,finally 关闭资源以后,程序可以继续向下执行。
- 在异常发生的时候,如果一直上抛给调用者,那么最终给抛给 main 方法,main 方法继续上抛异常的话,那么这个异常就会抛给 JVM,然后 JVM 就会终止程序的运行。
- 异常机制可以提高程序的健壮性
编译时异常
public class Main {
public static void main(String[] args) throws ParseException {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//date = sdf.parse("2020-1-1 00:00:00");//Wed Jan 01 00:00:00 CST 2020
date = sdf.parse("2020-1-1");//java.text.ParseException: Unparseable date: "2020-1-1"
//这里格式不对,所以程序中断抛出异常,程序不再往下进行
//如果不想让程序中断,那么就可以对这个异常进行捕获处
System.out.println(date);
}
}
//捕获异常
public class Main {
public static void main(String[] args) {
Date date = new Date();
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
date = sdf.parse("2020-1-1");//java.text.ParseException: Unparseable date: "2020-1-1"
} catch (ParseException e) {
e.printStackTrace();//java.text.ParseException: Unparseable date: "2020-1-1"
}
//这里对异常进行了捕获,所以程序在发生异常的时候并没有中断程序
System.out.println(date);//Fri Jul 31 15:28:21 CST 2020
}
}
运行时异常
public class Main {
public static void main(String[] args) {
String str = "abc";
System.out.println(str.charAt(3));
//StringIndexOutOfBoundsException: String index out of range: 3
//运行时异常要进行捕获处理
System.out.println(str);
}
}
//捕获异常
public class Main {
public static void main(String[] args) {
try {
System.out.println(3 / 0);
} catch (Exception e) {
//java.lang.ArithmeticException: / by zero
//e.printStackTrace();
System.out.println(e.getMessage());/// by zero
}
System.out.println(3);
}
}
异常的产生
public class Main {
public static void main(String[] args) throws ParseException {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//date = sdf.parse("2020-1-1 00:00:00");//Wed Jan 01 00:00:00 CST 2020
date = sdf.parse("2020-1-1");//java.text.ParseException: Unparseable date: "2020-1-1"
//这里格式不对,所以程序中断抛出异常,程序不再往下进行
//如果不想让程序中断,那么就可以对这个异常进行捕获处
System.out.println(date);
}
}
//parse源码
public Date parse(String source) throws ParseException {
ParsePosition pos = new ParsePosition(0);
Date result = parse(source, pos);
if (pos.index == 0)
throw new ParseException("Unparseable date: "" + source + """ ,
pos.errorIndex);
return result;
}
- 通过上面的源码我们可以看出,如果日期的格式不正确那么 JVM 就会检测出来这个异常,此时 JVM 会创建一个异常类对象,这个异常类对象包含了异常产生的原因,位置和内容,使用 throw 手动抛出异常,然后使用红色的字体打印在控制台,然后 JVM 终止当前的程序。
手动抛出异常
public class Main {
public static void main(String[] args) throws ParseException {
//Exception in thread "main" java.lang.RuntimeException: 除数不能为 0
//System.out.println(div(1, 0)); //程序中断,往下执行
try {
System.out.println(div(1, 0));
} catch (Exception e) {
System.out.println(e.getMessage());//除数不能为 0
}
System.out.println(div(1, 1));//1
}
public static int div(int a, int b) {
if(b == 0) throw new RuntimeException("除数不能为 0");
return a / b;
}
}
自定义异常类
public class NullPointerException extends RuntimeException {
private static final long serialVersionUID = 5162710183389028792L;
/**
* Constructs a {@code NullPointerException} with no detail message.
*/
public NullPointerException() {
super();
}
/**
* Constructs a {@code NullPointerException} with the specified
* detail message.
*
* @param s the detail message.
*/
public NullPointerException(String s) {
super(s);
}
}
- Java 中提供的异常类不能够处理所有的异常,通过查看源码,我们可以发现所有的异常类中都有两个方法,这个一无参构造方法,一个是有参构造方法。自定义异常类必须继承 (Exception) 或者 (RuntimeException),如果是发生概率小的异常可以继承 (RuntimeException),表示运行时异常;否则继承 (Exception),编译时异常,在编译阶段就要对异常进行处理。
public class MyException extends Exception {
public MyException() {
}
public MyException(String message) {
super(message);
}
}
public class Main {
public static void main(String[] args) throws MyException {
try {
System.out.println(div(1, 0));
} catch (Exception e) {
System.out.println(e.getMessage());//除数不能为 0
}
System.out.println(div(1, 0));//自己不处理,抛给虚拟机
//Exception in thread "main" cn.edu.zut.MyException: 除数不能为 0
}
public static int div(int a, int b) throws MyException {
//自定义的异常在使用的时候是要抛给调用者去处理的
//所以这里不进行处理
//MyException exception = new MyException("除数不能为 0");
//if(b == 0) exception;
if(b == 0) throw new MyException("除数不能为 0");
return a / b;
}
}
在继承机制中,子类的方法抛出的异常不能比父类方法抛出的异常还要多
final finally finalize
- final:关键字,最终的,不变的
- finally:关键字,在异常机制中,必然会执行,在发生异常的时候程序会终止,那么如果使用流的时候可能会打开一些资源,那么这些资源将不会被处理,如果使用 finally,不管程序是否发生异常,程序都会进入 finally 执行程序,此时在 finally 中就可以关闭一些资源,避免空间的浪费。(如果在进入 finally 程序代码块之前退出了 JVM 虚拟机,那么 finally 中的代码将不再执行。)
- finalize:Object 类中的一个方法,GC 负责调用这个方法进行垃圾回收。
public class Main {
public static void main(String[] args) {
System.out.println(print());//100
}
public static int print() {
int x = 100;
try {
return x;
} finally {
x ++;
}
}
}
- 我们知道 finally 中的程序一定会被执行,但是 Java 中又规定 Java 方法体中的代码必须自上而下的执行,return 一旦执行,整个方法必须结束。在 IDEA 中我们可以直接查看 .class 文件,然后查看其执行过程。
public class Main {
public Main() {
}
public static void main(String[] args) {
System.out.println(print());
}
public static int print() {
byte x = 100;
byte var1;
try {
var1 = x;
} finally {
int var5 = x + 1;
}
return var1;
}
}