各位看官早安午安晚安呀
如果您觉得这篇文章对您有帮助的话
欢迎您一键三连,小编尽全力做到更好
欢迎您分享给更多人哦
大家好我们今天来学习Java面向对象的的抽象类和接口,我们大家庭已经来啦~
目录
[1.(throws和throw)我们不管这个异常,把这个异常抛出去](#1.(throws和throw)我们不管这个异常,把这个异常抛出去)
[2.1. try块内抛出异常位置之后的代码将不会被执行](#2.1. try块内抛出异常位置之后的代码将不会被执行)
[2.2. 如果抛出异常类型与catch时异常类型不匹配](#2.2. 如果抛出异常类型与catch时异常类型不匹配)
[2.3. try中可能会抛出多个不同的异常对象](#2.3. try中可能会抛出多个不同的异常对象)
[3:关于 "调用栈"(e.printStackTrace())](#3:关于 "调用栈"(e.printStackTrace()))
在 Java 中,将程序执行过程中发生的不正常行为称为异常 。
要认识异常首先我们要认识一张图
从上图中可以看到:
- Throwable : 是异常体系的顶层类,其派生出两个重要的子类 , Error 和 Exception
- Error : 指的是 Java 虚拟机无法解决的严重问题,比如: JVM 的内部错误、资源耗尽等 ,典型代表:
StackOverflowError 和 OutOfMemoryError ,一旦发生回力乏术。
- Exception : 异常产生后程序员可以通过代码进行处理,使程序继续执行。比如:感冒、发烧。我们平时所说的异常就是Exception 。
异常大概处理方案:
关于异常的一般处理方式:
异常的种类有很多, 我们要根据不同的业务场景来决定.
对于比较严重的问题(例如和算钱相关的场景),应该让程序直接崩溃, 防止造成更严重的后果
对于不太严重的问题(大多数场景), 可以记录错误日志, 并通过监控报警程序及时通知程序猿
对于可能会恢复的问题(和网络相关的场景), 可以尝试进行重试
在我们当前的代码中采取的是经过简化的第二种方式. 我们记录的错误日志是出现异常的方法调用信息, 能很快速的让我们找到出现异常的位置. 以后在实际工作中我们会采取更完备的方式来记录异常信息
比如我们之前写代码时经常遇到的:
1: 算数异常
java
System.out.println(10/0);
2. 数组越界异常
int [] arr = { 1 , 2 , 3 };
System . out . println ( arr [ 100 ]);
3:空指针异常
java
int [] array = null;
System.out.println(array.length);
这些异常,我们写代码的时候不会显示异常,但是程序运行时就会报错;
(这种异常我们称之为运行时异常也叫非受查异常)
既然有非受查异常,就自然会有受查异常
譬如:我们之前一直没明白的克隆方法,我们子类要用这个克隆方法,克隆之后肯定要用一个新的对象来接收,那就是Person person2 = person1.clone();但是clone()是Object类的方法,我们只能在类里通过super调用,所以子类要用就必须要重写这个方法。
但是重写之后还是会报异常CloneNotSupportedException
我们只要调用这个方法就会报异常,但是我们还是要用到这个方法
所以说现在摆在我们面前的有两条路:
1.我们不管这个异常,把这个异常抛出去
2:我们主动地去捕捉这个异常
1.(throws和throw)我们不管这个异常,把这个异常抛出去
抛出去这个异常就要引出一个关键字throws了
然后还有一个关键字throw和它很像(这个关键字也是抛出一个异常)
他俩的区别在于:
throws是抛出已经出现的异常(一般放在方法参数列表之后)(也算是处理了,但不算是真正的处理,只是把****异常报告给抛出异常方法的调用者,由调用者处理 )
而throw是我们主动去抛出一个异常,一般是程序没出现我们为了提醒别人而抛出的异常
(但是这个异常迟早是要被捕捉的,不然异常一直存在,最后大家都处理,只能交给JVM去处理,最后程序就只能报出来这个异常)
我们throws出去这个异常
大家可以看到这个时候就不报异常了,但是我们现在调用它试试呢?
现在它又报错了 ,为啥呢?就像我上面所说的你只是抛出了这个异常(暂时解决了这个问题)实际上是把问题抛给了这个方法的调用者
然后我们还不想解决,那就,让main函数也把这个责任甩出去
按理说程序运行时,JVM会处理这个异常
但是我们实现了一个克隆接口(允许)该类的对象被拷贝
Cloneable
接口是一个标记接口(即它没有任何方法定义)。当一个类实现了Cloneable
接口,这表示这个类的对象支持克隆操作,而且Person
类实现了Cloneable
接口并且正确地重写了clone()
方法,所以说最终没有抛出这个异常
1.1:throws的注意事项
处在方法声明时参数列表之后,当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助 throws 将异常抛给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者处理异常 。
注意:
- throws 必须跟在方法的参数列表之后
- 声明的异常必须是 Exception 或者 Exception 的子类
- 方法内部如果抛出了多个异常, throws 之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型具有父子关系,直接声明父类即可。
javapublicstaticvoidmain(String[]args)throwsArrayIndexOutOfBoundsException,ArithmeticException{} 可以替换成 public static void main(String[] args) throws Exception{ }
4:调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用 throws 抛出
(我们讲述的clone方法和main方法)
5: 将光标放在抛出异常方法上,ALT点击+enter快速处理
1.2:throw的注意事项
1.throw 必须写在方法体内部
2. 抛出的对象必须是 Exception 或者 Exception 的子类对象
3. 如果抛出的是 RunTimeException 或者 RunTimeException 的子类,则可以不用处理,直接交给 JVM 来处理
4. 如果抛出的是编译时异常,用户必须处理,否则无法通过编译
throws继续抛出或者try-catch处理,最后try-catch不处理的话JVM处理,程序异常终止
5. 异常一旦抛出,其后的代码就不会执行
2:(try-catch)我们主动地去捕捉这个异常
就像如果我们捕捉了上面的克隆异常(但是他是一个异常,还是不会被执行,所以我们最好还是实现一个克隆接口,最终别抛出异常)
如果我们要捕捉异常
这时候就要引出一个组合了 try-catch****捕获并处理(细节我们一会会具体将到)
try {
// 将可能出现异常的代码放在这里
} catch ( 要捕获的异常类型 e ){
// 如果 try 中的代码抛出异常了,此处 catch 捕获时异常类型与 try 中抛出的异常类型一致时 ,或者是try中抛出异常的基类时,就会被捕获到
// 对异常就可以正常处理,处理完成后,跳出 try-catch 结构,继续执行后序代码
} finally {
// 此处的语句无论是否发生异常,都会被执行到
}
// 如果没有抛出异常,或者异常被捕获处理了,这里的代码也会执行
示例:
2.1. try块内抛出异常位置之后的代码将不会被执行
java
public static void main(String[] args){
try{
System.out.println(10/0);
System.out.println("想你");
}catch(ArithmeticException e){
System.out.println("捕捉到了算数异常");
}
}
这里我们捕捉到了算数异常,但是"想你"并没有被打印(这是因为这里程序运行到这里已经发现了异常,就会去捕捉(因为我们用try-catch组合了)所以后面的代码就不会被执行了)
就像程序这里(哈哈打印了,想你没有打印)
2.2. 如果抛出异常类型与catch时异常类型不匹配
即异常不会被成功捕获,也就不会被处理,继续往外抛,直到 JVM收到后中断程序 ----异常是按照类型来捕获的
java
public static void main(String[] args){
try{
System.out.println(10/0);
System.out.println("想你");
}catch(NullPointerException e){
System.out.println("捕捉到了算数异常");
}
System.out.println("想你");
}
一个想你都没有打印
2.3. try中可能会抛出多个不同的异常对象
则必须用多个catch来捕获----即多种异常,多次捕获 , 并且捕获到了一个异常,就不会进入另一个catch了
java
public static void main(String[] args){
try{
System.out.println(10/0);
int[] arr = {1, 2, 3};
System.out.println(arr[100]);
}catch(NullPointerException e){
System.out.println("捕获到了空指针异常");
}
catch(ArithmeticException e){
System.out.println("捕捉到了算数异常");
}
System.out.println("想你");
}
这时候就有小伙伴问了,那另一个异常去哪了?
其实我们上面就已经讲述过了,一个异常出现,我们用try-catch捕获处理(这个异常下面的代码就不会执行了,直接执行捕获后的catch里面的代码,以及处理完程序后面的代码)
2.4:我们也可以有以下两种不推荐的捕获方式
1.
catch ( ArrayIndexOutOfBoundsException | NullPointerException e ) {
...
}
不过我们不推荐这种方式:太模糊了,到底是哪种异常
2.一次性捕获所有异常:
catch( Exception e ){ 这是异常的父类
}
注意: 如果异常之间具有父子关系,一定是子类异常在前 catch ,父类异常在后 catch ,否则语法错误:(否则,相当于,你在前面就把水全部堵住了,异常根本流过来)
子类异常在前 catch ,父类异常在后 catch**(还是有点作用的,防止程序异常终止嘛)**
3:关于 "调用栈"( e.printStackTrace())
关于 "调用栈"
方法之间是存在相互调用关系的, 这种调用关系我们可以用 "调用栈" 来描述. 在 JVM 中有一块内存空间称为 "虚拟机栈" 专门存储方法之间的调用关系. 当代码中出现异常的时候, 我们就可以使用 e.printStackTrace(); 的方式查看出现异常代码的调用栈.
譬如上述代码:
java
public static void main(String[] args){
try{
System.out.println(10/0);
int[] arr = {1, 2, 3};
System.out.println(arr[100]);
}catch(NullPointerException e){
System.out.println("捕获到了空指针异常");
}
catch(ArithmeticException e){
System.out.println("捕捉到了算数异常");
e.printStackTrace();
}
System.out.println("想你");
}
4:finally
在写程序时, 有些特定的代码,不论程序是否发生异常,都需要执行,比如程序中打开的资源 :网络连接、数据库 连接,IO 流等, 在程序正常或者异常退出时,必须要对资源进进行回收 。
另外,因为 异常会引发程序的跳转,可能 导致有些语句执行不到 , finally 就是用来解决这个问题的。
java
public static void main(String[] args){
try {
System.out.println(10 / 0);
int[] arr = {1, 2, 3};
System.out.println(arr[100]);
}
catch (ArrayIndexOutOfBoundsException e){
System.out.println("捕获到了数组越界异常");
}finally {
System.out.println("不管是否捕获到了异常,finally后面的都会被执行");
}
System.out.println("想你");
}
就是想你没有被执行(finally就是用来处理善后工作的)
譬如关闭输入流
大家可以想想如果没有finally,这个程序刚才没有捕捉到异常,就异常终止了,但是输入流没有没关闭(这时候就造成了资源的泄露)
注意:一般我们不建议在 finally 中写 return (被编译器当做一个警告).
java
public static void main(String[] args) {
System.out.println(func());
}
public static int func() {
try {
return 10;
} finally {
return 20;
}
}
A: 10 B: 20 C: 30 D: 编译失败
选择B
finally 执行的时机是在方法返回之前 (try 或者 catch 中如果有 return 会在这个 return 之前执行 finally).
但是如果
finally 中也存在 return 语句 , 那么就会执行 finally 中的 return, 从而不会执行到 try 中原有的 return.
一般我们不建议在 finally 中写 return ( 被编译器当做一个警告 )
所以说我们这两个问题大家清楚了吗?
- throw 和 throws 的区别?
- finally 中的语句一定会执行吗?
【 异常处理流程总结 】
程序先执行 try 中的代码
如果 try 中的代码出现异常 , 就会结束 try 中的代码 , 看和 catch 中的异常类型是否匹配 .
如果找到匹配的异常类型 , 就会执行 catch 中的代码
如果没有找到匹配的异常类型 , 就会将异常向上传递到上层调用者
.
无论是否找到匹配的异常类型 , finally 中的代码都会被执行到 ( 在该方法结束之前执行 ).
如果上层调用者也没有处理的了异常 , 就继续向上传递 .
一直到 main 方法也没有合适的代码处理异常 , 就会交给 JVM 来进行处理 , 此时程序就会异常终止 .
4:自定义异常类
接下来我们实现一个登录系统
java
public class LogIn {
private String userName = "admin";
private String password = "123456";
public void loginInfo(String userName, String password) {
if (!this.userName.equals(userName)) {
System.out.println("名字错误");
return;
}
if (!this.password.equals(password)) {
System.out.println("密码错误");
return;
}
System.out.println("登陆成功");
}
public static void main(String[] args) {
LogIn logIn = new LogIn();
logIn.loginInfo("admin", "1234561");
}
}
但是总是sout输出太俗了,不能有效的提醒,这个时候我们就可以自定义一个异常类
但是我们应该如何定义一个异常类呢?(并且运行时能给我们检测出来)我们不妨看看其他异常类是怎么定义的?譬如:算数异常
那么我们也这样写两个异常
然后我们的代码就可以改成这个样子
java
public class LogIn {
private String userName = "admin";
private String password = "123456";
public void loginInfo(String userName, String password) throws UserNameException,UserPasswordException {
if (!this.userName.equals(userName)) {
throw new UserNameException("用户名错误");
}
if (!this.password.equals(password)) {
throw new UserPasswordException("登录密码错误");
}
System.out.println("登陆成功");
}
public static void main(String[] args) {
LogIn logIn = new LogIn();
try{
logIn.loginInfo("admin", "1234561");
}catch(UserNameException e){
System.out.println("");
}
}
}
登录密码错误
其实定义方式就是这个
- 自定义异常类,然后继承自 Exception 或者 RunTimeException
- 实现一个带有 String 类型参数的构造方法,参数含义:出现异常的原因
注意继承Exception时是编译异常 ,继承RunTimeException是受查时异常
所以说,继承RunTimeException时就不需要抛出异常了(写代码时也不会报异常),如果我们不处理,运行时JVM会帮我们处理(最后返回发生异常的位置)
大家可以看一下如果我继承Exception时不抛出异常,这个时候就会报错,因为继承Exception是编译时异常(受查异常)
总之,大家根据自己的需求去选择是用编译时异常还是运行时异常就好了
上述就是 Java异常处理了解,熟悉到进阶的全部内容了,能看到这里相信您一定对小编的文章有了一定的认可,异常的出现,让我们对于程序有了更多的认识~~~
有什么问题欢迎各位大佬指出
欢迎各位大佬评论区留言修正
您的支持就是我最大的动力!!!!