Java异常(超详细!)

2年前 (2022) 程序员胖胖胖虎阿
190 0 0

1、什么是异常,java提供异常处理机制有什么用?

  • 什么是异常:程序执行过程中的不正常情况。
  • 异常的作用:增强程序的 健壮性

eg.

public class ExceptionTest01 {
    public static void main(String[] args) {
        int a = 10;
        int b = 0;
        // 实际上JVM在执行到此处的时候,会new异常对象:new ArithmeticException("/ by zero");
        // 并且JVM将new的异常对象抛出,打印输出信息到控制台了。
        int c = a / b;
        System.out.println(a + "/" + b + "=" + c);

        // 此处运行也会创建一个:ArithmeticException类型的异常对象。
        System.out.println(100 / 0);
    }
}

2、java语言中异常是以什么形式存在的呢?

异常在java中以 的形式存在,每一个 异常类 都可以创建 异常对象

eg.

public class ExceptionTest02 {
    public static void main(String[] args) {
        // 通过“异常类”实例化“异常对象”
        NumberFormatException nfe = new NumberFormatException("数字格式化异常!");
        
        // java.lang.NumberFormatException: 数字格式化异常!
        System.out.println(nfe);
    }
}

3、异常继承结构图

Java异常(超详细!)

  • Exception的直接子类:编译时异常(要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错,因此得名编译时异常。)。
  • RuntimeException:运行时异常。(在编写程序阶段程序员可以预先处理,也可以不管,都行。)

4、异常的分类

异常分为 编译时异常运行时异常

所有异常都是在 运行阶段 发生的。因为只有程序运行阶段才可以 new对象。

因为异常的发生就是 new异常对象

4.1编译时异常因为什么而得名?

因为编译时异常必须在编译(编写)阶段预先处理,如果不处理编译器报错,因此得名。

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

  • 编译时异常一般发生的概率 比较高
  • 运行时异常一般发生的概率 比较低
  • 编译时异常发生概率较高,需要在运行之前对其进行 预处理
  • 运行时异常发生概率较低,没必要提前进行预处理。

4.3编译时异常和运行时异常别称

  • 编译时异常
  1. 受检异常:CheckedException
  2. 受控异常
  • 运行时异常
  1. 未受检异常:UnCheckedException
  2. 非受控异常

1、补充:

public class ExceptionTest03 {
    public static void main(String[] args) {
        System.out.println(100 / 0);

        // 这里的HelloWorld没有输出,没有执行。
        System.out.println("Hello World!");
    }
}

程序执行到System.out.println(100 / 0);
此处发生了 ArithmeticException 异常,底层 new 了一个ArithmeticException异常对象,然后抛出了。
由于是 main方法 调用了100 / 0,所以这个异常ArithmeticException抛给了main方法
main方法没有处理,将这个异常自动抛给了 JVMJVM最终终止程序的执行

此时System.out.println("Hello World!");并不会执行。

注意:
ArithmeticException 继承 RuntimeException,属于 运行时异常。在编写程序阶段不需要对这种异常进行预先的处理。


eg.

public class ExceptionTest04 {
    public static void main(String[] args) {
        // main方法中调用doSome()方法
        // 因为doSome()方法声明位置上有:throws ClassNotFoundException
        // 我们在调用doSome()方法的时候必须对这种异常进行预先的处理。
        // 如果不处理,编译器就报错。
        //编译器报错信息: Unhandled exception: java.lang.ClassNotFoundException
        doSome();
    }

    /**
     * doSome方法在方法声明的位置上使用了:throws ClassNotFoundException
     * 这个代码表示doSome()方法在执行过程中,有可能会出现ClassNotFoundException异常。
     * 叫做类没找到异常。这个异常直接父类是:Exception,所以ClassNotFoundException属于编译时异常。
     * @throws ClassNotFoundException
     */
    public static void doSome() throws ClassNotFoundException{
        System.out.println("doSome!!!!");
    }
}

解决方法一、throws上报给方法调用者(推卸责任:调用者知道)

public class ExceptionTest04 {
    public static void main(String[] args) throws ClassNotFoundException {
        doSome();
    }
    public static void doSome() throws ClassNotFoundException{
        System.out.println("doSome!!!!");
    }
}

解决方法二、try…catch捕捉,处理(调用者是不知道)

