前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【Java】异常处理:从基础到进阶

【Java】异常处理:从基础到进阶

作者头像
Yui_
发布2025-01-27 00:13:25
发布2025-01-27 00:13:25
15700
代码可运行
举报
文章被收录于专栏:Yui编程知识Yui编程知识
运行总次数:0
代码可运行

1. 什么是异常

在编程中,异常(Exception)是指程序在运行过程中程序的错误或者意外情况,它会导致程序的控制流发生改变。通常,异常发生时程序会停止正常执行,直到找到能够处理该异常的代码或者终止程序的执行。

1.1 异常的特点

  • 意外事件:异常是程序在运行过程中的预料之外的事情,经典的案例有:除0错误,文件为找到,网络连接中断等等。
  • 中断程序流:一旦发生异常,程序的正常执行会被中断,直到异常被捕获并处理或者程序崩溃。
  • 可捕获和处理:通过异常处理机制,可以捕获异常并进行处理,从而防止程序完全崩溃。

1.2 Java异常的的分类

  • 受检异常(Checked Exception)
    • 必须在代码中显式处理(通过 try-catch 或声明 throws)。
    • 通常由程序本身引起(如文件未找到、数据库连接失败)。
    • 继承自 Exception,但不包括其子类 RuntimeException
    • 示例:
      • IOException
      • SQLException
  • 非受检异常(Unchecked Exception)
    • 由程序中的逻辑错误或不正确的代码导致。
    • 运行时抛出,编译时不强制要求处理。
    • 继承自 RuntimeException
    • 示例:
      • NullPointerException
      • ArrayIndexOutOfBoundsException
  • 错误(Error)
    • 由 JVM 引发,表示严重问题。
    • 通常不可恢复,程序应避免依赖于错误的处理。
    • 继承自 Error 类。
    • 示例:
      • StackOverflowError
      • OutOfMemoryError

1.3 Java异常的层次结构

Java异常都是有其继承关系的,具体的层次结构如下:

代码语言:javascript
代码运行次数:0
复制
java.lang.Throwable
   ├── java.lang.Error
   │    └── (如 OutOfMemoryError, StackOverflowError)
   └── java.lang.Exception
        ├── java.lang.RuntimeException
        │    └── (如 NullPointerException, ArithmeticException)
        └── 其他受检异常
             └── (如 IOException, SQLException)

1.4 常见异常及其原因

异常

描述

ArithmeticException

算术运算异常,如除数为零。

NullPointerException

调用空引用对象的方法或访问其字段时抛出。

ArrayIndexOutOfBoundsException

访问数组索引超出范围。

ClassCastException

非法类型转换时抛出。

NumberFormatException

将字符串解析为数值类型失败。

IOException

I/O 操作失败时抛出,如文件未找到、无法读取等。


了解完异常后,下面就是异常的处理了。

2. 如何进行异常处理

