什么是异常?

什么是异常?

文章目录

1.异常的概念2.异常产生的原因3.异常的分类3.1 运行时异常3.2 编译时异常3.3 自定义异常

4.异常的处理4.1 防御式编程4.2 异常的抛出

5.异常的捕获5.1 异常声明throws5.2 try-catch捕获异常并处理5.3 finally

6.异常的处理流程

1.异常的概念

什么是异常(Exception)呢?

异常就是在程序执行过程中发生的不正常的行为.异常中断了正在执行程序的正常指令流

异常其实就是类.异常是可以继承的

异常产生后程序员可以通过代码进行处理,使程序继续执行,比如:我们感冒发烧,进行一些处理是会好起来的.

2.异常产生的原因

主要有3种原因:

😊编写程序的代码产生的错误.比如数组越界,空指针异常等.这些异常叫做未受查异常.

😊Java内部错误产生的异常,Java虚拟机产生的异常

😊通过throw(抛出异常)语句手动生成的异常,这种异常叫做受查异常,可以用来给方法的调用者一些信息.

3.异常的分类

🤨Throwable是异常体系的顶层类,它派生出两个子类,分别是Error(错误)和Exception(异常),前者是不受查异常(Unchecked Exception),后者是受查异常(Checked Exception).

异常的继承结构:

基类为Throwable,Error和Exception继承自Throwable,RunTimeException和IOException等继承自Exception. 非RunTimeException一般是外部错误(除了Error),其必须被try-catch语句捕获

🤨Error定义了在通常情况下不希望被程序捕获的异常.Error类型的异常是在运行时出现的错误.比如堆和栈的溢出.(StackOverflowError,OutOfMemoryError)这类异常是Java虚拟机无法解决的严重问题.

Error类体系描述了Java运行系统的内部错误以及资源耗尽的情景,Error不需要捕获.

因为异常可以在运行时发生,也可以在编译时发生,所以我们将异常分为运行时异常和编译时异常.

3.1 运行时异常

**运行时异常(不受查异常)**包括RuntimeException及其子类异常,比如

NullPointerException,IndexOutOfBoundsException,ArithmeticException.

也就是说这一类型的异常程序可以选择处理也可以选择不处理.这一类型的异常一般由于程序逻辑错误引起.

运行时异常指的是运行时的程序已经编译通过得到class文件了,再由JVM执行的时候出现的错误.

我们来举个例子:

当我们执行这段代码:

public class Test2 {

public static void main(String[] args) {

System.out.println(10/0);

}

}

运行后的结果是:

ArthmeticException这个异常叫做算术异常

再来看另一个代码:

public class Test2 {

public static void main(String[] args) {

int[] array=null;

System.out.println(array.length);

}

}

运行后的结果是:

NullPointerException这个异常叫做空指针异常.

再来看另一段代码:

public class Test2 {

public static void main(String[] args) {

int[] array= {1,2,3};

System.out.println(array[10]);

}

}

运行后的结果:

IndexOutOfBoundsException叫做下标越界异常

3.2 编译时异常

编译时异常就是RuntimeException以外的其他异常,包括Exception类及其子类异常.编译时异常是必须要处理的异常,如果不进行处理,那么程序的编译就不能通过.例如IOException,ClassNotFoundException,以及其他一些用户自定义的异常.一般用户自定义的异常都是编译时异常.

🐱例子:

public class Person implements Cloneable{

public Object clone() throws CloneNotSupportedException{

return super.clone();

}

}

public class Test2 {

public static void main(String[] args) {

Person person1=new Person();

Person person2=(Person) person1.clone();

}

}

结果:

正确的处理是这样:

🐶注意:**编译时出现的语法性错误,不能称之为异常.比如:System.out.println这一句代码拼写错误**,这属于"编译期出错"

3.3 自定义异常

😆虽然Java中有很多异常类,但是在实际开发中所遇到的一些异常,不能完全表示,所以就需要自定义异常类.

自定义异常默认会继承Exception或者RunTimeException.继承自Exception的异常默认是受查异常,继承自RunTimeException的异常默认是非受查异常.

