【Java进阶篇】—— 异常处理
创始人
2024-06-03 02:56:25
0

一、异常概述

1、什么是异常?

程序在执行的过程中出现的非正常情况,如果不处理最终会导致JVM的非正常停止。

对于语法错误(导致编译不能通过)和逻辑错误(导致无法达到预期效果)不属于异常

2、异常的抛出机制

Java中把不同的异常用不同类表示(异常属于一个对象),一旦发生某种异常,就创建该异常类型的对象,并且抛出(throw)。然后程序员可以捕获(catch)到这个异常对象,并处理;相反如果没有捕获到这个异常对象,它就会导致程序终止。

3、如何对待异常?

(1)遇到错误就终止程序运行

(2)添加异常检测与异常处理来保证程序的健壮性(预处理)

二、Java异常体系


  • java.lang.Throwable 类是Java异常类根类

    • public void printStackTrace():打印异常信息【异常的类型、原因、出现位置】
    • public String getMessage():获取异常发生的原因
  • Throwable 类具有两个子类 java.lang.Error 和 java.lang.Exception

    • 前者属于Java虚拟机无法解决的严重问题,例如:JVM系统内部错误、资源耗尽等严重情况,一般不编写代码处理
    • 后着属于因编程错误或偶然的外在因素导致的一般性问题,列如:空指针访问、读取不存在的文件、网络连接中断、数组角标越界

异常根据程序可能出现的阶段分为:

  • 编译时期异常(即checked异常、受检异常):在代码编译阶段,编译器就能明确警示当前代码可能发生(不是一定发生)xx异常,并明确督促程序员提前编写处理它的代码。如果程序员没有编写对应的异常处理代码,则编译器就会直接判定编译失败,从而不能生成字节码文件。通常,这类异常的发生不是由程序员的代码引起的,或者不是靠加简单判断就可以避免的,例如:FileNotFoundException(文件找不到异常)。
  • 运行时期异常(即runtime异常、unchecked异常、非受检异常):在代码编译阶段,编译器完全不做任何检查,无论该异常是否会发生,编译器都不给出任何提示。只有等代码运行起来并确实发生了xx异常,它才能被发现。通常,这类异常是由程序员的代码编写不当引起的,只要稍加判断,或者细心检查就可以避免。

请添加图片描述

三、常见的错误和异常

1、 Error 常见错误:

package com.zwh.shangguigu;/*** @author Bonbons* @version 1.0*/
public class Main {public static void main(String[] args) {fun();}//StackOverFlowErrorpublic static void fun(){fun();}
}

请添加图片描述

