Java中的 final、finally 和 finalize
这是关于面试观点的一个重要问题。
最终关键字
final(lowercase) 是Java中的保留关键字。我们不能将它用作标识符,因为它是保留的。我们可以将此关键字与变量、方法以及类一起使用。 Java中的final关键字根据应用于变量、类或方法而具有不同的含义。
- final with Variables :变量的值一旦初始化就不能改变。
Java
class A { public static void main(String[] args) { // Non final variable int a = 5; // final variable final int b = 6; // modifying the non final variable : Allowed a++; // modifying the final variable : // Immediately gives Compile Time error. b++; } }
Java
final class RR { public static void main(String[] args) { int a = 10; } } // here gets Compile time error that // we can't extend RR as it is final. class KK extends RR { // more code here with main method }
Java
class QQ { final void rr() {} public static void main(String[] args) { } } class MM extends QQ { // Here we get compile time error // since can't extend rr since it is final. void rr() {} }
Java
// Java program to illustrate final keyword final class G { // by default it is final. void h() {} // by default it is not final. static int j = 30; public static void main(String[] args) { // See modified contents of variable j. j = 36; System.out.println(j); } }
Java
// A Java program to demonstrate finally. class Geek { // A method that throws an exception and has finally. // This method will be called inside try-catch. static void A() { try { System.out.println("inside A"); throw new RuntimeException("demo"); } finally { System.out.println("A's finally"); } } // This method also calls finally. This method // will be called outside try-catch. static void B() { try { System.out.println("inside B"); return; } finally { System.out.println("B's finally"); } } public static void main(String args[]) { try { A(); } catch (Exception e) { System.out.println("Exception caught"); } B(); } }
Java
// Java program to illustrate finally in // Case where exceptions do not // occur in the program class B { public static void main(String[] args) { int k = 55; try { System.out.println("In try block"); int z = k / 55; } catch (ArithmeticException e) { System.out.println("In catch block"); System.out.println("Dividing by zero but caught"); } finally { System.out.println("Executes whether exception occurs or not"); } } }
Java
// Java program to illustrate finally in // Case where exceptions occur // and match in the program class C { public static void main(String[] args) { int k = 66; try { System.out.println("In try block"); int z = k / 0; // Carefully see flow doesn't come here System.out.println("Flow doesn't came here"); } catch (ArithmeticException e) { System.out.println("In catch block"); System.out.println("Dividing by zero but caught"); } finally { System.out.println("Executes whether an exception occurs or not"); } } }
Java
// Java program to illustrate finally in // Case where exceptions occur // and do not match any case in the program class D { public static void main(String[] args) { int k = 15; try { System.out.println("In try block"); int z = k / 0; } catch (NullPointerException e) { System.out.println("In catch block"); System.out.println("Dividing by zero but caught"); } finally { System.out.println("Executes whether an exception occurs or not"); } } }
Java
// Java program to illustrate // use of finally block import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; class K { private static final int SIZE = 10; public static void main(String[] args) { PrintWriter out = null; try { System.out.println("Entered try statement"); // PrintWriter, FileWriter // are classes in io package out = new PrintWriter(new FileWriter("OutFile.txt")); } catch (IOException e) { // Since the FileWriter in // try block can throw IOException } // Following finally block cleans up // and then closes the PrintWriter. finally { if (out != null) { System.out.println("Closing PrintWriter"); out.close(); } else { System.out.println("PrintWriter not open"); } } } }
Java
class Hello { public static void main(String[] args) { String s = new String("RR"); s = null; // Requesting JVM to call Garbage Collector method System.gc(); System.out.println("Main Completes"); } // Here overriding finalize method public void finalize() { System.out.println("finalize method overriden"); } }
Java
class Bye { public static void main(String[] args) { Bye m = new Bye(); // Calling finalize method Explicitly. m.finalize(); m.finalize(); m = null; // Requesting JVM to call Garbage Collector method System.gc(); System.out.println("Main Completes"); } // Here overriding finalize method public void finalize() { System.out.println("finalize method overriden"); } }
Java
class Hi { public static void main(String[] args) { Hi j = new Hi(); // Calling finalize method Explicitly. j.finalize(); j = null; // Requesting JVM to call Garbage Collector method System.gc(); System.out.println("Main Completes"); } // Here overriding finalize method public void finalize() { System.out.println("finalize method overriden"); System.out.println(10 / 0); } }
Java
class RR { public static void main(String[] args) { RR q = new RR(); q = null; // Requesting JVM to call Garbage Collector method System.gc(); System.out.println("Main Completes"); } // Here overriding finalize method public void finalize() { System.out.println("finalize method overriden"); System.out.println(10 / 0); } }
如果我们将任何变量声明为 final,我们就不能修改它的内容,因为它是 final,如果我们修改它,我们就会得到编译时错误。
- final with Class :该类不能被子类化。每当我们将任何类声明为 final 时,就意味着我们不能扩展该类,或者该类不能扩展,或者我们不能创建该类的子类。
Java
final class RR { public static void main(String[] args) { int a = 10; } } // here gets Compile time error that // we can't extend RR as it is final. class KK extends RR { // more code here with main method }
- final with Method :该方法不能被子类覆盖。每当我们将任何方法声明为 final 时,就意味着我们不能覆盖该方法。
Java
class QQ { final void rr() {} public static void main(String[] args) { } } class MM extends QQ { // Here we get compile time error // since can't extend rr since it is final. void rr() {} }
注意:如果一个类被声明为final,那么默认情况下该类中的所有方法都自动成为final,但变量不是。
Java
// Java program to illustrate final keyword final class G { // by default it is final. void h() {} // by default it is not final. static int j = 30; public static void main(String[] args) { // See modified contents of variable j. j = 36; System.out.println(j); } }
输出:36
finally 关键字
正如final是保留关键字一样,finally也是Java中的保留关键字,即我们不能将其用作标识符。 finally 关键字与 try/catch 块关联使用,并保证将执行一段代码,即使抛出异常也是如此。 finally 块将在 try 和 catch 块之后执行,但在控制转移回其原点之前。
Java
// A Java program to demonstrate finally.
class Geek {
// A method that throws an exception and has finally.
// This method will be called inside try-catch.
static void A()
{
try {
System.out.println("inside A");
throw new RuntimeException("demo");
}
finally
{
System.out.println("A's finally");
}
}
// This method also calls finally. This method
// will be called outside try-catch.
static void B()
{
try {
System.out.println("inside B");
return;
}
finally
{
System.out.println("B's finally");
}
}
public static void main(String args[])
{
try {
A();
}
catch (Exception e) {
System.out.println("Exception caught");
}
B();
}
}
输出:
inside A
A's finally
Exception caught
inside B
B's finally
finally 可以使用的情况有很多种。下面有讨论:
- 案例一:程序中没有出现异常
Java
// Java program to illustrate finally in // Case where exceptions do not // occur in the program class B { public static void main(String[] args) { int k = 55; try { System.out.println("In try block"); int z = k / 55; } catch (ArithmeticException e) { System.out.println("In catch block"); System.out.println("Dividing by zero but caught"); } finally { System.out.println("Executes whether exception occurs or not"); } } }
输出:
In try block Executes whether exception occurs or not
这里没有发生上述异常,但仍然最终阻止执行,因为 finally 意味着无论是否发生异常都执行。
上面程序的流程:首先它从main方法开始,然后进入try块,在try中,因为没有发生异常,所以流程不会去catch块,因此流程直接从try到finally块。 - 案例2:发生异常,对应的catch块匹配
Java
// Java program to illustrate finally in // Case where exceptions occur // and match in the program class C { public static void main(String[] args) { int k = 66; try { System.out.println("In try block"); int z = k / 0; // Carefully see flow doesn't come here System.out.println("Flow doesn't came here"); } catch (ArithmeticException e) { System.out.println("In catch block"); System.out.println("Dividing by zero but caught"); } finally { System.out.println("Executes whether an exception occurs or not"); } } }
输出:In try block In catch block Dividing by zero but caught Executes whether an exception occurs or not
这里,发生了上述异常,找到了对应的catch块,但仍然执行finally块,因为finally的意思是执行是否发生异常或是否找到对应的catch块。
上述程序的流程:首先,从main方法开始,然后进入try块,在try中发生算术异常,并且相应的catch块也可用,因此流程进入catch块。在该流不会再次尝试块之后,因为一旦在尝试块中发生异常,则流不会再次返回尝试块。在finally之后,execute since finally表示是否发生异常或是否找到相应的catch块。 - 案例 3:发生异常并且未找到/匹配相应的 catch 块
Java
// Java program to illustrate finally in // Case where exceptions occur // and do not match any case in the program class D { public static void main(String[] args) { int k = 15; try { System.out.println("In try block"); int z = k / 0; } catch (NullPointerException e) { System.out.println("In catch block"); System.out.println("Dividing by zero but caught"); } finally { System.out.println("Executes whether an exception occurs or not"); } } }
输出:In try block Executes whether an exception occurs or not Exception in thread "main":java.lang.ArithmeticException: / by zero followed by stack trace.
这里发生了上述异常并且相应的catch块未找到/匹配但仍然执行finally块,因为finally意味着执行是否发生异常或相应的catch块是否找到/匹配。
上述程序的流程:首先从main方法开始,然后进入try块,在try中发生算术异常,相应的catch块不可用,因此流程不会进入catch块。在该流不会再次尝试块之后,因为一旦在尝试块中发生异常,则流不会再次返回尝试块。在 finally 之后,execute since finally 意味着执行是否发生异常或是否找到/匹配相应的 catch 块。
finally 块的应用:所以 finally 块的使用基本上是资源释放。意味着我们在try块中打开的所有资源,例如网络连接,数据库连接,都需要关闭,这样我们就不会丢失打开的资源。所以这些资源需要在 finally 块中关闭。
Java
// Java program to illustrate
// use of finally block
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
class K {
private static final int SIZE = 10;
public static void main(String[] args)
{
PrintWriter out = null;
try {
System.out.println("Entered try statement");
// PrintWriter, FileWriter
// are classes in io package
out = new PrintWriter(new FileWriter("OutFile.txt"));
}
catch (IOException e) {
// Since the FileWriter in
// try block can throw IOException
}
// Following finally block cleans up
// and then closes the PrintWriter.
finally
{
if (out != null) {
System.out.println("Closing PrintWriter");
out.close();
} else {
System.out.println("PrintWriter not open");
}
}
}
}
输出:
Entered try statement
PrintWriter not open
注意:finally 块是防止资源泄漏的关键工具。关闭文件或以其他方式恢复资源时,将代码放在 finally 块中以确保始终恢复资源。
jdk 1.7 如何使 finally 块的使用成为可选的?
直到 jdk 1.6 finally 块就像一个英雄 ie,建议使用它来进行资源释放,但是从 jdk 1.7 finally 开始,块现在是可选的(但是你可以使用它)。由于我们在 try 块中打开的资源会在程序流到达 try 块的末尾时自动被释放/关闭。
这种不使用 finally 块的自动资源释放概念被称为try-with-resources 语句。
完成方法
这是垃圾收集器总是在删除/销毁符合垃圾收集条件的对象之前调用的方法,以执行清理活动。清理活动意味着关闭与该对象关联的资源,例如数据库连接、网络连接,或者我们可以说资源取消分配。请记住,它不是保留关键字。
一旦 finalize 方法完成,垃圾收集器立即销毁该对象。 finalize 方法存在于 Object 类中,其语法为:
protected void finalize throws Throwable{}
由于 Object 类包含 finalize 方法,因此 finalize 方法可用于每个Java类,因为 Object 是所有Java类的超类。由于它适用于每个Java类,因此垃圾收集器可以在任何Java对象上调用 finalize 方法
现在,Object 类中的 finalize 方法有一个空实现,在我们的类中有清理活动,那么我们必须重写这个方法来定义我们自己的清理活动。
与finalize方法相关的案例:
- 案例1:符合垃圾回收条件的对象,该对象对应的类finalize方法将被执行
Java
class Hello { public static void main(String[] args) { String s = new String("RR"); s = null; // Requesting JVM to call Garbage Collector method System.gc(); System.out.println("Main Completes"); } // Here overriding finalize method public void finalize() { System.out.println("finalize method overriden"); } }
输出:
Main Completes
注意:上面的输出仅来自Main Completes而不是“finalize method overriden”,因为 Garbage Collector 在该类对象上调用了 finalize 方法,该类对象符合 Garbage collection 的条件。上面我们已经完成了->
s = null并且's' 是String 类的对象,所以String 类的finalize 方法将被调用,而不是我们的类(即Hello 类)。所以我们修改我们的代码就像->Hello s = new Hello(); s = null;
现在我们的类,即Hello 类的finalize 方法被调用。输出:
finalize method overriden Main Completes
所以基本上,垃圾收集器在符合垃圾回收条件的类对象上调用 finalize 方法。因此,如果 String 对象符合垃圾回收条件,那么将调用String类的 finalize 方法,而不是 Hello 类的finalize 方法。
- 案例 2:我们可以显式调用 finalize 方法,然后它将像普通方法调用一样执行,但对象不会被删除/销毁
Java
class Bye { public static void main(String[] args) { Bye m = new Bye(); // Calling finalize method Explicitly. m.finalize(); m.finalize(); m = null; // Requesting JVM to call Garbage Collector method System.gc(); System.out.println("Main Completes"); } // Here overriding finalize method public void finalize() { System.out.println("finalize method overriden"); } }
输出:finalize method overriden //call by programmer but object won't gets destroyed. finalize method overriden //call by programmer but object won't gets destroyed. Main Completes finalize method overriden //call by Garbage Collector just before destroying the object.
注意:由于 finalize 是一个方法而不是保留关键字,所以我们可以显式调用 finalize 方法,然后它将像普通方法调用一样执行,但对象不会被删除/销毁。
- 案例3:
- a)如果程序员调用 finalize 方法,在执行 finalize 方法时会出现一些未经检查的异常。
Java
class Hi { public static void main(String[] args) { Hi j = new Hi(); // Calling finalize method Explicitly. j.finalize(); j = null; // Requesting JVM to call Garbage Collector method System.gc(); System.out.println("Main Completes"); } // Here overriding finalize method public void finalize() { System.out.println("finalize method overriden"); System.out.println(10 / 0); } }
输出:exception in thread "main" java.lang.ArithmeticException: / by zero followed by stack trace.
所以关键是:如果程序员调用finalize方法,在执行finalize方法时出现了一些未经检查的异常,那么JVM会通过引发异常异常终止程序。所以在这种情况下,程序终止是Abnormal 。
- b)如果垃圾收集器调用 finalize 方法,在执行 finalize 方法时会出现一些未经检查的异常。
Java
class RR { public static void main(String[] args) { RR q = new RR(); q = null; // Requesting JVM to call Garbage Collector method System.gc(); System.out.println("Main Completes"); } // Here overriding finalize method public void finalize() { System.out.println("finalize method overriden"); System.out.println(10 / 0); } }
输出:finalize method overriden Main Completes
所以关键是:如果垃圾收集器调用 finalize 方法,在执行 finalize 方法时出现一些未经检查的异常,那么 JVM 会忽略该异常,其余程序将正常继续。所以在这种情况下程序终止是正常的而不是异常的。
- a)如果程序员调用 finalize 方法,在执行 finalize 方法时会出现一些未经检查的异常。
要点:
- 无法保证调用 finalize 的时间。它可以在对象没有在任何地方被引用后的任何时间被调用(cab 被垃圾收集)。
- JVM 在执行 finalize 方法时不会忽略所有异常,但它只会忽略 Unchecked exceptions 。如果存在相应的catch 块,则JVM 不会忽略并执行相应的catch 块。
- System.gc() 只是对 JVM 执行垃圾收集器的请求。 JVM 是否调用 Garbage Collector 取决于 JVM。通常,当 Heap 区域中没有足够的可用空间或内存不足时,JVM 会调用 Garbage Collector。