(五)final、finally、finalize区别
一、final声明属性,表示属性不可变:它只能指向初始时指向的那个对象,而不关心指向对象内容的变化
//final 变量 对象可改变
final StringBuffer sb=new StringBuffer("abc");
sb.append("d");
System.out.println(sb.toString());
//其引用不可变
sb=new StringBuffer("hh");
/*
The final local variable sb cannot be assigned.
It must be blank and not using a compound assignment
*/
public class FinalAttributeC {
private final Person person = new Person("zhangbingxiao");
public void change(){
person.setName("chengfan");
System.out.println(person.getName());
}
//public void change(Person p){
//this.person = p;
//}
public static void main(String[] args) {
new FinalAttributeC().change();
}
}
//结果 : chengfan
注释掉的代码是会报错的代码,也就是说引用类型person是不可变的,其对象内部的属性是可以改变的。
引用存于栈中,而对象存放于堆中。引用的值是对象在堆中的地址。在本质上,final修饰的是引用,而不是对象。所以引用的哪个地址不可改变,而和对象没多大关系。
被final修饰的变量必须初始化:
①在定义的时候初始化
②final成员变量可以在初始化块中初始化,但不可在静态初始化块中初始化
原因:由(2)知道,单个类的程序加载顺序是:静态变量–>静态代码块–>非静态变量–>非静态代码块–>构造器。
③静态final成员变量可以在静态初始化块中初始化,但不可在初始化块中初始化
原因:static要求在编译期就确定值,然后放入静态区。而非静态部分是在运行期发生的,所以有如上结论
④在类的构造器中初始化,但静态final成员变量不可以在构造函数中初始化
原因:非final变量可以在构造器中初始化,这种final属性在编译期间是无法确定属性值的,只有在运行的时候才确定,增加了灵活性
public class FinalAttributeB {
private final String attribute_b;
public FinalAttributeB(String attribute_b){
this.attribute_b = attribute_b;
}
public void test(){
//attribute_b = "zhangbingxiao";
}
public void test(String attribute_b){
//this.attribute_b = attribute_b;
}
}
二、final方法
①此方法不允许任何子类重写这个方法,但子类仍然可以使用这个方法
public class Person {}
public class Woman extends Person{
/*
* Cannot override the final method from Person
public final void print(){
System.out.println("final method");
}
*/
public static void main(String[] args) {
Woman women=new Woman();
women.print();
}
}
②还有一种称为inline(内联)的机制,当调用一个被声明为final的方法时,直接将方法主体插入到调用处,而不是进行方法调用,这样做能够提升程序的效率。
这里先讲下函数调用:
调用某个函数实际上将程序执行顺序转移到该函数所存放在内存中某个地址,将函数的程序内容执行完后,再返回到
转去执行该函数前的地方。这种转移操作要求在转去前要保护现场并记忆执行的地址,转回后先要恢复现场,并按原来保
存地址继续执行。也就是通常说的压栈和出栈。因此,函数调用要有一定的时间和空间方面的开销。那么对于那些函数体
代码不是很大,又频繁调用的函数来说,这个时间和空间的消耗会很大。
那怎么解决这个性能消耗问题呢,这个时候需要引入内联函数了。内联函数就是在程序编译时,编译器将程序中出现
的内联函数的调用表达式用内联函数的函数体来直接进行替换。显然,这样就不会产生转去转回的问题,但是由于在编译
时将函数体中的代码被替代到程序中,因此会增加目标程序代码量,进而增加空间开销,而在时间代销上不象函数调用时
那么大,可见它是以目标代码的增加为代价来换取时间的节省。
在大学里学习写C代码时,我们都学到将一些简短的逻辑定义在宏里。这样做的好处是,在编译器编译的时候会将用
到该宏的地方直接用宏的代码替换。这样就不再需要象调用方法那样的压栈、出栈,传参了。性能上提升了。
三、final参数
用来表示这个参数在这个函数内部不允许被修改
public class FinalParam {
public void test(final int a ){
//a = 10; 值不可以被修改
}
public void test(final Person p){
//p = new Person("zhangbingxiao"); 引用本身不可以被修改
p.setName("zhangbingxiao"); //引用所指向的对象可以被修改
}
}
四、final类
①当一个类被声明为final时,此类不能被继承,所有方法不能被重写(不能继承,重写就无从谈起了)。但这并不表示final类的成员变量也是不可以边的,要想做到final类的成员变量不可改变,必须给成员比那里增加final修饰。在jdk中有很多应用,比如我们熟知的String、Integer等类都被final修改。
如何写一个不可变类呢?
- 将类声明为final,所以它不能被继承
- 将所有的成员声明为私有的,这样就不允许直接访问这些成员
- 对变量不要提供setter方法
- 将所有可变的成员声明为final,这样只能对它们赋值一次
- 通过构造器初始化所有成员,进行深拷贝(deep copy)
- 在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷
②一个类不能既被声明为abstract,又被声明为final
原因:final类不可以被继承,而abstract类需要被继承
五、使用final的好处:
- 被final修饰的常量,在编译阶段会存入调用类的常量池中。
- 被final修饰的方法,JVM会尝试为之寻求内联,这对于提升Java的效率是非常重要的。因此,假如能确定方法不会被继承,那么尽量将方法定义为final的。
- final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
二、finally
作为异常处理的一部分,它只能用在try/catch中,并且附带一个语句块,表示这段语句最终一定被执行,经常用在需要释放资源的情况下
①
正常情况下,程序运行没有问题;当update语句出现异常时,后面的close()方法将不会被调用,数据库的连接将得不到释放。这样长期运行后,将会耗尽数据库的连接资源,通过使用finally可以保证任何情况下数据库的连接资源都可以被释放
这样,不管程序运行是否会出现异常,finally中的代码一定会执行,这样能保证任何情况下数据库的连接资源都可以被释放
三、finalize
是Object类的一个方法,在垃圾回收器执行时会调用被回收对象的finalize()方法,可以覆盖此方法来实现对其他资源的回收,例如关闭文件等。需要注意的是,一旦垃圾回收器准备好释放对象占用的空间,将首先调用其finalize()方法,并且在下一次垃圾回收发生时,才会真正回收对象占用的内存
参考:
https://www.jianshu.com/p/e1b2db7bafce
https://berdy.iteye.com/blog/803715