corejava-笔记——异常

1 异常

1. 1 概述

程序在运行过程中,由于意外情况导致程序发生异常事件,默认情况下发生的异常会中断程序的运行。

在Java中,把常见的异常情况,都抽象成了对应的异常类型,那么每种异常类型都代表了一种特定的异常情况。

当程序中出现一种异常情况时,也会创建并抛出一个异常类型对象,这个异常就表示当前程序所出现的问题。

例:
使用下标从数组中取值时,这个下标值超过了数组下标的最大值,那么程序就出现了异常情况,Java中把这种情况抽象成一个类:java.lang.ArrayIndexOutOfBoundsException,将来这个类的对象,就表示程序中出现了数组下标越界的异常情况。

public class Test {

    public static void main(String[] args) {
        int[] arr = {1,3,5,7};
        System.out.println(arr[2]);
        //这行代码执行时,出现异常情况,因为下标超过了数组的最大边界
        System.out.println(arr[4]);
    }

}
//运行结果:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 4
	at com.briup.demo.Test.main(Test.java:7)

当前程序出现异常时,会创建并抛出和该异常情况对应的异常类的对象,这个异常对象中保存了一些信息,用来表示当前程序到底发生了什么异常情况。
通过异常信息,我们可以定位异常发生的位置,以及异常发生的原因。

1.2 异常体系

异常体系中的根类:java.lang.Throwable,该类下有两个子类,java.lang.Errorjava.lang.Exception
在这里插入图片描述

  • Error,表示错误情况,一般是程序中出现了比较严重的问题,并且程序自身并无法进行处理
  • Exception,表示异常情况,程序中出现了这种异常,大多是可以通过特定的方式进行处理和纠正的,并且处理完了之后,程序还可以继续往下正常运行。

我们常说的异常,指的是Exception

Exception中并没有定义方法,他的方法都是从Throwable中继承过来的,其中常用的方法有:

  • printStackTrace(),打印输出当前发送异常的详细信息
  • getMessage(),返回异常对象抛出时携带的信息,一般是异常的发生原因。(以上述数组越界结果为例,调用该方法得到的是4)
  • printStackTrace(PrintWriter s),方法重载,可以指定字符输出流,对异常信息进行输出
  • printStackTrace(PrintStream s),方法重载,可以指定字节输出流,对异常信息进行输出

1.3 异常种类

我们平常使用的异常类型,都是Exception类的子类型,他们把异常划分成了两种:

  • 编译时异常
  • 运行时异常

1.3.1 运行时异常

运行时异常,继承自RuntimeException类,也称为unchecked exception,编译器在编译期间,不会检查这种异常,也不要求我们去处理,但是在运行期间,代码中可能会抛出这种类型的异常。
常见的运行时异常:

  • NullPointerException(空指针异常)
  • ArrayIndexOutOfBoundsException(数组越界异常)
  • ArithmeticException(算术异常)
  • ClassCastException
    类类型转换异常
  • NumberFormatException
    字符串转数字异常

1.3.2 编译时异常

编译时异常,继承自Exception类,也称为checked exception,编译器在编译期间,会主动检查这种异常,发现后报错并提示我们要对这种异常进行处理。

在这里插入图片描述
ClassNotFoundException是编译时异常,直接继承Exception类,上图就是编译时异常的情况,此时需要向外抛异常才会编译通过。如下:
在这里插入图片描述

在这里插入图片描述

1.4 异常传播

如果一个方法中抛出了异常,并且一直没有进行处理,那么这个异常将会抛给当前方法的调用者,并一直向上抛给JVM,最后JVM将这个异常信息打印输出,同时程序运行停止。

在这里插入图片描述> 由上图可知,test3方法中抛出了异常,因为是运行时异常,编译器不会检查,这个异常在程序中没有处理,那么这个异常就抛给了test2,test2方法又将异常抛给了test1,test1又将异常抛给main方法,mian方法将异常抛给JVM,最后JVM将当前发生异常时,占内存中方法的调用情况,打印输出,方便我们定位错误信息,之后JVM停止运行。
在这里插入图片描述

test3方法中的第20行代码抛出异常,导致test2方法中的第16行也报错,因为test2方法中调用了test3方法
test2方法中的第16行代码报错,导致test1方法的第12行也报错,因为test1方法中调用了test2方法
test1方法中的第712行报错,导致main方法中的第7行也报错,因为main方法调用了test1方法
这时,test3方法中抛出的异常对象,就传播到了main方法中,main中也没有处理,那么就把这个异常抛给JVM,那么JVM就打印输出异常信息,然后JVM停止运行
所以程序最后停止在main方法中的第7行,test1方法的调用这句代码,下面的world也没有输出,因为JVM停止了,代码就不再往下执行了!

