JAVA--------对final,finally,finalize的理解

final ,finally,finalize他们没有相同点,只是单词长得像。

final

final 可以用来修饰类,方法,变量,分别有不同的意义,

  • final 修饰的 class 代表不可以继承扩展,
  • final 的变量是不可以修改的,
  • final 的方法也是不可以重写的(override).

在Java中final被称为终结器,可以使用final来定义类、方法、属性。

  • 使用final定义的类不能有子类(String类便是使用final定义)
  • final一旦修饰一个类之后,该类的所有方法默认都会加上final修饰。(不包含成员变量)
  • 使用final定义的变量就成为了常量,常量必须在声明时赋值,并且不能够被修改

作用:

1.保护性:我们可以将方法或者类声明为 final,这样就可以明确告知别人,这些行为是不许修改的。在Java 核心类库的定义或源码, 会发现 java.lang 包下面的很多类,相当一部分都被声明成为 final class。在第三方类库的一些基础类中同样如此,这可以有效避免 API 使用者更改基础功能,某种程度上,这是保证平台安全的必要手段。

2.使用 final 修饰参数或者变量,也可以清楚地避免意外赋值导致的编程错误,甚至,有人明确推荐将所有方法参数、本地变量成员变量声明成 final。

3.final 变量产生了某种程度的不可变(immutable)的效果,所以,可以用于保护只读数据,尤其是在并发编程中,因为明确地不能再赋值 final 变量,有利于减少额外的同步开销,也可以省去一些防御性拷贝的必要。

 

使用final修饰一个变量时,是指引用变量不能改变,但是引用变量所指的对象中内容可以改变
 

注意:final 不是 immutable(不可更变的类)

如果要实现 immutable 的类,我们需要做到:

  • 将 class 自身声明为 final,这样别人就不能扩展来绕过限制了。
  • 将所有成员变量定义为 private 和 final,并且不要实现 setter 方法。
  • 通常构造对象时,成员变量使用深度拷贝来初始化,而不是直接赋值,这是一种防御措施,因为你无法确定输入对象不被其他人修改。
  • 如果确实需要实现 getter 方法,或者其他可能会返回内部状态的方法,使用 copy-on-write原则,创建私有的 copy

 

关于final的重要知识点: 

1、final关键字可以用于成员变量、本地变量、方法以及类。
2、 final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。
3、 final成员变量表示常量,只能被赋值一次,赋值后值不再改变,即不能对final变量再次赋值,。
4、 final方法不能被子类的方法覆盖,但可以被继承。
5、 final类不能被继承,没有子类,final类中的方法默认是final的。
6、 没有在声明时初始化final变量的称为空白final变量(blank final variable),它们必须在构造器中初始化,或者调用
this()初始化。不这么做的话,编译器会报错“final变量(变量名)需要进行初始化”

说明:

父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的。

1、final类: 不能被继承,因此final类的成员方法没有机会被覆盖,默认都是final的。在设计类时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会载被扩展,那么就设计为final类。

2、final方法: 如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明为final方法。使用final方法的原因有二:①把方法锁定,防止任何继承类修改它的意义和实现。 ②高效,编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。

3、final变量(常量): 用final修饰的成员变量表示常量,值一旦给定就无法改变;final修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量。一旦给final变量初值后,值就不能再改变了。final变量定义的时候,可以先声明,而不给初值,这中变量也称为final空白,无论什么情况,编译器都确保空白final在使用之前必须被初始化。但是,final空白在final关键字final的使用上提供了更大的灵活性,为此,一个类中的final数据成员就可以实现依对象而有所不同,却有保持其恒定不变的特征。

4、final参数:当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。

 

面试题:string类为什么用final修饰?

主要是为了”安全性“和”效率“的缘故,因为:

1、由于String类不能被继承,所以就不会没修改,这就避免了因为继承引起的安全隐患;

2、String类在程序中出现的频率比较高,如果为了避免安全隐患,在它每次出现时都用final来修饰,这无疑会降低程序的执行效率,所以干脆直接将其设为final一提高效率;

说明:

       1.为了实现字符串池

        2.为了线程安全

        3.为了实现String可以创建HashCode不可变性

  • 字符串存储的位置和其他类型的常量不一样,字符串专门有个字符串池,也只有用final修饰才能实现字符串池。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String interning将不能实现(String interning是指对不同的字符串仅仅只保存一个,即不会保存多个相同的字符串。),因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。
  • 如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
  • 因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。
  • 类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。
  • 因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串
     

finally

finally 则是 Java 保证重点代码一定要被执行的一种机制.我们可以使用 try-finally 或者 trycatch-finally 来进行类似关闭 JDBC 连接,保证 unlock 锁等动作.

无论是否产生异常,无论try ,catch是否有返回语句,最终都会执行finaly块,比如Lock锁的释放,JDBC里数据库的关闭

finally执行的异常情况:

1. try-cach 异常退

try{
system.exit(1)
}finally{
print(abc)
}

2.死循环

try{
  while(ture){ 
     print(abc)
  }
}finally{
   print(abc)
}

3.线程被杀死

当执行 try,finally 的线程被杀死时。finally 也无法执行。

4.在执行finally之前jvm崩溃了。

5.断电

总结
1,不要在 finally 中使用 return 语句。
2,finally 总是执行,除非程序或者线程被中断。

try catch finally的执行流程

1.不管有没有出现异常,finally块中代码都会执行;

2.当try和catch中有return时,finally仍然会执行;当finally里面没有return语句是,执行try 和finally语句之后最后再执行return。 

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

4.finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。

https://blog.csdn.net/u013309870/article/details/73498860里面讲解了try catch finally的执行流程

 

finalize