package com.zwh.shangguigu;import java.util.HashSet;
import java.util.Set;/*** @author Bonbons* @version 1.0*/
public class Main {public static void main(String[] args) {//OutOfMemoryError//方法一:直接创建一个超大内存的数组int [] arr = new int[Integer.MAX_VALUE];//方法二:创建一个动态的容器,然后无限添加元素StringBuilder s = new StringBuilder();while(true){s.append("atguigu");}}}

请添加图片描述

2、 常见的运行时异常:

package com.zwh.shangguigu;import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;/*** @author Bonbons* @version 1.0*/
public class Main {public static void main(String[] args) {//NullPointerExceptionString s = null;System.out.println(s.length());//ClassCastException[类型转换异常] >> 为什么Integer不能转换成String类型//因为存在父子关系的类才能进行强转,Integer的父类是Number,String的父类是ObjectObject o = new Integer(6); //表面编译类型是Object,但是运行类型是IntegerString str = (String)o;//ArrayIndexOutOfBoundsExceptionint [] arr = {1, 3, 4};System.out.println(arr[3]);//InputMismatchExceptionScanner sc = new Scanner(System.in);int x = sc.nextInt(); //输入的时候故意输入一个其他类型的数据//ArithmeticExceptionint y = 1 / 0;}
}

3、常见的编译时异常

package com.atguigu.exception;import org.junit.Test;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;public class TestCheckedException {@Testpublic void test06() {Thread.sleep(1000);//休眠1秒  InterruptedException}@Testpublic void test07(){Class c = Class.forName("java.lang.String");//ClassNotFoundException}@Testpublic void test08() {Connection conn = DriverManager.getConnection("....");  //SQLException}@Testpublic void test09()  {FileInputStream fis = new FileInputStream("尚硅谷Java秘籍.txt"); //FileNotFoundException}@Testpublic void test10() {File file = new File("尚硅谷Java秘籍.txt");FileInputStream fis = new FileInputStream(file);//FileNotFoundExceptionint b = fis.read();//IOExceptionwhile(b != -1){System.out.print((char)b);b = fis.read();//IOException}fis.close();//IOException}
}

四、异常处理

  • 在编写程序时,在可能出现错误的地方加上检测的代码
  • Java采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,是程序简洁、有呀,并易于维护。
  • 主要分为两种方式:
    • try-catch-finally(捕获异常)
    • throws + 异常类型(抛出异常)

(1)利用 try-catch-finally 代码块捕获异常

  • Java提供了异常处理的抓抛模型:
    • Java程序在执行过程中出现了异常,会产生一个异常类的对象,该对象被提交给Java运行时系统的过程——称为抛出(throws)异常
    • 如果一个方法内抛出异常,该异常会被抛给调用者的方法中处理。如果调用者方法中没有进行处理,他会被抛给调用方法的上层方法,这个过程会一直进行下去,直到异常被处理,这一过程称为捕获(catch)异常
    • 如果一个异常回到main()方法,并且main()也没有处理,那么程序运行终止
try{......	//可能产生异常的代码
}
catch( 异常类型1 e ){......	//当产生异常类型1型异常时的处置措施
}
catch( 异常类型2 e ){...... 	//当产生异常类型2型异常时的处置措施
}  
finally{...... //无论是否发生异常,都无条件执行的语句
} 
  • 整体执行过程:

当某段代码可能发生异常,不管这个异常是编译时异常(受检异常)还是运行时异常(非受检异常),我们都可以用try块将它括起来,并在try块下面编写catch分支尝试捕获对应的异常对象。

  • 执行规律:

    • 如果try的代码块中没有发生异常,那么catch分支不会被执行
    • 如果try的代码块中发生了异常,那么try代码块从发生异常的位置之后的代码不会执行,如果catch分支有对应的异常则会跳转过去。接下来执行catch分支中对异常处理的代码
    • 无论是否发生异常,finally代码块中的内容都会被执行,但是finally代码块不能单独使用
  • 举例:

    • 利用try语句块选定捕获异常的范围,将可能出现异常的业务逻辑代码放到try语句中
    • catch(Exceptiontype e)
      • catch分支,分为两个部分,catch()中编写异常类型和异常参数名,{}中编写如果发生了这个异常,要做什么处理的代码。
      • 如果明确知道产生的是何种异常,可以用该异常类作为catch的参数;也可以用其父类作为catch的参数。
      • 比如:可以用ArithmeticException类作为参数的地方,就可以用RuntimeException类作为参数,或者用所有异常的父类Exception类作为参数。但不能是与ArithmeticException类无关的异常,如NullPointerException(catch中的语句将不会执行)。
      • 每个try语句块可以伴随一个或多个catch语句,用于处理可能产生的不同类型的异常对象。
      • 如果有多个catch分支,并且多个异常类型有父子类关系,必须保证小的子异常类型在上,大的父异常类型在下。否则,报错。
      • catch中常用异常处理的方式
        • public String getMessage():获取异常的描述信息,返回字符串
        • public void printStackTrace():打印异常的跟踪栈信息并输出到控制台。包含了异常的类型、异常的原因、还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace()。

@Test
public void test1(){try{String str1 = "atguigu.com";str1 = null;System.out.println(str1.charAt(0));}catch(NullPointerException e){//异常的处理方式1System.out.println("不好意思,亲~出现了小问题,正在加紧解决...");	}catch(ClassCastException e){//异常的处理方式2System.out.println("出现了类型转换的异常");}catch(RuntimeException e){//异常的处理方式3System.out.println("出现了运行时异常");}//此处的代码,在异常被处理了以后,是可以正常执行的System.out.println("hello");
}

插入一个经典笔试题: final、finally、finalize 之间得区别如下:
1、final可以修饰类,变量,方法,修饰的类不能被继承,修饰的变量不能重新赋值,修饰的方法不能被重写。
2、finally用于抛异常,finally代码块内语句无论是否发生异常,都会在执行finally,常用于一些流的关闭。
3、finalize方法用于垃圾回收。一般情况下不需要我们实现finalize,当对象被回收的时候需要释放一些资源,比如socket链接,在对象初始化时创建,整个生命周期内有效,那么需要实现finalize方法,关闭这个链接。但是当调用finalize方法后,并不意味着GC会立即回收该对象,所以有可能真正调用的时候,对象又不需要回收了,然后到了真正要回收的时候,因为之前调用过一次,这次又不会调用了,产生问题。所以不推荐使用finalize方法。

  • 异常处理的体会:
    • 对于RuntimeException类或是他的子类(运行时异常),编译能通过就不不提前捕获Java自己也能捕获到,但是运行的时候会导致程序终止。所以对于这类异常可以不做处理,因为这类异常很普通,如全处理可能会对程序的可读性和运行效率产生影响。
    • 如果是非运行时异常(例如:IOException),则必须捕获。否则编译错误无法运行,我们要对其进行捕获转化为运行时异常

(2)采用声明抛出异常类型(throws)

  • 在编写方法体的代码时,某句代码可能发生某个编译时异常,不处理编译不通过,但是在当前方法中可能不适合处理 或 无法给出合理的处理方法,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。

  • throws 异常列表

public void readFile(String file) throws FileNotFoundException, IOException{FileInputStream fis = new FileInputStream(file);
}

编译时异常举例:

public class TestThrowsCheckedException{public static void main(String [] args){System.out.println("上课......");try{afterClass();}catch(InterruptedException e){e.printStackTrace();System.out.println("准备提前上课");}System.out.println("上课......");}public static void afterClass() throws InterruptedException{for(int i = 10; i >= 1; i--){Thread.sleep(1000);System.out.println("距离上课还有:" + i + "分钟");}}
}

运行时异常举例

import java.util.InputMismatchException;
import java.util.Scanner;public class TestThrowsRuntimeException {public static void main(String[] args) {Scanner input = new Scanner(System.in);try {System.out.print("请输入第一个整数:");int a = input.nextInt();System.out.print("请输入第二个整数:");int b = input.nextInt();int result = divide(a,b);System.out.println(a + "/" + b +"=" + result);} catch (ArithmeticException | InputMismatchException e) {e.printStackTrace();} finally {input.close();}}public static int divide(int a, int b)throws ArithmeticException{return a/b;}
}
  • 在方法重写中throws的要求:

(1)方法名必须相同
(2)形参列表必须相同
(3)返回值类型
- 基本数据类型和void:必须相同
- 引用数据类型:<=
(4)权限修饰符:>=,而且要求父类被重写方法在子类中是可见的
(5)不能是static,final修饰的方法

import java.io.IOException;class Father{public void method()throws Exception{System.out.println("Father.method");}
}
class Son extends Father{@Overridepublic void method() throws IOException,ClassCastException {System.out.println("Son.method");}
}
  • 对于两种异常处理方式的选择:(此处的异常指的是编译时异常)
    • 如果程序中涉及资源的调用(流、数据库连接、网络连接等),必须采用try-catch-finally的方式来处理,避免内存泄漏
    • 如果父类被重写的方法没有throws异常类型,则子类重写的方法中如果出现异常,只能考虑使用try-catch-finally进行处理,不能throws
    • 在开发中,如果几个方法存在递进调用关系,那么在内层的方法我们一般直接throws,最外层的方法使用try-catch-finally处理

五、我们还可以手动抛出异常:throw

  • Java中异常对象的生成方式有两种:

    • 由虚拟机自动生成:程序运行过程中,虚拟机检测到程序发生了问题,那么会针对当前代码在后台创建一个对应异常类的实力对象并抛出
    • 由开发人员手动创建:new 异常类型([实参列表]),如果创建好的异常对象不抛出则对程序没有任何影响,和创建一个普通的对象一样。
  • throw语句抛出的异常对象和JVM自动创建并抛出的异常对象相同:

    • 如果是编译时的异常对象,需要使用try-catch-finallythrows处理,否则会导致编译不通过【Throwable或其子类的实例】
    • 如果是运行时的异常对象,尽管编译器不提示,但是如果不进行处理也会导致程序崩溃
    • 对于throw语句会明确的抛出一个异常对象,因此其后面的代码不会继续执行,类似于return

package com.atguigu.keyword;public class TestThrow {public static void main(String[] args) {try {System.out.println(max(4,2,31,1));} catch (Exception e) {e.printStackTrace();}try {System.out.println(max(4));} catch (Exception e) {e.printStackTrace();}try {System.out.println(max());} catch (Exception e) {e.printStackTrace();}}public static int max(int... nums){if(nums == null || nums.length==0){throw new IllegalArgumentException("没有传入任何整数,无法获取最大值");}int max = nums[0];for (int i = 1; i < nums.length; i++) {if(nums[i] > max){max = nums[i];}}return max;}
}

六、自定义异常

(1)为什么需要自定义异常类?

Java总不同的异常类,分别表示着某一种具体的异常情况。那么在开发中总有一些异常是核心类库中没有的,所以要想处理这些异常就需要我们自定义。

(2)如何自定义异常类?

  • 首先需要我们定义的异常类继承一个异常类型
    • 定义的是编译时异常类型就需要继承 java.lang.Exception
    • 定义的是运行时异常类型就需要继承 java.lang.RuntimeException
  • 其次,我们最好提供两个构造器(无参+有参)
  • 最后,自定义异常需要提供 serialVersionUID【序列化一个类时用的版本号】

(3)注意事项:

  • 自定义异常最重要的是异常类的名字和message属性。当异常出现时,可以根据名字判断异常类型
  • 自定义异常对象只能(throw)手动抛出,可以通过throws进一步抛出交给调用者处理,调用者通过处理

package com.zwh.shangguigu;
public class Main{public static void main(String[] args) {Triangle t = null;//在创建三角形的时候可能会发生异常try{t = new Triangle(2, 2, 3);System.out.println("三角形创建成功");System.out.println(t);}catch (NotTriangleException e){System.out.println("三角形创建失败");e.printStackTrace();}//在修改边的时候也可能会发生异常try{if(t != null){t.setA(1);}System.out.println("a边修改成功");}catch (NotTriangleException e){System.out.println("a边修改失败");e.printStackTrace();}}
}
//自定义异常类
class NotTriangleException extends Exception{//两个构造方法public NotTriangleException(){}public NotTriangleException(String message){super(message);}
}//定义一个三角形类
class Triangle{//三条边private double a, b, c;public Triangle(){}public Triangle(double a, double b, double c) throws NotTriangleException{if(a <= 0 || b <= 0 || c <= 0){throw new NotTriangleException("三角形的三条边必须都是正数");}if(a + b <= c || a + c <= b || b + c <= a){throw new NotTriangleException("不满足三角形的任意两边之和大于第三边");}//数据符合规定this.a = a;this.b = b;this.c = c;}//为私有成员变量提供get和set方法public double getA() {return a;}public double getB() {return b;}public double getC() {return c;}public void setA(double a) throws NotTriangleException{if(a <= 0){throw new NotTriangleException("三角形的任意边都为正数");}if(a + b <= c || a + c <= b || b + c <= a){throw new NotTriangleException("不满足三角形的任意两边之和大于第三边");}this.a = a;}public void setB(double b) throws NotTriangleException{if(b <= 0){throw new NotTriangleException("三角形的任意边都为正数");}if(a + b <= c || a + c <= b || b + c <= a){throw new NotTriangleException("不满足三角形的任意两边之和大于第三边");}this.b = b;}public void setC(double c) throws NotTriangleException{if(c <= 0){throw new NotTriangleException("三角形的任意边都为正数");}if(a + b <= c || a + c <= b || b + c <= a){throw new NotTriangleException("不满足三角形的任意两边之和大于第三边");}this.c = c;}@Overridepublic String toString() {return "Triangle:{" +"a=" + a +"b=" + b +"c=" + c +"}";}
}

请添加图片描述

补充案例:

package com.zwh.shangguigu.exception_;import java.util.Scanner;/*** @author Bonbons* @version 1.0* 编写应用程序EcmDef.java,接收命令行的两个参数,要求不能输入负数,计算两数相除。* 对数据类型不一致(NumberFormatException)、缺少命令行参数(ArrayIndexOutOfBoundsException、* 除0(ArithmeticException)及输入负数(EcDef 自定义的异常)进行异常处理。* 提示:* (1)在主类(EcmDef)中定义异常方法(ecm)完成两数相除功能。* (2)在main()方法中使用异常处理语句进行异常处理。* (3)在程序中,自定义对应输入负数的异常类(EcDef)。* (4)运行时接受参数 java EcmDef 20 10 //args[0]=“20” args[1]=“10”* (5)Interger类的static方法parseInt(String s)将s转换成对应的int值。* 如:int a=Interger.parseInt(“314”); //a=314;*//*** 代码存在的问题:没有通过控制台的args[]数组输入两个参数,而是采用Scanner输入的 >> 已解决*/
public class EcmDef {public static double ecm(double a, double b){if(b == 0.0){throw new ArithmeticException();}return a / b;}public static void main(String[] args) {
//        Scanner sc = new Scanner(System.in);try{
//            String num1 = sc.next();
//            String num2 = sc.next();double a = Integer.parseInt(args[0]);double b = Integer.parseInt(args[1]);if(a < 0 || b < 0){throw new EcDef("数据为非负数");}//NumberFormatException异常出现在数据类型转换的时候double res = ecm(a, b);System.out.println(a + " / " + b + " = " + res);}catch (EcDef | ArrayIndexOutOfBoundsException | NumberFormatException | ArithmeticException e){e.printStackTrace();}}
}class EcDef extends Exception{public EcDef(){}public EcDef(String message){super(message);}
}

相关内容

热门资讯

122.(leaflet篇)l... 听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行...
育碧GDC2018程序化大世界... 1.传统手动绘制森林的问题 采用手动绘制的方法的话,每次迭代地形都要手动再绘制森林。这...
育碧GDC2018程序化大世界... 1.传统手动绘制森林的问题 采用手动绘制的方法的话,每次迭代地形都要手动再绘制森林。这...
Vue使用pdf-lib为文件... 之前也写过两篇预览pdf的,但是没有加水印,这是链接:Vu...
PyQt5数据库开发1 4.1... 文章目录 前言 步骤/方法 1 使用windows身份登录 2 启用混合登录模式 3 允许远程连接服...
Android studio ... 解决 Android studio 出现“The emulator process for AVD ...
Linux基础命令大全(上) ♥️作者:小刘在C站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维...
再谈解决“因为文件包含病毒或潜... 前面出了一篇博文专门来解决“因为文件包含病毒或潜在的垃圾软件”的问题,其中第二种方法有...
南京邮电大学通达学院2023c... 题目展示 一.问题描述 实验题目1 定义一个学生类,其中包括如下内容: (1)私有数据成员 ①年龄 ...
PageObject 六大原则 PageObject六大原则: 1.封装服务的方法 2.不要暴露页面的细节 3.通过r...
【Linux网络编程】01:S... Socket多进程 OVERVIEWSocket多进程1.Server2.Client3.bug&...
数据结构刷题(二十五):122... 1.122. 买卖股票的最佳时机 II思路:贪心。把利润分解为每天为单位的维度,然后收...
浏览器事件循环 事件循环 浏览器的进程模型 何为进程? 程序运行需要有它自己专属的内存空间࿰...
8个免费图片/照片压缩工具帮您... 继续查看一些最好的图像压缩工具,以提升用户体验和存储空间以及网站使用支持。 无数图像压...
计算机二级Python备考(2... 目录  一、选择题 1.在Python语言中: 2.知识点 二、基本操作题 1. j...
端电压 相电压 线电压 记得刚接触矢量控制的时候,拿到板子,就赶紧去测各种波形,结...
如何使用Python检测和识别... 车牌检测与识别技术用途广泛,可以用于道路系统、无票停车场、车辆门禁等。这项技术结合了计...
带环链表详解 目录 一、什么是环形链表 二、判断是否为环形链表 2.1 具体题目 2.2 具体思路 2.3 思路的...
【C语言进阶:刨根究底字符串函... 本节重点内容: 深入理解strcpy函数的使用学会strcpy函数的模拟实现⚡strc...
Django web开发(一)... 文章目录前端开发1.快速开发网站2.标签2.1 编码2.2 title2.3 标题2.4 div和s...