一、异常
异常:非正常运行
@Test
public void testHappenException() {
int[] arr = {1,2,3,4,5};
for (int i = 1; i <= 20; i++) {
if (i == 15) {
// ArrayIndexOutOfBoundsException
int j = arr[100]; // 数组越界
}
System.out.println("正常:"+ i);
}
}
可以看出,当前程序出现异常情况时,会创建并抛出和该异常情况对应的异常类的对象,这个异常对象中保存了一些信息,用来表示当前程序到底发生了什么异常情况。
通过异常信息,我们可以定位异常发生的位置,以及异常发生的原因
1.1 异常体制
异常体系中的根类是:
java.lang.Throwable
,该类下面有两个子类型,java.lang.Error
和java.lang.Exception
Error
,表示错误情况,一般是程序中出现了比较严重的问题,并且程序自身并无法进行处理。Exception
,表示异常情况,程序中出了这种异常,大多是可以通过特定的方式进行处理和纠正的,并且处理完了之后,程序还可以继续往下正常运行。
注意,我们一般说的异常,都是指的Exception
Exception
中并没有定义方法,它的方法都是从Throwable
中继承过来的,其中常用的方式有:
printStackTrace()
,打印输出当前发送异常的详细信息(重要)getMessage()
,获取异常信息;返回异常对象抛出是携带的信息,一般是异常的发生原因(重要)printStackTrace(PrintWriter s)
,方法重载,可以指定字符输出流,对异常信息进行输出printStackTrace(PrintStream s)
,方法重载,可以指定字节输出流,对异常信息进行输出
1.1.1 编译时异常
1.1.2 运行时异常
- 空指针异常:
NullPointerException:
当某个引用为空,同时这个引用调用了这个方法或者属性。在使用某个引用之前先进行空处理,在以后的编码过程中在使用引用变量之前最好是判空处理,这样可以提高程序的健壮性。 - 数组下标超出异常:
ArrayIndexOutBoundsException:
当通过下标给数组赋值或者取值超过了数组下标的最大值,就会发生该异常;将下标缩减为下标的范围 - 算术异常:
ArithmeticException:
进行除法或者取模运算时,除数为0,将除数修改为非0即可 - 类型转换异常:
ClassCastException:
在进行强转之前先用instanceof判断一下,属于这种类型就可以强转 - 数字格式异常:
NumberFormatException:
将字符串转成数字发生了异常,将字符串改成数字字符传即可。
Integer.parseInt("aaa");
1.2 传播异常
如果一个方法中抛出了异常,并且一直没有进行处理,那么这个异常将会抛给当前方法的调用者,并一直向上抛出直到抛给JVM,最后JVM将这个异常信息打印输出,同时程序运行的停止。
public class Test {
public static void main(String[] args) {
System.out.println("hello");
test1();
System.out.println("world");
}
public static void test1(){
test2();
}
public static void test2(){
test3();
}
public static void test3(){
int a = 1/0;
}
}
//运行结果:
hello
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.lzyy.demo.Test.test3(Test.java:16)
at com.lzyy.demo.Test.test2(Test.java:13)
at com.lzyy.demo.Test.test1(Test.java:10)
at com.lzyy.demo.Test.main(Test.java:5)
可以看出,test3方法中抛出了异常,因为是运行时异常,编译器不检查,这个异常在程序中没有处理,那么这个异常就抛给了test2,test2方法又将异常抛给了test1方法,test1中又将异常抛给main方法,main方法将异常抛给JVM,最后JVM将当前发生异常时,栈内存中方法的调用情况,打印输出,方便我们定位错误信息!之后JVM停止运行。
如果,在异常传播的过程中,任何一个地方对异常进行了处理,那么JVM不会停止,程序还会正常往下运行!
1.3 异常抛出
-
自动抛出异常
例如,当代码中执行int a = 1/0;
的时候,代码会自动创建并抛出ArithmeticException
类型的异常对象,来表示当前的这种异常情况。(算术异常)例如,当前代码中执行
String str = null; str.toString();
的时候,代码会自动创建并抛出NullPointerException
类型的异常对象,来表示当前这种异常情况。(空指针异常) -
手动抛出异常
- 第一种方式:
public class Test {
public void test(String name) {
if (!"tom".equals(name)) {
throw new RuntimeException();
}
}
public static void main(String[] args) {
new Test().test("lisa");
}
}
注意,以为方法中抛出的异常是编译异常,编译器会做检查,所以代码编译会报错,提示我们需要在test方法上声明出方法内抛出异常的类型,或者在方法内对这个异常进行处理!
Exception in thread “main” java.lang.RuntimeException
at com.lzyy.test.Test.test(Test.java:7)
at com.lzyy.test.Test.main(Test.java:12)
- 第二种方式:
注意throws
public class Test {
public void test(String name) throws Exception{
if (!"tom".equals(name)) {
throw new RuntimeException();
}
}
public static void main(String[] args) throws Exception {
new Test().test("lisa");
}
}
使用throws关键字,声明方法所抛出的异常类型即可
这个声明的目的,就是告诉test方法的调用者,你调用我的这个test方法的时候要小心啦,方法在运行的时候可能会抛出Exception类型的异常
这里描述为可能会抛出异常的原因是,只有name的值不是tom的时候才会抛出异常,其他情况没有异常!
Exception in thread “main” java.lang.RuntimeException
at com.lzyy.test.Test.test(Test.java:13)
at com.lzyy.test.Test.main(Test.java:18)
方法内抛出异常对象使用关键字throw,方法上声明异常使用的是throws
1.4 异常捕获
当一个方法内,抛出了编译异常的时候,编译器在编译期间检查到,就会报错,提示我们两种修改方式:
- 把这个异常在方法上进行声明抛出
- 把这个异常再方法内进行捕获处理
- 声明抛出
public void test(String className)throws ClassNotFoundException{
Class.forName(className);
}
将来谁调用test方法,谁就要处理这个异常情况
- 捕获处理
public void test(String className){
try {
Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
这里使用了try-catch语句块,对可能抛出异常的代码进行异常捕获处理
1.4.1 try-catch
try-catch
语句块,就是用来对指定代码,进行异常捕获处理,并且处理完成后,JVM不会停止运行,代码还可以正常的往下运行!
try:该代码块中编写可能产生异常的代码。
catch:用来进行某种异常的捕获,并对捕获到的异常进行处理。
使用 | 来表示捕获多种不同的异常类型
catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
其实使用一个
Exception
就可以了 【 getMessage() : 获取异常信息】
其中:
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();
}
}
这里,使用了四个catch语句,分别对四种不同的异常类型进行捕获处理
注意,这种异常处理方式,要求多个catch中的异常不能相同,并且如果catch中的多个异常之间有子父类异常的关系的话,
那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。
因为如果父类型异常再最上面的话,下面catch语句代码,永远不会被执行
1.4.2 finall语句
try-catch语句块,虽然可以捕获并处理异常情况,但是它也会改变代码的执行流程,例如:
public class Test {
public static void main(String[] args) {
System.out.println("hello");
Test t = new Test();
try {
t.test("zs"); // 9
System.out.println("你好");//注意观察,这句代码是否执行 10
} catch (Exception e) { // 11
e.printStackTrace();
}
System.out.println("world");
}
public void test(String name)throws Exception{
if(!"tom".equals(name)){
throw new Exception("用户名不正确");
}
}
}
//运行结果:
hello
world
java.lang.Exception: 用户名不正确
at com.lzyy.demo.Test.test(Test.java:20)
at com.lzyy.demo.Test.main(Test.java:9)
可以看出,以上代码中的第10行,并没有执行,因为第9行代码调用方法出现异常时,代码就从第9行跳到了11行的catch语句块,刚好把11行的输出语句代码给跳过去了。
其中,只要使用finally
关键字,就可以保证指定代码一定会执行,无论是否发生异常!
思考,以下代码中的finally中的代码是否会被执行
public static void main(String[] args) {
System.out.println("hello");
try {
int a = 1;
if(a>0){
return; //结束该方法
}
} finally {
System.out.println("你好");//是否会被执行? 会
}
System.out.println("world");
}
hello
你好
该方法在finally 方法结束之前返回值之后执行