学习异常后,发现异常的知识是多么的吸引人!不仅可以用来标记错误,还可以自己定义一个异常,用来实现自己想完成的业务逻辑,接下来一起去学习吧
目录
一、异常的概念及体系结构
1.异常的概念
2.异常的体系结构
3.异常的分类
二、怎么处理异常
1.防御式编程
2.异常的抛出
3.异常的捕获
4.异常的处理流程
三、自定义异常
1.自定义异常的用途及注意事项
2. 自定义异常实现登录场景
(1)什么是异常
所谓异常,就是程序在执行的过程中,发生的不正常行为;也可以认为是代码存在bug
(2)常见的异常
也就是计算的过程中发生的异常,如分目不能为0,但是硬要写成0
public static void main(String[] args) {
//算术异常
System.out.println(10/0);
System.out.println("检验这句话是否被打印");
}
常见的如:对数组越界访问
public static void main(String[] args) {
//数组越界异常
int[] arr = {1,2,3,4,5};
System.out.println(arr[6]);
System.out.println("检验这句话是否被打印");
}
空指针异常,并不等于Java中存在指针,而是对空引用的变量进行访问
public static void main(String[] args) {
//空指针异常
int[] arr = null;//此时arr为一个空引用
System.out.println(arr[10]);
System.out.println("检验这句话是否被打印");
}
(3)异常小总结
public static void main(String[] args) {
//空指针异常
System.out.println("在异常发生前,是否会打印这句话");
int[] arr = null;//此时arr为一个空引用
System.out.println(arr[10]);
System.out.println("检验这句话是否被打印");
}
(1)每一个异常都是一个类,它们之间的关系为继承
(2)异常的体系结构
下面简单举个例子:无限递归致使栈溢出
public static void main(String[] args) {
func();
}
public static void func() {
func();//无限递归
}
这里的异常,也就是指Exception,后面产生的两个子类
(1)编译时异常(Checked Exception)--受查异常
编译时异常就是在编写代码的时候就报的错误,下面举一个克隆异常的例子
这是不支持克隆的异常,怎么做?需要声明异常:鼠标放到异常处,Alt+Enter键即可(要想实现克隆,还需要实现克隆接口)
(2)运行时异常(RutimeException)--非受查异常
在程序执行期间发生的异常,称为运行时异常,也称为非受检查异常(Unchecked Exception)
如:RunTimeException以及其子类对应的异常,都称为运行时异常
异常处理主要的5个关键字:throw、try、catch、finaly、throws
(1)LBYL(事前防御型)
boolean ret = false;
ret = 登陆游戏();
if (!ret) {
处理登陆游戏错误;
return;
}
ret = 开始匹配();
if (!ret) {
处理匹配错误;
return;
}
ret = 游戏确认();
if (!ret) {
处理游戏确认错误;
return;
}
ret = 选择英雄();
if (!ret) {
处理选择英雄错误;
return;
}
ret = 载入游戏画面();
if (!ret) {
处理载入游戏错误;
return;
}
登录游戏时就确认有没有错误,正确才能进入下一步
(2)EAFP(事后认错型)-主推
EAFP是异常处理的核心思想
try {
登陆游戏();
开始匹配();
游戏确认();
选择英雄();
载入游戏画面();
...
} catch (登陆游戏异常) {
处理登陆游戏异常;
} catch (开始匹配异常) {
处理开始匹配异常;
} catch (游戏确认异常) {
处理游戏确认异常;
} catch (选择英雄异常) {
处理选择英雄异常;
} catch (载入游戏画面异常) {
处理载入游戏画面异常;
}
(1)理解throw
throw new XXXException("异常产生的原因");
也就是throw一个异常的对象,给对象的传参可以自己定义
public static void func(int[] arr) {
if(arr == null) {
throw new NullPointerException("这是自己抛出的空指针异常");
}
}
public static void main(String[] args) {
func(null);
}
(2)throw的注意事项
(1)异常的声明
异常声明的位置:处在方法声明时参数列表之后,可以同时声明多个异常
作用:当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助throws将异常抛
给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者处理异常
格式:
修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2...{
}
(2)注意事项
比如:声明的多个异常类型都是运行时异常的子类,那么可以直接声明运行时异常(RuntimeExcepetion),更极端可以直接声明异常(Exception)
当被调用的方法(func)声明了异常之后:
说明:当方法后面直接声明Exception时,可能是运行时异常。也有可能是编译使异常;当什么都没有时,会默认是编译时异常,所以就报错了。
解决报错的第一种方法:调用者所在的方法也要声明同样的异常
解决报错的第二种方法:对可能发生异常的代码进行捕获,也就是“异常的捕获”;使用try{}catch对异常捕获:
也就是下面的内容
(2)try-catch捕获并处理
【简单语法】
正常的对异常进行声明或者抛出,只是简单的介绍了或者只是处理了编译时异常,而未正在的处理过异常;当异常不处理时,最后会把异常交给JAVM处理,则程序便会终止,不再执行
简单举例:只要捕获到异常,代码就可以继续往下执行
public static void func(){
int[] arr = null;
System.out.println(arr.length);
}
public static void main(String[] args) {
try{
func();
}catch (NullPointerException e) {
System.out.println("处理NullPointerException异常成功!");
}
System.out.println("处理完异常可以继续走完下面的代码!");
}
【注意事项】
public static void main(String[] args) {
try{
int[] arr = {1,2,3};
System.out.println(arr[10]);
}catch (NullPointerException e) {
e.printStackTrace();
System.out.println("成功捕获到空指针异常");
}catch(ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
System.out.println("捕获到了数组越界异常");
}
System.out.println("后续代码可被执行!");
}
做法:子类必须在父类前面(否在永远不会执行到子类,父类在后可以兜底)
【引入finally】
try{
// 可能会发生异常的代码
}catch(异常类型 e) {
//对捕获到的异常进行处理
}catch(异常类型 e) {
//对捕获到的异常进行处理
}finally {
//此处的语句无论是否发生异常,都会被执行到
}
【finally特点】
public static void main(String[] args) {
try(Scanner scanner = new Scanner(System.in)){
int a = scanner.nextInt();
}
catch (NullPointerException e){
System.out.println("子类在前");
}catch (RuntimeException e) {
System.out.println("父类在后");
}finally {
System.out.println("finally被执行了哦!");
}
}
public static int func6() {
try(Scanner scanner = new Scanner(System.in)){
int a = scanner.nextInt();
return a;
}
catch (NullPointerException e){
e.printStackTrace();
System.out.println("子类在前");
}finally {
System.out.println("finally被执行了哦!");
return -1;
}
}
public static void main(String[] args) {
int ret = func6();
System.out.println("接收的返回值"+ret);
}
输入:20
总结:finally中的语句一定会被执行,即使前面存在return;存在多个return,最终结果以finally中的为准
(1)简单三部曲
方法中是否有处理异常(未处理则下一步)--->调用该方法有没有处理异常(未处理则交给JVM)--->JVM最后处理异常,程序则会终止
public static void main(String[] args) {
func7();//调用者也未处理该异常
}
public static void func7() {
try{
int[] arr = {1,2,3};
System.out.println(arr[5]);//数组越界访问异常
}
catch (NullPointerException e){
e.printStackTrace();
System.out.println("子类在前");
}finally {
System.out.println("finally被执行了哦!");
}
}
做法:
public static void main(String[] args) {
try {
func7();//在调用者处处理异常
}catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
}
}
public static void func7() {
try{
int[] arr = {1,2,3};
System.out.println(arr[5]);//数组越界访问异常
}
catch (NullPointerException e){
e.printStackTrace();
System.out.println("子类在前");
}finally {
System.out.println("finally被执行了哦!");
}
}
(2)总结流程
(1)多用于一些业务逻辑中可能发生的异常,常常是系统中没有的;如账号登录时,输入的账号错误或者密码错误等等
(2)自定义的异常一般继承RuntimeException或者Exception
(1)先实现一个登录逻辑的类
public class Logic {
public String userName = "zhangsan";//设置初始账号名字为:zhangsan
public String password = "123456";//初始密码为1234456
public void loginInfo(String userName,String password) {
//该方法用来验证密码是否正确,参数为
//比较用户名
if(!this.userName.equals(userName)) {
System.out.println("用户名错误!");
}
//比较密码
if(!this.password.equals(password)) {
System.out.println("密码错误!");
}
}
}
以上是账户登录的大概逻辑
(2)实现自定义异常类
该类用来:当账户或密码错误时,抛出异常信息并定位错误的行号,利于修改
用户名异常类:
public class UerNameException extends RuntimeException{
//用户异常类
public UerNameException() {
super();
}
public UerNameException(String message) {
super(message);
}
}
密码异常类:
public class PassWordException extends RuntimeException{
//账号密码错异常类
//模拟原码实现两个构造方法
public PassWordException() {
super();
}
public PassWordException(String s) {
super(s);
}
}
(3)完成业务逻辑
为加异常类时:
public class Logic {
public String userName = "zhangsan";//设置初始账号名字为:zhangsan
public String password = "123456";//初始密码为1234456
public void loginInfo(String userName,String password) {
//该方法用来验证密码是否正确,参数为
//比较用户名
if(!this.userName.equals(userName)) {
System.out.println("用户名错误!");
}
//比较密码
if(!this.password.equals(password)) {
System.out.println("密码错误!");
}
}
public static void main(String[] args) {
Logic logic = new Logic();
logic.loginInfo("lisi","6666");//调用方法输入账号和密码
}
}
注入异常后:
public class Logic {
public String userName = "zhangsan";//设置初始账号名字为:zhangsan
public String password = "123456";//初始密码为1234456
public void loginInfo(String userName,String password) {
//该方法用来验证密码是否正确,参数为
//比较用户名
if(!this.userName.equals(userName)) {
//System.out.println("用户名错误!");
throw new UerNameException("用户名错误!");//抛出异常
}
//比较密码
if(!this.password.equals(password)) {
//System.out.println("密码错误!");
throw new PassWordException("密码错误!");//抛出异常
}
}
public static void main(String[] args) {
try {
Logic logic = new Logic();
logic.loginInfo("lisi","6666");//调用方法输入账号和密码
}catch (UerNameException e) {
e.printStackTrace();
}catch (PassWordException e) {
e.printStackTrace();
}
System.out.println("捕获异常后,不影响代码继续往下执行!");
}
}
这里用户名错误后,不再判断密码
(4)一些小问题
当自定义异常继承Exception需要添加的细节
因为Exception默认是受查异常/编译时异常,所以需要加上解决掉报错
本次的内容分享到这里就结束了,小伙伴快去试试吧!