带着BAT大厂的面试问题去理解final
1、所有的final修饰的字段都是编译期常量吗?
2、如何理解private所修饰的方法是隐式的final?
3、说说final类型的类如何拓展? 比如String是final类型,我们想写个MyString复用所有String中方法,同时增加一个新的toMyString()的方法,应该如何做?
f4、inal方法可以被重载吗? 可以
5、父类的final方法能不能够被子类重写? 不可以
6、说说final域重排序规则?
7、说说final的原理?
8、使用 final 的限制条件和局限性?
一 、final基础使用
1、修饰类
当某个类的整体定义为final时,就表明了该类不能被继承。final类中的所有方法都隐式为final,所以在final类中给任何方法添加final关键字是没有任何意义的。
设计模式中最重要的两种关系,一种是继承/实现;另外一种是组合关系。所以当遇到不能用继承的(final修饰的类),应该考虑用组合。
2、修饰方法
1)private 方法是隐式的final
2)final方法是可以被重载的
private final
类中所有private方法都隐式地指定为final的,由于无法取用private方法,所以也就不能覆盖它。。
3、修饰参数
Java允许在参数列表中以声明的方式将参数指明为final,这意味这你无法在方法中更改参数引用所指向的对象。这个特性主要用来向匿名内部类传递数据。
4、修饰变量
常规的用法比较简单,这里通过下面三个问题进一步说明。
1)所有的final修饰的字段在被初始化后才是编译期常量
2)static final所修饰的字段仅占据内存的一个一份空间,一旦被初始化之后便不会被更改
3)blank final。Java允许生成空白final,也就是说被声明为final但又没有给出定值的字段,但是必须在该字段被使用之前被赋值,增强了final的灵活性。这给予我们两种选择:
1>>在定义处进行赋值(这不叫空白final)
2>>在构造器中进行赋值,保证了该值在被使用前赋值。
二、final域重排序规则
java内存模型为了能让处理器和编译器底层发挥他们的最大优势,对底层的约束就很少,也就是说针对底层来说java内存模型就是一弱内存数据模型。同时,处理器和编译为了性能优化会对指令序列有编译器和处理器重排序。
1、final域为基本类型
1)写final域重排序规则写final域的重排序规则禁止对final域的写重排序到构造函数之外,这个规则的实现主要包含了两个方面:
1、JMM禁止编译器把final域的写重排序到构造函数之外;
2、编译器会在final域写之后,构造函数return之前,插入一个storestore屏障(关于内存屏障可以看这篇文章)。这个屏障可以禁止处理器把final域的写重排序到构造函数之外。
2)读final域重排序规则
在一个线程中,初次读对象引用和初次读该对象包含的final域,JMM会禁止这两个操作的重排序。(注意,这个规则仅仅是针对处理器),处理器会在读final域操作的前面插入一个LoadLoad屏障。
实际上,读对象的引用和读该对象的final域存在间接依赖性,一般处理器不会重排序这两个操作。但是有一些处理器会重排序,因此,这条禁止重排序规则就是针对这些处理器而设定的。read()方法主要包含了三个操作:
1、初次读引用变量finalDemo;
2、初次读引用变量finalDemo的普通域a;
3、初次读引用变量finalDemo的final与b;
读final域的重排序规则可以确保:在读一个对象的final域之前,一定会先读这个包含这个final域的对象的引用。
2、final域为引用类型
1)对final修饰的对象的成员域写操作
针对引用数据类型,final域写针对编译器和处理器重排序增加了这样的约束:在构造函数内对一个final修饰的对象的成员域的写入,与随后在构造函数之外把这个被构造的对象的引用赋给一个引用变量,这两个操作是不能被重排序的。注意这里的是“增加”也就说前面对final基本数据类型的重排序规则在这里还是使用。
2)对final修饰的对象的成员域读操作
JMM可以确保线程C至少能看到写线程A对final引用的对象的成员域的写入,即能看下arrays[0] = 1,而写线程B对数组元素的写入可能看到可能看不到。JMM不保证线程B的写入对线程C可见,线程B和线程C之间存在数据竞争,此时的结果是不可预知的。如果可见的,可使用锁或者volatile。
3、关于final重排序的总结
按照final修饰的数据类型分类:基本数据类型:
1、final域写:禁止final域写与构造方法重排序,即禁止final域写重排序到构造方法之外,从而保证该对象对所有线程可见时,该对象的final域全部已经初始化过。
2、final域读:禁止初次读对象的引用与读该对象包含的final域的重排序。
引用数据类型:
1、额外增加约束:禁止在构造函数对一个final修饰的对象的成员域的写入与随后将这个被构造的对象的引用赋值给引用变量 重排序
三、final再深入理解
1)为什么final引用不能从构造函数中“溢出”
在构造函数,不能让这个被构造的对象被其他线程可见,也就是说该对象引用不能在构造函数中“逸出”。尽管依然满足了final域写重排序规则:在引用对象对所有线程可见时,其final域已经完全初始化成功。但是,引用对象“this”逸出,该代码依然存在线程安全的问题。
2)使用 final 的限制条件和局限性
当声明一个 final 成员时,必须在构造函数退出前设置它的值。