代码救火队:try-catch-finally带你走出异常困境

发布时间:2023年12月21日

欢迎来到我的博客,代码的世界里,每一行都是一个故事


在这里插入图片描述

前言

在编写代码的过程中,我们经常会遇到各种意外情况,如输入错误、网络中断、文件不存在等。而try-catch-finally就像是一支代码的救火队,它可以在你的程序发生异常时迅速出动,保障程序的稳定运行。在本文中,我们将一同踏上异常探险之旅,揭示try-catch-finally的神秘面纱,让你成为异常处理的高手。

try-catch-finally的基本语法

当然,让我们来谈谈 Java 中 try-catch-finally 的基本语法。这就像编程世界的保险措施一样,有点像我们生活中的安全网,总是能够应对一些出乎意料的状况。

首先,我们有 try 块,就像是我们尝试走钢丝一样,这里我们把可能出现异常的代码写在这个块里面。比如说,你试图从冰箱里拿出一块巧克力,但是可能会发现冰箱是空的,那就是一个潜在的异常。

然后是 catch 块,就好比你有一个超能力,能够在发生异常的时候迅速反应。你可以指定在 try 块中抛出的特定类型的异常,如果有异常发生,就会跳到对应的 catch 块,那里你可以处理这个异常,或者至少记录一下。

接下来是 finally 块,这是我们的救援队,无论发生了什么,这里的代码都会被执行。就好比你不管发生了什么情况,最终你总能够回到自己的家中,这里是一些无论如何都需要执行的代码。

让我们来看一个简单的例子:

try {
    // 尝试从冰箱里拿出巧克力
    eatChocolate();
} catch (EmptyFridgeException e) {
    // 冰箱是空的异常
    buyChocolate();
} finally {
    // 无论如何都会执行的代码
    cleanHands();
}

在这个例子中,如果 eatChocolate 函数执行时发现冰箱是空的,就会抛出一个 EmptyFridgeException 异常,然后我们会跳到 catch 块,购买巧克力。最后,无论如何都会执行 finally 块中的代码,清理双手。

这就是 try-catch-finally 的基本语法和用法。记住,异常处理就像是人生中的一场冒险,我们总是希望能够处理好各种意外情况!

异常类层次结构

让我们一起深入了解 Java 异常类的层次结构,这个结构就像是一本异常百科全书,里面有各种各样的异常,有些甚至比电视剧里的情节还要精彩!

首先,所有的异常都继承自 Throwable 类,这是异常类层次结构的根。就像是一棵大树,所有的异常都是从这棵树上分出来的小树枝。

在这个异常森林中,我们有两个主要的分支:ErrorExceptionError 通常表示一些严重的问题,比如内存溢出,这种情况就像是你打算装下整个宇宙的信息,结果你的硬盘爆炸了。

Exception 则是一般性的异常,分为两种:受检异常非受检异常。就好比你去超市买东西,有些商品要被检查一下(受检异常),有些则不需要,你自己随便拿(非受检异常)。

接下来,我们再分出一些小分支。在 Exception 下,有 RuntimeException,这个类别的异常通常是由程序员的错误导致的,比如你在程序里写了个除零操作,结果整个程序崩溃了。这就像是你在玩拼图时,把一个大象的拼图放到了天鹅的位置,结果整个画面都变了。

另外,还有一些具体的异常类,比如 IOException,它表示输入输出异常,就好比你试图读取一个损坏的文件。还有 NullPointerException,这是许多程序员的老朋友,表示你试图在一个空的引用上进行操作,就像是你试图给空气中的朋友倒一杯咖啡。

总的来说,异常类层次结构就像是一部电影,有悲剧、喜剧,还有一些让你啼笑皆非的情节。理解这些异常,就像是了解电影中的角色一样,让你能够更好地应对编程世界的各种波折。希望你在异常的森林中能够游刃有余,笑对各种小插曲!

多重catch块的应用

当我们涉足到异常处理的深邃世界时,有时一个 catch 块是远远不够的,就像是你在游乐园的过山车上,有时候需要备好多重安全带才能应对各种突发状况。所以,让我们看看多重 catch 块的应用吧!

首先,让我们设想一个场景:你是一名动物园管理员,负责喂养各种动物。在你的代码中,可能会有各种异常情况,比如有的动物可能吃不惯某种食物,有的可能会在吃饭的时候发出奇怪的声音,总之,异常就像是动物们可能会制造的一些小淘气。

