胖熊NET,有趣实用的生活常识!

最新更新文章排行手机版

胖熊NET!

热门搜索:
当前位置: > 情感

finally是什么意思

时间:2024-03-08 07:48:01人气:99作者:用户投稿

我把自己以往的文章汇总成为了Github,欢迎各位大佬starhttps://github.com/crisxuan/bestJavaer已提交此篇文章

finally是什么意思

final是Java中的关键字,它也是Java中很重要的一个关键字,final修饰的类、方法、变量有不同的含义;finally也是一个关键字,不过我们可以使用finally和其他关键字结合做一些组合操作;finalize是一个不让人待见的方法,它是对象祖宗Object中的一个方法,finalize机制现在已经不推荐使用了。本篇文章,cxuan就带你从这三个关键字入手,带你从用法、应用、原理的角度带你深入浅出理解这三个关键字。

final、finally和finalize

我相信在座的各位都是资深程序员,final这种基础关键字就不用多说了。不过,还是要照顾一下小白读者,毕竟我们都是从小白走过来的嘛。

final修饰类、属性和方法

final可以用来修饰类,final修饰的类不允许其他类继承,也就是说,final修饰的类是独一无二的。如下所示

finally是什么意思

我们首先定义了一个FinalUsage类,它使用final修饰,同时我们又定义了一个FinalUsageExtend类,它想要继承(extend)FinalUsage,我们如上继承后,编译器不让我们这么玩儿,它提示我们不能从FinalUsage类继承,为什么呢?不用管,这是Java的约定,有一些为什么没有必要,遵守就行。

final可以用来修饰方法,final修饰的方法不允许被重写,我们先演示一下不用final关键字修饰的情况

finally是什么意思

如上图所示,我们使用FinalUsageExtend类继承了FinalUsage类,并提供了writeArticle方法的重写。这样编译是没有问题的,重写的关键点是@Override注解和方法修饰符、名称、返回值的一致性。

注意:很多程序员在重写方法的时候都会忽略@Override,这样其实无疑增加了代码阅读的难度,不建议这样。

当我们使用final修饰方法后,这个方法则不能被重写,如下所示

finally是什么意思

当我们把writeArticle方法声明为void后,重写的方法会报错,无法重写writeArticle方法。

final可以修饰变量,final修饰的变量一经定义后就不能被修改,如下所示

finally是什么意思

编译器提示的错误正是不能继承一个被final修饰的类。

我们上面使用的是字符串String,String默认就是final的,其实用不用final修饰意义不大,因为字符串本来就不能被改写,这并不能说明问题。

我们改写一下,使用基本数据类型来演示

finally是什么意思

同样的可以看到,编译器仍然给出了age不能被改写的提示,由此可以证明,final修饰的变量不能被重写。

在Java中不仅仅只有基本数据类型,还有引用数据类型,那么引用类型被final修饰后会如何呢?我们看一下下面的代码

首先构造一个Person类

publicclassPerson{\nintid;\nStringname;\nget()andset()...\ntoString()...\n}\n

然后我们定义一个final的Person变量。

staticfinalPersonperson=newPerson(25,"cxuan");\n\npublicstaticvoidmain(String[]args){\n\nSystem.out.println(person);\nperson.setId(26);\nperson.setName("cxuan001");\nSystem.out.println(person);\n}\n

输出一下,你会发现一个奇怪的现象,为什么我们明明改了person中的id和name,编译器却没有报错呢?

这是因为,final修饰的引用类型,只是保证对象的引用不会改变。对象内部的数据可以改变。这就涉及到对象在内存中的分配问题,我们后面再说。

finally保证程序一定被执行

finally是保证程序一定执行的机制,同样的它也是Java中的一个关键字,一般来讲,finally一般不会单独使用,它一般和try块一起使用,例如下面是一段try...finally代码块

try{\nlock.lock();\n}finally{\nlock.unlock();\n}\n

