1.异常概述

什么是异常

在程序执行过程中发生了不正常的情况。这种不正常的情况就叫异常。
java把异常信息打印到控制台上,供程序员参考,程序员在看到异常之后,可以对程序进行修改,让程序变得更健壮。也使得java更完善。
异常在java中是以类的形式存在的,每一个异常类都可以实例化对象。
在代码中,在遇到异常时,JVM会创建一个异常对象,异常对象的参数就是对不正常的程序的说明,JVM将异常抛出,并对异常的说明打印到控制台上。

看demo

  1. package exception;
  2. public class Exception {
  3. public static void main(String[] args) {
  4. int a = 10;
  5. int b = 0;
  6. int c = a/b; // 程序运行到这个时候就会存在异常,此时程序中断
  7. // 输出:Exception in thread "main" java.lang.ArithmeticException: / by zero
  8. // at exception.Exception.main(Exception.java:7)
  9. // System.out.println(100/0);
  10. // 在出现相同类型的异常时,JVM还会创建一个对象,但和刚才的对象是两个对象。
  11. }
  12. }

2.异常类的继承结构

首先Object下有Throwable类,意为可抛出的异常。
Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)
Exception下有两个分支,
1.Exception的直接子类,编译时异常,意为在编译阶段必须对异常进行处理,如果不处理,编译器报错。
2.RuntimeException:运行时异常,在编写程序时可以处理异常也可以不处理。

异常无论是编译时异常还是运行时异常,都只在运行时发生,在编译阶段不会发生只会报错,因为程序只有在运行阶段次会new对象。

编译时异常,比如得知下雨出门就是一个编译异常,出门带一把伞就是对异常的预先处理。
运行时异常,比如出门被花盆砸伤,这种异常发生的概况很低,同时我们没有必要对这样的异常做预处理。

3.异常的两种处理方式

1.在方法的后面用throws关键字,抛出异常,抛给上一级。谁调用该方法,将异常抛给谁,由调用者继续处理异常。
2.使用try-catch语句进行异常的捕捉。

java中异常发生之后如果一直上抛,抛给了main方法,main方法抛给了JVM,那么只有一个结果,就是终止程序的执行。

看下面一段demo

  1. package exception;
  2. public class Exception {
  3. // 我自定义一个方法,这个方法可能会抛出一个ClassNotFoundException异常(编译时异常)
  4. public static void doSome() throws ClassNotFoundException{
  5. }
  6. public static void main(String[] args) {
  7. // 我调用doSome方法,编译器就会报错,原因是我没有处理编译时异常
  8. doSome();
  9. }
  10. }

第一种处理异常的方式

  1. public static void main(String[] args) throws ClassNotFoundException //再上抛,不仅可以抛出当前的异常类,也可以抛出他的父异常类他,throws后面可以有多个异常,中间用逗号隔开
  2. // JVM调用main,最后main函数就会让JVM处理

第二种方式:真正的解决异常

  1. try {
  2. doSome();
  3. // sout //此时这里的代码就不会执行了,至今进入到catch语句块
  4. } catch (ClassNotFoundException e) {
  5. e.printStackTrace();
  6. }
  7. // 使用try catch语句解决异常
  8. // sout // 此时这里的代码继续执行,前提是异常被解决

关于catch:

1.catch中的参数是一个对象的引用,这个对象是JVM中new出来的异常对象
2.//catch中对象的类型也可以是其父类型(多态)
3.catch可以写多个。
4.catch可以写多个的时候,要从上到下,从小到大,从子到父。

java8的新特性:在catch的参数列表里,可以采用 |(或),写多个异常参数。

4.异常对象的两个方法

1.getMessage()方法,打印出错信息,返回值是String类型的,若想看信息,还需要自己写输出语句。

  1. // 自定义一个异常对象,参数就是异常信息(由于我没有抛出这个异常,所以在运行的时候不会终端)
  2. NullPointerException e = new NullPointerException("空指针异常");
  3. String s = e.getMessage();
  4. System.out.println(s);
  5. // 输出“空指针异常”

2.printStackTrace()方法,打印异常追踪的堆栈信息。(此时会创建一个新的线程)

  1. package exception;
  2. import java.io.FileInputStream;
  3. import java.io.FileNotFoundException;
  4. public class Exception {
  5. public static void main(String[] args) {
  6. try {
  7. m1();
  8. } catch (FileNotFoundException e) {
  9. e.printStackTrace(); // 调用该方法,在控制台会打印异常追踪的堆栈信息(来源)
  10. }
  11. }
  12. private static void m1() throws FileNotFoundException {
  13. m2();
  14. }
  15. private static void m2() throws FileNotFoundException {
  16. FileInputStream fis = new FileInputStream("sadf");
  17. }
  18. }