try {
    // 喂养动物
    feedAnimals();
} catch (NotHungryException e) {
    // 动物不饿异常
    console.log("动物们似乎都不太饿,没必要喂食。");
} catch (StrangeNoiseException e) {
    // 奇怪的声音异常
    console.log("听起来有点奇怪,可能是有动物在演唱会。");
} catch (FoodNotFoundException e) {
    // 食物没找到异常
    console.log("哎呀,某个动物的食物找不到了,赶紧去超市买一些。");
} catch (Exception e) {
    // 其他未知异常
    console.log("发生了一些未知的异常,管理员赶紧查看。");
} finally {
    // 无论如何都会执行的代码
    cleanAnimalCages();
}

在这个例子中,我们使用了多个 catch 块来处理不同类型的异常。如果动物不饿,我们捕获 NotHungryException,如果听到奇怪的声音,我们捕获 StrangeNoiseException,如果食物找不到,我们捕获 FoodNotFoundException。而如果出现了其他未知的异常,我们就用 catch (Exception e) 来兜底,确保不会漏掉任何异常。

这样,无论是大象吃素还是猴子唱歌,我们都能够妥善处理,就像是一名优秀的动物园管理员一样,总能够在各种情况下保持冷静。希望你的编程之旅也能够充满乐趣和冒险!

finally块的作用与陷阱

finally 块是异常处理中的一个安全港,它的作用是确保无论是否发生异常,其中的代码都会被执行。就像是生活中的保险柜,你可以放置一些重要的东西,确保它们不会丢失。

执行时机:
finally 块中的代码在以下情况下都会被执行:

  1. try 块中没有异常发生时,finally 块会在 try 块执行完之后立即执行。

  2. try 块中发生异常,且在 catch 块中没有找到匹配的异常处理程序时,finally 块也会在异常被抛出之前执行。

  3. 如果在 catch 块中找到了匹配的异常处理程序,finally 块会在 catch 块执行完之后执行。

作用:

  • 资源释放: finally 块通常用于确保资源(如文件、数据库连接、网络连接等)得到正确释放。无论发生异常与否,你都可以放心地在 finally 块中关闭文件、断开数据库连接等。
FileInputStream file = null;
try {
    file = new FileInputStream("example.txt");
    // 进行文件操作
} catch (IOException e) {
    // 处理异常
} finally {
    try {
        if (file != null) {
            file.close(); // 确保文件流被关闭
        }
    } catch (IOException e) {
        // 处理关闭文件时的异常
    }
}
  • 清理操作: finally 块也可以用于执行一些必要的清理操作,无论是否发生异常。

陷阱:

在使用 finally 块时,有一些需要注意的陷阱:

  1. return 语句在 finally 块中的影响: 如果在 finally 块中包含了 return 语句,它会覆盖掉 try 块或 catch 块中的 return 语句。这可能导致一些预料之外的行为。
public int foo() {
    try {
        return 1;
    } finally {
        return 2; // 这个值将会覆盖 try 块中的返回值
    }
}
  1. System.exit() 在 finally 中的影响: 如果在 finally 块中使用了 System.exit() 来退出程序,它会覆盖任何异常或返回语句,直接终止程序。这可能导致一些不可预测的结果。
try {
    // 一些代码
} finally {
    System.exit(0); // 这将会直接终止程序
}

总的来说,finally 块是一个非常有用的工具,但在使用时需要小心,特别是要注意它可能对程序流程和返回值产生的影响。

  1. 如果 finally 块中发生异常,它会覆盖 try 块或 catch 块中发生的异常,而且这个异常会成为最终抛出的异常。这可能会导致原始异常被掩盖,需要谨慎处理。

    以下是一个示例,演示了 finally 块中的异常覆盖情况:

    public class FinallyExceptionExample {
        public static void main(String[] args) {
            try {
                // 一些代码
                throw new IllegalArgumentException("Exception in try block");
            } catch (IllegalArgumentException e) {
                System.out.println("Caught exception: " + e.getMessage());
                throw new ArithmeticException("Exception in catch block");
            } finally {
                // finally 块中发生异常
                throw new NullPointerException("Exception in finally block");
            }
        }
    }
    

    在这个例子中,原始的 IllegalArgumentExceptionArithmeticException 都被 finally 块中的 NullPointerException 覆盖。程序最终抛出的异常是 NullPointerException

    要注意的是,覆盖原始异常可能会导致调试和错误定位变得困难,因为你失去了对原始异常的信息。因此,在 finally 块中发生异常时,最好记录或处理这个异常,而不是简单地让它抛出。例如:

    public class FinallyExceptionHandlingExample {
        public static void main(String[] args) {
            try {
                // 一些代码
                throw new IllegalArgumentException("Exception in try block");
            } catch (IllegalArgumentException e) {
                System.out.println("Caught exception: " + e.getMessage());
                throw new ArithmeticException("Exception in catch block");
            } finally {
                try {
                    // finally 块中发生异常
                    throw new NullPointerException("Exception in finally block");
                } catch (NullPointerException e) {
                    System.out.println("Caught exception in finally: " + e.getMessage());
                }
            }
        }
    }
    

    在这个修改后的例子中,finally 块中的异常被捕获并处理,而不是直接抛出,这样就能更好地保留原始异常的信息。

