推荐阅读文章
- JavaSE系列1️⃣《JavaSE系列教程》
- MySQL系列2️⃣《MySQL系列教程》
- JavaWeb系列3️⃣《JavaWeb系列教程》
- SSM框架系列4️⃣《SSM框架系列教程》
本博客知识点收录于《JavaSE系列教程》—>✈️11【泛型、Map、异常】✈️
文章目录
- 异常
- 1.1 异常概述
- 1.1.1 什么是异常
- 1.1.2 异常体系
- 1.1.3 异常分类
- 1.2 异常的处理
- 1.2.1 异常的捕获
- 1.2.2 异常的常用方法
- 1.2.3 异常的抛出
- 1.2.4 声明异常
- 1)运行时异常
- 2)编译时异常
- 1.3 自定义异常
- 1.4 方法的重写与异常
异常
1.1 异常概述
1.1.1 什么是异常
程序运行过程中出现的问题在Java中被称为异常,异常本身也是一个Java类,封装着异常信息;我们可以通过异常信息来快速定位问题所在;我们也可以针对性的定制异常,如用户找不到异常、密码错误异常、页面找不到异常、支付失败异常、文件找不到异常等等…
当程序出现异常时,我们可以提取异常信息,然后进行封装优化等操作,提示用户;
注意:语法错误并不是异常,语法错了编译都不能通过(但Java有提供编译时异常),不会生成字节码文件,根本不能运行;
默认情况下,出现异常时JVM默认的处理方式是中断程序执行,因此我们需要控制异常,当出现异常后进行相应修改,提供其他方案等操作,不要让程序中断执行;
我们之前有见到过很多的异常:
- 空指针异常:
java.lang.NullPointerException
String str=null;str.toString();
- 数字下标越界异常:
java.lang.ArrayIndexOutOfBoundsException
int[] arr = {1, 3, 4};System.out.println(arr[3]);
- 类型转换异常:
java.lang.ClassCastException
class A {}class B extends A {}class C extends A {}public static void main(String[] args) { A a = new B(); C c = (C) a;}
- 算数异常:
java.lang.ArithmeticException
int i=1/0;
- 日期格式化异常:
java.text.ParseException
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");Date parse = sdf.parse("2000a10-24");
1.1.2 异常体系
Java程序运行过程中所发生的异常事件可分为两类:
- Error:表示严重错误,一般是JVM系统内部错误、资源耗尽等严重情况,无法通过代码来处理;
- Exception:表示异常,一般是由于编程不当导致的问题,可以通过Java代码来处理,使得程序依旧正常运行;
Tips:我们平常说的异常指的就是Exception;因为Exception可以通过代码来控制,而Error一般是系统内部问题,代码处理不了;
1.1.3 异常分类
异常的分类是根据在编译器检查异常还是在运行时检查异常;
- 编译时期异常:在编译时期就会检查该异常,如果没有处理异常,则编译失败;
- 运行时期异常:在运行时才出发异常,编译时不检测异常;
Tips:在Java中如果一个类直接继承与Exception,那么这个异常将是编译时异常;如果继承与RuntimeException,那么这个类是运行时异常。即使RuntimeException也继承与Exception;
- 编译时异常举例:
public class Demo { public static void main(String[] args) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); Date date = sdf.parse("2000-10-24"); }}
- 运行时异常:
public class Demo { public static void main(String[] args) { int i = 1 / 0; }}
1.2 异常的处理
Java程序的执行过程中如出现异常,会自动生成一个异常类对象,该异常对象将被提交给Java运行时系统(JVM),这个过程称为抛出(throw)异常。
如果一个方法内抛出异常,该异常会被抛到调用方法中。如果异常没有在调用方法中处理,它继续被抛给这个调用方法的调用者。这个过程将一直继续下去,直到异常被处理。这一过程称为捕获(catch)异常。如果一个异常回到main()方法,并且main()也不处理,则程序运行终止。
流程如下:
1.2.1 异常的捕获
异常的捕获和处理需要采用 try 和 catch 来处理,具体格式如下:
- 1)
try...catch(){}
:
try {// 可能会出现异常的代码} catch (Exception1 e) {// 处理异常1} catch (Exception2 e) {// 处理异常2} catch (ExceptionN e) {// 处理异常N}
Tips:后处理的异常必须是前面处理异常的父类异常;
- 2)
try...catch(){}...finally{}
:
try {// 可能会出现异常的代码} catch (Exception1 e) {// 处理异常1} catch (Exception2 e) {// 处理异常2} catch (ExceptionN e) {// 处理异常N} finally {// 不管是否出现异常都会执行的代码}
- 3)
try...finally{}
:
try {// 可能会出现异常的代码} finally {// 不管是否出现异常都会执行的代码}
示例代码:
package com.dfbz.demo01;public class Demo01 { public static void main(String[] args) { method(); System.out.println("程序终止我就不能执行了~"); } public static void method() { try { String str = null; System.out.println(str.toString()); } catch (Exception e) { System.out.println("执行代码出现异常了!"); } finally { System.out.println("释放一些资源..."); } }}
运行结果:
tips:try…catch语句是可以单独使用的;即:不要finally代码块;
需要注意的是:如果finally有return语句,则永远返回finally中的结果。我们在开发过程中应该避免该情况;
- 示例代码:
package com.dfbz.demo02;public class Demo02 { public static void main(String[] args) { int result = method(); System.out.println("method方法的返回值: " + result); } public static int method() { try { int i = 10; return 1; } catch (Exception e) { e.printStackTrace(); return 2; } finally { return 3; // 不管是否出现异常都是返回3 } }}
1.2.2 异常的常用方法
在Throwable类中具备如下几个常用异常信息提示方法:
public void printStackTrace()
:获取异常的追踪信息;
包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。public String getMessage()
:异常的错误信息;
异常触发被抓捕时,异常的错误信息都被封装到了catch代码块中的Exception类中了,我可以通过该对象获取异常错误信息;
示例代码:
package com.dfbz.demo01;public class Demo02 { public static void main(String[] args) { method(); System.out.println("我继续执行~"); } public static void method() { try { int i=1/0; } catch (Exception e) { System.out.println("异常的错误信息: " + e.getMessage()); // 打印异常的追踪信息 e.printStackTrace(); } }}
运行结果如下:
异常的追踪信息可以帮助我们追踪异常的调用链路,一步一步找出异常所涉及到的方法,在实际开发非常常用;
1.2.3 异常的抛出
我们已经学习过出现异常该怎么抓捕了,有时候异常就当做提示信息一样,在调用者调用某个方法出现异常后及时针对性的进行处理,目前为止异常都是由JVM自行抛出,当然我们可以选择性的自己手动抛出某个异常;
Java提供了一个throw关键字,它用来抛出一个指定的异常对象;抛给上一级;
Tips:自己抛出的异常和JVM抛出的异常是一样的效果,都要进行处理,如果是自身抛出的异常一直未处理,最终抛给JVM时程序一样会终止执行;
语法格式:
throw new 异常类名(参数);
示例:
throw new NullPointerException("调用方法的对象是空的!");throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围");
示例代码:
package com.dfbz.demo01;public class Demo03 { public static void main(String[] args) { method(null); System.out.println("我还会执行吗?"); } public static void method(Object object) { if (object == null) { // 手动抛出异常(抛出异常后,后面的代码将不会被执行) throw new NullPointerException("这个对象是空的!不能调用方法!"); } System.out.println(object.toString()); }}
运行结果:
手动抛出的异常和JVM抛出的异常是一个效果,也需要我来处理抛出的异常;
修改代码:
package com.dfbz.demo01;public class Demo03 { public static void main(String[] args) { try { method(null); } catch (Exception e) { System.out.println("调用method方法出现异常了"); } System.out.println("我还会执行吗?"); } public static void method(Object object) { if (object == null) { // 手动抛出异常(抛出异常后,后面的代码将不会被执行) throw new NullPointerException("这个对象是空的!不能调用方法!"); } System.out.println("如果出现了异常我是不会执行了,你能执行到这里说明没有异常"); System.out.println(object.toString()); }}
运行结果:
1.2.4 声明异常
1)运行时异常
在定义方法时,可以在方法上声明异常,用于提示调用者;
Java提供throws关键字来声明异常;关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常);
语法格式:
... 方法名(参数) throws 异常类名1,异常类名2…{ }
代码示例:
package com.dfbz.demo01;import java.text.ParseException;public class Demo04 { public static void main(String[] args) { // 可以处理,也可以不处理 method("你好~"); // 编译时异常必须处理 try { method2("hello~"); } catch (ParseException e) { System.out.println("出现异常啦!"); } } // 调用此方法可能会出现空指针异常,提示调用者处理异常 public static void method(Object obj) throws NullPointerException { System.out.println(obj.toString()); } // 抛出的不是运行时异常,调用者调用该方法时必须处理 public static void method2(Object obj) throws ParseException { System.out.println(obj.toString()); } // 也可以同时抛出多个异常 public static void method3(Object obj) throws ClassCastException,ArithmeticException { System.out.println(obj.toString()); }}
2)编译时异常
在声明和抛出编译时异常时需要注意如下几点:
- 1)如果是抛出(throw)编译是异常,那么必须要处理,可以选择在方法上声明,或者try…catch处理
- 2)如果调用的方法上声明(throws)了编译时异常,那么在调用方法时就一定要处理这个异常,可以选择继续网上抛,也可以选择try…catch处理
- 示例代码:
package com.dfbz.demo04;/** * @author lscl * @version 1.0 * @intro: */public class Demo02 { public static void main(String[] args) throws Exception{ // 如果调用的方法上声明了编译时异常,那么在调用方法时就一定要处理这个异常,可以选择继续往上抛,也可以选择try..catch处理 test(500); } // 如果方法声明了编译时异常,那么在调用方法时就一定要处理这个异常 public static void test(Integer num) throws MyException{ if (num == 100) { // 如果是抛出编译是异常,那么必须要处理,可以选择在方法上声明,或者try...catch处理 throw new MyException(); /*try { throw new MyException(); } catch (MyException e) { e.printStackTrace(); }*/ } }}// 直接继承与Exception,那么这个异常是一个编译时异常class MyException extends Exception {}
1.3 自定义异常
我们说了Java中不同的异常类,分别表示着某一种具体的异常情况,那么在开发中总是有些异常情况是Java中没有定义好的,此时我们根据自己业务的异常情况来定义异常类。
我们前面提到过异常分类编译时异常和运行时异常:
- 1)继承于
java.lang.Exception
的类为编译时异常,编译时必须处理; - 2)继承于
java.lang.RuntimeException
的类为运行时异常,编译时可不处理; - 自定义用户名不存在异常:
package com.dfbz.demo04;public class UsernameNotFoundException extends RuntimeException { /** * 空参构造 */ public UsernameNotFoundException() { } /** * @param message 表示异常提示 */ public UsernameNotFoundException(String message) { // 调用父类的构造方法 super(message); }}
- 测试类:
package com.dfbz.demo04;public class Demo01 { // 定义内置账户 public static String[] users = {"xiaohui", "xiaolan", "xiaoliu"}; public static void main(String[] args) { findByUsername("abc"); } public static void findByUsername(String username) throws UsernameNotFoundException { for (String user : users) { if (username.equals(user)) { System.out.println("找到了: " + user); return; } } // 用户名没找到 throw new UsernameNotFoundException("没有用户: " + username); }}
运行结果:
1.4 方法的重写与异常
- 1)子类在重写方法时,父类方法没有声明编译时异常,则子类方法也不能声明编译时异常;
需要注意的是:运行时异常没有这个规定;也就是子类在重写父类方法时,不管父类方法是否有声明异常,子类方法都可以声明异常;
package com.dfbz.demo05;/** * @author lscl * @version 1.0 * @intro: */public class Demo01 { public static void main(String[] args) throws Exception { }}// 定义一个编译时异常class MyException extends Exception {}class Fu { public void method() { } public void method2() { }}class Zi extends Fu { // 语法通过,运行时异常随意 public void method() throws NullPointerException { } // 语法报错,父类方法没有声明编译时异常,那么子类重写方法时也不能声明编译时异常 public void method2() throws MyException { }}
- 2)同样是在编译时异常中,在子类重写父类方法时,子类不可以声明比父类方法大的编译时异常;
package com.dfbz.demo06;/** * @author lscl * @version 1.0 * @intro: */public class Demo01 { public static void main(String[] args) throws Exception { }}// 定义一个编译时异常class MyException extends Exception {}class Fu { public void method() throws NullPointerException{ } public void method2()throws MyException{ }}class Zi extends Fu { // 运行时异常没有问题 public void method() throws RuntimeException { } // 语法报错,如果是编译时异常,子类在重写父类方法时,不可以抛出比父类大的编译时异常 public void method2() throws Exception { }}