如何理解Java中的final、finally、finalize?用法和区别

2021年3月29日18:16:00 发表评论 1,177 次浏览

这是有关访谈观点的重要问题。

final关键字

final(小写)是Java中的保留关键字。我们不能将其用作标识符, 因为它已被保留。我们可以将此关键字与变量, 方法以及类一起使用。 java中的final关键字具有不同的含义, 具体取决于将其应用于变量, 类或方法。

final变量:变量的值一旦初始化就无法更改。

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++;
     }
}

如果我们将任何变量声明为final, 则因为它是final, 所以无法修改其内容;如果我们对其进行修改, 则会出现编译时错误。

final类:该类不能子类化。当我们将任何类声明为final时,这就意味着我们不能扩展这个类,或者这个类不能被扩展,或者我们不能创建这个类的子类。

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方法:

该方法不能被子类覆盖。每当我们将任何方法声明为final方法时, 这都意味着我们无法覆盖该方法。

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, 则默认该类中存在的所有方法都是自动最终的, 但变量不是.

// 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是保留关键字一样, final同样也是java中的保留关键字, 即我们不能将其用作标识符。最终关键字与尝试/捕获块并保证即使抛出异常也会执行一段代码。最终块将在try和catch块之后但在控制权移回其原始位置之前执行。

// 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

最终可以使用多种情况。讨论如下:

情况1:程序中未发生异常

// 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块仍然执行, 因为finally意味着无论是否发生异常都将执行。

以上程序流程:首先, 它从main方法开始, 然后进入try块, 在try中, 因为没有异常发生, 所以流不会捕获到块, 因此流直接从try到finally块。

情况2:发生异常并且对应的catch块匹配

// 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 dosen't come here
             System.out.println( "Flow dosen'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

在此, 发生上述异常并且找到了相应的捕获块, 但仍然最终块执行, 因为最后意味着执行异常是否发生或是否找到相应的捕获块。

以上程序流程:首先,从main方法开始,然后进入try块,在try中出现一个算术异常,并且相应的catch块也可用,因此流进入catch块。在此之后,流不会再次进入try block,因为一旦try block中出现异常,流就不会再次返回try block。在finally之后,执行,因为finally意味着执行是否发生异常或是否找到相应的catch块。

情况3:发生异常, 并且未找到/匹配相应的捕获块

// 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.

在此, 发生以上异常, 并且未找到/匹配相应的捕获块, 但仍最终执行块, 因为finally意味着执行是否发生异常或是否找到/匹配相应的捕获块。

以上程序流程:首先从main方法开始,然后进入try block,在try中出现了一个算术异常,相应的catch块不可用,所以流不会进入catch块。在此之后,流不会再次进入try block,因为一旦try block中出现异常,流就不会再次返回try block。在finally之后,执行,因为finally意味着执行是否发生异常,是否找到相应的catch块/是否匹配。

finally块的应用:所以基本上, 使用finally块是资源释放。表示需要关闭在try块中打开的所有资源, 例如网络连接, 数据库连接, 以免打开时不会丢失资源。因此, 需要在finally块中关闭这些资源。

// 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块就像英雄一样, 即建议将其用于资源释放, 但是从jdk 1.7开始, 最后, block现在是可选的(但是你可以使用它)。因为当程序流到达try块的末尾时, 我们在try块中打开的资源将自动释放/关闭。

这种不使用finally块的自动资源回收概念称为try-with-resources语句。

finalize方法

这是一个垃圾回收器总是在删除/销毁符合垃圾回收条件的对象之前调用的方法,以便执行清理活动。清理活动意味着关闭与该对象相关联的资源,如数据库连接、网络连接,或者我们可以说资源回收。记住,它不是一个保留关键字。

一旦finalize方法完成, 垃圾收集器立即销毁该对象。 finalize方法存在于Object类中, 其语法为:

protected void finalize throws Throwable{}

由于Object类包含finalize方法,因此finalize方法对于每个java类都是可用的,因为Object是所有java类的父类。由于finalize方法对每个java类都可用,因此垃圾收集器可以对任何java对象调用finalize方法

现在,对象类中的finalize方法有一个空实现,在类中有清理活动,然后我们必须重写这个方法来定义我们自己的清理活动。

与完成方法有关的案例:

情况1 :

符合垃圾收集条件的对象, 将执行该对象的相应类的finalize方法

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 completed而不是" finalize method overrides ",因为垃圾回收器对符合垃圾回收条件的类对象调用finalize方法。上面我们做了->

s = null并且' s '是String类的对象,所以String类finalize方法将被调用,而不是我们的类(i。e,你好类)。所以我们把代码修改成->

Hello s = new Hello();
s = null;

现在我们的类, 即Hello类的finalize方法被调用。输出如下:

finalize method overriden
Main Completes

因此, 基本上, 垃圾收集器会对该符合垃圾收集条件的类对象调用finalize方法, 因此, 如果String对象符合垃圾收集条件, 则String类的finalize方法将被调用, 不是Hello类定型方法。

情况2:

我们可以显式调用finalize方法, 然后像普通方法调用一样执行它, 但是对象不会被删除/销毁

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方法时会出现一些未经检查的异常。

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.

So关键点是:如果程序员调用finalize方法, 则在执行finalize方法时会出现一些未经检查的异常, 那么JVM将通过异常上升来异常终止程序。因此, 在这种情况下, 程序终止为异常.

b部分)

如果垃圾收集器调用finalize方法, 则在执行finalize方法时会出现一些未经检查的异常。

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

So关键点是:如果垃圾收集器调用finalize方法, 而在执行finalize方法时出现一些未经检查的异常, 则JVM忽略该异常和程序的其余部分将正常继续。因此, 在这种情况下, 程序终止为正常并没有异常。

要点:

无法保证调用finalize的时间。在任何地方都没有引用对象之后的任何时候都可以调用它(可能是垃圾回收)。

JVM在执行finalize方法时不会忽略所有异常, 但只会忽略未检查的异常。如果存在相应的catch块, 那么JVM将不会忽略, 并且将执行相应的catch块。

System.gc()只是对JVM的请求, 以执行垃圾回收器。由JVM决定是否调用Garbage Collector。通常, 当Heap区域中的可用空间不足或内存不足时, JVM会调用Garbage Collector。

如果发现任何不正确的地方, 或者想分享有关上述主题的更多信息, 请写评论。

木子山

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: