【Java面试题】19 final,finally和finalize的区别

总体区别

final       用于申明属性,方法和类,表示属性不可变,方法不可以被覆盖,类不可以被继承。
finally     是异常处理语句结构中,表示总是执行的部分。  
finallize   表示是object类一个方法,在垃圾回收机制中执行的时候会被调用被回收对象的方法。允许回收此前未回收的内存垃圾。所有object都继承了                                   finalize()方法

一.final详解

Java中final也用来修饰3类东西:变量,方法和类。
1.变量
final修饰变量表示该变量是不可变的。例如 final int i =1;在程序中i的值就不允许改变了。比较容易混淆的是final用来修饰引用变量时,表示该引用变量是不可变的即引用变量指向的内存地址是不变的,但是被指的内存地址中的类是可以改变的。例如:
final MyClass myClass = new MyClass();
这样声明myClass后,其所指向的内存地址就固定了,但仍然可以改变myClass所引用对象的成员变量。如果试图重用myClass这个变量,让其引用另一个对象则会出错。
myClass = new MyClass();//error!!!
2.方法
final修饰方法时表示该方法是不能被子类重写的。
3.类

final修饰类时表示该类是不能被继承的,由于java的单继承关系,所以该类是继承关系链中的终端。

关于final的几个注意事项:

a、final变量必须在声明的时候初始化或是在构造函数中初始化;
b、接口中声明的所有变量都是final的;
c、final,finally,finalize的区别。final表示不可变,final表示必须执行的语句,finalize表示垃圾回收时执行的代码。

二.finalize

方法名。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。注意:finalize不一定被jvm调用,只有当垃圾回收器要清除垃圾时才被调用。

Java中所有类都从Object类中继承finalize()方法。

当垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的finalize()方法。值得C++程序员注意的是,finalize()方法并不能等同与析构函数。Java中是没有析构函数的。C++的析构函数是在对象消亡时运行的。由于C++没有垃圾回收,对象空间手动回收,所以一旦对象用不到时,程序员就应当把它delete()掉。所以析构函数中经常做一些文件保存之类的收尾工作。但是在Java中很不幸,如果内存总是充足的,那么垃圾回收可能永远不会进行,也就是说filalize()可能永远不被执行,显然指望它做收尾工作是靠不住的。

那么finalize()究竟是做什么的呢?它最主要的用途是回收特殊渠道申请的内存。Java程序有垃圾回收器,所以一般情况下内存问题不用程序员操心。但有一种JNI(Java Native Interface)调用non-Java程序(C或C++),finalize()的工作就是回收这部分的内存。

三.finally 详解

       Java异常处理模型与其他语言相比,关键词finally是最出色的新增特性了。finally构件使得该区段中的代码总是得以执行,而无论是否发生异常,特别适用于维护对象的内部状态(用来保证异常发生恢复对象的有效状态,以确保程序能在处理完异常后自动再次投入运行)和清理non-memory资源(垃圾回收机制无法处理的资源,如数据库连接、Socket等等)。 

      但有一点值得注意,那就是尽量不要从try区段中返回(调用return),因为只要有finally区段存在,它就一定会被执行,那么如果你在finally区段中又调用了一次return语句,则try区段中的返回值将会被遮掩,使得方法调用者得到的是finally区段中的返回值--这常常又与程序编写的初衷相背。

问题分析

首先来问大家一个问题:finally 语句块一定会执行吗?

很多人都认为 finally 语句块是肯定要执行的,其中也包括一些很有经验的 Java 程序员。可惜并不像大多人所认为的那样,对于这个问题,答案当然是否定的,我们先来看下面这个例子。

清单 1.
复制代码
public class Test { 
 public static void main(String[] args) { 
 System.out.println("return value of test(): " + test()); 
     } 

 public static int test() { 
 int i = 1; 
        
 //          if(i == 1) 
 //              return 0; 
 System.out.println("the previous statement of try block"); 
 i = i / 0; 
        
 try { 
    System.out.println("try block"); 
      return i; 
     }finally { 
     System.out.println("finally block"); 
         } 
     } 
 }
复制代码

清单 1 的执行结果如下:

the previous statement of try block 
 Exception in thread "main" java.lang.ArithmeticException: / by zero 
 at com.bj.charlie.Test.test(Test.java:15) 
 at com.bj.charlie.Test.main(Test.java:6)

另外,如果去掉上例中被注释的两条语句前的注释符,执行结果则是:

 return value of test(): 0

在以上两种情况下,finally 语句块都没有执行,说明什么问题呢?只有与 finally 相对应的 try 语句块得到执行的情况下,finally 语句块才会执行。以上两种情况,都是在 try 语句块之前返回(return)或者抛出异常,所以 try 对应的 finally 语句块没有执行。

那好,即使与 finally 相对应的 try 语句块得到执行的情况下,finally 语句块一定会执行吗?不好意思,这次可能又让大家失望了,答案仍然是否定的。请看下面这个例子(清单 2)。

清单 2.
复制代码
public class Test { 
 public static void main(String[] args) { 
 System.out.println("return value of test(): " + test()); 
     } 

 public static int test() { 
 int i = 1; 

 try { 
 System.out.println("try block"); 
 System.exit(0); 
 return i; 
 }finally { 
 System.out.println("finally block"); 
         } 
     } 
 }
复制代码

清单 2 的执行结果如下:

try block

