Exception必知必会 三种try语句的用法

本文主要介绍了Exception的一些用法以及相应的注意点,并中i但介绍了三种try语句的用法,同时还增加了一些对于Exception的扩展,让大家在使用Exception的时候也能体会到Java语言为什么要设计Exception

文章目录

  • Exception和Error
  • Checked Exception和Unchecked Exception
  • Unchecked Exception
    • 异常的传递
    • 接口中定义的异常
    • 自定义异常
      • 自定义异常模板
      • 使用举例
    • try-catch语句
      • try-catch-finally
      • try-catch-catch
        • 写法一
        • 写法二
      • Try-with-resources
        • 介绍
        • 语法格式
        • 程序示例
    • Java中常见的异常
    • 异常中的注意点
  • 关于Java中的异常处理机制
  • Exception的扩展

Exception和Error

Exception 和 Error 都是继承了 Throwable 类,在 Java 中只有 Throwable 类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。在这里插入图片描述

  • Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。
  • Error 是指在正常情况下,不大可能出现的情况,绝大部分的 Error 都会导致程序(比如 JVM 自身)处于非正常的、不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常见的比如 OutOfMemoryError 之类,都是 Error 的子类。

所以我们应该重点关注的是Exception,本文也主要围绕Exception展开。

Checked Exception和Unchecked Exception

  • Checked Exception必须要求用语句进行处理,比如用try-catch包住或者是用throws抛出异常。
  • Unchecked Exception则不要求必须进行处理。Unchecked Exception继承自RuntimeException。需要注意的是Error 和RuntimeException都是Unchecked Exception的父类。但我们一般使用并重点关注的是RuntimeException,Error因为无法控制,也无法在程序中被恢复,也不重点关注。Unchecked Exception就是所谓的运行时异常,类似NullPointerException、ArrayIndexOutOfBoundsException之类,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,不要求在写代码的时候强制要求进行捕获或者抛出。
    下图为我们需要关注的一些异常,引用自极客时间专栏:《Java核心技术面试精讲》
    点此链接购买课程
    Exception必知必会 三种try语句的用法_第1张图片

Unchecked Exception

异常的传递

A方法调用B方法,B方法调用C方法,在执行C方法的时候遇到了异常,则会将异常往上抛,什么叫做往上抛?就是向调用C方法的上一级B方法抛,B方法接到这个异常,若这个异常能被catch住,也就说是catch里面的实例必须是这个异常的实例或者异常的父类的实例,异常被catch住以后就会停止往上抛异常,若没有catch住就会继续向A方法抛异常。若B方法catch住了异常,然后就会执行B方法未执行完的代码,然后B方法执行完以后就会出栈,然后执行A方法。C方法未执行完毕的代码不会再继续执行。

public class Test {
     
        public static void main(String[] args) throws ClassNotFoundException {
     
            makeCall();
        }

        private static void makeCall() throws ClassNotFoundException {
     
            // 检查异常需要强制处理,需要明确的throws或者catch
            System.out.println("调用开始");
            call2Exception();
            System.out.println("调用结束");
        }

        private static void call2Exception() throws ClassNotFoundException {
     
            System.out.println("Caller1.call2Exception开始");
            try {
     
                call3Exception();
            } catch (ClassNotFoundException ex) {
     
                System.out.println("got exception in Caller1: " + ex.getMessage());
            }
            System.out.println("Caller1.call2Exception结束");
        }

        private static void call3Exception() throws ClassNotFoundException {
     
            System.out.println("Caller2.call3Exception开始");
            callThrowException();
            System.out.println("Caller2.call3Exception结束");
        }

        private static void callThrowException() throws ClassNotFoundException {
     
            System.out.println("Caller3.callThrowException开始");
            Class.forName("com.neverland.Rabbit");
            System.out.println("Caller3.callThrowException结束");
        }
    }

