Java语言基础-如何让程序优雅的报错-异常处理

异常概述

在使用计算机语言进行项目开发的过程中,即使程序员把代码写的尽善尽美,在系统的运行过程中仍然会遇到一些问题,因为很多问题不是考代码就能避免的,比如:客户输入数据的格式,读取文件是否存在,网络是否始终保持通畅等。

异常:在Java语言中,将程序执行中发生的不正常的情况称为“异常”(开发过程中的语法错误与逻辑错误不叫异常)。

Java程序在执行中出现的异常分两类:

  • Error:Java虚拟机无法解决的严重问题,例如:JVM内部错误,资源耗尽等严重情况。一般不编写针对性的代码进行修复

    • 举两个例子
      • java.lang.StackOverflowError:栈溢出
      • java.lang.OutOfMemoryError:堆溢出
  • Exception:其他因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:空指针访问,视图读取不存在的文件,网络连接中断,越界等等。

异常举例

import java.util.Date;

import org.junit.Test;

import java.util.Scanner;

/**
 * java.lang.Throwable:
 *      |
 *      |------java.lang.Error:一般不编写针对性的代码进行处理
 *      |
 *      |------java.lang.Exception:可以进行异常的处理
 *              |
 *              |------编译时异常(checked)
 *              |       |   
 *              |       |------IOException  :IO异常
 *              |               |
 *              |               |------FileNotFoundException:文件或目录不存在
 *              |
 *              |------运行时异常(unchecked)
 *                      |
 *                      |------NullPointerException : 空指针异常
 *                      |
 *                      |------ArrayIndexOutOfBoundsException:数组越界
 *                      |
 *                      |------ClassCastException   : 类型转换异常
 *                      |
 *                      |------NumberFormatException:数字格式化异常
 *                      |
 *                      |------InputMismatchException: 输入异常
 *                      |
 *                      |------ArithmeticException : 算数异常
 * 
 */ 
public class ExceptionTest {
    // 空指针异常
    @Test
    public void test1(){
        int[] arr = null;
        System.out.println(arr[0]);

    }

    // 越界
    @Test
    public void test2(){
        int[] arr = new int[3];
        System.out.println(arr[4]);
    }

    // 类型转换异常
    @Test
    public void test3(){
        Object obj = new Date();
        String str = (String) obj;
        System.out.println(str);
    }

    // 数字格式化异常
    @Test
    public void test4(){
        String str = "abc";
        int num = Integer.parseInt(str);
        System.out.println(num);
    }

    // 输入异常
    @Test
    public void test5(){
        Scanner scanner = new Scanner(System.in);
        int a = scanner.nextInt();
        // 如果这里输入一个非int就会报异常
        System.out.println(a);
        scanner.close();
    }

    // 算数异常
    @Test
    public void test6(){
        int a = 10;
        int b = 0;
        System.out.println(a / b);
    }
}

异常处理

Java提供的异常处理是抓抛模型。

Java程序的执行过程中如出现异常,会生成一个异常类对象,该异常对象将被提交给Java运行系统,这个过程叫做抛出异常。

异常对象的生成分为两种,第一种是虚拟机自动生成,在程序运行过程中,虚拟机检测到程序发生问题,如果在当前代码中没有找到对应的处理程序,就由后台自动创建一个对应异常的实例对象抛出;第二种是由开发人员手动创建,如果不抛出则对程序没有任何影响。

try-catch-finally

语法:

try{
	// 可能出现异常的代码
}catch(异常类型1 变量名1){
	// 处理异常1
}catch(异常类型2 变量名2){
	// 处理异常2
}finally{
	// 一定会执行的代码
}

例子:

public class TryTest {
    public static void main(String[] args) {
        /**
         * finally 是可选的
         * 
         * 使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应的异常对象,
         * 然后去catch中匹配异常,如果匹配成功就执行catch块中的代码,如果匹配失败则抛出异常。
         * 
         * try-catch-finally结构运行完成后,继续执行后面的代码,如果catch中报错,只能执行finally中的代码
         * 
         * 优先度高的异常应该在优先度低的异常的后面(子类异常必须在父类异常之前)
         * 
         * 常用的异常对象处理方式:①String getMessage()     ②printStackTrace()
         * 
         * try-catch-finally结构内的变量是局部变量
         * 
         * finally中的代码一定执行
         * 
         * 一般会将数据库连接、输入输出流、socket等资源的释放操作放到finally中
         * 
         * try-catch-finally结构可以相互嵌套
         */
        try{
            String str = "abc";
            int num = Integer.parseInt(str);
            System.out.println(num);
        }catch(NumberFormatException e){
            System.out.println(e.getMessage());
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            System.out.println("执行成功");
        }
    }
}

体会1:使用try-catch-finally结构处理编译时异常,使得程序在编译时就不再报错,但是运行时仍可能报错,相当于我们使用try-catch-finally结构将一个编译时出现的异常延迟到运行时出现。

