书接上回《Java面向对象之类成员浅析》,老四引出了同学提出的一个探讨问题。关于final修饰符其实涉及到的知识点还是比较多比较杂的,所以老四也是想了一些时日,目的是希望能写的更清晰一些,方便理解和复查知识点。希望能对你有一点点帮助吧,写完final的知识点之后会顺便抛出几个面试中经常被提及到的面试/笔试题供你参考,最后会详细给出关于”为什么final static”经常连用的一些理由供你参考。
首先,你经常看到的或者听到的final修饰的变量不能被赋值这种说法是错误的!严格的说法是: final修饰的变量不可被改变,一旦获得了初始值,该final变量的值就不能被重新赋值了。
final可以可用于修饰类、变量(类变量、实例变量、局部变量以及形参)和方法,表示被final修饰的类、变量和方法不可改变。
1.final修饰变量
–final修饰成员变量(类变量和实例变量)
成员变量是随类初始化或对象初始化而初始化的。如果final修饰的成员变量没有指定初始值或者没有通过静态块、构造方法指定初始值,那么final的修饰就失去了意义,所以Java的语法规定我们码农使用final修饰的成员变量必须显式的指定初始值。final修饰类变量、实例变量能指定初始值的方式如下:
- 类变量: 必须在静态初始块中指定初始值或声明该类变量时指定初始值,而且只能二选一。
- 实例变量: 必须在非静态初始化块、声明该实例或者构造器中指定初始值,三选一。
–final修饰局部变量
众所周知,局部变量本身就要求我们高科技代码编写人员显式初始化,所以对于final修饰局部变量,既可以在定义的时候指定默认值,也可以不指定,不指定的时候可以在后面指定且只能指定一次,不允许重复指定。
注意: final修饰的形参不能被赋值,形参的值由根据传入的参数进行初始化。
–final修饰基本类型和引用类型
- final修饰基本类型变量,不能对及基本类型变量重新赋值,基本类型变量的值不能被改变。
- final修饰引用类型变量,保存的仅仅是一个引用,保证这个引用类型变量所引用的地址不变,但引用的对象或者引用的值是完全可以变的。
上面也解释了文章开头所说的”final修饰的变量不能被赋值”是错误的原因。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
package comglorze.finaltest; import java.util.Arrays; /** * final修饰符修饰成员变量 * @ClassName FinalVarTest * @author: glorze.com * @since: 2018年4月18日 下午11:22:31 */ public class FinalVarTest { /** * 定义成员变量的时候就指定初始值 * 注意,glorze的值不能够在改变了 * 尽管你后面再写"glorze=18"编译器都饶不了你的 */ final int glorze = 8; /** * 下面四个变量将会在构造器或者初始块中指定初始值 */ final String str; final int a; final static double B; final char ch; /** * 初始化块 * 注意:如果final修饰的变量还没有被赋初始值,成员变量也不允许被访问。比如,还没赋值你就"System.out.println(str);" * 这样的会报The blank final field str may not have been initialized,告诉你不初始化不能碰人家。 */ { // System.out.println(str); str="高老四Java博客"; } /** * 静态初始化块 * 注意,由于静态成员不能访问实例变量,所以这里面只能赋值static修饰的变量。 */ static { B = 8.8; } /** * 构造器赋值 * 注意:普通方法中不能对final修饰的变量进行赋值。 * FinalVarTest. * */ public FinalVarTest() { a = 88; ch = 'g'; } /** * 不允许对形参赋值 * 如下所示,如果编写gao=888; * 会报The final local variable gao cannot be assigned. It must be blank and not using a compound assignment错误 * @Title: testFinalLocalVar * @param gao * @return void * @author: 高老四博客 * @since: 2018年4月18日 下午11:46:51 */ public void testFinalLocalVar(final int gao) { // gao = 888; } public static void main(String[] args) { // final修饰局部变量 final String glorze = "高老四博客"; final double gaolaosi; gaolaosi = 888.888; // final修饰对象的引用,引用不变,值可以变化 final int gls[] = {8888,8,88,888}; Arrays.sort(gls); System.out.println(Arrays.toString(gls)); gls[2] = 666; System.out.println(Arrays.toString(gls)); } } |
这里在补一些关于final变量”宏替换”的相关知识点,其实”final int glorze = 8;”这句话对于程序来说变量glorze是根本不存在的,这里面final相当于定义了一个宏变量”a”,用这个”a”来代替5,这种宏变量还包含基本的算术表达式以及基本的字符串连接。我们都知道字符串: “高老四博客” = “高老四” + “博客”其实是一回事,他们都直接引用String常量池中的”高老四博客”,然而
1 2 3 4 5 |
String s1 = "高老四博客"; String s2 = "高老四"; String s3 = "博客"; String s4 = s2 + s3; System.out.println(s1 == s4); |
我们知道,因为无法在编译的时候确定s4的值,所以s4无法指向常量池中的”高老四博客”,所以结果为false。当给s2和s3加上final修饰符来达到宏替换的目的,这样的话你就会看到输出的结果是true了。如果不太懂的话,可以好好研究一下。
2.final修饰方法
final修饰的方法不可以被重写,但是可以重载。最典型的例子就是Object中的getClass方法就是final修饰的。所以如果父类有final的方法,而子类也存在,那并不是重写,而是属于子类自己定义的,跟爷爷长得像而已。
3.final修饰类
final修饰的类不能有子类,Java.lang.Math就是活生生的例子。 我们知道Java的8个包装类是不可变类(创建该类的实例后,该实例的实例变量是不可改变的),他们底层都使用了final,同样我们自定义不可变的类需要哪些条件呢?
- 使用private和final修饰符来修饰该类的成员变量。
- 提供带参数的构造器,根据入参初始化成员变量。
- 只提供getter方法
- [可选]重写hashCode()和equals()方法。
然而,我们所说的不可变的类,根据前面所讲的,只是引用地址不变,值或者类型都是可变的,那么如何保证真正意义上的不可变呢?这里不做细讲,给一些提示: 构造方法中直接使用目标对象作为入参,使用目标对象的get方法实例化目标对象。。。(你就当没看见这句话。。。)
关于final修饰类,这里在引申一些东西,那就是缓存实例的不可变类。相信你们搞Java的都见过这段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package comglorze.finaltest; /** * Integer包装类缓存对象 * @ClassName IntegerCacheTest * @author: glorze.com * @since: 2018年4月19日 上午12:36:40 */ public class IntegerCacheTest { public static void main(String[] args) { // 新对象 Integer glorze1 = new Integer(88); // 新对象并且缓存了起来 Integer glorze2 = Integer.valueOf(88); // 直接读取缓存数据 Integer glorze3 = Integer.valueOf(88); // false System.out.println(glorze1 == glorze2); // true System.out.println(glorze2 == glorze3); // Integer只缓存-128~127之间的值 Integer glorze4 = Integer.valueOf(188); Integer glorze5 = Integer.valueOf(188); // false System.out.println(glorze4 == glorze5); } } |
其实这里面就涉及到了不可变类的缓存技巧,底层也是使用final定义的不可变类实现缓存池利用先进先出的规则对数据进行处理,有兴趣也可以了解一下实现原理。
好了,基本的只是就算是说完了,告一段了,接下来在说为什么”static final”经常连用之前写几个关于final修饰经常被提及到的面试/笔试题,先当做基础知识的复习和标记。
1.final, finally, finalize的区别?
就特么因为长得像就放在一起问,也不知道哪个创始人出这种问题,不过咱们得受着,毕竟可以复习到知识,也能获取到相关修饰符的知识点。回答一下这三个词基本干啥的就行了,反正他们彼此互斥,没什么Jimmy Butler关系。。。
final就不说了,简单说一下finally和finalize:
- finally: 异常块的一部分,不管什么情况里面的代码块都会执行,包括try catch 里面用了return ,可以理解为只要执行了try或者catch,就一定会执行 finally
- finalize: Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。JVM不保证此方法总被调用。
2.final关键字有哪些用法?
仔细看文章。
3.final与static 关键字可以用于哪里?它们的作用是什么?
仔细看这篇文章以及文章开篇的链接提到的关于static的文章。
4.能否在运行时向 static final 类型的赋值?
有的能,有的不能。至于为啥?仔细看文章!
5.使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?
根据传统的中国封建制度,对象不能变啊。白头到老。
6.一个类被声明为final类型,表示了什么意思?
表明了这个类是final的人了,要动她,先问问铜锣湾final答不答应!!!
所以,static final为什么经常连用呢?
首先说连用的目的:static代表静态,final不允许重新赋值,所以就是说我们需要的是静态常量。那么我们使用静态常量的意义是什么呢?显而易见了,静态常量因为一直存放在静态空间中,因为final的原因不会被释放,一直存在内存当中,当一个常数、字符串或者对象是我们需要一直在程序中为我们所用的时候,就可以考虑使用静态常量,避免内存重复的申请和释放空间。说白了,这么问没有多么高深,只不过是一种情景之下的一种应对策略而已,为优化而考虑。
更博不易,喜欢的老铁有能力烦请赞助盒烟钱,点我去赞助。抱拳。