try-with-resource

当我们谈到资源管理和关闭时,就像是在谈论如何结束一场华丽的烟花秀,而 try-with-resources 就是你的烟花秀的绝佳舞台,让资源的打开和关闭变得如此轻松。

基本语法:

try (ResourceType resource = new ResourceType()) {
    // 使用资源的代码块
} catch (Exception e) {
    // 处理异常
}

在这里,ResourceType 是实现了 AutoCloseableCloseable 接口的资源类型。try-with-resources 语句会在代码块执行完毕后,自动调用资源的 close() 方法,无论代码块是否正常结束或者发生了异常。

优势:

  1. 简洁性: 能够以简单的方式管理资源,不再需要显式地在 finally 块中关闭资源,代码更加清晰。

  2. 安全性: 由于 try-with-resources 会自动关闭资源,避免了因为忘记关闭资源而导致的资源泄漏。

例子:

// 传统方式
BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("example.txt"));
    String line = reader.readLine();
    // 处理读取的数据
} catch (IOException e) {
    // 处理异常
} finally {
    try {
        if (reader != null) {
            reader.close();
        }
    } catch (IOException e) {
        // 处理关闭文件时的异常
    }
}

// 使用 try-with-resources
try (BufferedReader reader = new BufferedReader(new FileReader("example.txt"))) {
    String line = reader.readLine();
    // 处理读取的数据
} catch (IOException e) {
    // 处理异常
}

在第二个例子中,我们使用了 try-with-resources,不再需要手动关闭 BufferedReader,这让代码更加简洁而且更安全。

资源类型需满足的条件:
为了使用 try-with-resources,资源类型必须实现 AutoCloseableCloseable 接口。这两个接口都定义了一个 close() 方法,该方法在资源不再需要时会被调用。

所以,让我们大声喊出来:用 try-with-resources,告别繁琐的资源管理,让我们的代码更优雅、更干净,就像是一场代码的音乐会,资源的开启和关闭就是最和谐的旋律!

异常处理的最佳实践

当谈到异常处理时,就像是在编写代码的马戏团中表演惊险刺激的高飞动作。为了确保你的表演不会出现重大事故,让我们来分享一些异常处理的最佳实践,让你的代码更像是世界级的杂技表演,而不是马戏表演。

  1. 具体异常胜过泛化异常: 有时候异常就像是表演中的各种特技,不同的异常就像是各种绝活,用具体的异常来捕获特定情况,而不是泛泛地捕获 Exception

    // 不够专业的写法
    try {
        // 一些代码
    } catch (Exception e) {
        // 处理异常
    }
    
    // 专业的写法
    try {
        // 一些代码
    } catch (FileNotFoundException e) {
        // 处理文件未找到异常
    } catch (SQLException e) {
        // 处理数据库异常
    }
    
  2. 不要吞噬异常: 就像是在表演中,不要让异常消失得无影无踪,这样会让你在调试时感到更头疼。至少要在日志中记录异常信息,这样你在后期排查问题时能更容易找到问题的症结。

    try {
        // 一些代码
    } catch (Exception e) {
        // 不建议这样做,至少要记录日志
        e.printStackTrace();
    }
    
  3. 清理资源使用 try-with-resources: 就像是表演结束后的清理工作,确保你在使用完资源后正确关闭它们。try-with-resources 是一个非常优雅的方式,自动确保资源的关闭,就像是舞台上的幕后工作者悄悄完成清理工作。

    // 不够优雅的写法
    BufferedReader reader = null;
    try {
        reader = new BufferedReader(new FileReader("example.txt"));
        // 一些代码
    } catch (IOException e) {
        // 处理异常
    } finally {
        try {
            if (reader != null) {
                reader.close();
            }
        } catch (IOException e) {
            // 处理关闭文件时的异常
        }
    }
    
    // 更优雅的写法
    try (BufferedReader reader = new BufferedReader(new FileReader("example.txt"))) {
        // 一些代码
    } catch (IOException e) {
        // 处理异常
    }
    
  4. 避免空指针异常: 空指针异常就像是在空中演出失误的特技,要小心处理可能为空的对象,使用条件判断或者 Optional 类来避免悲剧的发生。

    // 可能引发空指针异常的写法
    String name = null;
    try {
        name.length(); // 可能引发 NullPointerException
    } catch (NullPointerException e) {
        // 处理异常
    }
    
    // 更安全的写法
    String name = null;
    if (name != null) {
        name.length(); // 避免 NullPointerException
    }
    
  5. 不要过度使用异常: 就像是在表演中,别让观众疲于应付各种花样繁多的表演,不要把异常处理当成一种常规操作。只在真正需要的地方使用异常,不要过度使用它们。

