相信同学们学习到面向对象的异常处理中应该会踩到finally语句块这个坑中,本篇博文通过各种案例帮你如何避免踩坑,踩雷,让你对异常处理的学习更加透彻,finally语句块掌握的更加扎实!
在进行异常处理时,finally语句块也在异常的处理格式中,finally语句块是整个异常处理的统一出口,不管是产生了异常还是未产生异常,那么finally语句块中的内容必然执行。
来一个小案例:
import java.lang.Exception;
public class Exception1 {
public static void main(String[] args){
int i = 10;
int j = 0;
try{
System.out.println("输入的结果为" + i/j);
}catch (Exception e ){
System.out.println("动动你的脑筋,除数不能为0");
return;
}finally {
System.out.println("finally语句执行了!");
}
}
}
运行结果:
动动你的脑筋,除数不能为0
finally语句执行了!
看完这段代码有人就有疑惑,为什么我在catch语句块中return了为什么后面的代码还会执行?
因为我们在进行异常处理流程时,当catch语句块执行到return的时候,我们需要做准备return的操作,虽然这个返回值没有,但默认还是要把这个没有返回值的东西返回(可能有点绕,慢慢理解),在这个准备的过程中,我们就把finally语句块的内容执行了,执行完毕后,再return 结束掉这个方法。
答案是:否!
第一种情况:JVM退出,finally不会执行。
import java.lang.Exception;
public class Exception2 {
public static void main(String[] args){
int i = 10;
int j = 0;
try{
System.out.println("输入的结果为" + i/j);
}catch (Exception e ){
System.out.println("动动你的脑筋,除数不能为0")
System.exit(1);// 退出JVM
}finally {
System.out.println("finally语句执行了!");
}
}
运行结果:
动动你的脑筋,除数不能为0
这种情况下,我们发现finally语句块并没有执行。因为我们在catch语句块中首先显示了错误信息,并退出了JAVA虚拟机,导致代码执行到此处,整个程序就结束了。
第二种情况:异常处理前结束方法,finally不会执行。
这种情况是在进行异常处理前就return,结束了方法,那么后面的所有代码都不会执行,finally语句块也一定不会执行(编译器也不会让你写return之后的代码)。
第三种情况:在异常处理前,产生了其他的异常,finally不会执行。
public class Exception3 {
public static void main(String[] args){
int i = 10;
int j = 0;
System.out.println(i/j);
try{
System.out.println("try..........");
}finally {
System.out.println("finally.........");
}
}
}
运行结果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at kkb.Exception3.main(Exception3.java:6)
这种情况是在处理异常前,产生了其他的异常,既出现异常,后面的代码不会执行,直接结束了方法。
有很多同学不知道finally和final是干什么的,就像方法的重载和重写一样,都是名字差不多,但是它们的应用的场景完全不同,完全没有共同点。
finally:finally是一个语句块,是属于异常处理结构的成员之一,无论异常是否发生,finally语句块的内容一定输出,所以当我们需要编写无论发生什么都必须执行的代码时,就可以在finally语句块中。
final:final是一个关键字,可以修饰类、变量和方法,被final修饰的类不可以被派生出新的子类。被final修饰的方法不能被重写,不能修改方法中的内容。被fianl修饰的变量只能赋一次值,通常用fianl来修饰常量:public abstract final int +变量名。
finally主要分为以下两种题型,掌握了思想,那么所有题目迎刃而解。
第一个案例:
class Person{ //创建一个Person类
int age; //属性有age
}
public class Exception4 {
public static void main(String[] args){
Person p = p(); //调用p方法
System.out.println(p.age); //输入age的值
}
public static Person p(){
Person p = new Person(); //创建Person对象
p.age = 0; //将默认值设为0
try{
p.age = 18;
return p;
}catch (Exception e){
p.age = 10;
return p;
}finally {
p.age = 20;
}
}
}
运行结果:
20
题解:
首先先分析流程 我们在try语句块中将p.age设置为了18,然后返回这个对象,因为我们没有设置年龄的限制,所以catch语句块的内容一定不会执行。
当代码执行进入try语句块时,首先将p.age设置为18,然后进入return p语句,在执行这段代码时,我们可以理解为先复制了这个p的内存地址,然后进入finally语句块中,将p.age设置为了20,然后再将这个p保存的内存地址返回。因为在堆对象中,这个p.age被finally语句块改变,所以在返回的p中包含的已被修改后的p.age的值。所以最终打印的结果为20;
重点:当返回的数据时引用数据类型时,返回的是该对象的内存地址!!!
第二个案例:
public class Exception5 {
public static void main(String[] args){
int code = i();
System.out.println(code);
}
public static int i(){
int i = 0;
try{
return i;
}finally {
i = 20;
}
}
}
运行结果:
0
题解:
首先分析流程,在i()方法中我们先定义了个基本数据类型的变量i =0,在try语句块中返回了这个i,在finally语句块中修改了i的值。大家可能会认为,在try语句的return返回之前应该先执行finally语句块,修改完毕后再返回吗。最后输出结果应该是20才对。
流程:我们在return语句返回这个i之前,首先先把i的值(数据0)复制一份,然后进入finally语句块,我们把i修改为20,然后再执行return,那么return返回的仍然是这个复制好的数据。所以输出是0;
总结两道案例:
这两道案例一个返回的基本数据类型,一个是返回的引用数据类型。
区别:
引用数据类型存储的数据在堆中,执行完finally语句后,返回的是这个对象的内存地址复制,内存地址再指向对象,如果对象中的数据被改变,那么内存地址指向的这个对象也会发生改变。
局部变量中基本数据类型的数据存储在栈中,执行完finally语句后,返回的是这个数据本身的复制,如果数据被改变,那么返回内容则不变。