final,finally,finalize的区别
1 final
在java中,final可以用来修饰类、方法和变量(成员变量或局部变量)。
1.1 修饰类
当用final修饰类的时,表明该类不能被其他类所继承。当我们需要让一个类永远不被继承,此时就可以用final修饰,但要注意:
final类中所有的成员方法都会隐式的定义为final方法。(即被final修饰的类,其方法一定是final类型;但是其方法是final类型,但却不一定是final类)
1.2 修饰方法
使用final方法的原因主要有两个:
-
把方法锁定,以防止继承类对其进行更改。
-
效率,在早期的java版本中,会将final方法转为内嵌调用。但若方法过于庞大,可能在性能上不会有多大提升。因此在最近版本中,不需要final方法进行这些优化了。
final方法意味着“最后的、最终的”含义,即此方法不能被重写。
public class A {
public final void getName(){
System.out.println("被final修饰的public方法,其子类不能重写这个方法");
}
}
public class B extends A{
/**
* 重写后,会爆这样的错误:'getName()' cannot override 'getName()' in 'com.thinking.A';
* overridden method is final
* 子类无法重写(override父类的final方法,否则编译时会报错
*/
// public void getName(){
// System.out.println("aaa");
// }
public static void main(String[] args) {
new B().getName();
}
}
1.3 修饰变量
final成员变量表示常量,只能被赋值一次,赋值后其值不再改变。类似于C++中的const。
-
当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;
-
如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化。
-
final修饰一个成员变量(属性),必须要显示初始化。这里有两种初始化方式,一种是在变量声明的时候初始化;第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。
-
当函数的参数类型声明为final时,说明该参数是只读型的。即你可以读取使用该参数,但是无法改变该参数的值。
public class Main {
public static void main(String[] args) {
final String str="hello word";
//Cannot assign a value to final variable 'str'
str="hello java";//报错
System.out.println(str);
String str2 = new String("abc");
final String str3=str2;
System.out.println(str2);
//Cannot assign a value to final variable 'str4'
final String str4 = new String("aaa");
str4="aaaa";//报错
}
}
在java中,String被设计成final类,那为什么平时使用时,String的值可以被改变呢?
字符串常量池是java堆内存中一个特殊的存储区域,当我们建立一个String对象时,假设常量池不存在该字符串,则创建一个,若存在则直接引用已经存在的字符串。当我们对String对象值改变的时候,例如 String a="A"; a="B"
。a是String对象的一个引用(我们这里所说的String对象其实是指字符串常量),当a=“B”执行时,并不是原本String对象(“A”)发生改变,而是创建一个新的对象(“B”),令a引用它。
2 finally
finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常),经常被用在需要释放资源的情况下。(×)(这句话其实存在一定的问题)
很多人都认为finally语句块一定会执行,但真的是这样么?答案是否定的,例如下面这个例子:
public class Test {
public static void main(String[] args) {
System.out.println("return value of test():"+test());
}
private static int test() {
int i=1;
// if(i==1){
// return i;
// }
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");
}
}
}
运行结果:
The previous statement of try block
Exception in thread "main" java.lang.ArithmeticException: / by zero
分析:
- 只有与finally对应的try语句块得到执行的情况下,finally语句块才会执行。以上两种情况在执行try语句块之前已经返回或抛出异常,所以try对应的finally语句并没有执行。
- 但是当
i=i/0;
被try包括的时候,finally语句就会执行。
如下:
public class Test {
public static void main(String[] args) {
System.out.println("return value of test():"+test());
}
private static int test() {
int i=1;
System.out.println("The previous statement of try block");
try {
i=i/0;
System.out.println("try block");
return i;
}finally {
System.out.println("finally block");
}
}
}
运行结果:
The previous statement of try block
finally block
Exception in thread "main" java.lang.ArithmeticException: / by zero
但是,在某些情况下,即使try语句执行了,finally语句也不一定执行。例如以下情况:
public class Test {
public static void main(String[] args) {
System.out.println("return value of test():"+test());
}
private static int test() {
int i=1;
// if(i==1){
// return i;
// }
System.out.println("The previous statement of try block");
//i=i/0;
try {
System.out.println("try block");
System.exit(0);
return i;
}finally {
System.out.println("finally block");
}
}
}
finally 语句块还是没有执行,为什么呢?
- 因为我们在 try 语句块中执行了 System.exit (0) 语句,终止了 Java 虚拟机的运行。
那有人说了,在一般的 Java 应用中基本上是不会调用这个 System.exit(0) 方法的。OK !没有问题,我们不调用 System.exit(0) 这个方法,那么 finally 语句块就一定会执行吗?
- 再一次让大家失望了,答案还是否定的。
- 当一个线程在执行 try 语句块或者 catch 语句块时被打断(interrupted)或者被终止(killed),与其相对应的 finally 语句块可能不会执行。还有更极端的情况,就是在线程运行 try 语句块或者 catch 语句块时,突然死机或者断电,finally 语句块肯定不会执行了。可能有人认为死机、断电这些理由有些强词夺理,没有关系,我们只是为了说明这个问题。
易错点
在try-catch-finally语句中执行return语句。我们看如下代码:
public class Test {
public static void main(String[] args) {
System.out.println(test1(null)+","+test1("0")+","+test1(""));
}
private static int test1(String str) {
try {
return str.charAt(0)-'0';
}catch (NullPointerException e){
return 1;
}catch (StringIndexOutOfBoundsException e2){
return 2;
}catch (Exception e3){
return 3;
}finally {
return 4;
}
}
}
运行结果:
4,4,4
- 首先finally语句在改代码中一定会执行,从运行结果来看,每次return的结果都是4(即finally语句),仿佛其他return语句被屏蔽掉了。
- 事实也确实如此,因为finally用法特殊,所以会撤销之前的return语句,继续执行最后的finally块中的代码。
3 finalize
-
finalize()是在java.lang.Object里定义的,也就是说每一个对象都有这么个方法。这个方法在gc启动,该对象被回收的时候被调用。其实gc可以回收大部分的对象(凡是new出来的对象,gc都能搞定,一般情况下我们又不会用new以外的方式去创建对象),所以一般是不需要程序员去实现finalize的。
-
特殊情况下,需要程序员实现finalize,当对象被回收的时候释放一些资源,比如:一个socket链接,在对象初始化时创建,整个生命周期内有效,那么就需要实现finalize,关闭这个链接。
-
使用finalize还需要注意一个事,调用super.finalize();
-
一个对象的finalize()方法只会被调用一次,而且finalize()被调用不意味着gc会立即回收该对象,所以有可能调用finalize()后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会调用finalize(),产生问题。 所以,推荐不要使用finalize()方法,它跟析构函数不一样。
4 区别
4.1 简单区别:
- final用于声明属性,方法和类,分别表示属性不可改变,方法不可覆盖,类不可继承。
- finally是异常处理语句结构的一部分,表示总是执行。
- finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,供垃圾收集时的其他资源回收,例如关闭文件等。
4.2 中等区别:
虽然这些单词在Java中都存在,但是并没太多关联:
final:java中的关键字,修饰符。
- 如果一个类被声明为final,就意味着它不能再派生出新的子类,不能作为父类被继承。因此,一个类不能同时被声明为abstract抽象类的和final的类。
- 如果将变量或者方法声明为final,可以保证它们在使用中不被改变.
被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。
被声明final的方法只能使用,不能重载。
finally:java的一种异常处理机制。
3. finally是对Java异常处理模型的最佳补充。finally结构使代码总会执行,而不管无异常发生。
4. 使用finally可以维护对象的内部状态,并可以清理非内存资源。特别是在关闭数据库连接这方面,如果程序员把数据库连接的close()方法放到finally中,就会大大降低程序出错的几率。
finalize:Java中的一个方法名。
- Java技术使用finalize()方法在垃圾收集器将对象从内存中清除出去前,做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没被引用时对这个对象调用的。
- 它是在Object类中定义的,因此所以的类都继承了它。子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。
4.3 详细区别
4.3.1 final
final关键字可以用于以下四个地方:
- 定义变量,包括静态的和非静态的。
- 定义方法的参数。
- 定义方法。
- 定义类。
4.3.1.1 定义变量
包括静态的和非静态的,定义方法的参数。
第一种情况:
- 如果final修饰的是一个基本类型,就表示这个变量被赋予的值是不可变的,即它是个常量;
- 如果final修饰的是一个对象,就表示这个变量被赋予的引用是不可变的
这里需要提醒大家注意的是,不可改变的只是这个变量所保存的引用,并不是这个引用所指向的对象。
第二种情况:
final的含义与第一种情况相同。
实际上对于前两种情况,一种更贴切的表述final的含义的描述,那就是,如果一个变量或方法参数被final修饰,就表示它只能被赋值一次,但是JAVA虚拟机为变量设定的默认值不记作一次赋值。被final修饰的变量必须被初始化。初始化的方式以下几种:
- 在定义的时候初始化。
- final变量可以在初始化块中初始化,不可以在静态初始化块中初始化。
- 静态final变量可以在定义时初始化,也可以在静态初始化块中初始化,不可以在初始化块中初始化。
- final变量还可以在类的构造器中初始化,但是静态final变量不可以。
通过下面的代码可以验证以上的观点:
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 L;
//静态final变量未被初始化,编译时就会报错
//public static final int STATIC_J;
}
用final修饰的变量(常量)比非final的变量(普通变量)拥更高的效率,因此我们在际编程中应该尽可能多的用常量来代替普通变量。
4.3.1.2 定义方法
当final用来定义一个方法时,它表示这个方法不可以被子类重写,但是并不影响它被子类继承。从上面1.2中的代码就可以看出,这里不再重复。
这里需要特殊说明的是,具有private访问权限的方法也可以增加final修饰,但是由于子类无法继承private方法,因此也无法重写它。编译器在处理private方法时,是照final方来对待的,这样可以提高该方法被调用时的效率。不过子类仍然可以定义同父类中private方法具同样结构的方法,但是这并不会产生重写的效果,而且它们之间也不存在必然联系。如下所示:
public class A {
private final void getName(){
System.out.println("被final修饰的public方法,其子类不能重写这个方法");
}
}
public class B extends A{
private void getName(){
System.out.println("aaa");
}
public static void main(String[] args) {
new B().getName();
}
}
4.3.1.3 定义类
最后我们再来回顾一下final用于类的情况。这个大家应该也很熟悉了,因为我们最常用的String类就是final的。
- 由于final类不允许被继承,编译器在处理时把它的所方法都当作final的,因此final类比普通类拥更高的效率。
- 而由关键字abstract定义的抽象类含必须由继承自它的子类重载实现的抽象方法,因此无法同时用final和abstract来修饰同一个类。
- 同样的道理,final也不能用来修饰接口。 final的类的所以方法都不能被重写,但这并不表示final的类的属性(变量值也是不可改变的,要想做到final类的属性值不可改变,必须给它增加final修饰,请看下面的例子:
public class FinalTest1 {
int i=20;
final int j=50;
public static void main(String[] args) {
FinalTest1 ft = new FinalTest1();
//final类FinalTest的属性值 i是可以改变的,因为属性值i前面没final修饰
ft.i=100;
//报错,因为j属性是final的不可以改变。
//ft.j=200;
System.out.println(ft.i);
}
}
运行上面的代码试试看,结果是100,而不是初始化时的10。
4.3.2 finally
finally只能用在try/catch语句中并且附带着一个语句块,表示这段语句最终总是被执行。请看下面的代码:
public class FinallyTest {
public static void main(String[] args){
try{
throw new NullPointerException();
}catch(NullPointerException e){
System.out.println("程序抛出了异常");
}finally{
//这里总会被执行,不受break,return影响另如数据库连接的close()一般写在这里,可以降低程序的出错几率
System.out.println("执行了finally语句块");
}
}
}
运行结果:
程序抛出了异常
执行了finally语句块
运行结果说明了finally的作用:
-
程序抛出了异常
-
执行了finally语句块请大家注意,捕获程序抛出的异常之后,既不加处理,也不继续向上抛出异常,并不是良好的编程习惯,它掩盖了程序执行中发生的错误,这里只是方便演示,请不要学习。
return、continue、break也不可以打乱代码顺序执行语句的规律。
public class FinallyTest3 {
//测试return语句
//结果显示:编译器在编译return new ReturnClass();时,
//将它分成了两个步骤,new ReturnClass()和return,前一个创建对象的语句是在finally语句块之前被执行的,
//而后一个return语句是在finally语句块之后执行的,也就是说finally语句块是在程序退出方法之前被执行的
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){
System.out.println("continue");
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) {
FinallyTest3 ft = new FinallyTest3();
// 测试return语句
ft.testReturn();
System.out.println();
// 测试continue语句
ft.testContinue();
System.out.println();
// 测试break语句
ft.testBreak();
}
}
public class ReturnClass {
public ReturnClass() {
System.out.println("执行了return语句");
}
}
上面这段代码的运行结果如下:
执行了return语句
执行了finally语句
0
执行了finally语句
1
continue
执行了finally语句
2
执行了finally语句
0
执行了finally语句
1
执行了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之前被执行的)
4.3.3 finalize
-
finalize是一个方法,属于java.lang.Object类,它的定义如下:
protected void finalize()throws Throwable{}
。 -
众所周知,finalize()方法是GC(garbagecollector运行机制的一部分,在此我们只说说finalize()方法的作用是什么呢?
-
finalize()方法是在GC清理它所从属的对象时被调用的,如果执行它的过程中抛出了无法捕获的异常(uncaughtexception,GC将终止对改对象的清理,并且该异常会被忽略;直到下一次GC开始清理这个对象时,它的finalize()会被再次调用。请看下面的示例:
public class FinalizeTest {
/**
* 重写finalize()方法
*/
@Override
protected void finalize() throws Throwable{
System.out.println("执行了finalize()方法");
}
public static void main(String[] args){
FinalizeTest ft = new FinalizeTest();
ft = null;
System.gc();
}
}
运行结果:
执行了finalize()方法
程序调用了java.lang.System类的gc()方法,引起GC的执行,GC在清理ft对象时调用了它的finalize()方法,因此才了上面的输出结果。
调用System.gc()等同于调用下面这行代码:Runtime.getRuntime().gc();调用它们的作用只是建议垃圾收集器(GC启动,清理无用的对象释放内存空间,但是GC的启动并不是一定的,这由JAVA虚拟机来决定。直到 JAVA虚拟机停止运行,一些对象的finalize()可能都没被运行过。
由于finalize()属于Object类,因此所有类都这个方法,Object的任意子类都可以重写(override)该方法,在其中释放系统资源或者做其它的清理工作,如关闭输入输出流。
参考:https://www.cnblogs.com/ktao/p/8586966.html
https://www.cnblogs.com/smart-hwt/p/8257330.html