5.finally语句

finally是try catch的子句,在任何情况下都会执行,而且不能单独存在。
一般用于释放资源,或者做出最终的说明。

  1. package exception;
  2. import java.io.FileInputStream;
  3. import java.io.FileNotFoundException;
  4. import java.io.IOException;
  5. public class Exception {
  6. public static void main(String[] args) {
  7. FileInputStream fis = null;
  8. try {
  9. fis = new FileInputStream("sadfsd");
  10. String s = null;
  11. s.toString(); // 在这里程序抛出异常,try语句块下面的语句都不执行,也就不能关闭流
  12. fis.close();
  13. } catch (FileNotFoundException e) {
  14. e.printStackTrace();
  15. } catch (NullPointerException e){
  16. e.printStackTrace();
  17. } catch (IOException e){
  18. e.printStackTrace();
  19. } finally {
  20. // 所以我们在这里去关闭流,十分安全
  21. if(fis != null){
  22. try {
  23. fis.close(); // 同时这个地方也要做异常处理
  24. } catch (IOException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. }
  29. }
  30. }

try可以直接和finally使用

  1. try {
  2. return;
  3. }finally {
  4. // 就算return了,finally里面的语句还是会执行
  5. System.out.println("finally");
  6. }
  7. // System.out.println("hello word"); 但是这里的代码不会执行了

但是有一种方法可以终止finally

  1. try{
  2. System.exit(0); // 这种方法可以直接退出JVM
  3. }finally{
  4. System.out.println("hello world");
  5. }

6.java中如何自定义异常

自定义异常实际上是自定义类,因为java中异常都已类的形式存在。

1.首先继承Exception类(或其他异常类)。
2.写改类的两个构造方法,有参(类型是字符串类型的)和无参。
3.有参构造方法中调用父级的构造函数,参数为构造方法的参数。

  1. package exception;
  2. // 首先要自己写一个异常类
  3. class MyException extends Exception{ // 1.继承
  4. public MyException(){} // 2.两个重写
  5. public MyException(String s){
  6. super(s);
  7. }
  8. }
  9. public class Exception01 {
  10. public static void main(String[] args) {
  11. MyException e = new MyException("自定义异常");
  12. System.out.println(e.getMessage());
  13. }
  14. }

7.实际开发中异常的使用

实际开发中,异常的作用就是判断程序出现不正确的方法,然后终止程序并且给开发者提示错误信息。以前我们会先输出程序哪里错了,然后return,但这是一种不完整的处理错误的机制,有了异常,我们就可以让以前的处理错误的机制更高级,完整。

比如下面的demo,我们以前是这样处理错误的:

  1. // 假设我们在这里输入x的值为3
  2. int x = 3;
  3. if (x>2||x<1){
  4. // 我们采用这种方式虽然可读性强,但是不能处理更复杂的错误程序。
  5. System.out.println("输入的值有错");
  6. return;
  7. }
  8. switch (x){
  9. case 1: ; break;
  10. case 2: ; break;
  11. }

我们使用异常之后:

  1. package exception;
  2. class InputException extends Exception{
  3. public InputException(){}
  4. public InputException(String s){
  5. super(s);
  6. }
  7. }
  8. public class Exception01 {
  9. public static void main(String[] args) {
  10. try {
  11. m1();
  12. } catch (InputException e) {
  13. System.out.println(e.getMessage());
  14. }
  15. }
  16. private static void m1() throws InputException {
  17. // 假设我们在这里输入x的值为3
  18. int x = 3;
  19. if (x>2||x<1){
  20. // 此时我们只需要这一句语句
  21. throw new InputException("输入的值有误");
  22. }
  23. switch (x){
  24. case 1: ; break;
  25. case 2: ; break;
  26. }
  27. }
  28. }

8.一个关于继承的小问题

重写之后的方法不能比之前的方法抛出更多(更宽泛)的异常,但可以更少

  1. package exception;
  2. class Exception01 {
  3. public void m1() throws RuntimeException{
  4. }
  5. }
  6. class A extends Exception01{
  7. public void m1(){} // 重写的方法可以不抛出异常
  8. }
  9. class B extends Exception01{
  10. public void m1() throws RuntimeException{} // 最好是抛出和父类一样的异常
  11. }
  12. class C extends Exception01{
  13. public void m1() throws Exception{} // 但不能抛出比父类更宽泛的异常,或者更多,这里会报错
  14. }

9.手动抛出异常

throw new Exception()