这是一段加锁/解锁的代码示例,在lock加锁后,在finally中执行解锁操作,因为finally能够保证代码一定被执行,所以一般都会把一些比较重要的代码放在finally中,例如解锁操作、流关闭操作、连接释放操作等。

当lock.lock()产生异常时还可以和try...catch...finally一起使用

try{\nlock.lock();\n}catch(Exceptione){\ne.printStackTrace();\n}finally{\nlock.unlock();\n}\n

try...finally这种写法适用于JDK1.7之前,在JDK1.7中引入了一种新的关闭流的操作,那就是try...with...resources,Java引入了try-with-resources声明,将try-catch-finally简化为try-catch,这其实是一种语法糖,并不是多了一种语法。try...with...resources在编译时还是会进行转化为try-catch-finally语句。

语法糖(Syntacticsugar),也译为糖衣语法,是指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

在Java中,有一些为了简化程序员使用的语法糖,后面有机会我们再谈。

finalize的作用

finalize是祖宗类Object类的一个方法,它的设计目的是保证对象在垃圾收集前完成特定资源的回收。finalize现在已经不再推荐使用,在JDK1.9中已经明确的被标记为deprecated。

深入理解final、finally和finalizefinal设计

许多编程语言都会有某种方法来告知编译器,某一块数据是恒定不变的。有时候恒定不变的数据很有用,比如

一个永不改变的编译期常量。例如staticfinalintnum=1024一个运行时被初始化的值,而且你不希望改变它

final的设计会和abstract的设计产生冲突,因为abstract关键字主要修饰抽象类,而抽象类需要被具体的类所实现。final表示禁止继承,也就不会存在被实现的问题。因为只有继承后,子类才可以实现父类的方法。

类中的所有private都隐式的指定为final的,在private修饰的代码中使用final并没有额外的意义。

空白final

Java是允许空白final的,空白final指的是声明为final,但是却没有对其赋值使其初始化。但是无论如何,编译器都需要初始化final,所以这个初始化的任务就交给了构造器来完成,空白final给final提供了更大的灵活性。如下代码

publicclassFinalTest{\n\nfinalIntegerfinalNum;\n\npublicFinalTest(){\nfinalNum=11;\n}\n\npublicFinalTest(intnum){\nfinalNum=num;\n}\n\npublicstaticvoidmain(String[]args){\nnewFinalTest();\nnewFinalTest(25);\n}\n}\n

在不同的构造器中对不同的final进行初始化,使finalNum的使用更加灵活。

使用final的方法主要有两个:不可变和效率

不可变:不可变说的是把方法锁定(注意不是加锁),重在防止其他方法重写。效率:这个主要是针对Java早期版本来说的,在Java早期实现中,如果将方法声明为final的,就是同意编译器将对此方法的调用改为内嵌调用,但是却没有带来显著的性能优化。这种调用就比较鸡肋,在Java5/6中,hotspot虚拟机会自动探测到内嵌调用,并把它们优化掉,所以使用final修饰的方法就主要有一个:不可变。

注意:final不是Immutable的,Immutable才是真正的不可变。

final不是真正的Immutable,因为final关键字引用的对象是可以改变的。如果我们真的希望对象不可变,通常需要相应的类支持不可变行为,比如下面这段代码

finalList<String>fList=newArrayList();\nfList.add("Hello");\nfList.add("World");\nListunmodfiableList=List.of("hello","world");\nunmodfiableList.add("again");\n

List.of方法创建的就是不可变的List。不可变Immutable在很多情况下是很好的选择,一般来说,实现Immutable需要注意如下几点

将类声明为final,防止其他类进行扩展。将类内部的成员变量(包括实例变量和类变量)声明为private或final的,不要提供可以修改成员变量的方法,也就是setter方法。在构造对象时,通常使用deep-clone,这样有助于防止在直接对对象赋值时,其他人对输入对象的修改。坚持copy-on-write原则,创建私有的拷贝。final能提高性能吗?