finally 语句块还是没有执行,为什么呢?因为我们在 try 语句块中执行了 System.exit (0) 语句,终止了 Java 虚拟机的运行。那有人说了,在一般的 Java 应用中基本上是不会调用这个 System.exit(0) 方法的。OK !没有问题,我们不调用 System.exit(0) 这个方法,那么 finally 语句块就一定会执行吗?

再一次让大家失望了,答案还是否定的。当一个线程在执行 try 语句块或者 catch 语句块时被打断(interrupted)或者被终止(killed),与其相对应的 finally 语句块可能不会执行。还有更极端的情况,就是在线程运行 try 语句块或者 catch 语句块时,突然死机或者断电,finally 语句块肯定不会执行了。可能有人认为死机、断电这些理由有些强词夺理,没有关系,我们只是为了说明这个问题。

下面,我们先来看一个简单的例子(清单 3)。

清单 3.
复制代码
public class Test { 
 public static void main(String[] args) {  
 try {  
 System.out.println("try block");  

 return ;  
 } finally {  
 System.out.println("finally block");  
         }  
     }  
 }
复制代码

清单 3 的执行结果为:

try block 
 finally block

清单 3 说明 finally 语句块在 try 语句块中的 return 语句之前执行。我们再来看另一个例子(清单 4)。

清单 4.
复制代码
public class Test { 
 public static void main(String[] args) {  
 System.out.println("reture value of test() : " + test()); 
     } 
    
 public static int test(){ 
 int i = 1; 
        
 try {  
 System.out.println("try block");  
             i = 1 / 0; 
 return 1;  
 }catch (Exception e){ 
 System.out.println("exception block"); 
 return 2; 
 }finally {  
 System.out.println("finally block");  
         } 
     } 
 }
复制代码

清单 4 的执行结果为:

try block 
 exception block 
 finally block 
 reture value of test() : 2

清单 4 说明了 finally 语句块在 catch 语句块中的 return 语句之前执行。

从上面的清单 3 和清单 4,我们可以看出,其实 finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。更加一般的说法是,finally 语句块应该是在控制转移语句之前执行,控制转移语句除了 return 外,还有 break 和 continue。另外,throw 语句也属于控制转移语句。虽然 return、throw、break 和 continue 都是控制转移语句,但是它们之间是有区别的。其中 return 和 throw 把程序控制权转交给它们的调用者(invoker),而 break 和 continue 的控制权是在当前方法内转移。请大家先记住它们的区别,在后续的分析中我们还会谈到。

还是得来点有说服力的证据,下面这段摘自 Java 语言规范第四版(The Java™ Programming Language, Fourth Edition),请读者自己体会一下其含义。

 

好了,看到这里,是不是有人认为自己已经掌握了 finally 的用法了?先别忙着下结论,我们再来看两个例子 – 清单 5 和清单 6。

清单 5.

 

复制代码
public class Test { 
 public static void main(String[] args) { 
        System.out.println("return value of getValue(): " + getValue()); 
     } 

 public static int getValue() { 
        try { 
                 return 0; 
        } finally { 
                 return 1; 
            } 
     } 
 }
复制代码

清单 5 的执行结果:

return value of getValue(): 1
清单 6.
复制代码
public class Test { 
 public static void main(String[] args) { 
        System.out.println("return value of getValue(): " + getValue()); 
     } 

 public static int getValue() { 
        int i = 1; 
        try { 
                 return i; 
        } finally { 
                 i++; 
        } 
     } 
 }
复制代码

清单 6 的执行结果:

return value of getValue(): 1

利用我们上面分析得出的结论:finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。 由此,可以轻松的理解清单 5 的执行结果是 1。因为 finally 中的 return 1;语句要在 try 中的 return 0;语句之前执行,那么 finally 中的 return 1;语句执行后,把程序的控制权转交给了它的调用者 main()函数,并且返回值为 1。那为什么清单 6 的返回值不是 2,而是 1 呢?按照清单 5 的分析逻辑,finally 中的 i++;语句应该在 try 中的 return i;之前执行啊? i 的初始值为 1,那么执行 i++;之后为 2,再执行 return i;那不就应该是 2 吗?怎么变成 1 了呢?

关于 Java 虚拟机是如何编译 finally 语句块的问题,有兴趣的读者可以参考《 The JavaTM Virtual Machine Specification, Second Edition 》中 7.13 节 Compiling finally。那里详细介绍了 Java 虚拟机是如何编译 finally 语句块。实际上,Java 虚拟机会把 finally 语句块作为 subroutine(对于这个 subroutine 不知该如何翻译为好,干脆就不翻译了,免得产生歧义和误解。)直接插入到 try 语句块或者 catch 语句块的控制转移语句之前。但是,还有另外一个不可忽视的因素,那就是在执行 subroutine(也就是 finally 语句块)之前,try 或者 catch 语句块会保留其返回值到本地变量表(Local Variable Table)中。待 subroutine 执行完毕之后,再恢复保留的返回值到操作数栈中,然后通过 return 或者 throw 语句将其返回给该方法的调用者(invoker)。请注意,前文中我们曾经提到过 return、throw 和 break、continue 的区别,对于这条规则(保留返回值),只适用于 return 和 throw 语句,不适用于 break 和 continue 语句,因为它们根本就没有返回值。

 

posted @ 2018-03-16 16:55  十月围城小童鞋  阅读(137)  评论(0编辑  收藏  举报