异常
java.lang.Exception 类继承自 Throwable
1 异常的定义
- 异常就是程序在编译或运行期间,所产生的一种不正常的结果。
- 异常指程序在运行过程中出现的非正常现象,例如用户输入错误、除数为0、需要处理的文件不存在、数组下标越界等。
- 在Javca的异常处理机制中,引进了很多用来描述和处理异常的类,称为异常类。
- 异常类定义中包含了改类异常的信息和异常进行处理的方法。
- 所谓异常处理,就是指该程序在出现问题时依然可以正确的执行完。
- 异常对程序产生的影响:当发生异常的时候,程序就会终止,异常代码之后的程序将不再执行。
2 Java中异常的体系结构
异常都是从 Throwable 类派生出来的,而 Throwable 类是直接从 Object 类继承而来。
2.1 异常的分类(通常)
- Error:系统内部错误,这类错误由系统进行处理,程序本身无需捕获处理。
- Exception:可以处理的异常。
- RuntimeException:可以捕获,也可以不捕获的异常。
- 继承Exception的其他类:必须捕获,通常在API中会说明这些方法抛出哪些异常。
-
运行时异常:(父类Exception)
- ClassCastException 错误的数据类型转换
- IndexOutOfBoundsException list集合索引越界
- ArrayIndexOutOfBoundsException 数组访问越界(继承自上面)
- NullPointerException 空指针异常
- ArithmeticException 算数异常
- ArrayStoreException 数据存储异常,操作数组时类型不一致
- BufferOverflowException 字节溢出异常—IO流操作
- InputMismatchException 输入不匹配异常
- NumberFormatException 数字格式异常
-
编译时异常:
- ClassNotFoundException 类找不到异常
- FileNotFoundException 编译文件夹中找不到,就是发布到tomcat中的,不是工程中
- SQLException 提供有关数据库访问错误或其他错误的信息的异常。( 比如SQL语句写错,访问的表不存在,连接数据库失败等等)
- IOexception IO流异常。一般在读写数据的时候会出现这种问题。
- EOFException 输入过程中意外到达文件或流的末尾时,抛出此异常。
2.2 常见的RuntimeException
- 错误的类型转换
public class ThrowTest {
public static void main(String[] args) {
//错误的类型转换
Object obj = new String();
Integer inte = (Integer)obj;
}
}
Exception in thread "main" java.lang.ClassCastException
- 数组访问越界
public class ThrowTest {
public static void main(String[] args) {
//数组访问越界
int[] arr = new int[5];
System.out.println(arr[6]);
}
}
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
- 访问null指针
public class ThrowTest {
public static void main(String[] args) {
//访问null指针
String str = null;
System.out.println(str.equals("java"));
}
}
Exception in thread "main" java.lang.NullPointerException
- 算术异常
public class ThrowTest {
public static void main(String[] args) {
//算数异常
System.out.println(1/0);
}
}
Exception in thread "main" java.lang.ArithmeticException
3 异常的处理
Java 中对异常的处理提供了一种异常处理模型:抓抛模型。
编译器异常继承:Exception
运行时异常继承:RuntimeException
3.1 捕获异常
try{
包含有可能发生异常的代码
}catch(异常类型 变量){ //捕获
针对这种异常的处理
}finally{
无论程序是否发生异常,都需要执行的代码
}
实例1:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ThrowTest {
public static void main(String[] args){
// 解析异常 ParseException
String strDate = "2020-12-23 " ;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = null; // 有可能发生异常,必须处理,不处理程序无法正常执行
try {
date = sdf.parse(strDate);
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(date);
}
}
运行结果:
java.text.ParseException: Unparseable date: "2020-12-23 "
at java.base/java.text.DateFormat.parse(DateFormat.java:395)
at ThrowDemo.ThrowTest.main(ThrowTest.java:14)
null
通过catch捕获异常,并对异常做出相应的处理,这样就可以保证程序继续执行。
在捕获异常时,可以有多个catch:
我们将可能出现异常的语句放到try{ }中
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ThrowTest {
public static void main(String[] args){
//运行时异常
int[] arr = new int[5];
//空指针异常
String str = null;
//类型转换异常
Object obj = new String();
//算术异常
// 解析异常 ParseException
String strDate = "2020-12-23 " ;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = null; // 有可能发生异常,必须处理,不处理程序无法正常执行
try {
System.out.println(arr[5]);
System.out.println(str.equals("abc"));
Integer iter = (Integer)obj;
System.out.println(1/0);
date = sdf.parse(strDate);
} catch (ParseException e) {
System.out.println("进行了异常处理");
e.printStackTrace();
}catch (ArrayIndexOutOfBoundsException ae){
System.out.println("处理数组下标越界异常");
}catch(NullPointerException ne){
System.out.println("处理空指针异常");
}
System.out.println(date);
System.out.println("程序执行结束");
}
}
运行结果:
处理数组下标越界异常
null
程序执行结束
从运行结果中可以看出,程序在处理完数组下标越界之后,try{ } 中的其他异常不再捕获。
多异常捕获的写法:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ThrowTest {
public static void main(String[] args){
//运行时异常
int[] arr = new int[5];
//空指针异常
String str = null;
//类型转换异常
Object obj = new String();
//算术异常
// 解析异常 ParseException
String strDate = "2020-12-23 " ;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = null; // 有可能发生异常,必须处理,不处理程序无法正常执行
try {
System.out.println(arr[5]);
System.out.println(str.equals("abc"));
Integer iter = (Integer)obj;
System.out.println(1/0);
date = sdf.parse(strDate);
} catch (ParseException | ArrayIndexOutOfBoundsException | NullPointerException e) //仅限于jdk7以上
{
System.out.println("进行了异常处理");
//e.printStackTrace();
}
System.out.println(date);
System.out.println("程序执行结束");
}
}
运行结果:
进行了异常处理
null
程序执行结束
通过异常的多态来捕获(因为Exception为这些异常的父类,所以可以用来替代):
try {
System.out.println(arr[5]);
System.out.println(str.equals("abc"));
Integer iter = (Integer)obj;
System.out.println(1/0);
date = sdf.parse(strDate);
} catch (Exception e) {
System.out.println("进行了异常处理");
//e.printStackTrace();
}
这样写是错误的:
catch (ArrayIndexOutOfBoundsException | NullPointerException | Exception e) {
System.out.println("进行了异常处理");
//e.printStackTrace();
}
但是可以这样写:
catch (ParseException e) {
System.out.println("进行了异常处理");
//e.printStackTrace();
}catch (NullPointerException ne){
}catch (ArrayIndexOutOfBoundsException ae){
}catch (Exception ee){
}
在处理异常时,并不要求抛出的异常同 catch 所声明的异常完全匹配,子类的对象也可以匹配父类的处理程序。比如异常 A 继承于异常 B,那么在处理多个异常时,一定要将异常 A 放在异常 B 之前捕获,如果将异常 B 放在异常 A 之前,那么将永远匹配到异常 B,异常 A 将永远不可能执行,并且编译器将会报错。
捕获异常:将有可能发生异常的代码写在try块中,当发生异常的时候,就会执行相应的catch块的内容,可以保证异常处理之后的代码的正常执行,从而使得程序可以正常终止。
在程序设计时,只需将有可能发生异常的代码放在try块中,而不要将没有异常发生的代码添加到try,这样会影响程序的执行的效率。
异常信息的分析:
异常信息的描述(一般用printStackTrace()):
返回值类型 | 方法 |
---|---|
String | getMessage() 返回此throwable的详细消息字符串。 |
void | printStackTrace() 将此throwable和其追溯打印到标准错误流。 |
String | toString() 返回此可抛出的简短描述。 |
在异常体系中,所有的子类都没有具体方法,方法都是来自于Throwable:
try {
System.out.println(arr[5]);
System.out.println(str.equals("abc"));
Integer iter = (Integer)obj;
System.out.println(1/0);
date = sdf.parse(strDate);
} catch (ParseException e) {
System.out.println("进行了异常处理");
e.printStackTrace();
}catch (NullPointerException ne){
ne.printStackTrace();
}catch (ArrayIndexOutOfBoundsException ae){
String message = ae.getMessage();
System.out.println(message);
String msg = ae.toString();
System.out.println(msg);
ae.printStackTrace(); //得到异常的全部信息
}
数组下标越界异常:
Index 5 out of bounds for length 5
java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
null
程序执行结束
java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
at ThrowDemo.ThrowTest.main(ThrowTest.java:26)
编译期异常和运行时异常的区别:
-
运行时异常不处理,不会影响程序的运行,而运行期异常则 须做出相应的处理,否则程序无法运行;
-
运行时异常往往都可以通过优化代码来进行规避,运行时异常的出现,都是我们的程序存在逻辑上的漏洞或者缺陷,在实际处理中处理的重点是编译期异常。
3.2 抛出异常
public class Test1 {
public static Date str2Date(String strdate) throws ParseException {
String strDate = "2020-12-23 " ;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse(strDate);
return date;
}
}
抛出异常是在方法的声明上通过throw关键字来进行声明。
告诉方法的调用者,该方法存在这样类型的异常,throw后面跟的是异常类型。
在throws后边可以跟多个类型异常
抛出异常是抛给了方法的调用者,此时对于方法的调用者来说将有两种选择:
- 使用try{}catch{}来捕获异常并处理
- 不处理继续使用throws往外抛
直到在main方法中,如果此时也不处理,则抛出就抛给了jvm,JVM对于接受到的异常的默认处理:
以上就是jvm的默认处理方式:打印异常的堆栈信息,并终止程序的执行。
异常信息打印顺序:首先打印的是距离抛出异常最近的语句,接着是调用该方法的方法,一直到最开始被调用的方法。
对于与业务相关的异常,就需要我们自己来定义异常
3.3 注意事项
- 一个try 块不是只有一个catch语句,也可以不使用catch
- catch,finally不能单独使用
- 多重捕获(multi-catch)可以对多个 catch 语句进行优化。
- finally 总会被执行,除非 catch 中出现了 System.exit(1)语句。
4. 自定义异常(MyException)
自定义异常可以更加明确定位异常出错的位置和给出详细出错信息
-
自定义异常有两种:
- 自定义编译期异常
- 自定义运行时异常
-
如果要自定义一个编译期异常,则继承Exception,并实现相应的构造方法即可。
如果要定义一个运行时异常,则继承RuntimeException,并实现相应的构造方法。 -
实例:
- 需求:编写程序模拟用户注册
程序开始执行时,提示用户输入“用户名”和“密码”信息。
输入信息之后,后台java程序模拟用户注册。
注册时用户名要求长度在[6-14]之间,小于或者大于都表示异常。
注意: 完成注册的方法放到一个单独的类中。 异常类自定义即可。
class UserService {
public void register(String username,String password){
//这个方法中完成注册!
}
}
- 实现:
public class UserExcepton extends Exception{
public UserExcepton(){
super();
}
public UserExcepton(String username){
super(username);
}
}
import java.util.Scanner;
public class UserService {
public void register(String username , String password) throws UserExcepton {
int l = username.length();
if(l <= 14 && l >= 6){
System.out.println("注册成功");
}else{
throw new UserExcepton("用户名输入不合法");
}
}
public static void main(String[] args) throws UserExcepton {
UserService user = new UserService();
Scanner sc = new Scanner(System.in);
System.out.println("输入用户名:");
String username = sc.nextLine();
System.out.println("输入密码:");
String password = sc.nextLine();
user.register(username,password);
}
}
运行结果:
输入用户名:
乐呵呵123
输入密码:
abc123
注册成功
输入用户名:
乐呵呵
输入密码:
abc123
Exception in thread "main" Day_1_1.UserExcepton: 用户名输入不合法
at Day_1_1.UserService.register(UserService.java:11)
at Day_1_1.UserService.main(UserService.java:22)
-
throw和throws的区别
- 位置:throws用在方法的声明上,throw在方法体
- 抛出类型:throws抛出的是异常的类型,throw抛出的是异常对象
- 数量:throws可以抛出多个异常 throw只能抛出 一个异常对象
-
什么时候抓什么时候抛?
- 捕获异常一般都在我们可以自己处理,并且处理之后不会再次产生新的异常时,才捕获处理。
- 如果不能完全处理异常,则将异常抛出给下一个方法的调用者,让其来做出相应的处理,直到最后抛给jvm。