执行结果如下图所示
Exception必知必会 三种try语句的用法_第2张图片
若不在call2Exception()方法中将call3的抛出的异常catch住,异常就会一直往上抛,直至该线程停止。
Exception必知必会 三种try语句的用法_第3张图片
需要注意的是,虽然我们能使用异常进行程序跳转,但是在程序设计的时候千万不要这么考虑。首先,异常本身就代表程序出了故障,用故障来实现正常的功能是不应该被考虑的,其次,异常的创建和处理是很耗费资源的,程序设计中如果用Exception来做跳转,这样的程序设计是有问题的。

接口中定义的异常

抽象方法中声明抛出异常是接口方法签名的一部分。也就是说,在实现该抽象方法的时候,不能超出抽象方法中定义的异常类型。实现方法可以不抛出异常,但若是要抛出异常,实现方法中抛出的异常必须是抽象方法中定义的异常或者是其子类。

自定义异常

自定义异常模板


public class MyException extends Exception {
     
    public MyException() {
     
    
    }

    //传入参数:异常的信息
    public MyException(String message) {
     
        super(message);
    }

    //传入参数:异常的信息,造成异常的异常
    public MyException(String message, Throwable cause) {
     
        super(message, cause);
    }

    //传入参数:造成异常的异常
    public MyException(Throwable cause) {
     
        super(cause);
    }
}

使用举例

MyException

public class MyException extends Exception {
     
    public MyException() {
     
        System.out.println("执行MyException()构造方法");
    }

    //传入参数:异常的信息
    public MyException(String message) {
     
        super(message);
        System.out.println("执行MyException(String message)构造方法");
    }

    //传入参数:异常的信息,造成异常的异常
    public MyException(String message, Throwable cause) {
     
        super(message, cause);
        System.out.println("执行MyException(String message, Throwable cause)构造方法");
    }

    //传入参数:造成异常的异常
    public MyException(Throwable cause) {
     
        super(cause);
        System.out.println("执行MyException(Throwable cause)构造方法");
    }
}
public class Main {
     
    public static void main(String[] args) {
     
        try{
     
            testMyException();
        } catch (MyException e) {
     
            e.printStackTrace();
        }
    }

    public static void testMyException() throws MyException {
     
        throw new MyException("这是我的自定义异常");
    }
}

打印结果:
Exception必知必会 三种try语句的用法_第4张图片

try-catch语句

try-catch-finally

  • finally里面的语句无论如何都是会被执行的,即便catch中已经return 了
  • 若catch中已经return了,可以认为finally语句会在catch return以后,后面的方法执行以前,再进行执行
  • 无论是因为catch中的return结束还是因为异常结束,finally语句都会执行
  • finally里面最好不要有return语句,finally里面的return语句会替换掉try或者是catch中return的值
  • 在finally里面给catch中的return的变量赋值是没有用的。比如catch中return了一个int类型的 变量len,然后在finally语句里面将len的值+1,这是没有任何意义的,return出去的变量值是不会发生改变的。
  • finally当然也遵循着程序的顺序执行,若try中没有抛出异常,catch就不会被执行,那么就按照程序的先后顺序来进行顺序执行。先执行try语句块中的内容,再执行finally语句块中的内容,再执行finally语句块后面的内容。如果在finally里面return 了,则后面的语句都是没办法被执行到的了,因为程序到这里便结束了。
try{
     
    
}
catch(Exception ex){
     
    //进行异常的代码,也可以在里面返回一个特殊的值,表示情况不对,有异常
}
finally{
     

}

try-catch-catch

写法一

注意:若采用写法一的写法,若第一个catch异常的实例对象已经包含了第二,第三个catch中的内容,当然就会报错。比如第一个catch的若是Exception,那后面的catch是不会被执行的,因为所有的异常都继承自Exception,第一个catch中捕获的异常已经包含了后面所有的异常,后面的catch已经没有了存在的意义。

所以,catch中的实例对象的关系只能是并行关系,即没有谁继承谁的说法,或者是从小到大的继承关系。

try {
     
    throwMultiException(0);
} catch (ClassNotFoundException e) {
     
    e.printStackTrace();
} catch (IOException e) {
     
    e.printStackTrace();
}

写法二

try {
     
    throwMultiException(0);
} catch (ClassNotFoundException | IOException e) {
     
    e.printStackTrace();
}

Try-with-resources

介绍

