📜  Java中final,finally和finalize

📅  最后修改于: 2020-03-29 06:16:56             🧑  作者: Mango

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

final关键字

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

  1. final变量:变量的值一旦初始化就无法更改。
    class A {
        public static void main(String[] args)
        {
            // 非final变量
            int a = 5;
            // final变量
            final int b = 6;
            // 修改非final变量,合法
            a++;
            // final变量,编译错误,非法
            b++;
        }
    }
    

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

  2. final类:该类不能被子类化(继承)。每当我们将任何类声明为final时,这都意味着该类无法扩展,或者我们不能将该类作为子类。
    final class RR {
        public static void main(String[] args)
        {
            int a = 10;
        }
    }
    // 编译错误
    class KK extends RR {
        // 待写
    }
    
  3. final方法:该方法不能被子类重写。每当我们将任何方法声明为final时,这都意味着我们无法重写该方法。
    class QQ {
        final void rr() {}
        public static void main(String[] args)
        {
        }
    }
    class MM extends QQ {
        // 编译错误
        void rr() {}
    }
    

    注意:如果将一个类声明为final,则默认情况下,该类中存在的所有方法都将自动为final,但变量不是

    // Java展示final关键字
    final class G {
        // 默认为final.
        void h() {}
        // 默认不是final.
        static int j = 30;
    public static void main(String[] args)
        {
            // 变量j被修改
            j = 36;
            System.out.println(j);
        }
    }

    输出

    36

finally关键字

就像final是保留关键字一样,同样,finally也是Java中的保留关键字,即我们不能将其用作标识符。finally关键字与try/catch块结合使用,并确保即使抛出异常也将执行一段代码。finally块将在try和catch块之后但在控制权移回其原始位置之前执行。

// Java展示使用finally.
class 芒果 {
    static void A()
    {
        try {
            System.out.println("在A");
            throw new RuntimeException("demo");
        }
        finally
        {
            System.out.println("A的finally");
        }
    }
    // 这个方法也会调用finally
    static void B()
    {
        try {
            System.out.println("在B");
            return;
        }
        finally
        {
            System.out.println("B的finally");
        }
    }
    public static void main(String args[])
    {
        try {
            A();
        }
        catch (Exception e) {
            System.out.println("报出异常");
        }
        B();
    }
}

输出:

在A
A的finally
报出异常
在B
B的finally

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

  1. 情况1:程序中未发生异常
    // Java展示使用finally,无异常
    class B {
        public static void main(String[] args)
        {
            int k = 55;
            try {
                System.out.println("在try内");
                int z = k / 55;
            }
            catch (ArithmeticException e) {
                System.out.println("在catch内");
                System.out.println("除以0");
            }
            finally
            {
                System.out.println("无论是否异常都会执行");
            }
        }
    }

    输出

    在try内
    无论是否异常都会执行

    在这里,上面的异常不会发生,但是finally块仍然执行,因为finally意味着无论是否发生异常都将执行。
    以上程序的流程:首先,它从main方法开始,然后进入try块,在try中,因为没有发生异常,所以流程不会进入catch块,因此流程直接从try到finally块。

  2. 情况2:发生异常,并且相应的catch块匹配 
    // Java展示finally和异常发生
    class C {
        public static void main(String[] args)
        {
            int k = 66;
            try {
                System.out.println("try内");
                int z = k / 0;
             
                System.out.println("flow没到这");
            }
            catch (ArithmeticException e) {
                System.out.println("catch内");
                System.out.println("除以0");
            }
            finally
            {
                System.out.println("无论是否异常都会执行");
            }
        }
    }

    输出: 

    try内
    catch内
    除以0
    无论是否异常都会执行

    在此,发生上述异常并且找到了相应的捕获块,但是仍然finally块被执行,因为finally意味着执行是否发生异常或者是否找到相应的捕获块。
    以上程序的流程:首先,从main方法开始,然后进入try块,在尝试中发生算术异常,并且相应的catch块也可用,因此流程进入catch块。在那之后,执行finally意味着执行无论是否发生异常或是否找到相应的catch块。

  3. 情况3:发生异常,并且未找到/匹配相应的捕获块 
    // Java展示发生异常,并且未找到/匹配相应的捕获块
    class D {
        public static void main(String[] args)
        {
            int k = 15;
            try {
                System.out.println("在try内");
                int z = k / 0;
            }
            catch (NullPointerException e) {
                System.out.println("在catch内");
                System.out.println("除以0");
            }
            finally
            {
                System.out.println("无论是否异常都会执行");
            }
        }
    }

    输出: 

    在try内
    无论是否异常都会执行
    Exception in thread "main":java.lang.ArithmeticException:
    / by zero followed by stack trace.
    

    在此,发生上述异常,并且未找到/匹配相应的捕获块,但仍finally块执行,因为finally意味着执行无论是否发生异常或是否找到/匹配相应的捕获块。
    上述程序的流程:首先从main方法开始,然后去try块,和try中出现算术异常和与之对应的catch是不是可用。在那之后,执行finally,从那以后意味着无论是否发生异常,或者是否找到/匹配相应的捕获块。

