“空指针”引用导致“程序异常退出”两例

本文分析了Android应用中常见的空指针异常原因,并通过两个具体案例详细展示了如何避免此类异常的发生,包括函数参数检查及确保构造函数的原子性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近,又偶尔发现在某些手机上会提示“程序异常退出”的情况,在android中,最通常的原因可能是资源引用的不正确(即findViewById函数引用的资源是错误的),或者是出现空指针问题。重新分析和review代码后,发现了几处可能引起问题的空指针引用。


1. 函数参数传入为空,在函数内部没有进行参数的检测。(例1)

2. 在实现一个类的构造函数时,发生异常,导致并没有构造出理解的对象,然后,再引用这个对象,其实是引用了一个null对象,导致程序发生“异常退出”。(例2)



1. 例1:函数参数传入为空,在函数内部没有进行参数的检测。

参考代码如下:

调用者:

public void run() {
    try {
        FileEngine.deleteFile(plainFilePath);
        ...

被调者FileEngine.deleteFile的实现:

public static boolean deleteFile(String filePath) {
    if (filePath == null) {
        return false;
    }
    File file = new File(filePath);
    if (!file.exists()) {
        return false;
    }
    if (file.isFile()) {
        return file.delete();
    } else {
        return false;
    }
}

错误异常:java.lang.NullPointerException

    atjava.io.File.<init>(File.java:277)


分析:可以在deleteFile函数内部进行参数的null值判断;

  或者,在调用deleteFile之前,进行输入参数的check。

 或者由于明知是文件操作,可以直接进行异常的捕获。


2. 例2:构造函数没有成功构造。

参考代码:

调用者:

LogWriter.getInstance().print("setOnItemLongClickListener===========000====");

被调用者LogWriter类中对应的函数实现:

public class LogWriter {
    private static LogWriter mLogWriter;
    private static String mPath;
    private static Writer mWriter;
    private static SimpleDateFormat df;

    private LogWriter() {
        mPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "test";
        File file = new File(mPath);
        if (!file.exists()) {
            file.mkdirs();
        }
        try {
            SimpleDateFormat df0 = new SimpleDateFormat("yy-MM-dd-HH-mm",Locale.getDefault());
            File logFile=new File(mPath,"Log-"+df0.format(new Date())+".txt");
            mWriter = new BufferedWriter(new FileWriter(logFile));
        } catch (IOException e) {
            e.printStackTrace();
        }
        df = new SimpleDateFormat("[yy-MM-dd HH:mm:ss]: ", Locale.getDefault());
    }
    public static LogWriter getInstance() {
        if (mLogWriter == null) {
            mLogWriter = new LogWriter();
        }
        return mLogWriter;
    }
    public void close() throws IOException {
        mWriter.close();
    }
    public void print(String log) {
        try {
            mWriter.write(df.format(new Date()));
            mWriter.write(log);
            mWriter.write("\r\n");
            mWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

分析:

这里,实现了一个单例模式LogWriter,通常情况下这个它能够正常运转。但是,LogWriter有可能创建不成功(即构造函数可能抛出异常,),导致getInstance()得到的值是null,再调用它的print函数时,就造成空指针引用。

可见,保持构造函数的原子性,也是设计一个类时需要考虑的。


<think>我们正在讨论异常处理机制,特别是参考了Java的异常处理,但用户的问题是关于编程中通用的异常处理机制。因此,我们需要从更一般的角度来讲解,同时可以结合Java作为例子。 异常处理机制的核心目标:在程序出现异常情况时,能够以可控的方式处理这些异常,避免程序崩溃,同时提供错误恢复或优雅退出的机制。 主要组成部分: 1. 异常类型:通常分为检查型异常(Checked Exception)和非检查型异常(Unchecked Exception,包括运行时异常和错误)。 2. 异常处理结构:包括抛出(throw)和捕获(catch)两个关键动作,以及可选的资源清理(finally)和声明(throws)机制。 步骤: 1. 抛出异常:当程序运行过程中遇到错误,可以创建一个异常对象并抛出(throw)。 2. 捕获异常:在可能发生异常的代码块周围,使用try-catch块来捕获并处理异常。 3. 资源清理:finally块用于确保无论是否发生异常,都会执行的代码(如关闭文件、释放资源)。 4. 异常传播:如果当前方法不处理异常,可以声明抛出(throws)异常,由调用者处理。 以Java为例(但注意用户问题是一般性的): ```java try { // 可能抛出异常的代码 FileInputStream file = new FileInputStream("file.txt"); // 其他操作 } catch (FileNotFoundException e) { // 处理文件未找到异常 System.out.println("文件未找到"); } catch (IOException e) { // 处理IO异常 System.out.println("IO错误"); } finally { // 清理资源,无论是否发生异常都会执行 System.out.println("执行清理"); } ``` 但是,不同语言的异常处理机制有所不同: - Java:强制处理检查型异常(Checked Exceptions),运行时异常(RuntimeException)和错误(Error)是非检查型的。 - C++:异常处理使用try、catch、throw,但没有finally,通常使用RAII(资源获取即初始化)模式来管理资源。 - Python:使用try-except-finally,所有异常都是非检查型的(运行时异常)。 - Go:没有传统的异常处理,而是通过返回错误值(error)来处理错误,使用panic/recover来处理严重错误。 异常处理的好处: 1. 分离正常逻辑和错误处理逻辑,使代码更清晰。 2. 将错误信息封装在异常对象中,便于传递和诊断。 3. 通过异常传播机制,可以在适当的层级处理错误。 注意事项: 1. 避免过度使用异常异常处理比普通条件判断开销大,且滥用会破坏代码结构。 2. 不要忽略异常:捕获异常后应做适当处理,至少记录日志。 3. 资源清理:确保在异常发生时资源也能被正确释放(如使用finally或RAII)。 下面我们用一个伪代码示例来说明一般流程: ``` 伪代码: 函数A { try { 函数B() // 可能抛出异常 } catch (异常类型1) { // 处理异常类型1 } catch (异常类型2) { // 处理异常类型2 } finally { // 清理资源 } } 函数B { if (发生错误) { throw 异常对象 } } ``` 在异常处理中,我们通常遵循以下原则: - 具体异常优先:先捕获具体的异常,再捕获一般的异常。 - 避免空的catch块:这样会隐藏错误,使调试困难。 - 使用finally确保资源释放:如关闭文件、网络连接等。 另外,关于错误(Error)和异常(Exception)的区别: - 错误(Error):通常指严重的、程序无法处理的系统级错误,如内存溢出(OutOfMemoryError)。这些错误一般不需要捕获,因为程序无法恢复。 - 异常(Exception):程序可以处理的错误,如文件不存在、网络连接中断等。 引用[4]提到:Error的出现意味着严重问题,程序应该直接崩溃掉。因此,在异常处理机制中,我们主要处理的是Exception。 总结:异常处理机制是现代编程语言中不可或缺的部分,它帮助开发者构建更健壮、可维护的应用程序。 根据要求,我们需要生成相关问题: 1. 异常处理机制中,检查型异常和非检查型异常有什么区别? 2. 在资源清理中,finally块和RAII模式各有什么优劣? 3. 为什么说过度使用异常会影响程序性能? 4. 如何处理未捕获的异常? 注意:在回答中,我们引用了用户提供的引用,所以在末尾添加了引用标识[^1][^2][^3][^4]。但我们的回答是基于一般性知识,引用作为支持。</think>### 编程中的异常处理机制详解 异常处理是编程中的核心防御机制,用于应对程序运行时出现的非预期事件。其核心目标是:**在错误发生时保持程序可控性**,避免崩溃,同时提供错误诊断和恢复能力。 #### 一、异常处理核心模型 1. **异常类型体系** - **错误(Error)**:系统级严重故障(如内存溢出`OutOfMemoryError`),程序无法恢复[^4] $$ \text{Error} \rightarrow \text{系统不可恢复错误} $$ - **异常(Exception)**:程序可处理的错误(如文件不存在、网络中断) $$ \text{Exception} \begin{cases} \text{Checked Exception (编译时检查)} \\ \text{Unchecked Exception (运行时异常)} \end{cases} $$ 2. **处理流程** ```mermaid graph LR A[程序执行] --> B{是否异常?} B -->|是| C[创建异常对象] C --> D[寻找处理程序] D --> E[执行catch块] E --> F[执行finally块] B -->|否| G[正常执行] ``` #### 二、关键处理结构 ```java try { // 可能抛出异常的代码 FileInputStream file = new FileInputStream("data.txt"); // 可能抛出FileNotFoundException[^1] int value = Integer.parseInt(input); // 可能抛出NumberFormatException } catch (FileNotFoundException e) { // 处理特定异常 System.out.println("文件不存在: " + e.getMessage()); } catch (Exception e) { // 通用异常处理 e.printStackTrace(); } finally { // 必然执行的清理代码 file.close(); // 确保资源释放[^3] } ``` #### 三、核心处理策略 1. **抛出(Throw)** 主动触发异常: ```java if (balance < amount) { throw new InsufficientFundsException("余额不足"); // 自定义异常 } ``` 2. **捕获(Catch)** - 精准捕获:优先处理具体异常类型 - 异常链:通过`e.initCause()`保留原始异常信息 3. **传播(Propagate)** 方法声明中传递异常: ```java public void readFile() throws IOException { // 声明可能抛出异常[^2] // ...代码逻辑... } ``` #### 四、设计原则 1. **防御性编程** ```java // 检测空指针避免NullPointerException if (obj != null) { obj.execute(); } ``` 2. **异常封装** 将底层异常转换为业务异常: ```java try { dbConnection.execute(sql); } catch (SQLException e) { throw new BusinessException("数据库操作失败", e); } ``` 3. **资源自动管理** 使用try-with-resources(Java 7+): ```java try (BufferedReader br = new BufferedReader(new FileReader(path))) { // 自动关闭资源 } ``` #### 五、最佳实践 1. **避免过度捕获** ```java // 错误示范:捕获过于宽泛 try { ... } catch (Exception e) { ... } // 正确做法:精确捕获 try { ... } catch (FileNotFoundException | ParseException e) { ... } ``` 2. **异常日志化** 记录异常上下文信息: ```java catch (IOException e) { logger.error("文件操作失败: {} @ {}", fileName, LocalDateTime.now(), e); } ``` 3. **自定义异常** 创建业务相关异常类: ```java public class PaymentFailedException extends Exception { public PaymentFailedException(String message) { super(message); } } ``` #### 六、跨语言对比 | 特性 | Java | Python | Go | |--------------|--------------------------|----------------------|----------------| | 异常类型 | Checked/Unchecked | 全部Unchecked | 无传统异常 | | 错误传递 | `throws`声明 | 自动传播 | 多返回值(error)| | 资源清理 | try-with-resources | `with`语句 | `defer` | > 关键价值:通过集中处理错误逻辑,使主业务代码保持**简洁性**(引用[3])和**可维护性**,避免大量if-else分支污染核心逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liranke

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值