异常
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 {
}
}
转载:https://blog.csdn.net/Bb15070047748/article/details/129244552