一、final——终极修饰符
- 修饰属性:被final修饰符修饰的
属性
和局部变量
都不能被重新赋值(见下例一),所以为了节省内存空间将被final修饰的属性同时被static
修饰,习惯书写顺序为static final
,如果定义为static,则强调只有一份。(见下例二)- 修饰基本数据类型,可以认为该数据是常量
- 修饰类的对象,可以认为该数据和其所指向的对象之间的绑定关系不可更改,但该数据指向的对象的属性是可更改的,即对象的引用不会改变,但对象本身可以改变
注: final是唯一一个既可修饰属性又可以修饰局部变量的修饰符
- 修饰方法:不能在子类中被覆盖,但可以被重载!! 使用final修饰方法有两个目的,一个是防止继承类对其进行更改,另外一个原因是效率,在早期的java版本中,会将final方法转为内嵌调用。但若方法过于庞大,可能在性能上不会有多大提升。因此在最近版本中,不需要final方法进行这些优化了。
- 修饰类:不能被继承
- 如果一个类被final修饰,说明这个类不可能有子类,类中的方法也一定是final方法。
【例一】
class A {
private int i;
A(int a) { i = a; }
private int x = 9;
void setI(int i) { this.i = i; }
}
public class BlankFinal {
private final int j;
private final A a;
public BlankFinal() {
j = 1;
a = new A(1);
}
public static void main(String args[]) {
final int con = 100;
con = 1000; // The final local variable can't be assigned.
BlankFinal b = new BlankFinal();
b.j = 5; // The final field BlankFinal.j can't be assigned.
b.a = new A(3); // The final field BlankFinal.a can't be assigned.
b.a.setI(10);
b.x++;
}
}
【例二】static final 和 final
class TestFinal {
private static Random ran = new Random();
private static final int a = ran.nextInt(99);
private final int b = ran.nextInt(99);
@Override
public String toString() {
return "a: " + a + "\tb: " + b;
}
public static void main (String args[]) {
TestFinal TestFinal1 = new TestFinal();
System.out.println(TestFinal1.toString());
TestFinal TestFinal2 = new TestFinal();
System.out.println(TestFinal2.toString());
}
}
运行结果
a: 89 b: 98
a: 89 b: 40
二、finally
有时程序希望无论是否发生异常都执行某些操作,一般用于资源释放,断开连接,关闭管道流等。一般搭配try -- catch --finally
或者 try --- finally
,finally子句无论如何都会执行,哪怕是有return语句,finally子句和return语句的执行顺序(见例三、例四、例五…)
注: 如果finally没有执行,有以下几种可能:
① 没有进入try
② try中发生死循环或者死锁
③ try中system.exit()
【例三】返回值是基本类型
public static void main(String[] args) {
int result1 = finallyNotWork();
System.out.println(result1); // 101
int result2 = finallyWork();
System.out.println(result2); // 1
}
public static int finallyNotWork() {
int temp = 100;
try {
return ++temp; // ①
} finally {
temp = 1; // ②
}
}
public static int finallyWork() {
int temp = 100;
try {
return ++temp; // ①
} finally {
return 1; // ②
}
}
输出结果
101
1
执行过程
1. 执行:语句①,计算该表达式,结果保存在操作数栈顶;
2. 执行:将操作数栈顶值(语句①的结果101)复制到局部变量区作为返回值;
3. 执行:finally语句块中的代码②;
4. 执行:将第2步复制到局部变量区的返回值又复制回操作数栈顶;
5. 执行:return指令,返回操作数栈顶的值;
总结
finallyNotWork() 的第三步temp=1是对原操作数栈顶中的temp赋值,而真正返回值已经复制到局部变量区,会在第四步的时候带回来
finallyWork() 的第三步return 1操作覆盖了try块中的语句①的结果
【例四】返回值是引用类型
public static Map<String,String> getMap(){
HashMap<String, String> map = new HashMap<>();
map.put("key","init");
try {
map.put("key","try");
return map; // ①
}catch(Exception e) {
map.put("key","catch");
}finally {
map.put("key","finally"); // ②
map = null; // ③
}
return map; // ④
}
public static Map<String,String> getMapReturnAtLast(){
HashMap<String, String> map = new HashMap<>();
map.put("key","init");
try {
map.put("key","try"); // ⑤
}catch(Exception e) {
map.put("key","catch");
}finally {
map.put("key","finally"); // ⑥
map = null; // ⑦
}
return map; // ⑧
}
public static void main(String[] args) {
String value1 = getMap().get("key");
System.out.println(value1);
String value2 = getMapReturnAtLast().get("key");
System.out.println(value2);
}
输出结果
finally
Exception in thread "main" java.lang.NullPointerException
at Test_finally.main(FinallyTest.java:xxx)
getMap()执行过程
1. 执行 语句① 此时将返回值结果“try”保存在操作数栈顶;
2. 执行:将操作数栈顶值(语句① 的结果)复制到局部变量区作为返回值;
3. 执行:finally语句块中的代码②,把map指向的空间值修改为finally(操作在局部变量区进行);
4. 执行:finally语句块中的代码③,把map的引用设为null(操作在操作数栈顶进行),null不会作为值传递,return有返回值所以不会返回null
5. 执行:将第2步复制到局部变量区的返回值又复制回操作数栈顶;
6. 执行:return指令,返回操作数栈顶的值;
总结
首先两个return代码 语句①和语句④只会按照顺序执行①,执行完①以后已经将返回值复制到局部变量区,暂时没有返回,并在执行语句②的时候更新了map引用所指向的空间的值,由于null不会作为值传递,所以语句③map=null并没起到作用
【例五】finally中抛出异常
class FinallyThrow {
static Logger logger;
public static int divide(int a, int b) {
try {
return a / b;
} finally {
logger.info(a + " / " + b);
}
}
public static void main(String[] args) {
try {
System.out.println(divide(1, 0));
} catch (Exception e) {
System.out.println("抛出ArithmeticException异常,除数不能为0");
e.printStackTrace();
}
}
}
执行结果
抛出ArithmeticException异常,除数不能为0
java.lang.NullPointerException
at FinallyThrow.divide(FinallyTest.java:92)
at FinallyThrow.main(FinallyTest.java:98)
正常应抛出ArithmeticException异常,但是此时在logger处抛出了空指针异常,覆盖了原来的异常
综上所述,finally不建议的用法!!!
- 在finally中抛出另外一个异常
因为java的运行上下文只能放一个异常,finally块中的异常替换了try块中的异常。这也是Java的一个bug,其他编程语言,比如C++,把这种异常替换场景视为编程错误,阻止编译通过(以上代码是用java 11测试的) - 在finally中return
finally块内的return语句会覆盖try里的return,导致运算结果的不正确
二、finalize
Java中提供了finalize()方法,在垃圾回收器在进行内存释放时会首先调用finalize,但会有一些误区。
1)对象可能不被垃圾回收。
2)垃圾回收并不等于"析构",finalize不是析构函数。
3)垃圾回收只与内存有关。
4)垃圾回收和finalize都是靠不住的,只要JVM还没有到内存耗尽的地步,它是不会浪费时间进行内存回收的。
finalize的调用前提情况:
1)所有对象被Garbage Collection自动调用,比如运行System.gc()的时候
2)程序退出时为每个对象调用finalize()方法
3)显式的调用finalize方法
并不建议使用finalize方法完成非内存资源的清理工作,但建议用于:
1)清理本地对象(通过JNI创建的对象)
2)作为确保某些非内存资源的释放(socket,文件,端口等等)
public class FinalizationDemo {
public static void main(String[] args) {
Cake c1 = new Cake(1);
Cake c2 = new Cake(2);
Cake c3 = new Cake(3);
c2 = c3 = null;
System.gc(); //Invoke the Java garbage collector
}
}
class Cake extends Object {
private int id;
public Cake(int id) {
this.id = id;
System.out.println("Cake Object " + id + " is created");
}
protected void finalize() throws java.lang.Throwable {
super.finalize();
System.out.println("Cake Object " + id + " is disposed");
}
}
运行结果
Cake Object 1 is created
Cake Object 2 is created
Cake Object 3 is created
Cake Object 3 is disposed
Cake Object 2 is disposed
关于更深层次的finalize认识,请参考《finalize方法总结、GC执行finalize的过程》