异常是如何产生:
首先在讲异常之前,我们先来看一张图:
Throwable 是所有可以作为异常被抛出的类的超类。Throwable 类包括两个重要的子类:Error 和 Exception。
Error表示严重得错误,jvm会直接停止程序得运行
Exception表示还可以解决得错误,jvm首先会向上抛出。
Exception得分类:
- Exception的直接子类:编译时异常(要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错,因此得名编译时异常。),这类异常同时也叫受检异常
- RuntimeException:运行时异常。(在编写程序阶段程序员可以预先处理,也可以不管,都行。),这类异常也叫做非受检异常
举例说明:
编译时异常其实很明显,一般编译器都会提示
下面举一个运行时异常得例子:
package a_Exception;
public class Demo1Ex {
public static void main(String[] args) {
int[] arr = {1,2,3,4};
method(arr);
}
public static void method(int[] arr){
System.out.println(arr[4]);
System.out.println("我想要执行");
}
}
这一段代码很明显发生了一个数组越界
然后我们对报错得异常进行追踪一下
很清楚,发现这就是一个运行时异常。
同时,我们可以研究一下这个异常的处理过程
从这张图中可以看到,当jvm检测到异常之后就会直接向上抛,最后如果没有人处理的话,虚拟机就会用自己的方式处理异常
从上面的代码的控制台也可以看出,我在数组越界之后还输出了一句话:我想要执行
可是控制台并没有输出
异常的解决方法:
1:throws向上抛:
格式:在方法参数和方法体之间位置上写
throws 异常
throws关键字主打一个谁调用我这个方法我就把这个异常抛给谁
下面上一个案例:
首先我们创建一个异常对象,但不对这个异常对象做任何处理
接着我在add方法上抛了异常,然后这个异常就抛到了add方法上
最后只能在main方法上把异常抛了才算解决
throws 抛出多个异常:
1.格式:throws 异常1,异常2
2.注意:
如果throws的多个异常之间有子父类继承关系,我们可以直接throws父类异常
如果不知道多个异常之间是否有子父类继承关系,我们可以直接throws Exception
其实总体总结下来,throws这种方式和jvm默认的处理方式一样,
像上面的代码:如果add功能受到影响,下面的方法都用不了,这很不符合我们开发
所以对开发来说意义不大
2:try…catch:
格式:
try{
可能出现异常的代码
}catch(异常 对象名){
处理异常的代码-> 将来开发会将异常信息保存到日志文件中
}
基本使用:
public static void main(String[] args)throws FileNotFoundException {
String s = "a.txt1";
try {
add(s);//添加功能
} catch (FileNotFoundException e) {
e.printStackTrace();
}
delete();//删除功能
update();//修改功能
find();//查询功能
}
删除功能
修改功能
查询功能
java.io.FileNotFoundException: 文件找不到
at a_Exception.Demo1Ex.add(Demo1Ex.java:22)
at a_Exception.Demo1Ex.main(Demo1Ex.java:9)
下面是控制台输出内容,上面是用try-catch来包围的代码
我们可以看出try-catch这种方式处理的异常明显比较优雅,
即使你add这个方法报错,下面的内容也依旧会执行
当我们执行上面这个代码的时候,我们发现打印数组为空是java里面经典的NPE,空指针异常
FileNotFoundException这个异常抓不到这个空指针异常,所以就会让jvm处理这个异常。
所有推荐这种情况直接catch(Exception e)抓个大的异常
Finally关键字:
概述:代表的是不管是否触发了异常,都会执行的代码块
基本使用:都是配合try...catch使用
try{
可能出现异常的代码
}catch(异常 对象名){
处理异常的代码-> 将来开发会将异常信息保存到日志文件中
}finally{
不管是否有异常,都会执行的代码
}
直接上个案例:
public static void main(String[] args)throws FileNotFoundException {
String s = "a.txt1";
try {
add(s);//添加功能
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
System.out.println("我必须滴执行");
}
delete();//删除功能
update();//修改功能
find();//查询功能
}
private static void add(String s)throws FileNotFoundException {
if (!s.endsWith(".txt")) {
//故意创建异常
throw new FileNotFoundException("文件找不到");
}
System.out.println("我要执行了");
}
java.io.FileNotFoundException: 文件找不到
at a_Exception.Demo1Ex.add(Demo1Ex.java:24)
at a_Exception.Demo1Ex.main(Demo1Ex.java:9)
我必须滴执行
删除功能
修改功能
查询功能
整体的代码执行逻辑就是:调用add方法,进入if判断,然后抛出异常
进入catch语句,打印异常的信息,最后执行finally
来看下面一个案例:
public static void main(String[] args)throws FileNotFoundException {
int result = method();
System.out.println(result);
}
public static int method() {
try {
String s = null;
System.out.println(s.length());//空指针异常
return 2;
} catch (Exception e) {
return 1;
} finally {
System.out.println("我一定要执行");
// return 3;
}
}
代码结果:
我一定要执行
1
当程序走到s.length()时,发现异常进入catch,return 1
不过此时jvm发现下面还有一段finally,jvm还得执行finally之后再return(finally真是硬啊,不停虚拟机你就楞是不停啊)
如果finally里面的return 3没有被注释,那返回的结果result就是3
finally的使用场景:
关闭资源:
对象如果没有用了,GC(垃圾回收器)回收,用来回收堆内存中的垃圾,释放内存,但是有一些对象GC回收不了,比如:连接对象(Connection),IO流对象,Socket对象,这些对象GC回收不了,就需要我们自己手动回收,手动关闭
将来不能回收的对象new完之后,后续操作不管是否操作成功,是否有异常,我们都需要手动关闭,此时我们就可以将关闭资源的代码放到finally中
看下面这段代码:
public class Test {
public static void main(String[] args) {
FileWriter fw = null;
try {
fw = new FileWriter("day13_exception_object\\1.txt");
fw.write("哈哈哈");//假如这里写失败或者写成功了
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
if (fw!=null){
try {
fw.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
抛异常的注意事项:
1.如果父类中的方法抛了异常,那么子类重写之后要不要抛?
可抛可不抛
2.如果父类中的方法没有抛异常,那么子类重写之后要不要抛?
不要抛3.如果你实现了父类的没有抛异常的方法,接着你在这个方法体中使用了一个父类抛了异常的方法,那这个时候,你处理异常的方式只能try-catch
第三点比较难理解,举个例子:
在多线程章节,创建了一个实体类叫:MyThread,并且继承了Thread
Thread中的run方法是没有抛异常的,不过sleep方法是抛了异常的
然后我在MyThread类中重写Thread的run方法,并且在其中调用了sleep方法
代码如下:
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(MyThread.currentThread().getName()+"方法:"+i);
}
}
}
这个时候,当我输入 Thread.sleep(1000L)
编译器就会提示我出现了编译异常
这个时候我们只能用try-catch的方式处理异常
原因是:
重写方法是不能修改方法签名(方法签名就是:方法名称、参数列表和返回类型,但不包括方法体)
在Thread中,run()
方法并没有声明任何异常抛出,因此在实现该接口时,您不能修改方法签名,包括添加throws
子句来向上抛出异常。
自定义异常类:
我们在写业务的时候,肯定会碰到很多java没有提供的异常,
比如密码错误异常,账号错误异常,或者各种业务异常之类的,这个时候就需要自己去创建异常了
首先;我们可以模拟需要处理异常的场景:
public class Test01 {
public static void main(String[] args) {
String test = "root";
Scanner scanner = new Scanner(System.in);
String next = scanner.next();
if(next.equals(test)){
System.out.println("success");
}else {
throw new LoginFailedException("登录错误");
}
}
}
这个时候其实是会爆红的
因为我们知道在java中,万物皆是对象,所以我们自己造的这个异常也是一个对象
所以:我们需要加入下面这段代码:
public class LoginFailedException extends RuntimeException{
LoginFailedException(String msg){
super(msg);
}
}
LoginFailedException继承了运行时异常
当然也可以去继承编译时异常,不过如果继承了编译异常,需要在主函数中抛出或者try-catch
继承了运行时异常,我们创建一个构造方法:super(msg)
运行结果:
这一段可以和我之前写过的文章全局异常处理器结合着看。