public class ExceptionTest04 {
    public static void main(String[] args) {
        try {
            doSome();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    
    public static void doSome() throws ClassNotFoundException{
        System.out.println("doSome!!!!");
    }
}

5、异常的处理方式

5.1 throws

在方法声明的位置上使用 throws 关键字抛出,谁调用我这个方法,我就抛给谁。抛给 调用者 来处理。

这种处理异常的态度:上报

5.2 try…catch

这个异常不会上报,自己把这个事儿处理了。
异常抛到此处为止,不再上抛了。

注意:

  • 只要异常没有捕捉,采用上报的方式,此方法的 后续代码不会执行
  • try语句块中的某一行出现异常,该行 后面的代码不会执行
  • try…catch捕捉异常之后,后续代码可以执行。

eg.

private static void m1() throws FileNotFoundException {
    System.out.println("m1 begin");
    m2();
    // 以上代码出异常,这里是无法执行的。
    System.out.println("m1 over");
}
try {
    m1();
    // m1方法出异常,下面代码不执行。
    System.out.println("hello world!");//不执行
} catch (FileNotFoundException e){ 
	//异常处理
    System.out.println("出异常了!!");
    System.out.println(e); 
}
System.out.println("hello world"); //会执行

注意:

  • 异常发生之后,如果我选择了上抛,抛给了我的调用者,调用者需要对这个异常继续处理,那么调用者处理这个异常同样有两种处理方式。
  • 一般不建议在main方法上使用throws,因为这个异常如果真正的发生了,一定会抛给JVM。JVM只有终止。
  • 一般main方法中的异常建议使用try…catch进行捕捉。

注意:

try {
    
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

这个分支中可以使用e引用,e引用 保存的内存地址是那个new出来 异常对象的内存地址


6、在以后开发中,处理编译时异常,应该上报还是捕捉呢?

  • 如果希望调用者来处理,选择throws上报。
  • 其它情况使用捕捉的方式。

7、深入try…catch

  1. catch后面的小括号中的类型可以是 具体的异常类型,也可以是该异常类型的 父类型
  2. catch可以写多个。建议catch的时候,精确的一个一个处理。这样有利于程序的调试。
  3. catch写多个的时候,从上到下,必须遵守 从小到大

eg.

try {
	FileInputStream fis = new FileInputStream("D:\\Download\\Javabean-addperson案例解析.docx");
} catch(FileNotFoundException e) {
	System.out.println("文件不存在!");
}

等同于

try {
	FileInputStream fis = new FileInputStream("D:\\Download\\Javabean-addperson案例解析.docx");
} catch(Exception e) {// 多态:Exception e = new FileNotFoundException();
	System.out.println("文件不存在!");
}
try {
    FileInputStream fis = new FileInputStream("D:\\Download\\Javabean-addperson案例解析.docx");
    fis.read();
} catch(IOException e){
    System.out.println("读文件报错了!");
} catch(FileNotFoundException e) {
    System.out.println("文件不存在!");
}
  1. JDK8的新特性:
    catch() 异常间可以自小到大| 分割

eg.

try {
    //创建输入流
    FileInputStream fis = new FileInputStream("D:\\Download\\Javabean-addperson案例解析.docx");
    // 进行数学运算
    System.out.println(100 / 0); // 这个异常是运行时异常,编写程序时可以处理,也可以不处理。
} catch(FileNotFoundException | ArithmeticException | NullPointerException e) {
    System.out.println("文件不存在?数学异常?空指针异常?都有可能!");
}

8、异常两个重要方法

方法名 作用
String getMessage() 返回异常的详细消息字符串
void printStackTrace() 追踪堆栈异常信息(采用异步线程)

9、finally字句

在finally子句中的代码是最后执行的,并且是 一定会执行 的,即使try语句块中的代码出现了异常。

finally子句必须和try一起出现,不能单独编写。

9.1 finally语句通常使用在哪些情况下呢?

通常在finally语句块中完成 资源的释放/关闭

eg.

public class ExceptionTest10 {
    public static void main(String[] args) {
        FileInputStream fis = null; // 声明位置放到try外面。这样在finally中才能用。
        try {
            fis = new FileInputStream("D:\\Download\\Javabean-addperson案例解析.docx");
            String s = null;
            // 这里一定会出现空指针异常!
            s.toString();
            System.out.println("hello world!");

            // 流使用完需要关闭,因为流是占用资源的。
            // 即使以上程序出现异常,流也必须要关闭!
            // 放在这里有可能流关不了。
            //fis.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch(IOException e){
            e.printStackTrace();
        } catch(NullPointerException e) {
            e.printStackTrace();
        } finally {
            System.out.println("hello 浩克!");
            // 流的关闭放在这里比较保险。
            // finally中的代码是一定会执行的。
            // 即使try中出现了异常!
            if (fis != null) { // 避免空指针异常!
                try {
                    // close()方法有异常,采用捕捉的方式。
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

9.2try和finally联用,没有catch

eg.

public class ExceptionTest11 {
    public static void main(String[] args) {
    	try {
            System.out.println("try...");
            return;
        } finally {
            System.out.println("finally...");
        }

        // 这里不能写语句,因为这个代码是无法执行到的。
        //System.out.println("Hello World!");
    }
}

以下代码的执行顺序:

  1. 先执行try…
  2. 再执行finally…
  3. 最后执行 return (return语句只要执行方法必然结束。)

注意:

  • try不能单独使用。
  • try finally可以联合使用。
  • 放在finally语句块中的代码是一定会执行的

9.3 finally子句失效

System.exit(0); 只有这个可以治finally。

public class ExceptionTest12 {
    public static void main(String[] args) {
        try {
            System.out.println("try...");
            // 退出JVM
            System.exit(0); // 退出JVM之后,finally语句中的代码就不执行了!
        } finally {
            System.out.println("finally...");
        }
    }
}

9.4 finally面试题

public class ExceptionTest13 {
    public static void main(String[] args) {
        int result = m();
        System.out.println(result); //100
    }

    /*
    java语法规则(有一些规则是不能破坏的,一旦这么说了,就必须这么做!):
        java中有一条这样的规则:
            方法体中的代码必须遵循自上而下顺序依次逐行执行(亘古不变的语法!)
        java中海油一条语法规则:
            return语句一旦执行,整个方法必须结束(亘古不变的语法!)
     */
    public static int m(){
        int i = 100;
        try {
            // 这行代码出现在int i = 100;的下面,所以最终结果必须是返回100
            // return语句还必须保证是最后执行的。一旦执行,整个方法结束。
            return i;
        } finally {
            i++;
        }
    }
}

反编译之后的效果:

public static int m(){
    int i = 100;
    int j = i;
    i++;
    return j;
}

9.5 final finally finalize有什么区别?

  • final 关键字
  1. final修饰的无法继承
  2. final修饰的方法无法覆盖
  3. final修饰的变量不能重新赋值
  • finally 关键字
  1. finally 和try一起联合使用。
  2. finally语句块中的代码是必须执行的。
  • finalize 标识符
  1. 是一个Object类中的方法名。
  2. 这个方法是由垃圾回收器GC负责调用的

10、自定义异常(开发中常用)

10.1前言

SUN提供的JDK内置的异常肯定是不够的用的。在实际的开发中,有很多业务,这些业务出现异常之后,JDK中都是没有的。和业务挂钩的。因此需要自定义异常。

10.2自定义异常步骤

  1. 第一步:编写一个类继承 Exception 或者 RuntimeException.
  2. 第二步:提供两个 构造方法,一个无参数的,一个带有String参数的。

eg.

//栈操作异常:自定义异常!
public class StackOperationException extends Exception{ // 编译时异常!
    public MyStackOperationException(){

    }

    public MyStackOperationException(String s){
        super(s);
    }
}

11、方法覆盖,时遗留的问题

  • 重写之后的方法不能比重写之前的方法抛出更多(更宽泛)的异常,可以更少。方法覆盖
class Animal {
    public void doSome(){

    }

    public void doOther() throws Exception{

    }
}

class Cat extends Animal {

    // 编译正常。
    public void doSome() throws RuntimeException{

    }

    // 编译报错。
    /*public void doSome() throws Exception{

    }*/

    // 编译正常。
    /*public void doOther() {

    }*/

    // 编译正常。
    /*public void doOther() throws Exception{

    }*/

    // 编译正常。
    public void doOther() throws NullPointerException{

    }
}

注意:
一般不会这样考虑,方法覆盖复制一份,然后重写就好了。

12、总结异常中的关键字

  • 异常捕捉:
  1. try
  2. catch
  3. finally
  • throws 在方法声明位置上使用,表示上报异常信息调用者
  • throw 手动抛出异常

eg.

    public void pop() throws StackOperationException {
        if(index < 0){
            throw new MyStackOperationException("弹栈失败,栈已空!");//手动抛出异常
        }
    }

该方法index < 0时手动抛出异常,然后在方法里没有处理,上报给调用者,让调用者处理!

版权声明:程序员胖胖胖虎阿 发表于 2022年9月13日 上午4:24。
转载请注明:Java异常(超详细!) | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...