`

JAVA面试题解惑系列(四)——final、finally和finalize的区别

阅读更多

JAVA面试题解惑系列(四)——final、finally和finalize的区别

关键字: java 面试题 final finally finalize

作者:臧圩人(zangweiren)
网址:http://zangweiren.iteye.com

>>>转载请注明出处! <<<

final、finally和finalize的区别是什么?

这是一道再经典不过的面试题了,我们在各个公司的面试题中几乎都能看到它的身影。final、finally和finalize虽然长得像孪生三兄弟一样,但是它们的含义和用法却是大相径庭。这一次我们就一起来回顾一下这方面的知识。

final关键字

我们首先来说说final。它可以用于以下四个地方:
  1. 定义变量,包括静态的和非静态的。
  2. 定义方法的参数。
  3. 定义方法。
  4. 定义类。

我们依次来回顾一下每种情况下final的作用。首先来看第一种情况,如果final修饰的是一个基本类型,就表示这个变量被赋予的值是不可变 的,即它是个常量;如果final修饰的是一个对象,就表示这个变量被赋予的引用是不可变的,这里需要提醒大家注意的是,不可改变的只是这个变量所保存的 引用,并不是这个引用所指向的对象。在第二种情况下,final的含义与第一种情况相同。实际上对于前两种情况,有一种更贴切的表述final的含义的描 述,那就是,如果一个变量或方法参数被final修饰,就表示它只能被赋值一次,但是JAVA虚拟机为变量设定的默认值不记作一次赋值。

被final修饰的变量必须被初始化。初始化的方式有以下几种:
  1. 在定义的时候初始化。
  2. final变量可以在初始化块中初始化,不可以在静态初始化块中初始化。
  3. 静态final变量可以在静态初始化块中初始化,不可以在初始化块中初始化。
  4. final变量还可以在类的构造器中初始化,但是静态final变量不可以。

通过下面的代码可以验证以上的观点:
Java代码 复制代码
  1. public   class  FinalTest {  
  2.     // 在定义时初始化   
  3.     public   final   int  A =  10 ;  
  4.   
  5.     public   final   int  B;  
  6.     // 在初始化块中初始化   
  7.     {  
  8.         B = 20 ;  
  9.     }  
  10.   
  11.     // 非静态final变量不能在静态初始化块中初始化   
  12.     // public final int C;   
  13.     // static {   
  14.     // C = 30;   
  15.     // }   
  16.   
  17.     // 静态常量,在定义时初始化   
  18.     public   static   final   int  STATIC_D =  40 ;  
  19.   
  20.     public   static   final   int  STATIC_E;  
  21.     // 静态常量,在静态初始化块中初始化   
  22.     static  {  
  23.         STATIC_E = 50 ;  
  24.     }  
  25.   
  26.     // 静态变量不能在初始化块中初始化   
  27.     // public static final int STATIC_F;   
  28.     // {   
  29.     // STATIC_F = 60;   
  30.     // }   
  31.   
  32.     public   final   int  G;  
  33.   
  34.     // 静态final变量不可以在构造器中初始化   
  35.     // public static final int STATIC_H;   
  36.   
  37.     // 在构造器中初始化   
  38.     public  FinalTest() {  
  39.         G = 70 ;  
  40.         // 静态final变量不可以在构造器中初始化   
  41.         // STATIC_H = 80;   
  42.   
  43.         // 给final的变量第二次赋值时,编译会报错   
  44.         // A = 99;   
  45.         // STATIC_D = 99;   
  46.     }  
  47.   
  48.     // final变量未被初始化,编译时就会报错   
  49.     // public final int I;   
  50.   
  51.     // 静态final变量未被初始化,编译时就会报错   
  52.     // public static final int STATIC_J;   
  53. }  
public class FinalTest {
	// 在定义时初始化
	public final int A = 10;

	public final int B;
	// 在初始化块中初始化
	{
		B = 20;
	}

	// 非静态final变量不能在静态初始化块中初始化
	// public final int C;
	// static {
	// C = 30;
	// }

	// 静态常量,在定义时初始化
	public static final int STATIC_D = 40;

	public static final int STATIC_E;
	// 静态常量,在静态初始化块中初始化
	static {
		STATIC_E = 50;
	}

	// 静态变量不能在初始化块中初始化
	// public static final int STATIC_F;
	// {
	// STATIC_F = 60;
	// }

	public final int G;

	// 静态final变量不可以在构造器中初始化
	// public static final int STATIC_H;

	// 在构造器中初始化
	public FinalTest() {
		G = 70;
		// 静态final变量不可以在构造器中初始化
		// STATIC_H = 80;

		// 给final的变量第二次赋值时,编译会报错
		// A = 99;
		// STATIC_D = 99;
	}

	// final变量未被初始化,编译时就会报错
	// public final int I;

	// 静态final变量未被初始化,编译时就会报错
	// public static final int STATIC_J;
}

我们运行上面的代码之后出了可以发现final变量(常量)和静态final变量(静态常量)未被初始化时,编译会报错。

用final修饰的变量(常量)比非final的变量(普通变量)拥有更高的效率,因此我们在实际编程中应该尽可能多的用常量来代替普通变量,这也是一个很好的编程习惯。

当final用来定义一个方法时,会有什么效果呢?正如大家所知,它表示这个方法不可以被子类重写,但是它这不影响它被子类继承。我们写段代码来验证一下:
Java代码 复制代码
  1. class  ParentClass {  
  2.     public   final   void  TestFinal() {  
  3.         System.out.println("父类--这是一个final方法" );  
  4.     }  
  5. }  
  6.   
  7. public   class  SubClass  extends  ParentClass {  
  8.     /**  
  9.      * 子类无法重写(override)父类的final方法,否则编译时会报错  
  10.      */   
  11.     // public void TestFinal() {   
  12.     // System.out.println("子类--重写final方法");   
  13.     // }   
  14.       
  15.     public   static   void  main(String[] args) {  
  16.         SubClass sc = new  SubClass();  
  17.         sc.TestFinal();  
  18.     }  
  19. }  
class ParentClass {
	public final void TestFinal() {
		System.out.println("父类--这是一个final方法");
	}
}

public class SubClass extends ParentClass {
	/**
	 * 子类无法重写(override)父类的final方法,否则编译时会报错
	 */
	// public void TestFinal() {
	// System.out.println("子类--重写final方法");
	// }
	
	public static void main(String[] args) {
		SubClass sc = new SubClass();
		sc.TestFinal();
	}
}

这里需要特殊说明的是,具有private访问权限的方法也可以增加final修饰,但是由于子类无法继承private方法,因此也无法重写 它。编译器在处理private方法时,是按照final方法来对待的,这样可以提高该方法被调用时的效率。不过子类仍然可以定义同父类中的 private方法具有同样结构的方法,但是这并不会产生重写的效果,而且它们之间也不存在必然联系。

最后我们再来回顾一下final用于类的情况。这个大家应该也很熟悉了,因为我们最常用的String类就是final的。由于final类不允 许被继承,编译器在处理时把它的所有方法都当作final的,因此final类比普通类拥有更高的效率。而由关键字abstract定义的抽象类含有必须 由继承自它的子类重载实现的抽象方法,因此无法同时用final和abstract来修饰同一个类。同样的道理,final也不能用来修饰接口。 final的类的所有方法都不能被重写,但这并不表示final的类的属性(变量)值也是不可改变的,要想做到final类的属性值不可改变,必须给它增 加final修饰,请看下面的例子:
Java代码 复制代码
  1. public   final   class  FinalTest {  
  2.   
  3.     int  i =  10 ;  
  4.   
  5.     public   static   void  main(String[] args) {  
  6.         FinalTest ft = new  FinalTest();  
  7.         ft.i = 99 ;  
  8.         System.out.println(ft.i);  
  9.     }  
  10. }  
public final class FinalTest {

	int i = 10;

	public static void main(String[] args) {
		FinalTest ft = new FinalTest();
		ft.i = 99;
		System.out.println(ft.i);
	}
}

运行上面的代码试试看,结果是99,而不是初始化时的10。

finally语句

接下来我们一起回顾一下finally的用法。这个就比较简单了,它只能用在try/catch语句中,并且附带着一个语句块,表示这段语句最终总是被执行。请看下面的代码:
Java代码 复制代码
  1. public   final   class  FinallyTest {  
  2.     public   static   void  main(String[] args) {  
  3.         try  {  
  4.             throw   new  NullPointerException();  
  5.         } catch  (NullPointerException e) {  
  6.             System.out.println("程序抛出了异常" );  
  7.         } finally  {  
  8.             System.out.println("执行了finally语句块" );  
  9.         }  
  10.     }  
  11. }  
public final class FinallyTest {
	public static void main(String[] args) {
		try {
			throw new NullPointerException();
		} catch (NullPointerException e) {
			System.out.println("程序抛出了异常");
		} finally {
			System.out.println("执行了finally语句块");
		}
	}
}

运行结果说明了finally的作用:
  1. 程序抛出了异常
  2. 执行了finally语句块

请大家注意,捕获程序抛出的异常之后,既不加处理,也不继续向上抛出异常,并不是良好的编程习惯,它掩盖了程序执行中发生的错误,这里只是方便演示,请不要学习。

那么,有没有一种情况使finally语句块得不到执行呢?大家可能想到了return、continue、break这三个可以打乱代码顺序执行语句的规律。那我们就来试试看,这三个语句是否能影响finally语句块的执行:
Java代码 复制代码
  1. public   final   class  FinallyTest {  
  2.   
  3.     // 测试return语句   
  4.     public  ReturnClass testReturn() {  
  5.         try  {  
  6.             return   new  ReturnClass();  
  7.         } catch  (Exception e) {  
  8.             e.printStackTrace();  
  9.         } finally  {  
  10.             System.out.println("执行了finally语句" );  
  11.         }  
  12.         return   null ;  
  13.     }  
  14.   
  15.     // 测试continue语句   
  16.     public   void  testContinue() {  
  17.         for  ( int  i =  0 ; i <  3 ; i++) {  
  18.             try  {  
  19.                 System.out.println(i);  
  20.                 if  (i ==  1 ) {  
  21.                     continue ;  
  22.                 }  
  23.             } catch  (Exception e) {  
  24.                 e.printStackTrace();  
  25.             } finally  {  
  26.                 System.out.println("执行了finally语句" );  
  27.             }  
  28.         }  
  29.     }  
  30.   
  31.     // 测试break语句   
  32.     public   void  testBreak() {  
  33.         for  ( int  i =  0 ; i <  3 ; i++) {  
  34.             try  {  
  35.                 System.out.println(i);  
  36.                 if  (i ==  1 ) {  
  37.                     break ;  
  38.                 }  
  39.             } catch  (Exception e) {  
  40.                 e.printStackTrace();  
  41.             } finally  {  
  42.                 System.out.println("执行了finally语句" );  
  43.             }  
  44.         }  
  45.     }  
  46.   
  47.     public   static   void  main(String[] args) {  
  48.         FinallyTest ft = new  FinallyTest();  
  49.         // 测试return语句   
  50.         ft.testReturn();  
  51.         System.out.println();  
  52.         // 测试continue语句   
  53.         ft.testContinue();  
  54.         System.out.println();  
  55.         // 测试break语句   
  56.         ft.testBreak();  
  57.     }  
  58. }  
  59.   
  60. class  ReturnClass {  
  61.     public  ReturnClass() {  
  62.         System.out.println("执行了return语句" );  
  63.     }  
  64. }  
public final class FinallyTest {

	// 测试return语句
	public ReturnClass testReturn() {
		try {
			return new ReturnClass();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			System.out.println("执行了finally语句");
		}
		return null;
	}

	// 测试continue语句
	public void testContinue() {
		for (int i = 0; i < 3; i++) {
			try {
				System.out.println(i);
				if (i == 1) {
					continue;
				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				System.out.println("执行了finally语句");
			}
		}
	}

	// 测试break语句
	public void testBreak() {
		for (int i = 0; i < 3; i++) {
			try {
				System.out.println(i);
				if (i == 1) {
					break;
				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				System.out.println("执行了finally语句");
			}
		}
	}

	public static void main(String[] args) {
		FinallyTest ft = new FinallyTest();
		// 测试return语句
		ft.testReturn();
		System.out.println();
		// 测试continue语句
		ft.testContinue();
		System.out.println();
		// 测试break语句
		ft.testBreak();
	}
}

class ReturnClass {
	public ReturnClass() {
		System.out.println("执行了return语句");
	}
}

上面这段代码的运行结果如下:
  1. 执行了return语句
  2. 执行了finally语句
  3. 0
  4. 执行了finally语句
  5. 1
  6. 执行了finally语句
  7. 2
  8. 执行了finally语句
  9. 0
  10. 执行了finally语句
  11. 1
  12. 执行了finally语句

很明显,return、continue和break都没能阻止finally语句块的执行。从输出的结果来看,return语句似乎在 finally语句块之前执行了,事实真的如此吗?我们来想想看,return语句的作用是什么呢?是退出当前的方法,并将值或对象返回。如果 finally语句块是在return语句之后执行的,那么return语句被执行后就已经退出当前方法了,finally语句块又如何能被执行呢?因 此,正确的执行顺序应该是这样的:编译器在编译return new ReturnClass();时,将它分成了两个步骤,new ReturnClass()和return,前一个创建对象的语句是在finally语句块之前被执行的,而后一个return语句是在finally语 句块之后执行的,也就是说finally语句块是在程序退出方法之前被执行的。同样,finally语句块是在循环被跳过(continue)和中断 (break)之前被执行的。

finalize方法

最后,我们再来看看finalize,它是一个方法,属于java.lang.Object类,它的定义如下:
Java代码 复制代码
  1. protected   void  finalize()  throws  Throwable { }  
protected void finalize() throws Throwable { }

众所周知,finalize()方法是GC(garbage collector)运行机制的一部分,关于GC的知识我们将在后续的章节中来回顾。

在此我们只说说finalize()方法的作用是什么呢?

finalize()方法是在GC清理它所从属的对象时被调用的,如果执行它的过程中抛出了无法捕获的异常(uncaught exception),GC将终止对改对象的清理,并且该异常会被忽略;直到下一次GC开始清理这个对象时,它的finalize()会被再次调用。

请看下面的示例:
Java代码 复制代码
  1. public   final   class  FinallyTest {  
  2.     // 重写finalize()方法   
  3.     protected   void  finalize()  throws  Throwable {  
  4.         System.out.println("执行了finalize()方法" );  
  5.     }  
  6.   
  7.     public   static   void  main(String[] args) {  
  8.         FinallyTest ft = new  FinallyTest();  
  9.         ft = null ;  
  10.         System.gc();  
  11.     }  
  12. }  
public final class FinallyTest {
	// 重写finalize()方法
	protected void finalize() throws Throwable {
		System.out.println("执行了finalize()方法");
	}

	public static void main(String[] args) {
		FinallyTest ft = new FinallyTest();
		ft = null;
		System.gc();
	}
}

运行结果如下:
  • 执行了finalize()方法

程序调用了java.lang.System类的gc()方法,引起GC的执行,GC在清理ft对象时调用了它的finalize()方法,因此才有了上面的输出结果。调用System.gc()等同于调用下面这行代码:
Java代码 复制代码
  1. Runtime.getRuntime().gc();  
Runtime.getRuntime().gc();

调用它们的作用只是建议垃圾收集器(GC)启动,清理无用的对象释放内存空间,但是GC的启动并不是一定的,这由JAVA虚拟机来决定。直到 JAVA虚拟机停止运行,有些对象的finalize()可能都没有被运行过,那么怎样保证所有对象的这个方法在JAVA虚拟机停止运行之前一定被调用 呢?答案是我们可以调用System类的另一个方法:
Java代码 复制代码
  1. public   static   void  runFinalizersOnExit( boolean  value) {  
  2.     //other code   
  3. }  
public static void runFinalizersOnExit(boolean value) {
	//other code
}

给这个方法传入true就可以保证对象的finalize()方法在JAVA虚拟机停止运行前一定被运行了,不过遗憾的是这个方法是不安全的,它会导致有用的对象finalize()被误调用,因此已经不被赞成使用了。

由于finalize()属于Object类,因此所有类都有这个方法,Object的任意子类都可以重写(override)该方法,在其中释放系统资源或者做其它的清理工作,如关闭输入输出流。

通过以上知识的回顾,我想大家对于final、finally、finalize的用法区别已经很清楚了。
分享到:
评论

相关推荐

    JAVA面试题解惑系列合集

    1.4 JAVA面试题解惑系列(四)——final、finally和finalize的区别 1.5 JAVA面试题解惑系列(五)——传了值还是传了引用? 1.6 JAVA面试题解惑系列(六)——字符串(String)杂谈 1.7 JAVA面试题解惑系列(七)...

    java面试题 谈谈final, finally, finalize的区别

    一,谈谈final, finally, finalize的区别。 java面试题

    Java面试题解惑系列

    4、final,finally,finalize;5.传了值还是传了引用;6.String杂谈;7.日期与时间的处理;8.基本类型总结;9.继承,多态,重载,重写;10.多线程;11.运算符总结。 适合将要笔试面试Java的朋友参考。

    final, finally, finalize的区别

    final, finally, finalize的区别

    Java中final,finally,finalize三个关键字的区别_动力节点Java学院整理

    Java中final,finally,finalize三个关键字的区别_动力节点Java学院整理

    JAVA面试题解惑系列114页.pdf

    (四)final、finally 和finalize 的区别 (五)传了值还是传了引用? (六)字符串(String)杂谈 (七)日期和时间的处理 (八)聊聊基本类型(内置类型) (九)继承、多态、重载和重写 (十)话说多线程 (十一)...

    JAVA面试题解惑系列

    类的初始化顺序 ...final、finally和finalize的区别 传了值还是传了引用? 字符串(String)杂谈 日期和时间的处理 聊聊基本类型(内置类型) 继承、多态、重载和重写 话说多线程 这些运算符你是否还记得?

    大公司的Java面试题集

    大公司的Java面试题集 大公司的Java面试题集  第一,谈谈final, finally, finalize的区别。  final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。 finally是异常处理语句结构的一...

    java 基础之final、finally和finalize的区别

    主要介绍了java 基础之final、finally和finalize的区别的相关资料,需要的朋友可以参考下

    典型JAVA面试试题及答案.pdf

    谈谈final, finally, finalize 的区别?final 是一个修饰符, 修饰类的时候表示类不可继承, 所以一个类不可能既是abstract 又是final 的; 修饰变量的时候表示这个变量不可被更改并且必须赋初始值; 修饰方法的时候...

    毕业就业-刷题库Java面试题大全(2021年-2022年).rar

    5. final、finally、finalize 有什么区别? 6. NoClassDefFoundError 和 ClassNotFoundException 区别? 7. try-catch-finally 中哪个部分可以省略? 8. try-catch-finally 中,如果 catch 中 return 了,finally 还...

    java面试题解析困惑之一

    java面试题解析困惑之final、finally和finalize的区别

    Java面试题及答案(面试题集锦)

    谈谈final, finally, finalize的区别。  final—修饰符(关键字)如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract的,又被声明为final的。将...

    final、fianlly、finalize区别

    final:修饰符(关键字)如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为 父类被继承。因此一个类不能既被声明为abstract 的,又被声明为final 的。将变量或 方法声明为 final,可以保证它们在...

    最新最全Java面试题汇总

    最新Java面试题汇总 sleep() 和 wait() 有什么区别、final, finally, finalize的区别、面向对象的特征有哪些方面、ArrayList,Vector, LinkedList的存储性能和特性等诸多面试常见题目

    Java面试大全(备战2021) 最新Java面试必问合集 PDF版

    Java面试大全是一套最新Java面试必问合集,这本面试手册包含了Java基础、Java集合、JVM、Spring、Spring Boot、Spring Cloud、Mysql、Redis、...5. final、finally、finalize 有什么区别? 6. NoClassDefFoundErr

    模拟面试题及答案 Java

    选择了一些经常问到的java面试题,及建议答案,希望对各位面试者有用。...描述final﹑finally和finalize的区别。 编程题:使用JavaScript和HTML编写网页实现如图一所示计算功能: 购买总价=购买价格×购买数量。

    详解Java编程中final,finalize,finally的区别

    主要介绍了详解Java编程中final,finalize,finally的区别,这个在Java面试题中简直是太常见了...需要的朋友可以参考下

    Java面试题及答案-java面试题

    Java 中的 final、finally 和 finalize 关键字有什么区别? final:当用“final”关键字声明一个变量时,它的值一旦被赋值就不能改变。当使用“final”关键字声明方法时,它不能在子类中被覆盖。当用“final”...

Global site tag (gtag.js) - Google Analytics