final能否提高性能一直是业界争论的点,很多书籍中都介绍了可以在特定场景提高性能,例如final可能用于帮助JVM将方法进行内联,可以改造编译器进行编译的能力等等,但这些结论很多都是基于假设作出的。

或许R大这篇回答会给我们一些结论https://www.zhihu.com/question/21762917

大致说的就是无论局部变量声明时带不带final关键字修饰,对其访问的效率都一样

比如下面这段代码(不带final的版本)

staticintfoo(){\ninta=someValueA();\nintb=someValueB();\nreturna+b;//这里访问局部变量\n}\n

带final的版本

staticintfoo(){\nfinalinta=someValueA();\nfinalintb=someValueB();\nreturna+b;//这里访问局部变量\n}\n

使用javac编译后得出来的结果一摸一样。

invokestaticsomeValueA:()I\nistore_0//设置a的值\ninvokestaticsomeValueB:()I\nistore_1//设置b的值\niload_0//读取a的值\niload_1//读取b的值\niadd\nireturn\n

因为上面是使用引用类型,所以字节码相同。

如果是常量类型,我们看一下

//带final\nstaticintfoo(){\n\nfinalinta=11;\nfinalintb=12;\n\nreturna+b;\n\n}\n\n//不带final\nstaticintfoo(){\n\ninta=11;\nintb=12;\n\nreturna+b;\n\n}\n

我们分别编译一下两个foo方法,会发现如下字节码

finally是什么意思

左边是非final关键字修饰的代码,右边是有final关键字修饰的代码,对比这两个字节码,可以得出如下结论。

不管有没有final修饰,inta=11或者inta=12都当作常量看待。在return返回处,不加final的a+b会当作变量来处理;加final修饰的a+b会直接当作常量处理。

其实这种层面上的差异只对比较简易的JVM影响较大,因为这样的VM对解释器的依赖较大,原本Class文件里的字节码是怎样的它就怎么执行;对高性能的JVM(例如HotSpot、J9等)则没啥影响。

所以,大部分final对性能优化的影响,可以直接忽略,我们使用final更多的考量在于其不可变性。

深入理解finally

我们上面大致聊到了finally的使用,其作用就是保证在try块中的代码执行完成之后,必然会执行finally中的语句。不管try块中是否抛出异常。

那么下面我们就来深入认识一下finally,以及finally的字节码是什么,以及finally究竟何时执行的本质。

首先我们知道finally块只会在try块执行的情况下才执行,finally不会单独存在

这个不用再过多解释,这是大家都知道的一条规则。finally必须和try块或者trycatch块一起使用。

其次,finally在离开try块执行完成后或者try块未执行完成但是接下来是控制转移语句时(return/continue/break)在控制转移语句之前执行

这一条其实是说明finally的执行时机的,我们以return为例来看一下是不是这么回事。

如下这段代码

staticintmayThrowException(){\ntry{\nreturn1;\n}finally{\nSystem.out.println("finally");\n}\n}\n\npublicstaticvoidmain(String[]args){\nSystem.out.println(FinallyTest.mayThrowException());\n}\n

从执行结果可以证明是finally要先于return执行的。

当finally有返回值时,会直接返回。不会再去返回try或者catch中的返回值。

staticintmayThrowException(){\ntry{\nreturn1;\n}finally{\nreturn2;\n}\n}\n\npublicstaticvoidmain(String[]args){\nSystem.out.println(FinallyTest.mayThrowException());\n}\n在执行finally语句之前,控制转移语句会将返回值存在本地变量中

看下面这段代码

staticintmayThrowException(){\ninti=100;\ntry{\nreturni;\n}finally{\n++i;\n}\n}\n\npublicstaticvoidmain(String[]args){\nSystem.out.println(FinallyTest.mayThrowException());\n}\n

上面这段代码能够说明returni是先于++i执行的,而且returni会把i的值暂存,和finally一起返回。

finally的本质

下面我们来看一段代码

publicstaticvoidmain(String[]args){\n\ninta1=0;\ntry{\na1=1;\n}catch(Exceptione){\na1=2;\n}finally{\na1=3;\n}\n\nSystem.out.println(a1);\n}\n

这段代码输出的结果是什么呢?答案是3,为啥呢?

抱着疑问,我们先来看一下这段代码的字节码

finally是什么意思

字节码的中文注释我已经给你标出来了,这里需要注意一下下面的Exceptiontable,Exceptiontable是异常表,异常表中每一个条目代表一个异常发生器,异常发生器由From指针,To指针,Target指针和应该捕获的异常类型构成。

所以上面这段代码的执行路径有三种

如果try语句块中出现了属于exception及其子类的异常,则跳转到catch处理如果try语句块中出现了不属于exception及其子类的异常,则跳转到finally处理如果catch语句块中新出现了异常,则跳转到finally处理

聊到这里,我们还没说finally的本质到底是什么,仔细观察一下上面的字节码,你会发现其实finally会把a1=3的字节码iconst_3和istore_1放在try块和catch块的后面,所以上面这段代码就形同于

publicstaticvoidmain(String[]args){\n\ninta1=0;\ntry{\na1=1;\n\t\t//finallya1=3\n}catch(Exceptione){\na1=2;\n//finallya1=3\n}finally{\na1=3;\n}\nSystem.out.println(a1);\n}\n

上面中的Exceptiontable是只有Throwable的子类exception和error才会执行异常走查的异常表,正常情况下没有try块是没有异常表的,下面来验证一下

publicstaticvoidmain(String[]args){\ninta1=1;\nSystem.out.println(a1);\n}\n

比如上面我们使用了一段非常简单的程序来验证,编译后我们来看一下它的字节码

finally是什么意思

可以看到,果然没有异常表的存在。

finally一定会执行吗

上面我们讨论的都是finally一定会执行的情况,那么finally一定会被执行吗?恐怕不是。

除了机房断电、机房爆炸、机房进水、机房被雷劈、强制关机、拔电源之外,还有几种情况能够使finally不会执行。

调用System.exit方法调用Runtime.getRuntime().halt(exitStatus)方法JVM宕机(搞笑脸)如果JVM在try或catch块中达到了无限循环(或其他不间断,不终止的语句)操作系统是否强行终止了JVM进程;例如,在UNIX上执行kill-9pid如果主机系统死机;例如电源故障,硬件错误,操作系统死机等不会执行如果finally块由守护程序线程执行,那么所有非守护线程在finally调用之前退出。finalize真的没用吗

我们上面简单介绍了一下finalize方法,并说明了它是一种不好的实践。那么finalize调用的时机是什么?为什么说finalize没用呢?

我们知道,Java与C++一个显著的区别在于Java能够自动管理内存,在Java中,由于GC的自动回收机制,因而并不能保证finalize方法会被及时地执行(垃圾对象的回收时机具有不确定性),也不能保证它们会被执行。

也就是说,finalize的执行时期不确定,我们并不能依赖于finalize方法帮我们进行垃圾回收,可能出现的情况是在我们耗尽资源之前,gc却仍未触发,所以推荐使用资源用完即显示释放的方式,比如close方法。除此之外,finalize方法也会生吞异常。

finalize的工作方式是这样的:一旦垃圾回收器准备好释放对象占用的存储空间,将会首先调用finalize方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。垃圾回收只与内存有关

我们在日常开发中并不提倡使用finalize方法,能用finalize方法的地方,使用try...finally会处理的更好。

你好,我是cxuan,一枚技术人。我一共写了六本PDF

《Java核心技术总结》《HTTP核心总结》《程序员必知的基础知识》《操作系统核心总结》《Java核心基础2.0》《Java面试题总结》

现在我把百度链接给大家放出来了,大家可以点击下方的链接领取

链接:https://pan.baidu.com/s/1mYAeS9hIhdMFh2rF3FDk0A密码:p9rs

finally是什么意思

标签:
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至123@。cc举报,一经查实,本站将立刻删除。