总之,异常处理就像是在编程的马戏场地上表演高难度的特技,保持平衡、谨慎和优雅是关键。通过遵循这些建议,你的异常处理表演将更加令人印象深刻,观众(也就是你的代码使用者)会感谢你为他们呈现的精彩表演。

自定义异常的创建与使用

🅱?:springboot全局异常实现以及@Valid和@Validated优雅实现入参验证

异常处理的高级技巧

当我们来到异常处理的高级领域,就像是在编程世界中进行一场独特的魔术表演,让我们一起揭示一些高级技巧,使你的异常处理变得更加优雅和精湛。

1. 异常链的构建:

有时候,异常就像是在代码中扔来扔去的热土豆,你可以使用异常链来追踪异常的根本原因,就像是在寻找一个魔术的解谜之道。

public class ExceptionChainingDemo {
    public static void main(String[] args) {
        try {
            performMagic();
        } catch (MagicianException e) {
            System.out.println("Caught magician exception: " + e.getMessage());
            // 获取原始异常信息
            System.out.println("Root cause: " + e.getRootCause().getMessage());
        }
    }

    private static void performMagic() throws MagicianException {
        try {
            // 模拟一个异常发生
            int result = 1 / 0;
        } catch (ArithmeticException ae) {
            // 抛出一个新的异常,并将原始异常作为其原因
            throw new MagicianException("Magic trick went wrong!", ae);
        }
    }
}

class MagicianException extends Exception {
    public MagicianException(String message, Throwable cause) {
        super(message, cause);
    }

    public Throwable getRootCause() {
        // 获取原始异常
        Throwable rootCause = this;
        while (rootCause.getCause() != null) {
            rootCause = rootCause.getCause();
        }
        return rootCause;
    }
}

在这个例子中,MagicianException 引入了一个新的异常,并将原始的 ArithmeticException 作为其原因,形成了异常链。这样在捕获异常时,你可以轻松地追溯到根本原因。

2. 异常的重新抛出:

有时候,你可能想要在捕获到异常后,将其重新抛出,就像是在表演中将魔术师的帽子抛到空中一样。

public class RethrowExceptionDemo {
    public static void main(String[] args) {
        try {
            performDangerousTrick();
        } catch (DangerousTrickException e) {
            System.out.println("Caught dangerous trick exception: " + e.getMessage());
            // 重新抛出异常
            rethrowException(e);
        }
    }

    private static void performDangerousTrick() throws DangerousTrickException {
        try {
            // 模拟一个危险的操作
            throw new DangerousTrickException("Oops! Something dangerous happened!");
        } catch (DangerousTrickException e) {
            // 捕获异常后重新抛出
            throw e;
        }
    }

    private static void rethrowException(Exception e) {
        // 在其他地方重新抛出异常,或者进行其他处理
        System.out.println("Rethrowing exception: " + e.getMessage());
    }
}

class DangerousTrickException extends Exception {
    public DangerousTrickException(String message) {
        super(message);
    }
}

在这个例子中,performDangerousTrick 方法抛出了一个危险的异常,然后在 catch 块中重新抛出了相同的异常。这样你可以在其他地方捕获并处理这个异常,或者在需要的时候重新抛出它。

通过这些高级的异常处理技巧,你可以在异常的魔法世界中自如驰骋,让你的代码更加灵活而不失优雅。就像是在魔术表演中,掌握这些技巧能让你轻松应对各种异常情况,让你成为异常处理的真正巫师!

结语

深深感谢你阅读完整篇文章,希望你从中获得了些许收获。如果觉得有价值,欢迎点赞、收藏,并关注我的更新,期待与你共同分享更多技术与思考。

在这里插入图片描述

文章来源:https://blog.csdn.net/Mrxiao_bo/article/details/135122626
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。