2 异常抛出

2.1 自动抛出异常

当前java代码中,出现了提前指定好的异常情况的时候,代码会自动创建异常对象,并且将该异常对象抛出。
例如:当代码中执行int a = 1 / 0;的时候,代码会自动创建并抛出ArithmeticException类型的异常对象,来表示当前的这种异常情况。(算术异常)
例如:当代码中执行String str = null; str.toString();的时候,代码会自动创建并抛出NullPointerException类型的异常对象,来表示当前这种异常情况(空指针异常)

2.2 手动抛出异常

以上描述的异常情况,都是JVM中提前规定好的,我们不需要干预,JVM内部自己就会创建并抛出异常对象。
但是在其他的一些情况下,我们也可以手动的创建并抛出异常对象,效果一样。

例:

	public void test1(String name) {
		if (!"tom".equals(name)) {
			throw new RuntimeException("名字和预期不符!");
		}
	}

注意,因为方法中抛出的是一个运行时异常,编译器不会检查,所以代码可以正常编译运行,但是运行的时候,name的值不是tom的时候,代码会报错,这个错误信息是我们自己抛出的。

例:

	public void test(String name) throws Exception {
		if (!"tom".equals(name)) {
			throw new Exception("用户名和预期不符!");
		}
	}

使用throws关键字,声明方法所抛出的异常类型即可
这个声明的目的,就是告诉test方法的调用者,当前调用的这个test方法的时候要小心,放在运行的时候可能会抛出Exception异常。

3 异常捕获

3.1 概述

当一个方法内,抛出了编译异常的时候,编译器在编译期间检查到,就会报错,提示我们有两种修改方案:

  • 把这个异常在方法上进行声明抛出
  • 把这个异常在方法内进行捕获处理

例1:声明抛出

	public void test1(String className) throws ClassNotFoundException {
		Class.forName(className);
	}

将来谁调用了test1方法,谁就要处理这个异常情况