比如,我们来自定义一个运行时异常:

public class MyException extends RuntimeException{

public MyException(){

}

public MyException(String message){

super(message);

}

}

我们来捕获一下这个异常:

结果:

👀接下来我们来看一个用户登录的自定义异常类:

public class UserNameException extends RuntimeException{

public UserNameException(){

}

public UserNameException(String message){

super(message);

}

}

public class PassWordException extends RuntimeException{

public PassWordException(){

}

public PassWordException(String message){

super(message);

}

}

public class Login {

public static String uName="zuozuo";

public static String pWord="666666";

public static void loginInfo(String userName,String passWord){

if(!uName.equals(userName)){

throw new UserNameException("用户名错误");

}

if(!pWord.equals(passWord)){

throw new PassWordException("密码错误");

}

System.out.println("登录成功");

}

public static void main(String[] args) {

try{

loginInfo("zuozuo","666666");

}catch (UserNameException e){

e.printStackTrace();

}catch (PassWordException e){

e.printStackTrace();

}

}

}

4.异常的处理

在java中,异常处理主要的5个关键字就是:throw,try,catch,final,throws

前提:错误在代码中是客观存在的,因此我们要让程序出现问题的时候及时通知程序员.所采取的方式有以下几种.

4.1 防御式编程

😺(1)LBYL:也就是Look before you leap.在操作之前就做充分的检查,也就是事先预防性

例:

public int func() {

Scanner sc=new Scanner(System.in);

int a=sc.nextInt();

int b=sc.nextInt();

if(b==0){

System.out.println("除数为0");

return 0;

}else {

return a/b;

}

}

但是这样做的缺点是:

正常流程和错误处理流程混在了一起,代码整体显得比较混乱

😺(2)EAFP:It is easier to ask forgiveness than permission.“事后获取原来比事先获取许可更容易”,也就是先操作,遇到问题再处理.事后认错型.

例:

public int func() {

try(Scanner sc=new Scanner(System.in)){

int a=sc.nextInt();

int b=sc.nextInt();

return a/b;

}catch(ArithmeticException e){

System.out.println("除数为0");

return 0;

}

}

优点:正常流程和错误流程分离开,程序员更关注正常流程,代码更清晰,容易理解代码.

🙉那么,异常处理的核心思想就是EAFP

问题来了:

处理异常的前提是什么?

处理异常的前提是得有异常,那么怎么才能有异常,那你就得抛出(触发)异常.

那如何抛出异常呢?请看下面.

4.2 异常的抛出

在编写程序时,如果程序中出现了错误,那么就需要将错误的信息告诉调用者.

在java中,可以通过throw关键字.抛出一个指定的异常对象,将错误信息告诉调用者.

语法规则是这样的:

throw new XXXXException(“异常产生的原因”);

例:获取数组任意位置元素

public static int getElement(int[] array,int index){

if(array==null){

throw new NullPointerException("传递的数组为null");

//抛出的是一个指定的异常,经常用的方式是,抛出自定义异常

}

return array[index];

}

🧐一些注意事项:

(1).throw必须写在方法体内部.

(2).抛出的对象必须是Exception或者Exception的子类对象

(3).如果抛出的是不受查异常(RunTimeException或者RunTimeException的子类),那么可以不用处理,直接交给JVM来处理

(4).如果抛出的是编译时异常,那么用户必须处理,否则无法通过编译

(5).异常一旦抛出,后面的代码就不会执行.

第五个注意点的例子:

结果就是:在没有对异常进行处理的情况下,抛出了异常,并且异常后面的内容不会被打印出来.(也就是异常抛出之后的代码都不会执行)

😎补充:

当我们没有解决这个异常的时候,这个异常就会被交给JVM来处理,一旦交给JVM来处理,程序就崩溃了.

5.异常的捕获

异常的捕获:也就是异常的具体处理方式.主要有两种:异常声明throws和try-catch捕获处理.

5.1 异常声明throws

也就是,在方法声明时参数列表之后,当方法中抛出编译时异常,用户不想处理该异常,就可以借助throws将异常抛给方法的调用者来处理,换句话说就是:当前方法不处理异常,提醒方法的调用者处理异常.

