【Java面试题系列】:Java中final finally finalize的区别

按个人我的理解,这个题目自己就问的有点问题,由于这3个关键字之间没啥关系,是相对独立的,我猜测这道题的初衷应该是想了解面试者对Java中final finally finalize的使用方法的掌握状况,只是由于3个关键字比较像,而成了如今网上流传的题目“Java中final finally finalize的区别”。java

既然是想了解面试者对Java中final finally finalize的使用方法的掌握状况,那么本篇咱们就分别讲解下final,finally,finalize的使用方法。面试

1.final用法

咱们先看下final的英文释义:最终的;决定性的;不可更改的,不由要推测被final修饰的变量,方法或者类是否是不可修改的呢?spring

1.1final修饰类

在Java中,被final修饰的类,不能被继承,也就是final类的成员方法没有机会被继承,也没有机会被重写。springboot

在设计类的时候,若是这个类不须要有子类,类的实现细节不容许改变,那么就能够设计为final类。ide

如咱们在开发中常用的String类就是final类,如下为部分源码:函数

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    ......
}

1.2final修饰方法

在Java中,被final修饰的方法,能够被继承,但不能被子类重写(覆盖)。this

在设计方法时,若是这个方法不但愿被子类重写(覆盖),那么就能够设计为final方法。spa

举个具体的例子,咱们新建个父类Animal以下:线程

package com.zwwhnly.springbootdemo;

public class Animal {
    public void eat() {
        System.out.println("Animal eat.");
    }

    public void call() {
        System.out.println("Animal call.");
    }

    public final void fly() {
        System.out.println("Animal fly.");
    }

    private final void swim() {
        System.out.println("Animal swim.");
    }
}

而后定义一个子类Cat继承Animal类,代码以下:翻译

package com.zwwhnly.springbootdemo;

public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("Cat eat.");
    }

    @Override
    public void fly() {
        System.out.println("Cat fly.");
    }

    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.eat();
        cat.call();
        cat.fly();
        cat.swim();
    }
}

咱们会发现,以上代码中有2个错误

1)当咱们重写fly()方法时,由于父类的fly()方法被定义为final方法,重写时会编译错误

2)cat.swim();报错,由于父类的swim()方法被定义为private,子类是继承不到的

而后咱们将报错的代码删除,运行结果以下:

Cat eat.

Animal call.

Animal fly.

也就是eat()方法被子类重写了,继承了父类的成员方法call()和final方法fly()。

可是值得注意的是,在子类Cat中,咱们是能够从新定义父类的私有final方法swim()的,不过此时明显不是重写(你能够加@Override试试,会编译报错),而是子类本身的成员方法swim()。

package com.zwwhnly.springbootdemo;

public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("Cat eat.");
    }

    public void swim() {
        System.out.println("Cat swim.");
    }

    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.eat();
        cat.call();
        cat.fly();
        cat.swim();
    }
}

此时的运行结果为:

Cat eat.

Animal call.

Animal fly.

Cat swim.

1.3final修饰成员变量

用final修饰的成员变量没有默认值,能够在声明时赋值或者在构造函数中赋值,但必须赋值且只能被赋值1次,赋值后没法修改。

咱们修改下1.2中的Cat类代码,定义2个final成员变量,1个声明完当即赋值,1个在构造函数中赋值:

package com.zwwhnly.springbootdemo;

public class Cat extends Animal {
    private final int age = 1;
    private final String name;

    public Cat(String name) {
        this.name = name;
    }

    @Override
    public void eat() {
        System.out.println("Cat eat.");
    }

    public static void main(String[] args) {
        Cat whiteCat = new Cat("小白");
        whiteCat.age = 2;
        System.out.println(whiteCat.age);
        System.out.println(whiteCat.name);

        Cat blackCat = new Cat("小黑");
        blackCat.name = "小黑猫";
        System.out.println(blackCat.age);
        System.out.println(blackCat.name);
    }
}

以上代码有2个编译错误,1个是whiteCat.age = 2;修改为员变量age时,另1个是blackCat.name = "小黑猫";修改为员变量name时,都提示不能修改final成员变量。

删除掉错误的代码,运行结果以下:

1

小白

1

小黑

1.4final修饰局部变量

被final修饰的局部变量,既能够在声明时当即赋值,也能够先声明,后赋值,但只能赋值一次,不能够重复赋值。

修改下Cat类的eat()方法以下:

@Override
public void eat() {

    final String breakfast;
    final String lunch = "午饭";
    breakfast = "早餐";
    lunch = "午饭2";
    breakfast = "早餐2";

    System.out.println("Cat eat.");
}

以上代码中2个错误,1个是lunch = "午饭2";,1个是breakfast = "早餐2";,都是对final局部变量第2次赋值时报错。

1.5final修饰方法参数

方法参数其实也是局部变量,所以final修饰方法参数和1.4中final修饰局部变量的使用相似,即方法中只能使用方法的参数值,但不能修改参数值。

在Cat类中新增方法printCatName,将方法参数修饰为final:

public static void main(String[] args) {
    Cat whiteCat = new Cat("小白");
    whiteCat.printCatName(whiteCat.name);
}

public void printCatName(final String catName) {
    //catName = "修改catName";    // 该行语句会报错
    System.out.println(catName);
}

运行结果:

小白

2.finally用法

提起finally,你们都知道,这是Java中处理异常的,一般和try,catch一块儿使用,主要做用是无论代码发不发生异常,都会保证finally中的语句块被执行。

你是这样认为的吗?说实话,哈哈。

那么问题来了,finally语句块必定会被执行吗?,答案是不必定

