Throwable类

不管现实多么惨不忍睹,都要持之以恒地相信,这只是黎明前短暂的黑暗而已。不要惶恐眼前的难关迈不过去,不要担心此刻的付出没有回报,别再花时间等待天降好运。真诚做人,努力做事!你想要的,岁月都会给你。Throwable类,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

7.Throwable
7.1.概念
  • 异常 :指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。
    在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。
  • 异常的根类是 java.lang.Throwable ,其下有两个子类:java.lang.Errorjava.lang.Exception ,平常所说的异常指 java.lang.Exception
  • Throwable体系:
    • Error:严重错误Error,无法通过处理的错误,只能事先避免。
    • Exception:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。

一些个人理解:

  • 异常处理的根本目的,还是想让程序继续运行下去,比如,某一块代码出现了异常,但是我想看一下后面的代码运行情况,就可以先处理掉异常,让后面的程序继续运行。
  • 某种情况下,用户输入的数据不合法,而我们的程序并不能中断,仍然要继续执行下去,因此我们可以throw一个异常,并在调用方法中处理这个异常,使程序继续执行。
  • 异常处理有两种方法,throws和try…catch,如果我们用throws持续向上抛出,最后交给JVM,一样会被中断处理(只有极少数抛出异常不会中断程序的运行,比如CloneNotSupportedException),结果到最后还是要try…catch来处理,因为我们想让程序继续运行下去,否则程序一定会被JVM中断。但是这里有个特殊情况,对于编译时期的异常,如果我们不处理,程序是没有办法Run的,所以编译时期的异常我们必须及时来处理。
  • 为什么main方法抛出CloneNotSupportedException异常,JVM没有中断程序呢?因为,并没有出现异常,throws声明在方法名称后,只是说,可能会出现异常,告诉程序员做好处理的准备,同时,目的也是解决编译期异常,否则程序无法Run,这是clone()所必需的规定,而对于大多数情况来说,throws一个异常,是确定了会抛出一个异常,因此我们应该尽力避免main方法抛出异常,最迟要在main方法自己捕捉异常。
7.2.常用方法
  • public void printStackTrace() :打印异常的详细信息。异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。
  • public String getMessage() :获取发生异常的原因。提示给用户的时候,就提示错误原因。
  • public String toString() :获取异常的类型和异常描述信息。
  • printStackTrace(PrintStream s) :使用IO流,将异常内容保存在日志文件中,以便查阅(过时的日志处理方式)
7.3.异常分类
  • 异常(Exception)的分类:根据在编译时期还是运行时期去检查异常
    • 编译时期异常:checked异常。在编译时期,就会检查,如果没有处理异常,则编译失败。(如日期格式化异常)
    • 运行时期异常:runtime异常。在运行时期,检查异常.在编译时期,运行异常不会编译器检测(不报错)。(如数学异常)
7.4.异常处理
  • Java中,异常处理的机制有两种,自己解决或者向上抛出,即某个方法发生了异常,如果该方法解决不了,那么就向上抛给调用自己的方法,直到main方法也解决不掉后,那么就抛给JVM处理,JVM会中断程序,将异常打印在控制台上。中断后的语句都无法执行。
7.4.1.抛出异常throw
  • 在java程序中,我们需要判断用户输入数据的合法性,等等,因此,Java提供了一个throw关键字,它用来抛出一个指定的异常对象。
    1. 创建一个异常对象。封装一些提示信息(信息可以自己编写)。
    2. 需要将这个异常对象告知给调用者。怎么告知呢?怎么将这个异常对象传递到调用者处呢?通过关键字throw就可以完成。throw 异常对象。
      throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行。
throw new 异常类名(参数);

//例如:
throw new NullPointerException("要访问的arr数组不存在");
throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围");