体会2:开发时,因为运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally结构,针对编译时异常,一定要考虑异常的处理。

throws

throws一般用于方法的定义中,表示抛出异常,谁调用这个方法就让谁来解决这个异常。



public class ThrowsTest {
    // 这里调用了test方法,所以他接收异常来处理,而且他是main方法,必须处理异常。
    public static void main(String[] args) {
        try{
            test();
        }catch(ArithmeticException e){
            e.printStackTrace();
        }
        
        
        // 因为test2内已经处理了异常,所以main可以直接调用
        test2();
        
        // 方法重写的抛出异常
        test3(new SubClass());

    }

    public static void test3(SuperClass s){
        try{
            // 如果subclass的method方法抛出的异常比superclass抛出的异常还大,有可能造成子类的异常无法被处理
            s.method();
        }catch(ArithmeticException e){
            e.printStackTrace();
        }
    }

    public static void test2(){
        try{
            test();
        }catch(ArithmeticException e){
            e.printStackTrace();
        }
    }


    // 报异常后不自己处理,抛出异常
    public static void test() throws ArithmeticException{
        int a = 10;
        int b = 0;
        System.out.println(a / b);
    }
}

class SuperClass {
    public void method() throws ArithmeticException{
        
    }
}

class SubClass extends SuperClass{
    @Override
    // 可以声明 ArithmeticException 同级别的异常,但是不能声明他的父类级别的异常  例如 Exception
    public void method() throws ArithmeticException{
        
    }
}
手动抛出异常
public class StudentTest {
    public static void main(String[] args) {
        Student s = new Student();
        try{
            // 传入负数,程序抛出自定义异常。
            // 传入正数,无异常
            s.regist(-100); 
            System.out.println(s);
        }catch(Exception e){
            // 处理异常
            System.out.println(e.getMessage());
        }
    }
}


class Student{
    private int id;
    
    public void regist(int id){
        if(id > 0){
            this.id = id;
        }else{
            // 手动抛出异常
            throw new RuntimeException("输入的数据非法");
        }
    }

    @Override
    public String toString() {
        return "Student [id=" + id + "]";
    }
}
自定义异常

我们使用的异常,哪怕是手动抛出的异常,都是Java为我们提供好的,那么我们能不能自定义异常呢?

/**
 * 首先继承与现有的异常结构
 * 必须提供全局变量serialVersionUID
 * 提供重载的构造器
 */

public class MyException extends RuntimeException{
    // 自动导入的一个静态属性
    private static final long serialVersionUID = -7034897196939L;

    public MyException(){

    }

    public MyException(String msg){
        super(msg);
    }
}

这样就算自定义了一个异常,然后把他放到上一个例子里。

public class StudentTest {
    public static void main(String[] args) {
        Student s = new Student();
        try{
            s.regist(-100); 
            System.out.println(s);
        }catch(RuntimeException e){
            // 处理异常
            System.out.println(e.getMessage());
        }
    }
}


class Student{
    private int id;
    
    public void regist(int id) throws Exception{
        if(id > 0){
            this.id = id;
        }else{
            // 手动抛出异常
            throw new MyException("输入的数据非法");
        }
    }

    @Override
    public String toString() {
        return "Student [id=" + id + "]";
    }
}

练习

判断运行结果

public class ReturnExceptionDemo {
	static void methodA() {
		try {
			System.out.println("进入方法A");
			throw new RuntimeException("制造异常");
		}finally {
			System.out.println("用A方法的finally");
		} 
	}
	
	static void methodB() {
		try {
			System.out.println("进入方法B");
			return;
		} finally {
			System.out.println("调用B方法的finally");
		}
	}

	public static void main(String[] args) {
		try {
			methodA();
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		methodB();
	}
}

----------
进入方法A
用A方法的finally
制造异常
进入方法B
调用B方法的finally

编写应用程序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;

/**
 * 编写应用程序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;
 */

public class EcmDef {
    public static void main(final String[] args) {
        try{
            final int i = Integer.parseInt(args[0]);
            final int j = Integer.parseInt(args[1]);
    
            final int result = ecm(i, j);
            System.out.println(result);
        }catch(NumberFormatException e){
            System.out.println("数据类型不一致");
        }catch(ArrayIndexOutOfBoundsException e){
            System.out.println("缺少命令行参数");
        }catch(ArithmeticException e){
            System.out.println("除0");
        }catch(EcDef e){
            System.out.print(e.getMessage());
        }
    }

    public static int ecm(int i, int j) throws EcDef {
        if(i < 0 || j < 0){
            throw new EcDef("分子或分母是负数了");
        }
        return i / j ;
    }
}


class EcDef extends Exception {

    private static final long serialVersionUID =  -3387516993124229948L;

    public EcDef(){

    }

    public EcDef(String msg){
        super(msg);
    }
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

寒 暄

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

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

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

打赏作者

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

抵扣说明:

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

余额充值