Skip to content

Latest commit

 

History

History
311 lines (216 loc) · 10.2 KB

十三、Java异常.md

File metadata and controls

311 lines (216 loc) · 10.2 KB

Java课程: 清空的霞光,自己做笔记方便自己查阅,青空网站上有它自己的课程笔记

异常机制

在编写一段代码时,我们肯定都会想程序按照我的的思想去运行,但是如果代码实现可能会不够完美,可能会有没考虑到的地方,如果还可以正常运行还好,如果出现错误或者异常将程序终止了那该怎么办?

在正常情况下,在程序不能正常运行就会提示报错或者异常

异常类型

异常有很多个类型比如,越界异常,空指针异常,算术异常单独;每一个异常它都是一个类,他们都继承自Exception类,异常的本质也是需要依赖对象,但是异常类型支持在程序运行出现问题时抛出,也可以提前声明,告知使用者需要处理可能出现的异常

异常的类型也有很多有运行时异常和编译时异常

1️⃣ 运行时异常是指,只有在程序运行的时候才知道会不会出错,这样的异常称为运行时异常,所有的运行时异常都是继承自RuntimeException

public class Main {

    public static void main(String[] args)  {
        test(10,0); //被除数为0会报错

    }

    public static int test(int a, int b){
        return  a/b; //除法运算
    }

}

image-20240409150319122

这里就报错了ArithmeticException运算错误,可以点进去看一下

//可以看到它也是一个类并且继承雨RuntimeException
public class ArithmeticException extends RuntimeException {
    
    private static final long serialVersionUID = 2256477558314496007L;

    /**
     * Constructs an {@code ArithmeticException} with no detail
     * message.
     */
    public ArithmeticException() {
        super();
    }

    /**
     * Constructs an {@code ArithmeticException} with the specified
     * detail message.
     *
     * @param   s   the detail message.
     */
    public ArithmeticException(String s) {
        super(s);
    }
}

2️⃣ 还有一种编译时异常,编译时异常明确指出可能会出现的异常,在编译阶段就需要进行处理,必须要考虑到出现异常的情况,如果不进行处理就无法进行编译,默认继承自Exception类的异常都是编译时异常,

Object类中定义了一个Clone方法,就明确指出了可能会出现异常使用throws关键字

protected native Object clone() throws CloneNotSupportedException;

它是继承自Exception

public
class CloneNotSupportedException extends Exception {
    private static final long serialVersionUID = 5195511250079656443L;

    /**
     * Constructs a <code>CloneNotSupportedException</code> with no
     * detail message.
     */
    public CloneNotSupportedException() {
        super();
    }

    /**
     * Constructs a <code>CloneNotSupportedException</code> with the
     * specified detail message.
     *
     * @param   s   the detail message.
     */
    public CloneNotSupportedException(String s) {
        super(s);
    }
}

3️⃣ 还有一种就是错误,错误比异常更加严重,异常不一定会导致致命的问题,而错误就是致命的问题,一般出现错误可能JVM就出无法进行正常运行了,看下面这个列子

public class Main {

    public static void main(String[] args)  {
        test();

    }

    public static void test(){
       test();
    }

}

这样无限循环会出现就会出现内存溢出的问题,这里已经时错误了,错误就会影响到整个程序的运行了

image-20240409151850536

还有一种情况就是内存溢出,JVM它是有一个内存限制的,如果超出了它的内存限制,就会导致内存溢出

public class Main {

    public static void main(String[] args)  {
       Object[] objects = new Object[Integer.MAX_VALUE];

    }

    public static void test(){
       test();
    }

}

image-20240409152107613

抛出异常

1️⃣ 调用方法时,如果传入了错误的参数而导致程序无法进行正常运行,这个时候就可以手动抛出一个异常来终止程序,同时告诉上一级方法执行出现了问题,需要使用到throw关键字

public class Main {

    public static void main(String[] args)  {
        test(10,0);

    }

    public static int test(int a, int b){
        if(b==0){
            throw new RuntimeException("被除数不能为0!");
        }
        return  a/b;
    }

}

异常的抛出同样需要船舰一个异常类出来,我们抛出的异常实际上就是这个异常对象的抛出,异常对象还携带我们抛出异常的一些信息,我们在这这里可以明确指定声明原因导致异常