//throw异常 举例
public class ThrowDemo {
    public static void main(String[] args) {
        //创建一个数组
        int[] arr = {2,4,52,2};
        //根据索引找对应的元素
        int index = 4;
        int element = getElement(arr, index);
        System.out.println(element);
        System.out.println("over");
    }
    /*
    * 根据 索引找到数组中对应的元素
    */
    public static int getElement(int[] arr,int index){
    //判断 索引是否越界
    if(index<0 || index>arr.length‐1){
    /*
    判断条件如果满足,当执行完throw抛出异常对象后,方法已经无法继续运算。
    这时就会结束当前方法的执行,并将异常告知给调用者。这时就需要通过异常来解决。
    */
    	throw new ArrayIndexOutOfBoundsException("角标越界");
    }
        int element = arr[index];
        return element;
    }
}
7.4.2.声明异常throws
  • 声明异常:将问题标识出来,报告给调用者。如果方法内通过throw抛出了编译时异常,而没有捕获处理(捕获处理在笔记后边),那么必须通过throws进行声明,让调用者去处理。
  • 关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常)。
    • 运行时异常会自动向上抛出,不用我们手动throws
      • 我们只需要手动throws编译时异常
    • 如果方法抛出一个编译时异常,可以在语法层面,强制要求方法调用者处理该异常
    • 异常列表可以是多个异常类,但是注意用逗号隔开
    • 列表中出现的异常如果有父子关系,那么编译器只会强制要求处理父类,所以尽量抛出同级别的异常
//修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{ }

public class ThrowsDemo {
    public static void main(String[] args) throws IOException {
    	read("a.txt");
    }
    public static void read(String path)throws FileNotFoundException, IOException {
        if (!path.equals("a.txt")) {//如果不是 a.txt这个文件
            
            //如果不是a.txt这个路径,那么我认为该文件不存在,假设它是一个异常。
            throw new FileNotFoundException("文件不存在");
        }
        if (!path.equals("b.txt")) {
        	throw new IOException();
        }
    }
}
7.4.3.捕获异常
  • 即使用try…catch关键字来捕获异常,那么就会有两种情况:
    • 若匹配成功,catch中写的异常对象就会接收JVM抛出的异常对象,程序未被JVM中断,仍然能够继续执行。
    • 若匹配失败,那么程序依然会自动向上抛出异常,直到JVM默认处理
  • 单分支try…catch语法如下:
try {
 //可能出现异常的,正常的代码逻辑
} catch(要捕捉的异常对象) {
 //每一个catch分支对应一个异常处理器
 //在catch分支中处理具体类型的代码异常
}
//其中catch的括号里可以添加多个异常类型,但是不建议这么做
//无论能够匹配多少种异常类型,始终都只有一个异常对象被接收,对象名只写一个
catch(要捕捉的异常类型1 | 要捕捉的异常对类型2 | 要捕捉的异常类型3 对象名...){}

注:

​ try代码块中某个位置产生了异常,那么try中的代码就不继续执行了,也就是说try当中要么不产生异常,要么只会产生一个异常。

  • 多分支try…catch语法如下:
try {
 //可能出现异常的,正常的代码逻辑
} catch(要捕捉的异常对象1) {
 //每一个catch分支对应一个异常处理器
 //在catch分支中处理具体类型的代码异常
}catch(要捕捉的异常对象2) {
 //每一个catch分支对应一个异常处理器
 //在catch分支中处理具体类型的代码异常
}
  • 对于多分支语法,匹配流程如下:
    • 根据实际的异常对象的类型,和catch中声明的异常类型,从上到下一次做类型匹配。
    • 一旦通过类型匹配,发现实际异常对象的类型和catch中的异常对象类型匹配,就把该异常对象交给这个catch分支进行处理(异常处理器)。
    • 没有相匹配catch代码块的异常,那么程序依然会自动向上抛出异常,直到 JVM 默认处理。

一些多分支匹配的注意事项:

  1. 多分支的异常处理的执行,有点类似于多分支if-else的执行,一次匹配,只会执行多个catch分支中的一个
  2. 如果多个catch中处理的是毫无关系的异常,那么catch的顺序并不需要特别注意
  3. 如果多个catch中处理的异常有父子关系,那么就必须要注意了
    • 如果父类异常写在了上面,那么子类异常的catch分支就永远没有机会执行了,并且会报错
      • 应该把具体子类放在catch分支的上面作类型匹配,父类放在后面作兜底
      • 另外,如果子类在上边,那么仍然会执行父类的异常语句
  4. catch()括号当中,尽量不要写Exception这种大而宽泛的异常,而是应该写具体的异常,越具体越好,这样对处理异常有帮助,能够清晰定位异常的类型
  • try…catch…finally语句
    • finally的特点
      1. 无论try中是否发生异常,都会执行
      2. try-catch代码中有return也不能阻止它
      3. 特殊情况:在执行到finally之前jvm退出了:System.exit(0)