和资源有关的异常处理往往都比较繁复,尤其是有多个资源的时候,需要考虑如何在出现Exception的情况下把资源回收掉,关掉。这时候Try-with-resources就能很好的胜任这样的工作,在程序出现异常的时候能自动帮我们关闭资源。

语法格式

try(将需要关闭的资源放入括号里){
     

}catch(Exception ex){
     

}

程序示例

AutoCloseable是一个资源回收的接口,实现这个接口就可以让try语句自动关闭资源。

public String read() 方法中使用了随机数,模拟读取文件的时候出现的错误,一旦出现错误,就会抛出异常。若文件在读取过程中出现了异常,我们要关闭掉该文件,不然可能造成之前读取的文件丢失。

import java.io.IOException;
public class MyAutoClosableResource implements AutoCloseable {
     

    private String resName;
    private int counter;

    public MyAutoClosableResource(String resName) {
     
        this.resName = resName;
    }

    public String read() throws IOException {
     
        counter++;
        if (Math.random() > 0.1) {
     
            return "You got lucky to read from " + resName + " for " + counter + " times...";
        } else {
     
            throw new IOException("resource不存在哦");
        }
    }

    @Override
    public void close() throws Exception {
     
        System.out.println("资源释放了:" + resName);
    }
}
public class TryWithResource {
     
    public static void main(String[] args) {
     
        //TODO try括号里面的内容是创建的资源
        //todo res1和res2都是两个文件资源,在while循环里不停地读资源,但是不知道什么时候就会出错,一旦出错了要把这两个文件正常的关闭掉
        try (
             MyAutoClosableResource res1 = new MyAutoClosableResource("res1");
             MyAutoClosableResource res2 = new MyAutoClosableResource("res2")
        ) {
     
            while (true) {
     
                System.out.println(res1.read());
                System.out.println(res2.read());
            }
        } catch (Exception e) {
     
            e.printStackTrace();
        }
    }
}

可以看到,在Main方法里面我们没有调用close方法,但是在程序执行出现异常时中却执行了close方法释放资源/这就是try-with-resource这个语法带来的功能。
Exception必知必会 三种try语句的用法_第5张图片

Java中常见的异常

  • NullPointerException:当尝试用为null的引用去调用方法的时候会出现这个问题。
  • IndexOutOfBoundException:数组索引出界
  • ClassCastException:当把一个类强行转化为另一个类的时候,如果两个类根本没有关系的时候就会出错
  • ClassNotFoundException:找不到类
  • IOException:和IO操作有关的Exception

前三个是Unchecked Exception,后两个是Checked Exception

异常中的注意点

  • 异常中最重要的信息是:类型错误信息出错时的调用栈
  • catch语句是根据异常类型匹配来捕捉相应类型的异常的,如果类型不匹配,catch语句是不会执行的,异常会继续抛出。
  • catch(Throwable th)当然就可以捕获所有的异常,包括Error,但是建议最大只捕获Exception
  • 如果catch一个其实并没有在try语句块中被抛出的checked exception,Java程序会报错,因为Java明确的知-道这个类型的异常不会发生。
  • 如果catch一个unchecked exception,Java程序不会报错,即使try语句块不会抛出这样的异常
  • 如果throws一个其实并没有被抛出的Checked exception或者Unchchecked exception,Java程序不会报错

关于Java中的异常处理机制

Java 的异常处理机制会带来两个相对昂贵的开销:

  • try-catch 代码段会产生额外的性能开销,或者换个角度说,它往往会影响 JVM 对代码进行优化,所以建议仅捕获有必要的代码段,尽量不要一个大的 try 包住整段的代码;与此同时,利用异常控制代码流程,也不是一个好主意,远比我们通常意义上的条件语句(if/else、switch)要低效。
  • Java 每实例化一个 Exception,都会对当时的栈进行快照,这是一个相对比较重的操作。如果发生的非常频繁,这个开销可就不能被忽略了。

Exception的扩展

Checked exceptions: Java’s biggest mistake
原文链接点此
译文链接点此

养成一个勤于记录的习惯,记录,让生活更美好。

你可能感兴趣的:(Java,java,设计模式)