在Java中有3种类型的异常:检查性异常(checked exception)、运行时异常(runtime exception)以及错误(error)。Java中的所有错误和异常都继承自Throwable类。
运行时异常
运行时异常就是RuntimeException的子类,不需要在方法或构造器上做出处理。常见的运行时异常有NullPointerException(空指针异常)、ArrayIndexOutOfBoundsException(数组下标越界异常)、ArithmeticException(算术异常)等等。可以看到,很多运行时异常都是只要我们稍微小心一点就可以避免的。
public class Demo{
public static void main(String[] args) {
int[] array = {0,1,2};
System.out.println(array[3]);//数组下标越界异常
}
}
上述代码是可以正常编译的,不需要去进行try-catch或者throws处理(可以使用),只是在执行的时候会报异常而已。要加也是可以的。
public class Demo{
public static void main(String[] args) {
int[] array = {0,1,2};
try {
System.out.println(array[3]);//数组下标越界异常
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
}
}
}
检查性异常
非运行时异常的异常就是检查性异常了,它需要在方法或者构造器上进行处理。常见的检查性异常有:IOException(IO异常)、FileNotFoundException(文件找不到异常)、SQLException(操纵数据库异常)等。这些异常大多都是我们不可预见的,所以需要提前进行处理。
public class Demo{
public static void main(String[] args) {
File file = new File("test.java");
try {
FileInputStream fileInputStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
}
}
}
需要在编译器就进行处理,否则会编译出错。
其实运行时异常和检查性异常的区别就是在编译期的时候进不进行检查的问题。运行时异常大多是我们在编写代码时就可以避免的,所以就无须多进行操作,小心一点就可以;而检查性异常是我们无法预料的,所以要在编译期就检查,出问题可以及时处理。
错误
错误已经脱离了我们的控制范畴,比如内存溢出。
异常捕获
异常的捕获使用try-catch两个关键字结合。
public class Demo{
public static void main(String[] args) {
int a = 10;
int b = 0;
try {
int c = a / b;//除0异常
System.out.println("c = " + c);//不会执行
System.out.println(a);//不会执行
} catch (ArithmeticException e) {
e.printStackTrace();
}
}
}
try里的语句捕获到异常之后,该语句往下的语句都不会得到执行(执行catch里面的语句),如果没有捕获到异常,则catch里的语句不会执行。很容易理解,前面已经错了,就不要将错就错了,浪费时间。
一个try可以搭配多个catch:
public class Demo{
public static void main(String[] args) {
int a = 10;
int b = 0;
try {
int c = a / b;//除0异常
System.out.println("c = " + c);
System.out.println(a);
} catch (ArithmeticException e) {
e.printStackTrace();
}catch (Exception e){
e.printStackTrace();
}
}
}
需要注意的是,下面的catch捕获范围不能比上一个小,或者说不能是上一个的子类,就是说你不能第一个catch捕获是Exception,下一个catch捕获是RuntimeException。其实也容易理解,如果你上面捕获的范围比下面大,那还写下一个catch有什么意义呢?当发生异常,异常与第一个catch匹配成功后就不会与下面的catch进行匹配了。原因也很简单,你都找到错的原因了,就不用浪费时间再去匹配别的了。
try-catch通常搭配finally一起使用。
public class Demo{
public static void main(String[] args) {
int a = 10;
int b = 0;
try {
int c = a / b;//除0异常
System.out.println("c = " + c);
System.out.println(a);
} catch (ArithmeticException e) {
e.printStackTrace();
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println("a = " + a);//10
}
}
}
无论try里的语句是否存在异常,finally里的语句都一定能够得到执行。从finally的语义也可以知道,最后肯定是要得到执行的。
当出现return的时候,finally的语句会不会执行呢?
public class Demo{
public static void main(String[] args) {
int a = 10;
int b = 0;
try {
int c = a / b;//除0异常
System.out.println("c = " + c);
System.out.println(a);
} catch (ArithmeticException e) {
return;
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println("a = " + a);//10
}
System.out.println("b = " + b);//不会得到执行
}
}
当出现return的时候finally也是会得到执行的。上述代码的流程是程序执行到了异常出(int c = a / b)开始匹配catch,第一个catch满足条件,进入catch里面,发现是return,先等着,看有没有finally,发现finally,执行finally里的语句,return。简而言之,finally里的语句是一定能够执行的,不能执行干嘛要写啊。
异常抛出
异常的抛出采用throws关键字
public class Demo{
public static void main(String[] args) throws FileNotFoundException {
File file = new File("test.java");
FileInputStream fileInputStream = new FileInputStream(file);
}
}
报什么异常就throws什么异常。手动抛出异常
public class Demo{
public static void main(String[] args) throws Exception {
throw new Exception("这是一个测试");
}
}
自定义异常
可以通过继承Exception类来自定义异常
public class MyException extends Exception{
}