一些finally语句的注意事项:

  1. finally一般用于释放资源,常用于IO流操作和数据库操作中。

  2. 若try代码块里有return语句,那么应仍然应该先执行finally语句,再继续执行return。

    如果finally中没有return,那么执行完finally后再回到try中执行return语句。

    如果finally中也有return,那么执行完finally语句中的return后,不会再回到try中。

    执行完return语句后,返回值无论怎么修改,都不会改变打印结果。

  • Example:
//方法名(形参列表) throws 异常列表{}

public class TryCatchDemo {
    public static void main(String[] args) {
        try {
        	read("a.txt");
        } catch (FileNotFoundException e) {
        	//抓取到的是编译期异常 抛出去的是运行期
        	throw new RuntimeException(e);
        } finally {
        	System.out.println("不管程序怎样,这里都将会被执行。");
        }
        	System.out.println("over");
    }
        /*
        *
        * 我们当前的这个方法中,有编译期异常
        */
    public static void read(String path) throws FileNotFoundException {
        if (!path.equals("a.txt")) {//如果不是 a.txt这个文件

            //如果不是a.txt这个路径,那么我认为该文件不存在,假设它是一个异常。
            throw new FileNotFoundException("文件不存在");
    	}
    }
}
7.5.自定义异常类
  • 在开发中根据自己业务的异常情况来定义异常类,比如年龄负数问题,考试成绩负数问题,等等。
    • 继承自Exception:编译时要检查
    • 继承自RuntimeException:编译不需要检查
// 业务逻辑异常
public class RegisterException extends Exception {
    /**
    * 空参构造
    */
    public RegisterException() {}
    
    /**
    *
    * @param message 表示异常提示
    */
    public RegisterException(String message) {
    	super(message);
    }
}

//模拟登陆操作,使用数组模拟数据库中存储的数据,并提供当前注册账号是否存在方法用于判断。
public class Demo {
    // 模拟数据库中已存在账号
    private static String[] names = {"bill","hill","jill"};
    public static void main(String[] args) {
		//调用方法
        try{
			// 可能出现异常的代码
            checkUsername("nill");
            System.out.println("注册成功");//如果没有异常就是注册成功
        }catch(RegisterException e){
			//处理异常
            e.printStackTrace();
        }
    }
    //判断当前注册账号是否存在
	//因为是编译期异常,又想调用者去处理 所以声明该异常
    public static boolean checkUsername(String uname) throws RegisterException {
        for (String name : names) {
            if(name.equals(uname)){//如果名字在这里面 就抛出登陆异常
                throw new RegisterException("亲"+name+"已经被注册了!");
            }
        }
        return true;
    }
}
7.6.方法覆盖中的异常列表匹配问题
  • 首先,异常说明属于方法声明的一部分,紧跟在形式参数列表之后,方法的声明中加了throws关键字表示所有要抛出的潜在异常类型后,方法在重写的时候也会发生一些变化。

  • 总体上的原则是:子类中的覆盖方法,不能比父类中的方法抛出更多异常

  • 如果子父类方法,完全抛出相同的异常,允许进行方法的重写

  • 如果父类方法没有抛出异常,子类重写方法,要么也不抛出异常,要么就只能抛出运行时异常(本身就是自动的,不算多)

  • 如果父类方法抛了异常,那么

    • 子类重写方法可以选择完全不抛出异常
    • 如果父类方法抛出的是RuntimeException,那么子类重写方法也只能抛出RuntimeException
      • 种类不限制,允许类型不同
      • 父类方法抛出一个RuntimeException子类,子类方法重写可以是RuntimeException
    • 如果父类方法抛出的是编译时异常,那么子类重写方法
      • 可以抛出相同的编译时异常,但不能抛出不同的编译时异常
      • 抛出所有运行时异常
      • 不可以抛出Exception
    • 如果父类方法直接抛出Exception
      • 那么子类重写方法就可以抛出任何异常了
  • 我们其实并不需要特别记忆这些规则,实际开发中,我们并不是像老师上课一样,需要一点不能出错。我们可以不断的尝试,然后最终提交出一份正确的代码。这样,我们仍然是一名优秀的Java开发工程师。如果面试中被问到,建议直接说子类中的覆盖方法,不能比父类中的方法抛出更多异常即可。

  • 建议在开发中,子父类重写方法拥有一致的抛出异常列表,避免自找麻烦。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/181083.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!