finalize 是基础类 java.lang.Object 的一个方法,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收.finalize 机制现在已经不推荐使用,并且在 JDK 9 开始被标记为deprecate(过期的)。因为,你无法保证 finalize 什么时候执行,执行的是否符合预期。使用不当会影响性能,导致程序死锁、挂起等

finalize是对象的自我拯救的方法,它是在可达性分析算法不可达之后才会覆写或者调用的方法。

finalize 的缺点:

finalize 的执行是和垃圾回收关联在一起的,一旦实现了非空的 finalize 方法,就会导致相应对象回收呈现数量级上的变慢,有人专门做过 benchmark,大概是 40~50 倍的下降。

有以下两个原因:

1.finalize 被设计成在对象被垃圾收集前调用,这就意味着实现了 finalize 方法的对象是个“特殊公民”,JVM 要对它进行额外处理。finalize 本质上成为了快速回收的阻碍者,可能导致你的对象经过多个垃圾收集周期才能被回收。因为
finalize 拖慢垃圾收集,导致大量对象堆积,也是一种典型的导致 OOM 的原因。

2.从另一个角度,我们要确保回收资源就是因为资源都是有限的,垃圾收集时间的不可预测,可能会极大加剧资源占用。这意味着对于消耗非常高频的资源,千万不要指望 finalize 去承担资源释放的主要职责,最多让 finalize 作为最后的“守门员”,况且它已经暴露了如此多的问题。这也是为什么我推荐,资源用完即显式释放,或者利用资源池来尽量重用。

在垃圾回收算法中finalize的作用:可达性分析算法

Java采用可达性分析算法来判断对象是否存活)(C#,Lisp)

核心思想:通过一系列“GC Roots”的对象作为起点,从这些起点开始向下搜索对象,搜索走过的路径,称为引用链“,当一个对象到任意一个GC  Roots 对象没有任何引用链想链接时,(从GC Roots到对象不可达时),证明对象已死。

Java中能作为GC Roots 的对象包含以下四种:

1. 虚拟机栈(栈帧中的本地变量表)中引用的对象

2. 方法区中类静态属性引用的对象

3. 方法区中常量引用的对象

4. 本地方法栈中JNI(Native方法)引用的

***********JDK1.2之后对于引用的概念做了扩充***********

将引用分为强引用(Strong Reference),软引用(Soft Reference),弱引用(Weak Reference),虚引用(Phantom Reference)四种,这四种强度依次递减。后边会讲。

 

对象自我拯救——finalize

protected void finalize() throws Throwable{}

在可达性分析算法中不可达的对象,也并非“非死不可”,所有不可达的对象处于“缓刑”阶段。要宣告一个对象彻底死亡,需要经历两次标记过程:

若对象在进行可达性分析之后发现GC Roots不可达,此对象会进行第一次标记并且进行一次筛选过程。筛选条件是此对象是否有必要执行finalize(),当对象没有覆写finalize()方法或finalize()方法已被JVM调用过,JVM会将此对象彻底宣判死亡。筛选成功(对象覆写了finalize()方法并且未被执行),会将此对象放入F-Queue,如果对象在finalize()成功自救(此对象与GC Roots建立联系),则对象会在第二次被标记时被移除回收集合,成功存活,若对象在fianlize()中仍与GC Roots不可达,宣告死亡。

 

注意:finalize 还会掩盖资源回收时的出错信息,会被Throwable 是被生吞异常!也就意味着一旦出现异常或者出错,你得不到任何有效信息。况且,Java 在 finalize 阶段也没有好的方式处理任何信息,不然更加不可预测

 private void runFinalizer(JavaLangAccess jla) {
     // ... 省略部分代码
     try {
         Object finalizee = this.get();
         if (finalizee != null && !(finalizeeinstanceofjava.lang.Enumjla.invokeFinalize(finalizee);
         // Clear stack slot containing this variable, to decrease
         // the chances of false retention with a conservative GC 
         finalizee = null;
         }
        } catch (Throwable x) { }
        super.clear();
}

 

有什么机制可以替换 finalize 吗?
1.Java 平台目前在逐步使用 java.lang.ref.Cleaner 来替换掉原有的 finalize 实现。Cleaner 的实现利用了幻象引用PhantomReference),这是一种常见的所谓 post-mortem 清理机制。我会在介绍 Java 的各种引用,利用幻象引用和引用队列,我们可以保证对象被彻底销毁前做一些类似资源回收的工作,比如关闭文件描述符(操作系统有限的资源),它比finalize 更加轻量、更加可靠。吸取了 finalize 里的教训,每个 Cleaner 的操作都是独立的,它有自己的运行线程,所以可以避免意外死锁等问题

2.Cleaner 或者幻象引用改善的程度仍然是有限的,如果由于种种原因导致幻象引用堆积,同样会出现问题。所以,Cleaner 适合作为一种最后的保证手段,

3.不是完全依赖 Cleaner 进行资源回收,不然我们就要再做一遍 finalize 的噩梦了。我也注意到很多第三方库自己直接利用幻象引用定制资源收集,比如广泛使用的 MySQL JDBCdriver 之一的 mysql-connector-j,就利用了幻象引用机制。幻象引用也可以进行类似链条式依赖关系的动作,比如,进行总量控制的场景,保证只有连接被关闭,相应资源被回收,连接才能创建新的连接。

4.另外,这种代码如果稍有不慎添加了对资源的强引用关系,就会导致循环引用关系,前面提到的
MySQL JDBC 就在特定模式下有这种问题,导致内存泄漏。上面的示例代码中,将 State 定义
为 static,就是为了避免普通的内部类隐含着对外部对象的强引用,因为那样会使外部对象无法
进入幻象可达的状态

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值