Java 的异常处理机制通过捕获和处理程序在运行时的异常情况,提高了代码的健壮性和可维护性。异常处理机制包括异常的抛出、捕获和恢复。 具体可以分为三步:

  1. 抛出异常:当程序遇到异常情况时,会抛出异常。抛出异常时,程序控制流会被转移到最近的异常处理代码。
  2. 捕获异常:通过try-catch语句,我们可以捕获并处理异常,当异常发生时,程序会跳转到与之匹配的except`块进行处理。
  3. 处理异常:处理异常的方式可以是记录错误日志,提供用户友好的错误信息、恢复程序的状态等。处理完异常后,程序可以继续执行或根据需求终止。
  4. 资源释放 :使用 finally 块或 try-with-resources 语句确保资源(如文件、数据库连接)被正确关闭。 基础演示代码为:
代码语言:javascript
代码运行次数:0
复制
try {
    // 可能引发异常的代码
} catch (ExceptionType1 e1) {
    // 处理异常类型1
} catch (ExceptionType2 e2) {
    // 处理异常类型2
} finally {
    // 始终执行的代码
}

那么先来介绍相关的关键字吧

2.1 try语句

try块用于编写可能会抛出异常的代码。如果代码执行过程中发生异常,会跳转到相应的catch块进行处理。 写一个除0错误来看看吧

代码语言:javascript
代码运行次数:0
复制
public class ExceptionDemo {
    public static void main(String[] args) {
        try {
            int result = 10 / 0; // 抛出 ArithmeticException
        } catch (ArithmeticException e) {
            System.out.println("捕获异常:" + e.getMessage());
        } finally {
            System.out.println("程序结束。");
        }
    }
}

2.2 catch语句

catch块用于捕获异常并处理它。可以指定捕获某一特定类型的异常,或者捕获所有异常。

代码语言:javascript
代码运行次数:0
复制
catch (ExceptionType e) {
    // 异常处理代码
}

捕获多个不同类型的异常:

代码语言:javascript
代码运行次数:0
复制
try {
    // 代码块
} catch (IOException | SQLException e) {
    e.printStackTrace();
}
代码语言:javascript
代码运行次数:0
复制
try {
    int[] arr = new int[2];
    System.out.println(arr[5]); // 可能抛出异常
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("数组越界:" + e.getMessage());
} catch (Exception e) {
    System.out.println("其他异常:" + e.getMessage());
}

捕获所有异常(不推荐,除非有充分的理由)

代码语言:javascript
代码运行次数:0
复制
try {
    // 可能抛出异常的代码
    int result = 10 / 0; // ArithmeticException
} catch (Exception e) {
    System.out.println("捕获异常:" + e.getClass().getName() + " - " + e.getMessage());
}

直接捕获 Exception 会导致一系列潜在问题,比如掩盖程序中的真实错误、影响程序的退出、难以针对特定错误做处理等。因此,推荐捕获特定的异常类型,确保程序能够精准地处理不同类型的错误,并且保持程序的可调试性、可维护性和灵活性。

2.3 finally语句

finally块用于执行一些清理工作,不管是否发生异常都会执行。它通常用于关闭文件,释放资源等操作。

代码语言:javascript
代码运行次数:0
复制
try {
    int result = 10 / 2;
    System.out.println("结果:" + result);
} catch (ArithmeticException e) {
    System.out.println("异常:" + e.getMessage());
} finally {
    System.out.println("资源释放:结束计算");
}

2.4 throw语句

用于手动抛出一个异常对象。

  • 只能抛出 Throwable 类及其子类的对象。
  • 通常配合自定义异常或特定条件下的错误处理。
代码语言:javascript
代码运行次数:0
复制
public void checkAge(int age) {
    if (age < 18) {
        throw new IllegalArgumentException("年龄必须大于18岁");
    }
    System.out.println("合法年龄:" + age);
}

2.5 throws语句

用于声明方法可能抛出的异常,提醒调用者进行处理。

  • 声明多个异常时,用逗号分隔。
  • 在受检异常(如 IOException)的场景下必须声明,非受检异常(如 RuntimeException)可以不声明。
代码语言:javascript
代码运行次数:0
复制
public void readFile() throws IOException {
    FileInputStream fis = new FileInputStream("file.txt");
}

结合起来使用如下:

代码语言:javascript
代码运行次数:0
复制
public class ExceptionDemo {
    public static void main(String[] args) {
        try {
            processFile();
        } catch (IOException e) {
            System.out.println("捕获异常:" + e.getMessage());
        } finally {
            System.out.println("程序执行完毕");
        }
    }

    public static void processFile() throws IOException {
        throw new IOException("文件读取失败");
    }
}
/*
捕获异常:文件读取失败
程序执行完毕
*/

2.4 自定义异常

开发者可以根据需要创建自己的异常类:

代码语言:javascript
代码运行次数:0
复制
class MyException extends Exception {
    public MyException(String message) {
        super(message);
    }
}
public class CustomExceptionDemo {
    public static void main(String[] args) {
        try {
            throw new MyException("自定义异常");
        } catch (MyException e) {
            System.out.println("捕获:" + e.getMessage());
        }
    }
}

2.6 上下文管理器

上下文管理器是执行代码是自动处理资源(如文件、网站连接等)的方式。通过自定义上下文管理器,可以确保在代码块执行前后自动处理异常。 Java 从 JDK 7 开始提供了 try-with-resources,这是管理资源的主要方式。它要求被管理的资源实现了 AutoCloseable 接口(或其子接口 Closeable)。

代码语言:javascript
代码运行次数:0
复制
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("example.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.err.println("发生异常: " + e.getMessage());
        }
    }
}

try-with-resources 中,资源会在 try 块结束时自动关闭,无需显式调用 close()

Java的异常处理机制通过 try-catch 语句让我们能够优雅地捕获和处理错误,确保程序在面对意外问题时不会崩溃,同时也能让我们在出错时进行适当的错误日志记录和资源清理。 你有没有想过既然我们已经知道了会发生什么类型的错误,为什么不把程序写对,还搞什么异常处理呢?其实可没这么简单哦~

3. 为什么要进行异常处理

在编程中,知道某些存在会发生异常并不总是意味着我们应该通过修改代码来避免这些异常。实际上,在很多情况下,异常处理是一种更加优雅且有效的解决方案。以下我会给出原因,为什么在已知可能会发生异常时,我们会选择进行异常处理而不是修改代码。

3.1 有些错误无法避免

有些异常是程序执行过程中无法避免的,比如:

  • 用户输入的非法数据(如数字输入要求,但是用户输入字符)。
  • 外部资源不可用(比如文件不存在,网络连接丢失,数据库连接失败)。
  • 时间和空间的限制(比如内存不足,文件过大等等)。

3.2 异常使得代码更加灵活

有时,异常发生并不意味着程序的失败,而是为了在某些意外情况下采取灵活的行动。程序的设计常常需要具备容错性和灵活性。

  • 外部依赖不可控:许多系统依赖于外部输入或服务,而这些服务不一定总是可靠。例如,文件是否存在、外部服务是否响应、用户是否输入有效数据等。
  • 不同的错误响应:有些错误我们希望通过恢复操作(如重试、使用备用方案)来解决,而有些错误则需要终止程序。 例如,如果数据库连接失败,可以通过异常处理捕获该异常,然后进行重试或者使用备用数据库;如果文件不存在,可以让用户提供路径,而不是直接退出程序。

3.3 避免过度复杂的代码修改

如果所有的潜在异常都要通过修改代码来避免,程序的复杂性将急剧增加。每个细节都需要为可能的错误情况添加检查,这会让代码变得臃肿且不易维护。 例如,检查用户输入是否有效,验证文件是否存在、数据库连接是否正常等,可以通过异常处理来集中管理错误,而不需要将大量的“防错”代码散布在程序中。

代码语言:javascript
代码运行次数:0
复制
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;

public class Main {
    public static void main(String[] args) {
        String filename = "example.txt";

        // 方法 1:复杂的代码(不太优雅)
        File file = new File(filename);
        if (file.exists()) {
            try {
                FileReader reader = new FileReader(file);
                System.out.println("文件已打开");
                // 执行读取操作
                reader.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("文件不存在");
        }

        // 方法 2:使用异常处理(简洁优雅)
        try {
            FileReader reader = new FileReader(filename);
            System.out.println("文件已打开");
            // 执行读取操作
            reader.close();
        } catch (FileNotFoundException e) {
            System.out.println("文件不存在");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

总的来说,虽然修改代码以避免异常是一种常见的处理方式,但并不是每次都能满足需求。异常处理提供了一种更加灵活、清晰和优雅的方式来应对错误,尤其在面对外部输入、环境变化等不可控因素时。通过异常处理,程序不仅可以更好地容忍错误,还能保持稳定和可维护性。

4. 异常和Bug有什么区别

你有没有想过异常和Bug有什么区别呢?

  • 异常(Exception)
    • 是程序在运行时遇到的错误或异常情况,通常会中断程序的正常流程。
    • 异常是由程序内部逻辑、环境问题或外部输入等原因引起的,开发者可以通过异常处理机制(如 try-except)来捕获并处理它们。
    • 异常通常是预期的错误,程序员可以预测并进行相应处理。 例如:FileNotFoundErrorZeroDivisionError
  • Bug
    • Bug 是程序中的缺陷、错误或设计不当,通常是由于程序员在编写代码时的疏忽、逻辑错误或误解需求导致的。
    • Bug 不一定是异常,它可能不会直接引发程序崩溃,但会导致程序的行为不符合预期。
    • Bug 是不期望的错误,需要通过调试和修复来解决。 例如:错误的算法实现、用户界面问题、数据处理中的逻辑漏洞。 一句话来说就是:异常是程序运行过程中遇到的错误,通常是可以被捕获和处理的;而bug是程序代码中的缺陷或设计问题,可能导致程序行为不符合预期,通常需要通过调试来修复。

5.总结

异常处理不仅仅是程序中应对错误的工具,更是确保程序健壮性和可维护性的重要手段。通过精确地捕获和处理异常,开发者能够有效地防止程序崩溃,并为用户提供更加友好的错误提示。合理的异常处理不仅能让代码在面对预期之外的情况时保持稳定,还能提升程序的可读性与可扩展性。无论是基础的异常捕获,还是进阶的自定义异常设计和上下文信息的传递,异常处理的每一层细节都关系到代码的质量和系统的可靠性。通过不断优化异常处理机制,我们不仅能提升用户体验,还能为程序的未来发展打下坚实的基础。掌握并善用异常处理技巧,将使我们的代码更加优雅、可靠,真正能够应对复杂多变的实际应用场景。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-01-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 什么是异常
    • 1.1 异常的特点
    • 1.2 Java异常的的分类
    • 1.3 Java异常的层次结构
    • 1.4 常见异常及其原因
  • 2. 如何进行异常处理
    • 2.1 try语句
    • 2.2 catch语句
    • 2.3 finally语句
    • 2.4 throw语句
    • 2.5 throws语句
    • 2.4 自定义异常
    • 2.6 上下文管理器
  • 3. 为什么要进行异常处理
    • 3.1 有些错误无法避免
    • 3.2 异常使得代码更加灵活
    • 3.3 避免过度复杂的代码修改
  • 4. 异常和Bug有什么区别
  • 5.总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档