例2:捕获处理

	public void test2(String className) {
		try {
			Class.forName(className);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

这里使用了try-catch语句块,对可能抛出异常的代码进行异常捕获处理

3.2 try-catch

try-catch语句块,就是用来对指定代码,进行异常捕获处理,并且处理完成后,JVM不会停止运行,代码还可以正常的往下运行。

语法:

try{
	//编写可能会出现异常的代码
}catch(异常类型 e){
	//处理异常的代码,可以是简单地输出异常信息,也可以使用日志进行记录,也可以对数据进行修改纠正等操作
}

例:

package com.test.demo1;

public class Test3 {
	public static void main(String[] args) {
		System.out.println("hello");
		Test3 test3 = new Test3();
		try {
			test3.test("nana");
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println(e.getMessage());
		}

		System.out.println("world");
	}

	public void test(String name) throws Exception {
		if (!"tome".equals(name)) {
			throw new Exception("用户名不正确");
		}
	}
}
运行结果:
hello
java.lang.Exception: 用户名不正确
	at com.test.demo1.Test3.test(Test3.java:19)
	at com.test.demo1.Test3.main(Test3.java:8)
用户名不正确
world

3.3 捕获多种异常

如果try语句块中的多句代码,都会抛出异常,并且是不同类型的异常,那么catch语句块就有不同的写法,来处理这几个不同类型的异常

例1:

使用一个catch,里面使用 | 来表示捕获多种不同异常类型

	public static void main(String[] args) {
		String className = "java.lang.Object";
		String methodName = "toString";
		
		
		try {
			//forName声明抛出ClassNotFoundException
			Class c = Class.forName(className);
			//getMethod方法声明抛出NoSuchMethodException
			Method method = c.getMethod(methodName);
			//invoke方法声明抛出IllegalAccessException和InvocationTargetException
			method.invoke(null);
		} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
			e.printStackTrace();
		}
	}

例2:

使用多个catch语句,分别对不同的异常类型进行捕获处理

注意,这种异常处理方式,要求多个catch中的异常不能相同,并且如果catch中的多个异常之间有子父类异常关系的话,那么子类异常要求在父类异常的catch处理之上。
因为如果父类型异常在最上面的话,下面catch语句块就不会被执行

public static void main(String[] args) {


    String className = "com.briup.demo.Student";
    String methodName = "sayHello";

    try {
        //forName声明抛出ClassNotFoundException
        Class c = Class.forName(className);
        //getMethod方法声明抛出NoSuchMethodException
        Method m = c.getMethod(methodName);
        //invoke方法声明抛出IllegalAccessException和InvocationTargetException
        m.invoke(null);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }

}

例3:

使用一个catch语句,但是捕捉的异常类型是Exception,它是最大的异常类型,由于多态的原因,Exception类型的引用e,可以捕获接收到任意类型的异常对象

	public static void main(String[] args) {
		String className = "com.briup.test1.Student";
		String methodName = "sayHello";

		try {
			// forName声明抛出ClassNotFoundException
			Class class1 = Class.forName(className);
			// getMethod方法声明抛出NoSuchMethodException
			Method method = class1.getMethod(methodName, null);
			// invoke方法声明抛出IllegalAccessException和InvocationTargetException
			method.invoke(null);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

3.4 finally语句

try-catch语句块,虽然可以捕获并处理异常情况,但是他也会改变代码的执行流程

例:
在这里插入图片描述如上图,代码调用了t.test("zs")抛出了异常,则第11行语句就直接跳过不再执行,而是直接执行了catch语句块,再去往下执行16行代码。

如果第11行代码是较为重要的代码,那么如何保证无论发生什么情况,他一定会被执行呢? >>>> 使用finally

  • finally

try-catch-finally,固定搭配,可以拆开使用也可以一起使用
注意:不论try块里面的代码是否抛异常,finally块的语句一定会被执行。

例:
在这里插入图片描述以下代码中的finally中的代码是否会被执行
在这里插入图片描述

4 自定义异常

4.1 自定义异常的原因

JavaAPI中已经存在的异常类,都是当年sun公司,提前定义好的,他们分别表示这某一种已知的异常情况。

但是,在我们开发的系统中,大多数业务功能里面总会出现一些新的异常情况,而这些异常情况是当年sun公司定义异常类型时想不到的。

例如,在学生信息管理系统中,学生的年龄设置为负数、学生的成绩大于100分,用户登录时的密码不正确、用户访问某些接口是的权限不足等情况,在系统中都是异常情况,而这些异常类型在JavaAPI中都是没有的。

所以在实际开发中,我们会自定义一些异常类型,用来表示上面描述的这些以上情况,这样做的好处就是,我们通过观察系统的运行日志,就可以很快的知道当前系统时发生了什么事情,才导致出这些异常情况。

4.2 如何自定义异常

  • 自定义一个编译时异常类型,需要先自定义一个类,并继承Exception
  • 自定义一个运行时异常类型,需要先自定义一个类,并继承RuntimeException

- 自定义编译时异常类型

通过名字可知,这是在用户登录期间发生异常时,应该创建并抛出的异常类型

public class LoginException extends Exception {
	public LoginException() {
	}
	public LoginException(String message) {
		super(message);
	}
}

- 自定义运行时异常类型

该异常是在修改用户信息期间发生异常时,应该创建并抛出的异常类型

public class ModifyUserInfoException extends RuntimeException {
	public ModifyUserInfoException() {}
	public ModifyUserInfoException(String message) {
		super(message);
	}
}

当前我们在系统的日志信息中看到 LoginExceptinModifyUserInfoExceptin 这俩种异常类型的信息时,就知道是用户在登录和修改信息的时候,出现了问题。

5 断言assert

断言(assert),是JDK1.4的时候,增加的一个关键字。用它可以在程序中,确认一些关键性条件必须是成立的,否则会抛出AssertionError类型的错误。
注意,断言并不是用来代替if判断的,而是确认系统中的一些关键性条件必须成立的,所以assert和if并不冲突,并且还可以通过给JVM传参数,来控制断言是否生效。

断线的使用方式:

assert 布尔表达式;
//或者
assert 布尔表达式 : "错误信息";

当布尔表达式为true时,断言通过,否则抛出AssertionError类型错误
所以,assert后面的布尔表达式必须为true才行。(即条件必须成立)

例:
在这里插入图片描述

注意:
Junit测试是默认开启了断言的
而直接使用mian方法执行的话JVM是默认没有开启断言的

手动通过JVM传参打开断言功能:

需要使用 -enableassertions 或者 -ea JVM参数
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值