📜  Java中的 final、finally 和 finalize

📅  最后修改于: 2022-05-13 01:54:51.101000             🧑  作者: Mango

Java中的 final、finally 和 finalize

这是关于面试观点的一个重要问题。

最终关键字

final(lowercase) 是Java中的保留关键字。我们不能将它用作标识符,因为它是保留的。我们可以将此关键字与变量、方法以及类一起使用。 Java中的final关键字根据应用于变量、类或方法而具有不同的含义。

  1. 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,如果我们修改它,我们就会得到编译时错误。

  2. 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
    }
    
  3. 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 可以使用的情况有很多种。下面有讨论:

  1. 案例一:程序中没有出现异常

    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. 案例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. 案例 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. 案例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. 案例 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. 案例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 会忽略该异常,其余程序将正常继续。所以在这种情况下程序终止是正常的而不是异常的。

要点:

  • 无法保证调用 finalize 的时间。它可以在对象没有在任何地方被引用后的任何时间被调用(cab 被垃圾收集)。
  • JVM 在执行 finalize 方法时不会忽略所有异常,但它只会忽略 Unchecked exceptions 。如果存在相应的catch 块,则JVM 不会忽略并执行相应的catch 块。
  • System.gc() 只是对 JVM 执行垃圾收集器的请求。 JVM 是否调用 Garbage Collector 取决于 JVM。通常,当 Heap 区域中没有足够的可用空间或内存不足时,JVM 会调用 Garbage Collector。