详解“异常”

算术异常:

public class Test {
    public static void main(String[] args) {
        System.out.println(10/0);
    }
}

数组越界异常 :

public class Test {
    public static void main(String[] args) {
        int[] array = {1,2,3};
        System.out.println(array[100]);
    }
}

空指针异常: 

public class Test {
    public static void main(String[] args) {
        int[] array = null;
        System.out.println(array.length);
    }
}

以上列举的是我们写程序时常常遇见的三种异常爆红,为减少异常的发生,我们应该了解程序员的一生之敌,异常。

异常的体系机构:

详解“异常”_第1张图片

由图易知:所有异常均继承于Throwable,细分则为两个大的子类Error 和 Exception。

Error:系统级的错误和资源耗尽等严重问题。(一般不会遇见)

特点
  • 严重问题:一旦出现 Error,往往意味着程序无法继续正常运行,例如内存不足、栈溢出等。
  • 不可恢复:程序通常没有办法从 Error 中恢复,因为这些问题超出了程序的控制范围。
  • 不强制处理:在代码中,并不需要也不应该尝试捕获和处理 Error,因为处理这些问题通常超出了应用程序的能力。

 仅列举一个例子:

public class Test {
    public class StackOverflowErrorExample {
        public static void recursiveMethod() {
            recursiveMethod();
        }
    }

        public static void main(String[] args) {
            StackOverflowErrorExample.recursiveMethod();
        }

}

详解“异常”_第2张图片

  • StackOverflowError:当方法调用栈深度超过了 JVM 允许的最大深度时抛出,通常是由于递归调用没有正确的终止条件导致的。

 Exception:程序可以捕获和处理异常。

特点
  • 可处理:程序可以通过 try-catch 块捕获并处理 Exception,或者在方法签名中使用 throws 关键字声明抛出异常。
  • 可恢复:大多数情况下,程序可以从 Exception 中恢复,继续正常运行。
分类
  • 运行时异常(Runtime Exception):在编译期间,编译器不会强制要求处理这类异常,通常是由程序逻辑错误引起的。
  • 编译时异常(Checked Exception):在编译期间,编译器会强制要求处理这类异常。

异常的处理:保障程序健壮性和稳定性,避免程序猿和程序一起崩溃。

try-catch块:这是最基础的异常处理方法

try{
//放置可能出现异常的代码
}catch(异常类型){
//处理异常代码
}catch(异常类型){
//处理异常代码
}...

注意:匹配顺序是自上而下,一旦匹配就不会再进入后续catch块!   

所以,你要把更具体的异常类型放在前面,更通用的异常类型放在后面。     

try {
    int[] arr = new int[5];
    // 引发数组越界异常
    arr[10] = 10; 
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("捕获到数组越界异常: " + e.getMessage());
} catch (Exception e) {
    System.out.println("捕获到通用异常: " + e.getMessage());
}

 同时借助getMessa方法可以获取详细的异常信息,便于调试和记录日志。

try-catch-finally 块:和try-catch类型类似,只是在catch块后加入一个finally块。

特点:

不管是否是否有异常抛出或者异常被处理,都会执行finally语句。该语句主要用于释放系统资源,关闭文件,资源库链接等。

try {
    int num = 10 / 0; // 会抛出 ArithmeticException
} catch (ArithmeticException e) {
    System.out.println("捕获到算术异常: " + e.getMessage());
} finally {
    System.out.println("finally 块总是会被执行");
}

throws 关键字:在方法签名中加入可能出现的异常,先不处理,调用方法时才处理。(保证代码的逻辑链严密)

import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class ThrowsExample {
    // 声明该方法可能会抛出 IOException
    public static void readFile() throws IOException { 
        File file = new File("nonexistent.txt");
        FileReader reader = new FileReader(file);
    }

    public static void main(String[] args) {
        try {
            readFile();
        } catch (IOException e) {
            System.out.println("捕获到 I/O 异常: " + e.getMessage());
        }
    }
}

throw 关键字:手动抛出一个异常

public class ThrowExample {
    public static void checkAge(int age) {
        if (age < 0) {
            // 手动抛出 IllegalArgumentException
            throw new IllegalArgumentException("年龄不能为负数"); 
        }
        System.out.println("年龄合法: " + age);
    }

    public static void main(String[] args) {
        try {
            checkAge(-5);
        } catch (IllegalArgumentException e) {
            System.out.println("捕获到异常: " + e.getMessage());
        }
    }
}

综上,异常处理的原则

  • 早抛出,晚捕获:在方法中尽早抛出异常,在调用链的合适位置捕获并处理异常。
  • 细化异常处理:针对不同类型的异常,编写不同的处理逻辑,避免捕获所有异常的宽泛处理。

碰到一些特定的业务场景,这些场景下系统自带的异常类无法精准地描述问题。此时,你就可以自定义异常类来满足特定需求。

自定义异常的步骤:

1. 继承合适的父类

自定义异常类需要继承 Exception 类(用于创建编译时异常)或者 RuntimeException 类(用于创建运行时异常)。

  • 编译时异常:继承 Exception 类,编译器会强制要求你处理这类异常。
  • 运行时异常:继承 RuntimeException 类,编译器不会强制要求你处理这类异常。
2. 提供构造方法

需要提供参数的构造方法或者不带参数

例如:

// 自定义编译时异常类
class CustomCheckedException extends Exception {
    // 无参构造方法
    public CustomCheckedException() {
        super();
    }

    // 带消息参数的构造方法
    public CustomCheckedException(String message) {
        super(message);
    }
}

// 测试类
public class CustomCheckedExceptionTest {
    public static void main(String[] args) {
        try {
            // 调用可能抛出异常的方法
            throwException();
        } catch (CustomCheckedException e) {
            System.out.println("捕获到自定义编译时异常: " + e.getMessage());
        }
    }

    public static void throwException() throws CustomCheckedException {
        // 抛出自定义编译时异常
        throw new CustomCheckedException("这是一个自定义编译时异常");
    }
}

结果:

在上述代码中,CustomCheckedException 类继承自 Exception 类,是一个编译时异常类。在 throwException 方法中抛出该异常,在 main 方法中捕获并处理。

自定义异常的使用场景

  • 业务逻辑异常:在业务处理过程中,如果出现不符合业务规则的情况,可以抛出自定义异常。例如,在用户注册时,如果用户名已存在,可以抛出自定义的 UsernameExistsException 异常。
  • 数据验证异常:在对输入数据进行验证时,如果数据不符合要求,可以抛出自定义异常。例如,在验证用户输入的年龄时,如果年龄为负数,可以抛出自定义的 InvalidAgeException 异常。

总结

自定义异常可以让你更精准地描述程序中出现的问题,提高代码的可读性和可维护性。通过继承 Exception 或 RuntimeException 类,并提供合适的构造方法。

你可能感兴趣的:(java,开发语言)