image-20240409154802200

2️⃣ 在抛出异常时,程序会终止,还会打印栈追踪信息,也就是会打印在哪里出现了错误

3️⃣ 如果抛出的异常时非运行时异常,那么就必须要告知函数的调用方法我们会抛出某个异常,函数的调用方法必须对这个异常进行处理才可以比如(并不是非运行时异常才可以,运行时异常也可以不过没什么意义)

public class Main {

    public static void main(String[] args) throws Exception { //当然调用一方也可以无视,直接进行向上抛,这样抛就是抛给JVM,那就是程序直接结束了
        test(); 

    }

    public static void test() throws Exception { //使用throw告诉调用需要处理好这个可能会的出现的异常
      throw new Exception("我是编译时异常"); 
    }

}

4️⃣ 如果不同的分支条件会出现不同的异常,那么所有在方法中可能会抛出的异常都需要注明,也就是可能会抛出多个非运行时异常,他们都需要使用throw告知

    public static void main(String[] args) throws Exception {
        test(1);

    }

    public static void test(int a) throws FileNotFoundException, ClassNotFoundException {
        if(a==1)
            throw new FileNotFoundException();
        else 
            throw new ClassNotFoundException();
    }

异常的处理

1️⃣当程序没有按照我们理想的样子运行时就会出现异常,出现异常就会使得程序停止,现在可以自己处理出现的问题让程序进行运行,使用try catch

    public static void main(String[] args) {
        try{ //相当于try内地代码被监控了
            Object object = null;
            object.toString();
        }catch (NullPointerException e){//这里就可以捕获出现的异常

        }
        System.out.println("程序还是会继续运行");
    }

⚠️ catch 捕获的只能说Throwable的子类,要么时异常要么时错误,不能时其他任何类型

catch中可以直接对捕获到的异常进行处理

    public static void main(String[] args) {
        try{
            Object object = null;
            object.toString();
        }catch (NullPointerException e){
            e.printStackTrace(); //打印栈追踪信息,当然不止这一个方法还有很多
            System.out.println("异常错误信息:"+ e.getMessage()); //获取异常错误信息
        }
        System.out.println("程序还是会继续运行");
    }

image-20240409163407304

2️⃣ 之前说过调用的方法如果告知了会抛出非运行时异常,调用方可以选择继续向上抛或者选择try-catch语句进行异常的捕获,不然就无法通过编译

 	public static void main(String[] args)  { //当然调用一方也可以无视,直接进行向上抛,这样抛就是抛给JVM,那就是程序直接结束了
        try {
            test();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    public static void test() throws Exception { //使用throw告诉调用需要处理好这个可能会的出现的异常
        throw new Exception("我是编译时异常");
    }

3️⃣ 如果我们要捕获的异常是某个异常的父类,那么发生这个异常时,也可以被捕获

public static void main(String[] args){
    try {
        int[] arr = new int[1];
        arr[1] = 100;    //这里发生的是数组越界异常,它是运行时异常的子类
    } catch (RuntimeException e){  //使用运行时异常同样可以捕获到
        System.out.println("捕获到异常");
    }
}

4️⃣ 当代码出现多种类型的异常时,可以使用多个catch来处理不同的异常,不过要注意捕获异常的顺序,如果第一个捕获的异常是第二个捕获异常的父类,发生了第二个异常,会直接捕获第一个异常,也就会直接使用它的父类来捕获

try {
  //....
} catch (RuntimeException e){  //父类型在前,会将子类的也捕获

} catch (NullPointerException e) {   //永远都不会被捕获

} catch (IndexOutOfBoundsException e){   //永远都不会被捕获

}

可以简写为

try {
     //....
} catch (NullPointerException | IndexOutOfBoundsException e) {  //用|隔开每种类型即可
		
}

5️⃣ 如果希望无论出现或者不出现异常都要执行这个任务可以使用finally

try {
    //....
}catch (Exception e){
            
}finally {
  	System.out.println("lbwnb");   //无论是否出现异常,都会在最后执行
}

⚠️ try至少要搭配catch或者Finnal中的一个

断言表达式

我们可以使用断言表达式来对某些东西进行判断,如果判断失误就会抛出错误,只不过默认情况下没有开启,需要手动开启

(我觉得这个没啥用我我直接过了)