【Java进阶篇】—— 异常处理

一、异常概述

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();
    }
    //StackOverFlowError
    public 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) {
        //NullPointerException
        String s = null;
        System.out.println(s.length());
        //ClassCastException[类型转换异常] >> 为什么Integer不能转换成String类型
        //因为存在父子关系的类才能进行强转,Integer的父类是Number,String的父类是Object
        Object o = new Integer(6); //表面编译类型是Object,但是运行类型是Integer
        String str = (String)o;
        //ArrayIndexOutOfBoundsException
        int [] arr = {1, 3, 4};
        System.out.println(arr[3]);
        //InputMismatchException
        Scanner sc = new Scanner(System.in);
        int x = sc.nextInt(); //输入的时候故意输入一个其他类型的数据
        //ArithmeticException
        int 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 {
    @Test
    public void test06() {
        Thread.sleep(1000);//休眠1秒  InterruptedException
    }

    @Test
    public void test07(){
        Class c = Class.forName("java.lang.String");//ClassNotFoundException
    }

    @Test
    public void test08() {
        Connection conn = DriverManager.getConnection("....");  //SQLException
    }
    @Test
    public void test09()  {
        FileInputStream fis = new FileInputStream("尚硅谷Java秘籍.txt"); //FileNotFoundException
    }
    @Test
    public void test10() {
        File file = new File("尚硅谷Java秘籍.txt");
		FileInputStream fis = new FileInputStream(file);//FileNotFoundException
		int b = fis.read();//IOException
		while(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){
		//异常的处理方式1
		System.out.println("不好意思,亲~出现了小问题,正在加紧解决...");	
	}catch(ClassCastException e){
		//异常的处理方式2
		System.out.println("出现了类型转换的异常");
	}catch(RuntimeException e){
		//异常的处理方式3
		System.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{
    @Override
    public 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;
    }

    @Override
    public 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);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bow.贾斯汀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值