让咱们经过具体的示例来证实该结论。

2.1在 try 语句块以前返回(return)或者抛出异常,finally不会被执行

package com.zwwhnly.springbootdemo;

public class FinallyTest {
    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");
        }
    }
}

运行结果以下:

也就是说,以上示例中,finally语句块没有被执行。

而后咱们将上例中注释的代码取消注释,此时运行结果为:

return value of test():0

finally语句块仍是没有被执行,所以,咱们能够得出结论:

只有与 finally 相对应的 try 语句块获得执行的状况下,finally 语句块才会执行。

以上两种状况,都是在 try 语句块以前返回(return)或者抛出异常,因此 try 对应的 finally 语句块没有执行。

2.2与 finally 相对应的 try 语句块获得执行,finally不必定会被执行

那么,与 finally 相对应的 try 语句块获得执行的状况下,finally 语句块必定会执行吗?答案仍然是不必定。

看下下面这个例子:

package com.zwwhnly.springbootdemo;

public class FinallyTest {
    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");
        }
    }
}

运行结果为:

try block

finally语句块仍是没有被执行,为何呢?由于咱们在try语句块中执行了System.exit(0);,终止了Java虚拟机的运行。固然,通常状况下,咱们的应用程序中是不会调用System.exit(0);的,那么,若是不调用这个方法,finally语句块必定会被执行吗?

答案固然仍是不必定,当一个线程在执行 try 语句块或者 catch 语句块时被打断(interrupted)或者被终止(killed),与其相对应的 finally 语句块可能不会执行。还有更极端的状况,就是在线程运行 try 语句块或者 catch 语句块时,忽然死机或者断电,finally 语句块确定不会执行了。固然,死机或者断电属于极端状况,在这里只是为了证实,finally语句块不必定会被执行。

2.3try语句块或者catch语句块中有return语句

若是try语句块中有return语句, 是return语句先执行仍是finally语句块先执行呢?

带着这个问题,咱们看下以下这个例子:

package com.zwwhnly.springbootdemo;

public class FinallyTest {
    public static void main(String[] args) {
        try {
            System.out.println("try block");
            return;
        } finally {
            System.out.println("finally block");
        }
    }
}

运行结果:

try block

finally block

结论:finally 语句块在 try 语句块中的 return 语句以前执行。

若是catch语句块中有return语句,是return语句先执行仍是finally语句块先执行呢?

带着这个问题,咱们看下以下这个例子:

package com.zwwhnly.springbootdemo;

public class FinallyTest {
    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");
            i = i / 0;
            return 1;
        } catch (Exception e) {
            System.out.println("catch block");
            return 2;
        } finally {
            System.out.println("finally block");
        }
    }
}

运行结果:

try block

catch block

finally block

return value of test():2

结论:finally 语句块在 catch 语句块中的 return 语句以前执行。

经过上面2个例子,咱们能够看出,其实 finally 语句块是在 try 或者 catch 中的 return 语句以前执行的。更加通常的说法是,finally 语句块应该是在控制转移语句以前执行,控制转移语句除了 return 外,还有 break ,continue和throw。

2.4其它几个例子

示例1:

package com.zwwhnly.springbootdemo;

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

    public static int getValue() {
        try {
            return 0;
        } finally {
            return 1;
        }
    }
}

运行结果:

return value of getValue():1

示例2:

package com.zwwhnly.springbootdemo;

public class FinallyTest {
    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++;
        }
    }
}

运行结果:

return value of getValue():1

也许你会好奇,应该会返回2,怎么返回1了呢?能够借鉴下如下内容来理解(牵扯到了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 语句,由于它们根本就没有返回值。

示例3:

package com.zwwhnly.springbootdemo;

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

    public static int getValue() {
        int i = 1;
        try {
            i = 4;
        } finally {
            i++;
            return i;
        }
    }
}

运行结果:

return value of getValue():5

示例4:

package com.zwwhnly.springbootdemo;

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

    public static int getValue() {
        int i = 1;
        try {
            i = 4;
        } finally {
            i++;
        }
        return i;
    }
}

运行结果:

return value of getValue():5

示例5:

package com.zwwhnly.springbootdemo;

public class FinallyTest {
    public static void main(String[] args) {
        System.out.println(test());
    }

    public static String test() {
        try {
            System.out.println("try block");
            return test1();
        } finally {
            System.out.println("finally block");
        }
    }

    public static String test1() {
        System.out.println("return statement");
        return "after return";
    }
}

try block

return statement

finally block

after return

2.5总结

  1. finally语句块不必定会被执行
  2. finally 语句块在 try 语句块中的 return 语句以前执行。
  3. finally 语句块在 catch 语句块中的 return 语句以前执行。
  4. 注意控制转移语句 return ,break ,continue,throw对执行顺序的影响

3.finalize用法

finalize()是Object类的一个方法,所以全部的类都继承了这个方法。

protected void finalize() throws Throwable { }

finalize()主要用于在垃圾收集器将对象从内存中清除出去以前作必要的清理工做。这个方法是由垃圾收集器在肯定这个对象没有被引用时对这个对象调用的。

子类覆盖 finalize() 方法以整理系统资源或者执行其余清理工做。finalize() 方法是在垃圾收集器删除对象以前对这个对象调用的。

当垃圾回收器(GC)决定回收某对象时,就会运行该对象的finalize()方法。

不过在Java中,若是内存老是充足的,那么垃圾回收可能永远不会进行,也就是说filalize()可能永远不被执行,显然期望它作收尾工做是靠不住的。

相关文章
相关标签/搜索