Java基础——通过异常处理错误

Java的基本理念是“结构不佳的代码不能运行”。

发现程序中的错误的理想时机是编译阶段(即生成.class文件的阶段),但现实是很多的错误只有在运行阶段才能被发现。比如说,程序需要打开一个文件,你不运行程序去试图打开文件,怎么会知道文件是不存在,打不开还是有什么其它的错误。又比如说程序中一个方法的参数是对象引用,然后向该对象引用发送消息,你不运行程序怎么知道传递给参数的实参是是不是为null。因此就有了通过异常来处理错误的说法。

Java异常体系结构图

Java异常体系图

所以的错误和异常都是Throwable的子类,其中Error及其子类一般是系统层面或者JVM层面的错误,不需要程序员的过多关注。Exception及其子类表示程序中的异常,它们是Java异常处理系统的主要部分。其中又分为被检查的异常和运行时异常,运行时异常(RuntimeException)不需要程序手动地捕获(Catch)或者是抛出(Throws),在产生运行时异常后,运行的程序通常会终止然后该异常被JVM捕获,进而执行JVM所提供的异常处理程序(通常是打印调用栈轨迹)。

1. 捕获和处理异常

  • 捕获和处理异常的一般形式

    try{
        // 可能产生异常的代码
    }
    catch(第一种异常) {
        // 对于第一种异常的处理
        // 注意:第一种异常不能是第二种异常的基类,因为加入第一种异常是第二种异常的基类的话,
        // 第二个Catch语句一定得不到执行(此时编译器会报错),因此如果要捕捉Exception异常,该
        // catch一定放在最后一位
    }
    catch(第二种异常){
        // 对于第二种异常的处理
    }
    finally{
        // 无论是否产生异常,无论异常是否被捕获,这里的代码都会得到执行
    }
    
  • 自定义异常类的一般形式

    // 自定义异常类
    class MyException extends Exception{
        public MyException() {
        }
        // message表示异常类的具体信息
        public MyException(String message) {
            super(message);
        }
    }
    
  • 生成,捕获和处理异常

    public class FullConstructors {
        public static void f() throws MyException {
            System.out.println("Throwing MyException from f()");
            // 抛出受检查的异常,此时要么捕捉(catch),要么声明该方法将抛出这个异常
            throw new MyException();
        }
        public static void g() throws MyException {
            System.out.println("Throwing MyException from g()");
            throw new MyException("Originated in g()");
        }
    
        public static void main(String[] args) {
            try {
                f();
            }
            // 由于捕捉了异常,因此程序可以继续往下执行
            catch (MyException e1) {
                // e1.printStackTrace()的作用为打印”从方法调用处到异常
                // 抛出处的方法调用序列(方法调用栈)
                // 同时,这里将方法调用序列的信息发送到了标准输出流,所以你会看到
                // 下面依次输出的情况,且在idea中输出内容都是白色的。默认是将方法调用
                // 序列的信息发送到标准错误流,这时控制台会先依次输出标准输出流的内容,再
                // 依次输出标准错误流的内容,且在idea中标准错误流(默认方法调用序列)是红色的
                e1.printStackTrace(System.out);
            }
            try {
                g();
            }
            catch (MyException e2) {
                e2.printStackTrace(System.out);
            }
        }
    }
    /* output
    Throwing MyException from f()
    exceptions.MyException(由基类Exception中默认的getMessage()方法得到)
    	at exceptions.FullConstructors.f(FullConstructors.java:21)
    	at exceptions.FullConstructors.main(FullConstructors.java:30)
    Throwing MyException from g()
    exceptions.MyException: Originated in g()
    	at exceptions.FullConstructors.g(FullConstructors.java:25)
    	at exceptions.FullConstructors.main(FullConstructors.java:36)
    *///
    
  • 捕获异常搭配日志工具的使用

    public class LoggingExceptions {
        // "LoggingExceptions"可以当做这个logger的名字
        private static Logger logger = Logger.getLogger("LoggingExceptions");
        static void logException(Exception e) {
            StringWriter trace = new StringWriter();
            // 将方法调用序列重定向到trace中
            e.printStackTrace(new PrintWriter(trace));
            // 将trace中的方法调用序列信息用严重级别(sever)的日志消息打印
            logger.severe(trace.toString());
        }
    
        public static void main(String[] args) {
            try {
                throw new NullPointerException();
            }
            catch (NullPointerException e) {
                // 将异常e的产生方法调用序列用日志打印出来
                logException(e);
            }
        }
    }
    /* output
    二月 27, 2022 11:07:19 下午 exceptions.LoggingExceptions logException
    严重: java.lang.NullPointerException
    	at exceptions.LoggingExceptions.main(LoggingExceptions.java:22)
    *///
    

    用日志打印的好处是会显示具体的时间,在哪个包下,日志的名称,日志的级别这些具体信息,有利于程序员的进一步调试。

  • 接收异常后重新抛出异常,重抛异常通常会交给上一级异常处理程序处理,如果想要更新重新抛出异常的信息(将新抛出点作为异常的发生地点)可通过fillInStackTrace()方法

  • 在异常捕获的过程中,finally字句是一定会执行的,那么通常使用finally字句是用来做什么的呢?有时候我们需要释放在try语句中出内存之外资源之外的其它的东西,如文件流,网络连接等,这些东西无论是否捕获异常或是在哪个catch语句中捕获都需要一定得到释放。因此就到了finally字句的用武之处,在新版本的JDK中是加入了try-with-resource语句的,它可以自动帮我们关闭文件流,资源等(实际上也是通过finally语句实现的,只不过编译器帮我们完成了,俗称“语法糖”。

2. 异常的限制

当覆盖方法时,被覆盖的方法只能抛出在基类方法中异常声明(throws)中的那些异常或其子类,这个限制很有用也很实际。当我们使用多态性质时,即基类对象引用->子类对象,向基类对象引用发送消息调用方法,实际上执行的子类中已覆盖的方法(因此子类方法抛出的异常应该被基类对象引用的方法抛出)。

基类的方法声明将抛出异常,但它实际上没有抛出异常,这个时候就强制用户去捕获它的覆盖类中可能抛出的该异常(说白了还是因为要满足多态的性质)。

异常限制对构造器不起作用,子类的构造器可以抛出任何异常,而不必只满足于基类所抛出的异常,但是子类构造器必须抛出基类构造器所抛出的异常(不能捕获基类构造器的异常,因为构造器中的super()语句必须是构造器中的第一句非注释语句)。这也是很显然易见的,我们不能通过基类引用调用子类构造器,因此不必满足于基类构造器的异常说明,但同时子类的构造器必须调用父类的构造器无论是显示的调用还是隐式调用,抛出的异常都需要处理。

注意:尽管在继承过程中,编译器会对异常说明做强制要求,但异常说明本身并不属于方法类型的一部分,方法类型是有方法的名字和参数的类型的组成的。因此,不能够根据异常说明来重载方法。此外,一个在基类方法中出现的异常说明,不一定会出现在子类该方法的异常说明中。

总结:日常使用Java开发时,我们大多时候是调用某个API,那个API会抛出异常,然后我们catch它,顺便打印一下方法调用序列。或者程序中有某个运行时bug,如数组越界,爆栈,空引用的方法调用等,程序会自动停止,JVM执行异常处理程序(打印方法调用序列)。这个性质可谓是我们做OJ的神器,再也不用担心Runtime Error了。只要到我们成为类库设计者时,我们的代码要被千千万万的程序员使用时,我们就要考虑某个方法在什么时候抛出什么异常。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
1. 什么是 Java 异常Java 异常是指程序执行期间可能发生的错误异常情况,例如除以零、数组越界、空指针引用等。当这些异常发生时,Java 虚拟机会抛出一个异常对象,并且程序的执行流程将被中断。 2. Java 异常处理机制有哪些关键字和语句? Java 异常处理机制包括以下关键字和语句: - try:用于包含可能会抛出异常的代码块。 - catch:用于捕获指定类型的异常,并在捕获到异常时执行相应的处理代码。 - finally:用于包含无论是否发生异常都需要执行的代码块。 - throw:用于抛出指定的异常对象。 - throws:用于声明可能会抛出指定类型异常的方法。 3. Java 中的异常分为哪几类? Java 中的异常分为两大类:Checked Exception 和 Unchecked Exception。 Checked Exception 是指在编译时就能够检查出来的异常,例如 IOException、ClassNotFoundException 等。程序必须显式地处理这些异常,否则编译不通过。 Unchecked Exception 是指在运行时才能检查出来的异常,例如 NullPointerException、ArrayIndexOutOfBoundsException 等。程序可以选择处理这些异常,但不处理也不会导致编译错误。 4. 请简要说明 try-catch-finally 的执行流程。 当程序执行到 try 块时,Java 会尝试执行其中的代码。如果在 try 块中抛出了异常,则会将异常对象传递给 catch 块进行处理。catch 块会匹配异常类型,如果匹配成功,则执行相应的处理代码。如果 catch 块处理异常后,程序需要继续执行,则会执行 finally 块中的代码。如果 finally 块中也抛出了异常,则该异常会覆盖 try 或 catch 块中的异常。 如果 try 块中没有抛出异常,则 catch 块不会被执行。如果 finally 块中抛出异常,则该异常会覆盖 try 块中的异常。 5. 什么是异常链? 异常链是指在处理异常时,将一个异常对象作为另一个异常的原因,并将它们组合成一个异常链。这样做的好处是,在抛出异常时可以同时传递多个异常信息,从而更加清晰地表示异常发生的原因。 6. 请简要说明 try-with-resources 的作用和使用方法。 try-with-resources 是 Java 7 中引入的语法,用于自动关闭实现了 AutoCloseable 接口的资源。在 try 块中声明需要使用的资源,Java 会在 try 块执行完毕后自动关闭这些资源,无需手动调用 close 方法。 try-with-resources 的语法如下: ``` try (Resource1 r1 = new Resource1(); Resource2 r2 = new Resource2()) { // 使用资源 } catch (Exception e) { // 处理异常 } ``` 7. 请简要说明 Java 中的文本 IO。 Java 中的文本 IO 主要包括两种类:Reader 和 Writer。Reader 用于读取字符流,而 Writer 用于写入字符流。 Java 中常用的 Reader 类包括 InputStreamReader、FileReader 和 BufferedReader,常用的 Writer 类包括 OutputStreamWriter、FileWriter 和 BufferedWriter。这些类提供了各种方法来读取和写入字符流,并且可以处理多种编码格式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ZW钟文

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

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

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

打赏作者

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

抵扣说明:

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

余额充值