finally块的应用:因此,finally块的基本用途是资源释放。意味着需要关闭在try块中打开的所有资源(例如网络连接,数据库连接),以便在打开时不会丢失资源。因此,需要在finally块中关闭这些资源。

// Java说明finally块的应用
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("try内");
            // PrintWriter, FileWriter
            out = new PrintWriter(new FileWriter("OutFile.txt"));
        }
        catch (IOException e) {
            // FileWriter在try内报错IOException
        }
        // finally会清空PrintWriter.
        finally
        {
            if (out != null) {
                System.out.println("关闭PrintWriter");
                out.close();
            } else {
                System.out.println("PrintWriter没被打开");
            }
        }
    }
}

输出

try内
PrintWriter没被打开

注意:finally块是防止资源泄漏的关键工具。当关闭文件或以其他方式恢复资源时,请将代码放在finally块中以确保始终恢复资源。

jdk 1.7如何使使用finally块为可选的

直到jdk 1.6 finally块就像英雄一样,即建议将其用于资源释放,但是从jdk 1.7开始,finally块现在是可选的(但是您可以使用它)。由于当程序流到达try块的末尾时,我们在try块中打开的资源将自动释放/关闭。
不使用finally块的自动资源重新分配的概念称为try-with-resources语句

finalize方法

方法垃圾收集器始终在删除/销毁符合垃圾收集条件的对象之前调用的方法,以执行清理活动。清理活动意味着关闭与该对象关联的资源,例如数据库连接、网络连接,或者我们可以说是收回资源分配。请记住,它不是保留关键字。
一旦finalize方法完成,垃圾收集器立即销毁该对象。finalize方法存在于Object类中,其语法为:

protected void finalize throws Throwable{}

因为Object类包含finalize方法,所以因为Object是所有Java类的超类,所以finalize方法可用于每个Java类。由于每个Java类都可以使用它,因此Garbage Collector可以在任何Java对象上调用finalize方法。
现在,Object类中存在的finalize方法具有空的实现,在我们的类中有清理活动,那么我们就可以重写这个方法来定义我们自己的清理活动。
与finalize方法有关的例子:

  1. 情况1:符合垃圾回收条件的对象将被执行,该对象的相应类的finalize方法将被执行
    class Hello {
        public static void main(String[] args)
        {
            String s = new String("RR");
            s = null;
            // 要求JVM调用垃圾回收方法
            System.gc();
            System.out.println("Main完成");
        }
        // 重写finalize方法
        public void finalize()
        {
            System.out.println("finalize方法被重写");
        }
    }
    

    输出

    Main完成

    注意:上面的输出仅是Main Completes,不是 “ finalize方法被重写”,因为垃圾回收器Garbage Collector在有资格进行Garbage收集的类对象上调用finalize方法。在上面的代码中,我们完成了->s = null,并且’s’是String类的对象,因此将调用String类的finalize方法,而不是我们的类(即Hello类)。因此,我们将代码修改为->

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

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

    finalize方法被重写
    Main完成

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

  2. 情况2:我们可以显式调用finalize方法,然后像普通方法调用一样执行该方法,但不会删除/销毁对象
    class Bye {
        public static void main(String[] args)
        {
            Bye m = new Bye();
            // 显示调用finalize方法
            m.finalize();
            m.finalize();
            m = null;
            // 要求JVM调用垃圾回收方法
            System.gc();
            System.out.println("Main完成");
        }
        // 重写finalize方法
        public void finalize()
        {
            System.out.println("finalize方法被重写");
        }
    }
    

    输出

    finalize方法被重写
    //对象没被销毁
    finalize方法被重写
    //对象没被销毁.
    Main完成
    finalize方法被重写
    //垃圾回收被调用,对象被销毁.
    

    注意:由于finalize是一个方法,而不是保留的关键字,因此我们可以显式调用finalize方法,然后它将像普通方法调用一样执行,但不会删除/销毁对象。

  3. 情况3:
    • a)如果程序员调用finalize方法,则在执行finalize方法时会出现一些未经检查的异常。
      class Hi {
          public static void main(String[] args)
          {
              Hi j = new Hi();
              // 显示调用finalize方法.
              j.finalize();
              j = null;
              // 要求JVM调用垃圾回收方法
              System.gc();
              System.out.println("Main完成");
          }
          // finalize方法被重写
          public void finalize()
          {
              System.out.println("finalize方法被重写");
              System.out.println(10 / 0);
          }
      }
      

      输出

      exception in thread "main" java.lang.ArithmeticException:
      / by zero followed by stack trace.
      

      因此关键是:如果程序员调用finalize方法,而在执行finalize方法时出现一些未经检查的异常,那么JVM将通过引发异常异常终止程序。因此,在这种情况下,程序终止是异常的

    • b)如果垃圾收集器调用finalize方法,则在执行finalize方法时会出现一些未经检查的异常。
      class RR {
          public static void main(String[] args)
          {
              RR q = new RR();
              q = null;
              // 要求JVM调用垃圾回收方法
              System.gc();
              System.out.println("Main完成");
          }
          // finalize方法被重写
          public void finalize()
          {
              System.out.println("finalize方法被重写");
              System.out.println(10 / 0);
          }
      }
      

      输出

      finalize方法被重写
      Main完成

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

重要事项:

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