Java关键字之final、finally、finalize

本文详细讲解了Java中的final关键字,包括其修饰属性、方法和类的含义及用例;接着讨论了finally块的用途,如资源释放,并通过示例分析了finally与return的执行顺序;最后,介绍了finalize方法在垃圾回收中的角色及其注意事项,提醒开发者避免依赖此方法进行资源清理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、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并没起到作用

Java只有传值,没有传址!!!
值传递引用传递参考这里

【例五】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不建议的用法!!!

  1. 在finally中抛出另外一个异常
    因为java的运行上下文只能放一个异常,finally块中的异常替换了try块中的异常。这也是Java的一个bug,其他编程语言,比如C++,把这种异常替换场景视为编程错误,阻止编译通过(以上代码是用java 11测试的)
  2. 在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的过程》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值