一、异常的概念
在Java中,将程序执行过程中发生的不正常行为称为异常。
Java当中,描述异常是根据类来进行描述的!!!不同的类表示不同的异常。
如图:
Throwable:是异常体系的顶层类,其派生出两个重要的子类:Error和Exception
Error:是指Java虚拟机无法解决的严重问题,比如StackOverflowError,一旦发生回天乏术
Exception:异常产生后程序员可以通过代码进行处理,使程序继续执行。比如感冒、发烧,这种日常中的小病在Java中称之为Exception
1、Error
如图下的代码:
程序运行:
系统会不停地调用func(),每次调用都会在栈上开辟内存,最后内存被挤爆,因此会出现错误! (StackOverflowError)这个就是栈溢出错误!
2、Exception
Exception其实还分为两种情况:运行时异常和编译时异常!
运行时异常(RuntimeException类)
运行时异常就是程序在运行过程中报的异常。
举个例子:
程序运行:
程序在run(运行)时才发现,array这个数组没有指向任何对象,因此无法计算数组长度!
编译时异常:(Exception类)
编译时异常就是程序在编译的时候发生的异常!
举个例子:
如图:
代码用红色波浪线提示,鼠标指向波浪线,然后会出现异常(exception)提示,程序还未执行就报异常错误,这个就是编译时异常!
注意:
编译时出现的语法性错误,并不能称之为异常!(如关键字拼写错误)
就像这个,命名了两个相同名字的变量,这种是编译时的语法问题,并不是异常!
二、异常的处理
既然异常出现了,那么我们就要想办法处理!处理异常有一个前提,那就是抛出异常!
1、抛出异常分为两类:
1、程序本身触发异常sho
2、手动抛出异常----->>(throw)
第一种就是我们前面所说的,程序会自己抛出异常!下面我们介绍如何手动抛出异常!
让我们看看实例:(以非受查异常为例子)
如果func()方法传入的数组指向null,抛出一个非受查异常(运行时异常)
public class Test1 {
public static void func(int [] array){
if(array==null){
throw new RuntimeException();
}
}
public static void main(String[] args) {
int [] array=null;
func(array);
}
}
程序运行结果:
再来看看一个实例:(以受查异常为例)
如图:
如果func()方法传入的数组指向null,抛出一个受查异常(编译时异常)
为什么会出现红色波浪线?
答:这是因为我们抛出的是一个受查异常(编译时异常),规定受查异常必须经过处理才能运行!
2、解决方法:
一、添加异常的声明操作!
此时,在main方法中调用func()方法的时候!也会出现红色波浪线!
那是因为,如果main方法调用了func方法,main方法也要进行异常的声明!
添加声明后:
二、try......catch......
虽然按照方法1添加声明后程序不会报错,但是真正的异常还没有处理!此时异常交给JAM处理了,但是JAM没有处理,它的处理方式就是直接崩溃!
那么该如何处理?
答:利用try......catch......
让我们看看实例:
try{
//可能出现异常的代码块
} catch(要捕获的异常类型 e){
//对异常进行处理
}
public class Test1 {
//func方法依旧要添加异常声明!
public static void func(int [] array) throws Exception {
if(array==null){
throw new Exception("传个参数看看");
}
}
//main方法不用添加异常声明,但是要使用try...catch...处理异常!
public static void main(String[] args) {
try{
//存放可能出现异常的代码
int [] array=null;
func(array);
}catch(Exception e){
System.out.println("捕获到了Exception异常" +
"此时这里可以开始处理异常了");
}
//异常解决后,正常执行后面的语句
System.out.println("异常处理结束,程序继续进行!");
}
}
程序运行结果:
可以看到,异常解决后,后面的语句仍然可以正常执行!
另外,如果我们要看看异常是在哪里抛出的,可以使用e.printStackTrace()打印出来:
异常处理流程:
程序会先执行try中的代码-->如果try中的代码出现异常,就结束try中的代码,看和catch中的异常是否匹配
3、异常类型不匹配
我们知道,异常分为很多种类型:空指针异常,算术异常......如果try{}里面的异常类型,与catch()里面写的要捕获的异常类型不匹配,程序还是无法解决异常的!
比如:
如下出现的其实是一个空指针异常,但是catch却要捕获一个算术异常。
那么:程序无法捕获异常,异常交给JAM处理,程序直接崩溃!
程序运行:(最后一条语句的1,表示不正常退出)
改进:
这里将算术异常改为空指针异常,程序就可以正常处理异常!
程序运行:(最后一条语句的0表示正常退出)
4、合并异常
1、 在实际使用过程中,为了同时捕获多种异常,我们可以使用多个catch,捕获不同的异常!
注:使用多个catch时,如果要捕获的异常具有父子类关系,那么一定要把子类异常写在前面,父类异常写在后面!
如图:
2、也可以将不同的异常类型合并为同一个!(使用‘|’ 连接)
三、finally的作用
在写程序时,有些特定的代码,无论程序是否发生异常,都需要执行,比如程序中打开了文件,在程序退出的时候就需要关闭文件。
但是,程序抛出异常可能会导致异常发生处之后的语句无法执行,因此,需要finally来解决这个问题!
注意:finally{}是紧跟在catch(){}后面写的!
让我们看看finally的作用:
如下代码会触发算术异常(0不能作为被除数)
此时程序会抛出异常,看看finally里面的语句是否会被执行?
可以看到,finally里面的语句被执行了,说明异常的抛出不会影响finally里面语句的执行!
那么,有一个问题,假如程序不抛出异常,finally里面的语句是否还会执行?
答:会执行!
如图:
可以看到,我这里把会触发算术异常的错误语句去除,因此程序不会抛出异常了!
此时看看finally里面的语句是否会执行?
我们发现:finally里面的语句正常执行了!那么我们得出一个结论:
无论程序是否抛出异常,finally里面的语句都会正常执行!
通过前面try.....catch......的学习,如果你记忆力好的话,那么你就会发现try......catch......处理好异常后,其后面的正常语句还是可以正常运行的,那么,又何必使用finally{ }呢?
答:这是因为,前面举的例子并不能很好地体现finally的特性!
我再举个例子:
与前面例子不同的是:这里try里面的语句有了return;语句,那么,这个时候,你再看看try......catch......后面的正常语句还会不会执行?
程序运行结果:
如图可知:try......catch.....后面的正常语句并没有打印出来“关闭文件!“也就是说它不会执行。
这个时候,你使用finally试试!
四、自定义异常类
在Java中,设置了各种异常类型,但是在实际使用过程中,我们可能会出现许多种Java中规定以外的异常,为了满足实际需要,我们可以自定义异常类型!
举个例子:
我们正在创建一个用户登录账号的功能!要求用户输入正确的用户名和密码!
要求:
如果输入用户名错误,抛出一个用户名异常!
如果密码输入错误,抛出一个密码异常!
实现方法如下:
1、在Login类中创建成员变量useName(用户名)和password(密码),并且赋值!
2、实现一个LoginInfo方法:如果用户名输入错误,抛出用户名异常,如果密码输入错误,抛出密码异常!
class Login{
private String useName="admin";
private String password="123456";
public void LoginInfo(String useName,String password) throws UserNameException,PasswordException {
if(!this.useName.equals(useName)){
//如果用户名与输入的用户名不匹配,抛出用户名异常
throw new UserNameException("用户名异常!");
}
if(!this.password.equals(password)){
//如果密码与输入的密码不匹配,抛出密码异常
throw new PasswordException("密码异常!");
}
}
}
注意:
这个LoginInfo方法后面添加throws UserNameException,PasswordException 语句的原因后面解释!在这里请先注意到这个点!
这个用户名异常类型(UserNameException)和密码异常类型(PasswordException)固然不是Java规定的类型!而是我们自定义的!
因此,我们还需要自定义异常类!
具体做法:
1、新建两个Java Class类:UserNameException和PasswordException
2、这两个类要继承一个父类Exception(编译时异常,也叫受查异常)或RuntimeException(运行时异常,也叫非受查异常)
3、在类中创建两个构造方法,一个带参数,一个不带参数!(具体写法参考下图代码!)
//用户名异常类
public class UserNameException extends Exception{
public UserNameException(){
}
public UserNameException(String msg){
super(msg);
}
}
//密码异常类
public class PasswordException extends Exception{
//创建两个构造方式:一个不带参数和一个带参数
public PasswordException(){
}
public PasswordException(String msg){
super(msg);
}
}
上面我们留下的一个小问题:
为什么在LoginInfo方法后面添加throws UserNameException,PasswordException 语句?
答:因为用户名异常和密码异常继承的父类都是Exception(编译时异常,也叫受查异常),前面的学习的添加异常声明的时候,你还记得为什么要添加异常声明吗?
是因为我们抛出的是一个受查异常!这个用户名异常类和密码异常类继承的异常类正是受查异常类!规定抛出受查异常的方法必须要添加异常的声明!
这样,自定义异常就完成了!让我们在main方法中看看效果!
1、调用LoginInfo方法时输入错误的用户名和密码!
程序运行:
程序抛出用户名异常!
2、调用LoginInfo方法时输入正确的用户名和密码!
程序运行:
可以看到,程序正常运行