java中final和finally和finalize的区别

final、finally、finalize傻傻分不清楚,今天让你彻底弄清楚

基础概念区分

final

final在Java中是一个保留的关键字,可以声明成员变量、方法、类以及本地变量。

一旦你将引用声明作final,你将不能改变这个引用了,编译器会检查代码,如果你试图将变量再次初始化的话,编译器会报编译错误。

final修饰变量

final成员变量表示常量,只能被赋值一次,赋值后值不再改变(final要求地址值不能改变

当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化

final修饰一个成员变量(属性),必须要显示初始化。这里有两种初始化方式,一种是在变量声明的时候初始化;第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。

final修饰方法

使用final方法的原因有两个。

  • 第一个原因是把方法锁定,以防任何继承类修改它的含义,不能被重写;
  • 第二个原因是效率,final方法比非final方法要快,因为在编译的时候已经静态绑定了,不需要在运行时再动态绑定。

(注:类的private方法会隐式地被指定为final方法)

final修饰类

当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法

final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。

在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。

final域的编译器和处理器要遵守两个重排序规则

  1. 写final域重排序规则:JMM禁止编译器把final域的写重排序到构造函数之外。编译器会在final域写之后,构造函数return之前,插入一个storestore屏障。这个屏障可以禁止处理器把final域的写重排序到构造函数之外(先写入final变量,后调用该对象引用)
  2. 读final域重排序规则为:在一个线程中,初次读对象引用和初次读该对象包含final域,JMM会禁止这两个操作的重排序,处理器会在读final域操作的前面插入一个LoadLoad屏障。(先读对象的引用,后读final变量)
  3. 针对引用数据类型,final域写,针对编译器和处理器重排序增加了这样的约束:在构造函数内对一个final修饰的对象的成员域的写入,与随后在构造函数之外把这个被构造的对象的引用赋给一个引用变量,这两个操作是不能被重排序的。
  4. 对final修饰的对象的成员域读操作,jvm保证对final修饰的对象的成员域的写(构造函数中赋值),可以被其他线程对该域的读可见,但非构造函数的写,其他线程不一定可见。

finally

异常处理语句结构的一部分,表示总是执行。

try-catch-finally(可省略)

finalize

Object类的一个方法

  1. 在垃圾回收器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。
  2. 该方法更像是一个对象生命周期的临终方法,当该方法被系统调用则代表该对象即将“死亡”,但是需要注意的是,我们主动行为上去调用该方法并不会导致该对象“死亡”,这是一个被动的方法(其实就是回调方法),不需要我们调用。
  3. 在GC要回收某个对象时,这个对象:“最后一刻,我还能再抢救一下!”。因此JVM要对它进行额外处理。finalize成为了CG回收的阻碍者,导致这个对象经过多个垃圾收集周期才能被回收。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Person {
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public String toString() {
return "姓名:" + this.name + ",年龄:" + this.age;
}

public void finalize() throws Throwable {//对象释放空间是默认调用此方法
System.out.println("对象被释放-->" + this);//直接输出次对象,调用toString()方法
}

}

class SystemDemo {

public static void main(String[] args) {
// TODO Auto-generated method stub
Person per = new Person("zhangsan", 30);
per = null;//断开引用,释放空间
//方法1:
System.gc();//强制性释放空间
//方法2:
// Runtime run=Runtime.getRuntime();
// run.gc();
}
}

try、catch、finally执行顺序

try、catch、finally为异常捕获结构,其中finally为非必需项,可有可无。当finally存在时,finally块中的代码都会执行。

try-catch结构;
try-catch-finally结构;
try-finally结构也是可以的。

特殊场景:
仅在以下四种情况下不会执行finally块中语句。

  1. 如果在try或catch语句中执行了System.exit(0);
  2. 在执行finally之前jvm崩溃了
  3. try语句中执行死循环
  4. 电源断电

不管有无异常,finally中代码都会执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class FinallyTest {
// 当传入参数str 为 "null" 时抛出异常
public static void throwTest(String str) {
if ("null".equals(str)) {
throw new NullPointerException();
}
}

public static String method(String str) {
try {
System.out.println("try");
throwTest(str);
} catch (Exception e) {
System.out.println("exception: " + e);
} finally {
System.out.println("finally");
}
return str;
}

public static void main(String[] args) {
System.out.println("-----无异常-----");
method("nl");
System.out.println("-----有异常-----");
method("null");
}
}

上述代码做了正常执行和抛出异常的测试,结果如下:

1
2
3
4
5
6
7
-----无异常-----
try
finally
-----有异常-----
try
exception: java.lang.NullPointerException
finally

try中有return时,finally依然会执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static String method(String str) {
try {
System.out.println("try--");
return str;
} catch (Exception e) {
System.out.println("exception-- " + e);
} finally {
System.out.println("finally--");
}
return "end";
}

public static void main(String[] args) {
System.out.println(method("str"));
}

上述代码做了try中返回测试,结果如下:

1
2
3
try--
finally--
str

finally对返回值的做修改,不会影响到try的返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static String method(String str) {
try {
System.out.println("try--");
return str;
} catch (Exception e) {
System.out.println("exception-- " + e);
} finally {
System.out.println("finally--");
str = "finally";
}
return "end";
}

public static void main(String[] args) {
System.out.println(method("str"));
}

上述代码做了在finally中修改返回值的测试,最终返回依然为”str”,结果如下:

1
2
3
try--
finally--
str

finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的。

finally中包含return语句,程序会在finally中提前退出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static String method(String str) {
try {
System.out.println("try--");
return str;
} catch (Exception e) {
System.out.println("exception-- " + e);
} finally {
System.out.println("finally--");
str = "finally";

// 在finally中的return 语句,会造成程序提前退出
return str;
}
}

public static void main(String[] args) {
System.out.println(method("str"));
}

上述代码做了在finally中,使用return语句的测试,程序会在finally中提前退出,结果如下:

1
2
3
try--
finally--
finally

经典测试题

面题目输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
public static int demo5() {
try {
return printX();
}
finally {
System.out.println("finally trumps return... sort of");
}
}

public static int printX() {
System.out.println("X");
return 0;
}

输出结果:

1
2
3
X
finally trumps return... sort of
0

上面这道题目含金量很高,程序顺序执行时先执行printX()函数,此时得到返回值0并且将0保存到variable中对应的用于保存返回值的区域;

此时程序在执行finally语句因为finally语句中没有return语句,所以程序将返回值区域的0返回给上一级函数。