语法格式:

方法名(参数列表) throws 异常类型1,异常类型2…{ }

例:

还需要注意的一点:

调用声明抛出异常的方法时,调用者必须对异常进行处理,或者继续使用throws抛出.

😏如果方法内部抛出了多个异常,那么throws之后必须跟多个异常类型,用逗号分隔.

例:

😏如果抛出多个异常,并且有父子关系,那么直接声明父类.

例:

😁注意事项:

(1)throws必须跟在声明的方法的参数列表之后

(2)声明的异常必须是Exception或者Exception的子类

(3)方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如果抛出的多个异常类型之间有父子关系,那么直接声明父类即可.

5.2 try-catch捕获异常并处理

当程序抛出异常时,可以通过try-catch捕获并处理异常.

语法格式:

try{ //可能出现异常的代码,try中的代码可能会抛出异常,也可能不会抛出异常 }catch(要捕获的异常类型 e){ //如果try中的代码抛出异常了,此处catch捕获到的异常类型与try中抛出的异常类型一样时,或者是try中抛出的异常的父类时,就会被捕获到 //对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行之后的代码 }catch(异常的类型 e){ //对异常进行处理 }finally{ //此处的代码一定会被执行到 } //之后的代码 //当异常被捕获到时,异常就被处理了,之后的代码一定会执行 //如果被捕获了,由于捕获的类型与抛出的异常的类型不匹配,还是会交给JVM来处理,那就没有捕获到,之后的代码也不会执行,相当于没有对抛出的异常进行正确的处理

例:

public static void main(String[] args) {

try{

int[] array=null;

System.out.println(array.length);

}catch (NullPointerException e){

System.out.println("捕获到一个空指针异常");

}

System.out.println("其他程序");

}

结果:

例:

public static void main(String[] args) {

try{

int[] array=null;

System.out.println(array.length);

}catch(ArithmeticException e){

System.out.println("捕获到空指针异常");

}

System.out.println("其他代码");

}

try-catch捕获到的不是抛出来的对应的异常,那就相当于没有对异常进行处理,那就会交给JVM来处理,后面的代码不会执行到.因为异常是按照类型来捕获的

结果:

😜如果try抛出了多个异常,就必须用多个catch来进行捕获

但是需要注意:用多个catch进行捕获,不是同时进行捕获的,因为不可能同时抛出不同的异常.

不会同时抛出两个或两个以上的异常,在同一时间,只会抛出一个异常.

例:

public static void main(String[] args) {

try{

int[] array=null;

System.out.println(array.length);

}catch(ArithmeticException e){

System.out.println("捕获到算术异常");

}catch(NullPointerException e){

System.out.println("捕获到空指针异常");

}

System.out.println("其他代码");

}

也可以简写成:

public static void main(String[] args) {

try{

int[] array=null;

System.out.println(array.length);

}catch(ArithmeticException | NullPointerException e){

System.out.println("捕获到算术异常或者空指针异常");

}

System.out.println("其他代码");

}

😁当try中存在多个异常时,从上往下执行,谁先抛出异常就捕获哪个异常. 换句话说也就是:try块内抛出异常位置之后的代码将不会执行.

catch的顺序不是捕获的顺序

![在这里插入图片描述](https://img-blog.csdnimg.cn/146cf73ce5e64a8b82fdba34ac777e7a.png

😮还有需要注意的一点是:

如果抛出的异常之间具有父子关系,那在catch时,子类异常的catch必须写在前面,父类异常的catch必须写在后面.

例:

public static void main(String[] args) {

try{

int[] array=null;

System.out.println(array.length);

}catch(NullPointerException e){

System.out.println("捕获到空指针异常");

}catch(Exception e){//可以把父类异常放在最后

System.out.println("捕获到一个异常");

}

System.out.println("其他代码");

}

结果:

来看这张图片:

这样的写法就是把父类异常的catch写在了子类异常的catch的前面,这样写代码编译就会报错.

原因是:Exception是所有异常的父类,它能捕获所有的异常,如果把他放到catch的第一个,后面的异常永远都不会捕获到,那么后续的catch没有任何作用了.

注意:

catch进行类型匹配的时候,不光会匹配相同类型的异常对象,也会捕获目标异常类型的子类对象.比如NullPointerException和ArrayIndexOutOfBoundsException都是Exception的子类,因此都能被捕获到.

5.3 finally

在写程序时,有些代码,不论程序是否发生异常,都需要执行,比如程序中打开的资源:网络连接,数据库连接,IO流,在程序正常或者异常退出时,都必须要对资源进行回收.而且,因为异常会引发程序的跳转,可能导致有些语句执行不到,finally就是用来解决这个问题的.

语法格式:

try{ //可能发生异常的代码 }catch(异常类型 e){ //对捕获到的异常进行处理 }finally{ //此处的语句无论是否发生异常,都会被执行到 } //如果没有抛出异常,或者异常被捕获处理了,这里的代码也会执行.

😜例:

public static void main(String[] args) {

Scanner sc=new Scanner(System.in);

try{

int[] array=null;

System.out.println(array.length);

}catch(NullPointerException e){

e.printStackTrace();

System.out.println("捕获到空指针异常");

}catch (ArithmeticException e){

e.printStackTrace();

System.out.println("捕获到算术异常");

}finally {

sc.close();

System.out.println("资源关闭");

}

System.out.println("其他代码");

}

结果:

以下的代码是没有抛出异常的情况:

public static void main(String[] args) {

Scanner sc=new Scanner(System.in);

try{

int[] array= {1,2,3,4};

System.out.println(array.length);

}catch(NullPointerException e){

e.printStackTrace();

System.out.println("捕获到空指针异常");

}catch (ArithmeticException e){

e.printStackTrace();

System.out.println("捕获到算术异常");

}finally {

sc.close();

System.out.println("资源关闭");

}

System.out.println("其他代码");

}

结果:

我们发现:不管是否抛出异常,finally中的代码都会执行.

😎补充一点:

资源写到try之后,finally中就不用手动close了

来看一段特殊的代码:

结果是20.如果finally中也存在return,那么就会执行finally中的return,从而不会执行到try中原有的return.

但是,一般不建议在finally中写return

🤓面试题:

1.throw和throws的区别? throw用来抛出异常,throws用来声明异常

2.finally中的语句一定会执行吗? finally中的语句一定会执行

6.异常的处理流程

😎关于调用栈:

方法之间是存在相互调用关系的,这种调用关系我们可以用"调用栈"来描述.在JVM中有一块内存空间称为"虚拟机栈",它是专门用来存储方法之间的调用关系.当代码中出现异常的时候,我们就可以用e.printStacktrace()的方式来查看出现异常的代码的调用栈.

如果本方法中没有合适的处理异常的方式,就会沿着调用栈(先进后出)向上传递.如果向上一直传递都没有处理异常,最终就会交给JVM来处理,程序就会崩溃.

例:

public static void func(){

int[] array={1,2,3,4,5};

System.out.println(array[99]);

}

public static void main(String[] args) {

try{

func();

}catch (ArrayIndexOutOfBoundsException e){

e.printStackTrace();

}

}

结果:

😶异常处理的流程:

1.程序先执行try中的代码 2.如果try中的代码抛出异常,就会结束try中的代码,然后和catch中的异常类型进行匹配 3.如果找到匹配的异常类型,就会执行catch中的代码 4.如果没有找到匹配的异常类型,就会将异常向上传递到上层调用者 5.无论是否找到匹配的异常类型,finally中的代码都会被执行 6.一直到main方法也没有代码去处理异常,就会把异常将给JVM来处理,这样程序就异常终止了.(崩溃了)

相关推荐

2024年芒果多少钱一斤
365速发登录入口

2024年芒果多少钱一斤

📅 07-13 👁️ 2229
Java 8,如何对 ArrayList 元素进行排序?
365速发登录入口

Java 8,如何对 ArrayList 元素进行排序?

📅 07-06 👁️ 5930